remote/WindowManager.jsm
author Andreas Tolfsen <ato@sny.no>
Wed, 13 Mar 2019 14:35:57 +0000
changeset 524707 95f4b83aed87e9d8638987cf30b40f5ac6cd1821
parent 524339 c5edac4ccac997f4843245ab78a2266fb12486c1
child 543765 dee2008c7a7d05bc882eb368dee6a3cc7d1f90a6
permissions -rw-r--r--
bug 1534827: remote: fix constants in transitionToLoadFlag; r=yulia The transitionType is converted into undefined because there is a typo in the constants. These should be "FLAGS" rather than "FLAG". Thanks-to: Mark Banner <standard8@mozilla.com> Differential Revision: https://phabricator.services.mozilla.com/D23315

/* 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 = [
  "TabManager",
  "TabObserver",
  "WindowObserver",
];

const {DOMContentLoadedPromise} = ChromeUtils.import("chrome://remote/content/Sync.jsm");
const {EventEmitter} = ChromeUtils.import("resource://gre/modules/EventEmitter.jsm");
const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");

/**
 * The WindowManager provides tooling for application-agnostic
 * observation of windows, tabs, and content browsers as they are
 * created and destroyed.
 */

// TODO(ato):
//
// The DOM team is working on pulling browsing context related behaviour,
// such as window and tab handling, out of product code and into the platform.
// This will have implication for the remote agent,
// and as the platform gains support for product-independent events
// we can likely get rid of this entire module.

/**
 * Observes DOMWindows as they open and close.
 *
 * "open" fires when a window opens.
 * "close" fires when a window closes.
 */
class WindowObserver {
  /**
   * @param {boolean?} [false] registerExisting
   *     Events will be despatched for the ChromeWindows that exist
   *     at the time the observer is started.
   */
  constructor({registerExisting = false} = {}) {
    this.registerExisting = registerExisting;
    EventEmitter.decorate(this);
  }

  async start() {
    if (this.registerExisting) {
      for (const window of Services.wm.getEnumerator("navigator:browser")) {
        await this.onOpenWindow(window);
      }
    }

    Services.wm.addListener(this);
  }

  stop() {
    Services.wm.removeListener(this);
  }

  // nsIWindowMediatorListener

  async onOpenWindow(xulWindow) {
    const window = xulWindow
        .QueryInterface(Ci.nsIInterfaceRequestor)
        .getInterface(Ci.nsIDOMWindow);
    await new DOMContentLoadedPromise(window);
    this.emit("open", window);
  }

  onCloseWindow(xulWindow) {
    const window = xulWindow
        .QueryInterface(Ci.nsIInterfaceRequestor)
        .getInterface(Ci.nsIDOMWindow);
    this.emit("close", window);
  }

  // XPCOM

  get QueryInterface() {
    return ChromeUtils.generateQI([Ci.nsIWindowMediatorListener]);
  }
}

/**
 * Observe Firefox tabs as they open and close.
 *
 * "open" fires when a tab opens.
 * "close" fires when a tab closes.
 */
class TabObserver {
  /**
   * @param {boolean?} [false] registerExisting
   *     Events will be fired for ChromeWIndows and their respective tabs
   *     at the time when the observer is started.
   */
  constructor({registerExisting = false} = {}) {
    this.windows = new WindowObserver({registerExisting});
    EventEmitter.decorate(this);
  }

  async start() {
    this.windows.on("open", this.onWindowOpen.bind(this));
    this.windows.on("close", this.onWindowClose.bind(this));
    await this.windows.start();
  }

  stop() {
    this.windows.off("open");
    this.windows.off("close");
    this.windows.stop();
  }

  onTabOpen(tab) {
    this.emit("open", tab);
  }

  onTabClose(tab) {
    this.emit("close", tab);
  }

  // WindowObserver

  async onWindowOpen(eventName, window) {
    if (!window.gBrowser) {
      return;
    }

    for (const tab of window.gBrowser.tabs) {
      // a missing linkedBrowser means the tab is still initialising,
      // and a TabOpen event will fire once it is ready
      if (!tab.linkedBrowser) {
        continue;
      }
      this.onTabOpen(tab);
    }

    window.addEventListener("TabOpen", ({target}) => this.onTabOpen(target));
    window.addEventListener("TabClose", ({target}) => this.onTabClose(target));
  }

  onWindowClose(window) {
    // TODO(ato): Is TabClose fired when the window closes?
  }
}

var TabManager = {
  addTab() {
    const window = Services.wm.getMostRecentWindow("navigator:browser");
    const { gBrowser } = window;
    const tab = gBrowser.addTab("about:blank", {
      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
    });
    gBrowser.selectedTab = tab;
    return tab;
  },
};