forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			793 lines
		
	
	
	
		
			26 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			793 lines
		
	
	
	
		
			26 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* 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 { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
 | 
						|
import { BrowserUtils } from "resource://gre/modules/BrowserUtils.sys.mjs";
 | 
						|
import { PrivateBrowsingUtils } from "resource://gre/modules/PrivateBrowsingUtils.sys.mjs";
 | 
						|
 | 
						|
const lazy = {};
 | 
						|
 | 
						|
ChromeUtils.defineESModuleGetters(lazy, {
 | 
						|
  AboutNewTab: "resource:///modules/AboutNewTab.sys.mjs",
 | 
						|
  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
 | 
						|
});
 | 
						|
 | 
						|
ChromeUtils.defineLazyGetter(lazy, "ReferrerInfo", () =>
 | 
						|
  Components.Constructor(
 | 
						|
    "@mozilla.org/referrer-info;1",
 | 
						|
    "nsIReferrerInfo",
 | 
						|
    "init"
 | 
						|
  )
 | 
						|
);
 | 
						|
 | 
						|
function saveLink(window, url, params) {
 | 
						|
  if ("isContentWindowPrivate" in params) {
 | 
						|
    window.saveURL(
 | 
						|
      url,
 | 
						|
      null,
 | 
						|
      null,
 | 
						|
      null,
 | 
						|
      true,
 | 
						|
      true,
 | 
						|
      params.referrerInfo,
 | 
						|
      null,
 | 
						|
      null,
 | 
						|
      params.isContentWindowPrivate,
 | 
						|
      params.originPrincipal
 | 
						|
    );
 | 
						|
  } else {
 | 
						|
    if (!params.initiatingDoc) {
 | 
						|
      console.error(
 | 
						|
        "openUILink/openLinkIn was called with " +
 | 
						|
          "where == 'save' but without initiatingDoc.  See bug 814264."
 | 
						|
      );
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    window.saveURL(
 | 
						|
      url,
 | 
						|
      null,
 | 
						|
      null,
 | 
						|
      null,
 | 
						|
      true,
 | 
						|
      true,
 | 
						|
      params.referrerInfo,
 | 
						|
      null,
 | 
						|
      params.initiatingDoc
 | 
						|
    );
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
function openInWindow(url, params, sourceWindow) {
 | 
						|
  let {
 | 
						|
    referrerInfo,
 | 
						|
    forceNonPrivate,
 | 
						|
    triggeringRemoteType,
 | 
						|
    forceAllowDataURI,
 | 
						|
    globalHistoryOptions,
 | 
						|
    allowThirdPartyFixup,
 | 
						|
    userContextId,
 | 
						|
    postData,
 | 
						|
    originPrincipal,
 | 
						|
    originStoragePrincipal,
 | 
						|
    triggeringPrincipal,
 | 
						|
    csp,
 | 
						|
    resolveOnContentBrowserCreated,
 | 
						|
  } = params;
 | 
						|
  let features = "chrome,dialog=no,all";
 | 
						|
  if (params.private) {
 | 
						|
    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)
 | 
						|
    referrerInfo = new lazy.ReferrerInfo(
 | 
						|
      referrerInfo.referrerPolicy,
 | 
						|
      false,
 | 
						|
      referrerInfo.originalReferrer
 | 
						|
    );
 | 
						|
  } else if (forceNonPrivate) {
 | 
						|
    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 (triggeringRemoteType) {
 | 
						|
    extraOptions.setPropertyAsACString(
 | 
						|
      "triggeringRemoteType",
 | 
						|
      triggeringRemoteType
 | 
						|
    );
 | 
						|
  }
 | 
						|
  if (params.hasValidUserGestureActivation !== undefined) {
 | 
						|
    extraOptions.setPropertyAsBool(
 | 
						|
      "hasValidUserGestureActivation",
 | 
						|
      params.hasValidUserGestureActivation
 | 
						|
    );
 | 
						|
  }
 | 
						|
  if (forceAllowDataURI) {
 | 
						|
    extraOptions.setPropertyAsBool("forceAllowDataURI", true);
 | 
						|
  }
 | 
						|
  if (params.fromExternal !== undefined) {
 | 
						|
    extraOptions.setPropertyAsBool("fromExternal", params.fromExternal);
 | 
						|
  }
 | 
						|
  if (globalHistoryOptions?.triggeringSponsoredURL) {
 | 
						|
    extraOptions.setPropertyAsACString(
 | 
						|
      "triggeringSponsoredURL",
 | 
						|
      globalHistoryOptions.triggeringSponsoredURL
 | 
						|
    );
 | 
						|
    if (globalHistoryOptions.triggeringSponsoredURLVisitTimeMS) {
 | 
						|
      extraOptions.setPropertyAsUint64(
 | 
						|
        "triggeringSponsoredURLVisitTimeMS",
 | 
						|
        globalHistoryOptions.triggeringSponsoredURLVisitTimeMS
 | 
						|
      );
 | 
						|
    }
 | 
						|
  }
 | 
						|
  if (params.wasSchemelessInput !== undefined) {
 | 
						|
    extraOptions.setPropertyAsBool(
 | 
						|
      "wasSchemelessInput",
 | 
						|
      params.wasSchemelessInput
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  var allowThirdPartyFixupSupports = Cc[
 | 
						|
    "@mozilla.org/supports-PRBool;1"
 | 
						|
  ].createInstance(Ci.nsISupportsPRBool);
 | 
						|
  allowThirdPartyFixupSupports.data = allowThirdPartyFixup;
 | 
						|
 | 
						|
  var userContextIdSupports = Cc[
 | 
						|
    "@mozilla.org/supports-PRUint32;1"
 | 
						|
  ].createInstance(Ci.nsISupportsPRUint32);
 | 
						|
  userContextIdSupports.data = userContextId;
 | 
						|
 | 
						|
  sa.appendElement(wuri);
 | 
						|
  sa.appendElement(extraOptions);
 | 
						|
  sa.appendElement(referrerInfo);
 | 
						|
  sa.appendElement(postData);
 | 
						|
  sa.appendElement(allowThirdPartyFixupSupports);
 | 
						|
  sa.appendElement(userContextIdSupports);
 | 
						|
  sa.appendElement(originPrincipal);
 | 
						|
  sa.appendElement(originStoragePrincipal);
 | 
						|
  sa.appendElement(triggeringPrincipal);
 | 
						|
  sa.appendElement(null); // allowInheritPrincipal
 | 
						|
  sa.appendElement(csp);
 | 
						|
 | 
						|
  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 (resolveOnContentBrowserCreated) {
 | 
						|
    waitForWindowStartup().then(() =>
 | 
						|
      resolveOnContentBrowserCreated(win.gBrowser.selectedBrowser)
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  win = Services.ww.openWindow(
 | 
						|
    sourceWindow,
 | 
						|
    AppConstants.BROWSER_CHROME_URL,
 | 
						|
    null,
 | 
						|
    features,
 | 
						|
    sa
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
function openInCurrentTab(targetBrowser, url, uriObj, params) {
 | 
						|
  let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
 | 
						|
 | 
						|
  if (params.allowThirdPartyFixup) {
 | 
						|
    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 (!params.allowInheritPrincipal) {
 | 
						|
    flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
 | 
						|
  }
 | 
						|
 | 
						|
  if (params.allowPopups) {
 | 
						|
    flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_POPUPS;
 | 
						|
  }
 | 
						|
  if (params.indicateErrorPageLoad) {
 | 
						|
    flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ERROR_LOAD_CHANGES_RV;
 | 
						|
  }
 | 
						|
  if (params.forceAllowDataURI) {
 | 
						|
    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 (
 | 
						|
    params.forceAboutBlankViewerInCurrent &&
 | 
						|
    (!uriObj ||
 | 
						|
      Services.io.getDynamicProtocolFlags(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.createAboutBlankDocumentViewer(
 | 
						|
      params.originPrincipal,
 | 
						|
      params.originStoragePrincipal
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  let {
 | 
						|
    triggeringPrincipal,
 | 
						|
    csp,
 | 
						|
    referrerInfo,
 | 
						|
    postData,
 | 
						|
    userContextId,
 | 
						|
    hasValidUserGestureActivation,
 | 
						|
    globalHistoryOptions,
 | 
						|
    triggeringRemoteType,
 | 
						|
    wasSchemelessInput,
 | 
						|
  } = params;
 | 
						|
 | 
						|
  targetBrowser.fixupAndLoadURIString(url, {
 | 
						|
    triggeringPrincipal,
 | 
						|
    csp,
 | 
						|
    flags,
 | 
						|
    referrerInfo,
 | 
						|
    postData,
 | 
						|
    userContextId,
 | 
						|
    hasValidUserGestureActivation,
 | 
						|
    globalHistoryOptions,
 | 
						|
    triggeringRemoteType,
 | 
						|
    wasSchemelessInput,
 | 
						|
  });
 | 
						|
  params.resolveOnContentBrowserCreated?.(targetBrowser);
 | 
						|
}
 | 
						|
 | 
						|
function updatePrincipals(window, params) {
 | 
						|
  let { userContextId } = params;
 | 
						|
  // 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 privateBrowsingId =
 | 
						|
        params.private ||
 | 
						|
        (window && PrivateBrowsingUtils.isWindowPrivate(window));
 | 
						|
      let attrs = {
 | 
						|
        userContextId,
 | 
						|
        privateBrowsingId,
 | 
						|
        firstPartyDomain: principal.originAttributes.firstPartyDomain,
 | 
						|
      };
 | 
						|
      return Services.scriptSecurityManager.principalWithOA(principal, attrs);
 | 
						|
    }
 | 
						|
    return principal;
 | 
						|
  }
 | 
						|
  params.originPrincipal = useOAForPrincipal(params.originPrincipal);
 | 
						|
  params.originStoragePrincipal = useOAForPrincipal(
 | 
						|
    params.originStoragePrincipal
 | 
						|
  );
 | 
						|
  params.triggeringPrincipal = useOAForPrincipal(params.triggeringPrincipal);
 | 
						|
}
 | 
						|
 | 
						|
export const URILoadingHelper = {
 | 
						|
  /* openLinkIn opens a URL in a place specified by the parameter |where|.
 | 
						|
   *
 | 
						|
   * The params object is the same as for `openLinkIn` and documented below.
 | 
						|
   *
 | 
						|
   * @param {String}  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!)
 | 
						|
   *
 | 
						|
   * @param {Object}  params
 | 
						|
   *
 | 
						|
   * Options relating to what tab/window to use and how to open it:
 | 
						|
   *
 | 
						|
   * @param {boolean} params.private
 | 
						|
   *                  Load the URL in a private window.
 | 
						|
   * @param {boolean} params.forceNonPrivate
 | 
						|
   *                  Force the load to happen in non-private windows.
 | 
						|
   * @param {boolean} params.relatedToCurrent
 | 
						|
   *                  Whether new tabs should go immediately next to the current tab.
 | 
						|
   * @param {Element} params.targetBrowser
 | 
						|
   *                  The browser to use for the load. Only used if where == "current".
 | 
						|
   * @param {boolean} params.inBackground
 | 
						|
   *                  If explicitly true or false, whether to switch to the tab immediately.
 | 
						|
   *                  If null, will switch to the tab if `forceForeground` was true. If
 | 
						|
   *                  neither is passed, will defer to the user preference browser.tabs.loadInBackground.
 | 
						|
   * @param {boolean} params.forceForeground
 | 
						|
   *                  Ignore the user preference and load in the foreground.
 | 
						|
   * @param {boolean} params.allowPinnedTabHostChange
 | 
						|
   *                  Allow even a pinned tab to change hosts.
 | 
						|
   * @param {boolean} params.allowPopups
 | 
						|
   *                  whether the link is allowed to open in a popup window (ie one with no browser
 | 
						|
   *                  chrome)
 | 
						|
   * @param {boolean} params.skipTabAnimation
 | 
						|
   *                  Skip the tab opening animation.
 | 
						|
   * @param {Element} params.openerBrowser
 | 
						|
   *                  The browser that started the load.
 | 
						|
   * @param {boolean} params.avoidBrowserFocus
 | 
						|
   *                  Don't focus the browser element immediately after starting
 | 
						|
   *                  the load. Used by the URL bar to avoid leaking user input
 | 
						|
   *                  into web content, see bug 1641287.
 | 
						|
   *
 | 
						|
   * Options relating to the load itself:
 | 
						|
   *
 | 
						|
   * @param {boolean} params.allowThirdPartyFixup
 | 
						|
   *                  Allow transforming the 'url' into a search query.
 | 
						|
   * @param {nsIInputStream} params.postData
 | 
						|
   *                  Data to post as part of the request.
 | 
						|
   * @param {nsIReferrerInfo} params.referrerInfo
 | 
						|
   *                  Referrer info for the request.
 | 
						|
   * @param {boolean} params.indicateErrorPageLoad
 | 
						|
   *                  Whether docshell should throw an exception (i.e. return non-NS_OK)
 | 
						|
   *                  if the load fails.
 | 
						|
   * @param {string}  params.charset
 | 
						|
   *                  Character set to use for the load. Only honoured for tabs.
 | 
						|
   *                  Legacy argument - do not use.
 | 
						|
   * @param {string}  params.wasSchemelessInput
 | 
						|
   *                  Whether the search/URL term was without an explicit scheme.
 | 
						|
   *
 | 
						|
   * Options relating to security, whether the load is allowed to happen,
 | 
						|
   * and what cookie container to use for the load:
 | 
						|
   *
 | 
						|
   * @param {boolean} params.forceAllowDataURI
 | 
						|
   *                  Force allow a data URI to load as a toplevel load.
 | 
						|
   * @param {number}  params.userContextId
 | 
						|
   *                  The userContextId (container identifier) to use for the load.
 | 
						|
   * @param {boolean} params.allowInheritPrincipal
 | 
						|
   *                  Allow the load to inherit the triggering principal.
 | 
						|
   * @param {boolean} params.forceAboutBlankViewerInCurrent
 | 
						|
   *                  Force load an about:blank page first. Only used if
 | 
						|
   *                  allowInheritPrincipal is passed or no URL was provided.
 | 
						|
   * @param {nsIPrincipal} params.triggeringPrincipal
 | 
						|
   *                  Triggering principal to pass to docshell for the load.
 | 
						|
   * @param {nsIPrincipal} params.originPrincipal
 | 
						|
   *                  Origin principal to pass to docshell for the load.
 | 
						|
   * @param {nsIPrincipal} params.originStoragePrincipal
 | 
						|
   *                  Storage principal to pass to docshell for the load.
 | 
						|
   * @param {string}  params.triggeringRemoteType
 | 
						|
   *                  The remoteType triggering this load.
 | 
						|
   * @param {nsIContentSecurityPolicy} params.csp
 | 
						|
   *                  The CSP that should apply to the load.
 | 
						|
   * @param {boolean} params.hasValidUserGestureActivation
 | 
						|
   *                  Indicates if a valid user gesture caused this load. This
 | 
						|
   *                  informs e.g. popup blocker decisions.
 | 
						|
   * @param {boolean} params.fromExternal
 | 
						|
   *                  Indicates the load was started outside of the browser,
 | 
						|
   *                  e.g. passed on the commandline or through OS mechanisms.
 | 
						|
   *
 | 
						|
   * Options used to track the load elsewhere
 | 
						|
   *
 | 
						|
   * @param {function} params.resolveOnNewTabCreated
 | 
						|
   *                   This callback will be called when a new tab is created.
 | 
						|
   * @param {function} params.resolveOnContentBrowserCreated
 | 
						|
   *                   This callback will be called with the content browser once it's created.
 | 
						|
   * @param {Object}   params.globalHistoryOptions
 | 
						|
   *                   Used by places to keep track of search related metadata for loads.
 | 
						|
   * @param {Number}   params.frameID
 | 
						|
   *                   Used by webextensions for their loads.
 | 
						|
   *
 | 
						|
   * Options used for where="save" only:
 | 
						|
   *
 | 
						|
   * @param {boolean}  params.isContentWindowPrivate
 | 
						|
   *                   Save content as coming from a private window.
 | 
						|
   * @param {Document} params.initiatingDoc
 | 
						|
   *                   Used to determine where to prompt for a filename.
 | 
						|
   */
 | 
						|
  openLinkIn(window, url, where, params) {
 | 
						|
    if (!where || !url) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let {
 | 
						|
      allowThirdPartyFixup,
 | 
						|
      postData,
 | 
						|
      charset,
 | 
						|
      relatedToCurrent,
 | 
						|
      allowInheritPrincipal,
 | 
						|
      forceAllowDataURI,
 | 
						|
      forceNonPrivate,
 | 
						|
      skipTabAnimation,
 | 
						|
      allowPinnedTabHostChange,
 | 
						|
      userContextId,
 | 
						|
      triggeringPrincipal,
 | 
						|
      originPrincipal,
 | 
						|
      originStoragePrincipal,
 | 
						|
      triggeringRemoteType,
 | 
						|
      csp,
 | 
						|
      resolveOnNewTabCreated,
 | 
						|
      resolveOnContentBrowserCreated,
 | 
						|
      globalHistoryOptions,
 | 
						|
    } = params;
 | 
						|
 | 
						|
    // We want to overwrite some things for convenience when passing it to other
 | 
						|
    // methods. To avoid impacting callers, copy the params.
 | 
						|
    params = Object.assign({}, params);
 | 
						|
 | 
						|
    if (!params.referrerInfo) {
 | 
						|
      params.referrerInfo = new lazy.ReferrerInfo(
 | 
						|
        Ci.nsIReferrerInfo.EMPTY,
 | 
						|
        true,
 | 
						|
        null
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    if (!triggeringPrincipal) {
 | 
						|
      throw new Error("Must load with a triggering Principal");
 | 
						|
    }
 | 
						|
 | 
						|
    if (where == "save") {
 | 
						|
      saveLink(window, url, params);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // Establish which window we'll load the link in.
 | 
						|
    let w;
 | 
						|
    if (where == "current" && params.targetBrowser) {
 | 
						|
      w = params.targetBrowser.ownerGlobal;
 | 
						|
    } else {
 | 
						|
      w = this.getTargetWindow(window, { forceNonPrivate });
 | 
						|
    }
 | 
						|
    // 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 = this.getTargetWindow(window, {
 | 
						|
        skipPopups: true,
 | 
						|
        forceNonPrivate,
 | 
						|
      });
 | 
						|
      relatedToCurrent = false;
 | 
						|
    }
 | 
						|
 | 
						|
    updatePrincipals(w, params);
 | 
						|
 | 
						|
    if (!w || where == "window") {
 | 
						|
      openInWindow(url, params, w || window);
 | 
						|
      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 (
 | 
						|
        !allowPinnedTabHostChange &&
 | 
						|
        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 = params.inBackground;
 | 
						|
      if (loadInBackground == null) {
 | 
						|
        loadInBackground = params.forceForeground
 | 
						|
          ? false
 | 
						|
          : Services.prefs.getBoolPref("browser.tabs.loadInBackground");
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    let focusUrlBar = false;
 | 
						|
 | 
						|
    switch (where) {
 | 
						|
      case "current":
 | 
						|
        openInCurrentTab(targetBrowser, url, uriObj, params);
 | 
						|
 | 
						|
        // 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) &&
 | 
						|
          !lazy.AboutNewTab.willNotifyUser;
 | 
						|
 | 
						|
        let tabUsedForLoad = w.gBrowser.addTab(url, {
 | 
						|
          referrerInfo: params.referrerInfo,
 | 
						|
          charset,
 | 
						|
          postData,
 | 
						|
          inBackground: loadInBackground,
 | 
						|
          allowThirdPartyFixup,
 | 
						|
          relatedToCurrent,
 | 
						|
          skipAnimation: skipTabAnimation,
 | 
						|
          userContextId,
 | 
						|
          originPrincipal,
 | 
						|
          originStoragePrincipal,
 | 
						|
          triggeringPrincipal,
 | 
						|
          allowInheritPrincipal,
 | 
						|
          triggeringRemoteType,
 | 
						|
          csp,
 | 
						|
          forceAllowDataURI,
 | 
						|
          focusUrlBar,
 | 
						|
          openerBrowser: params.openerBrowser,
 | 
						|
          fromExternal: params.fromExternal,
 | 
						|
          globalHistoryOptions,
 | 
						|
          wasSchemelessInput: params.wasSchemelessInput,
 | 
						|
        });
 | 
						|
        targetBrowser = tabUsedForLoad.linkedBrowser;
 | 
						|
 | 
						|
        resolveOnNewTabCreated?.(targetBrowser);
 | 
						|
        resolveOnContentBrowserCreated?.(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();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Finds a browser window suitable for opening a link matching the
 | 
						|
   * requirements given in the `params` argument. If the current window matches
 | 
						|
   * the requirements then it is returned otherwise the top-most window that
 | 
						|
   * matches will be returned.
 | 
						|
   *
 | 
						|
   * @param {Window} window - The current window.
 | 
						|
   * @param {Object} params - Parameters for selecting the window.
 | 
						|
   * @param {boolean} params.skipPopups - Require a non-popup window.
 | 
						|
   * @param {boolean} params.forceNonPrivate - Require a non-private window.
 | 
						|
   * @returns {Window | null} A matching browser window or null if none matched.
 | 
						|
   */
 | 
						|
  getTargetWindow(window, { skipPopups, forceNonPrivate } = {}) {
 | 
						|
    let { top } = window;
 | 
						|
    // 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 lazy.BrowserWindowTracker.getTopWindow({
 | 
						|
      private: !forceNonPrivate && PrivateBrowsingUtils.isWindowPrivate(window),
 | 
						|
      allowPopups: !skipPopups,
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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 openLinkIn, plus "ignoreButton"
 | 
						|
   *                           and "ignoreAlt".
 | 
						|
   * @param {Boolean} aIgnoreAlt
 | 
						|
   * @param {Boolean} aAllowThirdPartyFixup
 | 
						|
   * @param {Object} aPostData
 | 
						|
   * @param {Object} aReferrerInfo
 | 
						|
   */
 | 
						|
  openUILink(
 | 
						|
    window,
 | 
						|
    url,
 | 
						|
    event,
 | 
						|
    aIgnoreButton,
 | 
						|
    aIgnoreAlt,
 | 
						|
    aAllowThirdPartyFixup,
 | 
						|
    aPostData,
 | 
						|
    aReferrerInfo
 | 
						|
  ) {
 | 
						|
    event = BrowserUtils.getRootEvent(event);
 | 
						|
    let params;
 | 
						|
 | 
						|
    if (aIgnoreButton && typeof aIgnoreButton == "object") {
 | 
						|
      params = aIgnoreButton;
 | 
						|
 | 
						|
      // don't forward "ignoreButton" and "ignoreAlt" to openLinkIn
 | 
						|
      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 = BrowserUtils.whereToOpenLink(event, aIgnoreButton, aIgnoreAlt);
 | 
						|
    params.forceForeground ??= true;
 | 
						|
    this.openLinkIn(window, url, where, params);
 | 
						|
  },
 | 
						|
 | 
						|
  /* openTrustedLinkIn will attempt to open the given URI using the SystemPrincipal
 | 
						|
   * as the trigeringPrincipal, unless a more specific Principal is provided.
 | 
						|
   *
 | 
						|
   * Otherwise, parameters are the same as openLinkIn, but we will set `forceForeground`
 | 
						|
   * to true.
 | 
						|
   */
 | 
						|
  openTrustedLinkIn(window, url, where, params = {}) {
 | 
						|
    if (!params.triggeringPrincipal) {
 | 
						|
      params.triggeringPrincipal =
 | 
						|
        Services.scriptSecurityManager.getSystemPrincipal();
 | 
						|
    }
 | 
						|
 | 
						|
    params.forceForeground ??= true;
 | 
						|
    this.openLinkIn(window, url, where, params);
 | 
						|
  },
 | 
						|
 | 
						|
  /* openWebLinkIn will attempt to open the given URI using the NullPrincipal
 | 
						|
   * as the triggeringPrincipal, unless a more specific Principal is provided.
 | 
						|
   *
 | 
						|
   * Otherwise, parameters are the same as openLinkIn, but we will set `forceForeground`
 | 
						|
   * to true.
 | 
						|
   */
 | 
						|
  openWebLinkIn(window, url, where, params = {}) {
 | 
						|
    if (!params.triggeringPrincipal) {
 | 
						|
      params.triggeringPrincipal =
 | 
						|
        Services.scriptSecurityManager.createNullPrincipal({});
 | 
						|
    }
 | 
						|
    if (params.triggeringPrincipal.isSystemPrincipal) {
 | 
						|
      throw new Error(
 | 
						|
        "System principal should never be passed into openWebLinkIn()"
 | 
						|
      );
 | 
						|
    }
 | 
						|
    params.forceForeground ??= true;
 | 
						|
    this.openLinkIn(window, url, where, params);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Given a URI, guess which container to use to open it. This is used for external
 | 
						|
   * openers as a quality of life improvement (e.g. to open a document into the container
 | 
						|
   * where you are logged in to the service that hosts it).
 | 
						|
   * matches will be returned.
 | 
						|
   * For now this can only use currently-open tabs, until history is tagged with the
 | 
						|
   * container id (https://bugzilla.mozilla.org/show_bug.cgi?id=1283320).
 | 
						|
   *
 | 
						|
   * @param {nsIURI} aURI - The URI being opened.
 | 
						|
   * @returns {number | null} The guessed userContextId, or null if none.
 | 
						|
   */
 | 
						|
  guessUserContextId(aURI) {
 | 
						|
    let host;
 | 
						|
    try {
 | 
						|
      host = aURI.host;
 | 
						|
    } catch (e) {}
 | 
						|
    if (!host) {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
    const containerScores = new Map();
 | 
						|
    let guessedUserContextId = null;
 | 
						|
    let maxCount = 0;
 | 
						|
    for (let win of lazy.BrowserWindowTracker.orderedWindows) {
 | 
						|
      for (let tab of win.gBrowser.visibleTabs) {
 | 
						|
        let { userContextId } = tab;
 | 
						|
        let currentURIHost = null;
 | 
						|
        try {
 | 
						|
          currentURIHost = tab.linkedBrowser.currentURI.host;
 | 
						|
        } catch (e) {}
 | 
						|
 | 
						|
        if (currentURIHost == host) {
 | 
						|
          let count = (containerScores.get(userContextId) ?? 0) + 1;
 | 
						|
          containerScores.set(userContextId, count);
 | 
						|
          if (count > maxCount) {
 | 
						|
            guessedUserContextId = userContextId;
 | 
						|
            maxCount = count;
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    return guessedUserContextId;
 | 
						|
  },
 | 
						|
};
 |