remote/targets/Targets.jsm
author Alexandre Poirot <poirot.alex@gmail.com>
Mon, 11 Mar 2019 12:50:26 +0000
changeset 521341 c5edac4ccac997f4843245ab78a2266fb12486c1
parent 521285 44e4bcd29904d7c4242d1bfb5ddfeda29e33c398
permissions -rw-r--r--
Bug 1533682 - Implements more Target/Page methods/events to support puppeteer's browser.newPage. r=ato Depends on D22694 Differential Revision: https://phabricator.services.mozilla.com/D22695

/* 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/. */

"use strict";

var EXPORTED_SYMBOLS = ["Targets"];

const {EventEmitter} = ChromeUtils.import("resource://gre/modules/EventEmitter.jsm");
const {MessagePromise} = ChromeUtils.import("chrome://remote/content/Sync.jsm");
const {TabTarget} = ChromeUtils.import("chrome://remote/content/targets/TabTarget.jsm");
const {MainProcessTarget} = ChromeUtils.import("chrome://remote/content/targets/MainProcessTarget.jsm");

class Targets {
  constructor() {
    // browser context ID -> Target<XULElement>
    this._targets = new Map();

    EventEmitter.decorate(this);
  }

  /** @param {BrowserElement} browser */
  async connect(browser) {
    // The tab may just have been created and not fully initialized yet.
    // Target class expects BrowserElement.browsingContext to be defined
    // whereas it is asynchronously set by the custom element class.
    // At least ensure that this property is set before instantiating the target.
    if (!browser.browsingContext) {
      await new MessagePromise(browser.messageManager, "Browser:Init");
    }

    const target = new TabTarget(this, browser);
    target.connect();
    this._targets.set(target.id, target);
    this.emit("connect", target);
  }

  /** @param {BrowserElement} browser */
  disconnect(browser) {
    // Ignore the browsers that haven't had time to initialize.
    if (!browser.browsingContext) {
      return;
    }

    const target = this._targets.get(browser.browsingContext.id);
    if (target) {
      this.emit("disconnect", target);
      target.disconnect();
      this._targets.delete(target.id);
    }
  }

  clear() {
    for (const target of this) {
      this.disconnect(target.browser);
    }
    if (this.mainProcessTarget) {
      this.mainProcessTarget.disconnect();
      this.mainProcessTarget = null;
    }
  }

  get size() {
    return this._targets.size;
  }

  /**
   * Get Target instance by target id
   *
   * @param int id Target id
   */
  getById(id) {
    return this._targets.get(id);
  }

  /**
   * Get the Target instance for the main process.
   * This target is a singleton and only exposes a subset of domains.
   */
  getMainProcessTarget() {
    if (!this.mainProcessTarget) {
      this.mainProcessTarget = new MainProcessTarget(this);
      this.emit("connect", this.mainProcessTarget);
    }
    return this.mainProcessTarget;
  }

  * [Symbol.iterator]() {
    for (const target of this._targets.values()) {
      yield target;
    }
  }

  toJSON() {
    return [...this];
  }

  toString() {
    return `[object Targets ${this.size}]`;
  }
}