summaryrefslogtreecommitdiffstats
path: root/browser/base/content/utilityOverlay.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/base/content/utilityOverlay.js1175
1 files changed, 1175 insertions, 0 deletions
diff --git a/browser/base/content/utilityOverlay.js b/browser/base/content/utilityOverlay.js
new file mode 100644
index 0000000000..c6c4c86a44
--- /dev/null
+++ b/browser/base/content/utilityOverlay.js
@@ -0,0 +1,1175 @@
+/* -*- 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/. */
+
+// Services = object with smart getters for common XPCOM services
+var { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
+ ContextualIdentityService:
+ "resource://gre/modules/ContextualIdentityService.sys.mjs",
+ PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
+});
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ AboutNewTab: "resource:///modules/AboutNewTab.jsm",
+ BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
+ ExtensionSettingsStore: "resource://gre/modules/ExtensionSettingsStore.jsm",
+ ShellService: "resource:///modules/ShellService.jsm",
+});
+
+XPCOMUtils.defineLazyGetter(this, "ReferrerInfo", () =>
+ Components.Constructor(
+ "@mozilla.org/referrer-info;1",
+ "nsIReferrerInfo",
+ "init"
+ )
+);
+
+Object.defineProperty(this, "BROWSER_NEW_TAB_URL", {
+ enumerable: true,
+ get() {
+ if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+ if (
+ !PrivateBrowsingUtils.permanentPrivateBrowsing &&
+ !AboutNewTab.newTabURLOverridden
+ ) {
+ return "about:privatebrowsing";
+ }
+ // If an extension controls the setting and does not have private
+ // browsing permission, use the default setting.
+ let extensionControlled = Services.prefs.getBoolPref(
+ "browser.newtab.extensionControlled",
+ false
+ );
+ let privateAllowed = Services.prefs.getBoolPref(
+ "browser.newtab.privateAllowed",
+ false
+ );
+ // There is a potential on upgrade that the prefs are not set yet, so we double check
+ // for moz-extension.
+ if (
+ !privateAllowed &&
+ (extensionControlled ||
+ AboutNewTab.newTabURL.startsWith("moz-extension://"))
+ ) {
+ return "about:privatebrowsing";
+ }
+ }
+ return AboutNewTab.newTabURL;
+ },
+});
+
+var TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab";
+
+var gBidiUI = false;
+
+/**
+ * Determines whether the given url is considered a special URL for new tabs.
+ */
+function isBlankPageURL(aURL) {
+ return (
+ aURL == "about:blank" ||
+ aURL == "about:home" ||
+ aURL == "about:welcome" ||
+ aURL == BROWSER_NEW_TAB_URL ||
+ aURL == "chrome://browser/content/blanktab.html"
+ );
+}
+
+function getTopWin({ skipPopups, forceNonPrivate } = {}) {
+ // If this is called in a browser window, use that window regardless of
+ // whether it's the frontmost window, since commands can be executed in
+ // background windows (bug 626148).
+ if (
+ top.document.documentElement.getAttribute("windowtype") ==
+ "navigator:browser" &&
+ (!skipPopups || top.toolbar.visible) &&
+ (!forceNonPrivate || !PrivateBrowsingUtils.isWindowPrivate(top))
+ ) {
+ return top;
+ }
+
+ return BrowserWindowTracker.getTopWindow({
+ private: !forceNonPrivate && PrivateBrowsingUtils.isWindowPrivate(window),
+ allowPopups: !skipPopups,
+ });
+}
+
+function doGetProtocolFlags(aURI) {
+ return Services.io.getDynamicProtocolFlags(aURI);
+}
+
+/**
+ * openUILink handles clicks on UI elements that cause URLs to load.
+ *
+ * @param {string} url
+ * @param {Event | Object} event Event or JSON object representing an Event
+ * @param {Boolean | Object} aIgnoreButton
+ * Boolean or object with the same properties as
+ * accepted by openUILinkIn, plus "ignoreButton"
+ * and "ignoreAlt".
+ * @param {Boolean} aIgnoreAlt
+ * @param {Boolean} aAllowThirdPartyFixup
+ * @param {Object} aPostData
+ * @param {Object} aReferrerInfo
+ */
+function openUILink(
+ url,
+ event,
+ aIgnoreButton,
+ aIgnoreAlt,
+ aAllowThirdPartyFixup,
+ aPostData,
+ aReferrerInfo
+) {
+ event = getRootEvent(event);
+ let params;
+
+ if (aIgnoreButton && typeof aIgnoreButton == "object") {
+ params = aIgnoreButton;
+
+ // don't forward "ignoreButton" and "ignoreAlt" to openUILinkIn
+ aIgnoreButton = params.ignoreButton;
+ aIgnoreAlt = params.ignoreAlt;
+ delete params.ignoreButton;
+ delete params.ignoreAlt;
+ } else {
+ params = {
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ postData: aPostData,
+ referrerInfo: aReferrerInfo,
+ initiatingDoc: event ? event.target.ownerDocument : null,
+ };
+ }
+
+ if (!params.triggeringPrincipal) {
+ throw new Error(
+ "Required argument triggeringPrincipal missing within openUILink"
+ );
+ }
+
+ let where = whereToOpenLink(event, aIgnoreButton, aIgnoreAlt);
+ openUILinkIn(url, where, params);
+}
+
+// This is here for historical reasons. bug 1742889 covers cleaning this up.
+function getRootEvent(aEvent) {
+ return BrowserUtils.getRootEvent(aEvent);
+}
+
+// This is here for historical reasons. bug 1742889 covers cleaning this up.
+function whereToOpenLink(e, ignoreButton, ignoreAlt) {
+ return BrowserUtils.whereToOpenLink(e, ignoreButton, ignoreAlt);
+}
+
+/* openTrustedLinkIn will attempt to open the given URI using the SystemPrincipal
+ * as the trigeringPrincipal, unless a more specific Principal is provided.
+ *
+ * See openUILinkIn for a discussion of parameters
+ */
+function openTrustedLinkIn(url, where, aParams) {
+ var params = aParams;
+
+ if (!params) {
+ params = {};
+ }
+
+ if (!params.triggeringPrincipal) {
+ params.triggeringPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+ }
+
+ openUILinkIn(url, where, params);
+}
+
+/* openWebLinkIn will attempt to open the given URI using the NullPrincipal
+ * as the triggeringPrincipal, unless a more specific Principal is provided.
+ *
+ * See openUILinkIn for a discussion of parameters
+ */
+function openWebLinkIn(url, where, params) {
+ if (!params) {
+ params = {};
+ }
+
+ if (!params.triggeringPrincipal) {
+ params.triggeringPrincipal = Services.scriptSecurityManager.createNullPrincipal(
+ {}
+ );
+ }
+ if (params.triggeringPrincipal.isSystemPrincipal) {
+ throw new Error(
+ "System principal should never be passed into openWebLinkIn()"
+ );
+ }
+
+ openUILinkIn(url, where, params);
+}
+
+/* openUILinkIn opens a URL in a place specified by the parameter |where|.
+ *
+ * |where| can be:
+ * "current" current tab (if there aren't any browser windows, then in a new window instead)
+ * "tab" new tab (if there aren't any browser windows, then in a new window instead)
+ * "tabshifted" same as "tab" but in background if default is to select new tabs, and vice versa
+ * "window" new window
+ * "save" save to disk (with no filename hint!)
+ *
+ * DEPRECATION WARNING:
+ * USE -> openTrustedLinkIn(url, where, aParams) if the source is always
+ * a user event on a user- or product-specified URL (as
+ * opposed to URLs provided by a webpage)
+ * USE -> openWebLinkIn(url, where, aParams) if the URI should be loaded
+ * with a specific triggeringPrincipal, for instance, if
+ * the url was supplied by web content.
+ * DEPRECATED -> openUILinkIn(url, where, AllowThirdPartyFixup, aPostData, ...)
+ *
+ *
+ * allowThirdPartyFixup controls whether third party services such as Google's
+ * I Feel Lucky are allowed to interpret this URL. This parameter may be
+ * undefined, which is treated as false.
+ *
+ * Instead of aAllowThirdPartyFixup, you may also pass an object with any of
+ * these properties:
+ * allowThirdPartyFixup (boolean)
+ * fromChrome (boolean)
+ * postData (nsIInputStream)
+ * referrerInfo (nsIReferrerInfo)
+ * relatedToCurrent (boolean)
+ * skipTabAnimation (boolean)
+ * allowPinnedTabHostChange (boolean)
+ * allowPopups (boolean)
+ * userContextId (unsigned int)
+ * targetBrowser (XUL browser)
+ */
+function openUILinkIn(
+ url,
+ where,
+ aAllowThirdPartyFixup,
+ aPostData,
+ aReferrerInfo
+) {
+ var params;
+
+ if (arguments.length == 3 && typeof arguments[2] == "object") {
+ params = aAllowThirdPartyFixup;
+ }
+ if (!params || !params.triggeringPrincipal) {
+ throw new Error(
+ "Required argument triggeringPrincipal missing within openUILinkIn"
+ );
+ }
+
+ params.fromChrome = params.fromChrome ?? true;
+
+ openLinkIn(url, where, params);
+}
+
+/* eslint-disable complexity */
+function openLinkIn(url, where, params) {
+ if (!where || !url) {
+ return;
+ }
+
+ var aFromChrome = params.fromChrome;
+ var aAllowThirdPartyFixup = params.allowThirdPartyFixup;
+ var aPostData = params.postData;
+ var aCharset = params.charset;
+ var aReferrerInfo = params.referrerInfo
+ ? params.referrerInfo
+ : new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, null);
+ var aRelatedToCurrent = params.relatedToCurrent;
+ var aAllowInheritPrincipal = !!params.allowInheritPrincipal;
+ var aForceAllowDataURI = params.forceAllowDataURI;
+ var aInBackground = params.inBackground;
+ var aInitiatingDoc = params.initiatingDoc;
+ var aIsPrivate = params.private;
+ var aForceNonPrivate = params.forceNonPrivate;
+ var aSkipTabAnimation = params.skipTabAnimation;
+ var aAllowPinnedTabHostChange = !!params.allowPinnedTabHostChange;
+ var aAllowPopups = !!params.allowPopups;
+ var aUserContextId = params.userContextId;
+ var aIndicateErrorPageLoad = params.indicateErrorPageLoad;
+ var aPrincipal = params.originPrincipal;
+ var aStoragePrincipal = params.originStoragePrincipal;
+ var aTriggeringPrincipal = params.triggeringPrincipal;
+ var aTriggeringRemoteType = params.triggeringRemoteType;
+ var aCsp = params.csp;
+ var aForceAboutBlankViewerInCurrent = params.forceAboutBlankViewerInCurrent;
+ var aResolveOnNewTabCreated = params.resolveOnNewTabCreated;
+ // This callback will be called with the content browser once it's created.
+ var aResolveOnContentBrowserReady = params.resolveOnContentBrowserCreated;
+ var aGlobalHistoryOptions = params.globalHistoryOptions;
+
+ if (!aTriggeringPrincipal) {
+ throw new Error("Must load with a triggering Principal");
+ }
+
+ if (where == "save") {
+ if ("isContentWindowPrivate" in params) {
+ saveURL(
+ url,
+ null,
+ null,
+ null,
+ true,
+ true,
+ aReferrerInfo,
+ null,
+ null,
+ params.isContentWindowPrivate,
+ aPrincipal
+ );
+ } else {
+ if (!aInitiatingDoc) {
+ console.error(
+ "openUILink/openLinkIn was called with " +
+ "where == 'save' but without initiatingDoc. See bug 814264."
+ );
+ return;
+ }
+ saveURL(
+ url,
+ null,
+ null,
+ null,
+ true,
+ true,
+ aReferrerInfo,
+ null,
+ aInitiatingDoc
+ );
+ }
+ return;
+ }
+
+ // Establish which window we'll load the link in.
+ let w;
+ if (where == "current" && params.targetBrowser) {
+ w = params.targetBrowser.ownerGlobal;
+ } else {
+ w = getTopWin({ forceNonPrivate: aForceNonPrivate });
+ }
+ // We don't want to open tabs in popups, so try to find a non-popup window in
+ // that case.
+ if ((where == "tab" || where == "tabshifted") && w && !w.toolbar.visible) {
+ w = getTopWin({ skipPopups: true, forceNonPrivate: aForceNonPrivate });
+ aRelatedToCurrent = false;
+ }
+
+ // Teach the principal about the right OA to use, e.g. in case when
+ // opening a link in a new private window, or in a new container tab.
+ // Please note we do not have to do that for SystemPrincipals and we
+ // can not do it for NullPrincipals since NullPrincipals are only
+ // identical if they actually are the same object (See Bug: 1346759)
+ function useOAForPrincipal(principal) {
+ if (principal && principal.isContentPrincipal) {
+ let attrs = {
+ userContextId: aUserContextId,
+ privateBrowsingId:
+ aIsPrivate || (w && PrivateBrowsingUtils.isWindowPrivate(w)),
+ firstPartyDomain: principal.originAttributes.firstPartyDomain,
+ };
+ return Services.scriptSecurityManager.principalWithOA(principal, attrs);
+ }
+ return principal;
+ }
+ aPrincipal = useOAForPrincipal(aPrincipal);
+ aStoragePrincipal = useOAForPrincipal(aStoragePrincipal);
+ aTriggeringPrincipal = useOAForPrincipal(aTriggeringPrincipal);
+
+ if (!w || where == "window") {
+ let features = "chrome,dialog=no,all";
+ if (aIsPrivate) {
+ features += ",private";
+ // To prevent regular browsing data from leaking to private browsing sites,
+ // strip the referrer when opening a new private window. (See Bug: 1409226)
+ aReferrerInfo = new ReferrerInfo(
+ aReferrerInfo.referrerPolicy,
+ false,
+ aReferrerInfo.originalReferrer
+ );
+ } else if (aForceNonPrivate) {
+ features += ",non-private";
+ }
+
+ // This propagates to window.arguments.
+ var sa = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+
+ var wuri = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ wuri.data = url;
+
+ let extraOptions = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
+ Ci.nsIWritablePropertyBag2
+ );
+ if (aTriggeringRemoteType) {
+ extraOptions.setPropertyAsACString(
+ "triggeringRemoteType",
+ aTriggeringRemoteType
+ );
+ }
+ if (params.hasValidUserGestureActivation !== undefined) {
+ extraOptions.setPropertyAsBool(
+ "hasValidUserGestureActivation",
+ params.hasValidUserGestureActivation
+ );
+ }
+ if (aForceAllowDataURI) {
+ extraOptions.setPropertyAsBool("forceAllowDataURI", true);
+ }
+ if (params.fromExternal !== undefined) {
+ extraOptions.setPropertyAsBool("fromExternal", params.fromExternal);
+ }
+ if (aGlobalHistoryOptions?.triggeringSponsoredURL) {
+ extraOptions.setPropertyAsACString(
+ "triggeringSponsoredURL",
+ aGlobalHistoryOptions.triggeringSponsoredURL
+ );
+ if (aGlobalHistoryOptions.triggeringSponsoredURLVisitTimeMS) {
+ extraOptions.setPropertyAsUint64(
+ "triggeringSponsoredURLVisitTimeMS",
+ aGlobalHistoryOptions.triggeringSponsoredURLVisitTimeMS
+ );
+ }
+ }
+
+ var allowThirdPartyFixupSupports = Cc[
+ "@mozilla.org/supports-PRBool;1"
+ ].createInstance(Ci.nsISupportsPRBool);
+ allowThirdPartyFixupSupports.data = aAllowThirdPartyFixup;
+
+ var userContextIdSupports = Cc[
+ "@mozilla.org/supports-PRUint32;1"
+ ].createInstance(Ci.nsISupportsPRUint32);
+ userContextIdSupports.data = aUserContextId;
+
+ sa.appendElement(wuri);
+ sa.appendElement(extraOptions);
+ sa.appendElement(aReferrerInfo);
+ sa.appendElement(aPostData);
+ sa.appendElement(allowThirdPartyFixupSupports);
+ sa.appendElement(userContextIdSupports);
+ sa.appendElement(aPrincipal);
+ sa.appendElement(aStoragePrincipal);
+ sa.appendElement(aTriggeringPrincipal);
+ sa.appendElement(null); // allowInheritPrincipal
+ sa.appendElement(aCsp);
+
+ const sourceWindow = w || window;
+ let win;
+
+ // Returns a promise that will be resolved when the new window's startup is finished.
+ function waitForWindowStartup() {
+ return new Promise(resolve => {
+ const delayedStartupObserver = aSubject => {
+ if (aSubject == win) {
+ Services.obs.removeObserver(
+ delayedStartupObserver,
+ "browser-delayed-startup-finished"
+ );
+ resolve();
+ }
+ };
+ Services.obs.addObserver(
+ delayedStartupObserver,
+ "browser-delayed-startup-finished"
+ );
+ });
+ }
+
+ if (params.frameID != undefined && sourceWindow) {
+ // Only notify it as a WebExtensions' webNavigation.onCreatedNavigationTarget
+ // event if it contains the expected frameID params.
+ // (e.g. we should not notify it as a onCreatedNavigationTarget if the user is
+ // opening a new window using the keyboard shortcut).
+ const sourceTabBrowser = sourceWindow.gBrowser.selectedBrowser;
+ waitForWindowStartup().then(() => {
+ Services.obs.notifyObservers(
+ {
+ wrappedJSObject: {
+ url,
+ createdTabBrowser: win.gBrowser.selectedBrowser,
+ sourceTabBrowser,
+ sourceFrameID: params.frameID,
+ },
+ },
+ "webNavigation-createdNavigationTarget"
+ );
+ });
+ }
+
+ if (aResolveOnContentBrowserReady) {
+ waitForWindowStartup().then(() =>
+ aResolveOnContentBrowserReady(win.gBrowser.selectedBrowser)
+ );
+ }
+
+ win = Services.ww.openWindow(
+ sourceWindow,
+ AppConstants.BROWSER_CHROME_URL,
+ null,
+ features,
+ sa
+ );
+
+ return;
+ }
+
+ // We're now committed to loading the link in an existing browser window.
+
+ // Raise the target window before loading the URI, since loading it may
+ // result in a new frontmost window (e.g. "javascript:window.open('');").
+ w.focus();
+
+ let targetBrowser;
+ let loadInBackground;
+ let uriObj;
+
+ if (where == "current") {
+ targetBrowser = params.targetBrowser || w.gBrowser.selectedBrowser;
+ loadInBackground = false;
+ try {
+ uriObj = Services.io.newURI(url);
+ } catch (e) {}
+
+ // In certain tabs, we restrict what if anything may replace the loaded
+ // page. If a load request bounces off for the currently selected tab,
+ // we'll open a new tab instead.
+ let tab = w.gBrowser.getTabForBrowser(targetBrowser);
+ if (tab == w.FirefoxViewHandler.tab) {
+ where = "tab";
+ targetBrowser = null;
+ } else if (
+ !aAllowPinnedTabHostChange &&
+ tab.pinned &&
+ url != "about:crashcontent"
+ ) {
+ try {
+ // nsIURI.host can throw for non-nsStandardURL nsIURIs.
+ if (
+ !uriObj ||
+ (!uriObj.schemeIs("javascript") &&
+ targetBrowser.currentURI.host != uriObj.host)
+ ) {
+ where = "tab";
+ targetBrowser = null;
+ }
+ } catch (err) {
+ where = "tab";
+ targetBrowser = null;
+ }
+ }
+ } else {
+ // `where` is "tab" or "tabshifted", so we'll load the link in a new tab.
+ loadInBackground = aInBackground;
+ if (loadInBackground == null) {
+ loadInBackground = aFromChrome
+ ? false
+ : Services.prefs.getBoolPref("browser.tabs.loadInBackground");
+ }
+ }
+
+ let focusUrlBar = false;
+
+ switch (where) {
+ case "current":
+ let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+
+ if (aAllowThirdPartyFixup) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
+ }
+ // LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL isn't supported for javascript URIs,
+ // i.e. it causes them not to load at all. Callers should strip
+ // "javascript:" from pasted strings to prevent blank tabs
+ if (!aAllowInheritPrincipal) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
+ }
+
+ if (aAllowPopups) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_POPUPS;
+ }
+ if (aIndicateErrorPageLoad) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ERROR_LOAD_CHANGES_RV;
+ }
+ if (aForceAllowDataURI) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FORCE_ALLOW_DATA_URI;
+ }
+ if (params.fromExternal) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
+ }
+
+ let { URI_INHERITS_SECURITY_CONTEXT } = Ci.nsIProtocolHandler;
+ if (
+ aForceAboutBlankViewerInCurrent &&
+ (!uriObj || doGetProtocolFlags(uriObj) & URI_INHERITS_SECURITY_CONTEXT)
+ ) {
+ // Unless we know for sure we're not inheriting principals,
+ // force the about:blank viewer to have the right principal:
+ targetBrowser.createAboutBlankContentViewer(
+ aPrincipal,
+ aStoragePrincipal
+ );
+ }
+
+ targetBrowser.loadURI(url, {
+ triggeringPrincipal: aTriggeringPrincipal,
+ csp: aCsp,
+ flags,
+ referrerInfo: aReferrerInfo,
+ postData: aPostData,
+ userContextId: aUserContextId,
+ hasValidUserGestureActivation: params.hasValidUserGestureActivation,
+ globalHistoryOptions: aGlobalHistoryOptions,
+ triggeringRemoteType: aTriggeringRemoteType,
+ });
+ if (aResolveOnContentBrowserReady) {
+ aResolveOnContentBrowserReady(targetBrowser);
+ }
+
+ // Don't focus the content area if focus is in the address bar and we're
+ // loading the New Tab page.
+ focusUrlBar =
+ w.document.activeElement == w.gURLBar.inputField &&
+ w.isBlankPageURL(url);
+ break;
+ case "tabshifted":
+ loadInBackground = !loadInBackground;
+ // fall through
+ case "tab":
+ focusUrlBar =
+ !loadInBackground &&
+ w.isBlankPageURL(url) &&
+ !AboutNewTab.willNotifyUser;
+
+ let tabUsedForLoad = w.gBrowser.addTab(url, {
+ referrerInfo: aReferrerInfo,
+ charset: aCharset,
+ postData: aPostData,
+ inBackground: loadInBackground,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ relatedToCurrent: aRelatedToCurrent,
+ skipAnimation: aSkipTabAnimation,
+ userContextId: aUserContextId,
+ originPrincipal: aPrincipal,
+ originStoragePrincipal: aStoragePrincipal,
+ triggeringPrincipal: aTriggeringPrincipal,
+ allowInheritPrincipal: aAllowInheritPrincipal,
+ triggeringRemoteType: aTriggeringRemoteType,
+ csp: aCsp,
+ forceAllowDataURI: aForceAllowDataURI,
+ focusUrlBar,
+ openerBrowser: params.openerBrowser,
+ fromExternal: params.fromExternal,
+ globalHistoryOptions: aGlobalHistoryOptions,
+ });
+ targetBrowser = tabUsedForLoad.linkedBrowser;
+
+ if (aResolveOnNewTabCreated) {
+ aResolveOnNewTabCreated(targetBrowser);
+ }
+ if (aResolveOnContentBrowserReady) {
+ aResolveOnContentBrowserReady(targetBrowser);
+ }
+
+ if (params.frameID != undefined && w) {
+ // Only notify it as a WebExtensions' webNavigation.onCreatedNavigationTarget
+ // event if it contains the expected frameID params.
+ // (e.g. we should not notify it as a onCreatedNavigationTarget if the user is
+ // opening a new tab using the keyboard shortcut).
+ Services.obs.notifyObservers(
+ {
+ wrappedJSObject: {
+ url,
+ createdTabBrowser: targetBrowser,
+ sourceTabBrowser: w.gBrowser.selectedBrowser,
+ sourceFrameID: params.frameID,
+ },
+ },
+ "webNavigation-createdNavigationTarget"
+ );
+ }
+ break;
+ }
+
+ if (
+ !params.avoidBrowserFocus &&
+ !focusUrlBar &&
+ targetBrowser == w.gBrowser.selectedBrowser
+ ) {
+ // Focus the content, but only if the browser used for the load is selected.
+ targetBrowser.focus();
+ }
+}
+
+// Used as an onclick handler for UI elements with link-like behavior.
+// e.g. onclick="checkForMiddleClick(this, event);"
+// Not needed for menuitems because those fire command events even on middle clicks.
+function checkForMiddleClick(node, event) {
+ // We should be using the disabled property here instead of the attribute,
+ // but some elements that this function is used with don't support it (e.g.
+ // menuitem).
+ if (node.getAttribute("disabled") == "true") {
+ return;
+ } // Do nothing
+
+ if (event.target.tagName == "menuitem") {
+ // Menu items fire command on middle-click by themselves.
+ return;
+ }
+
+ if (event.button == 1) {
+ /* Execute the node's oncommand or command.
+ */
+
+ let cmdEvent = document.createEvent("xulcommandevent");
+ cmdEvent.initCommandEvent(
+ "command",
+ true,
+ true,
+ window,
+ 0,
+ event.ctrlKey,
+ event.altKey,
+ event.shiftKey,
+ event.metaKey,
+ 0,
+ event,
+ event.mozInputSource
+ );
+ node.dispatchEvent(cmdEvent);
+
+ // Stop the propagation of the click event, to prevent the event from being
+ // handled more than once.
+ // E.g. see https://bugzilla.mozilla.org/show_bug.cgi?id=1657992#c4
+ event.stopPropagation();
+ event.preventDefault();
+
+ // If the middle-click was on part of a menu, close the menu.
+ // (Menus close automatically with left-click but not with middle-click.)
+ closeMenus(event.target);
+ }
+}
+
+// Populate a menu with user-context menu items. This method should be called
+// by onpopupshowing passing the event as first argument.
+function createUserContextMenu(
+ event,
+ {
+ isContextMenu = false,
+ excludeUserContextId = 0,
+ showDefaultTab = false,
+ useAccessKeys = true,
+ } = {}
+) {
+ while (event.target.hasChildNodes()) {
+ event.target.firstChild.remove();
+ }
+
+ let bundle = Services.strings.createBundle(
+ "chrome://browser/locale/browser.properties"
+ );
+ let docfrag = document.createDocumentFragment();
+
+ // If we are excluding a userContextId, we want to add a 'no-container' item.
+ if (excludeUserContextId || showDefaultTab) {
+ let menuitem = document.createXULElement("menuitem");
+ menuitem.setAttribute("data-usercontextid", "0");
+ menuitem.setAttribute(
+ "label",
+ bundle.GetStringFromName("userContextNone.label")
+ );
+ menuitem.setAttribute(
+ "accesskey",
+ bundle.GetStringFromName("userContextNone.accesskey")
+ );
+
+ if (!isContextMenu) {
+ menuitem.setAttribute("command", "Browser:NewUserContextTab");
+ }
+
+ docfrag.appendChild(menuitem);
+
+ let menuseparator = document.createXULElement("menuseparator");
+ docfrag.appendChild(menuseparator);
+ }
+
+ ContextualIdentityService.getPublicIdentities().forEach(identity => {
+ if (identity.userContextId == excludeUserContextId) {
+ return;
+ }
+
+ let menuitem = document.createXULElement("menuitem");
+ menuitem.setAttribute("data-usercontextid", identity.userContextId);
+ menuitem.setAttribute(
+ "label",
+ ContextualIdentityService.getUserContextLabel(identity.userContextId)
+ );
+
+ if (identity.accessKey && useAccessKeys) {
+ menuitem.setAttribute(
+ "accesskey",
+ bundle.GetStringFromName(identity.accessKey)
+ );
+ }
+
+ menuitem.classList.add("menuitem-iconic");
+ menuitem.classList.add("identity-color-" + identity.color);
+
+ if (!isContextMenu) {
+ menuitem.setAttribute("command", "Browser:NewUserContextTab");
+ }
+
+ menuitem.classList.add("identity-icon-" + identity.icon);
+
+ docfrag.appendChild(menuitem);
+ });
+
+ if (!isContextMenu) {
+ docfrag.appendChild(document.createXULElement("menuseparator"));
+
+ let menuitem = document.createXULElement("menuitem");
+ menuitem.setAttribute(
+ "label",
+ bundle.GetStringFromName("userContext.aboutPage.label")
+ );
+ if (useAccessKeys) {
+ menuitem.setAttribute(
+ "accesskey",
+ bundle.GetStringFromName("userContext.aboutPage.accesskey")
+ );
+ }
+ menuitem.setAttribute("command", "Browser:OpenAboutContainers");
+ docfrag.appendChild(menuitem);
+ }
+
+ event.target.appendChild(docfrag);
+ return true;
+}
+
+// Closes all popups that are ancestors of the node.
+function closeMenus(node) {
+ if ("tagName" in node) {
+ if (
+ node.namespaceURI ==
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" &&
+ (node.tagName == "menupopup" || node.tagName == "popup")
+ ) {
+ node.hidePopup();
+ }
+
+ closeMenus(node.parentNode);
+ }
+}
+
+/** This function takes in a key element and compares it to the keys pressed during an event.
+ *
+ * @param aEvent
+ * The KeyboardEvent event you want to compare against your key.
+ *
+ * @param aKey
+ * The <key> element checked to see if it was called in aEvent.
+ * For example, aKey can be a variable set to document.getElementById("key_close")
+ * to check if the close command key was pressed in aEvent.
+ */
+function eventMatchesKey(aEvent, aKey) {
+ let keyPressed = aKey.getAttribute("key").toLowerCase();
+ let keyModifiers = aKey.getAttribute("modifiers");
+ let modifiers = ["Alt", "Control", "Meta", "Shift"];
+
+ if (aEvent.key != keyPressed) {
+ return false;
+ }
+ let eventModifiers = modifiers.filter(modifier =>
+ aEvent.getModifierState(modifier)
+ );
+ // Check if aEvent has a modifier and aKey doesn't
+ if (eventModifiers.length && !keyModifiers.length) {
+ return false;
+ }
+ // Check whether aKey's modifiers match aEvent's modifiers
+ if (keyModifiers) {
+ keyModifiers = keyModifiers.split(/[\s,]+/);
+ // Capitalize first letter of aKey's modifers to compare to aEvent's modifier
+ keyModifiers.forEach(function(modifier, index) {
+ if (modifier == "accel") {
+ keyModifiers[index] =
+ AppConstants.platform == "macosx" ? "Meta" : "Control";
+ } else {
+ keyModifiers[index] = modifier[0].toUpperCase() + modifier.slice(1);
+ }
+ });
+ return modifiers.every(
+ modifier =>
+ keyModifiers.includes(modifier) == aEvent.getModifierState(modifier)
+ );
+ }
+ return true;
+}
+
+// Gather all descendent text under given document node.
+function gatherTextUnder(root) {
+ var text = "";
+ var node = root.firstChild;
+ var depth = 1;
+ while (node && depth > 0) {
+ // See if this node is text.
+ if (node.nodeType == Node.TEXT_NODE) {
+ // Add this text to our collection.
+ text += " " + node.data;
+ } else if (HTMLImageElement.isInstance(node)) {
+ // If it has an "alt" attribute, add that.
+ var altText = node.getAttribute("alt");
+ if (altText && altText != "") {
+ text += " " + altText;
+ }
+ }
+ // Find next node to test.
+ // First, see if this node has children.
+ if (node.hasChildNodes()) {
+ // Go to first child.
+ node = node.firstChild;
+ depth++;
+ } else {
+ // No children, try next sibling (or parent next sibling).
+ while (depth > 0 && !node.nextSibling) {
+ node = node.parentNode;
+ depth--;
+ }
+ if (node.nextSibling) {
+ node = node.nextSibling;
+ }
+ }
+ }
+ // Strip leading and tailing whitespace.
+ text = text.trim();
+ // Compress remaining whitespace.
+ text = text.replace(/\s+/g, " ");
+ return text;
+}
+
+// This function exists for legacy reasons.
+function getShellService() {
+ return ShellService;
+}
+
+function isBidiEnabled() {
+ // first check the pref.
+ if (Services.prefs.getBoolPref("bidi.browser.ui", false)) {
+ return true;
+ }
+
+ // now see if the app locale is an RTL one.
+ const isRTL = Services.locale.isAppLocaleRTL;
+
+ if (isRTL) {
+ Services.prefs.setBoolPref("bidi.browser.ui", true);
+ }
+ return isRTL;
+}
+
+function openAboutDialog() {
+ for (let win of Services.wm.getEnumerator("Browser:About")) {
+ // Only open one about window (Bug 599573)
+ if (win.closed) {
+ continue;
+ }
+ win.focus();
+ return;
+ }
+
+ var features = "chrome,";
+ if (AppConstants.platform == "win") {
+ features += "centerscreen,dependent";
+ } else if (AppConstants.platform == "macosx") {
+ features += "resizable=no,minimizable=no";
+ } else {
+ features += "centerscreen,dependent,dialog=no";
+ }
+
+ window.openDialog("chrome://browser/content/aboutDialog.xhtml", "", features);
+}
+
+async function openPreferences(paneID, extraArgs) {
+ // This function is duplicated from preferences.js.
+ function internalPrefCategoryNameToFriendlyName(aName) {
+ return (aName || "").replace(/^pane./, function(toReplace) {
+ return toReplace[4].toLowerCase();
+ });
+ }
+
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ let friendlyCategoryName = internalPrefCategoryNameToFriendlyName(paneID);
+ let params;
+ if (extraArgs && extraArgs.urlParams) {
+ params = new URLSearchParams();
+ let urlParams = extraArgs.urlParams;
+ for (let name in urlParams) {
+ if (urlParams[name] !== undefined) {
+ params.set(name, urlParams[name]);
+ }
+ }
+ }
+ let preferencesURL =
+ "about:preferences" +
+ (params ? "?" + params : "") +
+ (friendlyCategoryName ? "#" + friendlyCategoryName : "");
+ let newLoad = true;
+ let browser = null;
+ if (!win) {
+ let windowArguments = Cc["@mozilla.org/array;1"].createInstance(
+ Ci.nsIMutableArray
+ );
+ let supportsStringPrefURL = Cc[
+ "@mozilla.org/supports-string;1"
+ ].createInstance(Ci.nsISupportsString);
+ supportsStringPrefURL.data = preferencesURL;
+ windowArguments.appendElement(supportsStringPrefURL);
+
+ win = Services.ww.openWindow(
+ null,
+ AppConstants.BROWSER_CHROME_URL,
+ "_blank",
+ "chrome,dialog=no,all",
+ windowArguments
+ );
+ } else {
+ let shouldReplaceFragment = friendlyCategoryName
+ ? "whenComparingAndReplace"
+ : "whenComparing";
+ newLoad = !win.switchToTabHavingURI(preferencesURL, true, {
+ ignoreFragment: shouldReplaceFragment,
+ replaceQueryString: true,
+ triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ });
+ browser = win.gBrowser.selectedBrowser;
+ }
+
+ if (!newLoad && paneID) {
+ if (browser.contentDocument?.readyState != "complete") {
+ await new Promise(resolve => {
+ browser.addEventListener("load", resolve, {
+ capture: true,
+ once: true,
+ });
+ });
+ }
+ browser.contentWindow.gotoPref(paneID);
+ }
+}
+
+/**
+ * Opens the troubleshooting information (about:support) page for this version
+ * of the application.
+ */
+function openTroubleshootingPage() {
+ openTrustedLinkIn("about:support", "tab");
+}
+
+/**
+ * Opens the feedback page for this version of the application.
+ */
+function openFeedbackPage() {
+ var url = Services.urlFormatter.formatURLPref("app.feedback.baseURL");
+ openTrustedLinkIn(url, "tab");
+}
+
+function buildHelpMenu() {
+ document.getElementById(
+ "feedbackPage"
+ ).disabled = !Services.policies.isAllowed("feedbackCommands");
+
+ document.getElementById(
+ "helpSafeMode"
+ ).disabled = !Services.policies.isAllowed("safeMode");
+
+ document.getElementById(
+ "troubleShooting"
+ ).disabled = !Services.policies.isAllowed("aboutSupport");
+
+ let supportMenu = Services.policies.getSupportMenu();
+ if (supportMenu) {
+ let menuitem = document.getElementById("helpPolicySupport");
+ menuitem.hidden = false;
+ menuitem.setAttribute("label", supportMenu.Title);
+ if ("AccessKey" in supportMenu) {
+ menuitem.setAttribute("accesskey", supportMenu.AccessKey);
+ }
+ document.getElementById("helpPolicySeparator").hidden = false;
+ }
+
+ // Enable/disable the "Report Web Forgery" menu item.
+ if (typeof gSafeBrowsing != "undefined") {
+ gSafeBrowsing.setReportPhishingMenu();
+ }
+
+ // We're testing to see if the WebCompat team's "Report Site Issue"
+ // access point makes sense in the Help menu. Normally checking this
+ // pref wouldn't be enough, since there's also the case that the
+ // add-on has somehow been disabled by the user or third-party software
+ // without flipping the pref. Since this add-on is only used on pre-release
+ // channels, and since the jury is still out on whether or not the Help menu
+ // is the right place for this item, we're going to do a least-effort
+ // approach here and assume that the pref is enough to determine whether the
+ // menuitem should appear.
+ //
+ // See bug 1690573 for further details.
+ let reportSiteIssueEnabled = Services.prefs.getBoolPref(
+ "extensions.webcompat-reporter.enabled",
+ false
+ );
+ let reportSiteIssue = document.getElementById("help_reportSiteIssue");
+ reportSiteIssue.hidden = !reportSiteIssueEnabled;
+ if (reportSiteIssueEnabled) {
+ let uri = gBrowser.currentURI;
+ let isReportablePage =
+ uri && (uri.schemeIs("http") || uri.schemeIs("https"));
+ reportSiteIssue.disabled = !isReportablePage;
+ }
+}
+
+function isElementVisible(aElement) {
+ if (!aElement) {
+ return false;
+ }
+
+ // If aElement or a direct or indirect parent is hidden or collapsed,
+ // height, width or both will be 0.
+ var rect = aElement.getBoundingClientRect();
+ return rect.height > 0 && rect.width > 0;
+}
+
+function makeURLAbsolute(aBase, aUrl) {
+ // Note: makeURI() will throw if aUri is not a valid URI
+ return makeURI(aUrl, null, makeURI(aBase)).spec;
+}
+
+function getHelpLinkURL(aHelpTopic) {
+ var url = Services.urlFormatter.formatURLPref("app.support.baseURL");
+ return url + aHelpTopic;
+}
+
+// aCalledFromModal is optional
+function openHelpLink(aHelpTopic, aCalledFromModal, aWhere) {
+ var url = getHelpLinkURL(aHelpTopic);
+ var where = aWhere;
+ if (!aWhere) {
+ where = aCalledFromModal ? "window" : "tab";
+ }
+
+ openTrustedLinkIn(url, where);
+}
+
+function openPrefsHelp(aEvent) {
+ let helpTopic = aEvent.target.getAttribute("helpTopic");
+ openHelpLink(helpTopic);
+}