author | Mark Banner <standard8@mozilla.com> |
Wed, 20 Mar 2019 20:22:35 +0000 | |
changeset 465381 | 8aef30b8a2be10a22438549491fd05ee64b03fcf |
parent 465380 | d517ff77de9ef47f5a987e2821bed56a2f2c538a |
child 465382 | 747a5da93708d6ad12832272d794b21d825a4bb9 |
push id | 81044 |
push user | mbanner@mozilla.com |
push date | Thu, 21 Mar 2019 08:43:59 +0000 |
treeherder | autoland@8aef30b8a2be [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | mak, rpl |
bugs | 1530675 |
milestone | 68.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/components/extensions/test/browser/browser_ext_webNavigation_urlbar_transitions.js +++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_urlbar_transitions.js @@ -1,27 +1,22 @@ /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set sts=2 sw=2 et tw=80: */ "use strict"; ChromeUtils.defineModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm"); +ChromeUtils.defineModuleGetter(this, "UrlbarTestUtils", + "resource://testing-common/UrlbarTestUtils.jsm"); const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches"; const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml"; -async function promiseAutocompleteResultPopup(inputText) { - gURLBar.focus(); - gURLBar.value = inputText; - gURLBar.controller.startSearch(inputText); - await promisePopupShown(gURLBar.popup); - await BrowserTestUtils.waitForCondition(() => { - return gURLBar.controller.searchStatus >= - Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH; - }); +function promiseAutocompleteResultPopup(inputText) { + return UrlbarTestUtils.promiseAutocompleteResultPopup(window, inputText, waitForFocus); } async function addBookmark(bookmark) { if (bookmark.keyword) { await PlacesUtils.keywords.insert({ keyword: bookmark.keyword, url: bookmark.url, }); @@ -55,20 +50,17 @@ async function prepareSearchEngine() { let engine = await addSearchEngine(TEST_ENGINE_BASENAME); await Services.search.setDefault(engine); registerCleanupFunction(async function() { Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, suggestionsEnabled); await Services.search.setDefault(oldDefaultEngine); // Make sure the popup is closed for the next test. - gURLBar.blur(); - gURLBar.popup.selectedIndex = -1; - gURLBar.popup.hidePopup(); - ok(!gURLBar.popup.popupOpen, "popup should be closed"); + await UrlbarTestUtils.promisePopupClose(window); // Clicking suggestions causes visits to search results pages, so clear that // history now. await PlacesUtils.history.clear(); }); } add_task(async function test_webnavigation_urlbar_typed_transitions() { @@ -104,17 +96,56 @@ add_task(async function test_webnavigati const inputValue = "http://example.com/?q=typed"; gURLBar.inputField.value = inputValue.slice(0, -1); EventUtils.sendString(inputValue.slice(-1)); EventUtils.synthesizeKey("VK_RETURN", {altKey: true}); await extension.awaitFinish("webNavigation.from_address_bar.typed"); await extension.unload(); - info("extension unloaded"); +}); + +add_task(async function test_webnavigation_urlbar_typed_closed_popup_transitions() { + function backgroundScript() { + browser.webNavigation.onCommitted.addListener((msg) => { + browser.test.assertEq("http://example.com/?q=typedClosed", msg.url, + "Got the expected url"); + // assert from_address_bar transition qualifier + browser.test.assertTrue(msg.transitionQualifiers && + msg.transitionQualifiers.includes("from_address_bar"), + "Got the expected from_address_bar transitionQualifier"); + browser.test.assertEq("typed", msg.transitionType, + "Got the expected transitionType"); + browser.test.notifyPass("webNavigation.from_address_bar.typed"); + }); + + browser.test.sendMessage("ready"); + } + + let extension = ExtensionTestUtils.loadExtension({ + background: backgroundScript, + manifest: { + permissions: ["webNavigation"], + }, + }); + + await extension.startup(); + await SimpleTest.promiseFocus(window); + + await extension.awaitMessage("ready"); + await UrlbarTestUtils.promiseAutocompleteResultPopup(window, "http://example.com/?q=typedClosed", waitForFocus); + await UrlbarTestUtils.promiseSearchComplete(window); + // Closing the popup forces a different code route that handles no results + // being displayed. + await UrlbarTestUtils.promisePopupClose(window); + EventUtils.synthesizeKey("VK_RETURN", {}); + + await extension.awaitFinish("webNavigation.from_address_bar.typed"); + + await extension.unload(); }); add_task(async function test_webnavigation_urlbar_bookmark_transitions() { function backgroundScript() { browser.webNavigation.onCommitted.addListener((msg) => { browser.test.assertEq("http://example.com/?q=bookmark", msg.url, "Got the expected url"); @@ -144,22 +175,21 @@ add_task(async function test_webnavigati await extension.startup(); await SimpleTest.promiseFocus(window); await extension.awaitMessage("ready"); await promiseAutocompleteResultPopup("Bookmark To Click"); - let item = gURLBar.popup.richlistbox.getItemAtIndex(1); - item.click(); + let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 1); + EventUtils.synthesizeMouseAtCenter(result.element.row, {}); await extension.awaitFinish("webNavigation.from_address_bar.auto_bookmark"); await extension.unload(); - info("extension unloaded"); }); add_task(async function test_webnavigation_urlbar_keyword_transition() { function backgroundScript() { browser.webNavigation.onCommitted.addListener((msg) => { browser.test.assertEq(`http://example.com/?q=search`, msg.url, "Got the expected url"); @@ -190,23 +220,22 @@ add_task(async function test_webnavigati await extension.startup(); await SimpleTest.promiseFocus(window); await extension.awaitMessage("ready"); await promiseAutocompleteResultPopup("testkw search"); - let item = gURLBar.popup.richlistbox.getItemAtIndex(0); - item.click(); + let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0); + EventUtils.synthesizeMouseAtCenter(result.element.row, {}); await extension.awaitFinish("webNavigation.from_address_bar.keyword"); await extension.unload(); - info("extension unloaded"); }); add_task(async function test_webnavigation_urlbar_search_transitions() { function backgroundScript() { browser.webNavigation.onCommitted.addListener((msg) => { browser.test.assertEq("http://mochi.test:8888/", msg.url, "Got the expected url"); @@ -232,16 +261,15 @@ add_task(async function test_webnavigati await extension.startup(); await SimpleTest.promiseFocus(window); await extension.awaitMessage("ready"); await prepareSearchEngine(); await promiseAutocompleteResultPopup("foo"); - let item = gURLBar.popup.richlistbox.getItemAtIndex(0); - item.click(); + let result = await UrlbarTestUtils.getDetailsOfResultAt(window, 0); + EventUtils.synthesizeMouseAtCenter(result.element.row, {}); await extension.awaitFinish("webNavigation.from_address_bar.generated"); await extension.unload(); - info("extension unloaded"); });
--- a/browser/components/urlbar/UrlbarInput.jsm +++ b/browser/components/urlbar/UrlbarInput.jsm @@ -340,17 +340,17 @@ class UrlbarInput { let browser = this.window.gBrowser.selectedBrowser; let lastLocationChange = browser.lastLocationChange; UrlbarUtils.getShortcutOrURIAndPostData(url).then(data => { if (where != "current" || browser.lastLocationChange == lastLocationChange) { openParams.postData = data.postData; openParams.allowInheritPrincipal = data.mayInheritPrincipal; - this._loadURL(data.url, where, openParams, browser); + this._loadURL(data.url, where, openParams, null, browser); } }); return; } this._loadURL(url, where, openParams); } @@ -440,17 +440,20 @@ class UrlbarInput { where); return; } } if (!url) { throw new Error(`Invalid url for result ${JSON.stringify(result)}`); } - this._loadURL(url, where, openParams); + this._loadURL(url, where, openParams, { + source: result.source, + type: result.type, + }); } /** * Called by the view when moving through results with the keyboard, and when * picking a result. * * @param {UrlbarResult} [result] * The result that was selected or picked, null if no result was selected. @@ -981,19 +984,25 @@ class UrlbarInput { * The parameters related to how and where the result will be opened. * Further supported paramters are listed in utilityOverlay.js#openUILinkIn. * @param {object} params.triggeringPrincipal * The principal that the action was triggered from. * @param {nsIInputStream} [params.postData] * The POST data associated with a search submission. * @param {boolean} [params.allowInheritPrincipal] * If the principal may be inherited + * @param {object} [result] + * Details of the selected result, if any + * @param {UrlbarUtils.RESULT_TYPE} [result.type] + * Details of the result type, if any. + * @param {UrlbarUtils.RESULT_SOURCE} [result.source] + * Details of the result source, if any. * @param {object} browser [optional] the browser to use for the load. */ - _loadURL(url, openUILinkWhere, params, + _loadURL(url, openUILinkWhere, params, result = {}, browser = this.window.gBrowser.selectedBrowser) { this.value = url; browser.userTypedValue = url; if (this.window.gInitialPages.includes(url)) { browser.initialPageLoadedFromUserAction = url; } try { @@ -1027,16 +1036,19 @@ class UrlbarInput { // occurs in a new tab, we want focus to be restored to the content // area when the current tab is re-selected. browser.focus(); if (openUILinkWhere != "current") { this.handleRevert(); } + // Notify about the start of navigation. + this._notifyStartNavigation(result); + try { this.window.openTrustedLinkIn(url, openUILinkWhere, params); } catch (ex) { // This load can throw an exception in certain cases, which means // we'll want to replace the URL with the loaded URL: if (ex.result != Cr.NS_ERROR_LOAD_SHOWED_ERRORPAGE) { this.handleRevert(); } @@ -1126,16 +1138,30 @@ class UrlbarInput { } else { pasteAndGo.setAttribute("disabled", "true"); } }); insertLocation.insertAdjacentElement("afterend", pasteAndGo); } + /** + * This notifies observers that the user has entered or selected something in + * the URL bar which will cause navigation. + * + * We use the observer service, so that we don't need to load extra facilities + * if they aren't being used, e.g. WebNavigation. + * + * @param {UrlbarResult} [result] + * The result that was selected, if any. + */ + _notifyStartNavigation(result) { + Services.obs.notifyObservers({result}, "urlbar-user-start-navigation"); + } + // Event handlers below. _on_blur(event) { this.formatValue(); // Respect the autohide preference for easier inspecting/debugging via // the browser toolbox. if (!UrlbarPrefs.get("ui.popup.disable_autohide")) { this.view.close(UrlbarUtils.CANCEL_REASON.BLUR);
--- a/browser/components/urlbar/UrlbarUtils.jsm +++ b/browser/components/urlbar/UrlbarUtils.jsm @@ -61,16 +61,18 @@ var UrlbarUtils = { PROFILE: 2, // Can be delayed, contains results coming from the network. NETWORK: 3, // Can be delayed, contains results coming from unknown sources. EXTENSION: 4, }, // Defines UrlbarResult types. + // If you add new result types, consider checking if consumers of + // "urlbar-user-start-navigation" need update as well. RESULT_TYPE: { // An open tab. // Payload: { icon, url, userContextId } TAB_SWITCH: 1, // A search suggestion or engine. // Payload: { icon, suggestion, keyword, query } SEARCH: 2, // A common url/title tuple, may be a bookmark with tags. @@ -86,16 +88,18 @@ var UrlbarUtils = { // Payload: { url, icon, device, title } REMOTE_TAB: 6, }, // This defines the source of results returned by a provider. Each provider // can return results from more than one source. This is used by the // ProvidersManager to decide which providers must be queried and which // results can be returned. + // If you add new source types, consider checking if consumers of + // "urlbar-user-start-navigation" need update as well. RESULT_SOURCE: { BOOKMARKS: 1, HISTORY: 2, SEARCH: 3, TABS: 4, OTHER_LOCAL: 5, OTHER_NETWORK: 6, },
--- a/toolkit/modules/addons/WebNavigation.jsm +++ b/toolkit/modules/addons/WebNavigation.jsm @@ -7,16 +7,18 @@ const EXPORTED_SYMBOLS = ["WebNavigation"]; const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm"); ChromeUtils.defineModuleGetter(this, "BrowserWindowTracker", "resource:///modules/BrowserWindowTracker.jsm"); ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm"); +ChromeUtils.defineModuleGetter(this, "UrlbarUtils", + "resource:///modules/UrlbarUtils.jsm"); // Maximum amount of time that can be passed and still consider // the data recent (similar to how is done in nsNavHistory, // e.g. nsNavHistory::CheckIsRecentEvent, but with a lower threshold value). const RECENT_DATA_THRESHOLD = 5 * 1000000; var Manager = { // Map[string -> Map[listener -> URLFilter]] @@ -28,32 +30,34 @@ var Manager = { this.recentTabTransitionData = new WeakMap(); // Collect the pending created navigation target events that still have to // pair the message received from the source tab to the one received from // the new tab. this.createdNavigationTargetByOuterWindowId = new Map(); Services.obs.addObserver(this, "autocomplete-did-enter-text", true); + Services.obs.addObserver(this, "urlbar-user-start-navigation", true); Services.obs.addObserver(this, "webNavigation-createdNavigationTarget"); Services.mm.addMessageListener("Content:Click", this); Services.mm.addMessageListener("Extension:DOMContentLoaded", this); Services.mm.addMessageListener("Extension:StateChange", this); Services.mm.addMessageListener("Extension:DocumentChange", this); Services.mm.addMessageListener("Extension:HistoryChange", this); Services.mm.addMessageListener("Extension:CreatedNavigationTarget", this); Services.mm.loadFrameScript("resource://gre/modules/WebNavigationContent.js", true); }, uninit() { // Stop collecting recent tab transition data and reset the WeakMap. Services.obs.removeObserver(this, "autocomplete-did-enter-text"); + Services.obs.removeObserver(this, "urlbar-user-start-navigation"); Services.obs.removeObserver(this, "webNavigation-createdNavigationTarget"); Services.mm.removeMessageListener("Content:Click", this); Services.mm.removeMessageListener("Extension:StateChange", this); Services.mm.removeMessageListener("Extension:DocumentChange", this); Services.mm.removeMessageListener("Extension:HistoryChange", this); Services.mm.removeMessageListener("Extension:DOMContentLoaded", this); Services.mm.removeMessageListener("Extension:CreatedNavigationTarget", this); @@ -103,17 +107,21 @@ var Manager = { * and webNavigation-createdNavigationTarget (to fire the onCreatedNavigationTarget * related to windows or tabs opened from the main process) topics. * * @param {nsIAutoCompleteInput|Object} subject * @param {string} topic * @param {string|undefined} data */ observe: function(subject, topic, data) { - if (topic == "autocomplete-did-enter-text") { + if (topic == "urlbar-user-start-navigation") { + this.onURLBarUserStartNavigation(subject.wrappedJSObject); + } else if (topic == "autocomplete-did-enter-text") { + // autocomplete-did-enter-text supports the legacy urlbar. Bug 1535379 will + // clean this up. this.onURLBarAutoCompletion(subject); } else if (topic == "webNavigation-createdNavigationTarget") { // The observed notification is coming from privileged JavaScript components running // in the main process (e.g. when a new tab or window is opened using the context menu // or Ctrl/Shift + click on a link). const { createdTabBrowser, url, @@ -129,83 +137,142 @@ var Manager = { } }, /** * Recognize the type of urlbar user interaction (e.g. typing a new url, * clicking on an url generated from a searchengine or a keyword, or a * bookmark found by the urlbar autocompletion). * + * @param {object} acData + * The data for the autocompleted item. + * @param {object} [acData.result] + * The result information associated with the navigation action. + * @param {UrlbarUtils.RESULT_TYPE} [acData.result.type] + * The result type associated with the navigation action. + * @param {UrlbarUtils.RESULT_SOURCE} [acData.result.source] + * The result source associated with the navigation action. + */ + onURLBarUserStartNavigation(acData) { + let tabTransitionData = { + from_address_bar: true, + }; + + if (!acData.result) { + tabTransitionData.typed = true; + } else { + switch (acData.result.type) { + case UrlbarUtils.RESULT_TYPE.KEYWORD: + tabTransitionData.keyword = true; + break; + case UrlbarUtils.RESULT_TYPE.SEARCH: + tabTransitionData.generated = true; + break; + case UrlbarUtils.RESULT_TYPE.URL: + if (acData.result.source == UrlbarUtils.RESULT_SOURCE.BOOKMARKS) { + tabTransitionData.auto_bookmark = true; + } else { + tabTransitionData.typed = true; + } + break; + case UrlbarUtils.RESULT_TYPE.REMOTE_TAB: + // Remote tab are autocomplete results related to + // tab urls from a remote synchronized Firefox. + tabTransitionData.typed = true; + break; + case UrlbarUtils.RESULT_TYPE.TAB_SWITCH: + // This "switchtab" autocompletion should be ignored, because + // it is not related to a navigation. + // Fall through. + case UrlbarUtils.RESULT_TYPE.OMNIBOX: + // "Omnibox" should be ignored as the add-on may or may not initiate + // a navigation on the item being selected. + throw new Error(`Unexpectedly received notification for ${acData.result.type}`); + default: + Cu.reportError(`Received unexpected result type ${acData.result.type}, falling back to typed transition.`); + // Fallback on "typed" if the type is unknown. + tabTransitionData.typed = true; + } + } + + this.setRecentTabTransitionData(tabTransitionData); + }, + + /** + * Recognize the type of urlbar user interaction (e.g. typing a new url, + * clicking on an url generated from a searchengine or a keyword, or a + * bookmark found by the urlbar autocompletion). + * * @param {nsIAutoCompleteInput} input */ onURLBarAutoCompletion(input) { if (input && input instanceof Ci.nsIAutoCompleteInput) { // We are only interested in urlbar autocompletion events if (input.id !== "urlbar") { return; } let controller = input.popup.view.QueryInterface(Ci.nsIAutoCompleteController); let idx = input.popup.selectedIndex; - let tabTransistionData = { + let tabTransitionData = { from_address_bar: true, }; if (idx < 0 || idx >= controller.matchCount) { // Recognize when no valid autocomplete results has been selected. - tabTransistionData.typed = true; + tabTransitionData.typed = true; } else { let value = controller.getValueAt(idx); let action = input._parseActionUrl(value); if (action) { // Detect keyword and generated and more typed scenarios. switch (action.type) { case "keyword": - tabTransistionData.keyword = true; + tabTransitionData.keyword = true; break; case "searchengine": case "searchsuggestion": - tabTransistionData.generated = true; + tabTransitionData.generated = true; break; case "visiturl": // Visiturl are autocompletion results related to // history suggestions. - tabTransistionData.typed = true; + tabTransitionData.typed = true; break; case "remotetab": // Remote tab are autocomplete results related to // tab urls from a remote synchronized Firefox. - tabTransistionData.typed = true; + tabTransitionData.typed = true; break; case "switchtab": // This "switchtab" autocompletion should be ignored, because // it is not related to a navigation. return; default: // Fallback on "typed" if unable to detect a known moz-action type. - tabTransistionData.typed = true; + tabTransitionData.typed = true; } } else { // Special handling for bookmark urlbar autocompletion // (which happens when we got a null action and a valid selectedIndex) let styles = new Set(controller.getStyleAt(idx).split(/\s+/)); if (styles.has("bookmark")) { - tabTransistionData.auto_bookmark = true; + tabTransitionData.auto_bookmark = true; } else { // Fallback on "typed" if unable to detect a specific actionType // (and when in the styles there are "autofill" or "history"). - tabTransistionData.typed = true; + tabTransitionData.typed = true; } } } - this.setRecentTabTransitionData(tabTransistionData); + this.setRecentTabTransitionData(tabTransitionData); } }, /** * Keep track of a recent user interaction and cache it in a * map associated to the current selected tab. * * @param {object} tabTransitionData