From 36d469443d498a2b24079776b6e24afbd9fa7248 Mon Sep 17 00:00:00 2001 From: Daisuke Akatsuka Date: Mon, 26 Oct 2020 05:30:44 +0000 (2020-10-26) Subject: [PATCH] CVE-2020-26979 --- browser/base/content/utilityOverlay.js | 7 +- browser/components/urlbar/UrlbarInput.jsm | 63 +++++++++-- .../urlbar/tests/browser/browser_enter.js | 100 ++++++++++++++++-- 3 files changed, 147 insertions(+), 23 deletions(-) diff --git a/browser/base/content/utilityOverlay.js b/browser/base/content/utilityOverlay.js index a23d6f05e6..c9d0ef8a71 100644 --- a/browser/base/content/utilityOverlay.js +++ b/browser/base/content/utilityOverlay.js @@ -695,8 +695,11 @@ function openLinkIn(url, where, params) { } break; } - - if (!focusUrlBar && targetBrowser == w.gBrowser.selectedBrowser) { + if ( + !params.avoidBrowserFocus && + !focusUrlBar && + targetBrowser == w.gBrowser.selectedBrowser + ) { // Focus the content, but only if the browser used for the load is selected. targetBrowser.focus(); } diff --git a/browser/components/urlbar/UrlbarInput.jsm b/browser/components/urlbar/UrlbarInput.jsm index 366149ec07..b9a916ca1b 100644 --- a/browser/components/urlbar/UrlbarInput.jsm +++ b/browser/components/urlbar/UrlbarInput.jsm @@ -16,6 +16,7 @@ XPCOMUtils.defineLazyModuleGetters(this, { ExtensionSearchHandler: "resource://gre/modules/ExtensionSearchHandler.jsm", FormHistory: "resource://gre/modules/FormHistory.jsm", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm", + PromiseUtils: "resource://gre/modules/PromiseUtils.jsm", ReaderMode: "resource://gre/modules/ReaderMode.jsm", Services: "resource://gre/modules/Services.jsm", TopSiteAttribution: "resource:///modules/TopSiteAttribution.jsm", @@ -474,7 +475,7 @@ class UrlbarInput { isValidUrl = true; } catch (ex) {} if (isValidUrl) { - this._loadURL(url, where, openParams); + this._loadURL(url, event, where, openParams); return; } @@ -528,8 +529,8 @@ class UrlbarInput { browser.lastLocationChange == lastLocationChange ) { openParams.postData = postData.value; - this._loadURL(uri.spec, where, openParams, null, browser); - } + this._loadURL(uri.spec, event, where, openParams, null, browser); + } } }); // Don't add further handling here, the catch above is our last resort. @@ -600,8 +601,8 @@ class UrlbarInput { selIndex, selType: "canonized", }); - this._loadURL(this.value, where, openParams, browser); - return; + this._loadURL(this.value, event, where, openParams, browser); + return; } let { url, postData } = UrlbarUtils.getUrlFromResult(result); @@ -844,6 +845,7 @@ class UrlbarInput { this._loadURL( url, + event, where, openParams, { @@ -1757,6 +1759,8 @@ class UrlbarInput { * * @param {string} url * The URL to open. + @param {Event} event + * The event that triggered to load the url. * @param {string} openUILinkWhere * Where we expect the result to be opened. * @param {object} params @@ -1778,6 +1782,7 @@ class UrlbarInput { */ _loadURL( url, + event, openUILinkWhere, params, resultDetails = null, @@ -1835,12 +1840,19 @@ class UrlbarInput { } else { params.initiatingDoc = this.window.document; } + if (event?.keyCode === KeyEvent.DOM_VK_RETURN) { + if (openUILinkWhere === "current") { + params.avoidBrowserFocus = true; + this._keyDownEnterDeferred?.resolve(browser); + } + } // Focus the content area before triggering loads, since if the load // 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 (!params.avoidBrowserFocus) { + browser.focus(); + } if (openUILinkWhere != "current") { this.handleRevert(); } @@ -2059,7 +2071,14 @@ class UrlbarInput { ) { this.window.UpdatePopupNotificationsVisibility(); } - + + // If user move the focus to another component while pressing Enter key, + // then keyup at that component, as we can't get the event, clear the promise. + if (this._keyDownEnterDeferred) { + this._keyDownEnterDeferred.resolve(); + this._keyDownEnterDeferred = null; + } + Services.obs.notifyObservers(null, "urlbar-blur"); } @@ -2376,6 +2395,12 @@ class UrlbarInput { } _on_keydown(event) { + if (event.keyCode === KeyEvent.DOM_VK_RETURN) { + if (this._keyDownEnterDeferred) { + this._keyDownEnterDeferred.reject(); + } + this._keyDownEnterDeferred = PromiseUtils.defer(); + } // Due to event deferring, it's possible preventDefault() won't be invoked // soon enough to actually prevent some of the default behaviors, thus we // have to handle the event "twice". This first immediate call passes false @@ -2391,9 +2416,27 @@ class UrlbarInput { this.controller.handleKeyNavigation(event); }); } + + async _on_keyup(event) { + if ( + event.keyCode === KeyEvent.DOM_VK_RETURN && + this._keyDownEnterDeferred + ) { + try { + const loadingBrowser = await this._keyDownEnterDeferred.promise; + // Ensure the selected browser didn't change in the meanwhile. + if (this.window.gBrowser.selectedBrowser === loadingBrowser) { + loadingBrowser.focus(); + } + } catch (ex) { + // Not all the Enter actions in the urlbar will cause a navigation, then it + // is normal for this to be rejected. + } + this._keyDownEnterDeferred = null; + return; + } - _on_keyup(event) { - this._toggleActionOverride(event); + this._toggleActionOverride(event); } _on_compositionstart(event) { diff --git a/browser/components/urlbar/tests/browser/browser_enter.js b/browser/components/urlbar/tests/browser/browser_enter.js index 3eb6df89c7..6d89815345 100644 --- a/browser/components/urlbar/tests/browser/browser_enter.js +++ b/browser/components/urlbar/tests/browser/browser_enter.js @@ -6,6 +6,20 @@ const TEST_VALUE = "example.com/\xF7?\xF7"; const START_VALUE = "example.com/%C3%B7?%C3%B7"; +add_task(async function setup() { + const engine = await SearchTestUtils.promiseNewSearchEngine( + getRootDirectory(gTestPath) + "searchSuggestionEngine.xml" + ); + + const defaultEngine = Services.search.defaultEngine; + Services.search.defaultEngine = engine; + + registerCleanupFunction(async function() { + Services.search.defaultEngine = defaultEngine; + }); +}); + + add_task(async function returnKeypress() { info("Simple return keypress"); let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, START_VALUE); @@ -82,12 +96,7 @@ add_task(async function altGrReturnKeypress() { add_task(async function searchOnEnterNoPick() { info("Search on Enter without picking a urlbar result"); - let engine = await SearchTestUtils.promiseNewSearchEngine( - getRootDirectory(gTestPath) + "searchSuggestionEngine.xml" - ); - let defaultEngine = Services.search.defaultEngine; - Services.search.defaultEngine = engine; - // Why is BrowserTestUtils.openNewForegroundTab not causing the bug? + // Why is BrowserTestUtils.openNewForegroundTab not causing the bug? let promiseTabOpened = BrowserTestUtils.waitForEvent( gBrowser.tabContainer, "TabOpen" @@ -95,11 +104,7 @@ add_task(async function searchOnEnterNoPick() { EventUtils.synthesizeMouseAtCenter(gBrowser.tabContainer.newTabButton, {}); let openEvent = await promiseTabOpened; let tab = openEvent.target; - registerCleanupFunction(async function() { - Services.search.defaultEngine = defaultEngine; - BrowserTestUtils.removeTab(tab); - }); - + let loadPromise = BrowserTestUtils.browserLoaded( gBrowser.selectedBrowser, false, @@ -120,4 +125,77 @@ add_task(async function searchOnEnterNoPick() { gURLBar.untrimmedValue, "The location should have changed" ); + + // Cleanup. + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function searchOnEnterSoon() { + info("Search on Enter as soon as typing a char"); + const tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + START_VALUE + ); + + const onLoad = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser); + const onBeforeUnload = SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + () => { + return new Promise(resolve => { + content.window.addEventListener("beforeunload", () => { + resolve(); + }); + }); + } + ); + const onResult = SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { + return new Promise(resolve => { + content.window.addEventListener("keyup", () => { + resolve("keyup"); + }); + content.window.addEventListener("unload", () => { + resolve("unload"); + }); + }); + }); + + // Focus on the input field in urlbar. + EventUtils.synthesizeMouseAtCenter(gURLBar.inputField, {}); + const ownerDocument = gBrowser.selectedBrowser.ownerDocument; + is( + ownerDocument.activeElement, + gURLBar.inputField, + "The input field in urlbar has focus" + ); + + info("Keydown a char and Enter"); + EventUtils.synthesizeKey("x", { type: "keydown" }); + EventUtils.synthesizeKey("KEY_Enter", { type: "keydown" }); + + // Wait for beforeUnload event in the content. + await onBeforeUnload; + is( + ownerDocument.activeElement, + gURLBar.inputField, + "The input field in urlbar still has focus" + ); + + // Keyup both key as soon as beforeUnload event happens. + EventUtils.synthesizeKey("x", { type: "keyup" }); + EventUtils.synthesizeKey("KEY_Enter", { type: "keyup" }); + + // Wait for moving the focus. + await TestUtils.waitForCondition( + () => ownerDocument.activeElement === gBrowser.selectedBrowser + ); + info("The focus is moved to the browser"); + + // Check whether keyup event is not captured before unload event happens. + const result = await onResult; + is(result, "unload", "Keyup event is not captured."); + + // Cleanup. + await onLoad; + BrowserTestUtils.removeTab(tab); }); -- 2.27.0