tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

BrowserDOMWindow.sys.mjs (15822B)


      1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
      2 * This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 import { BrowserWindowTracker } from "resource:///modules/BrowserWindowTracker.sys.mjs";
      7 import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
      8 import { PrivateBrowsingUtils } from "resource://gre/modules/PrivateBrowsingUtils.sys.mjs";
      9 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
     10 
     11 let lazy = {};
     12 
     13 ChromeUtils.defineESModuleGetters(lazy, {
     14  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
     15  TaskbarTabsUtils: "resource:///modules/taskbartabs/TaskbarTabsUtils.sys.mjs",
     16  URILoadingHelper: "resource:///modules/URILoadingHelper.sys.mjs",
     17 });
     18 
     19 XPCOMUtils.defineLazyPreferenceGetter(
     20  lazy,
     21  "loadDivertedInBackground",
     22  "browser.tabs.loadDivertedInBackground"
     23 );
     24 
     25 ChromeUtils.defineLazyGetter(lazy, "ReferrerInfo", () =>
     26  Components.Constructor(
     27    "@mozilla.org/referrer-info;1",
     28    "nsIReferrerInfo",
     29    "init"
     30  )
     31 );
     32 
     33 /**
     34 * This class is instantiated once for each browser window, and the instance
     35 * is exposed as a `browserDOMWindow` property on that window.
     36 *
     37 * It implements the nsIBrowserDOMWindow interface, which is used by C++ as
     38 * well as toolkit code to have an application-agnostic interface to do things
     39 * like opening new tabs and windows. Fenix (Firefox on Android) has its own
     40 * implementation of the same interface.
     41 *
     42 * @implements {nsIBrowserDOMWindow}
     43 */
     44 export class BrowserDOMWindow {
     45  /**
     46   * @type {Window}
     47   */
     48  win = null;
     49 
     50  /**
     51   * @param {Window} win
     52   */
     53  constructor(win) {
     54    this.win = win;
     55  }
     56 
     57  /**
     58   * @param {Window} win
     59   */
     60  static setupInWindow(win) {
     61    win.browserDOMWindow = new BrowserDOMWindow(win);
     62  }
     63 
     64  /**
     65   * @param {Window} win
     66   */
     67  static teardownInWindow(win) {
     68    win.browserDOMWindow = null;
     69  }
     70 
     71  /**
     72   * @param {nsIURI} aURI
     73   * @param {nsIReferrerInfo} aReferrerInfo
     74   * @param {boolean} aIsPrivate
     75   * @param {boolean} aIsExternal
     76   * @param {boolean} [aForceNotRemote=false]
     77   * @param {number} [aUserContextId=0]
     78   * @param {nsIOpenWindowInfo} [aOpenWindowInfo=null]
     79   * @param {Element} [aOpenerBrowser=null]
     80   * @param {nsIPrincipal} [aTriggeringPrincipal=null]
     81   * @param {string} [aName=""]
     82   * @param {nsIPolicyContainer} [aPolicyContainer=null]
     83   * @param {boolean} [skipLoad=false]
     84   * @param {i16} [aWhere=undefined]
     85   * @returns {nsIBrowser|null}
     86   */
     87  #openURIInNewTab(
     88    aURI,
     89    aReferrerInfo,
     90    aIsPrivate,
     91    aIsExternal,
     92    aForceNotRemote = false,
     93    aUserContextId = Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID,
     94    aOpenWindowInfo = null,
     95    aOpenerBrowser = null,
     96    aTriggeringPrincipal = null,
     97    aName = "",
     98    aPolicyContainer = null,
     99    aSkipLoad = false,
    100    aWhere = undefined
    101  ) {
    102    let win, needToFocusWin;
    103 
    104    // try the current window. if we're in a popup or a taskbar tab, fall
    105    // back on the most recent browser window
    106    if (
    107      this.win.toolbar.visible &&
    108      !lazy.TaskbarTabsUtils.isTaskbarTabWindow(this.win)
    109    ) {
    110      win = this.win;
    111    } else {
    112      win = BrowserWindowTracker.getTopWindow({ private: aIsPrivate });
    113      needToFocusWin = true;
    114    }
    115 
    116    if (!win) {
    117      // we couldn't find a suitable window, a new one needs to be opened.
    118      return null;
    119    }
    120 
    121    if (aIsExternal && (!aURI || aURI.spec == "about:blank")) {
    122      win.BrowserCommands.openTab(); // this also focuses the location bar
    123      win.focus();
    124      return win.gBrowser.selectedBrowser;
    125    }
    126 
    127    // OPEN_NEWTAB_BACKGROUND and OPEN_NEWTAB_FOREGROUND are used by
    128    // `window.open` with modifiers.
    129    // The last case is OPEN_NEWTAB, which is used by:
    130    //   * a link with `target="_blank"`, without modifiers
    131    //   * `window.open` without features, without modifiers
    132    let loadInBackground;
    133    if (aWhere === Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_BACKGROUND) {
    134      loadInBackground = true;
    135    } else if (aWhere === Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_FOREGROUND) {
    136      loadInBackground = false;
    137    } else {
    138      loadInBackground = lazy.loadDivertedInBackground;
    139    }
    140 
    141    const uriString = aURI ? aURI.spec : "about:blank";
    142    const tabOptions = {
    143      triggeringPrincipal: aTriggeringPrincipal,
    144      referrerInfo: aReferrerInfo,
    145      userContextId: aUserContextId,
    146      fromExternal: aIsExternal,
    147      inBackground: loadInBackground,
    148      forceNotRemote: aForceNotRemote,
    149      openWindowInfo: aOpenWindowInfo,
    150      openerBrowser: aOpenerBrowser,
    151      name: aName,
    152      policyContainer: aPolicyContainer,
    153      skipLoad: aSkipLoad,
    154    };
    155 
    156    let tab;
    157    if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_AFTER_CURRENT) {
    158      tab = win.gBrowser.addAdjacentTab(
    159        win.gBrowser.selectedTab,
    160        uriString,
    161        tabOptions
    162      );
    163    } else {
    164      tab = win.gBrowser.addTab(uriString, tabOptions);
    165    }
    166 
    167    let browser = win.gBrowser.getBrowserForTab(tab);
    168 
    169    if (needToFocusWin || (!loadInBackground && aIsExternal)) {
    170      win.focus();
    171    }
    172 
    173    return browser;
    174  }
    175 
    176  /**
    177   * @type {nsIBrowserDOMWindow["createContentWindow"]}
    178   */
    179  createContentWindow(
    180    aURI,
    181    aOpenWindowInfo,
    182    aWhere,
    183    aFlags,
    184    aTriggeringPrincipal,
    185    aPolicyContainer
    186  ) {
    187    return this.#getContentWindowOrOpenURI(
    188      null,
    189      aOpenWindowInfo,
    190      aWhere,
    191      aFlags,
    192      aTriggeringPrincipal,
    193      aPolicyContainer,
    194      true
    195    );
    196  }
    197 
    198  /**
    199   * @type {nsIBrowserDOMWindow["openURI"]}
    200   */
    201  openURI(
    202    aURI,
    203    aOpenWindowInfo,
    204    aWhere,
    205    aFlags,
    206    aTriggeringPrincipal,
    207    aPolicyContainer
    208  ) {
    209    if (!aURI) {
    210      console.error("openURI should only be called with a valid URI");
    211      throw Components.Exception("", Cr.NS_ERROR_FAILURE);
    212    }
    213    return this.#getContentWindowOrOpenURI(
    214      aURI,
    215      aOpenWindowInfo,
    216      aWhere,
    217      aFlags,
    218      aTriggeringPrincipal,
    219      aPolicyContainer,
    220      false
    221    );
    222  }
    223 
    224  /**
    225   * @param {nsIURI} aURI
    226   * @param {nsIOpenWindowInfo} aOpenWindowInfo
    227   * @param {i16} aWhere
    228   * @param {i32} aFlags
    229   * @param {nsIPrincipal} aTriggeringPrincipal
    230   * @param {nsIPolicyContainer} aPolicyContainer
    231   * @param {boolean} aSkipLoad
    232   * @returns {BrowsingContext}
    233   */
    234  #getContentWindowOrOpenURI(
    235    aURI,
    236    aOpenWindowInfo,
    237    aWhere,
    238    aFlags,
    239    aTriggeringPrincipal,
    240    aPolicyContainer,
    241    aSkipLoad
    242  ) {
    243    var browsingContext = null;
    244    var isExternal = !!(aFlags & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
    245    var guessUserContextIdEnabled =
    246      isExternal &&
    247      !Services.prefs.getBoolPref(
    248        "browser.link.force_default_user_context_id_for_external_opens",
    249        false
    250      );
    251    var openingUserContextId =
    252      (guessUserContextIdEnabled &&
    253        lazy.URILoadingHelper.guessUserContextId(aURI)) ||
    254      Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
    255 
    256    if (aOpenWindowInfo && isExternal) {
    257      console.error(
    258        "BrowserDOMWindow.openURI did not expect aOpenWindowInfo to be " +
    259          "passed if the context is OPEN_EXTERNAL."
    260      );
    261      throw Components.Exception("", Cr.NS_ERROR_FAILURE);
    262    }
    263 
    264    if (isExternal && aURI && aURI.schemeIs("chrome")) {
    265      dump("use --chrome command-line option to load external chrome urls\n");
    266      return null;
    267    }
    268 
    269    if (isExternal) {
    270      lazy.NimbusFeatures.externalLinkHandling.recordExposureEvent({
    271        once: true,
    272      });
    273    }
    274 
    275    if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
    276      /** @type {number} proxy for `browser.link.open_newwindow.override.external` */
    277      const externalLinkOpeningBehavior =
    278        lazy.NimbusFeatures.externalLinkHandling.getVariable("openBehavior");
    279      if (isExternal && externalLinkOpeningBehavior != -1) {
    280        aWhere = externalLinkOpeningBehavior;
    281      } else {
    282        aWhere = Services.prefs.getIntPref("browser.link.open_newwindow");
    283      }
    284    }
    285 
    286    let referrerInfo;
    287    if (aFlags & Ci.nsIBrowserDOMWindow.OPEN_NO_REFERRER) {
    288      referrerInfo = new lazy.ReferrerInfo(
    289        Ci.nsIReferrerInfo.EMPTY,
    290        false,
    291        null
    292      );
    293    } else if (
    294      aOpenWindowInfo &&
    295      aOpenWindowInfo.parent &&
    296      aOpenWindowInfo.parent.window
    297    ) {
    298      referrerInfo = new lazy.ReferrerInfo(
    299        aOpenWindowInfo.parent.window.document.referrerInfo.referrerPolicy,
    300        true,
    301        Services.io.newURI(aOpenWindowInfo.parent.window.location.href)
    302      );
    303    } else {
    304      referrerInfo = new lazy.ReferrerInfo(
    305        Ci.nsIReferrerInfo.EMPTY,
    306        true,
    307        null
    308      );
    309    }
    310 
    311    let isPrivate = aOpenWindowInfo
    312      ? aOpenWindowInfo.originAttributes.privateBrowsingId != 0
    313      : PrivateBrowsingUtils.isWindowPrivate(this.win);
    314 
    315    switch (aWhere) {
    316      case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW: {
    317        // FIXME: Bug 408379. So how come this doesn't send the
    318        // referrer like the other loads do?
    319        var url = aURI && aURI.spec;
    320        let features = "all,dialog=no";
    321        if (isPrivate) {
    322          features += ",private";
    323        }
    324        // Pass all params to openDialog to ensure that "url" isn't passed through
    325        // loadOneOrMoreURIs, which splits based on "|"
    326        try {
    327          let extraOptions = Cc[
    328            "@mozilla.org/hash-property-bag;1"
    329          ].createInstance(Ci.nsIWritablePropertyBag2);
    330          extraOptions.setPropertyAsBool("fromExternal", isExternal);
    331 
    332          this.win.openDialog(
    333            AppConstants.BROWSER_CHROME_URL,
    334            "_blank",
    335            features,
    336            // window.arguments
    337            url,
    338            extraOptions,
    339            null,
    340            null,
    341            null,
    342            null,
    343            null,
    344            null,
    345            aTriggeringPrincipal,
    346            null,
    347            aPolicyContainer,
    348            aOpenWindowInfo
    349          );
    350          // At this point, the new browser window is just starting to load, and
    351          // hasn't created the content <browser> that we should return.
    352          // If the caller of this function is originating in C++, they can pass a
    353          // callback in nsOpenWindowInfo and it will be invoked when the browsing
    354          // context for a newly opened window is ready.
    355          browsingContext = null;
    356        } catch (ex) {
    357          console.error(ex);
    358        }
    359        break;
    360      }
    361      case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB:
    362      case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_BACKGROUND:
    363      case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_FOREGROUND:
    364      case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_AFTER_CURRENT: {
    365        // If we have an opener, that means that the caller is expecting access
    366        // to the nsIDOMWindow of the opened tab right away. For e10s windows,
    367        // this means forcing the newly opened browser to be non-remote so that
    368        // we can hand back the nsIDOMWindow. DocumentLoadListener will do the
    369        // job of shuttling off the newly opened browser to run in the right
    370        // process once it starts loading a URI.
    371        let forceNotRemote = aOpenWindowInfo && !aOpenWindowInfo.isRemote;
    372        let userContextId = aOpenWindowInfo
    373          ? aOpenWindowInfo.originAttributes.userContextId
    374          : openingUserContextId;
    375        let browser = this.#openURIInNewTab(
    376          aURI,
    377          referrerInfo,
    378          isPrivate,
    379          isExternal,
    380          forceNotRemote,
    381          userContextId,
    382          aOpenWindowInfo,
    383          aOpenWindowInfo?.parent?.top.embedderElement,
    384          aTriggeringPrincipal,
    385          "",
    386          aPolicyContainer,
    387          aSkipLoad,
    388          aWhere
    389        );
    390        if (browser) {
    391          browsingContext = browser.browsingContext;
    392        }
    393        break;
    394      }
    395      case Ci.nsIBrowserDOMWindow.OPEN_PRINT_BROWSER: {
    396        let browser =
    397          this.win.PrintUtils.handleStaticCloneCreatedForPrint(aOpenWindowInfo);
    398        if (browser) {
    399          browsingContext = browser.browsingContext;
    400        }
    401        break;
    402      }
    403      default:
    404        // OPEN_CURRENTWINDOW or an illegal value
    405        browsingContext = this.win.gBrowser.selectedBrowser.browsingContext;
    406        if (aURI) {
    407          let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
    408          if (isExternal) {
    409            loadFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
    410          } else if (!aTriggeringPrincipal.isSystemPrincipal) {
    411            // XXX this code must be reviewed and changed when bug 1616353
    412            // lands.
    413            loadFlags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIRST_LOAD;
    414          }
    415          // This should ideally be able to call loadURI with the actual URI.
    416          // However, that would bypass some styles of fixup (notably Windows
    417          // paths passed as "URI"s), so this needs some further thought. It
    418          // should be addressed in bug 1815509.
    419          this.win.gBrowser.fixupAndLoadURIString(aURI.spec, {
    420            triggeringPrincipal: aTriggeringPrincipal,
    421            policyContainer: aPolicyContainer,
    422            loadFlags,
    423            referrerInfo,
    424          });
    425        }
    426        if (!lazy.loadDivertedInBackground) {
    427          this.win.focus();
    428        }
    429    }
    430    return browsingContext;
    431  }
    432 
    433  /**
    434   * @type {nsIBrowserDOMWindow["createContentWindowInFrame"]}
    435   */
    436  createContentWindowInFrame(aURI, aParams, aWhere, aFlags, aName) {
    437    // Passing a null-URI to only create the content window,
    438    // and pass true for aSkipLoad to prevent loading of
    439    // about:blank
    440    return this.#getContentWindowOrOpenURIInFrame(
    441      null,
    442      aParams,
    443      aWhere,
    444      aFlags,
    445      aName,
    446      true
    447    );
    448  }
    449 
    450  /**
    451   * @type {nsIBrowserDOMWindow["openURIInFrame"]}
    452   */
    453  openURIInFrame(aURI, aParams, aWhere, aFlags, aName) {
    454    return this.#getContentWindowOrOpenURIInFrame(
    455      aURI,
    456      aParams,
    457      aWhere,
    458      aFlags,
    459      aName,
    460      false
    461    );
    462  }
    463 
    464  /**
    465   * @param {nsIURI} aURI
    466   * @param {nsIOpenURIInFrameParams} aParams
    467   * @param {i16} aWhere
    468   * @param {i32} aFlags
    469   * @param {string} aName
    470   * @param {boolean} aSkipLoad
    471   * @returns {Element}
    472   */
    473  #getContentWindowOrOpenURIInFrame(
    474    aURI,
    475    aParams,
    476    aWhere,
    477    aFlags,
    478    aName,
    479    aSkipLoad
    480  ) {
    481    if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_PRINT_BROWSER) {
    482      return this.win.PrintUtils.handleStaticCloneCreatedForPrint(
    483        aParams.openWindowInfo
    484      );
    485    }
    486 
    487    if (
    488      aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB &&
    489      aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_BACKGROUND &&
    490      aWhere != Ci.nsIBrowserDOMWindow.OPEN_NEWTAB_FOREGROUND
    491    ) {
    492      dump("Error: openURIInFrame can only open in new tabs or print");
    493      return null;
    494    }
    495 
    496    var isExternal = !!(aFlags & Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
    497 
    498    var userContextId =
    499      aParams.openerOriginAttributes &&
    500      "userContextId" in aParams.openerOriginAttributes
    501        ? aParams.openerOriginAttributes.userContextId
    502        : Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
    503 
    504    return this.#openURIInNewTab(
    505      aURI,
    506      aParams.referrerInfo,
    507      aParams.isPrivate,
    508      isExternal,
    509      false,
    510      userContextId,
    511      aParams.openWindowInfo,
    512      aParams.openerBrowser,
    513      aParams.triggeringPrincipal,
    514      aName,
    515      aParams.policyContainer,
    516      aSkipLoad,
    517      aWhere
    518    );
    519  }
    520 
    521  /**
    522   * @type {nsIBrowserDOMWindow["canClose"]}
    523   */
    524  canClose() {
    525    return this.win.CanCloseWindow();
    526  }
    527 
    528  /**
    529   * @type {nsIBrowserDOMWindow["tabCount"]}
    530   */
    531  get tabCount() {
    532    return this.win.gBrowser.tabs.length;
    533  }
    534 }
    535 
    536 BrowserDOMWindow.prototype.QueryInterface = ChromeUtils.generateQI([
    537  "nsIBrowserDOMWindow",
    538 ]);