| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/modules/font_access/font_manager.h" |
| |
| #include <algorithm> |
| |
| #include "base/feature_list.h" |
| #include "third_party/blink/public/common/browser_interface_broker_proxy.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/font_access/font_enumeration_table.pb.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" |
| #include "third_party/blink/renderer/bindings/modules/v8/v8_query_options.h" |
| #include "third_party/blink/renderer/core/dom/dom_exception.h" |
| #include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/modules/font_access/font_metadata.h" |
| #include "third_party/blink/renderer/platform/bindings/script_state.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| |
| namespace blink { |
| |
| using mojom::blink::FontEnumerationStatus; |
| |
| FontManager::FontManager(ExecutionContext* context) |
| : ExecutionContextLifecycleObserver(context) { |
| // Only connect if the feature is enabled. Otherwise, there will |
| // be no service to connect to on the end. |
| if (base::FeatureList::IsEnabled(blink::features::kFontAccess)) { |
| context->GetBrowserInterfaceBroker().GetInterface( |
| remote_manager_.BindNewPipeAndPassReceiver()); |
| remote_manager_.set_disconnect_handler( |
| WTF::Bind(&FontManager::OnDisconnect, WrapWeakPersistent(this))); |
| } |
| } |
| |
| ScriptPromise FontManager::query(ScriptState* script_state, |
| const QueryOptions* options) { |
| DCHECK(options->hasSelect()); |
| auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state); |
| ScriptPromise promise = resolver->Promise(); |
| |
| if (options->persistentAccess() && |
| RuntimeEnabledFeatures::FontAccessPersistentEnabled()) { |
| remote_manager_->EnumerateLocalFonts(WTF::Bind( |
| &FontManager::DidGetEnumerationResponse, WrapWeakPersistent(this), |
| WrapPersistent(resolver), options->select())); |
| return promise; |
| } |
| |
| remote_manager_->ChooseLocalFonts( |
| options->select(), |
| WTF::Bind(&FontManager::DidShowFontChooser, WrapWeakPersistent(this), |
| WrapPersistent(resolver))); |
| |
| return promise; |
| } |
| |
| void FontManager::Trace(blink::Visitor* visitor) const { |
| ScriptWrappable::Trace(visitor); |
| ContextLifecycleObserver::Trace(visitor); |
| } |
| |
| void FontManager::DidShowFontChooser( |
| ScriptPromiseResolver* resolver, |
| FontEnumerationStatus status, |
| Vector<mojom::blink::FontMetadataPtr> fonts) { |
| if (RejectPromiseIfNecessary(status, resolver)) |
| return; |
| |
| auto entries = HeapVector<Member<FontMetadata>>(); |
| for (const auto& font : fonts) { |
| auto entry = FontEnumerationEntry{font->postscript_name, font->full_name, |
| font->family}; |
| entries.push_back(FontMetadata::Create(std::move(entry))); |
| } |
| resolver->Resolve(std::move(entries)); |
| } |
| |
| void FontManager::DidGetEnumerationResponse( |
| ScriptPromiseResolver* resolver, |
| const Vector<String>& selection, |
| FontEnumerationStatus status, |
| base::ReadOnlySharedMemoryRegion region) { |
| if (RejectPromiseIfNecessary(status, resolver)) |
| return; |
| |
| base::ReadOnlySharedMemoryMapping mapping = region.Map(); |
| FontEnumerationTable table; |
| |
| if (mapping.size() > INT_MAX) { |
| // Cannot deserialize without overflow. |
| resolver->Reject(MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kDataError, "Font data exceeds memory limit.")); |
| return; |
| } |
| |
| // Used to compare with data coming from the browser to avoid conversions. |
| std::set<std::string> selection_utf8; |
| for (const String& postscriptName : selection) { |
| // While postscript names are encoded in a subset of ASCII, we convert the |
| // input into UTF8. This will still allow exact matches to occur. |
| selection_utf8.insert(postscriptName.Utf8()); |
| } |
| |
| HeapVector<Member<FontMetadata>> entries; |
| table.ParseFromArray(mapping.memory(), static_cast<int>(mapping.size())); |
| for (const auto& element : table.fonts()) { |
| // If the selection list contains items, only allow items that match. |
| if (!selection_utf8.empty() && |
| selection_utf8.find(element.postscript_name().c_str()) == |
| selection_utf8.end()) |
| continue; |
| |
| auto entry = FontEnumerationEntry{ |
| String::FromUTF8(element.postscript_name().c_str()), |
| String::FromUTF8(element.full_name().c_str()), |
| String::FromUTF8(element.family().c_str()), |
| String::FromUTF8(element.style().c_str())}; |
| entries.push_back(FontMetadata::Create(std::move(entry))); |
| } |
| |
| resolver->Resolve(std::move(entries)); |
| } |
| |
| bool FontManager::RejectPromiseIfNecessary(const FontEnumerationStatus& status, |
| ScriptPromiseResolver* resolver) { |
| switch (status) { |
| case FontEnumerationStatus::kOk: |
| break; |
| case FontEnumerationStatus::kUnimplemented: |
| resolver->Reject(MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kNotSupportedError, |
| "Not yet supported on this platform.")); |
| return true; |
| case FontEnumerationStatus::kCanceled: |
| resolver->Reject(MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kAbortError, "The user canceled the operation.")); |
| return true; |
| case FontEnumerationStatus::kNeedsUserActivation: |
| resolver->Reject(MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kSecurityError, "User activation is required.")); |
| return true; |
| case FontEnumerationStatus::kNotVisible: |
| resolver->Reject(MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kSecurityError, "Page needs to be visible.")); |
| return true; |
| case FontEnumerationStatus::kPermissionDenied: |
| resolver->Reject(MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kNotAllowedError, "Permission not granted.")); |
| return true; |
| case FontEnumerationStatus::kUnexpectedError: |
| default: |
| resolver->Reject(MakeGarbageCollected<DOMException>( |
| DOMExceptionCode::kUnknownError, "An unexpected error occured.")); |
| return true; |
| } |
| return false; |
| } |
| |
| void FontManager::ContextDestroyed() { |
| remote_manager_.reset(); |
| } |
| |
| void FontManager::OnDisconnect() { |
| remote_manager_.reset(); |
| } |
| |
| } // namespace blink |