Bug 1933003 - Add support to use restricted search keywords in both en-US and the localized language. r=mak,fluent-reviewers,settings-reviewers,urlbar-reviewers,mossop
This patch adds English restrict keyword strings to the enUS-searchFeature.ftl file. The restrictKeywords providers return an array of keywords for their l10nRestrictKeywords property. The first is the localized keyword, and the second is the English keyword. If the user is in the English locale, the providers return an array with one element: the English keyword. Differential Revision: https://phabricator.services.mozilla.com/D230009
This commit is contained in:
@@ -57,7 +57,7 @@ class ProviderRestrictKeywords extends UrlbarProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [token, l10nRestrictKeyword] of tokenToKeyword.entries()) {
|
||||
for (const [token, l10nRestrictKeywords] of tokenToKeyword.entries()) {
|
||||
let icon = UrlbarUtils.LOCAL_SEARCH_MODES.find(
|
||||
mode => mode.restrict == token
|
||||
)?.icon;
|
||||
@@ -68,7 +68,10 @@ class ProviderRestrictKeywords extends UrlbarProvider {
|
||||
...lazy.UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
|
||||
icon,
|
||||
keyword: token,
|
||||
l10nRestrictKeyword,
|
||||
l10nRestrictKeywords: [
|
||||
l10nRestrictKeywords,
|
||||
UrlbarUtils.HIGHLIGHT.TYPED,
|
||||
],
|
||||
providesSearchMode: true,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -27,7 +27,7 @@ const RESTRICT_KEYWORDS_FEATURE_GATE = "searchRestrictKeywords.featureGate";
|
||||
*/
|
||||
class ProviderRestrictKeywordsAutofill extends UrlbarProvider {
|
||||
#autofillData;
|
||||
#lowerCaseTokenToKeyword;
|
||||
#lowerCaseTokenToKeywords;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -45,24 +45,23 @@ class ProviderRestrictKeywordsAutofill extends UrlbarProvider {
|
||||
return 1;
|
||||
}
|
||||
|
||||
async #getLowerCaseTokenToKeyword() {
|
||||
if (!this.#lowerCaseTokenToKeyword) {
|
||||
let tokenToKeyword = await lazy.UrlbarTokenizer.getL10nRestrictKeywords();
|
||||
this.#lowerCaseTokenToKeyword = new Map(
|
||||
[...tokenToKeyword].map(([token, keyword]) => [
|
||||
token,
|
||||
keyword.toLowerCase(),
|
||||
])
|
||||
);
|
||||
}
|
||||
async #getLowerCaseTokenToKeywords() {
|
||||
let tokenToKeywords = await lazy.UrlbarTokenizer.getL10nRestrictKeywords();
|
||||
|
||||
return this.#lowerCaseTokenToKeyword;
|
||||
this.#lowerCaseTokenToKeywords = new Map(
|
||||
[...tokenToKeywords].map(([token, keywords]) => [
|
||||
token,
|
||||
keywords.map(keyword => keyword.toLowerCase()),
|
||||
])
|
||||
);
|
||||
|
||||
return this.#lowerCaseTokenToKeywords;
|
||||
}
|
||||
|
||||
async #getKeywordAliases() {
|
||||
return Array.from(await this.#lowerCaseTokenToKeyword.values()).map(
|
||||
keyword => "@" + keyword
|
||||
);
|
||||
return Array.from(await this.#lowerCaseTokenToKeywords.values())
|
||||
.flat()
|
||||
.map(keyword => "@" + keyword);
|
||||
}
|
||||
|
||||
async isActive(queryContext) {
|
||||
@@ -120,28 +119,24 @@ class ProviderRestrictKeywordsAutofill extends UrlbarProvider {
|
||||
let typedKeyword = queryContext.lowerCaseSearchString;
|
||||
let typedKeywordTrimmed =
|
||||
queryContext.trimmedLowerCaseSearchString.substring(1);
|
||||
let tokenToKeyword = await this.#getLowerCaseTokenToKeyword();
|
||||
let tokenToKeywords = await this.#getLowerCaseTokenToKeywords();
|
||||
|
||||
if (instance != this.queryInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
let keywords = Array.from(tokenToKeyword.values());
|
||||
let doesKeywordExists = keywords
|
||||
.map(keyword => keyword)
|
||||
.includes(typedKeywordTrimmed);
|
||||
|
||||
let restrictSymbol;
|
||||
let aliasKeyword;
|
||||
for (let [token, keyword] of tokenToKeyword) {
|
||||
if (keyword == typedKeywordTrimmed) {
|
||||
|
||||
for (let [token, keywords] of tokenToKeywords) {
|
||||
if (keywords.includes(typedKeywordTrimmed)) {
|
||||
restrictSymbol = token;
|
||||
aliasKeyword = "@" + keyword + " ";
|
||||
aliasKeyword = "@" + typedKeywordTrimmed + " ";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (doesKeywordExists && typedKeyword == aliasKeyword) {
|
||||
if (restrictSymbol && typedKeyword == aliasKeyword) {
|
||||
let result = new lazy.UrlbarResult(
|
||||
UrlbarUtils.RESULT_TYPE.RESTRICT,
|
||||
UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL,
|
||||
@@ -164,14 +159,17 @@ class ProviderRestrictKeywordsAutofill extends UrlbarProvider {
|
||||
}
|
||||
|
||||
async #getAutofillResult(queryContext) {
|
||||
let tokenToKeyword = await this.#getLowerCaseTokenToKeyword();
|
||||
let tokenToKeywords = await this.#getLowerCaseTokenToKeywords();
|
||||
let { lowerCaseSearchString } = queryContext;
|
||||
|
||||
for (let [token, l10nRestrictKeyword] of tokenToKeyword.entries()) {
|
||||
let autofillKeyword = `@${l10nRestrictKeyword}`;
|
||||
for (let [token, l10nRestrictKeywords] of tokenToKeywords.entries()) {
|
||||
let keywords = [...l10nRestrictKeywords].map(keyword => `@${keyword}`);
|
||||
let autofillKeyword = keywords.find(keyword =>
|
||||
keyword.startsWith(lowerCaseSearchString)
|
||||
);
|
||||
|
||||
// found the keyword
|
||||
if (autofillKeyword.startsWith(lowerCaseSearchString)) {
|
||||
if (autofillKeyword) {
|
||||
// Add an autofill result. Append a space so the user can hit enter
|
||||
// or the right arrow key and immediately start typing their query.
|
||||
let keywordPreservingUserCase =
|
||||
@@ -188,7 +186,10 @@ class ProviderRestrictKeywordsAutofill extends UrlbarProvider {
|
||||
...lazy.UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
|
||||
icon,
|
||||
keyword: token,
|
||||
l10nRestrictKeyword,
|
||||
l10nRestrictKeywords: [
|
||||
l10nRestrictKeywords,
|
||||
UrlbarUtils.HIGHLIGHT.TYPED,
|
||||
],
|
||||
autofillKeyword: [
|
||||
keywordPreservingUserCase,
|
||||
UrlbarUtils.HIGHLIGHT.TYPED,
|
||||
|
||||
@@ -25,10 +25,14 @@ ChromeUtils.defineLazyGetter(lazy, "gFluentStrings", function () {
|
||||
|
||||
/*
|
||||
* This Map stores key-value pairs where each key is a restrict token
|
||||
* and each value is a corresponding localized restrict keyword.
|
||||
* E.g. "*" maps to "Bookmarks"
|
||||
* and each value is an array containing the localized keyword and the
|
||||
* english keyword.
|
||||
*
|
||||
* For example,
|
||||
* "*" maps to "Bookmarks" for english locales
|
||||
* "*" maps to "Marcadores, Bookmarks" for es-ES
|
||||
*/
|
||||
let tokenToKeyword = new Map();
|
||||
let tokenToKeywords = new Map();
|
||||
|
||||
export var UrlbarTokenizer = {
|
||||
// Regex matching on whitespaces.
|
||||
@@ -110,8 +114,23 @@ export var UrlbarTokenizer = {
|
||||
})
|
||||
);
|
||||
|
||||
let englishSearchStrings = new Localization([
|
||||
"preview/enUS-searchFeatures.ftl",
|
||||
]);
|
||||
|
||||
let englishKeywords = await englishSearchStrings.formatValues(
|
||||
lazy.UrlbarUtils.LOCAL_SEARCH_MODES.map(mode => {
|
||||
let name = lazy.UrlbarUtils.getResultSourceName(mode.source);
|
||||
return { id: `urlbar-search-mode-${name}-en` };
|
||||
})
|
||||
);
|
||||
|
||||
for (let { restrict } of lazy.UrlbarUtils.LOCAL_SEARCH_MODES) {
|
||||
tokenToKeyword.set(restrict, l10nKeywords.shift());
|
||||
let uniqueKeywords = [
|
||||
...new Set([l10nKeywords.shift(), englishKeywords.shift()]),
|
||||
];
|
||||
|
||||
tokenToKeywords.set(restrict, uniqueKeywords);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -119,14 +138,14 @@ export var UrlbarTokenizer = {
|
||||
* Gets the cached localized restrict keywords. If keywords are not cached
|
||||
* fetch the localized keywords first and then return the keywords.
|
||||
*
|
||||
* @returns {Map} The tokenToKeyword Map.
|
||||
* @returns {Map} The tokenToKeywords Map.
|
||||
*/
|
||||
async getL10nRestrictKeywords() {
|
||||
if (tokenToKeyword.size === 0) {
|
||||
if (tokenToKeywords.size === 0) {
|
||||
await this.loadL10nRestrictKeywords();
|
||||
}
|
||||
|
||||
return tokenToKeyword;
|
||||
return tokenToKeywords;
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -2231,8 +2231,11 @@ UrlbarUtils.RESULT_PAYLOAD_SCHEMA = {
|
||||
keyword: {
|
||||
type: "string",
|
||||
},
|
||||
l10nRestrictKeyword: {
|
||||
type: "string",
|
||||
l10nRestrictKeywords: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
autofillKeyword: {
|
||||
type: "string",
|
||||
|
||||
@@ -2744,12 +2744,17 @@ export class UrlbarView {
|
||||
|
||||
if (result.payload.providesSearchMode) {
|
||||
if (result.type == lazy.UrlbarUtils.RESULT_TYPE.RESTRICT) {
|
||||
let keywords = result.payload.l10nRestrictKeyword;
|
||||
let localSearchMode =
|
||||
result.payload.l10nRestrictKeywords[0].toLowerCase();
|
||||
let keywords = result.payload.l10nRestrictKeywords
|
||||
.map(keyword => `@${keyword.toLowerCase()}`)
|
||||
.join(", ");
|
||||
|
||||
this.#setElementL10n(titleNode, {
|
||||
id: "urlbar-result-search-with-local-search-mode",
|
||||
args: {
|
||||
keywords: `@${keywords.toLowerCase()}`,
|
||||
localSearchMode: keywords,
|
||||
keywords,
|
||||
localSearchMode,
|
||||
},
|
||||
});
|
||||
} else if (
|
||||
|
||||
@@ -400,3 +400,10 @@ urlbar-firefox-suggest-contextual-opt-in-description-2 =
|
||||
<a data-l10n-name="learn-more-link">Learn more</a>
|
||||
urlbar-firefox-suggest-contextual-opt-in-allow = Allow suggestions
|
||||
urlbar-firefox-suggest-contextual-opt-in-dismiss = Not now
|
||||
|
||||
## Local search mode indicator labels in the urlbar
|
||||
|
||||
urlbar-search-mode-bookmarks-en = Bookmarks
|
||||
urlbar-search-mode-tabs-en = Tabs
|
||||
urlbar-search-mode-history-en = History
|
||||
urlbar-search-mode-actions-en = Actions
|
||||
|
||||
@@ -426,6 +426,8 @@ https_first_disabled = true
|
||||
|
||||
["browser_restrict_keywords_autofill.js"]
|
||||
|
||||
["browser_restrict_keywords_autofill_locales.js"]
|
||||
|
||||
["browser_resultSpan.js"]
|
||||
|
||||
["browser_result_menu.js"]
|
||||
|
||||
@@ -11,10 +11,21 @@ ChromeUtils.defineESModuleGetters(this, {
|
||||
UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.sys.mjs",
|
||||
});
|
||||
|
||||
const RESTRICT_TOKENS = [
|
||||
UrlbarTokenizer.RESTRICT.HISTORY,
|
||||
UrlbarTokenizer.RESTRICT.BOOKMARK,
|
||||
UrlbarTokenizer.RESTRICT.OPENPAGE,
|
||||
UrlbarTokenizer.RESTRICT.ACTION,
|
||||
];
|
||||
|
||||
add_setup(async function () {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["browser.urlbar.searchRestrictKeywords.featureGate", true]],
|
||||
});
|
||||
|
||||
registerCleanupFunction(async function () {
|
||||
sinon.restore();
|
||||
});
|
||||
});
|
||||
|
||||
async function getRestrictKeywordResult(window, restrictToken) {
|
||||
@@ -29,14 +40,20 @@ async function getRestrictKeywordResult(window, restrictToken) {
|
||||
|
||||
for (let index = 0; !restrictResult && index < resultCount; index++) {
|
||||
let details = await UrlbarTestUtils.getDetailsOfResultAt(window, index);
|
||||
let category = details.result.payload.l10nRestrictKeyword;
|
||||
let keyword = `@${category?.toLowerCase()}`;
|
||||
if (details.result.type != UrlbarUtils.RESULT_TYPE.RESTRICT) {
|
||||
continue;
|
||||
}
|
||||
let l10nRestrictKeywords = details.result.payload.l10nRestrictKeywords;
|
||||
let localSearchMode = l10nRestrictKeywords[0].toLowerCase();
|
||||
let keywords = l10nRestrictKeywords
|
||||
.map(keyword => `@${keyword.toLowerCase()}`)
|
||||
.join(", ");
|
||||
let symbol = details.result.payload.keyword;
|
||||
|
||||
if (symbol == restrictToken) {
|
||||
Assert.equal(
|
||||
details.displayed.title,
|
||||
`${keyword} - Search ${category}`,
|
||||
`${keywords} - Search ${localSearchMode}`,
|
||||
"The result's title is set correctly."
|
||||
);
|
||||
|
||||
@@ -79,11 +96,14 @@ async function assertRestrictKeywordResult(window, restrictToken) {
|
||||
});
|
||||
|
||||
const scalars = TelemetryTestUtils.getProcessScalars("parent", true, true);
|
||||
let category =
|
||||
restrictResult.result.payload.l10nRestrictKeyword.toLowerCase();
|
||||
let l10nRestrictKeywords = restrictResult.result.payload.l10nRestrictKeywords;
|
||||
let englishLocalSearchMode =
|
||||
l10nRestrictKeywords.length > 1
|
||||
? l10nRestrictKeywords[1].toLowerCase()
|
||||
: l10nRestrictKeywords[0].toLowerCase();
|
||||
TelemetryTestUtils.assertKeyedScalar(
|
||||
scalars,
|
||||
`urlbar.picked.restrict_keyword_${category}`,
|
||||
`urlbar.picked.restrict_keyword_${englishLocalSearchMode}`,
|
||||
resultIndex,
|
||||
1
|
||||
);
|
||||
@@ -92,14 +112,23 @@ async function assertRestrictKeywordResult(window, restrictToken) {
|
||||
}
|
||||
|
||||
add_task(async function test_search_restrict_keyword_results() {
|
||||
const restrictTokens = [
|
||||
UrlbarTokenizer.RESTRICT.HISTORY,
|
||||
UrlbarTokenizer.RESTRICT.BOOKMARK,
|
||||
UrlbarTokenizer.RESTRICT.OPENPAGE,
|
||||
UrlbarTokenizer.RESTRICT.ACTION,
|
||||
];
|
||||
|
||||
for (const restrictToken of restrictTokens) {
|
||||
for (const restrictToken of RESTRICT_TOKENS) {
|
||||
await assertRestrictKeywordResult(window, restrictToken);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_search_restrict_keyword_results_es_en_locales() {
|
||||
let spanishEnglishKeywords = new Map([
|
||||
["*", ["Marcadores", "Bookmarks"]],
|
||||
["%", ["Pesta\xF1as", "Tabs"]],
|
||||
["^", ["Historial", "History"]],
|
||||
[">", ["Acciones", "Actions"]],
|
||||
]);
|
||||
|
||||
let tokenizerStub = sinon.stub(UrlbarTokenizer, "getL10nRestrictKeywords");
|
||||
tokenizerStub.resolves(spanishEnglishKeywords);
|
||||
|
||||
for (const restrictToken of RESTRICT_TOKENS) {
|
||||
await assertRestrictKeywordResult(window, restrictToken);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests autofill functionality for restrict keywords (@tabs, @bookmarks,
|
||||
// @history, @actions) in localized language and in english. The autofill
|
||||
// is tested by typing the full or partial keyword to enter search mode.
|
||||
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.defineESModuleGetters(this, {
|
||||
UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.sys.mjs",
|
||||
});
|
||||
|
||||
let gFluentStrings = new Localization(["browser/browser.ftl"]);
|
||||
|
||||
add_setup(async function () {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["browser.urlbar.searchRestrictKeywords.featureGate", true]],
|
||||
});
|
||||
|
||||
let italianEnglishKeywords = new Map([
|
||||
["^", ["Cronologia", "History"]],
|
||||
["*", ["Segnalibri", "Bookmarks"]],
|
||||
["%", ["Schede", "Tabs"]],
|
||||
[">", ["Azioni", "Actions"]],
|
||||
]);
|
||||
|
||||
let tokenizerStub = sinon.stub(UrlbarTokenizer, "getL10nRestrictKeywords");
|
||||
tokenizerStub.resolves(italianEnglishKeywords);
|
||||
|
||||
registerCleanupFunction(async function () {
|
||||
sinon.restore();
|
||||
});
|
||||
});
|
||||
|
||||
async function assertAutofill(
|
||||
searchString,
|
||||
searchMode,
|
||||
entry,
|
||||
userEventAction
|
||||
) {
|
||||
await UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||
window,
|
||||
value: searchString,
|
||||
});
|
||||
|
||||
if (userEventAction) {
|
||||
userEventAction();
|
||||
}
|
||||
await UrlbarTestUtils.assertSearchMode(window, {
|
||||
...searchMode,
|
||||
entry,
|
||||
restrictType: "keyword",
|
||||
});
|
||||
|
||||
await UrlbarTestUtils.exitSearchMode(window);
|
||||
}
|
||||
|
||||
async function getSearchModeKeywords() {
|
||||
let searchModeKeys = [
|
||||
"urlbar-search-mode-bookmarks",
|
||||
"urlbar-search-mode-tabs",
|
||||
"urlbar-search-mode-history",
|
||||
"urlbar-search-mode-actions",
|
||||
];
|
||||
|
||||
let [bookmarks, tabs, history, actions] = await Promise.all(
|
||||
searchModeKeys.map(key =>
|
||||
gFluentStrings.formatValue(key).then(str => `@${str.toLowerCase()}`)
|
||||
)
|
||||
);
|
||||
|
||||
return [bookmarks, tabs, history, actions];
|
||||
}
|
||||
|
||||
add_task(async function test_autofill_enters_search_mode_it_en_locales() {
|
||||
let [bookmarks, tabs, history, actions] = await getSearchModeKeywords();
|
||||
|
||||
const keywordsToToken = new Map([
|
||||
[["@cronologia", history], UrlbarTokenizer.RESTRICT.HISTORY],
|
||||
[["@segnalibri", bookmarks], UrlbarTokenizer.RESTRICT.BOOKMARK],
|
||||
[["@schede", tabs], UrlbarTokenizer.RESTRICT.OPENPAGE],
|
||||
[["@azioni", actions], UrlbarTokenizer.RESTRICT.ACTION],
|
||||
]);
|
||||
|
||||
for (const [keywords, token] of keywordsToToken) {
|
||||
let searchMode = UrlbarUtils.searchModeForToken(token);
|
||||
let italianKeyword = `${keywords[0]} `;
|
||||
let englishKeyword = `${keywords[1]} `;
|
||||
|
||||
info("Test full italian keyword");
|
||||
await assertAutofill(italianKeyword, searchMode, "typed", null);
|
||||
|
||||
info("Test partial italian keyword autofill by pressing right arrow");
|
||||
italianKeyword = italianKeyword.slice(0, 3);
|
||||
await assertAutofill(italianKeyword, searchMode, "typed", () =>
|
||||
EventUtils.synthesizeKey("KEY_ArrowRight")
|
||||
);
|
||||
|
||||
info("Test partial italian keyword autofill by pressing enter");
|
||||
await assertAutofill(italianKeyword, searchMode, "keywordoffer", () =>
|
||||
EventUtils.synthesizeKey("KEY_Enter")
|
||||
);
|
||||
|
||||
info("Test full english keyword");
|
||||
await assertAutofill(englishKeyword, searchMode, "typed", null);
|
||||
|
||||
info("Test partial english keyword autofill by pressing right arrow");
|
||||
englishKeyword = englishKeyword.slice(0, 3);
|
||||
await assertAutofill(englishKeyword, searchMode, "typed", () =>
|
||||
EventUtils.synthesizeKey("KEY_ArrowRight")
|
||||
);
|
||||
|
||||
info("Test partial english keyword autofill by pressing enter");
|
||||
await assertAutofill(englishKeyword, searchMode, "keywordoffer", () =>
|
||||
EventUtils.synthesizeKey("KEY_Enter")
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -424,7 +424,7 @@ add_task(async function clickAndFillAlias() {
|
||||
let details = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
|
||||
|
||||
if (details.result.type == UrlbarUtils.RESULT_TYPE.RESTRICT) {
|
||||
let category = details.result.payload.l10nRestrictKeyword;
|
||||
let category = details.result.payload.l10nRestrictKeywords[0];
|
||||
let keyword = `@${category.toLowerCase()}`;
|
||||
|
||||
Assert.equal(
|
||||
|
||||
@@ -415,7 +415,7 @@ add_task(async function test_keywordoffer_restrict_keyword() {
|
||||
let restrictResult = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
|
||||
|
||||
Assert.equal(
|
||||
restrictResult.result.payload.l10nRestrictKeyword,
|
||||
restrictResult.result.payload.l10nRestrictKeywords[0],
|
||||
"bookmarks",
|
||||
"The first result should be restrict bookmarks result with the correct keyword."
|
||||
);
|
||||
|
||||
@@ -292,7 +292,9 @@ async function doRestrictKeywordsTest({ trigger, assert }) {
|
||||
for (let i = 0; i < totalResults; i++) {
|
||||
let details = await UrlbarTestUtils.getDetailsOfResultAt(window, i);
|
||||
let symbol = details.result.payload.keyword;
|
||||
let keyword = details.result.payload.l10nRestrictKeyword?.toLowerCase();
|
||||
let keyword = details.result.payload.l10nRestrictKeywords
|
||||
?.at(0)
|
||||
.toLowerCase();
|
||||
|
||||
if (restrictSymbols.includes(symbol)) {
|
||||
let rowToSelect = await UrlbarTestUtils.waitForAutocompleteResultAt(
|
||||
|
||||
Reference in New Issue
Block a user