diff --git a/browser/components/urlbar/UrlbarController.jsm b/browser/components/urlbar/UrlbarController.jsm index d9fb6f946a47..ca7b19517f6c 100644 --- a/browser/components/urlbar/UrlbarController.jsm +++ b/browser/components/urlbar/UrlbarController.jsm @@ -80,6 +80,13 @@ class UrlbarController { this.engagementEvent = new TelemetryEvent(options.eventTelemetryCategory); } + uninit() { + this.browserWindow = null; + this.input = null; + this.view = null; + this._listeners.clear(); + } + get NOTIFICATIONS() { return NOTIFICATIONS; } diff --git a/browser/components/urlbar/UrlbarInput.jsm b/browser/components/urlbar/UrlbarInput.jsm index dc6f42179f27..2cd97364ac26 100644 --- a/browser/components/urlbar/UrlbarInput.jsm +++ b/browser/components/urlbar/UrlbarInput.jsm @@ -270,6 +270,8 @@ class UrlbarInput { Services.prefs.removeObserver("browser.urlbar.openViewOnFocus", this); + this.controller.uninit(); + delete this.document; delete this.window; delete this.eventBufferer; @@ -956,11 +958,12 @@ class UrlbarInput { get openViewOnFocusForCurrentTab() { return ( - this._openViewOnFocus && - !["about:newtab", "about:home"].includes( - this.window.gBrowser.currentURI.spec - ) && - !this.isPrivate + this._openViewOnFocusAndSearchString || + (this._openViewOnFocus && + !["about:newtab", "about:home"].includes( + this.window.gBrowser.currentURI.spec + ) && + !this.isPrivate) ); } @@ -1016,6 +1019,14 @@ class UrlbarInput { // Private methods below. + get _openViewOnFocusAndSearchString() { + return ( + this.megabar && + this.value && + this.getAttribute("pageproxystate") != "valid" + ); + } + async _updateLayoutBreakoutDimensions() { // When this method gets called a second time before the first call // finishes, we need to disregard the first one. diff --git a/browser/components/urlbar/UrlbarProvidersManager.jsm b/browser/components/urlbar/UrlbarProvidersManager.jsm index 4a61bc3aa812..2c334c9fcbc1 100644 --- a/browser/components/urlbar/UrlbarProvidersManager.jsm +++ b/browser/components/urlbar/UrlbarProvidersManager.jsm @@ -314,6 +314,9 @@ class Query { // Nothing should be failing above, since we catch all the promises, thus // this is not in a finally for now. this.complete = true; + + // Break cycles with the controller to avoid leaks. + this.controller = null; } /** diff --git a/browser/components/urlbar/tests/browser/browser.ini b/browser/components/urlbar/tests/browser/browser.ini index 0149cea3d417..cfe55bb3b018 100644 --- a/browser/components/urlbar/tests/browser/browser.ini +++ b/browser/components/urlbar/tests/browser/browser.ini @@ -90,6 +90,7 @@ support-files = redirect_error.sjs [browser_removeUnsafeProtocolsFromURLBarPaste.js] tags = clipboard [browser_restoreEmptyInput.js] +[browser_retainedResultsOnFocus.js] [browser_search_favicon.js] skip-if = true # Bug 1526222 - Doesn't currently work with QuantumBar [browser_searchTelemetry.js] diff --git a/browser/components/urlbar/tests/browser/browser_retainedResultsOnFocus.js b/browser/components/urlbar/tests/browser/browser_retainedResultsOnFocus.js new file mode 100644 index 000000000000..d109688745be --- /dev/null +++ b/browser/components/urlbar/tests/browser/browser_retainedResultsOnFocus.js @@ -0,0 +1,116 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Tests "megabar" redesign approach with retained results. +// When there is a pending search (user typed a search string and blurred +// without picking a result), on focus we should the search results again. + +// Note: this is similar to openViewOnFocus, but has a different behavior: +// * Private browsing windows work the same as normal windows +// * Opens the panel only if there is a user typed search string + +async function checkOpensOnFocus(win = window) { + Assert.ok(!win.gURLBar.view.isOpen, "check urlbar panel is not open"); + win.gURLBar.blur(); + + info("Check the keyboard shortcut."); + await UrlbarTestUtils.promisePopupOpen(win, () => { + win.document.getElementById("Browser:OpenLocation").doCommand(); + }); + await UrlbarTestUtils.promisePopupClose(win, () => { + win.gURLBar.blur(); + }); + info("Focus with the mouse."); + await UrlbarTestUtils.promisePopupOpen(win, () => { + EventUtils.synthesizeMouseAtCenter(win.gURLBar.inputField, {}, win); + }); +} + +async function checkDoesNotOpenOnFocus(win = window) { + Assert.ok( + !win.gURLBar.openViewOnFocusForCurrentTab, + "openViewOnFocusForCurrentTab should be false" + ); + Assert.ok(!win.gURLBar.view.isOpen, "check urlbar panel is not open"); + win.gURLBar.blur(); + + info("Check the keyboard shortcut."); + win.document.getElementById("Browser:OpenLocation").doCommand(); + // Because the panel opening may not be immediate, we must wait a bit. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, 500)); + Assert.ok(!win.gURLBar.view.isOpen, "check urlbar panel is not open"); + win.gURLBar.blur(); + info("Focus with the mouse."); + EventUtils.synthesizeMouseAtCenter(win.gURLBar.inputField, {}, win); + // Because the panel opening may not be immediate, we must wait a bit. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, 500)); + Assert.ok(!win.gURLBar.view.isOpen, "check urlbar panel is not open"); +} + +add_task(async function setup() { + await SpecialPowers.pushPrefEnv({ + set: [["browser.urlbar.megabar", true]], + }); + // Add some history for the empty panel. + await PlacesTestUtils.addVisits([ + { + uri: "http://mochi.test:8888/", + transition: PlacesUtils.history.TRANSITIONS.TYPED, + }, + ]); + registerCleanupFunction(PlacesUtils.history.clear); +}); + +async function test_window(win) { + for (let url of ["about:newtab", "about:home", "http://example.com/"]) { + // withNewTab may hang on preloaded pages, thus instead of waiting for load + // we just wait for the expected currentURI value. + await BrowserTestUtils.withNewTab( + { gBrowser: win.gBrowser, url, waitForLoad: false }, + async browser => { + await TestUtils.waitForCondition( + () => win.gBrowser.currentURI.spec == url, + "Ensure we're on the expected page" + ); + + info("The panel should not open on the page by default"); + await checkDoesNotOpenOnFocus(win); + + await UrlbarTestUtils.promiseAutocompleteResultPopup({ + window: win, + waitForFocus, + value: "foo", + }); + await UrlbarTestUtils.promisePopupClose(win, () => { + win.gURLBar.blur(); + }); + + info("The panel should open when there's a search string"); + await checkOpensOnFocus(win); + + await UrlbarTestUtils.promisePopupClose(win, () => { + win.gURLBar.blur(); + }); + } + ); + } +} + +add_task(async function test_normalWindow() { + // The megabar works properly in a new window. + let win = await BrowserTestUtils.openNewBrowserWindow(); + await test_window(win); + await BrowserTestUtils.closeWindow(win); +}); + +add_task(async function privateWindow() { + let privateWin = await BrowserTestUtils.openNewBrowserWindow({ + private: true, + }); + await test_window(privateWin); + await BrowserTestUtils.closeWindow(privateWin); +});