Commit d5eb905e authored by Olivier Yiptong's avatar Olivier Yiptong Committed by Chromium LUCI CQ

FontAccess: Option bag for filtering enumeration

This introduces an option bag for the enumeration API. The first
capability enabled by this option bag is to apply an exact-match filter
to fonts returned by the API.

Bug: 1164077
Change-Id: Id6e76038c1647859c15e8e0ad6aa711b809466ab
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2622063
Commit-Queue: Olivier Yiptong <oyiptong@chromium.org>
Reviewed-by: default avatarJoshua Bell <jsbell@chromium.org>
Cr-Commit-Position: refs/heads/master@{#843820}
parent 15f44d67
...@@ -19,12 +19,19 @@ ...@@ -19,12 +19,19 @@
namespace blink { namespace blink {
FontIterator::FontIterator(ExecutionContext* context) FontIterator::FontIterator(ExecutionContext* context,
const Vector<String>& selection)
: ExecutionContextLifecycleObserver(context) { : ExecutionContextLifecycleObserver(context) {
context->GetBrowserInterfaceBroker().GetInterface( context->GetBrowserInterfaceBroker().GetInterface(
remote_manager_.BindNewPipeAndPassReceiver()); remote_manager_.BindNewPipeAndPassReceiver());
remote_manager_.set_disconnect_handler( remote_manager_.set_disconnect_handler(
WTF::Bind(&FontIterator::OnDisconnect, WrapWeakPersistent(this))); WTF::Bind(&FontIterator::OnDisconnect, WrapWeakPersistent(this)));
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_.insert(postscriptName.Utf8());
}
} }
ScriptPromise FontIterator::next(ScriptState* script_state) { ScriptPromise FontIterator::next(ScriptState* script_state) {
...@@ -118,6 +125,11 @@ void FontIterator::DidGetEnumerationResponse( ...@@ -118,6 +125,11 @@ void FontIterator::DidGetEnumerationResponse(
table.ParseFromArray(mapping.memory(), static_cast<int>(mapping.size())); table.ParseFromArray(mapping.memory(), static_cast<int>(mapping.size()));
for (const auto& element : table.fonts()) { for (const auto& element : table.fonts()) {
// If the selection list contains items, only allow items that match.
if (!selection_.empty() &&
selection_.find(element.postscript_name().c_str()) == selection_.end())
continue;
auto entry = FontEnumerationEntry{ auto entry = FontEnumerationEntry{
String::FromUTF8(element.postscript_name().c_str()), String::FromUTF8(element.postscript_name().c_str()),
String::FromUTF8(element.full_name().c_str()), String::FromUTF8(element.full_name().c_str()),
......
...@@ -31,7 +31,7 @@ class FontIterator final : public ScriptWrappable, ...@@ -31,7 +31,7 @@ class FontIterator final : public ScriptWrappable,
public: public:
using PermissionStatus = mojom::blink::PermissionStatus; using PermissionStatus = mojom::blink::PermissionStatus;
explicit FontIterator(ExecutionContext* context); FontIterator(ExecutionContext* context, const Vector<String>& filter);
ScriptPromise next(ScriptState*); ScriptPromise next(ScriptState*);
...@@ -49,6 +49,11 @@ class FontIterator final : public ScriptWrappable, ...@@ -49,6 +49,11 @@ class FontIterator final : public ScriptWrappable,
mojo::Remote<mojom::blink::FontAccessManager> remote_manager_; mojo::Remote<mojom::blink::FontAccessManager> remote_manager_;
PermissionStatus permission_status_ = PermissionStatus::ASK; PermissionStatus permission_status_ = PermissionStatus::ASK;
// Used to select from results before they go back to the script.
// We use a std::string here because this is used at the boundary
// between Blink and Chromium and avoids unnecessary conversions.
std::set<std::string> selection_;
}; };
} // namespace blink } // namespace blink
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/modules/font_access/font_iterator.h" #include "third_party/blink/renderer/modules/font_access/font_iterator.h"
#include "third_party/blink/renderer/modules/font_access/font_metadata.h" #include "third_party/blink/renderer/modules/font_access/font_metadata.h"
#include "third_party/blink/renderer/modules/font_access/query_options.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h" #include "third_party/blink/renderer/platform/bindings/script_state.h"
namespace blink { namespace blink {
...@@ -43,12 +44,16 @@ FontManager::FontManager(ExecutionContext* context) ...@@ -43,12 +44,16 @@ FontManager::FontManager(ExecutionContext* context)
} }
ScriptValue FontManager::query(ScriptState* script_state, ScriptValue FontManager::query(ScriptState* script_state,
const QueryOptions* options,
ExceptionState& exception_state) { ExceptionState& exception_state) {
if (exception_state.HadException()) if (exception_state.HadException())
return ScriptValue(); return ScriptValue();
auto* iterator = Vector<String> selection =
MakeGarbageCollected<FontIterator>(ExecutionContext::From(script_state)); options->hasSelect() ? options->select() : Vector<String>();
auto* iterator = MakeGarbageCollected<FontIterator>(
ExecutionContext::From(script_state), std::move(selection));
auto* isolate = script_state->GetIsolate(); auto* isolate = script_state->GetIsolate();
auto context = script_state->GetContext(); auto context = script_state->GetContext();
......
...@@ -33,7 +33,7 @@ class FontManager final : public ScriptWrappable, ...@@ -33,7 +33,7 @@ class FontManager final : public ScriptWrappable,
FontManager operator=(const FontManager&) = delete; FontManager operator=(const FontManager&) = delete;
// FontManager IDL interface implementation. // FontManager IDL interface implementation.
ScriptValue query(ScriptState*, ExceptionState&); ScriptValue query(ScriptState*, const QueryOptions* options, ExceptionState&);
ScriptPromise showFontChooser(ScriptState*, const QueryOptions* options); ScriptPromise showFontChooser(ScriptState*, const QueryOptions* options);
void Trace(blink::Visitor*) const override; void Trace(blink::Visitor*) const override;
......
...@@ -8,6 +8,6 @@ ...@@ -8,6 +8,6 @@
SecureContext, SecureContext,
RuntimeEnabled=FontAccess RuntimeEnabled=FontAccess
] interface FontManager { ] interface FontManager {
[CallWith=ScriptState, RaisesException, Measure] object query(); [CallWith=ScriptState, RaisesException, Measure] object query(optional QueryOptions options = {});
[CallWith=ScriptState, RuntimeEnabled=FontAccessChooser] Promise<sequence<FontMetadata>> showFontChooser(optional QueryOptions options = {}); [CallWith=ScriptState, RuntimeEnabled=FontAccessChooser] Promise<sequence<FontMetadata>> showFontChooser(optional QueryOptions options = {});
}; };
...@@ -4,6 +4,5 @@ ...@@ -4,6 +4,5 @@
// https://wicg.github.io/local-font-access/ // https://wicg.github.io/local-font-access/
dictionary QueryOptions { dictionary QueryOptions {
boolean all = false; sequence<DOMString> select;
sequence<ByteString> fonts;
}; };
...@@ -351,6 +351,24 @@ function assert_fonts_exist(availableFonts, expectedFonts) { ...@@ -351,6 +351,24 @@ function assert_fonts_exist(availableFonts, expectedFonts) {
`Missing Families: ${setToString(familySet)}.`); `Missing Families: ${setToString(familySet)}.`);
} }
function assert_postscript_name_exists(
availableFonts, postscriptNameSelection) {
const postscriptNameSet = new Set(postscriptNameSelection);
const foundFonts = [];
for (const f of availableFonts) {
if (postscriptNameSet.has(f.postscriptName)) {
foundFonts.push(f.postscriptName);
}
}
assert_equals(
foundFonts.length, postscriptNameSelection.length,
`Expected to only find selected fonts ${
JSON.stringify(postscriptNameSelection)}. Instead found: ${
JSON.stringify(foundFonts)}`);
}
function assert_font_has_tables(name, tables, expectedTables) { function assert_font_has_tables(name, tables, expectedTables) {
for (const t of expectedTables) { for (const t of expectedTables) {
assert_equals(t.length, 4, assert_equals(t.length, 4,
......
'use strict'; 'use strict';
font_access_test(async t => { const standard_fonts_tests = [
const iterator = navigator.fonts.query(); null,
undefined,
{},
{select: []},
];
if (!isPlatformSupported()) { for (const test of standard_fonts_tests) {
await promise_rejects_dom(t, 'NotSupportedError', (async () => { const inputAsString = JSON.stringify(test) ? JSON.stringify(test) : test;
for await (const f of iterator) {
}
})());
return;
}
assert_equals(typeof iterator, 'object', 'query() should return an Object'); font_access_test(async t => {
assert_true(!!iterator[Symbol.asyncIterator], if (!isPlatformSupported()) {
'query() has an asyncIterator method'); await promise_rejects_dom(
t, 'NotSupportedError', (async () => {
for await (const f of navigator.fonts.query()) {
}
})());
return;
}
const availableFonts = []; const iterator = navigator.fonts.query();
for await (const f of iterator) {
availableFonts.push(f); assert_equals(typeof iterator, 'object', 'query() should return an Object');
} assert_true(
!!iterator[Symbol.asyncIterator],
'query() has an asyncIterator method');
const availableFonts = [];
for await (const f of iterator) {
availableFonts.push(f);
}
assert_fonts_exist(availableFonts, getEnumerationTestSet()); assert_fonts_exist(availableFonts, getEnumerationTestSet());
}, 'query(): standard fonts returned'); }, `query(): standard fonts returned for input: ${inputAsString}`);
}
font_access_test(async t => { font_access_test(async t => {
const iterator = navigator.fonts.query(); const iterator = navigator.fonts.query();
...@@ -51,3 +64,65 @@ font_access_test(async t => { ...@@ -51,3 +64,65 @@ font_access_test(async t => {
previousFont = font; previousFont = font;
} }
}, 'query(): fonts are sorted'); }, 'query(): fonts are sorted');
font_access_test(async t => {
if (!isPlatformSupported()) {
await promise_rejects_dom(t, 'NotSupportedError', (async () => {
for await (const f of navigator.fonts.query()) {
}
})());
return;
}
const test = {select: [getEnumerationTestSet()[0].postscriptName]};
const iterator = navigator.fonts.query(test);
const fonts = [];
for await (const f of iterator) {
fonts.push(f);
}
assert_postscript_name_exists(fonts, test.select);
assert_equals(
fonts.length, test.select.length,
'The result length should match the test length.');
}, 'query(): fonts are selected for input');
const non_ascii_input = [
{select: ['¥']},
{select: ['ß']},
{select: ['🎵']},
// UTF-16LE, encodes to the same first four bytes as "Ahem" in ASCII.
{select: ['\u6841\u6d65']},
// U+6C34 CJK UNIFIED IDEOGRAPH (water)
{select: ['\u6C34']},
// U+1D11E MUSICAL SYMBOL G-CLEF (UTF-16 surrogate pair)
{select: ['\uD834\uDD1E']},
// U+FFFD REPLACEMENT CHARACTER
{select: ['\uFFFD']},
// UTF-16 surrogate lead
{select: ['\uD800']},
// UTF-16 surrogate trail
{select: ['\uDC00']},
];
for (const test of non_ascii_input) {
font_access_test(async t => {
if (!isPlatformSupported()) {
await promise_rejects_dom(
t, 'NotSupportedError', (async () => {
for await (const f of navigator.fonts.query()) {
}
})());
return;
}
const fonts = [];
const iterator = navigator.fonts.query(test);
for await (const f of iterator) {
fonts.push(f);
}
assert_equals(
fonts.length, 0,
`There should be no results. Instead got: ${JSON.stringify(fonts)}`);
}, `query(): No match for input: ${JSON.stringify(test)}`);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment