mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			445 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			445 lines
		
	
	
	
		
			13 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* -*- 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 <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: {
 | 
						|
        // 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",
 | 
						|
]);
 |