diff options
Diffstat (limited to 'browser/components/urlbar')
62 files changed, 1730 insertions, 2061 deletions
diff --git a/browser/components/urlbar/ActionsProvider.sys.mjs b/browser/components/urlbar/ActionsProvider.sys.mjs new file mode 100644 index 0000000000..9cd99969a2 --- /dev/null +++ b/browser/components/urlbar/ActionsProvider.sys.mjs @@ -0,0 +1,110 @@ +/* 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/. */ + +/** + * A provider that matches the urlbar input to built in actions. + */ +export class ActionsProvider { + /** + * Unique name for the provider. + * + * @abstract + */ + get name() { + return "ActionsProviderBase"; + } + + /** + * Whether this provider should be invoked for the given context. + * If this method returns false, the providers manager won't start a query + * with this provider, to save on resources. + * + * @param {UrlbarQueryContext} _queryContext The query context object. + * @returns {boolean} Whether this provider should be invoked for the search. + * @abstract + */ + isActive(_queryContext) { + throw new Error("Not implemented."); + } + + /** + * Query for actions based on the current users input. + * + * @param {UrlbarQueryContext} _queryContext The query context object. + * @param {UrlbarController} _controller The urlbar controller. + * @returns {ActionsResult} + * @abstract + */ + async queryAction(_queryContext, _controller) { + throw new Error("Not implemented."); + } + + /** + * Pick an action. + * + * @param {UrlbarQueryContext} _queryContext The query context object. + * @param {UrlbarController} _controller The urlbar controller. + * @param {DOMElement} _element The element that was selected. + * @abstract + */ + pickAction(_queryContext, _controller, _element) { + throw new Error("Not implemented."); + } +} + +/** + * Class used to create an Actions Result. + */ +export class ActionsResult { + providerName; + + #key; + #l10nId; + #l10nArgs; + #icon; + #dataset; + + /** + * @param {object} options + * An option object. + * @param { string } options.key + * A string key used to distinguish between different actions. + * @param { string } options.l10nId + * The id of the l10n string displayed in the action button. + * @param { string } options.l10nArgs + * Arguments passed to construct the above string + * @param { string } options.icon + * The icon displayed in the button. + * @param {object} options.dataset + * An object of properties we set on the action button that + * can be used to pass data when it is selected. + */ + constructor({ key, l10nId, l10nArgs, icon, dataset }) { + this.#key = key; + this.#l10nId = l10nId; + this.#l10nArgs = l10nArgs; + this.#icon = icon; + this.#dataset = dataset; + } + + get key() { + return this.#key; + } + + get l10nId() { + return this.#l10nId; + } + + get l10nArgs() { + return this.#l10nArgs; + } + + get icon() { + return this.#icon; + } + + get dataset() { + return this.#dataset; + } +} diff --git a/browser/components/urlbar/ActionsProviderContextualSearch.sys.mjs b/browser/components/urlbar/ActionsProviderContextualSearch.sys.mjs new file mode 100644 index 0000000000..58af7c94c5 --- /dev/null +++ b/browser/components/urlbar/ActionsProviderContextualSearch.sys.mjs @@ -0,0 +1,121 @@ +/* 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 { UrlbarUtils } from "resource:///modules/UrlbarUtils.sys.mjs"; + +import { + ActionsProvider, + ActionsResult, +} from "resource:///modules/ActionsProvider.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + OpenSearchEngine: "resource://gre/modules/OpenSearchEngine.sys.mjs", + loadAndParseOpenSearchEngine: + "resource://gre/modules/OpenSearchLoader.sys.mjs", + UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs", + UrlbarSearchUtils: "resource:///modules/UrlbarSearchUtils.sys.mjs", +}); + +const ENABLED_PREF = "contextualSearch.enabled"; + +/** + * A provider that returns an option for using the search engine provided + * by the active view if it utilizes OpenSearch. + */ +class ProviderContextualSearch extends ActionsProvider { + constructor() { + super(); + this.engines = new Map(); + } + + get name() { + return "ActionsProviderContextualSearch"; + } + + isActive(queryContext) { + return ( + queryContext.trimmedSearchString && + lazy.UrlbarPrefs.get(ENABLED_PREF) && + !queryContext.searchMode + ); + } + + async queryAction(queryContext, controller) { + let instance = this.queryInstance; + const hostname = URL.parse(queryContext.currentPage)?.hostname; + + // This happens on about pages, which won't have associated engines + if (!hostname) { + return null; + } + + let engine = await this.fetchEngine(controller); + let icon = engine?.icon || (await engine?.getIconURL?.()); + let defaultEngine = lazy.UrlbarSearchUtils.getDefaultEngine(); + + if ( + !engine || + engine.name === defaultEngine?.name || + instance != this.queryInstance + ) { + return null; + } + + return new ActionsResult({ + key: "contextual-search", + l10nId: "urlbar-result-search-with", + l10nArgs: { engine: engine.name || engine.title }, + icon, + }); + } + + async fetchEngine(controller) { + let browser = controller.browserWindow.gBrowser.selectedBrowser; + let hostname = browser?.currentURI.host; + + if (this.engines.has(hostname)) { + return this.engines.get(hostname); + } + + // Strip www. to allow for partial matches when looking for an engine. + const [host] = UrlbarUtils.stripPrefixAndTrim(hostname, { + stripWww: true, + }); + let engines = await lazy.UrlbarSearchUtils.enginesForDomainPrefix(host, { + matchAllDomainLevels: true, + }); + return engines[0] ?? browser?.engines?.[0]; + } + + async pickAction(queryContext, controller, element) { + // If we have an engine to add, first create a new OpenSearchEngine, then + // get and open a url to execute a search for the term in the url bar. + let engine = await this.fetchEngine(controller); + + if (engine.uri) { + let engineData = await lazy.loadAndParseOpenSearchEngine( + Services.io.newURI(engine.uri) + ); + engine = new lazy.OpenSearchEngine({ engineData }); + engine._setIcon(engine.icon, false); + } + + const [url] = UrlbarUtils.getSearchQueryUrl( + engine, + queryContext.searchString + ); + element.ownerGlobal.gBrowser.fixupAndLoadURIString(url, { + triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(), + }); + element.ownerGlobal.gBrowser.selectedBrowser.focus(); + } + + resetForTesting() { + this.engines = new Map(); + } +} + +export var ActionsProviderContextualSearch = new ProviderContextualSearch(); diff --git a/browser/components/urlbar/ActionsProviderQuickActions.sys.mjs b/browser/components/urlbar/ActionsProviderQuickActions.sys.mjs new file mode 100644 index 0000000000..a693e7686a --- /dev/null +++ b/browser/components/urlbar/ActionsProviderQuickActions.sys.mjs @@ -0,0 +1,152 @@ +/* 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 { + ActionsProvider, + ActionsResult, +} from "resource:///modules/ActionsProvider.sys.mjs"; + +const lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + QuickActionsLoaderDefault: + "resource:///modules/QuickActionsLoaderDefault.sys.mjs", + UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs", +}); + +// These prefs are relative to the `browser.urlbar` branch. +const ENABLED_PREF = "quickactions.enabled"; +const MATCH_IN_PHRASE_PREF = "quickactions.matchInPhrase"; +const MIN_SEARCH_PREF = "quickactions.minimumSearchString"; + +/** + * A provider that matches the urlbar input to built in actions. + */ +class ProviderQuickActions extends ActionsProvider { + get name() { + return "ActionsProviderQuickActions"; + } + + isActive(queryContext) { + return ( + lazy.UrlbarPrefs.get(ENABLED_PREF) && + !queryContext.searchMode && + queryContext.trimmedSearchString.length < 50 && + queryContext.trimmedSearchString.length > + lazy.UrlbarPrefs.get(MIN_SEARCH_PREF) + ); + } + + async queryAction(queryContext) { + await lazy.QuickActionsLoaderDefault.ensureLoaded(); + let input = queryContext.trimmedLowerCaseSearchString; + let results = [...(this.#prefixes.get(input) ?? [])]; + + if (lazy.UrlbarPrefs.get(MATCH_IN_PHRASE_PREF)) { + for (let [keyword, key] of this.#keywords) { + if (input.includes(keyword)) { + results.push(key); + } + } + } + + // Remove invisible actions. + results = results.filter(key => { + const action = this.#actions.get(key); + return action.isVisible?.() ?? true; + }); + + if (!results.length) { + return null; + } + + let action = this.#actions.get(results[0]); + return new ActionsResult({ + key: results[0], + l10nId: action.label, + icon: action.icon, + dataset: { + action: results[0], + inputLength: queryContext.trimmedSearchString.length, + }, + }); + } + + pickAction(_queryContext, _controller, element) { + let action = element.dataset.action; + let inputLength = Math.min(element.dataset.inputLength, 10); + Services.telemetry.keyedScalarAdd( + `quickaction.picked`, + `${action}-${inputLength}`, + 1 + ); + let options = this.#actions.get(action).onPick(); + if (options?.focusContent) { + element.ownerGlobal.gBrowser.selectedBrowser.focus(); + } + } + + /** + * Adds a new QuickAction. + * + * @param {string} key A key to identify this action. + * @param {string} definition An object that describes the action. + */ + addAction(key, definition) { + this.#actions.set(key, definition); + definition.commands.forEach(cmd => this.#keywords.set(cmd, key)); + this.#loopOverPrefixes(definition.commands, prefix => { + let result = this.#prefixes.get(prefix); + if (result) { + if (!result.includes(key)) { + result.push(key); + } + } else { + result = [key]; + } + this.#prefixes.set(prefix, result); + }); + } + + /** + * Removes an action. + * + * @param {string} key A key to identify this action. + */ + removeAction(key) { + let definition = this.#actions.get(key); + this.#actions.delete(key); + definition.commands.forEach(cmd => this.#keywords.delete(cmd)); + this.#loopOverPrefixes(definition.commands, prefix => { + let result = this.#prefixes.get(prefix); + if (result) { + result = result.filter(val => val != key); + } + this.#prefixes.set(prefix, result); + }); + } + + // A map from keywords to an action. + #keywords = new Map(); + + // A map of all prefixes to an array of actions. + #prefixes = new Map(); + + // The actions that have been added. + #actions = new Map(); + + #loopOverPrefixes(commands, fun) { + for (const command of commands) { + // Loop over all the prefixes of the word, ie + // "", "w", "wo", "wor", stopping just before the full + // word itself which will be matched by the whole + // phrase matching. + for (let i = 1; i <= command.length; i++) { + let prefix = command.substring(0, command.length - i); + fun(prefix); + } + } + } +} + +export var ActionsProviderQuickActions = new ProviderQuickActions(); diff --git a/browser/components/urlbar/QuickActionsLoaderDefault.sys.mjs b/browser/components/urlbar/QuickActionsLoaderDefault.sys.mjs index 0ab9c4c83e..9a7b7a6a34 100644 --- a/browser/components/urlbar/QuickActionsLoaderDefault.sys.mjs +++ b/browser/components/urlbar/QuickActionsLoaderDefault.sys.mjs @@ -8,12 +8,10 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs", - ClientEnvironment: "resource://normandy/lib/ClientEnvironment.sys.mjs", DevToolsShim: "chrome://devtools-startup/content/DevToolsShim.sys.mjs", ResetProfile: "resource://gre/modules/ResetProfile.sys.mjs", - UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs", - UrlbarProviderQuickActions: - "resource:///modules/UrlbarProviderQuickActions.sys.mjs", + ActionsProviderQuickActions: + "resource:///modules/ActionsProviderQuickActions.sys.mjs", }); import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; @@ -26,7 +24,6 @@ if (AppConstants.MOZ_UPDATER) { "nsIApplicationUpdateService" ); } - XPCOMUtils.defineLazyPreferenceGetter( lazy, "SCREENSHOT_BROWSER_COMPONENT", @@ -54,7 +51,7 @@ let openUrl = url => { let openAddonsUrl = url => { return () => { let window = lazy.BrowserWindowTracker.getTopWindow(); - window.BrowserOpenAddonsMgr(url, { selectTabByViewId: true }); + window.BrowserAddonUI.openAddonsMgr(url, { selectTabByViewId: true }); }; }; @@ -97,24 +94,22 @@ const DEFAULT_ACTIONS = { }, }, downloads: { - l10nCommands: ["quickactions-cmd-downloads", "quickactions-downloads2"], + l10nCommands: ["quickactions-cmd-downloads"], icon: "chrome://browser/skin/downloads/downloads.svg", label: "quickactions-downloads2", onPick: openUrlFun("about:downloads"), }, extensions: { - l10nCommands: ["quickactions-cmd-extensions", "quickactions-extensions"], + l10nCommands: ["quickactions-cmd-extensions"], icon: "chrome://mozapps/skin/extensions/category-extensions.svg", label: "quickactions-extensions", onPick: openAddonsUrl("addons://list/extension"), }, inspect: { - l10nCommands: ["quickactions-cmd-inspector", "quickactions-inspector2"], + l10nCommands: ["quickactions-cmd-inspector"], icon: "chrome://devtools/skin/images/open-inspector.svg", label: "quickactions-inspector2", - isVisible: () => - lazy.DevToolsShim.isEnabled() || lazy.DevToolsShim.isDevToolsUser(), - isActive: () => { + isVisible: () => { // The inspect action is available if: // 1. DevTools is enabled. // 2. The user can be considered as a DevTools user. @@ -132,18 +127,18 @@ const DEFAULT_ACTIONS = { onPick: openInspector, }, logins: { - l10nCommands: ["quickactions-cmd-logins", "quickactions-logins2"], + l10nCommands: ["quickactions-cmd-logins"], label: "quickactions-logins2", onPick: openUrlFun("about:logins"), }, plugins: { - l10nCommands: ["quickactions-cmd-plugins", "quickactions-plugins"], + l10nCommands: ["quickactions-cmd-plugins"], icon: "chrome://mozapps/skin/extensions/category-extensions.svg", label: "quickactions-plugins", onPick: openAddonsUrl("addons://list/plugin"), }, print: { - l10nCommands: ["quickactions-cmd-print", "quickactions-print2"], + l10nCommands: ["quickactions-cmd-print"], label: "quickactions-print2", icon: "chrome://global/skin/icons/print.svg", onPick: () => { @@ -153,7 +148,7 @@ const DEFAULT_ACTIONS = { }, }, private: { - l10nCommands: ["quickactions-cmd-private", "quickactions-private2"], + l10nCommands: ["quickactions-cmd-private"], label: "quickactions-private2", icon: "chrome://global/skin/icons/indicator-private-browsing.svg", onPick: () => { @@ -163,7 +158,7 @@ const DEFAULT_ACTIONS = { }, }, refresh: { - l10nCommands: ["quickactions-cmd-refresh", "quickactions-refresh"], + l10nCommands: ["quickactions-cmd-refresh"], label: "quickactions-refresh", onPick: () => { lazy.ResetProfile.openConfirmationDialog( @@ -172,7 +167,7 @@ const DEFAULT_ACTIONS = { }, }, restart: { - l10nCommands: ["quickactions-cmd-restart", "quickactions-restart"], + l10nCommands: ["quickactions-cmd-restart"], label: "quickactions-restart", onPick: restartBrowser, }, @@ -197,10 +192,10 @@ const DEFAULT_ACTIONS = { }, }, screenshot: { - l10nCommands: ["quickactions-cmd-screenshot", "quickactions-screenshot3"], + l10nCommands: ["quickactions-cmd-screenshot"], label: "quickactions-screenshot3", icon: "chrome://browser/skin/screenshot.svg", - isActive: () => { + isVisible: () => { return !lazy.BrowserWindowTracker.getTopWindow().gScreenshots.shouldScreenshotsButtonBeDisabled(); }, onPick: () => { @@ -221,21 +216,21 @@ const DEFAULT_ACTIONS = { }, }, settings: { - l10nCommands: ["quickactions-cmd-settings", "quickactions-settings2"], + l10nCommands: ["quickactions-cmd-settings"], icon: "chrome://global/skin/icons/settings.svg", label: "quickactions-settings2", onPick: openUrlFun("about:preferences"), }, themes: { - l10nCommands: ["quickactions-cmd-themes", "quickactions-themes"], + l10nCommands: ["quickactions-cmd-themes"], icon: "chrome://mozapps/skin/extensions/category-extensions.svg", label: "quickactions-themes", onPick: openAddonsUrl("addons://list/theme"), }, update: { - l10nCommands: ["quickactions-cmd-update", "quickactions-update"], + l10nCommands: ["quickactions-cmd-update"], label: "quickactions-update", - isActive: () => { + isVisible: () => { if (!AppConstants.MOZ_UPDATER) { return false; } @@ -246,10 +241,10 @@ const DEFAULT_ACTIONS = { onPick: restartBrowser, }, viewsource: { - l10nCommands: ["quickactions-cmd-viewsource", "quickactions-viewsource2"], + l10nCommands: ["quickactions-cmd-viewsource"], icon: "chrome://global/skin/icons/settings.svg", label: "quickactions-viewsource2", - isActive: () => currentBrowser()?.currentURI.scheme !== "view-source", + isVisible: () => currentBrowser()?.currentURI.scheme !== "view-source", onPick: () => openUrl("view-source:" + currentBrowser().currentURI.spec), }, }; @@ -287,18 +282,6 @@ function restartBrowser() { } } -function random(seed) { - let x = Math.sin(seed) * 10000; - return x - Math.floor(x); -} - -function shuffle(array, seed) { - for (let i = array.length - 1; i > 0; i--) { - const j = Math.floor(random(seed) * (i + 1)); - [array[i], array[j]] = [array[j], array[i]]; - } -} - /** * Loads the default QuickActions. */ @@ -308,18 +291,6 @@ export class QuickActionsLoaderDefault { static async load() { let keys = Object.keys(DEFAULT_ACTIONS); - if (lazy.UrlbarPrefs.get("quickactions.randomOrderActions")) { - // We insert the actions in a random order which means they will be returned - // in a random but consistent order (the order of results for "view" and "views" - // should be the same). - // We use the Nimbus randomizationId as the seed as the order should not change - // for the user between restarts, it should be random between users but a user should - // see actions the same order. - let seed = [...lazy.ClientEnvironment.randomizationId] - .map(x => x.charCodeAt(0)) - .reduce((sum, a) => sum + a, 0); - shuffle(keys, seed); - } for (const key of keys) { let actionData = DEFAULT_ACTIONS[key]; let messages = await lazy.gFluentStrings.formatMessages( @@ -328,7 +299,7 @@ export class QuickActionsLoaderDefault { actionData.commands = messages .map(({ value }) => value.split(",").map(x => x.trim().toLowerCase())) .flat(); - lazy.UrlbarProviderQuickActions.addAction(key, actionData); + lazy.ActionsProviderQuickActions.addAction(key, actionData); } } static async ensureLoaded() { diff --git a/browser/components/urlbar/UrlbarController.sys.mjs b/browser/components/urlbar/UrlbarController.sys.mjs index 7e4d0ff1c5..5172c14943 100644 --- a/browser/components/urlbar/UrlbarController.sys.mjs +++ b/browser/components/urlbar/UrlbarController.sys.mjs @@ -1001,7 +1001,6 @@ class TelemetryEvent { searchWords, searchSource, searchMode, - selectedElement, selIndex, selType, } @@ -1045,11 +1044,7 @@ class TelemetryEvent { currentResults[selIndex], selType ); - const selected_result_subtype = - lazy.UrlbarUtils.searchEngagementTelemetrySubtype( - currentResults[selIndex], - selectedElement - ); + const selected_result_subtype = ""; if (selected_result === "input_field" && !this._controller.view?.isOpen) { numResults = 0; diff --git a/browser/components/urlbar/UrlbarInput.sys.mjs b/browser/components/urlbar/UrlbarInput.sys.mjs index a96e862cff..2ee463dda8 100644 --- a/browser/components/urlbar/UrlbarInput.sys.mjs +++ b/browser/components/urlbar/UrlbarInput.sys.mjs @@ -11,6 +11,7 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { BrowserSearchTelemetry: "resource:///modules/BrowserSearchTelemetry.sys.mjs", BrowserUIUtils: "resource:///modules/BrowserUIUtils.sys.mjs", + BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs", ExtensionSearchHandler: "resource://gre/modules/ExtensionSearchHandler.sys.mjs", ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs", @@ -24,6 +25,7 @@ ChromeUtils.defineESModuleGetters(lazy, { UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs", UrlbarQueryContext: "resource:///modules/UrlbarUtils.sys.mjs", UrlbarProviderOpenTabs: "resource:///modules/UrlbarProviderOpenTabs.sys.mjs", + UrlbarProvidersManager: "resource:///modules/UrlbarProvidersManager.sys.mjs", UrlbarSearchUtils: "resource:///modules/UrlbarSearchUtils.sys.mjs", UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.sys.mjs", UrlbarUtils: "resource:///modules/UrlbarUtils.sys.mjs", @@ -330,8 +332,6 @@ export class UrlbarInput { } setSelectionRange(selectionStart, selectionEnd) { - this.focus(); - let beforeSelect = new CustomEvent("beforeselect", { bubbles: true, cancelable: true, @@ -348,6 +348,32 @@ export class UrlbarInput { this._suppressPrimaryAdjustment = false; } + saveSelectionStateForBrowser(browser) { + let state = this.#browserStates.get(browser); + if (!state) { + state = {}; + this.#browserStates.set(browser, state); + } + state.selection = { + start: this.selectionStart, + end: this.selectionEnd, + // When restoring a URI from an empty value, we don't want to untrim it. + shouldUntrim: this.value && !this._protocolIsTrimmed, + }; + } + + restoreSelectionStateForBrowser(browser) { + // Address bar must be focused to untrim and for selection to make sense. + this.focus(); + let state = this.#browserStates.get(browser); + if (state?.selection) { + if (state.selection.shouldUntrim) { + this.#maybeUntrimUrl(); + } + this.setSelectionRange(state.selection.start, state.selection.end); + } + } + /** * Sets the URI to display in the location bar. * @@ -900,6 +926,15 @@ export class UrlbarInput { if (!result) { return; } + if (element?.dataset.action && element?.dataset.action != "tabswitch") { + this.view.close(); + let provider = lazy.UrlbarProvidersManager.getActionProvider( + element.dataset.providerName + ); + let { queryContext } = this.controller._lastQueryContextWrapper || {}; + provider.pickAction(queryContext, this.controller, element); + return; + } this.pickResult(result, event, element); } @@ -1053,7 +1088,13 @@ export class UrlbarInput { break; } case lazy.UrlbarUtils.RESULT_TYPE.TAB_SWITCH: { - if (this.hasAttribute("action-override")) { + // Behaviour is reversed with SecondaryActions, default behaviour is to navigate + // and button is provided to switch to tab. + if ( + this.hasAttribute("action-override") || + (lazy.UrlbarPrefs.get("secondaryActions.featureGate") && + element?.dataset.action !== "tabswitch") + ) { where = "current"; break; } @@ -2268,22 +2309,32 @@ export class UrlbarInput { * @param {string} val The new value to set. * @param {object} [options] Options for setting. * @param {boolean} [options.allowTrim] Whether the value can be trimmed. + * @param {string} [options.untrimmedValue] Override for this._untrimmedValue. + * @param {boolean} [options.valueIsTyped] Override for this.valueIsTypede. + * * * @returns {string} The set value. */ - _setValue(val, { allowTrim = false } = {}) { + _setValue( + val, + { allowTrim = false, untrimmedValue = null, valueIsTyped = false } = {} + ) { // Don't expose internal about:reader URLs to the user. let originalUrl = lazy.ReaderMode.getOriginalUrlObjectForDisplay(val); if (originalUrl) { val = originalUrl.displaySpec; } - this._untrimmedValue = val; - + this._untrimmedValue = untrimmedValue ?? val; + this._protocolIsTrimmed = false; if (allowTrim) { + let oldVal = val; val = this._trimValue(val); + this._protocolIsTrimmed = + oldVal.startsWith(lazy.BrowserUIUtils.trimURLProtocol) && + !val.startsWith(lazy.BrowserUIUtils.trimURLProtocol); } - this.valueIsTyped = false; + this.valueIsTyped = valueIsTyped; this._resultForCurrentValue = null; this.inputField.value = val; this.formatValue(); @@ -2400,6 +2451,7 @@ export class UrlbarInput { selectionEnd: autofillValue.length, type: this._autofillPlaceholder.type, adaptiveHistoryInput: this._autofillPlaceholder.adaptiveHistoryInput, + untrimmedValue: this._autofillPlaceholder.untrimmedValue, }); } @@ -2725,6 +2777,8 @@ export class UrlbarInput { * @param {string} options.adaptiveHistoryInput * If the autofill type is "adaptive", this is the matching `input` value * from adaptive history. + * @param {string} options.untrimmedValue + * Untrimmed value including a protocol. */ _autofillValue({ value, @@ -2732,10 +2786,11 @@ export class UrlbarInput { selectionEnd, type, adaptiveHistoryInput, + untrimmedValue, }) { // The autofilled value may be a URL that includes a scheme at the // beginning. Do not allow it to be trimmed. - this._setValue(value); + this._setValue(value, { untrimmedValue }); this.inputField.setSelectionRange(selectionStart, selectionEnd); this._autofillPlaceholder = { value, @@ -2743,6 +2798,7 @@ export class UrlbarInput { adaptiveHistoryInput, selectionStart, selectionEnd, + untrimmedValue, }; } @@ -2993,7 +3049,7 @@ export class UrlbarInput { // pressed, open in current tab to allow ctrl-enter to canonize URL. where = "current"; } else { - where = this.window.whereToOpenLink(event, false, false); + where = lazy.BrowserUtils.whereToOpenLink(event, false, false); } if (lazy.UrlbarPrefs.get("openintab")) { if (where == "current") { @@ -3059,7 +3115,12 @@ export class UrlbarInput { // Error check occurs during isClipboardURIValid uri = Services.io.newURI(copyString); - strippedURI = lazy.QueryStringStripper.stripForCopyOrShare(uri); + try { + strippedURI = lazy.QueryStringStripper.stripForCopyOrShare(uri); + } catch (e) { + console.warn(`stripForCopyOrShare: ${e.message}`); + return uri; + } if (strippedURI) { return this.makeURIReadable(strippedURI); @@ -3087,6 +3148,59 @@ export class UrlbarInput { return true; } + /** + * Restores the untrimmed value in the urlbar. + */ + #maybeUntrimUrl() { + // Check if we can untrim the current value. + if ( + !lazy.UrlbarPrefs.get("untrimOnUserInteraction.featureGate") || + !this._protocolIsTrimmed || + !this.focused || + this.#allTextSelected + ) { + return; + } + + let selectionStart = this.selectionStart; + let selectionEnd = this.selectionEnd; + + // Correct the selection taking the trimmed protocol into account. + let offset = lazy.BrowserUIUtils.trimURLProtocol.length; + + // In case of autofill, we may have to adjust its boundaries. + if (this._autofillPlaceholder) { + this._autofillPlaceholder.selectionStart += offset; + this._autofillPlaceholder.selectionEnd += offset; + } + + if (selectionStart == selectionEnd) { + // When cursor is at the end of the string, untrimming may + // reintroduced a trailing slash and we want to move past it. + if (selectionEnd == this.value.length) { + offset += 1; + } + selectionStart = selectionEnd += offset; + } else { + // If there's a selection, we must calculate both the initial + // protocol and the eventual trailing slash. + if (selectionStart != 0) { + selectionStart += offset; + } + if (selectionEnd == this.value.length) { + offset += 1; + } + selectionEnd += offset; + } + + this._setValue(this._untrimmedValue, { + allowTrim: false, + valueIsTyped: this.valueIsTyped, + }); + + this.setSelectionRange(selectionStart, selectionEnd); + } + // The strip-on-share feature will strip known tracking/decorational // query params from the URI and copy the stripped version to the clipboard. _initStripOnShare() { @@ -3331,7 +3445,7 @@ export class UrlbarInput { if ( !this._preventClickSelectsAll && this._compositionState != lazy.UrlbarUtils.COMPOSITION.COMPOSING && - this.document.activeElement == this.inputField && + this.focused && this.inputField.selectionStart == this.inputField.selectionEnd ) { this.select(); @@ -3381,14 +3495,17 @@ export class UrlbarInput { // If we were autofilling, remove the autofilled portion, by restoring // the value to the last typed one. this.value = this.window.gBrowser.userTypedValue; - } else if (this.value == this._focusUntrimmedValue) { + } else if ( + this.value == this._untrimmedValue && + !this.window.gBrowser.userTypedValue && + !this.focused + ) { // If the value was untrimmed by _on_focus and didn't change, trim it. - this.value = this._focusUntrimmedValue; + this.value = this._untrimmedValue; } else { // We're not updating the value, so just format it. this.formatValue(); } - this._focusUntrimmedValue = null; this._revertOnBlurValue = null; this._resetSearchState(); @@ -3446,6 +3563,7 @@ export class UrlbarInput { event.target.id == SEARCH_BUTTON_ID ) { this._maybeSelectAll(); + this.#maybeUntrimUrl(); } if (event.target == this._searchModeIndicatorClose && event.button != 2) { @@ -3487,7 +3605,7 @@ export class UrlbarInput { // This is necessary when a protocol was typed, but the whole url has // invalid parts, like the origin, then editing and confirming the trimmed // value would execute a search instead of visiting the typed url. - if (this.value != this._untrimmedValue) { + if (this._protocolIsTrimmed) { let untrim = false; let fixedURI = this._getURIFixupInfo(this.value)?.preferredURI; if (fixedURI) { @@ -3508,8 +3626,7 @@ export class UrlbarInput { } } if (untrim) { - this._focusUntrimmedValue = this._untrimmedValue; - this._setValue(this._focusUntrimmedValue); + this._setValue(this._untrimmedValue); } } @@ -3639,6 +3756,7 @@ export class UrlbarInput { let value = this.value; this.valueIsTyped = true; this._untrimmedValue = value; + this._protocolIsTrimmed = false; this._resultForCurrentValue = null; this.window.gBrowser.userTypedValue = value; @@ -3935,6 +4053,7 @@ export class UrlbarInput { } _on_keydown(event) { + this.#allTextSelectedOnKeyDown = this.#allTextSelected; if (event.keyCode === KeyEvent.DOM_VK_RETURN) { if (this._keyDownEnterDeferred) { this._keyDownEnterDeferred.reject(); @@ -3962,6 +4081,9 @@ export class UrlbarInput { } async _on_keyup(event) { + if (this.#allTextSelectedOnKeyDown) { + this.#maybeUntrimUrl(); + } if (event.keyCode === KeyEvent.DOM_VK_CONTROL) { this._isKeyDownWithCtrl = false; } @@ -4066,8 +4188,7 @@ export class UrlbarInput { // Only customize the drag data if the entire value is selected and it's a // loaded URI. Use default behavior otherwise. if ( - this.selectionStart != 0 || - this.selectionEnd != this.inputField.textLength || + !this.#allTextSelected || this.getAttribute("pageproxystate") != "valid" ) { return; @@ -4128,6 +4249,11 @@ export class UrlbarInput { this._initStripOnShare(); } + #allTextSelectedOnKeyDown = false; + get #allTextSelected() { + return this.selectionStart == 0 && this.selectionEnd == this.value.length; + } + /** * @param {string} value A untrimmed address bar input. * @returns {boolean} @@ -4148,6 +4274,12 @@ export class UrlbarInput { .nonWebControlledBlankURI ); } + + /** + * Tracks a state object per browser. + * TODO: Merge _searchModesByBrowser into this. + */ + #browserStates = new WeakMap(); } /** diff --git a/browser/components/urlbar/UrlbarPrefs.sys.mjs b/browser/components/urlbar/UrlbarPrefs.sys.mjs index cd8a6b0f4c..264d86a3f4 100644 --- a/browser/components/urlbar/UrlbarPrefs.sys.mjs +++ b/browser/components/urlbar/UrlbarPrefs.sys.mjs @@ -69,7 +69,7 @@ const PREF_URLBAR_DEFAULTS = new Map([ // Whether to show a link for using the search functionality provided by the // active view if the the view utilizes OpenSearch. - ["contextualSearch.enabled", false], + ["contextualSearch.enabled", true], // Whether using `ctrl` when hitting return/enter in the URL bar // (or clicking 'go') should prefix 'www.' and suffix @@ -178,7 +178,7 @@ const PREF_URLBAR_DEFAULTS = new Map([ // If disabled, QuickActions will not be included in either the default search // mode or the QuickActions search mode. - ["quickactions.enabled", false], + ["quickactions.enabled", true], // Whether we will match QuickActions within a phrase and not only a prefix. ["quickactions.matchInPhrase", true], @@ -188,9 +188,6 @@ const PREF_URLBAR_DEFAULTS = new Map([ // zero prefix state. ["quickactions.minimumSearchString", 3], - // Show multiple actions in a random order. - ["quickactions.randomOrderActions", false], - // Whether we show the Actions section in about:preferences. ["quickactions.showPrefs", false], @@ -322,11 +319,13 @@ const PREF_URLBAR_DEFAULTS = new Map([ // homepage is opened. ["searchTips.test.ignoreShowLimits", false], + // Feature gate pref for secondary actions being shown in the urlbar. + ["secondaryActions.featureGate", false], + // Whether to show each local search shortcut button in the view. ["shortcuts.bookmarks", true], ["shortcuts.tabs", true], ["shortcuts.history", true], - ["shortcuts.quickactions", false], // Boolean to determine if the providers defined in `exposureResults` // should be displayed in search results. This can be set by a @@ -464,6 +463,10 @@ const PREF_URLBAR_DEFAULTS = new Map([ // The index where we show unit conversion results. ["unitConversion.suggestedIndex", 1], + // Untrim url, when urlbar is focused. + // Note: This pref will be removed once the feature is stable. + ["untrimOnUserInteraction.featureGate", false], + // Controls the empty search behavior in Search Mode: // 0 - Show nothing // 1 - Show search history diff --git a/browser/components/urlbar/UrlbarProviderAutofill.sys.mjs b/browser/components/urlbar/UrlbarProviderAutofill.sys.mjs index 7470df0fea..2ed1ee4444 100644 --- a/browser/components/urlbar/UrlbarProviderAutofill.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderAutofill.sys.mjs @@ -68,7 +68,7 @@ const SQL_AUTOFILL_FRECENCY_THRESHOLD = `host_frecency >= ( )`; function originQuery(where) { - // `frecency`, `bookmarked` and `visited` are partitioned by the fixed host, + // `frecency`, `n_bookmarks` and `visited` are partitioned by the fixed host, // without `www.`. `host_prefix` instead is partitioned by full host, because // we assume a prefix may not work regardless of `www.`. let selectVisited = where.includes("visited") @@ -78,7 +78,7 @@ function originQuery(where) { : "0"; let selectTitle; let joinBookmarks; - if (where.includes("bookmarked")) { + if (where.includes("n_bookmarks")) { selectTitle = "ifnull(b.title, iif(h.frecency <> 0, h.title, NULL))"; joinBookmarks = "LEFT JOIN moz_bookmarks b ON b.fk = h.id"; } else { @@ -87,7 +87,7 @@ function originQuery(where) { } return `/* do not warn (bug no): cannot use an index to sort */ ${SQL_AUTOFILL_WITH}, - origins(id, prefix, host_prefix, host, fixed, host_frecency, frecency, bookmarked, visited) AS ( + origins(id, prefix, host_prefix, host, fixed, host_frecency, frecency, n_bookmarks, visited) AS ( SELECT id, prefix, @@ -96,11 +96,11 @@ function originQuery(where) { ), host, fixup_url(host), - IFNULL(total(${ORIGIN_FRECENCY_FIELD}) OVER (PARTITION BY fixup_url(host)), 0.0), + total(${ORIGIN_FRECENCY_FIELD}) OVER (PARTITION BY fixup_url(host)), ${ORIGIN_FRECENCY_FIELD}, - MAX(EXISTS( - SELECT 1 FROM moz_places WHERE origin_id = o.id AND foreign_count > 0 - )) OVER (PARTITION BY fixup_url(host)), + total( + (SELECT total(foreign_count) FROM moz_places WHERE origin_id = o.id) + ) OVER (PARTITION BY fixup_url(host)), ${selectVisited} FROM moz_origins o WHERE prefix NOT IN ('about:', 'place:') @@ -112,7 +112,7 @@ function originQuery(where) { ifnull(:prefix, host_prefix) || host || '/' FROM origins ${where} - ORDER BY frecency DESC, prefix = "https://" DESC, id DESC + ORDER BY frecency DESC, n_bookmarks DESC, prefix = "https://" DESC, id DESC LIMIT 1 ), matched_place(host_fixed, url, id, title, frecency) AS ( @@ -157,11 +157,11 @@ function urlQuery(where1, where2, isBookmarkContained) { joinBookmarks = ""; } return `/* do not warn (bug no): cannot use an index to sort */ - WITH matched_url(url, title, frecency, bookmarked, visited, stripped_url, is_exact_match, id) AS ( + WITH matched_url(url, title, frecency, n_bookmarks, visited, stripped_url, is_exact_match, id) AS ( SELECT url, title, frecency, - foreign_count > 0 AS bookmarked, + foreign_count AS n_bookmarks, visit_count > 0 AS visited, strip_prefix_and_userinfo(url) AS stripped_url, strip_prefix_and_userinfo(url) = strip_prefix_and_userinfo(:strippedURL) AS is_exact_match, @@ -173,7 +173,7 @@ function urlQuery(where1, where2, isBookmarkContained) { SELECT url, title, frecency, - foreign_count > 0 AS bookmarked, + foreign_count AS n_bookmarks, visit_count > 0 AS visited, strip_prefix_and_userinfo(url) AS stripped_url, strip_prefix_and_userinfo(url) = 'www.' || strip_prefix_and_userinfo(:strippedURL) AS is_exact_match, @@ -196,12 +196,12 @@ function urlQuery(where1, where2, isBookmarkContained) { // Queries const QUERY_ORIGIN_HISTORY_BOOKMARK = originQuery( - `WHERE bookmarked OR ${SQL_AUTOFILL_FRECENCY_THRESHOLD}` + `WHERE n_bookmarks > 0 OR ${SQL_AUTOFILL_FRECENCY_THRESHOLD}` ); const QUERY_ORIGIN_PREFIX_HISTORY_BOOKMARK = originQuery( `WHERE prefix BETWEEN :prefix AND :prefix || X'FFFF' - AND (bookmarked OR ${SQL_AUTOFILL_FRECENCY_THRESHOLD})` + AND (n_bookmarks > 0 OR ${SQL_AUTOFILL_FRECENCY_THRESHOLD})` ); const QUERY_ORIGIN_HISTORY = originQuery( @@ -213,38 +213,38 @@ const QUERY_ORIGIN_PREFIX_HISTORY = originQuery( AND visited AND ${SQL_AUTOFILL_FRECENCY_THRESHOLD}` ); -const QUERY_ORIGIN_BOOKMARK = originQuery(`WHERE bookmarked`); +const QUERY_ORIGIN_BOOKMARK = originQuery(`WHERE n_bookmarks > 0`); const QUERY_ORIGIN_PREFIX_BOOKMARK = originQuery( - `WHERE prefix BETWEEN :prefix AND :prefix || X'FFFF' AND bookmarked` + `WHERE prefix BETWEEN :prefix AND :prefix || X'FFFF' AND n_bookmarks > 0` ); const QUERY_URL_HISTORY_BOOKMARK = urlQuery( - `AND (bookmarked OR frecency > 20) + `AND (n_bookmarks > 0 OR frecency > 20) AND stripped_url COLLATE NOCASE BETWEEN :strippedURL AND :strippedURL || X'FFFF'`, - `AND (bookmarked OR frecency > 20) + `AND (n_bookmarks > 0 OR frecency > 20) AND stripped_url COLLATE NOCASE BETWEEN 'www.' || :strippedURL AND 'www.' || :strippedURL || X'FFFF'`, true ); const QUERY_URL_PREFIX_HISTORY_BOOKMARK = urlQuery( - `AND (bookmarked OR frecency > 20) + `AND (n_bookmarks > 0 OR frecency > 20) AND url COLLATE NOCASE BETWEEN :prefix || :strippedURL AND :prefix || :strippedURL || X'FFFF'`, - `AND (bookmarked OR frecency > 20) + `AND (n_bookmarks > 0 OR frecency > 20) AND url COLLATE NOCASE BETWEEN :prefix || 'www.' || :strippedURL AND :prefix || 'www.' || :strippedURL || X'FFFF'`, true ); const QUERY_URL_HISTORY = urlQuery( - `AND (visited OR NOT bookmarked) + `AND (visited OR n_bookmarks = 0) AND frecency > 20 AND stripped_url COLLATE NOCASE BETWEEN :strippedURL AND :strippedURL || X'FFFF'`, - `AND (visited OR NOT bookmarked) + `AND (visited OR n_bookmarks = 0) AND frecency > 20 AND stripped_url COLLATE NOCASE BETWEEN 'www.' || :strippedURL AND 'www.' || :strippedURL || X'FFFF'`, @@ -252,11 +252,11 @@ const QUERY_URL_HISTORY = urlQuery( ); const QUERY_URL_PREFIX_HISTORY = urlQuery( - `AND (visited OR NOT bookmarked) + `AND (visited OR n_bookmarks = 0) AND frecency > 20 AND url COLLATE NOCASE BETWEEN :prefix || :strippedURL AND :prefix || :strippedURL || X'FFFF'`, - `AND (visited OR NOT bookmarked) + `AND (visited OR n_bookmarks = 0) AND frecency > 20 AND url COLLATE NOCASE BETWEEN :prefix || 'www.' || :strippedURL AND :prefix || 'www.' || :strippedURL || X'FFFF'`, @@ -264,20 +264,20 @@ const QUERY_URL_PREFIX_HISTORY = urlQuery( ); const QUERY_URL_BOOKMARK = urlQuery( - `AND bookmarked + `AND n_bookmarks > 0 AND stripped_url COLLATE NOCASE BETWEEN :strippedURL AND :strippedURL || X'FFFF'`, - `AND bookmarked + `AND n_bookmarks > 0 AND stripped_url COLLATE NOCASE BETWEEN 'www.' || :strippedURL AND 'www.' || :strippedURL || X'FFFF'`, true ); const QUERY_URL_PREFIX_BOOKMARK = urlQuery( - `AND bookmarked + `AND n_bookmarks > 0 AND url COLLATE NOCASE BETWEEN :prefix || :strippedURL AND :prefix || :strippedURL || X'FFFF'`, - `AND bookmarked + `AND n_bookmarks > 0 AND url COLLATE NOCASE BETWEEN :prefix || 'www.' || :strippedURL AND :prefix || 'www.' || :strippedURL || X'FFFF'`, true @@ -452,17 +452,19 @@ class ProviderAutofill extends UrlbarProvider { sources.includes(UrlbarUtils.RESULT_SOURCE.HISTORY) && sources.includes(UrlbarUtils.RESULT_SOURCE.BOOKMARKS) ) { - conditions.push(`(bookmarked OR ${SQL_AUTOFILL_FRECENCY_THRESHOLD})`); + conditions.push( + `(n_bookmarks > 0 OR ${SQL_AUTOFILL_FRECENCY_THRESHOLD})` + ); } else if (sources.includes(UrlbarUtils.RESULT_SOURCE.HISTORY)) { conditions.push(`visited AND ${SQL_AUTOFILL_FRECENCY_THRESHOLD}`); } else if (sources.includes(UrlbarUtils.RESULT_SOURCE.BOOKMARKS)) { - conditions.push("bookmarked"); + conditions.push("n_bookmarks > 0"); } let rows = await db.executeCached( ` ${SQL_AUTOFILL_WITH}, - origins(id, prefix, host_prefix, host, fixed, host_frecency, frecency, bookmarked, visited) AS ( + origins(id, prefix, host_prefix, host, fixed, host_frecency, frecency, n_bookmarks, visited) AS ( SELECT id, prefix, @@ -471,11 +473,11 @@ class ProviderAutofill extends UrlbarProvider { ), host, fixup_url(host), - IFNULL(total(${ORIGIN_FRECENCY_FIELD}) OVER (PARTITION BY fixup_url(host)), 0.0), + total(${ORIGIN_FRECENCY_FIELD}) OVER (PARTITION BY fixup_url(host)), ${ORIGIN_FRECENCY_FIELD}, - MAX(EXISTS( - SELECT 1 FROM moz_places WHERE origin_id = o.id AND foreign_count > 0 - )) OVER (PARTITION BY fixup_url(host)), + total( + (SELECT total(foreign_count) FROM moz_places WHERE origin_id = o.id) + ) OVER (PARTITION BY fixup_url(host)), MAX(EXISTS( SELECT 1 FROM moz_places WHERE origin_id = o.id AND visit_count > 0 )) OVER (PARTITION BY fixup_url(host)) diff --git a/browser/components/urlbar/UrlbarProviderContextualSearch.sys.mjs b/browser/components/urlbar/UrlbarProviderContextualSearch.sys.mjs deleted file mode 100644 index 5714f11e72..0000000000 --- a/browser/components/urlbar/UrlbarProviderContextualSearch.sys.mjs +++ /dev/null @@ -1,280 +0,0 @@ -/* 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 { - UrlbarProvider, - UrlbarUtils, -} from "resource:///modules/UrlbarUtils.sys.mjs"; - -const lazy = {}; - -ChromeUtils.defineESModuleGetters(lazy, { - BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs", - OpenSearchEngine: "resource://gre/modules/OpenSearchEngine.sys.mjs", - loadAndParseOpenSearchEngine: - "resource://gre/modules/OpenSearchLoader.sys.mjs", - UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs", - UrlbarResult: "resource:///modules/UrlbarResult.sys.mjs", - UrlbarSearchUtils: "resource:///modules/UrlbarSearchUtils.sys.mjs", - UrlbarView: "resource:///modules/UrlbarView.sys.mjs", -}); - -const DYNAMIC_RESULT_TYPE = "contextualSearch"; - -const ENABLED_PREF = "contextualSearch.enabled"; - -const VIEW_TEMPLATE = { - attributes: { - selectable: true, - }, - children: [ - { - name: "no-wrap", - tag: "span", - classList: ["urlbarView-no-wrap", "urlbarView-overflowable"], - children: [ - { - name: "icon", - tag: "img", - classList: ["urlbarView-favicon"], - }, - { - name: "search", - tag: "span", - classList: ["urlbarView-title", "urlbarView-overflowable"], - }, - { - name: "separator", - tag: "span", - classList: ["urlbarView-title-separator"], - }, - { - name: "description", - tag: "span", - }, - ], - }, - ], -}; - -/** - * A provider that returns an option for using the search engine provided - * by the active view if it utilizes OpenSearch. - */ -class ProviderContextualSearch extends UrlbarProvider { - constructor() { - super(); - this.engines = new Map(); - lazy.UrlbarResult.addDynamicResultType(DYNAMIC_RESULT_TYPE); - lazy.UrlbarView.addDynamicViewTemplate(DYNAMIC_RESULT_TYPE, VIEW_TEMPLATE); - } - - /** - * Unique name for the provider, used by the context to filter on providers. - * Not using a unique name will cause the newest registration to win. - * - * @returns {string} - */ - get name() { - return "UrlbarProviderContextualSearch"; - } - - /** - * The type of the provider. - * - * @returns {UrlbarUtils.PROVIDER_TYPE} - */ - get type() { - return UrlbarUtils.PROVIDER_TYPE.PROFILE; - } - - /** - * Whether this provider should be invoked for the given context. - * If this method returns false, the providers manager won't start a query - * with this provider, to save on resources. - * - * @param {UrlbarQueryContext} queryContext The query context object - * @returns {boolean} Whether this provider should be invoked for the search. - */ - isActive(queryContext) { - return ( - queryContext.trimmedSearchString && - !queryContext.searchMode && - lazy.UrlbarPrefs.get(ENABLED_PREF) - ); - } - - /** - * Starts querying. Extended classes should return a Promise resolved when the - * provider is done searching AND returning results. - * - * @param {UrlbarQueryContext} queryContext The query context object - * @param {Function} addCallback Callback invoked by the provider to add a new - * result. A UrlbarResult should be passed to it. - */ - async startQuery(queryContext, addCallback) { - let engine; - const hostname = - queryContext?.currentPage && new URL(queryContext.currentPage).hostname; - - // This happens on about pages, which won't have associated engines - if (!hostname) { - return; - } - - // First check to see if there's a cached search engine for the host. - // If not, check to see if an installed engine matches the current view. - if (this.engines.has(hostname)) { - engine = this.engines.get(hostname); - } else { - // Strip www. to allow for partial matches when looking for an engine. - const [host] = UrlbarUtils.stripPrefixAndTrim(hostname, { - stripWww: true, - }); - engine = ( - await lazy.UrlbarSearchUtils.enginesForDomainPrefix(host, { - matchAllDomainLevels: true, - }) - )[0]; - } - - if (engine) { - let instance = this.queryInstance; - let icon = await engine.getIconURL(); - if (instance != this.queryInstance) { - return; - } - - this.engines.set(hostname, engine); - // Check to see if the engine that was found is the default engine. - // The default engine will often be used to populate the heuristic result, - // and we want to avoid ending up with two nearly identical search results. - let defaultEngine = lazy.UrlbarSearchUtils.getDefaultEngine(); - if (engine.name === defaultEngine?.name) { - return; - } - const [url] = UrlbarUtils.getSearchQueryUrl( - engine, - queryContext.searchString - ); - let result = this.makeResult({ - url, - engine: engine.name, - icon, - input: queryContext.searchString, - shouldNavigate: true, - }); - addCallback(this, result); - return; - } - - // If the current view has engines that haven't been added, return a result - // that will first add an engine, then use it to search. - let window = lazy.BrowserWindowTracker.getTopWindow(); - let engineToAdd = window?.gBrowser.selectedBrowser?.engines?.[0]; - - if (engineToAdd) { - let result = this.makeResult({ - hostname, - url: engineToAdd.uri, - engine: engineToAdd.title, - icon: engineToAdd.icon, - input: queryContext.searchString, - shouldAddEngine: true, - }); - addCallback(this, result); - } - } - - makeResult({ - engine, - icon, - url, - input, - hostname, - shouldNavigate = false, - shouldAddEngine = false, - }) { - let result = new lazy.UrlbarResult( - UrlbarUtils.RESULT_TYPE.DYNAMIC, - UrlbarUtils.RESULT_SOURCE.OTHER_LOCAL, - { - engine, - icon, - url, - input, - hostname, - shouldAddEngine, - shouldNavigate, - dynamicType: DYNAMIC_RESULT_TYPE, - } - ); - result.suggestedIndex = -1; - return result; - } - - /** - * This is called when the urlbar view updates the view of one of the results - * of the provider. It should return an object describing the view update. - * See the base UrlbarProvider class for more. - * - * @param {UrlbarResult} result The result whose view will be updated. - * @returns {object} An object describing the view update. - */ - getViewUpdate(result) { - return { - icon: { - attributes: { - src: result.payload.icon || UrlbarUtils.ICON.SEARCH_GLASS, - }, - }, - search: { - textContent: result.payload.input, - attributes: { - title: result.payload.input, - }, - }, - description: { - l10n: { - id: "urlbar-result-action-search-w-engine", - args: { - engine: result.payload.engine, - }, - }, - }, - }; - } - - onLegacyEngagement(state, queryContext, details, controller) { - let { result } = details; - if (result?.providerName == this.name) { - this.#pickResult(result, controller.browserWindow); - } - } - - async #pickResult(result, window) { - // If we have an engine to add, first create a new OpenSearchEngine, then - // get and open a url to execute a search for the term in the url bar. - // In cases where we don't have to create a new engine, navigation is - // handled automatically by providing `shouldNavigate: true` in the result. - if (result.payload.shouldAddEngine) { - let engineData = await lazy.loadAndParseOpenSearchEngine( - Services.io.newURI(result.payload.url) - ); - let newEngine = new lazy.OpenSearchEngine({ engineData }); - newEngine._setIcon(result.payload.icon, false); - this.engines.set(result.payload.hostname, newEngine); - const [url] = UrlbarUtils.getSearchQueryUrl( - newEngine, - result.payload.input - ); - window.gBrowser.fixupAndLoadURIString(url, { - triggeringPrincipal: - Services.scriptSecurityManager.getSystemPrincipal(), - }); - } - } -} - -export var UrlbarProviderContextualSearch = new ProviderContextualSearch(); diff --git a/browser/components/urlbar/UrlbarProviderInputHistory.sys.mjs b/browser/components/urlbar/UrlbarProviderInputHistory.sys.mjs index 17b6a4c9b0..a259d639cc 100644 --- a/browser/components/urlbar/UrlbarProviderInputHistory.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderInputHistory.sys.mjs @@ -144,15 +144,25 @@ class ProviderInputHistory extends UrlbarProvider { // Don't suggest switching to the current page. continue; } - let result = new lazy.UrlbarResult( - UrlbarUtils.RESULT_TYPE.TAB_SWITCH, - UrlbarUtils.RESULT_SOURCE.TABS, - ...lazy.UrlbarResult.payloadAndSimpleHighlights(queryContext.tokens, { + let payload = lazy.UrlbarResult.payloadAndSimpleHighlights( + queryContext.tokens, + { url: [url, UrlbarUtils.HIGHLIGHT.TYPED], title: [resultTitle, UrlbarUtils.HIGHLIGHT.TYPED], icon: UrlbarUtils.getIconForUrl(url), userContextId: row.getResultByName("userContextId") || 0, - }) + } + ); + if (lazy.UrlbarPrefs.get("secondaryActions.featureGate")) { + payload[0].action = { + key: "tabswitch", + l10nId: "urlbar-result-action-switch-tab", + }; + } + let result = new lazy.UrlbarResult( + UrlbarUtils.RESULT_TYPE.TAB_SWITCH, + UrlbarUtils.RESULT_SOURCE.TABS, + ...payload ); addCallback(this, result); continue; diff --git a/browser/components/urlbar/UrlbarProviderPlaces.sys.mjs b/browser/components/urlbar/UrlbarProviderPlaces.sys.mjs index c94ebee80a..0787d4c209 100644 --- a/browser/components/urlbar/UrlbarProviderPlaces.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderPlaces.sys.mjs @@ -330,17 +330,25 @@ function makeUrlbarResult(tokens, info) { action.params.searchSuggestion.toLocaleLowerCase(), }) ); - case "switchtab": + case "switchtab": { + let payload = lazy.UrlbarResult.payloadAndSimpleHighlights(tokens, { + url: [action.params.url, UrlbarUtils.HIGHLIGHT.TYPED], + title: [info.comment, UrlbarUtils.HIGHLIGHT.TYPED], + icon: info.icon, + userContextId: info.userContextId, + }); + if (lazy.UrlbarPrefs.get("secondaryActions.featureGate")) { + payload[0].action = { + key: "tabswitch", + l10nId: "urlbar-result-action-switch-tab", + }; + } return new lazy.UrlbarResult( UrlbarUtils.RESULT_TYPE.TAB_SWITCH, UrlbarUtils.RESULT_SOURCE.TABS, - ...lazy.UrlbarResult.payloadAndSimpleHighlights(tokens, { - url: [action.params.url, UrlbarUtils.HIGHLIGHT.TYPED], - title: [info.comment, UrlbarUtils.HIGHLIGHT.TYPED], - icon: info.icon, - userContextId: info.userContextId, - }) + ...payload ); + } case "visiturl": return new lazy.UrlbarResult( UrlbarUtils.RESULT_TYPE.URL, diff --git a/browser/components/urlbar/UrlbarProviderQuickActions.sys.mjs b/browser/components/urlbar/UrlbarProviderQuickActions.sys.mjs deleted file mode 100644 index 29370cbaaf..0000000000 --- a/browser/components/urlbar/UrlbarProviderQuickActions.sys.mjs +++ /dev/null @@ -1,357 +0,0 @@ -/* 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 { - UrlbarProvider, - UrlbarUtils, -} from "resource:///modules/UrlbarUtils.sys.mjs"; - -const lazy = {}; -ChromeUtils.defineESModuleGetters(lazy, { - QuickActionsLoaderDefault: - "resource:///modules/QuickActionsLoaderDefault.sys.mjs", - UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs", - UrlbarResult: "resource:///modules/UrlbarResult.sys.mjs", -}); - -// These prefs are relative to the `browser.urlbar` branch. -const ENABLED_PREF = "quickactions.enabled"; -const SUGGEST_PREF = "suggest.quickactions"; -const MATCH_IN_PHRASE_PREF = "quickactions.matchInPhrase"; -const MIN_SEARCH_PREF = "quickactions.minimumSearchString"; -const DYNAMIC_TYPE_NAME = "quickactions"; - -// When the urlbar is first focused and no search term has been -// entered we show a limited number of results. -const ACTIONS_SHOWN_FOCUS = 4; - -// Default icon shown for actions if no custom one is provided. -const DEFAULT_ICON = "chrome://global/skin/icons/settings.svg"; - -// The suggestion index of the actions row within the urlbar results. -const SUGGESTED_INDEX = 1; - -/** - * A provider that returns a suggested url to the user based on what - * they have currently typed so they can navigate directly. - */ -class ProviderQuickActions extends UrlbarProvider { - constructor() { - super(); - lazy.UrlbarResult.addDynamicResultType(DYNAMIC_TYPE_NAME); - } - - /** - * Returns the name of this provider. - * - * @returns {string} the name of this provider. - */ - get name() { - return DYNAMIC_TYPE_NAME; - } - - /** - * The type of the provider. - * - * @returns {UrlbarUtils.PROVIDER_TYPE} - */ - get type() { - return UrlbarUtils.PROVIDER_TYPE.PROFILE; - } - - getPriority(context) { - if (!context.searchString) { - return 1; - } - return 0; - } - - /** - * Whether this provider should be invoked for the given context. - * If this method returns false, the providers manager won't start a query - * with this provider, to save on resources. - * - * @param {UrlbarQueryContext} queryContext The query context object - * @returns {boolean} Whether this provider should be invoked for the search. - */ - isActive(queryContext) { - return ( - queryContext.trimmedSearchString.length < 50 && - lazy.UrlbarPrefs.get(ENABLED_PREF) && - ((lazy.UrlbarPrefs.get(SUGGEST_PREF) && !queryContext.searchMode) || - queryContext.searchMode?.source == UrlbarUtils.RESULT_SOURCE.ACTIONS) - ); - } - - /** - * Starts querying. Extended classes should return a Promise resolved when the - * provider is done searching AND returning results. - * - * @param {UrlbarQueryContext} queryContext The query context object - * @param {Function} addCallback Callback invoked by the provider to add a new - * result. A UrlbarResult should be passed to it. - * @returns {Promise} - */ - async startQuery(queryContext, addCallback) { - await lazy.QuickActionsLoaderDefault.ensureLoaded(); - let input = queryContext.trimmedLowerCaseSearchString; - - if ( - !queryContext.searchMode && - input.length < lazy.UrlbarPrefs.get(MIN_SEARCH_PREF) - ) { - return; - } - - let results = [...(this.#prefixes.get(input) ?? [])]; - - if (lazy.UrlbarPrefs.get(MATCH_IN_PHRASE_PREF)) { - for (let [keyword, key] of this.#keywords) { - if (input.includes(keyword)) { - results.push(key); - } - } - } - // Ensure results are unique. - results = [...new Set(results)]; - - // Remove invisible actions. - results = results.filter(key => { - const action = this.#actions.get(key); - return !action.isVisible || action.isVisible(); - }); - - if (!results?.length) { - return; - } - - // If all actions are inactive, don't show anything. - if ( - results.every(key => { - const action = this.#actions.get(key); - return action.isActive && !action.isActive(); - }) - ) { - return; - } - - // If we are in the Actions searchMode then we want to show all the actions - // but not when we are in the normal url mode on first focus. - if ( - results.length > ACTIONS_SHOWN_FOCUS && - !input && - !queryContext.searchMode - ) { - results.length = ACTIONS_SHOWN_FOCUS; - } - - const result = new lazy.UrlbarResult( - UrlbarUtils.RESULT_TYPE.DYNAMIC, - UrlbarUtils.RESULT_SOURCE.ACTIONS, - { - results: results.map(key => ({ key })), - dynamicType: DYNAMIC_TYPE_NAME, - inputLength: input.length, - inQuickActionsSearchMode: - queryContext.searchMode?.source == UrlbarUtils.RESULT_SOURCE.ACTIONS, - } - ); - result.suggestedIndex = SUGGESTED_INDEX; - addCallback(this, result); - this.#resultFromLastQuery = result; - } - - getViewTemplate(result) { - return { - children: [ - { - name: "buttons", - tag: "div", - attributes: { - "data-is-quickactions-searchmode": - result.payload.inQuickActionsSearchMode, - }, - children: result.payload.results.map(({ key }, i) => { - let action = this.#actions.get(key); - let inActive = "isActive" in action && !action.isActive(); - return { - name: `button-${i}`, - tag: "span", - attributes: { - "data-key": key, - "data-input-length": result.payload.inputLength, - class: "urlbarView-quickaction-button", - role: inActive ? "" : "button", - disabled: inActive, - }, - children: [ - { - name: `icon-${i}`, - tag: "div", - attributes: { class: "urlbarView-favicon" }, - children: [ - { - name: `image-${i}`, - tag: "img", - attributes: { - class: "urlbarView-favicon-img", - src: action.icon || DEFAULT_ICON, - }, - }, - ], - }, - { - name: `label-${i}`, - tag: "span", - attributes: { class: "urlbarView-label" }, - }, - ], - }; - }), - }, - ], - }; - } - - getViewUpdate(result) { - let viewUpdate = {}; - result.payload.results.forEach(({ key }, i) => { - let action = this.#actions.get(key); - viewUpdate[`label-${i}`] = { - l10n: { id: action.label, cacheable: true }, - }; - }); - return viewUpdate; - } - - #pickResult(result, itemPicked) { - let { key, inputLength } = itemPicked.dataset; - // We clamp the input length to limit the number of keys to - // the number of actions * 10. - inputLength = Math.min(inputLength, 10); - Services.telemetry.keyedScalarAdd( - `quickaction.picked`, - `${key}-${inputLength}`, - 1 - ); - let options = this.#actions.get(itemPicked.dataset.key).onPick() ?? {}; - if (options.focusContent) { - itemPicked.ownerGlobal.gBrowser.selectedBrowser.focus(); - } - } - - onLegacyEngagement(state, queryContext, details, controller) { - // Ignore engagements on other results that didn't end the session. - if (details.result?.providerName != this.name && details.isSessionOngoing) { - return; - } - - if (state == "engagement" && queryContext) { - // Get the result that's visible in the view. `details.result` is the - // engaged result, if any; if it's from this provider, then that's the - // visible result. Otherwise fall back to #getVisibleResultFromLastQuery. - let { result } = details; - if (result?.providerName != this.name) { - result = this.#getVisibleResultFromLastQuery(controller.view); - } - - result?.payload.results.forEach(({ key }) => { - Services.telemetry.keyedScalarAdd( - `quickaction.impression`, - `${key}-${queryContext.trimmedSearchString.length}`, - 1 - ); - }); - } - - // Handle picks. - if (details.result?.providerName == this.name) { - this.#pickResult(details.result, details.element); - } - - this.#resultFromLastQuery = null; - } - - /** - * Adds a new QuickAction. - * - * @param {string} key A key to identify this action. - * @param {string} definition An object that describes the action. - */ - addAction(key, definition) { - this.#actions.set(key, definition); - definition.commands.forEach(cmd => this.#keywords.set(cmd, key)); - this.#loopOverPrefixes(definition.commands, prefix => { - let result = this.#prefixes.get(prefix); - if (result) { - if (!result.includes(key)) { - result.push(key); - } - } else { - result = [key]; - } - this.#prefixes.set(prefix, result); - }); - } - - /** - * Removes an action. - * - * @param {string} key A key to identify this action. - */ - removeAction(key) { - let definition = this.#actions.get(key); - this.#actions.delete(key); - definition.commands.forEach(cmd => this.#keywords.delete(cmd)); - this.#loopOverPrefixes(definition.commands, prefix => { - let result = this.#prefixes.get(prefix); - if (result) { - result = result.filter(val => val != key); - } - this.#prefixes.set(prefix, result); - }); - } - - // A map from keywords to an action. - #keywords = new Map(); - - // A map of all prefixes to an array of actions. - #prefixes = new Map(); - - // The actions that have been added. - #actions = new Map(); - - // The result we added during the most recent query. - #resultFromLastQuery = null; - - #loopOverPrefixes(commands, fun) { - for (const command of commands) { - // Loop over all the prefixes of the word, ie - // "", "w", "wo", "wor", stopping just before the full - // word itself which will be matched by the whole - // phrase matching. - for (let i = 1; i <= command.length; i++) { - let prefix = command.substring(0, command.length - i); - fun(prefix); - } - } - } - - #getVisibleResultFromLastQuery(view) { - let result = this.#resultFromLastQuery; - - if ( - result?.rowIndex >= 0 && - view?.visibleResults?.[result.rowIndex] == result - ) { - // The result was visible. - return result; - } - - // Find a visible result. - return view?.visibleResults?.find(r => r.providerName == this.name); - } -} - -export var UrlbarProviderQuickActions = new ProviderQuickActions(); diff --git a/browser/components/urlbar/UrlbarProviderQuickSuggest.sys.mjs b/browser/components/urlbar/UrlbarProviderQuickSuggest.sys.mjs index fbc8cc8c3f..10244e06b5 100644 --- a/browser/components/urlbar/UrlbarProviderQuickSuggest.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderQuickSuggest.sys.mjs @@ -45,7 +45,6 @@ const TELEMETRY_SCALARS = { CLICK_NAV_SUPERCEDED: `${TELEMETRY_PREFIX}.click_nav_superceded`, CLICK_NONSPONSORED: `${TELEMETRY_PREFIX}.click_nonsponsored`, CLICK_SPONSORED: `${TELEMETRY_PREFIX}.click_sponsored`, - HELP_DYNAMIC_WIKIPEDIA: `${TELEMETRY_PREFIX}.help_dynamic_wikipedia`, HELP_NONSPONSORED: `${TELEMETRY_PREFIX}.help_nonsponsored`, HELP_SPONSORED: `${TELEMETRY_PREFIX}.help_sponsored`, IMPRESSION_DYNAMIC_WIKIPEDIA: `${TELEMETRY_PREFIX}.impression_dynamic_wikipedia`, @@ -413,14 +412,11 @@ class ProviderQuickSuggest extends UrlbarProvider { let payload = { url: suggestion.url, isSponsored: suggestion.is_sponsored, - helpUrl: lazy.QuickSuggest.HELP_URL, - helpL10n: { - id: "urlbar-result-menu-learn-more-about-firefox-suggest", - }, isBlockable: true, blockL10n: { id: "urlbar-result-menu-dismiss-firefox-suggest", }, + isManageable: true, }; if (suggestion.full_keyword) { @@ -592,9 +588,6 @@ class ProviderQuickSuggest extends UrlbarProvider { scalars.push(TELEMETRY_SCALARS.CLICK_DYNAMIC_WIKIPEDIA); } else { switch (resultSelType) { - case "help": - scalars.push(TELEMETRY_SCALARS.HELP_DYNAMIC_WIKIPEDIA); - break; case "dismiss": scalars.push(TELEMETRY_SCALARS.BLOCK_DYNAMIC_WIKIPEDIA); break; diff --git a/browser/components/urlbar/UrlbarProviderWeather.sys.mjs b/browser/components/urlbar/UrlbarProviderWeather.sys.mjs index 8e9b6b8f3e..6dc38e7aed 100644 --- a/browser/components/urlbar/UrlbarProviderWeather.sys.mjs +++ b/browser/components/urlbar/UrlbarProviderWeather.sys.mjs @@ -20,7 +20,6 @@ const TELEMETRY_PREFIX = "contextual.services.quicksuggest"; const TELEMETRY_SCALARS = { BLOCK: `${TELEMETRY_PREFIX}.block_weather`, CLICK: `${TELEMETRY_PREFIX}.click_weather`, - HELP: `${TELEMETRY_PREFIX}.help_weather`, IMPRESSION: `${TELEMETRY_PREFIX}.impression_weather`, }; @@ -233,7 +232,6 @@ class ProviderWeather extends UrlbarProvider { * * - "": The user didn't pick the row or any part of it * - "weather": The user picked the main part of the row - * - "help": The user picked the help button * - "dismiss": The user dismissed the result * * An empty string means the user picked some other row to end the @@ -265,10 +263,6 @@ class ProviderWeather extends UrlbarProvider { clickScalars.push(TELEMETRY_SCALARS.CLICK); eventObject = "click"; break; - case "help": - clickScalars.push(TELEMETRY_SCALARS.HELP); - eventObject = "help"; - break; case "dismiss": clickScalars.push(TELEMETRY_SCALARS.BLOCK); eventObject = "block"; diff --git a/browser/components/urlbar/UrlbarProvidersManager.sys.mjs b/browser/components/urlbar/UrlbarProvidersManager.sys.mjs index ac70e03e1b..3fd9b0caf3 100644 --- a/browser/components/urlbar/UrlbarProvidersManager.sys.mjs +++ b/browser/components/urlbar/UrlbarProvidersManager.sys.mjs @@ -40,8 +40,6 @@ var localProviderModules = { "resource:///modules/UrlbarProviderCalculator.sys.mjs", UrlbarProviderClipboard: "resource:///modules/UrlbarProviderClipboard.sys.mjs", - UrlbarProviderContextualSearch: - "resource:///modules/UrlbarProviderContextualSearch.sys.mjs", UrlbarProviderHeuristicFallback: "resource:///modules/UrlbarProviderHeuristicFallback.sys.mjs", UrlbarProviderHistoryUrlHeuristic: @@ -54,8 +52,6 @@ var localProviderModules = { UrlbarProviderPlaces: "resource:///modules/UrlbarProviderPlaces.sys.mjs", UrlbarProviderPrivateSearch: "resource:///modules/UrlbarProviderPrivateSearch.sys.mjs", - UrlbarProviderQuickActions: - "resource:///modules/UrlbarProviderQuickActions.sys.mjs", UrlbarProviderQuickSuggest: "resource:///modules/UrlbarProviderQuickSuggest.sys.mjs", UrlbarProviderQuickSuggestContextualOptIn: @@ -84,6 +80,14 @@ var localMuxerModules = { "resource:///modules/UrlbarMuxerUnifiedComplete.sys.mjs", }; +import { ActionsProviderQuickActions } from "resource:///modules/ActionsProviderQuickActions.sys.mjs"; +import { ActionsProviderContextualSearch } from "resource:///modules/ActionsProviderContextualSearch.sys.mjs"; + +let globalActionsProviders = [ + ActionsProviderContextualSearch, + ActionsProviderQuickActions, +]; + const DEFAULT_MUXER = "UnifiedComplete"; /** @@ -179,6 +183,17 @@ class ProvidersManager { } /** + * Returns the provider with the given name. + * + * @param {string} name + * The provider name. + * @returns {UrlbarProvider} The provider. + */ + getActionProvider(name) { + return globalActionsProviders.find(p => p.name == name); + } + + /** * Registers a muxer object with the manager. * * @param {object} muxer @@ -284,6 +299,12 @@ class ProvidersManager { // history and bookmarks even if search engines are not available. } + // All current global actions are currently memory lookups so it is safe to + // wait on them. + this.#globalAction = lazy.UrlbarPrefs.get("secondaryActions.featureGate") + ? await this.pickGlobalAction(queryContext, controller) + : null; + if (query.canceled) { return; } @@ -357,6 +378,25 @@ class ProvidersManager { ); } } + + #globalAction = null; + + async pickGlobalAction(queryContext, controller) { + for (let provider of globalActionsProviders) { + if (provider.isActive(queryContext)) { + let action = await provider.queryAction(queryContext, controller); + if (action) { + action.providerName = provider.name; + return action; + } + } + } + return null; + } + + getGlobalAction() { + return this.#globalAction; + } } export var UrlbarProvidersManager = new ProvidersManager(); diff --git a/browser/components/urlbar/UrlbarTokenizer.sys.mjs b/browser/components/urlbar/UrlbarTokenizer.sys.mjs index c0b3a9c069..ee565ec1c9 100644 --- a/browser/components/urlbar/UrlbarTokenizer.sys.mjs +++ b/browser/components/urlbar/UrlbarTokenizer.sys.mjs @@ -246,7 +246,7 @@ export var UrlbarTokenizer = { queryContext.tokens = []; return queryContext; } - let unfiltered = splitString(queryContext.searchString); + let unfiltered = splitString(queryContext); let tokens = filterTokens(unfiltered); queryContext.tokens = tokens; return queryContext; @@ -276,13 +276,17 @@ const CHAR_TO_TYPE_MAP = new Map( ); /** - * Given a search string, splits it into string tokens. + * Given a queryContext object, splits its searchString into string tokens. * - * @param {string} searchString - * The search string to split + * @param {UrlbarQueryContext} queryContext + * The query context object to tokenize. + * @param {string} queryContext.searchString + * The search string to split. + * @param {object} queryContext.searchMode + * A search mode object. * @returns {Array} An array of string tokens. */ -function splitString(searchString) { +function splitString({ searchString, searchMode }) { // The first step is splitting on unicode whitespaces. We ignore whitespaces // if the search string starts with "data:", to better support Web developers // and compatiblity with other browsers. @@ -327,7 +331,8 @@ function splitString(searchString) { // allow for a typed question to yield only search results. if ( CHAR_TO_TYPE_MAP.has(firstToken[0]) && - !UrlbarTokenizer.REGEXP_PERCENT_ENCODED_START.test(firstToken) + !UrlbarTokenizer.REGEXP_PERCENT_ENCODED_START.test(firstToken) && + !searchMode ) { tokens[0] = firstToken.substring(1); tokens.splice(0, 0, firstToken[0]); diff --git a/browser/components/urlbar/UrlbarUtils.sys.mjs b/browser/components/urlbar/UrlbarUtils.sys.mjs index 9fca8426a3..096d1c8f2d 100644 --- a/browser/components/urlbar/UrlbarUtils.sys.mjs +++ b/browser/components/urlbar/UrlbarUtils.sys.mjs @@ -113,8 +113,7 @@ export var UrlbarUtils = { TABS: 4, OTHER_LOCAL: 5, OTHER_NETWORK: 6, - ACTIONS: 7, - ADDON: 8, + ADDON: 7, }, // This defines icon locations that are commonly used in the UI. @@ -228,13 +227,6 @@ export var UrlbarUtils = { pref: "shortcuts.history", telemetryLabel: "history", }, - { - source: UrlbarUtils.RESULT_SOURCE.ACTIONS, - restrict: lazy.UrlbarTokenizer.RESTRICT.ACTION, - icon: "chrome://browser/skin/quickactions.svg", - pref: "shortcuts.quickactions", - telemetryLabel: "actions", - }, ]; }, @@ -1279,8 +1271,6 @@ export var UrlbarUtils = { if (result.providerName == "TabToSearch") { // This is the onboarding result. return "tabtosearch"; - } else if (result.providerName == "quickactions") { - return "quickaction"; } else if (result.providerName == "Weather") { return "weather"; } @@ -1435,14 +1425,10 @@ export var UrlbarUtils = { switch (result.providerName) { case "calculator": return "calc"; - case "quickactions": - return "action"; case "TabToSearch": return "tab_to_search"; case "UnitConversion": return "unit"; - case "UrlbarProviderContextualSearch": - return "site_specific_contextual_search"; case "UrlbarProviderQuickSuggest": return this._getQuickSuggestTelemetryType(result); case "UrlbarProviderQuickSuggestContextualOptIn": @@ -1535,28 +1521,6 @@ export var UrlbarUtils = { return "unknown"; }, - /** - * Extracts a subtype for search engagement telemetry from a result and the picked element. - * - * @param {UrlbarResult} result The result to analyze. - * @param {DOMElement} element The picked view element. Nullable. - * @returns {string} Subtype as string. - */ - searchEngagementTelemetrySubtype(result, element) { - if (!result) { - return ""; - } - - if ( - result.providerName === "quickactions" && - element?.classList.contains("urlbarView-quickaction-button") - ) { - return element.dataset.key; - } - - return ""; - }, - _getQuickSuggestTelemetryType(result) { if (result.payload.telemetryType == "weather") { // Return "weather" without the usual source prefix for consistency with @@ -1641,6 +1605,17 @@ UrlbarUtils.RESULT_PAYLOAD_SCHEMA = { type: "object", required: ["url"], properties: { + action: { + type: "object", + properties: { + l10nId: { + type: "string", + }, + key: { + type: "string", + }, + }, + }, displayUrl: { type: "string", }, diff --git a/browser/components/urlbar/UrlbarValueFormatter.sys.mjs b/browser/components/urlbar/UrlbarValueFormatter.sys.mjs index b27bede750..2fa3d0137d 100644 --- a/browser/components/urlbar/UrlbarValueFormatter.sys.mjs +++ b/browser/components/urlbar/UrlbarValueFormatter.sys.mjs @@ -146,12 +146,14 @@ export class UrlbarValueFormatter { // we can skip most of this. if ( browser._urlMetaData && - browser._urlMetaData.inputValue == this.urlbarInput.untrimmedValue + browser._urlMetaData.inputValue == inputValue && + browser._urlMetaData.untrimmedValue == this.urlbarInput.untrimmedValue ) { return browser._urlMetaData.data; } browser._urlMetaData = { - inputValue: this.urlbarInput.untrimmedValue, + inputValue, + untrimmedValue: this.urlbarInput.untrimmedValue, data: null, }; diff --git a/browser/components/urlbar/UrlbarView.sys.mjs b/browser/components/urlbar/UrlbarView.sys.mjs index 3d6ea46781..c5ea040f1f 100644 --- a/browser/components/urlbar/UrlbarView.sys.mjs +++ b/browser/components/urlbar/UrlbarView.sys.mjs @@ -1635,6 +1635,38 @@ export class UrlbarView { item.appendChild(button); } + #createSecondaryAction(action, global = false) { + let actionContainer = this.#createElement("div"); + actionContainer.classList.add("urlbarView-actions-container"); + + let button = this.#createElement("span"); + button.classList.add("urlbarView-action-btn"); + if (global) { + button.classList.add("urlbarView-global-action-btn"); + } + button.setAttribute("role", "button"); + if (action.icon) { + let icon = this.#createElement("img"); + icon.src = action.icon; + button.appendChild(icon); + } + for (let key in action.dataset ?? {}) { + button.dataset[key] = action.dataset[key]; + } + button.dataset.action = action.key; + button.dataset.providerName = action.providerName; + + let label = this.#createElement("span"); + if (action.l10nId) { + this.#setElementL10n(label, { id: action.l10nId, args: action.l10nArgs }); + } else { + this.document.l10n.setAttributes(label, action.label, action.l10nArgs); + } + button.appendChild(label); + actionContainer.appendChild(button); + return actionContainer; + } + // eslint-disable-next-line complexity #updateRow(item, result) { let oldResult = item.result; @@ -1707,6 +1739,26 @@ export class UrlbarView { } item._content.id = item.id + "-inner"; + let isFirstChild = item === this.#rows.children[0]; + let secAction = + result.heuristic || isFirstChild + ? lazy.UrlbarProvidersManager.getGlobalAction() + : result.payload.action; + let container = item.querySelector(".urlbarView-actions-container"); + if (secAction && !container) { + item.appendChild(this.#createSecondaryAction(secAction, isFirstChild)); + } else if ( + secAction && + secAction.key != container.firstChild.dataset.action + ) { + item.replaceChild( + this.#createSecondaryAction(secAction, isFirstChild), + container + ); + } else if (!secAction && container) { + item.removeChild(container); + } + item.removeAttribute("feedback-acknowledgment"); if ( @@ -1800,6 +1852,10 @@ export class UrlbarView { let isRowSelectable = true; switch (result.type) { case lazy.UrlbarUtils.RESULT_TYPE.TAB_SWITCH: + // Hide chichlet when showing secondaryActions. + if (lazy.UrlbarPrefs.get("secondaryActions.featureGate")) { + break; + } actionSetter = () => { this.#setSwitchTabActionChiclet(result, action); }; diff --git a/browser/components/urlbar/docs/firefox-suggest-telemetry.rst b/browser/components/urlbar/docs/firefox-suggest-telemetry.rst index 8d9c7c20ff..e3d37605e1 100644 --- a/browser/components/urlbar/docs/firefox-suggest-telemetry.rst +++ b/browser/components/urlbar/docs/firefox-suggest-telemetry.rst @@ -501,7 +501,11 @@ Changelog Firefox 109.0 Introduced. [Bug 1800993_] + Firefox 127.0 + Removed. [Bug 1891602_] + .. _1800993: https://bugzilla.mozilla.org/show_bug.cgi?id=1800993 +.. _1891602: https://bugzilla.mozilla.org/show_bug.cgi?id=1891602 contextual.services.quicksuggest.help_nonsponsored ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -579,7 +583,11 @@ Changelog Firefox 110.0 Introduced. [Bug 1804536_] + Firefox 127.0 + Removed. [Bug 1891602_] + .. _1804536: https://bugzilla.mozilla.org/show_bug.cgi?id=1804536 +.. _1891602: https://bugzilla.mozilla.org/show_bug.cgi?id=1891602 contextual.services.quicksuggest.impression ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/browser/components/urlbar/metrics.yaml b/browser/components/urlbar/metrics.yaml index 173ee08a10..5140391e4f 100644 --- a/browser/components/urlbar/metrics.yaml +++ b/browser/components/urlbar/metrics.yaml @@ -487,11 +487,13 @@ urlbar: - https://bugzilla.mozilla.org/show_bug.cgi?id=1852058 data_reviews: - https://bugzilla.mozilla.org/show_bug.cgi?id=1852058#c2 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1866204#c8 + - https://bugzilla.mozilla.org/show_bug.cgi?id=1892377#c2 data_sensitivity: - interaction notification_emails: - fx-search-telemetry@mozilla.com - expires: 128 + expires: 132 pref_max_results: lifetime: application diff --git a/browser/components/urlbar/moz.build b/browser/components/urlbar/moz.build index e35ea11655..4b91bef331 100644 --- a/browser/components/urlbar/moz.build +++ b/browser/components/urlbar/moz.build @@ -12,6 +12,9 @@ DIRS += [ ] EXTRA_JS_MODULES += [ + "ActionsProvider.sys.mjs", + "ActionsProviderContextualSearch.sys.mjs", + "ActionsProviderQuickActions.sys.mjs", "MerinoClient.sys.mjs", "QuickActionsLoaderDefault.sys.mjs", "QuickSuggest.sys.mjs", @@ -26,7 +29,6 @@ EXTRA_JS_MODULES += [ "UrlbarProviderBookmarkKeywords.sys.mjs", "UrlbarProviderCalculator.sys.mjs", "UrlbarProviderClipboard.sys.mjs", - "UrlbarProviderContextualSearch.sys.mjs", "UrlbarProviderHeuristicFallback.sys.mjs", "UrlbarProviderHistoryUrlHeuristic.sys.mjs", "UrlbarProviderInputHistory.sys.mjs", @@ -35,7 +37,6 @@ EXTRA_JS_MODULES += [ "UrlbarProviderOpenTabs.sys.mjs", "UrlbarProviderPlaces.sys.mjs", "UrlbarProviderPrivateSearch.sys.mjs", - "UrlbarProviderQuickActions.sys.mjs", "UrlbarProviderQuickSuggest.sys.mjs", "UrlbarProviderQuickSuggestContextualOptIn.sys.mjs", "UrlbarProviderRecentSearches.sys.mjs", diff --git a/browser/components/urlbar/private/SuggestBackendRust.sys.mjs b/browser/components/urlbar/private/SuggestBackendRust.sys.mjs index 3993149757..120e7b7d0c 100644 --- a/browser/components/urlbar/private/SuggestBackendRust.sys.mjs +++ b/browser/components/urlbar/private/SuggestBackendRust.sys.mjs @@ -289,7 +289,10 @@ export class SuggestBackendRust extends BaseFeature { if (instance != this.#ingestInstance) { return; } - await (this.#ingestPromise = this.#ingestHelper()); + this.#ingestPromise = new Promise(resolve => { + ChromeUtils.idleDispatch(() => this.#ingestHelper().finally(resolve)); + }); + await this.#ingestPromise; } async #ingestHelper() { diff --git a/browser/components/urlbar/tests/UrlbarTestUtils.sys.mjs b/browser/components/urlbar/tests/UrlbarTestUtils.sys.mjs index f576f4ca19..793af24b41 100644 --- a/browser/components/urlbar/tests/UrlbarTestUtils.sys.mjs +++ b/browser/components/urlbar/tests/UrlbarTestUtils.sys.mjs @@ -1043,9 +1043,11 @@ export var UrlbarTestUtils = { * Removes the scheme from an url according to user prefs. * * @param {string} url - * The url that is supposed to be sanitizied. - * @param {{removeSingleTrailingSlash: (boolean)}} options - * removeSingleTrailingSlash: Remove trailing slash, when trimming enabled. + * The url that is supposed to be trimmed. + * @param {object} [options] + * Options for the trimming. + * @param {boolean} [options.removeSingleTrailingSlash] + * Remove trailing slash, when trimming enabled. * @returns {string} * The sanitized URL. */ @@ -1060,15 +1062,13 @@ export var UrlbarTestUtils = { lazy.BrowserUIUtils.removeSingleTrailingSlashFromURL(sanitizedURL); } + // Also remove emphasis markers if present. if (lazy.UrlbarPrefs.get("trimHttps")) { - sanitizedURL = sanitizedURL.replace("https://", ""); + sanitizedURL = sanitizedURL.replace(/^<?https:\/\/>?/, ""); } else { - sanitizedURL = sanitizedURL.replace("http://", ""); + sanitizedURL = sanitizedURL.replace(/^<?http:\/\/>?/, ""); } - // Remove empty emphasis markers in case the protocol was trimmed. - sanitizedURL = sanitizedURL.replace("<>", ""); - return sanitizedURL; }, diff --git a/browser/components/urlbar/tests/browser/browser.toml b/browser/components/urlbar/tests/browser/browser.toml index 44b964e5ca..38046aa26b 100644 --- a/browser/components/urlbar/tests/browser/browser.toml +++ b/browser/components/urlbar/tests/browser/browser.toml @@ -68,6 +68,8 @@ skip-if = ["apple_catalina && debug"] # Bug 1773790 ["browser_UrlbarInput_trimURLs.js"] https_first_disabled = true +["browser_UrlbarInput_untrimOnUserInteraction.js"] + ["browser_aboutHomeLoading.js"] skip-if = [ "tsan", # Intermittently times out, see 1622698 (frequent on TSan). @@ -365,6 +367,8 @@ support-files = [ ["browser_quickactions.js"] +["browser_quickactions_commands.js"] + ["browser_quickactions_devtools.js"] ["browser_quickactions_screenshot.js"] @@ -498,6 +502,8 @@ support-files = ["search-engines", "../../../search/test/browser/trendingSuggest ["browser_search_history_from_history_panel.js"] +["browser_secondaryActions.js"] + ["browser_selectStaleResults.js"] support-files = [ "searchSuggestionEngineSlow.xml", @@ -645,9 +651,6 @@ tags = "search-telemetry" https_first_disabled = true tags = "search-telemetry" -["browser_urlbar_telemetry_quickactions.js"] -tags = "search-telemetry" - ["browser_urlbar_telemetry_remotetab.js"] tags = "search-telemetry" diff --git a/browser/components/urlbar/tests/browser/browser_UrlbarInput_untrimOnUserInteraction.js b/browser/components/urlbar/tests/browser/browser_UrlbarInput_untrimOnUserInteraction.js new file mode 100644 index 0000000000..a6714df360 --- /dev/null +++ b/browser/components/urlbar/tests/browser/browser_UrlbarInput_untrimOnUserInteraction.js @@ -0,0 +1,124 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +let tests = [ + { + description: "Test single click doesn't untrim", + untrimmedValue: BrowserUIUtils.trimURLProtocol + "www.example.com/", + execute() { + EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {}); + Assert.equal(gURLBar.selectionStart, 0, "Selection start is 0."); + Assert.equal( + gURLBar.selectionEnd, + gURLBar.value.length, + "Selection end is at and of text." + ); + }, + shouldUntrim: false, + }, + { + description: "Test CTRL+L doesn't untrim", + untrimmedValue: BrowserUIUtils.trimURLProtocol + "www.example.com/", + execute() { + EventUtils.synthesizeKey("l", { accelKey: true }); + Assert.equal(gURLBar.selectionStart, 0, "Selection start is 0."); + Assert.equal( + gURLBar.selectionEnd, + gURLBar.value.length, + "Selection end is at and of text." + ); + }, + shouldUntrim: false, + }, + { + description: "Test drag selection untrims", + untrimmedValue: BrowserUIUtils.trimURLProtocol + "www.example.com/", + execute() { + selectWithMouseDrag(100, 200); + Assert.greater(gURLBar.selectionStart, 0, "Selection start is positive."); + Assert.greater( + gURLBar.selectionEnd, + gURLBar.selectionStart, + "Selection is not empty." + ); + }, + shouldUntrim: true, + }, + { + description: "Test double click selection untrims", + untrimmedValue: BrowserUIUtils.trimURLProtocol + "www.example.com/", + execute() { + selectWithDoubleClick(200); + Assert.greater(gURLBar.selectionStart, 0, "Selection start is positive."); + Assert.greater( + gURLBar.selectionEnd, + gURLBar.selectionStart, + "Selection is not empty." + ); + }, + shouldUntrim: true, + }, + { + description: "Test click, LEFT untrims", + untrimmedValue: BrowserUIUtils.trimURLProtocol + "www.example.com/", + execute() { + EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {}); + EventUtils.synthesizeKey("KEY_ArrowLeft"); + }, + shouldUntrim: true, + }, + { + description: "Test CTRL+L, HOME untrims", + untrimmedValue: BrowserUIUtils.trimURLProtocol + "www.example.com/", + execute() { + EventUtils.synthesizeKey("l", { accelKey: true }); + if (AppConstants.platform == "macosx") { + EventUtils.synthesizeKey("KEY_ArrowLeft", { metaKey: true }); + } else { + EventUtils.synthesizeKey("KEY_Home"); + } + }, + shouldUntrim: true, + }, + { + description: "Test SHIFT+LEFT untrims", + untrimmedValue: BrowserUIUtils.trimURLProtocol + "www.example.com/", + async execute() { + EventUtils.synthesizeKey("l", { accelKey: true }); + EventUtils.synthesizeKey("KEY_ArrowLeft", { shiftKey: true }); + Assert.equal(gURLBar.selectionStart, 0, "Selection start is 0."); + Assert.less( + gURLBar.selectionEnd, + gURLBar.value.length, + "Selection skips last characters." + ); + }, + shouldUntrim: true, + }, +]; + +add_task(async function test_untrim() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.untrimOnUserInteraction.featureGate", true]], + }); + + for (let test of tests) { + info(test.description); + let trimmedValue = UrlbarTestUtils.trimURL(test.untrimmedValue); + gURLBar._setValue(test.untrimmedValue, { + allowTrim: true, + valueIsTyped: false, + }); + gURLBar.blur(); + Assert.equal(gURLBar.value, trimmedValue, "Value has been trimmed"); + await test.execute(); + Assert.equal( + gURLBar.value, + test.shouldUntrim ? test.untrimmedValue : trimmedValue, + "Value has been untrimmed" + ); + gURLBar.handleRevert(); + } +}); diff --git a/browser/components/urlbar/tests/browser/browser_autocomplete_edit_completed.js b/browser/components/urlbar/tests/browser/browser_autocomplete_edit_completed.js index 4fa60f6bf3..220634eb2c 100644 --- a/browser/components/urlbar/tests/browser/browser_autocomplete_edit_completed.js +++ b/browser/components/urlbar/tests/browser/browser_autocomplete_edit_completed.js @@ -51,6 +51,7 @@ add_task(async function () { info("Press backspace"); EventUtils.synthesizeKey("KEY_Backspace"); + info("Backspaced value is " + gURLBar.value); await UrlbarTestUtils.promiseSearchComplete(window); let editedValue = gURLBar.value; diff --git a/browser/components/urlbar/tests/browser/browser_contextualsearch.js b/browser/components/urlbar/tests/browser/browser_contextualsearch.js index 60e489a542..449d1864c2 100644 --- a/browser/components/urlbar/tests/browser/browser_contextualsearch.js +++ b/browser/components/urlbar/tests/browser/browser_contextualsearch.js @@ -3,22 +3,54 @@ "use strict"; -const { UrlbarProviderContextualSearch } = ChromeUtils.importESModule( - "resource:///modules/UrlbarProviderContextualSearch.sys.mjs" +const { ActionsProviderContextualSearch } = ChromeUtils.importESModule( + "resource:///modules/ActionsProviderContextualSearch.sys.mjs" +); + +const { AddonTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/AddonTestUtils.sys.mjs" ); add_setup(async function setup() { await SpecialPowers.pushPrefEnv({ - set: [["browser.urlbar.contextualSearch.enabled", true]], + set: [ + ["browser.urlbar.contextualSearch.enabled", true], + ["browser.urlbar.secondaryActions.featureGate", true], + ], }); -}); -add_task(async function test_selectContextualSearchResult_already_installed() { - await SearchTestUtils.installSearchExtension({ + let ext = await SearchTestUtils.installSearchExtension({ name: "Contextual", search_url: "https://example.com/browser", }); + await AddonTestUtils.waitForSearchProviderStartup(ext); +}); + +add_task(async function test_no_engine() { + const ENGINE_TEST_URL = "https://example.org/"; + let onLoaded = BrowserTestUtils.browserLoaded( + gBrowser.selectedBrowser, + false, + ENGINE_TEST_URL + ); + BrowserTestUtils.startLoadingURIString( + gBrowser.selectedBrowser, + ENGINE_TEST_URL + ); + await onLoaded; + + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "test", + }); + Assert.ok( + UrlbarTestUtils.getResultCount(window) > 0, + "At least one result is shown" + ); +}); + +add_task(async function test_selectContextualSearchResult_already_installed() { const ENGINE_TEST_URL = "https://example.com/"; let onLoaded = BrowserTestUtils.browserLoaded( gBrowser.selectedBrowser, @@ -44,25 +76,15 @@ add_task(async function test_selectContextualSearchResult_already_installed() { window, value: query, }); - const resultIndex = UrlbarTestUtils.getResultCount(window) - 1; - const result = await UrlbarTestUtils.getDetailsOfResultAt( - window, - resultIndex - ); - - is( - result.dynamicType, - "contextualSearch", - "Second last result is a contextual search result" - ); info("Focus and select the contextual search result"); - UrlbarTestUtils.setSelectedRowIndex(window, resultIndex); let onLoad = BrowserTestUtils.browserLoaded( gBrowser.selectedBrowser, false, expectedUrl ); + + EventUtils.synthesizeKey("KEY_Tab"); EventUtils.synthesizeKey("KEY_Enter"); await onLoad; @@ -95,25 +117,14 @@ add_task(async function test_selectContextualSearchResult_not_installed() { window, value: query, }); - const resultIndex = UrlbarTestUtils.getResultCount(window) - 1; - const result = await UrlbarTestUtils.getDetailsOfResultAt( - window, - resultIndex - ); - - Assert.equal( - result.dynamicType, - "contextualSearch", - "Second last result is a contextual search result" - ); info("Focus and select the contextual search result"); - UrlbarTestUtils.setSelectedRowIndex(window, resultIndex); let onLoad = BrowserTestUtils.browserLoaded( gBrowser.selectedBrowser, false, EXPECTED_URL ); + EventUtils.synthesizeKey("KEY_Tab"); EventUtils.synthesizeKey("KEY_Enter"); await onLoad; @@ -122,4 +133,6 @@ add_task(async function test_selectContextualSearchResult_not_installed() { EXPECTED_URL, "Selecting the contextual search result opens the search URL" ); + + ActionsProviderContextualSearch.resetForTesting(); }); diff --git a/browser/components/urlbar/tests/browser/browser_decode.js b/browser/components/urlbar/tests/browser/browser_decode.js index 577d39b587..ee7831eea6 100644 --- a/browser/components/urlbar/tests/browser/browser_decode.js +++ b/browser/components/urlbar/tests/browser/browser_decode.js @@ -72,7 +72,7 @@ add_task(async function actionURILosslessDecode() { Assert.equal( gURLBar.value, - UrlbarTestUtils.trimURL(urlNoScheme), + urlNoScheme, "The string displayed in the textbox should not be escaped" ); diff --git a/browser/components/urlbar/tests/browser/browser_keepStateAcrossTabSwitches.js b/browser/components/urlbar/tests/browser/browser_keepStateAcrossTabSwitches.js index 6ad6ce43e6..abe12846ab 100644 --- a/browser/components/urlbar/tests/browser/browser_keepStateAcrossTabSwitches.js +++ b/browser/components/urlbar/tests/browser/browser_keepStateAcrossTabSwitches.js @@ -3,6 +3,10 @@ "use strict"; +add_setup(async function setup() { + registerCleanupFunction(PlacesUtils.history.clear); +}); + /** * Verify user typed text remains in the URL bar when tab switching, even when * loads fail. @@ -78,94 +82,94 @@ add_task(async function invalidURL() { * Test the urlbar status of text selection and focusing by tab switching. */ add_task(async function selectAndFocus() { - // Create a tab with normal web page. Use a test-url that uses a protocol that - // is not trimmed. - const webpageTabURL = - UrlbarTestUtils.getTrimmedProtocolWithSlashes() == "https://" - ? "http://example.com" - : "https://example.com"; - const webpageTab = await BrowserTestUtils.openNewForegroundTab({ - gBrowser, - url: webpageTabURL, - }); + // Test both protocols to ensure we're testing any trimming case. + for (let protocol of ["http://", "https://"]) { + const webpageTabURL = protocol + "example.com"; + const webpageTab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + url: webpageTabURL, + }); - // Create a tab with userTypedValue. - const userTypedTabText = "test"; - const userTypedTab = await BrowserTestUtils.openNewForegroundTab({ - gBrowser, - }); - await UrlbarTestUtils.inputIntoURLBar(window, userTypedTabText); + // Create a tab with userTypedValue. + const userTypedTabText = "test"; + const userTypedTab = await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + }); + await UrlbarTestUtils.inputIntoURLBar(window, userTypedTabText); - // Create an empty tab. - const emptyTab = await BrowserTestUtils.openNewForegroundTab({ gBrowser }); + // Create an empty tab. + const emptyTab = await BrowserTestUtils.openNewForegroundTab({ gBrowser }); - registerCleanupFunction(async () => { - await PlacesUtils.history.clear(); - BrowserTestUtils.removeTab(webpageTab); - BrowserTestUtils.removeTab(userTypedTab); - BrowserTestUtils.removeTab(emptyTab); - }); + async function cleanup() { + await PlacesUtils.history.clear(); + BrowserTestUtils.removeTab(webpageTab); + BrowserTestUtils.removeTab(userTypedTab); + BrowserTestUtils.removeTab(emptyTab); + } - await doSelectAndFocusTest({ - targetTab: webpageTab, - targetSelectionStart: 0, - targetSelectionEnd: 0, - anotherTab: userTypedTab, - }); - await doSelectAndFocusTest({ - targetTab: webpageTab, - targetSelectionStart: 2, - targetSelectionEnd: 5, - anotherTab: userTypedTab, - }); - await doSelectAndFocusTest({ - targetTab: webpageTab, - targetSelectionStart: webpageTabURL.length, - targetSelectionEnd: webpageTabURL.length, - anotherTab: userTypedTab, - }); - await doSelectAndFocusTest({ - targetTab: webpageTab, - targetSelectionStart: 0, - targetSelectionEnd: 0, - anotherTab: emptyTab, - }); - await doSelectAndFocusTest({ - targetTab: userTypedTab, - targetSelectionStart: 0, - targetSelectionEnd: 0, - anotherTab: webpageTab, - }); - await doSelectAndFocusTest({ - targetTab: userTypedTab, - targetSelectionStart: 0, - targetSelectionEnd: 0, - anotherTab: emptyTab, - }); - await doSelectAndFocusTest({ - targetTab: userTypedTab, - targetSelectionStart: 1, - targetSelectionEnd: 2, - anotherTab: emptyTab, - }); - await doSelectAndFocusTest({ - targetTab: userTypedTab, - targetSelectionStart: userTypedTabText.length, - targetSelectionEnd: userTypedTabText.length, - anotherTab: emptyTab, - }); - await doSelectAndFocusTest({ - targetTab: emptyTab, - targetSelectionStart: 0, - targetSelectionEnd: 0, - anotherTab: webpageTab, - }); - await doSelectAndFocusTest({ - targetTab: emptyTab, - targetSelectionStart: 0, - targetSelectionEnd: 0, - anotherTab: userTypedTab, - }); + await doSelectAndFocusTest({ + targetTab: webpageTab, + targetSelectionStart: 0, + targetSelectionEnd: 0, + anotherTab: userTypedTab, + }); + await doSelectAndFocusTest({ + targetTab: webpageTab, + targetSelectionStart: 2, + targetSelectionEnd: 5, + anotherTab: userTypedTab, + }); + await doSelectAndFocusTest({ + targetTab: webpageTab, + targetSelectionStart: webpageTabURL.length, + targetSelectionEnd: webpageTabURL.length, + anotherTab: userTypedTab, + }); + await doSelectAndFocusTest({ + targetTab: webpageTab, + targetSelectionStart: 0, + targetSelectionEnd: 0, + anotherTab: emptyTab, + }); + await doSelectAndFocusTest({ + targetTab: userTypedTab, + targetSelectionStart: 0, + targetSelectionEnd: 0, + anotherTab: webpageTab, + }); + await doSelectAndFocusTest({ + targetTab: userTypedTab, + targetSelectionStart: 0, + targetSelectionEnd: 0, + anotherTab: emptyTab, + }); + await doSelectAndFocusTest({ + targetTab: userTypedTab, + targetSelectionStart: 1, + targetSelectionEnd: 2, + anotherTab: emptyTab, + }); + await doSelectAndFocusTest({ + targetTab: userTypedTab, + targetSelectionStart: userTypedTabText.length, + targetSelectionEnd: userTypedTabText.length, + anotherTab: emptyTab, + }); + await doSelectAndFocusTest({ + targetTab: emptyTab, + targetSelectionStart: 0, + targetSelectionEnd: 0, + anotherTab: webpageTab, + }); + await doSelectAndFocusTest({ + targetTab: emptyTab, + targetSelectionStart: 0, + targetSelectionEnd: 0, + anotherTab: userTypedTab, + }); + + await cleanup(); + } }); async function doSelectAndFocusTest({ @@ -197,6 +201,13 @@ async function doSelectAndFocusTest({ targetSelectionStart, targetSelectionEnd ); + const targetSelectedText = getSelectedText(); + if (gURLBar.selectionStart != gURLBar.selectionEnd) { + Assert.ok( + targetSelectedText, + `Some text is selected: "${targetSelectedText}"` + ); + } const targetValue = gURLBar.value; // Switch to another tab. @@ -210,8 +221,9 @@ async function doSelectAndFocusTest({ Assert.equal(gURLBar.value, targetValue); Assert.equal(gURLBar.focused, targetFocus); if (gURLBar.focused) { - Assert.equal(gURLBar.selectionStart, targetSelectionStart); - Assert.equal(gURLBar.selectionEnd, targetSelectionEnd); + // Check the selected text rather than the selection indices, to keep + // untrimming into account. + Assert.equal(targetSelectedText, getSelectedText()); } else { Assert.equal(gURLBar.selectionStart, gURLBar.value.length); Assert.equal(gURLBar.selectionEnd, gURLBar.value.length); @@ -221,12 +233,21 @@ async function doSelectAndFocusTest({ function setURLBarFocus(focus) { if (focus) { - gURLBar.focus(); + // Simulate a user interaction, to eventually cause untrimming. + EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {}); } else { gURLBar.blur(); } } +function getSelectedText() { + return gURLBar.inputField.editor.selection.toStringWithFormat( + "text/plain", + Ci.nsIDocumentEncoder.OutputPreformatted | Ci.nsIDocumentEncoder.OutputRaw, + 0 + ); +} + async function switchTab(tab) { if (gBrowser.selectedTab !== tab) { EventUtils.synthesizeMouseAtCenter(tab, {}); diff --git a/browser/components/urlbar/tests/browser/browser_less_common_selection_manipulations.js b/browser/components/urlbar/tests/browser/browser_less_common_selection_manipulations.js index 2ad6ee0e07..44a7ea64b2 100644 --- a/browser/components/urlbar/tests/browser/browser_less_common_selection_manipulations.js +++ b/browser/components/urlbar/tests/browser/browser_less_common_selection_manipulations.js @@ -238,51 +238,3 @@ function getTextWidth(inputText) { .getPropertyValue("font"); return context.measureText(inputText).width; } - -function selectWithMouseDrag(fromX, toX) { - let target = gURLBar.inputField; - let rect = target.getBoundingClientRect(); - let promise = BrowserTestUtils.waitForEvent(target, "mouseup"); - EventUtils.synthesizeMouse( - target, - fromX, - rect.height / 2, - { type: "mousemove" }, - target.ownerGlobal - ); - EventUtils.synthesizeMouse( - target, - fromX, - rect.height / 2, - { type: "mousedown" }, - target.ownerGlobal - ); - EventUtils.synthesizeMouse( - target, - toX, - rect.height / 2, - { type: "mousemove" }, - target.ownerGlobal - ); - EventUtils.synthesizeMouse( - target, - toX, - rect.height / 2, - { type: "mouseup" }, - target.ownerGlobal - ); - return promise; -} - -function selectWithDoubleClick(offsetX) { - let target = gURLBar.inputField; - let rect = target.getBoundingClientRect(); - let promise = BrowserTestUtils.waitForEvent(target, "dblclick"); - EventUtils.synthesizeMouse(target, offsetX, rect.height / 2, { - clickCount: 1, - }); - EventUtils.synthesizeMouse(target, offsetX, rect.height / 2, { - clickCount: 2, - }); - return promise; -} diff --git a/browser/components/urlbar/tests/browser/browser_oneOffs.js b/browser/components/urlbar/tests/browser/browser_oneOffs.js index 0c04f1e321..e517ea0a9a 100644 --- a/browser/components/urlbar/tests/browser/browser_oneOffs.js +++ b/browser/components/urlbar/tests/browser/browser_oneOffs.js @@ -30,7 +30,6 @@ add_setup(async function () { set: [ ["browser.search.separatePrivateDefault.ui.enabled", false], ["browser.urlbar.suggest.quickactions", false], - ["browser.urlbar.shortcuts.quickactions", true], ], }); @@ -943,7 +942,7 @@ async function doLocalShortcutsShownTest() { await rebuildPromise; let buttons = oneOffSearchButtons.localButtons; - Assert.equal(buttons.length, 4, "Expected number of local shortcuts"); + Assert.equal(buttons.length, 3, "Expected number of local shortcuts"); let expectedSource; let seenIDs = new Set(); @@ -963,9 +962,6 @@ async function doLocalShortcutsShownTest() { case "urlbar-engine-one-off-item-history": expectedSource = UrlbarUtils.RESULT_SOURCE.HISTORY; break; - case "urlbar-engine-one-off-item-actions": - expectedSource = UrlbarUtils.RESULT_SOURCE.ACTIONS; - break; default: Assert.ok(false, `Unexpected local shortcut ID: ${button.id}`); break; diff --git a/browser/components/urlbar/tests/browser/browser_quickactions.js b/browser/components/urlbar/tests/browser/browser_quickactions.js index ccf045d9e8..1fc4ef7cd6 100644 --- a/browser/components/urlbar/tests/browser/browser_quickactions.js +++ b/browser/components/urlbar/tests/browser/browser_quickactions.js @@ -10,32 +10,41 @@ ChromeUtils.defineESModuleGetters(this, { AppConstants: "resource://gre/modules/AppConstants.sys.mjs", UpdateService: "resource://gre/modules/UpdateService.sys.mjs", - UrlbarProviderQuickActions: - "resource:///modules/UrlbarProviderQuickActions.sys.mjs", + ActionsProviderQuickActions: + "resource:///modules/ActionsProviderQuickActions.sys.mjs", }); const DUMMY_PAGE = - "http://example.com/browser/browser/base/content/test/general/dummy_page.html"; + "https://example.com/browser/browser/base/content/test/general/dummy_page.html"; let testActionCalled = 0; +const assertAction = async name => { + await BrowserTestUtils.waitForCondition(() => + window.document.querySelector(`.urlbarView-action-btn[data-action=${name}]`) + ); + Assert.ok(true, `We found action "${name}`); +}; + +const hasQuickActions = win => + !!win.document.querySelector(".urlbarView-action-btn"); + add_setup(async function setup() { await SpecialPowers.pushPrefEnv({ set: [ ["browser.urlbar.quickactions.enabled", true], - ["browser.urlbar.suggest.quickactions", true], - ["browser.urlbar.shortcuts.quickactions", true], + ["browser.urlbar.secondaryActions.featureGate", true], ], }); - UrlbarProviderQuickActions.addAction("testaction", { + ActionsProviderQuickActions.addAction("testaction", { commands: ["testaction"], label: "quickactions-downloads2", onPick: () => testActionCalled++, }); registerCleanupFunction(() => { - UrlbarProviderQuickActions.removeAction("testaction"); + ActionsProviderQuickActions.removeAction("testaction"); }); }); @@ -57,183 +66,16 @@ add_task(async function basic() { value: "testact", }); - Assert.equal( - UrlbarTestUtils.getResultCount(window), - 2, - "We matched the action" - ); + await assertAction("testaction"); info("The callback of the action is fired when selected"); - EventUtils.synthesizeKey("KEY_ArrowDown", {}, window); + EventUtils.synthesizeKey("KEY_Tab", {}, window); EventUtils.synthesizeKey("KEY_Enter", {}, window); - Assert.equal(testActionCalled, 1, "Test actionwas called"); -}); - -add_task(async function test_label_command() { - info("A prefix of the label matches"); - await UrlbarTestUtils.promiseAutocompleteResultPopup({ - window, - value: "View Dow", - }); - Assert.equal( - UrlbarTestUtils.getResultCount(window), - 2, - "We matched the action" - ); - - let { result } = await UrlbarTestUtils.getDetailsOfResultAt(window, 1); - Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.DYNAMIC); - Assert.equal(result.providerName, "quickactions"); - await UrlbarTestUtils.promisePopupClose(window, () => { - EventUtils.synthesizeKey("KEY_Escape"); - }); -}); - -add_task(async function enter_search_mode_button() { - await UrlbarTestUtils.promiseAutocompleteResultPopup({ - window, - value: "test", - }); - - await clickQuickActionOneoffButton(); - - await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0); - Assert.ok(true, "Actions are shown when we enter actions search mode."); - - await UrlbarTestUtils.exitSearchMode(window); - await UrlbarTestUtils.promisePopupClose(window); - EventUtils.synthesizeKey("KEY_Escape"); -}); - -add_task(async function enter_search_mode_oneoff_by_key() { - // Select actions oneoff button by keyboard. - await UrlbarTestUtils.promiseAutocompleteResultPopup({ - window, - value: "", - }); - await UrlbarTestUtils.enterSearchMode(window); - const oneOffButtons = UrlbarTestUtils.getOneOffSearchButtons(window); - for (;;) { - EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true }); - if ( - oneOffButtons.selectedButton.source === UrlbarUtils.RESULT_SOURCE.ACTIONS - ) { - break; - } - } - - await UrlbarTestUtils.promiseAutocompleteResultPopup({ - window, - value: " ", - }); - await UrlbarTestUtils.assertSearchMode(window, { - source: UrlbarUtils.RESULT_SOURCE.ACTIONS, - entry: "oneoff", - }); - - await UrlbarTestUtils.exitSearchMode(window); - await UrlbarTestUtils.promisePopupClose(window); - EventUtils.synthesizeKey("KEY_Escape"); -}); - -add_task(async function enter_search_mode_key() { - await UrlbarTestUtils.promiseAutocompleteResultPopup({ - window, - value: "> ", - }); - await UrlbarTestUtils.assertSearchMode(window, { - source: UrlbarUtils.RESULT_SOURCE.ACTIONS, - entry: "typed", - }); - Assert.equal( - await hasQuickActions(window), - true, - "Actions are shown in search mode" - ); - await UrlbarTestUtils.exitSearchMode(window); - await UrlbarTestUtils.promisePopupClose(window); - EventUtils.synthesizeKey("KEY_Escape"); -}); - -add_task(async function test_disabled() { - UrlbarProviderQuickActions.addAction("disabledaction", { - commands: ["disabledaction"], - isActive: () => false, - label: "quickactions-restart", - }); - - await UrlbarTestUtils.promiseAutocompleteResultPopup({ - window, - value: "disabled", - }); - - Assert.equal( - await hasQuickActions(window), - false, - "Result for quick actions is hidden" - ); - - await UrlbarTestUtils.promisePopupClose(window); - UrlbarProviderQuickActions.removeAction("disabledaction"); -}); - -/** - * The first part of this test confirms that when the screenshots component is enabled - * the screenshot quick action button will be enabled on about: pages. - * The second part confirms that when the screenshots extension is enabled the - * screenshot quick action button will be disbaled on about: pages. - */ -add_task(async function test_screenshot_enabled_or_disabled() { - let onLoaded = BrowserTestUtils.browserLoaded( - gBrowser.selectedBrowser, - false, - "about:blank" - ); - BrowserTestUtils.startLoadingURIString( - gBrowser.selectedBrowser, - "about:blank" - ); - await onLoaded; - - await UrlbarTestUtils.promiseAutocompleteResultPopup({ - window, - value: "screenshot", - }); - Assert.equal( - UrlbarTestUtils.getResultCount(window), - 2, - "The action is displayed" - ); - let screenshotButton = window.document.querySelector( - ".urlbarView-row[dynamicType=quickactions] .urlbarView-quickaction-button" - ); - Assert.ok( - !screenshotButton.hasAttribute("disabled"), - "Screenshot button is enabled on about pages" - ); - - await UrlbarTestUtils.promisePopupClose(window); - EventUtils.synthesizeKey("KEY_Escape"); - - await SpecialPowers.pushPrefEnv({ - set: [["screenshots.browser.component.enabled", false]], - }); - - await UrlbarTestUtils.promiseAutocompleteResultPopup({ - window, - value: "screenshot", - }); - Assert.equal( - await hasQuickActions(window), - false, - "Result for quick actions is hidden" - ); - - await UrlbarTestUtils.promisePopupClose(window); + Assert.equal(testActionCalled, 1, "Test action was called"); }); add_task(async function match_in_phrase() { - UrlbarProviderQuickActions.addAction("newtestaction", { + ActionsProviderQuickActions.addAction("newtestaction", { commands: ["matchingstring"], label: "quickactions-downloads2", }); @@ -243,304 +85,31 @@ add_task(async function match_in_phrase() { window, value: "Test we match at end of matchingstring", }); - Assert.equal( - UrlbarTestUtils.getResultCount(window), - 2, - "We matched the action" - ); - await UrlbarTestUtils.promisePopupClose(window); - EventUtils.synthesizeKey("KEY_Escape"); - UrlbarProviderQuickActions.removeAction("newtestaction"); -}); - -add_task(async function test_other_search_mode() { - let defaultEngine = await SearchTestUtils.promiseNewSearchEngine({ - url: getRootDirectory(gTestPath) + "searchSuggestionEngine.xml", - }); - defaultEngine.alias = "testalias"; - let oldDefaultEngine = await Services.search.getDefault(); - Services.search.setDefault( - defaultEngine, - Ci.nsISearchService.CHANGE_REASON_UNKNOWN - ); - - await UrlbarTestUtils.promiseAutocompleteResultPopup({ - window, - value: defaultEngine.alias + " ", - }); - Assert.equal( - UrlbarTestUtils.getResultCount(window), - 0, - "The results should be empty as no actions are displayed in other search modes" - ); - await UrlbarTestUtils.assertSearchMode(window, { - engineName: defaultEngine.name, - entry: "typed", - }); - await UrlbarTestUtils.promisePopupClose(window, () => { - EventUtils.synthesizeKey("KEY_Escape"); - }); - Services.search.setDefault( - oldDefaultEngine, - Ci.nsISearchService.CHANGE_REASON_UNKNOWN - ); -}); - -add_task(async function test_no_quickactions_suggestions() { - await SpecialPowers.pushPrefEnv({ - set: [ - ["browser.urlbar.suggest.quickactions", false], - ["screenshots.browser.component.enabled", true], - ], - }); - await UrlbarTestUtils.promiseAutocompleteResultPopup({ - window, - value: "screenshot", - }); - Assert.ok( - !window.document.querySelector( - ".urlbarView-row[dynamicType=quickactions] .urlbarView-quickaction-button" - ), - "Screenshot button is not suggested" - ); - - await UrlbarTestUtils.promiseAutocompleteResultPopup({ - window, - value: "> screenshot", - }); - Assert.ok( - window.document.querySelector( - ".urlbarView-row[dynamicType=quickactions] .urlbarView-quickaction-button" - ), - "Screenshot button is suggested" - ); - - await UrlbarTestUtils.promisePopupClose(window); - EventUtils.synthesizeKey("KEY_Escape"); - - await SpecialPowers.popPrefEnv(); -}); - -add_task(async function test_quickactions_disabled() { - await SpecialPowers.pushPrefEnv({ - set: [ - ["browser.urlbar.quickactions.enabled", false], - ["browser.urlbar.suggest.quickactions", true], - ], - }); - await UrlbarTestUtils.promiseAutocompleteResultPopup({ - window, - value: "screenshot", - }); - - Assert.ok( - !window.document.querySelector( - ".urlbarView-row[dynamicType=quickactions] .urlbarView-quickaction-button" - ), - "Screenshot button is not suggested" - ); - - await UrlbarTestUtils.promiseAutocompleteResultPopup({ - window, - value: "> screenshot", - }); - Assert.ok( - !window.document.querySelector( - ".urlbarView-row[dynamicType=quickactions] .urlbarView-quickaction-button" - ), - "Screenshot button is not suggested" - ); - + await assertAction("newtestaction"); await UrlbarTestUtils.promisePopupClose(window); EventUtils.synthesizeKey("KEY_Escape"); - - await SpecialPowers.popPrefEnv(); -}); - -let COMMANDS_TESTS = [ - { - cmd: "add-ons", - uri: "about:addons", - testFun: async () => isSelected("button[name=discover]"), - }, - { - cmd: "plugins", - uri: "about:addons", - testFun: async () => isSelected("button[name=plugin]"), - }, - { - cmd: "extensions", - uri: "about:addons", - testFun: async () => isSelected("button[name=extension]"), - }, - { - cmd: "themes", - uri: "about:addons", - testFun: async () => isSelected("button[name=theme]"), - }, - { - cmd: "add-ons", - setup: async () => { - const onLoad = BrowserTestUtils.browserLoaded( - gBrowser.selectedBrowser, - false, - "http://example.com/" - ); - BrowserTestUtils.startLoadingURIString( - gBrowser.selectedBrowser, - "http://example.com/" - ); - await onLoad; - }, - uri: "about:addons", - isNewTab: true, - testFun: async () => isSelected("button[name=discover]"), - }, - { - cmd: "plugins", - setup: async () => { - const onLoad = BrowserTestUtils.browserLoaded( - gBrowser.selectedBrowser, - false, - "http://example.com/" - ); - BrowserTestUtils.startLoadingURIString( - gBrowser.selectedBrowser, - "http://example.com/" - ); - await onLoad; - }, - uri: "about:addons", - isNewTab: true, - testFun: async () => isSelected("button[name=plugin]"), - }, - { - cmd: "extensions", - setup: async () => { - const onLoad = BrowserTestUtils.browserLoaded( - gBrowser.selectedBrowser, - false, - "http://example.com/" - ); - BrowserTestUtils.startLoadingURIString( - gBrowser.selectedBrowser, - "http://example.com/" - ); - await onLoad; - }, - uri: "about:addons", - isNewTab: true, - testFun: async () => isSelected("button[name=extension]"), - }, - { - cmd: "themes", - setup: async () => { - const onLoad = BrowserTestUtils.browserLoaded( - gBrowser.selectedBrowser, - false, - "http://example.com/" - ); - BrowserTestUtils.startLoadingURIString( - gBrowser.selectedBrowser, - "http://example.com/" - ); - await onLoad; - }, - uri: "about:addons", - isNewTab: true, - testFun: async () => isSelected("button[name=theme]"), - }, -]; - -let isSelected = async selector => - SpecialPowers.spawn(gBrowser.selectedBrowser, [selector], arg => { - return ContentTaskUtils.waitForCondition(() => - content.document.querySelector(arg)?.hasAttribute("selected") - ); - }); - -add_task(async function test_pages() { - for (const { cmd, uri, setup, isNewTab, testFun } of COMMANDS_TESTS) { - info(`Testing ${cmd} command is triggered`); - let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); - - if (setup) { - info("Setup"); - await setup(); - } - - let onLoad = isNewTab - ? BrowserTestUtils.waitForNewTab(gBrowser, uri, true) - : BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, uri); - - await UrlbarTestUtils.promiseAutocompleteResultPopup({ - window, - value: cmd, - }); - EventUtils.synthesizeKey("KEY_ArrowDown", {}, window); - EventUtils.synthesizeKey("KEY_Enter", {}, window); - - const newTab = await onLoad; - - Assert.ok( - await testFun(), - `The command "${cmd}" passed completed its test` - ); - - if (isNewTab) { - await BrowserTestUtils.removeTab(newTab); - } - await BrowserTestUtils.removeTab(tab); - } + ActionsProviderQuickActions.removeAction("newtestaction"); }); -const assertActionButtonStatus = async (name, expectedEnabled, description) => { - await BrowserTestUtils.waitForCondition(() => - window.document.querySelector(`[data-key=${name}]`) - ); - const target = window.document.querySelector(`[data-key=${name}]`); - Assert.equal(!target.hasAttribute("disabled"), expectedEnabled, description); -}; - add_task(async function test_viewsource() { info("Check the button status of when the page is not web content"); const tab = await BrowserTestUtils.openNewForegroundTab({ gBrowser, - opening: "about:home", + opening: "https://example.com", waitForLoad: true, }); - await UrlbarTestUtils.promiseAutocompleteResultPopup({ - window, - value: "viewsource", - }); - await assertActionButtonStatus( - "viewsource", - true, - "Should be enabled even if the page is not web content" - ); - info("Check the button status of when the page is web content"); - BrowserTestUtils.startLoadingURIString( - gBrowser.selectedBrowser, - "http://example.com" - ); - await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "viewsource", }); - await assertActionButtonStatus( - "viewsource", - true, - "Should be enabled on web content as well" - ); info("Do view source action"); const onLoad = BrowserTestUtils.waitForNewTab( gBrowser, - "view-source:http://example.com/" + "view-source:https://example.com/" ); - EventUtils.synthesizeKey("KEY_ArrowDown", {}, window); + EventUtils.synthesizeKey("KEY_Tab", {}, window); EventUtils.synthesizeKey("KEY_Enter", {}, window); const viewSourceTab = await onLoad; @@ -551,7 +120,7 @@ add_task(async function test_viewsource() { }); Assert.equal( - await hasQuickActions(window), + hasQuickActions(window), false, "Result for quick actions is hidden" ); @@ -575,7 +144,7 @@ async function doAlertDialogTest({ input, dialogContentURI }) { }, }); - EventUtils.synthesizeKey("KEY_ArrowDown", {}, window); + EventUtils.synthesizeKey("KEY_Tab", {}, window); EventUtils.synthesizeKey("KEY_Enter", {}, window); await onDialog; @@ -601,16 +170,16 @@ add_task(async function test_clear() { }); }); -async function doUpdateActionTest(isActiveExpected, description) { +async function doUpdateActionTest(isActiveExpected) { await UrlbarTestUtils.promiseAutocompleteResultPopup({ window, value: "update", }); if (isActiveExpected) { - await assertActionButtonStatus("update", isActiveExpected, description); + await assertAction("update"); } else { - Assert.equal(await hasQuickActions(window), false, description); + Assert.equal(hasQuickActions(window), false, "No QuickActions were shown"); } } @@ -644,43 +213,6 @@ add_task(async function test_update() { } }); -async function hasQuickActions(win) { - for (let i = 0, count = UrlbarTestUtils.getResultCount(win); i < count; i++) { - const { result } = await UrlbarTestUtils.getDetailsOfResultAt(win, i); - if (result.providerName === "quickactions") { - return true; - } - } - return false; -} - -add_task(async function test_show_in_zero_prefix() { - for (const minimumSearchString of [0, 3]) { - info( - `Test when quickactions.minimumSearchString pref is ${minimumSearchString}` - ); - await SpecialPowers.pushPrefEnv({ - set: [ - [ - "browser.urlbar.quickactions.minimumSearchString", - minimumSearchString, - ], - ], - }); - await UrlbarTestUtils.promiseAutocompleteResultPopup({ - window, - value: "", - }); - - Assert.equal( - await hasQuickActions(window), - !minimumSearchString, - "Result for quick actions is as expected" - ); - await SpecialPowers.popPrefEnv(); - } -}); - add_task(async function test_whitespace() { info("Test with quickactions.showInZeroPrefix pref is false"); await SpecialPowers.pushPrefEnv({ @@ -691,7 +223,7 @@ add_task(async function test_whitespace() { value: " ", }); Assert.equal( - await hasQuickActions(window), + hasQuickActions(window), false, "Result for quick actions is not shown" ); diff --git a/browser/components/urlbar/tests/browser/browser_quickactions_commands.js b/browser/components/urlbar/tests/browser/browser_quickactions_commands.js new file mode 100644 index 0000000000..19b8d31ada --- /dev/null +++ b/browser/components/urlbar/tests/browser/browser_quickactions_commands.js @@ -0,0 +1,154 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test QuickActions. + */ + +"use strict"; + +add_setup(async function setup() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.quickactions.enabled", true], + ["browser.urlbar.secondaryActions.featureGate", true], + ], + }); +}); + +let COMMANDS_TESTS = [ + { + cmd: "add-ons", + uri: "about:addons", + testFun: async () => isSelected("button[name=discover]"), + }, + { + cmd: "plugins", + uri: "about:addons", + testFun: async () => isSelected("button[name=plugin]"), + }, + { + cmd: "extensions", + uri: "about:addons", + testFun: async () => isSelected("button[name=extension]"), + }, + { + cmd: "themes", + uri: "about:addons", + testFun: async () => isSelected("button[name=theme]"), + }, + { + cmd: "add-ons", + setup: async () => { + const onLoad = BrowserTestUtils.browserLoaded( + gBrowser.selectedBrowser, + false, + "https://example.com/" + ); + BrowserTestUtils.startLoadingURIString( + gBrowser.selectedBrowser, + "https://example.com/" + ); + await onLoad; + }, + uri: "about:addons", + isNewTab: true, + testFun: async () => isSelected("button[name=discover]"), + }, + { + cmd: "plugins", + setup: async () => { + const onLoad = BrowserTestUtils.browserLoaded( + gBrowser.selectedBrowser, + false, + "https://example.com/" + ); + BrowserTestUtils.startLoadingURIString( + gBrowser.selectedBrowser, + "https://example.com/" + ); + await onLoad; + }, + uri: "about:addons", + isNewTab: true, + testFun: async () => isSelected("button[name=plugin]"), + }, + { + cmd: "extensions", + setup: async () => { + const onLoad = BrowserTestUtils.browserLoaded( + gBrowser.selectedBrowser, + false, + "https://example.com/" + ); + BrowserTestUtils.startLoadingURIString( + gBrowser.selectedBrowser, + "https://example.com/" + ); + await onLoad; + }, + uri: "about:addons", + isNewTab: true, + testFun: async () => isSelected("button[name=extension]"), + }, + { + cmd: "themes", + setup: async () => { + const onLoad = BrowserTestUtils.browserLoaded( + gBrowser.selectedBrowser, + false, + "https://example.com/" + ); + BrowserTestUtils.startLoadingURIString( + gBrowser.selectedBrowser, + "https://example.com/" + ); + await onLoad; + }, + uri: "about:addons", + isNewTab: true, + testFun: async () => isSelected("button[name=theme]"), + }, +]; + +let isSelected = async selector => + SpecialPowers.spawn(gBrowser.selectedBrowser, [selector], arg => { + return ContentTaskUtils.waitForCondition(() => + content.document.querySelector(arg)?.hasAttribute("selected") + ); + }); + +add_task(async function test_pages() { + for (const { cmd, uri, setup, isNewTab, testFun } of COMMANDS_TESTS) { + info(`Testing ${cmd} command is triggered`); + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser); + + if (setup) { + info("Setup"); + await setup(); + } + + let onLoad = isNewTab + ? BrowserTestUtils.waitForNewTab(gBrowser, uri, true) + : BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser, false, uri); + + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: cmd, + }); + EventUtils.synthesizeKey("KEY_Tab", {}, window); + EventUtils.synthesizeKey("KEY_Enter", {}, window); + + const newTab = await onLoad; + + Assert.ok( + await testFun(), + `The command "${cmd}" passed completed its test` + ); + + if (isNewTab) { + await BrowserTestUtils.removeTab(newTab); + } + await BrowserTestUtils.removeTab(tab); + } +}); diff --git a/browser/components/urlbar/tests/browser/browser_quickactions_devtools.js b/browser/components/urlbar/tests/browser/browser_quickactions_devtools.js index 1e1e92fb31..bee98d42af 100644 --- a/browser/components/urlbar/tests/browser/browser_quickactions_devtools.js +++ b/browser/components/urlbar/tests/browser/browser_quickactions_devtools.js @@ -17,7 +17,7 @@ add_setup(async function setup() { await SpecialPowers.pushPrefEnv({ set: [ ["browser.urlbar.quickactions.enabled", true], - ["browser.urlbar.suggest.quickactions", true], + ["browser.urlbar.secondaryActions.featureGate", true], ["browser.urlbar.shortcuts.quickactions", true], ], }); @@ -25,21 +25,14 @@ add_setup(async function setup() { const assertActionButtonStatus = async (name, expectedEnabled, description) => { await BrowserTestUtils.waitForCondition(() => - window.document.querySelector(`[data-key=${name}]`) + window.document.querySelector(`[data-action=${name}]`) ); - const target = window.document.querySelector(`[data-key=${name}]`); + const target = window.document.querySelector(`[data-action=${name}]`); Assert.equal(!target.hasAttribute("disabled"), expectedEnabled, description); }; -async function hasQuickActions(win) { - for (let i = 0, count = UrlbarTestUtils.getResultCount(win); i < count; i++) { - const { result } = await UrlbarTestUtils.getDetailsOfResultAt(win, i); - if (result.providerName === "quickactions") { - return true; - } - } - return false; -} +const hasQuickActions = win => + !!win.document.querySelector(".urlbarView-action-btn"); add_task(async function test_inspector() { const testData = [ @@ -129,7 +122,7 @@ add_task(async function test_inspector() { ); } else { Assert.equal( - await hasQuickActions(window), + hasQuickActions(window), false, "Result for quick actions is not shown since the inspector tool is disabled" ); @@ -142,7 +135,7 @@ add_task(async function test_inspector() { } info("Do inspect action"); - EventUtils.synthesizeKey("KEY_ArrowDown", {}, window); + EventUtils.synthesizeKey("KEY_Tab", {}, window); EventUtils.synthesizeKey("KEY_Enter", {}, window); await BrowserTestUtils.waitForCondition( () => DevToolsShim.hasToolboxForTab(gBrowser.selectedTab), @@ -160,7 +153,7 @@ add_task(async function test_inspector() { value: "inspector", }); Assert.equal( - await hasQuickActions(window), + hasQuickActions(window), false, "Result for quick actions is not shown since the inspector is already opening" ); diff --git a/browser/components/urlbar/tests/browser/browser_quickactions_screenshot.js b/browser/components/urlbar/tests/browser/browser_quickactions_screenshot.js index c81442f0f5..c83fdff441 100644 --- a/browser/components/urlbar/tests/browser/browser_quickactions_screenshot.js +++ b/browser/components/urlbar/tests/browser/browser_quickactions_screenshot.js @@ -34,11 +34,18 @@ async function clickQuickActionOneoffButton() { }); } +const assertAction = async name => { + await BrowserTestUtils.waitForCondition(() => + window.document.querySelector(`.urlbarView-action-btn[data-action=${name}]`) + ); + Assert.ok(true, `We found action "${name}`); +}; + add_setup(async function setup() { await SpecialPowers.pushPrefEnv({ set: [ ["browser.urlbar.quickactions.enabled", true], - ["browser.urlbar.suggest.quickactions", true], + ["browser.urlbar.secondaryActions.featureGate", true], ["browser.urlbar.shortcuts.quickactions", true], ], }); @@ -61,17 +68,10 @@ add_task(async function test_screenshot() { window, value: "screenshot", }); - Assert.equal( - UrlbarTestUtils.getResultCount(window), - 2, - "We matched the action" - ); - let { result } = await UrlbarTestUtils.getDetailsOfResultAt(window, 1); - Assert.equal(result.type, UrlbarUtils.RESULT_TYPE.DYNAMIC); - Assert.equal(result.providerName, "quickactions"); + await assertAction("screenshot"); info("Trigger the screenshot mode"); - EventUtils.synthesizeKey("KEY_ArrowDown", {}, window); + EventUtils.synthesizeKey("KEY_Tab", {}, window); EventUtils.synthesizeKey("KEY_Enter", {}, window); await TestUtils.waitForCondition( isScreenshotInitialized, @@ -104,38 +104,6 @@ add_task(async function search_mode_on_webpage() { }); await UrlbarTestUtils.promiseSearchComplete(window); - info("Enter quick action search mode"); - await clickQuickActionOneoffButton(); - await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0); - Assert.ok(true, "Actions are shown when we enter actions search mode."); - - info("Trigger the screenshot mode"); - const initialActionButtons = window.document.querySelectorAll( - ".urlbarView-row[dynamicType=quickactions] .urlbarView-quickaction-button" - ); - let screenshotButton; - for (let i = 0; i < initialActionButtons.length; i++) { - const item = initialActionButtons.item(i); - if (item.dataset.key === "screenshot") { - screenshotButton = item; - break; - } - } - EventUtils.synthesizeMouseAtCenter(screenshotButton, {}, window); - await TestUtils.waitForCondition( - isScreenshotInitialized, - "Screenshot component is active", - 200, - 100 - ); - - info("Press Escape to exit screenshot mode"); - EventUtils.synthesizeKey("KEY_Escape", {}, window); - await TestUtils.waitForCondition( - async () => !(await isScreenshotInitialized()), - "Screenshot component has been dismissed" - ); - info("Check the urlbar state"); Assert.equal(gURLBar.value, UrlbarTestUtils.trimURL("https://example.com")); Assert.equal(gURLBar.getAttribute("pageproxystate"), "valid"); @@ -146,23 +114,7 @@ add_task(async function search_mode_on_webpage() { }); await UrlbarTestUtils.promiseSearchComplete(window); - info("Enter quick action search mode again"); - await clickQuickActionOneoffButton(); - await UrlbarTestUtils.waitForAutocompleteResultAt(window, 0); - const finalActionButtons = window.document.querySelectorAll( - ".urlbarView-row[dynamicType=quickactions] .urlbarView-quickaction-button" - ); - - info("Check the action buttons and the urlbar"); - Assert.equal( - finalActionButtons.length, - initialActionButtons.length, - "The same buttons as initially displayed will display" - ); - Assert.equal(gURLBar.value, ""); - info("Clean up"); - await UrlbarTestUtils.exitSearchMode(window); await UrlbarTestUtils.promisePopupClose(window); EventUtils.synthesizeKey("KEY_Escape"); BrowserTestUtils.removeTab(tab); diff --git a/browser/components/urlbar/tests/browser/browser_quickactions_tab_refocus.js b/browser/components/urlbar/tests/browser/browser_quickactions_tab_refocus.js index abac861931..ee1f3cab62 100644 --- a/browser/components/urlbar/tests/browser/browser_quickactions_tab_refocus.js +++ b/browser/components/urlbar/tests/browser/browser_quickactions_tab_refocus.js @@ -13,7 +13,7 @@ add_setup(async function setup() { await SpecialPowers.pushPrefEnv({ set: [ ["browser.urlbar.quickactions.enabled", true], - ["browser.urlbar.suggest.quickactions", true], + ["browser.urlbar.secondaryActions.featureGate", true], ["browser.urlbar.shortcuts.quickactions", true], ], }); @@ -90,7 +90,7 @@ add_task(async function test_about_pages() { window, value: firstInput, }); - EventUtils.synthesizeKey("KEY_ArrowDown", {}, window); + EventUtils.synthesizeKey("KEY_Tab", {}, window); EventUtils.synthesizeKey("KEY_Enter", {}, window); } await onLoad; @@ -106,7 +106,7 @@ add_task(async function test_about_pages() { window, value: secondInput || firstInput, }); - EventUtils.synthesizeKey("KEY_ArrowDown", {}, window); + EventUtils.synthesizeKey("KEY_Tab", {}, window); EventUtils.synthesizeKey("KEY_Enter", {}, window); Assert.equal( gBrowser.selectedTab, @@ -158,7 +158,7 @@ add_task(async function test_about_addons_pages() { window, value: cmd, }); - EventUtils.synthesizeKey("KEY_ArrowDown", {}, window); + EventUtils.synthesizeKey("KEY_Tab", {}, window); EventUtils.synthesizeKey("KEY_Enter", {}, window); await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); Assert.ok(await testFun(), "The page content is correct"); @@ -175,7 +175,7 @@ add_task(async function test_about_addons_pages() { window, value: cmd, }); - EventUtils.synthesizeKey("KEY_ArrowDown", {}, window); + EventUtils.synthesizeKey("KEY_Tab", {}, window); EventUtils.synthesizeKey("KEY_Enter", {}, window); await BrowserTestUtils.waitForCondition(() => testFun()); Assert.ok(true, "The tab correspondent action is selected"); diff --git a/browser/components/urlbar/tests/browser/browser_secondaryActions.js b/browser/components/urlbar/tests/browser/browser_secondaryActions.js new file mode 100644 index 0000000000..7e03ae036c --- /dev/null +++ b/browser/components/urlbar/tests/browser/browser_secondaryActions.js @@ -0,0 +1,141 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Basic tests for secondary Actions. + */ + +"use strict"; + +ChromeUtils.defineESModuleGetters(this, { + ActionsProviderQuickActions: + "resource:///modules/ActionsProviderQuickActions.sys.mjs", +}); + +let testActionCalled = 0; + +let loadURI = async (browser, uri) => { + let onLoaded = BrowserTestUtils.browserLoaded(browser, false, uri); + BrowserTestUtils.startLoadingURIString(browser, uri); + return onLoaded; +}; + +add_setup(async function setup() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.urlbar.quickactions.enabled", true], + ["browser.urlbar.secondaryActions.featureGate", true], + ["browser.urlbar.contextualSearch.enabled", true], + ], + }); + + ActionsProviderQuickActions.addAction("testaction", { + commands: ["testaction"], + actionKey: "testaction", + label: "quickactions-downloads2", + onPick: () => testActionCalled++, + }); + + registerCleanupFunction(() => { + ActionsProviderQuickActions.removeAction("testaction"); + }); +}); + +add_task(async function test_quickaction() { + info("Match an installed quickaction and trigger it via tab"); + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "testact", + }); + + Assert.equal( + UrlbarTestUtils.getResultCount(window), + 1, + "We matched the action" + ); + + info("The callback of the action is fired when selected"); + EventUtils.synthesizeKey("KEY_Tab", {}, window); + EventUtils.synthesizeKey("KEY_Enter", {}, window); + Assert.equal(testActionCalled, 1, "Test action was called"); +}); + +add_task(async function test_switchtab() { + let win = await BrowserTestUtils.openNewBrowserWindow(); + await loadURI(win.gBrowser, "https://example.com/"); + + info("Open a new tab, type in the urlbar and switch to previous tab"); + await BrowserTestUtils.openNewForegroundTab(win.gBrowser); + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window: win, + value: "example", + }); + + EventUtils.synthesizeKey("KEY_ArrowDown", {}, win); + EventUtils.synthesizeKey("KEY_Tab", {}, win); + EventUtils.synthesizeKey("KEY_Enter", {}, win); + + is(win.gBrowser.tabs.length, 1, "We switched to previous tab"); + is( + win.gBrowser.currentURI.spec, + "https://example.com/", + "We switched to previous tab" + ); + + info( + "Open a new tab, type in the urlbar, select result and open url in current tab" + ); + await BrowserTestUtils.openNewForegroundTab(win.gBrowser); + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window: win, + value: "example", + }); + + EventUtils.synthesizeKey("KEY_ArrowDown", {}, win); + EventUtils.synthesizeKey("KEY_Enter", {}, win); + await BrowserTestUtils.browserLoaded(win.gBrowser); + is(win.gBrowser.tabs.length, 2, "We switched to previous tab"); + is( + win.gBrowser.currentURI.spec, + "https://example.com/", + "We opened in current tab" + ); + + BrowserTestUtils.closeWindow(win); +}); + +add_task(async function test_sitesearch() { + await SearchTestUtils.installSearchExtension({ + name: "Contextual", + search_url: "https://example.com/browser", + }); + + let ENGINE_TEST_URL = "https://example.com/"; + await loadURI(gBrowser.selectedBrowser, ENGINE_TEST_URL); + + const query = "search"; + let engine = Services.search.getEngineByName("Contextual"); + const [expectedUrl] = UrlbarUtils.getSearchQueryUrl(engine, query); + + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window, + value: "sea", + }); + + let onLoad = BrowserTestUtils.browserLoaded( + gBrowser.selectedBrowser, + false, + expectedUrl + ); + gURLBar.value = query; + UrlbarTestUtils.fireInputEvent(window); + EventUtils.synthesizeKey("KEY_Tab"); + EventUtils.synthesizeKey("KEY_Enter"); + await onLoad; + + Assert.equal( + gBrowser.selectedBrowser.currentURI.spec, + expectedUrl, + "Selecting the contextual search result opens the search URL" + ); +}); diff --git a/browser/components/urlbar/tests/browser/browser_strip_on_share.js b/browser/components/urlbar/tests/browser/browser_strip_on_share.js index 9e045cee9c..ec3f96425a 100644 --- a/browser/components/urlbar/tests/browser/browser_strip_on_share.js +++ b/browser/components/urlbar/tests/browser/browser_strip_on_share.js @@ -101,6 +101,32 @@ add_task(async function testQueryParamIsNotStrippedForWrongSiteSpecific() { }); }); +// Ensuring site specific parameters are stripped regardless +// of capitalization in the URI +add_task(async function testQueryParamIsStrippedWhenParamIsCapitalized() { + let originalUrl = "https://www.example.com/?TEST_1=1234"; + let shortenedUrl = "https://www.example.com/"; + await testMenuItemEnabled({ + selectWholeUrl: true, + validUrl: originalUrl, + strippedUrl: shortenedUrl, + useTestList: true, + }); +}); + +// Ensuring site specific parameters are stripped regardless +// of capitalization in the URI +add_task(async function testQueryParamIsStrippedWhenParamIsCapitalized() { + let originalUrl = "https://www.example.com/?test_5=1234"; + let shortenedUrl = "https://www.example.com/"; + await testMenuItemEnabled({ + selectWholeUrl: true, + validUrl: originalUrl, + strippedUrl: shortenedUrl, + useTestList: true, + }); +}); + /** * Opens a new tab, opens the ulr bar context menu and checks that the strip-on-share menu item is not visible * @@ -163,7 +189,7 @@ async function testMenuItemEnabled({ topLevelSites: ["*"], }, example: { - queryParams: ["test_2", "test_1"], + queryParams: ["test_2", "test_1", "TEST_5"], topLevelSites: ["www.example.com"], }, exampleNet: { diff --git a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_quickactions.js b/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_quickactions.js deleted file mode 100644 index b29807900b..0000000000 --- a/browser/components/urlbar/tests/browser/browser_urlbar_telemetry_quickactions.js +++ /dev/null @@ -1,133 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -/** - * This file tests urlbar telemetry for quickactions. - */ - -"use strict"; - -ChromeUtils.defineESModuleGetters(this, { - UrlbarProviderQuickActions: - "resource:///modules/UrlbarProviderQuickActions.sys.mjs", -}); - -let testActionCalled = 0; - -add_setup(async function setup() { - await SpecialPowers.pushPrefEnv({ - set: [ - ["browser.urlbar.suggest.quickactions", true], - ["browser.urlbar.quickactions.enabled", true], - ], - }); - - UrlbarProviderQuickActions.addAction("testaction", { - commands: ["testaction"], - label: "quickactions-downloads2", - onPick: () => testActionCalled++, - }); - - registerCleanupFunction(() => { - UrlbarProviderQuickActions.removeAction("testaction"); - }); -}); - -add_task(async function test() { - const histograms = snapshotHistograms(); - - // Do a search to show the quickaction. - await UrlbarTestUtils.promiseAutocompleteResultPopup({ - window, - value: "testaction", - waitForFocus, - fireInputEvent: true, - }); - - await UrlbarTestUtils.promisePopupClose(window, () => { - EventUtils.synthesizeKey("KEY_ArrowDown"); - EventUtils.synthesizeKey("KEY_Enter"); - }); - - Assert.equal(testActionCalled, 1, "Test action was called"); - - TelemetryTestUtils.assertHistogram( - histograms.resultMethodHist, - UrlbarTestUtils.SELECTED_RESULT_METHODS.arrowEnterSelection, - 1 - ); - - let scalars = TelemetryTestUtils.getProcessScalars("parent", true, true); - TelemetryTestUtils.assertKeyedScalar( - scalars, - `urlbar.picked.quickaction`, - 1, - 1 - ); - - TelemetryTestUtils.assertKeyedScalar( - scalars, - "quickaction.picked", - "testaction-10", - 1 - ); - - TelemetryTestUtils.assertKeyedScalar( - scalars, - "quickaction.impression", - "testaction-10", - 1 - ); - - // Clean up for subsequent tests. - gURLBar.handleRevert(); -}); - -add_task(async function test_impressions() { - UrlbarProviderQuickActions.addAction("testaction2", { - commands: ["testaction2"], - label: "quickactions-downloads2", - onPick: () => {}, - }); - - await UrlbarTestUtils.promiseAutocompleteResultPopup({ - window, - value: "testaction", - waitForFocus, - fireInputEvent: true, - }); - - await UrlbarTestUtils.promisePopupClose(window, () => { - EventUtils.synthesizeKey("KEY_ArrowDown"); - EventUtils.synthesizeKey("KEY_Enter"); - }); - - let scalars = TelemetryTestUtils.getProcessScalars("parent", true, true); - - TelemetryTestUtils.assertKeyedScalar( - scalars, - "quickaction.impression", - `testaction-10`, - 1 - ); - TelemetryTestUtils.assertKeyedScalar( - scalars, - "quickaction.impression", - `testaction2-10`, - 1 - ); - - UrlbarProviderQuickActions.removeAction("testaction2"); - gURLBar.handleRevert(); -}); - -function snapshotHistograms() { - Services.telemetry.clearScalars(); - Services.telemetry.clearEvents(); - return { - resultMethodHist: TelemetryTestUtils.getAndClearHistogram( - "FX_URLBAR_SELECTED_RESULT_METHOD" - ), - }; -} diff --git a/browser/components/urlbar/tests/browser/browser_view_selectionByMouse.js b/browser/components/urlbar/tests/browser/browser_view_selectionByMouse.js index 10110a8928..0625a3397f 100644 --- a/browser/components/urlbar/tests/browser/browser_view_selectionByMouse.js +++ b/browser/components/urlbar/tests/browser/browser_view_selectionByMouse.js @@ -6,16 +6,16 @@ // Test selection on result view by mouse. ChromeUtils.defineESModuleGetters(this, { - UrlbarProviderQuickActions: - "resource:///modules/UrlbarProviderQuickActions.sys.mjs", + ActionsProviderQuickActions: + "resource:///modules/ActionsProviderQuickActions.sys.mjs", }); add_setup(async function () { await SpecialPowers.pushPrefEnv({ set: [ + ["browser.urlbar.secondaryActions.featureGate", true], ["browser.urlbar.quickactions.enabled", true], ["browser.urlbar.suggest.quickactions", true], - ["browser.urlbar.shortcuts.quickactions", true], ], }); @@ -23,7 +23,7 @@ add_setup(async function () { await SearchTestUtils.installSearchExtension({}, { setAsDefault: true }); - UrlbarProviderQuickActions.addAction("test-addons", { + ActionsProviderQuickActions.addAction("test-addons", { commands: ["test-addons"], label: "quickactions-addons", onPick: () => @@ -32,7 +32,7 @@ add_setup(async function () { "about:about" ), }); - UrlbarProviderQuickActions.addAction("test-downloads", { + ActionsProviderQuickActions.addAction("test-downloads", { commands: ["test-downloads"], label: "quickactions-downloads2", onPick: () => @@ -43,40 +43,22 @@ add_setup(async function () { }); registerCleanupFunction(function () { - UrlbarProviderQuickActions.removeAction("test-addons"); - UrlbarProviderQuickActions.removeAction("test-downloads"); + ActionsProviderQuickActions.removeAction("test-addons"); + ActionsProviderQuickActions.removeAction("test-downloads"); }); }); add_task(async function basic() { const testData = [ { - description: "Normal result to quick action button", - mousedown: ".urlbarView-row:nth-child(1) > .urlbarView-row-inner", - mouseup: ".urlbarView-quickaction-button[data-key=test-downloads]", - expected: "about:downloads", - }, - { - description: "Normal result to out of result", - mousedown: ".urlbarView-row:nth-child(1) > .urlbarView-row-inner", - mouseup: "body", - expected: false, - }, - { description: "Quick action button to normal result", - mousedown: ".urlbarView-quickaction-button[data-key=test-addons]", + mousedown: ".urlbarView-action-btn[data-action=test-addons]", mouseup: ".urlbarView-row:nth-child(1)", expected: "https://example.com/?q=test", }, { - description: "Quick action button to quick action button", - mousedown: ".urlbarView-quickaction-button[data-key=test-addons]", - mouseup: ".urlbarView-quickaction-button[data-key=test-downloads]", - expected: "about:downloads", - }, - { description: "Quick action button to out of result", - mousedown: ".urlbarView-quickaction-button[data-key=test-downloads]", + mousedown: ".urlbarView-action-btn[data-action=test-addons]", mouseup: "body", expected: false, }, @@ -148,7 +130,7 @@ add_task(async function outOfBrowser() { }, { description: "Quick action button to out of browser", - mousedown: ".urlbarView-quickaction-button[data-key=test-addons]", + mousedown: ".urlbarView-action-btn[data-action=test-addons]", }, ]; @@ -204,22 +186,22 @@ add_task(async function withSelectionByKeyboard() { mouseup: "body", expected: { selectedElementByKey: - "#urlbar-results .urlbarView-quickaction-button[selected]", + "#urlbar-results .urlbarView-action-btn[selected]", selectedElementAfterMouseDown: - "#urlbar-results .urlbarView-quickaction-button[selected]", + "#urlbar-results .urlbarView-action-btn[selected]", actionedPage: false, }, }, { - description: "Select normal result, then click on about:downloads", - mousedown: ".urlbarView-quickaction-button[data-key=test-downloads]", - mouseup: ".urlbarView-quickaction-button[data-key=test-downloads]", + description: "Select normal result, then click on about:addons", + mousedown: ".urlbarView-action-btn[data-action=test-addons]", + mouseup: ".urlbarView-action-btn[data-action=test-addons]", expected: { selectedElementByKey: "#urlbar-results .urlbarView-row > .urlbarView-row-inner[selected]", selectedElementAfterMouseDown: - ".urlbarView-quickaction-button[data-key=test-downloads]", - actionedPage: "about:downloads", + ".urlbarView-action-btn[data-action=test-addons]", + actionedPage: "about:about", }, }, ]; @@ -244,11 +226,7 @@ add_task(async function withSelectionByKeyboard() { ]); if (arrowDown) { - EventUtils.synthesizeKey( - "KEY_ArrowDown", - { repeat: arrowDown }, - window - ); + EventUtils.synthesizeKey("KEY_Tab", { repeat: arrowDown }, window); } let [selectedElementByKey] = await waitForElements([ diff --git a/browser/components/urlbar/tests/browser/head.js b/browser/components/urlbar/tests/browser/head.js index f78624e68e..73f9fda3a9 100644 --- a/browser/components/urlbar/tests/browser/head.js +++ b/browser/components/urlbar/tests/browser/head.js @@ -161,7 +161,7 @@ async function search({ // Set the input value and move the caret to the end to simulate the user // typing. It's important the caret is at the end because otherwise autofill // won't happen. - gURLBar.value = searchString; + gURLBar._setValue(searchString, { allowTrim: false }); gURLBar.inputField.setSelectionRange( searchString.length, searchString.length @@ -173,28 +173,21 @@ async function search({ // autofill before the search completes. UrlbarTestUtils.fireInputEvent(window); - // Subtract the protocol length, when the searchString contains the https:// - // protocol and trimHttps is enabled. - let trimmedProtocolWSlashes = UrlbarTestUtils.getTrimmedProtocolWithSlashes(); - let selectionOffset = searchString.includes(trimmedProtocolWSlashes) - ? trimmedProtocolWSlashes.length - : 0; - // Check the input value and selection immediately, before waiting on the // search to complete. Assert.equal( gURLBar.value, - UrlbarTestUtils.trimURL(valueBefore), + valueBefore, "gURLBar.value before the search completes" ); Assert.equal( gURLBar.selectionStart, - searchString.length - selectionOffset, + searchString.length, "gURLBar.selectionStart before the search completes" ); Assert.equal( gURLBar.selectionEnd, - valueBefore.length - selectionOffset, + valueBefore.length, "gURLBar.selectionEnd before the search completes" ); @@ -205,17 +198,17 @@ async function search({ // Check the final value after the results arrived. Assert.equal( gURLBar.value, - UrlbarTestUtils.trimURL(valueAfter), + valueAfter, "gURLBar.value after the search completes" ); Assert.equal( gURLBar.selectionStart, - searchString.length - selectionOffset, + searchString.length, "gURLBar.selectionStart after the search completes" ); Assert.equal( gURLBar.selectionEnd, - valueAfter.length - selectionOffset, + valueAfter.length, "gURLBar.selectionEnd after the search completes" ); @@ -227,7 +220,7 @@ async function search({ ); Assert.strictEqual( gURLBar._autofillPlaceholder.value, - UrlbarTestUtils.trimURL(placeholderAfter), + placeholderAfter, "gURLBar._autofillPlaceholder.value after the search completes" ); } else { @@ -246,3 +239,51 @@ async function search({ "First result is an autofill result iff a placeholder is expected" ); } + +function selectWithMouseDrag(fromX, toX, win = window) { + let target = win.gURLBar.inputField; + let rect = target.getBoundingClientRect(); + let promise = BrowserTestUtils.waitForEvent(target, "mouseup"); + EventUtils.synthesizeMouse( + target, + fromX, + rect.height / 2, + { type: "mousemove" }, + target.ownerGlobal + ); + EventUtils.synthesizeMouse( + target, + fromX, + rect.height / 2, + { type: "mousedown" }, + target.ownerGlobal + ); + EventUtils.synthesizeMouse( + target, + toX, + rect.height / 2, + { type: "mousemove" }, + target.ownerGlobal + ); + EventUtils.synthesizeMouse( + target, + toX, + rect.height / 2, + { type: "mouseup" }, + target.ownerGlobal + ); + return promise; +} + +function selectWithDoubleClick(offsetX, win = window) { + let target = win.gURLBar.inputField; + let rect = target.getBoundingClientRect(); + let promise = BrowserTestUtils.waitForEvent(target, "dblclick"); + EventUtils.synthesizeMouse(target, offsetX, rect.height / 2, { + clickCount: 1, + }); + EventUtils.synthesizeMouse(target, offsetX, rect.height / 2, { + clickCount: 2, + }); + return promise; +} diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_groups.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_groups.js index ce69d30517..f930b28f59 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_groups.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_groups.js @@ -59,9 +59,9 @@ add_task(async function recent_search() { assert: () => assertAbandonmentTelemetry([ { - groups: "recent_search,suggested_index", - results: "recent_search,action", - n_results: 2, + groups: "recent_search", + results: "recent_search", + n_results: 1, }, ]), }); @@ -114,9 +114,9 @@ add_task(async function top_site() { assert: () => assertAbandonmentTelemetry([ { - groups: "top_site,suggested_index", - results: "top_site,action", - n_results: 2, + groups: "top_site", + results: "top_site", + n_results: 1, }, ]), }); @@ -128,9 +128,9 @@ add_task(async function clipboard() { assert: () => assertAbandonmentTelemetry([ { - groups: "general,suggested_index", - results: "clipboard,action", - n_results: 2, + groups: "general", + results: "clipboard", + n_results: 1, }, ]), }); @@ -170,9 +170,9 @@ add_task(async function general() { assert: () => assertAbandonmentTelemetry([ { - groups: "heuristic,suggested_index,general", - results: "search_engine,action,bookmark", - n_results: 3, + groups: "heuristic,general", + results: "search_engine,bookmark", + n_results: 2, }, ]), }); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_search_mode.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_search_mode.js index 7edcc47a30..45f4b79e7c 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_search_mode.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_abandonment_search_mode.js @@ -45,10 +45,3 @@ add_task(async function tabs() { assert: () => assertAbandonmentTelemetry([{ search_mode: "tabs" }]), }); }); - -add_task(async function actions() { - await doActionsTest({ - trigger: () => doBlur(), - assert: () => assertAbandonmentTelemetry([{ search_mode: "actions" }]), - }); -}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_edge_cases.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_edge_cases.js index 04ef7e9757..1668470714 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_edge_cases.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_edge_cases.js @@ -196,7 +196,6 @@ add_task(async function enter_to_reload_current_url() { await BrowserTestUtils.waitForCondition( () => window.document.activeElement === gURLBar.inputField ); - await UrlbarTestUtils.promiseSearchComplete(window); // Press Enter key to reload the page without selecting any suggestions. await doEnter(); @@ -213,8 +212,8 @@ add_task(async function enter_to_reload_current_url() { selected_result: "input_field", selected_result_subtype: "", provider: undefined, - results: "action", - groups: "suggested_index", + results: "", + groups: "", }, ]); }); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_groups.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_groups.js index d46c874403..a0ef61dd19 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_groups.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_groups.js @@ -59,9 +59,9 @@ add_task(async function recent_search() { assert: () => assertEngagementTelemetry([ { - groups: "recent_search,suggested_index", - results: "recent_search,action", - n_results: 2, + groups: "recent_search", + results: "recent_search", + n_results: 1, }, ]), }); @@ -114,9 +114,9 @@ add_task(async function top_site() { assert: () => assertEngagementTelemetry([ { - groups: "top_site,suggested_index", - results: "top_site,action", - n_results: 2, + groups: "top_site", + results: "top_site", + n_results: 1, }, ]), }); @@ -128,9 +128,9 @@ add_task(async function clipboard() { assert: () => assertEngagementTelemetry([ { - groups: "general,suggested_index", - results: "clipboard,action", - n_results: 2, + groups: "general", + results: "clipboard", + n_results: 1, }, ]), }); @@ -170,9 +170,9 @@ add_task(async function general() { assert: () => assertEngagementTelemetry([ { - groups: "heuristic,suggested_index,general", - results: "search_engine,action,bookmark", - n_results: 3, + groups: "heuristic,general", + results: "search_engine,bookmark", + n_results: 2, }, ]), }); @@ -255,6 +255,7 @@ add_task(async function always_empty_if_drop_go() { await doTest(async () => { // Open the results view once. + await addTopSites("https://example.com/"); await showResultByArrowDown(); await UrlbarTestUtils.promisePopupClose(window); @@ -282,6 +283,7 @@ add_task(async function always_empty_if_paste_go() { await doTest(async () => { // Open the results view once. + await addTopSites("https://example.com/"); await showResultByArrowDown(); await UrlbarTestUtils.promisePopupClose(window); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_interaction.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_interaction.js index 2866186c30..d2deacf597 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_interaction.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_interaction.js @@ -42,6 +42,7 @@ add_task(async function dropped() { }); await doTest(async () => { + await addTopSites("https://example.com/"); await showResultByArrowDown(); await doDropAndGo("example.com"); @@ -67,6 +68,7 @@ add_task(async function pasted() { }); await doTest(async () => { + await addTopSites("https://example.com/"); await showResultByArrowDown(); await doPasteAndGo("www.example.com"); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_search_mode.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_search_mode.js index 013bef1904..9227b81fd0 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_search_mode.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_search_mode.js @@ -42,6 +42,7 @@ add_task(async function tabs() { await doTabTest({ trigger: async () => { const currentTab = gBrowser.selectedTab; + EventUtils.synthesizeKey("KEY_Tab"); EventUtils.synthesizeKey("KEY_Enter"); await BrowserTestUtils.waitForCondition( () => gBrowser.selectedTab !== currentTab @@ -50,14 +51,3 @@ add_task(async function tabs() { assert: () => assertEngagementTelemetry([{ search_mode: "tabs" }]), }); }); - -add_task(async function actions() { - await doActionsTest({ - trigger: async () => { - const onLoad = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); - doClickSubButton(".urlbarView-quickaction-button[data-key=addons]"); - await onLoad; - }, - assert: () => assertEngagementTelemetry([{ search_mode: "actions" }]), - }); -}); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_selected_result.js b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_selected_result.js index bea266dbf4..34083e4369 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_selected_result.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/browser_glean_telemetry_engagement_selected_result.js @@ -119,9 +119,9 @@ add_task(async function selected_result_bookmark() { { selected_result: "bookmark", selected_result_subtype: "", - selected_position: 3, + selected_position: 2, provider: "Places", - results: "search_engine,action,bookmark", + results: "search_engine,bookmark", }, ]); }); @@ -267,27 +267,11 @@ add_task(async function selected_result_url() { }); }); -add_task(async function selected_result_action() { - await doTest(async () => { - await showResultByArrowDown(); - await selectRowByProvider("quickactions"); - const onLoad = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); - doClickSubButton(".urlbarView-quickaction-button[data-key=addons]"); - await onLoad; - - assertEngagementTelemetry([ - { - selected_result: "action", - selected_result_subtype: "addons", - selected_position: 1, - provider: "quickactions", - results: "action", - }, - ]); +add_task(async function selected_result_tab() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.secondaryActions.featureGate", false]], }); -}); -add_task(async function selected_result_tab() { const tab = BrowserTestUtils.addTab(gBrowser, "https://example.com/"); await doTest(async () => { @@ -307,6 +291,7 @@ add_task(async function selected_result_tab() { ]); }); + await SpecialPowers.popPrefEnv(); BrowserTestUtils.removeTab(tab); }); @@ -402,7 +387,7 @@ add_task(async function selected_result_top_site() { selected_result_subtype: "", selected_position: 1, provider: "UrlbarProviderTopSites", - results: "top_site,action", + results: "top_site", }, ]); }); @@ -456,7 +441,7 @@ add_task(async function selected_result_clipboard() { selected_result_subtype: "", selected_position: 1, provider: "UrlbarProviderClipboard", - results: "clipboard,action", + results: "clipboard", }, ]); }); @@ -492,50 +477,6 @@ add_task(async function selected_result_unit() { await SpecialPowers.popPrefEnv(); }); -add_task(async function selected_result_site_specific_contextual_search() { - await SpecialPowers.pushPrefEnv({ - set: [["browser.urlbar.contextualSearch.enabled", true]], - }); - - await doTest(async () => { - const extension = await SearchTestUtils.installSearchExtension( - { - name: "Contextual", - search_url: "https://example.com/browser", - }, - { skipUnload: true } - ); - const onLoaded = BrowserTestUtils.browserLoaded( - gBrowser.selectedBrowser, - false, - "https://example.com/" - ); - BrowserTestUtils.startLoadingURIString( - gBrowser.selectedBrowser, - "https://example.com/" - ); - await onLoaded; - - await openPopup("search"); - await selectRowByProvider("UrlbarProviderContextualSearch"); - await doEnter(); - - assertEngagementTelemetry([ - { - selected_result: "site_specific_contextual_search", - selected_result_subtype: "", - selected_position: 2, - provider: "UrlbarProviderContextualSearch", - results: "search_engine,site_specific_contextual_search", - }, - ]); - - await extension.unload(); - }); - - await SpecialPowers.popPrefEnv(); -}); - add_task(async function selected_result_rs_adm_sponsored() { const cleanupQuickSuggest = await ensureQuickSuggestInit({ prefs: [["quicksuggest.rustEnabled", false]], diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/head-interaction.js b/browser/components/urlbar/tests/engagementTelemetry/browser/head-interaction.js index 58c55b416f..dffdebad97 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/head-interaction.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/head-interaction.js @@ -130,6 +130,7 @@ async function doTypedTest({ trigger, assert }) { async function doTypedWithResultsPopupTest({ trigger, assert }) { await doTest(async () => { + await addTopSites("https://example.org/"); await showResultByArrowDown(); EventUtils.synthesizeKey("x"); await UrlbarTestUtils.promiseSearchComplete(window); @@ -150,6 +151,7 @@ async function doPastedTest({ trigger, assert }) { async function doPastedWithResultsPopupTest({ trigger, assert }) { await doTest(async () => { + await addTopSites("https://example.org/"); await showResultByArrowDown(); await doPaste("x"); @@ -257,6 +259,7 @@ async function doPersistedSearchTermsRestartedRefinedTest({ for (const { firstInput, secondInput, expected } of testData) { await doTest(async () => { + await addTopSites("https://example.com/"); await openPopup(firstInput); await doEnter(); diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/head-search_mode.js b/browser/components/urlbar/tests/engagementTelemetry/browser/head-search_mode.js index 86151e1ba3..25a6504109 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/head-search_mode.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/head-search_mode.js @@ -78,16 +78,3 @@ async function doTabTest({ trigger, assert }) { BrowserTestUtils.removeTab(tab); } - -async function doActionsTest({ trigger, assert }) { - await doTest(async () => { - await openPopup("add"); - await UrlbarTestUtils.enterSearchMode(window, { - source: UrlbarUtils.RESULT_SOURCE.ACTIONS, - }); - await selectRowByProvider("quickactions"); - - await trigger(); - await assert(); - }); -} diff --git a/browser/components/urlbar/tests/engagementTelemetry/browser/head.js b/browser/components/urlbar/tests/engagementTelemetry/browser/head.js index 1373cc7e27..a3b2b020c0 100644 --- a/browser/components/urlbar/tests/engagementTelemetry/browser/head.js +++ b/browser/components/urlbar/tests/engagementTelemetry/browser/head.js @@ -406,9 +406,7 @@ async function setup() { set: [ ["browser.urlbar.searchEngagementTelemetry.enabled", true], ["browser.urlbar.quickactions.enabled", true], - ["browser.urlbar.quickactions.minimumSearchString", 0], - ["browser.urlbar.suggest.quickactions", true], - ["browser.urlbar.shortcuts.quickactions", true], + ["browser.urlbar.secondaryActions.featureGate", true], ], }); diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest.js index 98f6ba6117..87f94a641b 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_quicksuggest.js @@ -34,6 +34,27 @@ const REMOTE_SETTINGS_RESULTS = [ }, ]; +const MERINO_NAVIGATIONAL_SUGGESTION = { + url: "https://example.com/navigational-suggestion", + title: "Navigational suggestion", + provider: "top_picks", + is_sponsored: false, + score: 0.25, + block_id: 0, + is_top_pick: true, +}; + +const MERINO_DYNAMIC_WIKIPEDIA_SUGGESTION = { + url: "https://example.com/dynamic-wikipedia", + title: "Dynamic Wikipedia suggestion", + click_url: "https://example.com/click", + impression_url: "https://example.com/impression", + advertiser: "dynamic-wikipedia", + provider: "wikipedia", + iab_category: "5 - Education", + block_id: 1, +}; + add_setup(async function () { await PlacesUtils.history.clear(); await PlacesUtils.bookmarks.eraseEverything(); @@ -46,7 +67,11 @@ add_setup(async function () { attachment: REMOTE_SETTINGS_RESULTS, }, ], + merinoSuggestions: [], }); + + // Disable Merino so we trigger only remote settings suggestions. + UrlbarPrefs.set("quicksuggest.dataCollection.enabled", false); }); // Tests a sponsored result and keyword highlighting. @@ -167,30 +192,9 @@ add_tasks_with_rust( // Tests the "Manage" result menu for sponsored suggestion. add_tasks_with_rust(async function resultMenu_manage_sponsored() { - await BrowserTestUtils.withNewTab({ gBrowser }, async browser => { - await UrlbarTestUtils.promiseAutocompleteResultPopup({ - window, - value: "fra", - }); - - const managePage = "about:preferences#search"; - let onManagePageLoaded = BrowserTestUtils.browserLoaded( - browser, - false, - managePage - ); - // Click the command. - await UrlbarTestUtils.openResultMenuAndClickItem(window, "manage", { - resultIndex: 1, - }); - await onManagePageLoaded; - Assert.equal( - browser.currentURI.spec, - managePage, - "The manage page is loaded" - ); - - await UrlbarTestUtils.promisePopupClose(window); + await doManageTest({ + input: "fra", + index: 1, }); }); @@ -201,3 +205,35 @@ add_tasks_with_rust(async function resultMenu_manage_nonSponsored() { index: 1, }); }); + +// Tests the "Manage" result menu for Navigational suggestion. +add_tasks_with_rust(async function resultMenu_manage_navigational() { + // Enable Merino. + UrlbarPrefs.set("quicksuggest.dataCollection.enabled", true); + MerinoTestUtils.server.response.body.suggestions = [ + MERINO_NAVIGATIONAL_SUGGESTION, + ]; + + await doManageTest({ + input: "test", + index: 1, + }); + + UrlbarPrefs.clear("quicksuggest.dataCollection.enabled"); +}); + +// Tests the "Manage" result menu for Dynamic Wikipedia suggestion. +add_tasks_with_rust(async function resultMenu_manage_dynamicWikipedia() { + // Enable Merino. + UrlbarPrefs.set("quicksuggest.dataCollection.enabled", true); + MerinoTestUtils.server.response.body.suggestions = [ + MERINO_DYNAMIC_WIKIPEDIA_SUGGESTION, + ]; + + await doManageTest({ + input: "test", + index: 1, + }); + + UrlbarPrefs.clear("quicksuggest.dataCollection.enabled"); +}); diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_dynamicWikipedia.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_dynamicWikipedia.js index 71c289e0ef..c659eee268 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_dynamicWikipedia.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_dynamicWikipedia.js @@ -88,24 +88,6 @@ add_task(async function () { }, }, }, - // help - { - command: "help", - scalars: { - [TELEMETRY_SCALARS.IMPRESSION_DYNAMIC_WIKIPEDIA]: position, - [TELEMETRY_SCALARS.HELP_DYNAMIC_WIKIPEDIA]: position, - }, - event: { - category: QuickSuggest.TELEMETRY_EVENT_CATEGORY, - method: "engagement", - object: "help", - extra: { - suggestion_type, - match_type, - position: position.toString(), - }, - }, - }, ], }); }); diff --git a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_weather.js b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_weather.js index e87c64740f..90044a95bd 100644 --- a/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_weather.js +++ b/browser/components/urlbar/tests/quicksuggest/browser/browser_telemetry_weather.js @@ -121,24 +121,6 @@ add_task(async function () { }, }, }, - // help - { - command: "help", - scalars: { - [WEATHER_SCALARS.IMPRESSION]: position, - [WEATHER_SCALARS.HELP]: position, - }, - event: { - category: QuickSuggest.TELEMETRY_EVENT_CATEGORY, - method: "engagement", - object: "help", - extra: { - suggestion_type, - match_type, - position: position.toString(), - }, - }, - }, ], }); }); diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_dynamicWikipedia.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_dynamicWikipedia.js index a9f339c324..7fc687d3df 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_dynamicWikipedia.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_dynamicWikipedia.js @@ -90,14 +90,11 @@ function makeExpectedResult() { qsSuggestion: "full_keyword", source: "merino", provider: "wikipedia", - helpUrl: QuickSuggest.HELP_URL, - helpL10n: { - id: "urlbar-result-menu-learn-more-about-firefox-suggest", - }, isBlockable: true, blockL10n: { id: "urlbar-result-menu-dismiss-firefox-suggest", }, + isManageable: true, }, }; } diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merino.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merino.js index 64f4991236..b88e14e0e0 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merino.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_merino.js @@ -549,14 +549,11 @@ add_task(async function bestMatch() { url: "url", icon: null, qsSuggestion: "full_keyword", - helpUrl: QuickSuggest.HELP_URL, - helpL10n: { - id: "urlbar-result-menu-learn-more-about-firefox-suggest", - }, isBlockable: true, blockL10n: { id: "urlbar-result-menu-dismiss-firefox-suggest", }, + isManageable: true, displayUrl: "url", source: "merino", provider: "some_top_pick_provider", diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_scoreMap.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_scoreMap.js index 224dd6cb22..89fefd3163 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_scoreMap.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_scoreMap.js @@ -657,14 +657,11 @@ function makeExpectedDefaultResult({ suggestion }) { ? { id: "urlbar-result-action-sponsored" } : undefined, shouldShowUrl: true, - helpUrl: QuickSuggest.HELP_URL, - helpL10n: { - id: "urlbar-result-menu-learn-more-about-firefox-suggest", - }, isBlockable: true, blockL10n: { id: "urlbar-result-menu-dismiss-firefox-suggest", }, + isManageable: true, }, }; } diff --git a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_topPicks.js b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_topPicks.js index 1b8da54920..468cedbe0b 100644 --- a/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_topPicks.js +++ b/browser/components/urlbar/tests/quicksuggest/unit/test_quicksuggest_topPicks.js @@ -175,14 +175,11 @@ function makeExpectedResult({ shouldShowUrl: true, source: "merino", provider: telemetryType, - helpUrl: QuickSuggest.HELP_URL, - helpL10n: { - id: "urlbar-result-menu-learn-more-about-firefox-suggest", - }, isBlockable: true, blockL10n: { id: "urlbar-result-menu-dismiss-firefox-suggest", }, + isManageable: true, }, }; if (typeof dupedHeuristic == "boolean") { diff --git a/browser/components/urlbar/tests/unit/test_autofill_origins.js b/browser/components/urlbar/tests/unit/test_autofill_origins.js index 33e462a8af..454881b933 100644 --- a/browser/components/urlbar/tests/unit/test_autofill_origins.js +++ b/browser/components/urlbar/tests/unit/test_autofill_origins.js @@ -1039,3 +1039,68 @@ async function doTitleTest({ visits, input, expected }) { await cleanup(); } + +/* Tests sorting order when only unvisited bookmarks are available (e.g. in + permanent private browsing mode), then the only information we have is the + number of bookmarks per origin, and we're going to use that. */ +add_task(async function just_multiple_unvisited_bookmarks() { + // These are sorted to avoid confusion with natural sorting, so the one with + // the highest score is added in the middle. + let filledUrl = "https://www.tld2.com/"; + let urls = [ + { + url: "https://tld1.com/", + count: 1, + }, + { + url: "https://tld2.com/", + count: 2, + }, + { + url: filledUrl, + count: 2, + }, + { + url: "https://tld3.com/", + count: 3, + }, + ]; + + await PlacesUtils.history.clear(); + for (let { url, count } of urls) { + while (count--) { + await PlacesTestUtils.addBookmarkWithDetails({ + uri: url, + }); + } + } + await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies(); + + let context = createContext("tld", { isPrivate: false }); + await check_results({ + context, + autofilled: "tld2.com/", + completed: filledUrl, + matches: [ + makeVisitResult(context, { + uri: filledUrl, + title: "A bookmark", + heuristic: true, + }), + makeBookmarkResult(context, { + uri: "https://tld3.com/", + title: "A bookmark", + }), + makeBookmarkResult(context, { + uri: "https://tld2.com/", + title: "A bookmark", + }), + makeBookmarkResult(context, { + uri: "https://tld1.com/", + title: "A bookmark", + }), + ], + }); + + await cleanup(); +}); diff --git a/browser/components/urlbar/tests/unit/test_quickactions.js b/browser/components/urlbar/tests/unit/test_quickactions.js index 00206c77b2..30e3fbdd95 100644 --- a/browser/components/urlbar/tests/unit/test_quickactions.js +++ b/browser/components/urlbar/tests/unit/test_quickactions.js @@ -5,108 +5,58 @@ "use strict"; ChromeUtils.defineESModuleGetters(this, { - UrlbarProviderQuickActions: - "resource:///modules/UrlbarProviderQuickActions.sys.mjs", + ActionsProviderQuickActions: + "resource:///modules/ActionsProviderQuickActions.sys.mjs", }); -let expectedMatch = (key, inputLength) => ({ - type: UrlbarUtils.RESULT_TYPE.DYNAMIC, - source: UrlbarUtils.RESULT_SOURCE.ACTIONS, - heuristic: false, - payload: { - results: [{ key }], - dynamicType: "quickactions", - inQuickActionsSearchMode: false, - helpUrl: UrlbarProviderQuickActions.helpUrl, - inputLength, - }, -}); - -testEngine_setup(); - add_setup(async () => { UrlbarPrefs.set("quickactions.enabled", true); - UrlbarPrefs.set("suggest.quickactions", true); - UrlbarProviderQuickActions.addAction("newaction", { + ActionsProviderQuickActions.addAction("newaction", { commands: ["newaction"], }); registerCleanupFunction(async () => { UrlbarPrefs.clear("quickactions.enabled"); - UrlbarPrefs.clear("suggest.quickactions"); - UrlbarProviderQuickActions.removeAction("newaction"); + ActionsProviderQuickActions.removeAction("newaction"); }); }); add_task(async function nomatch() { - let context = createContext("this doesnt match", { - providers: [UrlbarProviderQuickActions.name], - isPrivate: false, - }); - await check_results({ - context, - matches: [], - }); -}); - -add_task(async function quickactions_disabled() { - UrlbarPrefs.set("suggest.quickactions", false); - let context = createContext("new", { - providers: [UrlbarProviderQuickActions.name], - isPrivate: false, - }); - await check_results({ - context, - matches: [], - }); + let context = createContext("this doesnt match", {}); + let result = await ActionsProviderQuickActions.queryAction(context); + Assert.ok(result === null, "there were no matches"); }); add_task(async function quickactions_match() { - UrlbarPrefs.set("suggest.quickactions", true); - let context = createContext("new", { - providers: [UrlbarProviderQuickActions.name], - isPrivate: false, - }); - await check_results({ - context, - matches: [expectedMatch("newaction", 3)], - }); + let context = createContext("new", {}); + let result = await ActionsProviderQuickActions.queryAction(context); + Assert.ok(result.key == "newaction", "Matched the new action"); }); add_task(async function duplicate_matches() { - UrlbarProviderQuickActions.addAction("testaction", { + ActionsProviderQuickActions.addAction("testaction", { commands: ["testaction", "test"], }); - let context = createContext("testaction", { - providers: [UrlbarProviderQuickActions.name], - isPrivate: false, - }); + let context = createContext("test", {}); + let result = await ActionsProviderQuickActions.queryAction(context); - await check_results({ - context, - matches: [expectedMatch("testaction", 10)], - }); + Assert.ok(result.key == "testaction", "Matched the test action"); - UrlbarProviderQuickActions.removeAction("testaction"); + ActionsProviderQuickActions.removeAction("testaction"); }); add_task(async function remove_action() { - UrlbarProviderQuickActions.addAction("testaction", { + ActionsProviderQuickActions.addAction("testaction", { commands: ["testaction"], }); - UrlbarProviderQuickActions.removeAction("testaction"); + ActionsProviderQuickActions.removeAction("testaction"); - let context = createContext("test", { - providers: [UrlbarProviderQuickActions.name], - isPrivate: false, - }); + let context = createContext("test", {}); + let result = await ActionsProviderQuickActions.queryAction(context); - await check_results({ - context, - matches: [], - }); + Assert.ok(result === null, "there were no matches"); }); add_task(async function minimum_search_string() { @@ -114,13 +64,18 @@ add_task(async function minimum_search_string() { for (let minimumSearchString of [0, 3]) { UrlbarPrefs.set("quickactions.minimumSearchString", minimumSearchString); for (let i = 1; i < 4; i++) { - let context = createContext(searchString.substring(0, i), { - providers: [UrlbarProviderQuickActions.name], - isPrivate: false, - }); - let matches = - i >= minimumSearchString ? [expectedMatch("newaction", i)] : []; - await check_results({ context, matches }); + let context = createContext(searchString.substring(0, i), {}); + let result = await ActionsProviderQuickActions.queryAction(context); + + if (i >= minimumSearchString) { + Assert.ok(result.key == "newaction", "Matched the new action"); + } else { + Assert.equal( + ActionsProviderQuickActions.isActive(context), + false, + "QuickActions Provider is not active" + ); + } } } UrlbarPrefs.clear("quickactions.minimumSearchString"); diff --git a/browser/components/urlbar/tests/unit/test_tokenizer.js b/browser/components/urlbar/tests/unit/test_tokenizer.js index 835d1a5909..76b2c31ac2 100644 --- a/browser/components/urlbar/tests/unit/test_tokenizer.js +++ b/browser/components/urlbar/tests/unit/test_tokenizer.js @@ -32,6 +32,12 @@ add_task(async function test_tokenizer() { ], }, { + desc: "do not separate restriction char at beginning in search mode", + searchMode: { engineName: "testEngine" }, + searchString: `${UrlbarTokenizer.RESTRICT.SEARCH}test`, + expectedTokens: [{ value: "?test", type: UrlbarTokenizer.TYPE.TEXT }], + }, + { desc: "separate restriction char at end", searchString: `test ${UrlbarTokenizer.RESTRICT.BOOKMARK}`, expectedTokens: [ |