Bug 1594739 - Test workers listening via the TargetList API. r=jdescottes
authorAlexandre Poirot <poirot.alex@gmail.com>
Thu, 12 Dec 2019 12:33:45 +0000
changeset 506672 3d0a1659b1605b45cdd6b09a6babeed2a484a5ee
parent 506671 68b0102b619ea3fa5eb5373a152f3a924e85576f
child 506673 fdc4a588912eeb3ec971f9f947935346fa852311
push id36910
push usercsabou@mozilla.com
push dateThu, 12 Dec 2019 21:50:40 +0000
treeherdermozilla-central@0f6958f49842 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdescottes
bugs1594739
milestone73.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 1594739 - Test workers listening via the TargetList API. r=jdescottes Differential Revision: https://phabricator.services.mozilla.com/D55189
devtools/shared/resources/target-list.js
devtools/shared/resources/tests/browser.ini
devtools/shared/resources/tests/browser_target_list_workers.js
devtools/shared/resources/tests/fission_document.html
devtools/shared/resources/tests/test_worker.js
--- a/devtools/shared/resources/target-list.js
+++ b/devtools/shared/resources/target-list.js
@@ -206,16 +206,21 @@ class TargetList {
       ),
       worker: new LegacyImplementationWorkers(
         this.rootFront,
         this.targetFront,
         this._onTargetAvailable,
         this._onTargetDestroyed
       ),
     };
+
+    // Public flag to allow listening for workers even if the fission pref is off
+    // This allows listening for workers in the content toolbox outside of fission contexts
+    // For now, this is only toggled by tests.
+    this.listenForWorkers = false;
   }
 
   _fissionEnabled() {
     const fissionBrowserToolboxEnabled = Services.prefs.getBoolPref(
       BROWSERTOOLBOX_FISSION_ENABLED
     );
     // For now, only enable additional targets when:
     // - browser toolbox's fission pref is turned on, and,
@@ -290,17 +295,21 @@ class TargetList {
   async startListening(types) {
     for (const type of types) {
       if (this._isListening(type)) {
         continue;
       }
       this._setListening(type, true);
 
       // We only listen for additional target when the fission pref is turned on.
-      if (!this._fissionEnabled()) {
+      // Or we we explicitely ask for workers without the fission pref.
+      if (
+        !this._fissionEnabled() &&
+        !(type == "worker" && this.listenForWorkers)
+      ) {
         continue;
       }
       if (this.legacyImplementation[type]) {
         await this.legacyImplementation[type].listen();
       } else {
         // TO BE IMPLEMENTED via this.targetFront.watchFronts(type)
         // For now we always go throught "legacy" codepath.
         throw new Error(`Unsupported target type '${type}'`);
@@ -309,18 +318,23 @@ class TargetList {
   }
 
   stopListening(types) {
     for (const type of types) {
       if (!this._isListening(type)) {
         continue;
       }
       this._setListening(type, false);
+
       // We only listen for additional target when the fission pref is turned on.
-      if (!this._fissionEnabled()) {
+      // Or we we explicitely ask for workers without the fission pref.
+      if (
+        !this._fissionEnabled() &&
+        !(type == "worker" && this.listenForWorkers)
+      ) {
         continue;
       }
       if (this.legacyImplementation[type]) {
         this.legacyImplementation[type].unlisten();
       } else {
         // TO BE IMPLEMENTED via this.targetFront.unwatchFronts(type)
         // For now we always go throught "legacy" codepath.
         throw new Error(`Unsupported target type '${type}'`);
--- a/devtools/shared/resources/tests/browser.ini
+++ b/devtools/shared/resources/tests/browser.ini
@@ -2,14 +2,16 @@
 tags = devtools
 subsuite = devtools
 support-files =
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/shared/test/telemetry-test-helpers.js
   head.js
   fission_document.html
   fission_iframe.html
+  test_worker.js
 
 [browser_target_list_frames.js]
 [browser_target_list_preffedoff.js]
 [browser_target_list_processes.js]
 [browser_target_list_switchToTarget.js]
 [browser_target_list_watchTargets.js]
+[browser_target_list_workers.js]
new file mode 100644
--- /dev/null
+++ b/devtools/shared/resources/tests/browser_target_list_workers.js
@@ -0,0 +1,165 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test the TargetList API around workers
+
+const { TargetList } = require("devtools/shared/resources/target-list");
+
+const FISSION_TEST_URL = URL_ROOT + "fission_document.html";
+const CHROME_WORKER_URL = CHROME_URL_ROOT + "test_worker.js";
+const WORKER_URL = URL_ROOT + "test_worker.js";
+
+add_task(async function() {
+  // Enabled fission's pref as the TargetList is almost disabled without it
+  await pushPref("devtools.browsertoolbox.fission", true);
+
+  const client = await createLocalClient();
+  const mainRoot = client.mainRoot;
+
+  await testBrowserWorkers(mainRoot);
+  await testTabWorkers(mainRoot);
+
+  await client.close();
+});
+
+async function testBrowserWorkers(mainRoot) {
+  info("Test TargetList against workers via the parent process target");
+
+  // Instantiate a worker in the parent process
+  // eslint-disable-next-line no-unused-vars
+  const worker = new Worker(CHROME_WORKER_URL);
+
+  const target = await mainRoot.getMainProcess();
+  const targetList = new TargetList(mainRoot, target);
+  await targetList.startListening([TargetList.TYPES.WORKER]);
+
+  // Very naive sanity check against getAllTargets(worker)
+  const workers = await targetList.getAllTargets(TargetList.TYPES.WORKER);
+  const hasWorker = workers.find(workerTarget => {
+    return workerTarget.url == CHROME_WORKER_URL;
+  });
+  ok(hasWorker, "retrieve the target for the worker");
+
+  // Check that calling getAllTargets(worker) return the same target instances
+  const workers2 = await targetList.getAllTargets(TargetList.TYPES.WORKER);
+  is(workers2.length, workers.length, "retrieved the same number of workers");
+
+  function sortFronts(f1, f2) {
+    return f1.actorID < f2.actorID;
+  }
+  workers.sort(sortFronts);
+  workers2.sort(sortFronts);
+  for (let i = 0; i < workers.length; i++) {
+    is(workers[i], workers2[i], `worker ${i} targets are the same`);
+  }
+
+  // Assert that watchTargets will call the create callback for all existing workers
+  const targets = [];
+  const onAvailable = ({ type, targetFront, isTopLevel }) => {
+    is(
+      type,
+      TargetList.TYPES.WORKER,
+      "We are only notified about worker targets"
+    );
+    ok(
+      targetFront == target ? isTopLevel : !isTopLevel,
+      "isTopLevel argument is correct"
+    );
+    targets.push(targetFront);
+  };
+  await targetList.watchTargets([TargetList.TYPES.WORKER], onAvailable);
+  is(
+    targets.length,
+    workers.length,
+    "retrieved the same number of workers via watchTargets"
+  );
+
+  workers.sort(sortFronts);
+  targets.sort(sortFronts);
+  for (let i = 0; i < workers.length; i++) {
+    is(
+      workers[i],
+      targets[i],
+      `worker ${i} targets are the same via watchTargets`
+    );
+  }
+  targetList.unwatchTargets([TargetList.TYPES.WORKER], onAvailable);
+
+  // Create a new worker and see if the worker target is reported
+  const onWorkerCreated = new Promise(resolve => {
+    const onAvailable2 = ({ type, targetFront, isTopLevel }) => {
+      if (targets.includes(targetFront)) {
+        return;
+      }
+      targetList.unwatchTargets([TargetList.TYPES.WORKER], onAvailable2);
+      resolve(targetFront);
+    };
+    targetList.watchTargets([TargetList.TYPES.WORKER], onAvailable2);
+  });
+  // eslint-disable-next-line no-unused-vars
+  const worker2 = new Worker(CHROME_WORKER_URL + "#second");
+  const workerTarget = await onWorkerCreated;
+
+  is(
+    workerTarget.url,
+    CHROME_WORKER_URL + "#second",
+    "This worker target is about the new worker"
+  );
+
+  const workers3 = await targetList.getAllTargets(TargetList.TYPES.WORKER);
+  const hasWorker2 = workers3.find(
+    worderTarget => workerTarget.url == CHROME_WORKER_URL + "#second"
+  );
+  ok(hasWorker2, "retrieve the target for tab via getAllTargets");
+
+  targetList.stopListening([TargetList.TYPES.WORKER]);
+}
+
+async function testTabWorkers(mainRoot) {
+  info("Test TargetList against workers via a tab target");
+
+  // Create a TargetList for a given test tab
+  gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+  const tab = await addTab(FISSION_TEST_URL);
+  const target = await mainRoot.getTab({ tab });
+
+  // Ensure attaching the target as BrowsingContextTargetActor.listWorkers
+  // assert that the target actor is attached.
+  // It isn't clear if this assertion is meaningful?
+  await target.attach();
+
+  const targetList = new TargetList(mainRoot, target);
+
+  // Workaround to allow listening for workers in the content toolbox
+  // without the fission preferences
+  targetList.listenForWorkers = true;
+
+  await targetList.startListening([TargetList.TYPES.WORKER]);
+
+  // Check that calling getAllTargets(workers) return the same target instances
+  const workers = await targetList.getAllTargets(TargetList.TYPES.WORKER);
+  is(workers.length, 1, "retrieved the worker");
+  is(workers[0].url, WORKER_URL, "The first worker is the page worker");
+
+  // Assert that watchTargets will call the create callback for all existing workers
+  const targets = [];
+  const onAvailable = ({ type, targetFront, isTopLevel }) => {
+    is(
+      type,
+      TargetList.TYPES.WORKER,
+      "We are only notified about worker targets"
+    );
+    ok(!isTopLevel, "The workers are never top level");
+    targets.push(targetFront);
+  };
+  await targetList.watchTargets([TargetList.TYPES.WORKER], onAvailable);
+  is(targets.length, 1, "retrieved just the worker");
+  is(targets[0], workers[0], "Got the exact same target front");
+  targetList.unwatchTargets([TargetList.TYPES.WORKER], onAvailable);
+
+  targetList.stopListening([TargetList.TYPES.WORKER]);
+
+  BrowserTestUtils.removeTab(tab);
+}
--- a/devtools/shared/resources/tests/fission_document.html
+++ b/devtools/shared/resources/tests/fission_document.html
@@ -4,11 +4,16 @@
   <meta charset="utf8">
   <title>Test fission document</title>
   <!-- Any copyright is dedicated to the Public Domain.
      - http://creativecommons.org/publicdomain/zero/1.0/ -->
 </head>
 <body>
 <p>Test fission iframe</p>
 
-<iframe src="https://example.com/browser/devtools/shared/resources/tests/fission_iframe.html"></iframe>
+<iframe src="http://example.com/browser/devtools/shared/resources/tests/fission_iframe.html"></iframe>
+<script>
+"use strict";
+// eslint-disable-next-line no-unused-vars
+const worker = new Worker("http://example.com/browser/devtools/shared/resources/tests/test_worker.js");
+</script>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/devtools/shared/resources/tests/test_worker.js
@@ -0,0 +1,5 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// We don't need any computation in the worker,
+// just it to be alive