forked from mirrors/gecko-dev
This turned out to be a huge pain. Many tests didn't work with the Rust backend. Generally I tried to keep tests and tasks that truly need the JS backend, and I skip them when Rust is enabled. I also added checks for `quickSuggestRustEnabled` so that tests don't assume the Rust backend is enabled. I don't want to remove anything related to the JS backend until the Rust backend is shipped in Release and we're confident we don't need the JS backend anymore. Also, browser tests didn't work properly because by the time `QuickSuggestTestUtils.ensureQuickSuggestInit()` runs, the Rust backend has already initialized, and `_test_remoteSettingsConfig` is not defined. To fix that, I added a new `_test_setRemoteSettingsConfig()` function that recreates the store with the new RS test config. I also added some new helpers for generating remote settings data and for making expected results in xpcshell tests. Differential Revision: https://phabricator.services.mozilla.com/D201800
314 lines
9.8 KiB
JavaScript
314 lines
9.8 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
import { BaseFeature } from "resource:///modules/urlbar/private/BaseFeature.sys.mjs";
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
QuickSuggest: "resource:///modules/QuickSuggest.sys.mjs",
|
|
SuggestionsMap: "resource:///modules/urlbar/private/SuggestBackendJs.sys.mjs",
|
|
UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
|
|
UrlbarResult: "resource:///modules/UrlbarResult.sys.mjs",
|
|
UrlbarUtils: "resource:///modules/UrlbarUtils.sys.mjs",
|
|
});
|
|
|
|
const RESULT_MENU_COMMAND = {
|
|
HELP: "help",
|
|
NOT_INTERESTED: "not_interested",
|
|
NOT_RELEVANT: "not_relevant",
|
|
SHOW_LESS_FREQUENTLY: "show_less_frequently",
|
|
};
|
|
|
|
/**
|
|
* A feature that manages Pocket suggestions in remote settings.
|
|
*/
|
|
export class PocketSuggestions extends BaseFeature {
|
|
constructor() {
|
|
super();
|
|
this.#lowConfidenceSuggestionsMap = new lazy.SuggestionsMap();
|
|
this.#highConfidenceSuggestionsMap = new lazy.SuggestionsMap();
|
|
}
|
|
|
|
get shouldEnable() {
|
|
return (
|
|
lazy.UrlbarPrefs.get("pocketFeatureGate") &&
|
|
lazy.UrlbarPrefs.get("suggest.pocket") &&
|
|
lazy.UrlbarPrefs.get("suggest.quicksuggest.nonsponsored")
|
|
);
|
|
}
|
|
|
|
get enablingPreferences() {
|
|
return ["suggest.pocket", "suggest.quicksuggest.nonsponsored"];
|
|
}
|
|
|
|
get merinoProvider() {
|
|
return "pocket";
|
|
}
|
|
|
|
get rustSuggestionTypes() {
|
|
return ["Pocket"];
|
|
}
|
|
|
|
get showLessFrequentlyCount() {
|
|
let count = lazy.UrlbarPrefs.get("pocket.showLessFrequentlyCount") || 0;
|
|
return Math.max(count, 0);
|
|
}
|
|
|
|
get canShowLessFrequently() {
|
|
let cap =
|
|
lazy.UrlbarPrefs.get("pocketShowLessFrequentlyCap") ||
|
|
lazy.QuickSuggest.backend.config?.showLessFrequentlyCap ||
|
|
0;
|
|
return !cap || this.showLessFrequentlyCount < cap;
|
|
}
|
|
|
|
enable(enabled) {
|
|
if (enabled) {
|
|
lazy.QuickSuggest.jsBackend.register(this);
|
|
} else {
|
|
lazy.QuickSuggest.jsBackend.unregister(this);
|
|
this.#lowConfidenceSuggestionsMap.clear();
|
|
this.#highConfidenceSuggestionsMap.clear();
|
|
}
|
|
}
|
|
|
|
async queryRemoteSettings(searchString) {
|
|
// If the search string matches high confidence suggestions, they should be
|
|
// treated as top picks. Otherwise try to match low confidence suggestions.
|
|
let is_top_pick = false;
|
|
let suggestions = this.#highConfidenceSuggestionsMap.get(searchString);
|
|
if (suggestions.length) {
|
|
is_top_pick = true;
|
|
} else {
|
|
suggestions = this.#lowConfidenceSuggestionsMap.get(searchString);
|
|
}
|
|
|
|
let lowerSearchString = searchString.toLocaleLowerCase();
|
|
return suggestions.map(suggestion => {
|
|
// Add `full_keyword` to each matched suggestion. It should be the longest
|
|
// keyword that starts with the user's search string.
|
|
let full_keyword = lowerSearchString;
|
|
let keywords = is_top_pick
|
|
? suggestion.highConfidenceKeywords
|
|
: suggestion.lowConfidenceKeywords;
|
|
for (let keyword of keywords) {
|
|
if (
|
|
keyword.startsWith(lowerSearchString) &&
|
|
full_keyword.length < keyword.length
|
|
) {
|
|
full_keyword = keyword;
|
|
}
|
|
}
|
|
return { ...suggestion, is_top_pick, full_keyword };
|
|
});
|
|
}
|
|
|
|
async onRemoteSettingsSync(rs) {
|
|
let records = await rs.get({ filters: { type: "pocket-suggestions" } });
|
|
if (!this.isEnabled) {
|
|
return;
|
|
}
|
|
|
|
let lowMap = new lazy.SuggestionsMap();
|
|
let highMap = new lazy.SuggestionsMap();
|
|
|
|
this.logger.debug(`Got ${records.length} records`);
|
|
for (let record of records) {
|
|
let { buffer } = await rs.attachments.download(record);
|
|
if (!this.isEnabled) {
|
|
return;
|
|
}
|
|
|
|
let suggestions = JSON.parse(new TextDecoder("utf-8").decode(buffer));
|
|
this.logger.debug(`Adding ${suggestions.length} suggestions`);
|
|
|
|
await lowMap.add(suggestions, {
|
|
keywordsProperty: "lowConfidenceKeywords",
|
|
mapKeyword:
|
|
lazy.SuggestionsMap.MAP_KEYWORD_PREFIXES_STARTING_AT_FIRST_WORD,
|
|
});
|
|
if (!this.isEnabled) {
|
|
return;
|
|
}
|
|
|
|
await highMap.add(suggestions, {
|
|
keywordsProperty: "highConfidenceKeywords",
|
|
});
|
|
if (!this.isEnabled) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.#lowConfidenceSuggestionsMap = lowMap;
|
|
this.#highConfidenceSuggestionsMap = highMap;
|
|
}
|
|
|
|
makeResult(queryContext, suggestion, searchString) {
|
|
if (!this.isEnabled) {
|
|
// The feature is disabled on the client, but Merino may still return
|
|
// suggestions anyway, and we filter them out here.
|
|
return null;
|
|
}
|
|
|
|
// If the user hasn't clicked the "Show less frequently" command, the
|
|
// suggestion can be shown. Otherwise, the suggestion can be shown if the
|
|
// user typed more than one word with at least `showLessFrequentlyCount`
|
|
// characters after the first word, including spaces.
|
|
if (this.showLessFrequentlyCount) {
|
|
let spaceIndex = searchString.search(/\s/);
|
|
if (
|
|
spaceIndex < 0 ||
|
|
searchString.length - spaceIndex < this.showLessFrequentlyCount
|
|
) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
if (suggestion.source == "rust") {
|
|
suggestion.is_top_pick = suggestion.isTopPick;
|
|
delete suggestion.isTopPick;
|
|
|
|
// The Rust component doesn't implement these properties. For now we use
|
|
// dummy values. See issue #5878 in application-services.
|
|
suggestion.description = suggestion.title;
|
|
suggestion.full_keyword = searchString;
|
|
}
|
|
|
|
let url = new URL(suggestion.url);
|
|
url.searchParams.set("utm_medium", "firefox-desktop");
|
|
url.searchParams.set("utm_source", "firefox-suggest");
|
|
url.searchParams.set(
|
|
"utm_campaign",
|
|
"pocket-collections-in-the-address-bar"
|
|
);
|
|
url.searchParams.set("utm_content", "treatment");
|
|
|
|
return Object.assign(
|
|
new lazy.UrlbarResult(
|
|
lazy.UrlbarUtils.RESULT_TYPE.URL,
|
|
lazy.UrlbarUtils.RESULT_SOURCE.OTHER_NETWORK,
|
|
...lazy.UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, {
|
|
url: url.href,
|
|
originalUrl: suggestion.url,
|
|
title: [suggestion.title, lazy.UrlbarUtils.HIGHLIGHT.TYPED],
|
|
description: suggestion.is_top_pick ? suggestion.description : "",
|
|
// Use the favicon for non-best matches so the icon exactly matches
|
|
// the Pocket favicon in the user's history and tabs.
|
|
icon: suggestion.is_top_pick
|
|
? "chrome://global/skin/icons/pocket.svg"
|
|
: "chrome://global/skin/icons/pocket-favicon.ico",
|
|
shouldShowUrl: true,
|
|
bottomTextL10n: {
|
|
id: "firefox-suggest-pocket-bottom-text",
|
|
args: {
|
|
keywordSubstringTyped: searchString,
|
|
keywordSubstringNotTyped: suggestion.full_keyword.substring(
|
|
searchString.length
|
|
),
|
|
},
|
|
},
|
|
helpUrl: lazy.QuickSuggest.HELP_URL,
|
|
})
|
|
),
|
|
{
|
|
isRichSuggestion: true,
|
|
richSuggestionIconSize: suggestion.is_top_pick ? 24 : 16,
|
|
showFeedbackMenu: true,
|
|
}
|
|
);
|
|
}
|
|
|
|
handleCommand(view, result, selType) {
|
|
switch (selType) {
|
|
case RESULT_MENU_COMMAND.HELP:
|
|
// "help" is handled by UrlbarInput, no need to do anything here.
|
|
break;
|
|
// selType == "dismiss" when the user presses the dismiss key shortcut.
|
|
case "dismiss":
|
|
case RESULT_MENU_COMMAND.NOT_RELEVANT:
|
|
// PocketSuggestions adds the UTM parameters to the original URL and
|
|
// returns it as payload.url in the result. However, as
|
|
// UrlbarProviderQuickSuggest filters suggestions with original URL of
|
|
// provided suggestions, need to use the original URL when adding to the
|
|
// block list.
|
|
lazy.QuickSuggest.blockedSuggestions.add(result.payload.originalUrl);
|
|
result.acknowledgeDismissalL10n = {
|
|
id: "firefox-suggest-dismissal-acknowledgment-one",
|
|
};
|
|
view.controller.removeResult(result);
|
|
break;
|
|
case RESULT_MENU_COMMAND.NOT_INTERESTED:
|
|
lazy.UrlbarPrefs.set("suggest.pocket", false);
|
|
result.acknowledgeDismissalL10n = {
|
|
id: "firefox-suggest-dismissal-acknowledgment-all",
|
|
};
|
|
view.controller.removeResult(result);
|
|
break;
|
|
case RESULT_MENU_COMMAND.SHOW_LESS_FREQUENTLY:
|
|
view.acknowledgeFeedback(result);
|
|
this.incrementShowLessFrequentlyCount();
|
|
if (!this.canShowLessFrequently) {
|
|
view.invalidateResultMenuCommands();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
getResultCommands(result) {
|
|
let commands = [];
|
|
|
|
if (!result.isBestMatch && this.canShowLessFrequently) {
|
|
commands.push({
|
|
name: RESULT_MENU_COMMAND.SHOW_LESS_FREQUENTLY,
|
|
l10n: {
|
|
id: "firefox-suggest-command-show-less-frequently",
|
|
},
|
|
});
|
|
}
|
|
|
|
commands.push(
|
|
{
|
|
l10n: {
|
|
id: "firefox-suggest-command-dont-show-this",
|
|
},
|
|
children: [
|
|
{
|
|
name: RESULT_MENU_COMMAND.NOT_RELEVANT,
|
|
l10n: {
|
|
id: "firefox-suggest-command-not-relevant",
|
|
},
|
|
},
|
|
{
|
|
name: RESULT_MENU_COMMAND.NOT_INTERESTED,
|
|
l10n: {
|
|
id: "firefox-suggest-command-not-interested",
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{ name: "separator" },
|
|
{
|
|
name: RESULT_MENU_COMMAND.HELP,
|
|
l10n: {
|
|
id: "urlbar-result-menu-learn-more-about-firefox-suggest",
|
|
},
|
|
}
|
|
);
|
|
|
|
return commands;
|
|
}
|
|
|
|
incrementShowLessFrequentlyCount() {
|
|
if (this.canShowLessFrequently) {
|
|
lazy.UrlbarPrefs.set(
|
|
"pocket.showLessFrequentlyCount",
|
|
this.showLessFrequentlyCount + 1
|
|
);
|
|
}
|
|
}
|
|
|
|
#lowConfidenceSuggestionsMap;
|
|
#highConfidenceSuggestionsMap;
|
|
}
|