tor-browser

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

tab.js (9747B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 /*
      8 * Descriptor Actor that represents a Tab in the parent process. It
      9 * launches a WindowGlobalTargetActor in the content process to do the real work and tunnels the
     10 * data.
     11 *
     12 * See devtools/docs/backend/actor-hierarchy.md for more details.
     13 */
     14 
     15 const { Actor } = require("resource://devtools/shared/protocol.js");
     16 const {
     17  tabDescriptorSpec,
     18 } = require("resource://devtools/shared/specs/descriptors/tab.js");
     19 
     20 const lazy = {};
     21 ChromeUtils.defineESModuleGetters(
     22  lazy,
     23  {
     24    PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
     25  },
     26  { global: "contextual" }
     27 );
     28 
     29 const { AppConstants } = ChromeUtils.importESModule(
     30  "resource://gre/modules/AppConstants.sys.mjs",
     31  { global: "contextual" }
     32 );
     33 const {
     34  createBrowserElementSessionContext,
     35 } = require("resource://devtools/server/actors/watcher/session-context.js");
     36 
     37 loader.lazyRequireGetter(
     38  this,
     39  "WatcherActor",
     40  "resource://devtools/server/actors/watcher.js",
     41  true
     42 );
     43 loader.lazyRequireGetter(
     44  this,
     45  "connectToFrame",
     46  "resource://devtools/server/connectors/frame-connector.js",
     47  true
     48 );
     49 
     50 /**
     51 * Creates a target actor proxy for handling requests to a single browser frame.
     52 * Both <xul:browser> and <iframe mozbrowser> are supported.
     53 * This actor is a shim that connects to a WindowGlobalTargetActor in a remote browser process.
     54 * All RDP packets get forwarded using the message manager.
     55 *
     56 * @param connection The main RDP connection.
     57 * @param browser <xul:browser> or <iframe mozbrowser> element to connect to.
     58 */
     59 class TabDescriptorActor extends Actor {
     60  constructor(connection, browser) {
     61    super(connection, tabDescriptorSpec);
     62    this._browser = browser;
     63  }
     64 
     65  form() {
     66    const form = {
     67      actor: this.actorID,
     68      browserId: this._browser.browserId,
     69      browsingContextID:
     70        this._browser && this._browser.browsingContext
     71          ? this._browser.browsingContext.id
     72          : null,
     73      isZombieTab: this._isZombieTab(),
     74      outerWindowID: this._getOuterWindowId(),
     75      selected: this.selected,
     76      title: this._getTitle(),
     77      traits: {
     78        // Supports the Watcher actor. Can be removed as part of Bug 1680280.
     79        watcher: true,
     80        supportsReloadDescriptor: true,
     81        // Tab descriptor is the only one to support navigation
     82        supportsNavigation: true,
     83      },
     84      url: this._getUrl(),
     85    };
     86 
     87    return form;
     88  }
     89 
     90  _getTitle() {
     91    // If the content already provides a title, use it.
     92    if (this._browser.contentTitle) {
     93      return this._browser.contentTitle;
     94    }
     95 
     96    // For zombie or lazy tabs (tab created, but content has not been loaded),
     97    // try to retrieve the title from the XUL Tab itself.
     98    // Note: this only works on Firefox desktop.
     99    if (this._tabbrowser) {
    100      const tab = this._tabbrowser.getTabForBrowser(this._browser);
    101      if (tab) {
    102        return tab.label;
    103      }
    104    }
    105 
    106    // No title available.
    107    return null;
    108  }
    109 
    110  _getUrl() {
    111    if (!this._browser || !this._browser.browsingContext) {
    112      return "";
    113    }
    114 
    115    const { browsingContext } = this._browser;
    116    return browsingContext.currentWindowGlobal.documentURI.spec;
    117  }
    118 
    119  _getOuterWindowId() {
    120    if (!this._browser || !this._browser.browsingContext) {
    121      return "";
    122    }
    123 
    124    const { browsingContext } = this._browser;
    125    return browsingContext.currentWindowGlobal.outerWindowId;
    126  }
    127 
    128  get selected() {
    129    // getMostRecentBrowserWindow will find the appropriate window on Firefox
    130    // Desktop and on GeckoView.
    131    const topAppWindow = Services.wm.getMostRecentBrowserWindow();
    132 
    133    const selectedBrowser = topAppWindow?.gBrowser?.selectedBrowser;
    134    if (!selectedBrowser) {
    135      // Note: gBrowser is not available on GeckoView.
    136      // We should find another way to know if this browser is the selected
    137      // browser. See Bug 1631020.
    138      return false;
    139    }
    140 
    141    return this._browser === selectedBrowser;
    142  }
    143 
    144  async getTarget() {
    145    if (!this.conn) {
    146      return {
    147        error: "tabDestroyed",
    148        message: "Tab destroyed while performing a TabDescriptorActor update",
    149      };
    150    }
    151 
    152    /* eslint-disable-next-line no-async-promise-executor */
    153    return new Promise(async (resolve, reject) => {
    154      const onDestroy = () => {
    155        // Reject the update promise if the tab was destroyed while requesting an update
    156        reject({
    157          error: "tabDestroyed",
    158          message: "Tab destroyed while performing a TabDescriptorActor update",
    159        });
    160 
    161        // Targets created from the TabDescriptor are not created via JSWindowActors and
    162        // we need to notify the watcher manually about their destruction.
    163        // TabDescriptor's targets are created via TabDescriptor.getTarget and are still using
    164        // message manager instead of JSWindowActors.
    165        if (this.watcher && this.targetActorForm) {
    166          this.watcher.notifyTargetDestroyed(this.targetActorForm);
    167        }
    168      };
    169 
    170      try {
    171        // Check if the browser is still connected before calling connectToFrame
    172        if (!this._browser.isConnected) {
    173          onDestroy();
    174          return;
    175        }
    176 
    177        const connectForm = await connectToFrame(
    178          this.conn,
    179          this._browser,
    180          onDestroy
    181        );
    182        this.targetActorForm = connectForm;
    183        resolve(connectForm);
    184      } catch (e) {
    185        reject({
    186          error: "tabDestroyed",
    187          message: "Tab destroyed while connecting to the frame",
    188        });
    189      }
    190    });
    191  }
    192 
    193  /**
    194   * Return a Watcher actor, allowing to keep track of targets which
    195   * already exists or will be created. It also helps knowing when they
    196   * are destroyed.
    197   */
    198  getWatcher(config) {
    199    if (!this.watcher) {
    200      this.watcher = new WatcherActor(
    201        this.conn,
    202        createBrowserElementSessionContext(this._browser, {
    203          isServerTargetSwitchingEnabled: config.isServerTargetSwitchingEnabled,
    204          isPopupDebuggingEnabled: config.isPopupDebuggingEnabled,
    205        })
    206      );
    207      this.manage(this.watcher);
    208    }
    209    return this.watcher;
    210  }
    211 
    212  get _tabbrowser() {
    213    if (this._browser && typeof this._browser.getTabBrowser == "function") {
    214      return this._browser.getTabBrowser();
    215    }
    216    return null;
    217  }
    218 
    219  async getFavicon() {
    220    if (!AppConstants.MOZ_PLACES) {
    221      // PlacesUtils is not supported
    222      return null;
    223    }
    224 
    225    try {
    226      const favicon = await lazy.PlacesUtils.favicons.getFaviconForPage(
    227        lazy.PlacesUtils.toURI(this._getUrl())
    228      );
    229      return favicon.rawData;
    230    } catch (e) {
    231      // Favicon unavailable for this url.
    232      return null;
    233    }
    234  }
    235 
    236  _isZombieTab() {
    237    // Note: GeckoView doesn't support zombie tabs
    238    const tabbrowser = this._tabbrowser;
    239    const tab = tabbrowser ? tabbrowser.getTabForBrowser(this._browser) : null;
    240    return tab?.hasAttribute && tab.hasAttribute("pending");
    241  }
    242 
    243  /**
    244   * Navigate this tab to a new URL.
    245   *
    246   * @param {string} url
    247   * @param {boolean} waitForLoad
    248   * @return {Promise}
    249   *         A promise which resolves only once the requested URL is fully loaded.
    250   */
    251  async navigateTo(url, waitForLoad = true) {
    252    if (!this._browser || !this._browser.browsingContext) {
    253      throw new Error("Tab is destroyed");
    254    }
    255 
    256    let validURL;
    257    try {
    258      validURL = Services.io.newURI(url);
    259    } catch (e) {
    260      throw new Error("Error: Cannot navigate to invalid URL: " + url);
    261    }
    262 
    263    // Setup a nsIWebProgressListener in order to be able to know when the
    264    // new document is done loading.
    265    const deferred = Promise.withResolvers();
    266    const listener = {
    267      onStateChange(webProgress, request, stateFlags) {
    268        if (
    269          webProgress.isTopLevel &&
    270          stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW &&
    271          stateFlags &
    272            // Either wait for the start or the end of the document load
    273            (waitForLoad
    274              ? Ci.nsIWebProgressListener.STATE_STOP
    275              : Ci.nsIWebProgressListener.STATE_START)
    276        ) {
    277          const loadedURL = request.QueryInterface(Ci.nsIChannel).originalURI
    278            .spec;
    279          if (loadedURL === validURL.spec) {
    280            deferred.resolve();
    281          }
    282        }
    283      },
    284 
    285      QueryInterface: ChromeUtils.generateQI([
    286        "nsIWebProgressListener",
    287        "nsISupportsWeakReference",
    288      ]),
    289    };
    290    this._browser.addProgressListener(
    291      listener,
    292      Ci.nsIWebProgress.NOTIFY_STATE_WINDOW
    293    );
    294 
    295    this._browser.browsingContext.loadURI(validURL, {
    296      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
    297    });
    298 
    299    await deferred.promise;
    300 
    301    this._browser.removeProgressListener(
    302      listener,
    303      Ci.nsIWebProgress.NOTIFY_STATE_WINDOW
    304    );
    305  }
    306 
    307  goBack() {
    308    if (!this._browser || !this._browser.browsingContext) {
    309      throw new Error("Tab is destroyed");
    310    }
    311 
    312    this._browser.browsingContext.goBack();
    313  }
    314 
    315  goForward() {
    316    if (!this._browser || !this._browser.browsingContext) {
    317      throw new Error("Tab is destroyed");
    318    }
    319 
    320    this._browser.browsingContext.goForward();
    321  }
    322 
    323  reloadDescriptor({ bypassCache }) {
    324    if (!this._browser || !this._browser.browsingContext) {
    325      return;
    326    }
    327 
    328    this._browser.browsingContext.reload(
    329      bypassCache
    330        ? Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE
    331        : Ci.nsIWebNavigation.LOAD_FLAGS_NONE
    332    );
    333  }
    334 
    335  destroy() {
    336    this.emit("descriptor-destroyed");
    337    this._browser = null;
    338 
    339    super.destroy();
    340  }
    341 }
    342 
    343 exports.TabDescriptorActor = TabDescriptorActor;