Bug 1533682 - Implements more Target/Page methods/events to support puppeteer's browser.newPage. r=ato
authorAlexandre Poirot <poirot.alex@gmail.com>
Mon, 11 Mar 2019 12:50:26 +0000
changeset 521341 c5edac4ccac997f4843245ab78a2266fb12486c1
parent 521340 fde0fa0c16da411aa5b39f423e7be6715ee3d54a
child 521342 016f24de0fa3b206905f1d0c5365e44d66bc2da4
push id10866
push usernerli@mozilla.com
push dateTue, 12 Mar 2019 18:59:09 +0000
treeherdermozilla-beta@445c24a51727 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersato
bugs1533682
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
remote/WindowManager.jsm
remote/domains/content/Page.jsm
remote/domains/parent/Target.jsm
remote/targets/Targets.jsm
--- a/remote/WindowManager.jsm
+++ b/remote/WindowManager.jsm
@@ -1,15 +1,16 @@
 /* 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");
 
@@ -138,8 +139,20 @@ class TabObserver {
     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;
+  },
+};
--- a/remote/domains/content/Page.jsm
+++ b/remote/domains/content/Page.jsm
@@ -62,30 +62,48 @@ class Page extends ContentProcessDomain 
       triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
     };
     this.docShell.QueryInterface(Ci.nsIWebNavigation);
     this.docShell.loadURI(url, opts);
 
     return {frameId: "42"};
   }
 
+  getFrameTree() {
+    return {
+      frameTree: {
+        frame: {
+          // id, parentId
+        },
+        childFrames: [],
+      },
+    };
+  }
+
+  setLifecycleEventsEnabled() {}
+  addScriptToEvaluateOnNewDocument() {}
+  createIsolatedWorld() {}
+
   url() {
     return this.content.location.href;
   }
 
   handleEvent({type}) {
     const timestamp = Date.now();
 
     switch (type) {
     case "DOMContentLoaded":
       this.emit("Page.domContentEventFired", {timestamp});
       break;
 
     case "pageshow":
       this.emit("Page.loadEventFired", {timestamp});
+      // XXX this should most likely be sent differently
+      this.emit("Page.navigatedWithinDocument", {timestamp});
+      this.emit("Page.frameStoppedLoading", {timestamp});
       break;
     }
   }
 }
 
 function transitionToLoadFlag(transitionType) {
   switch (transitionType) {
   case "reload":
--- a/remote/domains/parent/Target.jsm
+++ b/remote/domains/parent/Target.jsm
@@ -2,19 +2,88 @@
  * 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 = ["Target"];
 
 const {Domain} = ChromeUtils.import("chrome://remote/content/domains/Domain.jsm");
+const {TabManager} = ChromeUtils.import("chrome://remote/content/WindowManager.jsm");
+const {TabSession} = ChromeUtils.import("chrome://remote/content/sessions/TabSession.jsm");
+
+let sessionIds = 1;
 
 class Target extends Domain {
+  constructor(session) {
+    super(session);
+
+    this.onTargetCreated = this.onTargetCreated.bind(this);
+  }
+
   getBrowserContexts() {
     return {
       browserContextIds: [],
     };
   }
 
   setDiscoverTargets({ discover }) {
+    const { targets } = this.session.target;
+    if (discover) {
+      targets.on("connect", this.onTargetCreated);
+    } else {
+      targets.off("connect", this.onTargetCreated);
+    }
   }
+
+  onTargetCreated(eventName, target) {
+    this.emit("Target.targetCreated", {
+      targetInfo: {
+        browserContextId: target.id,
+        targetId: target.id,
+        type: "page",
+      },
+    });
+  }
+
+  async createTarget(a, b) {
+    const tab = TabManager.addTab();
+    const browser = tab.linkedBrowser;
+
+    // Wait for the related target to be created
+    const target = await new Promise(resolve => {
+      const { targets } = this.session.target;
+      const listener = (eventName, target) => {
+        if (target.browser == browser) {
+          targets.off("connect", listener);
+          resolve(target);
+        }
+      };
+      targets.on("connect", listener);
+    });
+
+    return {
+      targetId: target.id,
+    };
+  }
+
+  attachToTarget({ targetId }) {
+    const { targets } = this.session.target;
+    const target = targets.getById(targetId);
+    if (!target) {
+      return new Error(`Unable to find target with id '${targetId}'`);
+    }
+
+    const session = new TabSession(this.session.connection, target, sessionIds++);
+    this.emit("Target.attachedToTarget", {
+      targetInfo: {
+        type: "page",
+      },
+      sessionId: session.id,
+    });
+
+    return {
+      sessionId: session.id,
+    };
+  }
+
+  setAutoAttach() {}
 }
--- a/remote/targets/Targets.jsm
+++ b/remote/targets/Targets.jsm
@@ -60,16 +60,25 @@ class Targets {
     }
   }
 
   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);
     }