Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
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: {
// 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 <browser> 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) {
// 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
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",
]);