/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- * 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 { BrowserWindowTracker } from "resource:///modules/BrowserWindowTracker.sys.mjs"; import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; import { PrivateBrowsingUtils } from "resource://gre/modules/PrivateBrowsingUtils.sys.mjs"; import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; let lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", TaskbarTabsUtils: "resource:///modules/taskbartabs/TaskbarTabsUtils.sys.mjs", URILoadingHelper: "resource:///modules/URILoadingHelper.sys.mjs", }); XPCOMUtils.defineLazyPreferenceGetter( lazy, "loadDivertedInBackground", "browser.tabs.loadDivertedInBackground" ); ChromeUtils.defineLazyGetter(lazy, "ReferrerInfo", () => Components.Constructor( "@mozilla.org/referrer-info;1", "nsIReferrerInfo", "init" ) ); /** * This class is instantiated once for each browser window, and the instance * is exposed as a `browserDOMWindow` property on that window. * * It implements the nsIBrowserDOMWindow interface, which is used by C++ as * well as toolkit code to have an application-agnostic interface to do things * like opening new tabs and windows. Fenix (Firefox on Android) has its own * implementation of the same interface. * * @implements {nsIBrowserDOMWindow} */ export class BrowserDOMWindow { /** * @type {Window} */ win = null; /** * @param {Window} win */ constructor(win) { this.win = win; } /** * @param {Window} win */ static setupInWindow(win) { win.browserDOMWindow = new BrowserDOMWindow(win); } /** * @param {Window} win */ static teardownInWindow(win) { win.browserDOMWindow = null; } /** * @param {nsIURI} aURI * @param {nsIReferrerInfo} aReferrerInfo * @param {boolean} aIsPrivate * @param {boolean} aIsExternal * @param {boolean} [aForceNotRemote=false] * @param {number} [aUserContextId=0] * @param {nsIOpenWindowInfo} [aOpenWindowInfo=null] * @param {Element} [aOpenerBrowser=null] * @param {nsIPrincipal} [aTriggeringPrincipal=null] * @param {string} [aName=""] * @param {nsIPolicyContainer} [aPolicyContainer=null] * @param {boolean} [skipLoad=false] * @param {i16} [aWhere=undefined] * @returns {nsIBrowser|null} */ #openURIInNewTab( aURI, aReferrerInfo, aIsPrivate, aIsExternal, aForceNotRemote = false, aUserContextId = Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID, aOpenWindowInfo = null, aOpenerBrowser = null, aTriggeringPrincipal = null, aName = "", aPolicyContainer = null, aSkipLoad = false, aWhere = undefined ) { let win, needToFocusWin; // try the current window. if we're in a popup or a taskbar tab, fall // back on the most recent browser window if ( this.win.toolbar.visible && !lazy.TaskbarTabsUtils.isTaskbarTabWindow(this.win) ) { win = this.win; } else { win = BrowserWindowTracker.getTopWindow({ private: aIsPrivate }); needToFocusWin = true; } if (!win) { // we couldn't find a suitable window, a new one needs to be opened. return null; } if (aIsExternal && (!aURI || aURI.spec == "about:blank")) { win.BrowserCommands.openTab(); // this also focuses the location bar win.focus(); return win.gBrowser.selectedBrowser; } // OPEN_NEWTAB_BACKGROUND and OPEN_NEWTAB_FOREGROUND are used by // `window.open` with modifiers. // The last case is OPEN_NEWTAB, which is used by: // * a link with `target="_blank"`, without modifiers // * `window.open` without features, without modifiers let loadInBackground; if (aWhere === Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_BACKGROUND) { loadInBackground = true; } else if (aWhere === Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_FOREGROUND) { loadInBackground = false; } else { loadInBackground = lazy.loadDivertedInBackground; } const uriString = aURI ? aURI.spec : "about:blank"; const tabOptions = { triggeringPrincipal: aTriggeringPrincipal, referrerInfo: aReferrerInfo, userContextId: aUserContextId, fromExternal: aIsExternal, inBackground: loadInBackground, forceNotRemote: aForceNotRemote, openWindowInfo: aOpenWindowInfo, openerBrowser: aOpenerBrowser, name: aName, policyContainer: aPolicyContainer, skipLoad: aSkipLoad, }; let tab; if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_AFTER_CURRENT) { tab = win.gBrowser.addAdjacentTab( win.gBrowser.selectedTab, uriString, tabOptions ); } else { tab = win.gBrowser.addTab(uriString, tabOptions); } let browser = win.gBrowser.getBrowserForTab(tab); if (needToFocusWin || (!loadInBackground && aIsExternal)) { win.focus(); } return browser; } /** * @type {nsIBrowserDOMWindow["createContentWindow"]} */ createContentWindow( aURI, aOpenWindowInfo, aWhere, aFlags, aTriggeringPrincipal, aPolicyContainer ) { return this.#getContentWindowOrOpenURI( null, aOpenWindowInfo, aWhere, aFlags, aTriggeringPrincipal, aPolicyContainer, true ); } /** * @type {nsIBrowserDOMWindow["openURI"]} */ openURI( aURI, aOpenWindowInfo, aWhere, aFlags, aTriggeringPrincipal, aPolicyContainer ) { if (!aURI) { console.error("openURI should only be called with a valid URI"); throw Components.Exception("", Cr.NS_ERROR_FAILURE); } return this.#getContentWindowOrOpenURI( aURI, aOpenWindowInfo, aWhere, aFlags, aTriggeringPrincipal, aPolicyContainer, false ); } /** * @param {nsIURI} aURI * @param {nsIOpenWindowInfo} aOpenWindowInfo * @param {i16} aWhere * @param {i32} aFlags * @param {nsIPrincipal} aTriggeringPrincipal * @param {nsIPolicyContainer} aPolicyContainer * @param {boolean} aSkipLoad * @returns {BrowsingContext} */ #getContentWindowOrOpenURI( aURI, aOpenWindowInfo, aWhere, aFlags, aTriggeringPrincipal, aPolicyContainer, aSkipLoad ) { var browsingContext = null; var isExternal = !!(aFlags & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL); var guessUserContextIdEnabled = isExternal && !Services.prefs.getBoolPref( "browser.link.force_default_user_context_id_for_external_opens", false ); var openingUserContextId = (guessUserContextIdEnabled && lazy.URILoadingHelper.guessUserContextId(aURI)) || Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID; if (aOpenWindowInfo && isExternal) { console.error( "BrowserDOMWindow.openURI did not expect aOpenWindowInfo to be " + "passed if the context is OPEN_EXTERNAL." ); throw Components.Exception("", Cr.NS_ERROR_FAILURE); } if (isExternal && aURI && aURI.schemeIs("chrome")) { dump("use --chrome command-line option to load external chrome urls\n"); return null; } if (isExternal) { lazy.NimbusFeatures.externalLinkHandling.recordExposureEvent({ once: true, }); } if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) { /** @type {number} proxy for `browser.link.open_newwindow.override.external` */ const externalLinkOpeningBehavior = lazy.NimbusFeatures.externalLinkHandling.getVariable("openBehavior"); if (isExternal && externalLinkOpeningBehavior != -1) { aWhere = externalLinkOpeningBehavior; } else { aWhere = Services.prefs.getIntPref("browser.link.open_newwindow"); } } let referrerInfo; if (aFlags & Ci.nsIBrowserDOMWindow.OPEN_NO_REFERRER) { referrerInfo = new lazy.ReferrerInfo( Ci.nsIReferrerInfo.EMPTY, false, null ); } else if ( aOpenWindowInfo && aOpenWindowInfo.parent && aOpenWindowInfo.parent.window ) { referrerInfo = new lazy.ReferrerInfo( aOpenWindowInfo.parent.window.document.referrerInfo.referrerPolicy, true, Services.io.newURI(aOpenWindowInfo.parent.window.location.href) ); } else { referrerInfo = new lazy.ReferrerInfo( Ci.nsIReferrerInfo.EMPTY, true, null ); } let isPrivate = aOpenWindowInfo ? aOpenWindowInfo.originAttributes.privateBrowsingId != 0 : PrivateBrowsingUtils.isWindowPrivate(this.win); switch (aWhere) { case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW: { // FIXME: Bug 408379. So how come this doesn't send the // referrer like the other loads do? var url = aURI && aURI.spec; let features = "all,dialog=no"; if (isPrivate) { features += ",private"; } // Pass all params to openDialog to ensure that "url" isn't passed through // loadOneOrMoreURIs, which splits based on "|" try { let extraOptions = Cc[ "@mozilla.org/hash-property-bag;1" ].createInstance(Ci.nsIWritablePropertyBag2); extraOptions.setPropertyAsBool("fromExternal", isExternal); this.win.openDialog( AppConstants.BROWSER_CHROME_URL, "_blank", features, // window.arguments url, extraOptions, null, null, null, null, null, null, aTriggeringPrincipal, null, aPolicyContainer, aOpenWindowInfo ); // At this point, the new browser window is just starting to load, and // hasn't created the content that we should return. // If the caller of this function is originating in C++, they can pass a // callback in nsOpenWindowInfo and it will be invoked when the browsing // context for a newly opened window is ready. browsingContext = null; } catch (ex) { console.error(ex); } break; } case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB: case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_BACKGROUND: case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_FOREGROUND: case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_AFTER_CURRENT: { // If we have an opener, that means that the caller is expecting access // to the nsIDOMWindow of the opened tab right away. For e10s windows, // this means forcing the newly opened browser to be non-remote so that // we can hand back the nsIDOMWindow. DocumentLoadListener will do the // job of shuttling off the newly opened browser to run in the right // process once it starts loading a URI. let forceNotRemote = aOpenWindowInfo && !aOpenWindowInfo.isRemote; let userContextId = aOpenWindowInfo ? aOpenWindowInfo.originAttributes.userContextId : openingUserContextId; let browser = this.#openURIInNewTab( aURI, referrerInfo, isPrivate, isExternal, forceNotRemote, userContextId, aOpenWindowInfo, aOpenWindowInfo?.parent?.top.embedderElement, aTriggeringPrincipal, "", aPolicyContainer, aSkipLoad, aWhere ); if (browser) { browsingContext = browser.browsingContext; } break; } case Ci.nsIBrowserDOMWindow.OPEN_PRINT_BROWSER: { let browser = this.win.PrintUtils.handleStaticCloneCreatedForPrint(aOpenWindowInfo); if (browser) { browsingContext = browser.browsingContext; } break; } default: // OPEN_CURRENTWINDOW or an illegal value browsingContext = this.win.gBrowser.selectedBrowser.browsingContext; if (aURI) { let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE; if (isExternal) { loadFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL; } else if (!aTriggeringPrincipal.isSystemPrincipal) { // XXX this code must be reviewed and changed when bug 1616353 // lands. loadFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIRST_LOAD; } // This should ideally be able to call loadURI with the actual URI. // However, that would bypass some styles of fixup (notably Windows // paths passed as "URI"s), so this needs some further thought. It // should be addressed in bug 1815509. this.win.gBrowser.fixupAndLoadURIString(aURI.spec, { triggeringPrincipal: aTriggeringPrincipal, policyContainer: aPolicyContainer, loadFlags, referrerInfo, }); } if (!lazy.loadDivertedInBackground) { this.win.focus(); } } return browsingContext; } /** * @type {nsIBrowserDOMWindow["createContentWindowInFrame"]} */ createContentWindowInFrame(aURI, aParams, aWhere, aFlags, aName) { // Passing a null-URI to only create the content window, // and pass true for aSkipLoad to prevent loading of // about:blank return this.#getContentWindowOrOpenURIInFrame( null, aParams, aWhere, aFlags, aName, true ); } /** * @type {nsIBrowserDOMWindow["openURIInFrame"]} */ openURIInFrame(aURI, aParams, aWhere, aFlags, aName) { return this.#getContentWindowOrOpenURIInFrame( aURI, aParams, aWhere, aFlags, aName, false ); } /** * @param {nsIURI} aURI * @param {nsIOpenURIInFrameParams} aParams * @param {i16} aWhere * @param {i32} aFlags * @param {string} aName * @param {boolean} aSkipLoad * @returns {Element} */ #getContentWindowOrOpenURIInFrame( aURI, aParams, aWhere, aFlags, aName, aSkipLoad ) { if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_PRINT_BROWSER) { return this.win.PrintUtils.handleStaticCloneCreatedForPrint( aParams.openWindowInfo ); } if ( aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB && aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_BACKGROUND && aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_FOREGROUND ) { dump("Error: openURIInFrame can only open in new tabs or print"); return null; } var isExternal = !!(aFlags & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL); var userContextId = aParams.openerOriginAttributes && "userContextId" in aParams.openerOriginAttributes ? aParams.openerOriginAttributes.userContextId : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID; return this.#openURIInNewTab( aURI, aParams.referrerInfo, aParams.isPrivate, isExternal, false, userContextId, aParams.openWindowInfo, aParams.openerBrowser, aParams.triggeringPrincipal, aName, aParams.policyContainer, aSkipLoad, aWhere ); } /** * @type {nsIBrowserDOMWindow["canClose"]} */ canClose() { return this.win.CanCloseWindow(); } /** * @type {nsIBrowserDOMWindow["tabCount"]} */ get tabCount() { return this.win.gBrowser.tabs.length; } } BrowserDOMWindow.prototype.QueryInterface = ChromeUtils.generateQI([ "nsIBrowserDOMWindow", ]);