diff --git a/browser/components/uitour/UITourChild.sys.mjs b/browser/components/uitour/UITourChild.sys.mjs index 969b6923520d..2df3b6e4c51a 100644 --- a/browser/components/uitour/UITourChild.sys.mjs +++ b/browser/components/uitour/UITourChild.sys.mjs @@ -2,12 +2,11 @@ * 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/. */ -const PREF_TEST_ORIGINS = "browser.uitour.testingOrigins"; -const UITOUR_PERMISSION = "uitour"; +import { UITourUtils } from "moz-src:///browser/components/uitour/UITourUtils.sys.mjs"; export class UITourChild extends JSWindowActorChild { handleEvent(event) { - if (!this.ensureTrustedOrigin()) { + if (!UITourUtils.ensureTrustedOrigin(this.manager)) { return; } @@ -18,62 +17,6 @@ export class UITourChild extends JSWindowActorChild { }); } - isTestingOrigin(aURI) { - let testingOrigins = Services.prefs.getStringPref(PREF_TEST_ORIGINS, ""); - if (!testingOrigins) { - return false; - } - - // Allow any testing origins (comma-seperated). - for (let origin of testingOrigins.split(/\s*,\s*/)) { - try { - let testingURI = Services.io.newURI(origin); - if (aURI.prePath == testingURI.prePath) { - return true; - } - } catch (ex) { - console.error(ex); - } - } - return false; - } - - // This function is copied from UITour.sys.mjs. - isSafeScheme(aURI) { - let allowedSchemes = new Set(["https", "about"]); - if (!allowedSchemes.has(aURI.scheme)) { - return false; - } - - return true; - } - - ensureTrustedOrigin() { - if (this.browsingContext.top != this.browsingContext) { - return false; - } - - let uri = this.document.documentURIObject; - - if (uri.schemeIs("chrome")) { - return true; - } - - if (!this.isSafeScheme(uri)) { - return false; - } - - let permission = Services.perms.testPermissionFromPrincipal( - this.document.nodePrincipal, - UITOUR_PERMISSION - ); - if (permission == Services.perms.ALLOW_ACTION) { - return true; - } - - return this.isTestingOrigin(uri); - } - receiveMessage(aMessage) { switch (aMessage.name) { case "UITour:SendPageCallback": @@ -86,7 +29,7 @@ export class UITourChild extends JSWindowActorChild { } sendPageEvent(type, detail) { - if (!this.ensureTrustedOrigin()) { + if (!UITourUtils.ensureTrustedOrigin(this.manager)) { return; } diff --git a/browser/components/uitour/UITourParent.sys.mjs b/browser/components/uitour/UITourParent.sys.mjs index 562222510747..61506d358db6 100644 --- a/browser/components/uitour/UITourParent.sys.mjs +++ b/browser/components/uitour/UITourParent.sys.mjs @@ -3,9 +3,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { UITour } from "moz-src:///browser/components/uitour/UITour.sys.mjs"; +import { UITourUtils } from "moz-src:///browser/components/uitour/UITourUtils.sys.mjs"; export class UITourParent extends JSWindowActorParent { receiveMessage(message) { + if (!UITourUtils.ensureTrustedOrigin(this.manager)) { + return; + } switch (message.name) { case "UITour:onPageEvent": if (this.manager.rootFrameLoader) { diff --git a/browser/components/uitour/UITourUtils.sys.mjs b/browser/components/uitour/UITourUtils.sys.mjs new file mode 100644 index 000000000000..b390b2b53dca --- /dev/null +++ b/browser/components/uitour/UITourUtils.sys.mjs @@ -0,0 +1,84 @@ +/* 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/. */ + +const PREF_TEST_ORIGINS = "browser.uitour.testingOrigins"; +const UITOUR_PERMISSION = "uitour"; + +export let UITourUtils = { + /** + * Check if we've got a testing origin. + * + * @param {nsIURI} uri + * The URI to check + * @returns {boolean} + * Whether or not it's a testing origin. + */ + isTestingOrigin(uri) { + let testingOrigins = Services.prefs.getStringPref(PREF_TEST_ORIGINS, ""); + if (!testingOrigins) { + return false; + } + + // Allow any testing origins (comma-seperated). + for (let origin of testingOrigins.split(/\s*,\s*/)) { + try { + let testingURI = Services.io.newURI(origin); + if (uri.prePath == testingURI.prePath) { + return true; + } + } catch (ex) { + console.error(ex); + } + } + return false; + }, + + /** + * + * @param {WindowGlobalChild|WindowGlobalParent} windowGlobal + * The parent/child representation of a window global to check if we can + * use UITour. + * @returns {boolean} + * Whether or not we can use UITour here. + */ + ensureTrustedOrigin(windowGlobal) { + // If we're not top-most or no longer current, bail out immediately. + if (windowGlobal.browsingContext.parent || !windowGlobal.isCurrentGlobal) { + return false; + } + + let principal, uri; + // We can get either a WindowGlobalParent or WindowGlobalChild, depending on + // what process we're called in, and determining the secure context-ness and + // principal + URI needs different approaches based on this. + if (WindowGlobalParent.isInstance(windowGlobal)) { + if (!windowGlobal.browsingContext.secureBrowserUI?.isSecureContext) { + return false; + } + principal = windowGlobal.documentPrincipal; + uri = windowGlobal.documentURI; + } else { + if (!windowGlobal.contentWindow?.isSecureContext) { + return false; + } + let document = windowGlobal.contentWindow.document; + principal = document?.nodePrincipal; + uri = document?.documentURIObject; + } + + if (!principal) { + return false; + } + + let permission = Services.perms.testPermissionFromPrincipal( + principal, + UITOUR_PERMISSION + ); + if (permission == Services.perms.ALLOW_ACTION) { + return true; + } + + return uri && this.isTestingOrigin(uri); + }, +}; diff --git a/browser/components/uitour/moz.build b/browser/components/uitour/moz.build index 3a891ae10a02..e679af7e23eb 100644 --- a/browser/components/uitour/moz.build +++ b/browser/components/uitour/moz.build @@ -2,7 +2,12 @@ # 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/. -MOZ_SRC_FILES += ["UITour.sys.mjs", "UITourChild.sys.mjs", "UITourParent.sys.mjs"] +MOZ_SRC_FILES += [ + "UITour.sys.mjs", + "UITourChild.sys.mjs", + "UITourParent.sys.mjs", + "UITourUtils.sys.mjs", +] BROWSER_CHROME_MANIFESTS += [ "test/browser.toml", diff --git a/browser/components/uitour/test/browser_UITour_private_browsing.js b/browser/components/uitour/test/browser_UITour_private_browsing.js index 0c5a52f66742..d69bb30fbb9a 100644 --- a/browser/components/uitour/test/browser_UITour_private_browsing.js +++ b/browser/components/uitour/test/browser_UITour_private_browsing.js @@ -16,6 +16,10 @@ add_task(async function test_privatebrowsing_window() { const ABOUT_ORIGIN_WITH_UITOUR_DEFAULT = "about:newtab"; const HTTPS_ORIGIN_WITH_UITOUR_DEFAULT = "https://www.mozilla.org"; + let { UITourUtils } = ChromeUtils.importESModule( + "moz-src:///browser/components/uitour/UITourUtils.sys.mjs" + ); + let win = await BrowserTestUtils.openNewBrowserWindow({ private: true }); let browser = win.gBrowser.selectedBrowser; @@ -26,10 +30,21 @@ add_task(async function test_privatebrowsing_window() { BrowserTestUtils.startLoadingURIString(browser, uri); await BrowserTestUtils.browserLoaded(browser); + Assert.ok( + UITourUtils.ensureTrustedOrigin( + browser.browsingContext.currentWindowGlobal + ), + "Page should be considered trusted for UITour in the parent." + ); + await SpecialPowers.spawn(browser, [], async () => { let actor = content.windowGlobalChild.getActor("UITour"); + // eslint-disable-next-line no-shadow + let { UITourUtils } = ChromeUtils.importESModule( + "moz-src:///browser/components/uitour/UITourUtils.sys.mjs" + ); Assert.ok( - actor.ensureTrustedOrigin(), + UITourUtils.ensureTrustedOrigin(actor.manager), "Page should be considered trusted for UITour." ); }); diff --git a/dom/chrome-webidl/WindowGlobalActors.webidl b/dom/chrome-webidl/WindowGlobalActors.webidl index ead070a2fec9..54b477197822 100644 --- a/dom/chrome-webidl/WindowGlobalActors.webidl +++ b/dom/chrome-webidl/WindowGlobalActors.webidl @@ -178,6 +178,7 @@ interface WindowGlobalChild { readonly attribute boolean isInProcess; readonly attribute BrowsingContext browsingContext; readonly attribute WindowContext windowContext; + readonly attribute WindowProxy? contentWindow; readonly attribute boolean isCurrentGlobal; diff --git a/dom/ipc/WindowGlobalChild.cpp b/dom/ipc/WindowGlobalChild.cpp index 356f12605e12..de149d7126c9 100644 --- a/dom/ipc/WindowGlobalChild.cpp +++ b/dom/ipc/WindowGlobalChild.cpp @@ -258,6 +258,13 @@ dom::BrowsingContext* WindowGlobalChild::BrowsingContext() { return mWindowContext->GetBrowsingContext(); } +Nullable WindowGlobalChild::GetContentWindow() { + if (IsCurrentGlobal()) { + return WindowProxyHolder(BrowsingContext()); + } + return nullptr; +} + uint64_t WindowGlobalChild::InnerWindowId() { return mWindowContext->InnerWindowId(); } diff --git a/dom/ipc/WindowGlobalChild.h b/dom/ipc/WindowGlobalChild.h index b90f692ddcd9..b5f207a5b9cd 100644 --- a/dom/ipc/WindowGlobalChild.h +++ b/dom/ipc/WindowGlobalChild.h @@ -14,6 +14,7 @@ #include "nsWrapperCache.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/WindowGlobalActor.h" +#include "mozilla/dom/WindowProxyHolder.h" class nsGlobalWindowInner; class nsDocShell; @@ -53,6 +54,8 @@ class WindowGlobalChild final : public WindowGlobalActor, dom::WindowContext* WindowContext() const { return mWindowContext; } nsGlobalWindowInner* GetWindowGlobal() const { return mWindowGlobal; } + Nullable GetContentWindow(); + // Has this actor been shut down bool IsClosed() { return !CanSend(); } void Destroy();