tor-browser

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

panel.js (7169B)


      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 "use strict";
      5 
      6 const EventEmitter = require("resource://devtools/shared/event-emitter.js");
      7 loader.lazyRequireGetter(
      8  this,
      9  "openContentLink",
     10  "resource://devtools/client/shared/link.js",
     11  true
     12 );
     13 
     14 /**
     15 * This object represents DOM panel. It's responsibility is to
     16 * render Document Object Model of the current debugger target.
     17 */
     18 class DomPanel {
     19  constructor(iframeWindow, toolbox, commands) {
     20    this.panelWin = iframeWindow;
     21    this._toolbox = toolbox;
     22    this._commands = commands;
     23 
     24    this.onContentMessage = this.onContentMessage.bind(this);
     25    this.onPanelVisibilityChange = this.onPanelVisibilityChange.bind(this);
     26 
     27    this.pendingRequests = new Map();
     28 
     29    EventEmitter.decorate(this);
     30  }
     31  /**
     32   * Open is effectively an asynchronous constructor.
     33   *
     34   * @return object
     35   *         A promise that is resolved when the DOM panel completes opening.
     36   */
     37  async open() {
     38    // Wait for the retrieval of root object properties before resolving open
     39    const onGetProperties = new Promise(resolve => {
     40      this._resolveOpen = resolve;
     41    });
     42 
     43    await this.initialize();
     44 
     45    await onGetProperties;
     46 
     47    return this;
     48  }
     49 
     50  // Initialization
     51  async initialize() {
     52    this.panelWin.addEventListener(
     53      "devtools/content/message",
     54      this.onContentMessage,
     55      true
     56    );
     57 
     58    this._toolbox.on("select", this.onPanelVisibilityChange);
     59 
     60    // onTargetAvailable is mandatory when calling watchTargets
     61    this._onTargetAvailable = () => {};
     62    this._onTargetSelected = this._onTargetSelected.bind(this);
     63    await this._commands.targetCommand.watchTargets({
     64      types: [this._commands.targetCommand.TYPES.FRAME],
     65      onAvailable: this._onTargetAvailable,
     66      onSelected: this._onTargetSelected,
     67    });
     68 
     69    this.onResourceAvailable = this.onResourceAvailable.bind(this);
     70    await this._commands.resourceCommand.watchResources(
     71      [this._commands.resourceCommand.TYPES.DOCUMENT_EVENT],
     72      {
     73        onAvailable: this.onResourceAvailable,
     74      }
     75    );
     76 
     77    // Export provider object with useful API for DOM panel.
     78    const provider = {
     79      getToolbox: this.getToolbox.bind(this),
     80      getPrototypeAndProperties: this.getPrototypeAndProperties.bind(this),
     81      openLink: this.openLink.bind(this),
     82      // Resolve DomPanel.open once the object properties are fetched
     83      onPropertiesFetched: () => {
     84        if (this._resolveOpen) {
     85          this._resolveOpen();
     86          this._resolveOpen = null;
     87        }
     88      },
     89    };
     90 
     91    exportIntoContentScope(this.panelWin, provider, "DomProvider");
     92  }
     93 
     94  destroy() {
     95    if (this._destroyed) {
     96      return;
     97    }
     98    this._destroyed = true;
     99 
    100    this._commands.targetCommand.unwatchTargets({
    101      types: [this._commands.targetCommand.TYPES.FRAME],
    102      onAvailable: this._onTargetAvailable,
    103      onSelected: this._onTargetSelected,
    104    });
    105    this._commands.resourceCommand.unwatchResources(
    106      [this._commands.resourceCommand.TYPES.DOCUMENT_EVENT],
    107      { onAvailable: this.onResourceAvailable }
    108    );
    109    this._toolbox.off("select", this.onPanelVisibilityChange);
    110 
    111    this.emit("destroyed");
    112  }
    113 
    114  // Events
    115  refresh() {
    116    // Do not refresh if the panel isn't visible.
    117    if (!this.isPanelVisible()) {
    118      return;
    119    }
    120 
    121    // Do not refresh if it isn't necessary.
    122    if (!this.shouldRefresh) {
    123      return;
    124    }
    125 
    126    // Alright reset the flag we are about to refresh the panel.
    127    this.shouldRefresh = false;
    128 
    129    this.getRootGrip().then(rootGrip => {
    130      this.postContentMessage("initialize", rootGrip);
    131    });
    132  }
    133 
    134  /**
    135   * Make sure the panel is refreshed, either when navigation occurs or when a frame is
    136   * selected in the iframe picker.
    137   * The panel is refreshed immediately if it's currently selected or lazily when the user
    138   * actually selects it.
    139   */
    140  forceRefresh() {
    141    this.shouldRefresh = true;
    142    // This will end up calling scriptCommand execute method to retrieve the `window` grip
    143    // on targetCommand.selectedTargetFront.
    144    this.refresh();
    145  }
    146 
    147  _onTargetSelected() {
    148    this.forceRefresh();
    149  }
    150 
    151  onResourceAvailable(resources) {
    152    for (const resource of resources) {
    153      // Only consider top level document, and ignore remote iframes top document
    154      if (
    155        resource.resourceType ===
    156          this._commands.resourceCommand.TYPES.DOCUMENT_EVENT &&
    157        resource.name === "dom-complete" &&
    158        resource.targetFront.isTopLevel
    159      ) {
    160        this.forceRefresh();
    161      }
    162    }
    163  }
    164 
    165  /**
    166   * Make sure the panel is refreshed (if needed) when it's selected.
    167   */
    168  onPanelVisibilityChange() {
    169    this.refresh();
    170  }
    171 
    172  // Helpers
    173  /**
    174   * Return true if the DOM panel is currently selected.
    175   */
    176  isPanelVisible() {
    177    return this._toolbox.currentToolId === "dom";
    178  }
    179 
    180  async getPrototypeAndProperties(objectFront) {
    181    if (!objectFront.actorID) {
    182      console.error("No actor!", objectFront);
    183      throw new Error("Failed to get object front.");
    184    }
    185 
    186    // Bail out if target doesn't exist (toolbox maybe closed already).
    187    if (!this.currentTarget) {
    188      return null;
    189    }
    190 
    191    // Check for a previously stored request for grip.
    192    let request = this.pendingRequests.get(objectFront.actorID);
    193 
    194    // If no request is in progress create a new one.
    195    if (!request) {
    196      request = objectFront.getPrototypeAndProperties();
    197      this.pendingRequests.set(objectFront.actorID, request);
    198    }
    199 
    200    const response = await request;
    201    this.pendingRequests.delete(objectFront.actorID);
    202 
    203    // Fire an event about not having any pending requests.
    204    if (!this.pendingRequests.size) {
    205      this.emit("no-pending-requests");
    206    }
    207 
    208    return response;
    209  }
    210 
    211  openLink(url) {
    212    openContentLink(url);
    213  }
    214 
    215  async getRootGrip() {
    216    const { result } = await this._toolbox.commands.scriptCommand.execute(
    217      "window",
    218      {
    219        disableBreaks: true,
    220      }
    221    );
    222    return result;
    223  }
    224 
    225  postContentMessage(type, args) {
    226    const data = {
    227      type,
    228      args,
    229    };
    230 
    231    const event = new this.panelWin.MessageEvent("devtools/chrome/message", {
    232      bubbles: true,
    233      cancelable: true,
    234      data,
    235    });
    236 
    237    this.panelWin.dispatchEvent(event);
    238  }
    239 
    240  onContentMessage(event) {
    241    const data = event.data;
    242    const method = data.type;
    243    if (typeof this[method] == "function") {
    244      this[method](data.args);
    245    }
    246  }
    247 
    248  getToolbox() {
    249    return this._toolbox;
    250  }
    251 
    252  get currentTarget() {
    253    return this._toolbox.target;
    254  }
    255 }
    256 
    257 // Helpers
    258 
    259 function exportIntoContentScope(win, obj, defineAs) {
    260  const clone = Cu.createObjectIn(win, {
    261    defineAs,
    262  });
    263 
    264  const props = Object.getOwnPropertyNames(obj);
    265  for (let i = 0; i < props.length; i++) {
    266    const propName = props[i];
    267    const propValue = obj[propName];
    268    if (typeof propValue == "function") {
    269      Cu.exportFunction(propValue, clone, {
    270        defineAs: propName,
    271      });
    272    }
    273  }
    274 }
    275 
    276 // Exports from this module
    277 exports.DomPanel = DomPanel;