/* -*- 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, { 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. */ export class BrowserDOMWindow { /** * @type {Window} */ win = null; constructor(win) { this.win = win; } static setupInWindow(win) { win.browserDOMWindow = new BrowserDOMWindow(win); } static teardownInWindow(win) { win.browserDOMWindow = null; } #openURIInNewTab( aURI, aReferrerInfo, aIsPrivate, aIsExternal, aForceNotRemote = false, aUserContextId = Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID, aOpenWindowInfo = null, aOpenerBrowser = null, aTriggeringPrincipal = null, aName = "", aCsp = null, aSkipLoad = false, aWhere = undefined ) { let win, needToFocusWin; // try the current window. if we're in a popup, fall back on the most recent browser window if (this.win.toolbar.visible) { 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; } let tab = win.gBrowser.addTab(aURI ? aURI.spec : "about:blank", { triggeringPrincipal: aTriggeringPrincipal, referrerInfo: aReferrerInfo, userContextId: aUserContextId, fromExternal: aIsExternal, inBackground: loadInBackground, forceNotRemote: aForceNotRemote, openWindowInfo: aOpenWindowInfo, openerBrowser: aOpenerBrowser, name: aName, csp: aCsp, skipLoad: aSkipLoad, }); let browser = win.gBrowser.getBrowserForTab(tab); if (needToFocusWin || (!loadInBackground && aIsExternal)) { win.focus(); } return browser; } createContentWindow( aURI, aOpenWindowInfo, aWhere, aFlags, aTriggeringPrincipal, aCsp ) { return this.getContentWindowOrOpenURI( null, aOpenWindowInfo, aWhere, aFlags, aTriggeringPrincipal, aCsp, true ); } openURI(aURI, aOpenWindowInfo, aWhere, aFlags, aTriggeringPrincipal, aCsp) { 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, aCsp, false ); } getContentWindowOrOpenURI( aURI, aOpenWindowInfo, aWhere, aFlags, aTriggeringPrincipal, aCsp, 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 (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) { if ( isExternal && Services.prefs.prefHasUserValue( "browser.link.open_newwindow.override.external" ) ) { aWhere = Services.prefs.getIntPref( "browser.link.open_newwindow.override.external" ); } 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, aCsp, 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: { // 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, "", aCsp, 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, csp: aCsp, loadFlags, referrerInfo, }); } if (!lazy.loadDivertedInBackground) { this.win.focus(); } } return browsingContext; } 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 ); } openURIInFrame(aURI, aParams, aWhere, aFlags, aName) { return this.getContentWindowOrOpenURIInFrame( aURI, aParams, aWhere, aFlags, aName, false ); } 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.csp, aSkipLoad, aWhere ); } canClose() { return this.win.CanCloseWindow(); } get tabCount() { return this.win.gBrowser.tabs.length; } } BrowserDOMWindow.prototype.QueryInterface = ChromeUtils.generateQI([ "nsIBrowserDOMWindow", ]);