Merge inbound to mozilla-central. a=merge
authorGurzau Raul <rgurzau@mozilla.com>
Sun, 03 Mar 2019 23:57:22 +0200
changeset 520066 0d261741c46150c46144dd5ef382d57e40e45279
parent 520059 325cacd860797a527eddfb6652984169a3882523 (current diff)
parent 520065 faddaf06f46c8452a6c3b2358940d2c23fa137b2 (diff)
child 520068 f9fb3fea307246f272cce497d34e818f060b5515
child 520078 390f162b7d9b04c1c0337aac1149303daad52e02
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.0a1
first release with
nightly linux32
0d261741c461 / 67.0a1 / 20190303215808 / files
nightly linux64
0d261741c461 / 67.0a1 / 20190303215808 / files
nightly mac
0d261741c461 / 67.0a1 / 20190303215808 / files
nightly win32
0d261741c461 / 67.0a1 / 20190303215808 / files
nightly win64
0d261741c461 / 67.0a1 / 20190303215808 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
dom/tests/browser/shared_worker.js
--- a/devtools/client/debugger/new/src/client/firefox/commands.js
+++ b/devtools/client/debugger/new/src/client/firefox/commands.js
@@ -34,16 +34,18 @@ import type {
 let workerClients: Object;
 let threadClient: ThreadClient;
 let tabTarget: TabTarget;
 let debuggerClient: DebuggerClient;
 let sourceActors: { [ActorId]: SourceId };
 let breakpoints: { [string]: Object };
 let supportsWasm: boolean;
 
+let shouldWaitForWorkers = false;
+
 type Dependencies = {
   threadClient: ThreadClient,
   tabTarget: TabTarget,
   debuggerClient: DebuggerClient,
   supportsWasm: boolean
 };
 
 function setupCommands(dependencies: Dependencies) {
@@ -84,16 +86,20 @@ function lookupThreadClient(thread: stri
 
 function lookupConsoleClient(thread: string) {
   if (thread == threadClient.actor) {
     return tabTarget.activeConsole;
   }
   return workerClients[thread].console;
 }
 
+function listWorkerThreadClients() {
+  return (Object.values(workerClients): any).map(({ thread }) => thread);
+}
+
 function resume(thread: string): Promise<*> {
   return new Promise(resolve => {
     lookupThreadClient(thread).resume(resolve);
   });
 }
 
 function stepIn(thread: string): Promise<*> {
   return new Promise(resolve => {
@@ -161,42 +167,58 @@ function removeXHRBreakpoint(path: strin
 
 // Get the string key to use for a breakpoint location.
 // See also duplicate code in breakpoint-actor-map.js :(
 function locationKey(location) {
   const { sourceUrl, sourceId, line, column } = location;
   return `${(sourceUrl: any)}:${(sourceId: any)}:${line}:${(column: any)}`;
 }
 
+function waitForWorkers(shouldWait: boolean) {
+  shouldWaitForWorkers = shouldWait;
+}
+
 async function setBreakpoint(
   location: BreakpointLocation,
   options: BreakpointOptions
 ) {
   breakpoints[locationKey(location)] = { location, options };
   await threadClient.setBreakpoint(location, options);
 
   // Set breakpoints in other threads as well, but do not wait for the requests
   // to complete, so that we don't get hung up if one of the threads stops
   // responding. We don't strictly need to wait for the main thread to finish
   // setting its breakpoint, but this leads to more consistent behavior if the
   // user sets a breakpoint and immediately starts interacting with the page.
   // If the main thread stops responding then we're toast regardless.
-  for (const { thread } of (Object.values(workerClients): any)) {
-    thread.setBreakpoint(location, options);
+  if (shouldWaitForWorkers) {
+    for (const thread of listWorkerThreadClients()) {
+      await thread.setBreakpoint(location, options);
+    }
+  } else {
+    for (const thread of listWorkerThreadClients()) {
+      thread.setBreakpoint(location, options);
+    }
   }
 }
 
 async function removeBreakpoint(location: BreakpointLocation) {
   delete breakpoints[locationKey(location)];
   await threadClient.removeBreakpoint(location);
 
   // Remove breakpoints without waiting for the thread to respond, for the same
   // reason as in setBreakpoint.
-  for (const { thread } of (Object.values(workerClients): any)) {
-    thread.removeBreakpoint(location);
+  if (shouldWaitForWorkers) {
+    for (const thread of listWorkerThreadClients()) {
+      await thread.removeBreakpoint(location);
+    }
+  } else {
+    for (const thread of listWorkerThreadClients()) {
+      thread.removeBreakpoint(location);
+    }
   }
 }
 
 async function evaluateInFrame(script: Script, options: EvaluateParam) {
   return evaluate(script, options);
 }
 
 async function evaluateExpressions(scripts: Script[], options: EvaluateParam) {
@@ -444,12 +466,13 @@ const clientCommands = {
   getFrameScopes,
   pauseOnExceptions,
   fetchSources,
   registerSourceActor,
   fetchWorkers,
   getMainThread,
   sendPacket,
   setSkipPausing,
-  setEventListenerBreakpoints
+  setEventListenerBreakpoints,
+  waitForWorkers
 };
 
 export { setupCommands, clientCommands };
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-windowless-workers-early-breakpoint.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-windowless-workers-early-breakpoint.js
@@ -4,16 +4,21 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test that breakpoints at worker startup are hit when using windowless workers.
 add_task(async function() {
   const dbg = await initDebugger("doc-windowless-workers-early-breakpoint.html", "simple-worker.js");
 
   const workerSource = findSource(dbg, "simple-worker.js");
 
+  // NOTE: by default we do not wait on worker
+  // commands to complete because the thread could be
+  // shutting down.
+  dbg.client.waitForWorkers(true);
+
   await addBreakpoint(dbg, workerSource, 1);
   invokeInTab("startWorker");
   await waitForPaused(dbg, "simple-worker.js");
 
   // We should be paused at the first line of simple-worker.js
   assertPausedAtSourceAndLine(dbg, workerSource.id, 1);
   await removeBreakpoint(dbg, workerSource.id, 1);
   await resume(dbg);
--- a/devtools/client/debugger/new/test/mochitest/browser_dbg-windowless-workers.js
+++ b/devtools/client/debugger/new/test/mochitest/browser_dbg-windowless-workers.js
@@ -27,16 +27,21 @@ function getValue(dbg, index) {
 // Test basic windowless worker functionality: the main thread and worker can be
 // separately controlled from the same debugger.
 add_task(async function() {
   await pushPref("devtools.debugger.features.windowless-workers", true);
 
   const dbg = await initDebugger("doc-windowless-workers.html");
   const mainThread = dbg.toolbox.threadClient.actor;
 
+  // NOTE: by default we do not wait on worker
+  // commands to complete because the thread could be
+  // shutting down.
+  dbg.client.waitForWorkers(true);
+
   const workers = await getWorkers(dbg);
   ok(workers.length == 2, "Got two workers");
   const worker1Thread = workers[0].actor;
   const worker2Thread = workers[1].actor;
 
   const mainThreadSource = findSource(dbg, "doc-windowless-workers.html");
   const workerSource = findSource(dbg, "simple-worker.js");
 
--- a/devtools/client/debugger/new/test/mochitest/helpers.js
+++ b/devtools/client/debugger/new/test/mochitest/helpers.js
@@ -519,16 +519,18 @@ function clearDebuggerPreferences() {
  * @param {String} url
  * @return {Promise} dbg
  * @static
  */
 async function initDebugger(url, ...sources) {
   clearDebuggerPreferences();
   const toolbox = await openNewTabAndToolbox(EXAMPLE_URL + url, "jsdebugger");
   const dbg = createDebuggerContext(toolbox);
+  dbg.client.waitForWorkers(false);
+
   await waitForSources(dbg, ...sources);
   return dbg;
 }
 
 async function initPane(url, pane) {
   clearDebuggerPreferences();
   return openNewTabAndToolbox(EXAMPLE_URL + url, pane);
 }
--- a/devtools/server/actors/object/previewers.js
+++ b/devtools/server/actors/object/previewers.js
@@ -124,34 +124,35 @@ const previewers = {
     if (hooks.getGripDepth() > 1) {
       return true;
     }
 
     const raw = obj.unsafeDereference();
     const items = grip.preview.items = [];
 
     for (let i = 0; i < length; ++i) {
-      if (raw) {
+      if (raw && !isWorker) {
         // Array Xrays filter out various possibly-unsafe properties (like
         // functions, and claim that the value is undefined instead. This
         // is generally the right thing for privileged code accessing untrusted
         // objects, but quite confusing for Object previews. So we manually
         // override this protection by waiving Xrays on the array, and re-applying
         // Xrays on any indexed value props that we pull off of it.
         const desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(raw), i);
         if (desc && !desc.get && !desc.set) {
           let value = Cu.unwaiveXrays(desc.value);
           value = ObjectUtils.makeDebuggeeValueIfNeeded(obj, value);
           items.push(hooks.createValueGrip(value));
         } else {
           items.push(null);
         }
       } else {
-        // When recording/replaying we don't have a raw object, but also don't
-        // need to deal with Xrays into the debuggee compartment.
+        // Workers do not have access to Cu, and when recording/replaying we
+        // don't have a raw object. In either case we do not need to deal with
+        // xray wrappers.
         const value = DevToolsUtils.getProperty(obj, i);
         items.push(hooks.createValueGrip(value));
       }
 
       if (items.length == OBJECT_PREVIEW_MAX_ITEMS) {
         break;
       }
     }
@@ -452,18 +453,19 @@ previewers.Object = [
 
     if (hooks.getGripDepth() > 1) {
       return true;
     }
 
     const raw = obj.unsafeDereference();
 
     // The raw object will be null/unavailable when interacting with a
-    // replaying execution.
-    if (raw) {
+    // replaying execution, and Cu is unavailable in workers. In either case we
+    // do not need to worry about xrays.
+    if (raw && !isWorker) {
       const global = Cu.getGlobalForObject(DebuggerServer);
       const classProto = global[obj.class].prototype;
       // The Xray machinery for TypedArrays denies indexed access on the grounds
       // that it's slow, and advises callers to do a structured clone instead.
       const safeView = Cu.cloneInto(classProto.subarray.call(raw, 0,
         OBJECT_PREVIEW_MAX_ITEMS), global);
       const items = grip.preview.items = [];
       for (let i = 0; i < safeView.length; i++) {
--- a/devtools/server/actors/object/property-iterator.js
+++ b/devtools/server/actors/object/property-iterator.js
@@ -108,18 +108,21 @@ const PropertyIteratorActor  = protocol.
     return this.slice({ start: 0, count: this.iterator.size });
   },
 });
 
 /**
  * Helper function to create a grip from a Map/Set entry
  */
 function gripFromEntry({ obj, hooks }, entry) {
+  if (!isWorker) {
+    entry = Cu.unwaiveXrays(entry);
+  }
   return hooks.createValueGrip(
-    ObjectUtils.makeDebuggeeValueIfNeeded(obj, Cu.unwaiveXrays(entry)));
+    ObjectUtils.makeDebuggeeValueIfNeeded(obj, entry));
 }
 
 function enumArrayProperties(objectActor, options) {
   return {
     size: ObjectUtils.getArrayLength(objectActor.obj),
     propertyName(index) {
       return index;
     },
@@ -246,33 +249,47 @@ function enumMapEntries(objectActor) {
   // Arrays, the semantics often deny access to the entires based on the
   // nature of the values. So we need waive Xrays for the iterator object
   // and the tupes, and then re-apply them on the underlying values until
   // we fix bug 1023984.
   //
   // Even then though, we might want to continue waiving Xrays here for the
   // same reason we do so for Arrays above - this filtering behavior is likely
   // to be more confusing than beneficial in the case of Object previews.
-  const raw = objectActor.obj.unsafeDereference();
+  let keys, getValue;
+  if (isWorker) {
+    const keysIterator = DevToolsUtils.callPropertyOnObject(objectActor.obj, "keys");
+    keys = [...DevToolsUtils.makeDebuggeeIterator(keysIterator)];
+    const valuesIterator = DevToolsUtils.callPropertyOnObject(objectActor.obj, "values");
+    const values = [...DevToolsUtils.makeDebuggeeIterator(valuesIterator)];
+    const map = new Map();
+    for (let i = 0; i < keys.length; i++) {
+      map.set(keys[i], values[i]);
+    }
+    getValue = key => map.get(key);
+  } else {
+    const raw = objectActor.obj.unsafeDereference();
+    keys = [...Cu.waiveXrays(Map.prototype.keys.call(raw))];
+    getValue = key => Map.prototype.get.call(raw, key);
+  }
 
-  const keys = [...Cu.waiveXrays(Map.prototype.keys.call(raw))];
   return {
     [Symbol.iterator]: function* () {
       for (const key of keys) {
-        const value = Map.prototype.get.call(raw, key);
+        const value = getValue(key);
         yield [ key, value ].map(val => gripFromEntry(objectActor, val));
       }
     },
     size: keys.length,
     propertyName(index) {
       return index;
     },
     propertyDescription(index) {
       const key = keys[index];
-      const val = Map.prototype.get.call(raw, key);
+      const val = getValue(key);
       return {
         enumerable: true,
         value: {
           type: "mapEntry",
           preview: {
             key: gripFromEntry(objectActor, key),
             value: gripFromEntry(objectActor, val),
           },
@@ -369,18 +386,24 @@ function enumSetEntries(objectActor) {
   // compartment. However, we _do_ have Xrays to Object now, so we end up
   // Xraying those temporary objects, and filtering access to |it.value|
   // based on whether or not it's Xrayable and/or callable, which breaks
   // the for/of iteration.
   //
   // This code is designed to handle untrusted objects, so we can safely
   // waive Xrays on the iterable, and relying on the Debugger machinery to
   // make sure we handle the resulting objects carefully.
-  const raw = objectActor.obj.unsafeDereference();
-  const values = [...Cu.waiveXrays(Set.prototype.values.call(raw))];
+  let values;
+  if (isWorker) {
+    const iterator = DevToolsUtils.callPropertyOnObject(objectActor.obj, "values");
+    values = [...DevToolsUtils.makeDebuggeeIterator(iterator)];
+  } else {
+    const raw = objectActor.obj.unsafeDereference();
+    values = [...Cu.waiveXrays(Set.prototype.values.call(raw))];
+  }
 
   return {
     [Symbol.iterator]: function* () {
       for (const item of values) {
         yield gripFromEntry(objectActor, item);
       }
     },
     size: values.length,
--- a/devtools/server/actors/object/utils.js
+++ b/devtools/server/actors/object/utils.js
@@ -166,16 +166,22 @@ function getArrayLength(object) {
   // Real arrays have a reliable `length` own property.
   if (object.class === "Array") {
     return DevToolsUtils.getProperty(object, "length");
   }
 
   // For typed arrays, `DevToolsUtils.getProperty` is not reliable because the `length`
   // getter could be shadowed by an own property, and `getOwnPropertyNames` is
   // unnecessarily slow. Obtain the `length` getter safely and call it manually.
+  if (isWorker) {
+    // Workers can't wrap debugger values into debuggees, so do the calculations
+    // in the debuggee itself.
+    const getter = object.proto.proto.getOwnPropertyDescriptor("length").get;
+    return getter.call(object).return;
+  }
   const typedProto = Object.getPrototypeOf(Uint8Array.prototype);
   const getter = Object.getOwnPropertyDescriptor(typedProto, "length").get;
   return getter.call(object.unsafeDereference());
 }
 
 /**
  * Returns true if the parameter is suitable to be an array index.
  *
--- a/devtools/server/tests/unit/test_objectgrips-20.js
+++ b/devtools/server/tests/unit/test_objectgrips-20.js
@@ -140,17 +140,30 @@ add_task(threadClientTest(async ({ threa
     expectedNonIndexedProperties: [
       ["foo", "bar"],
       ["bar", "foo"],
       ["length", 2],
       ["buffer", DO_NOT_CHECK_VALUE],
       ["byteLength", 2],
       ["byteOffset", 0],
     ],
-  }, {
+  }];
+
+  for (const test of testCases) {
+    await test_object_grip(debuggee, client, threadClient, test);
+  }
+}));
+
+// These tests are not yet supported in workers.
+add_task(threadClientTest(async ({ threadClient, debuggee, client }) => {
+  debuggee.eval(function stopMe(arg1) {
+    debugger;
+  }.toString());
+
+  const testCases = [{
     evaledObject: `(() => {
       x = new Int8Array([1, 2]);
       Object.defineProperty(x, 'length', {value: 0});
       return x;
     })()`,
     expectedIndexedProperties: [["0", 1], ["1", 2]],
     expectedNonIndexedProperties: [
       ["length", 0],
@@ -166,17 +179,17 @@ add_task(threadClientTest(async ({ threa
     })()`,
     expectedIndexedProperties: [["0", 1], ["1", 2]],
     expectedNonIndexedProperties: [],
   }];
 
   for (const test of testCases) {
     await test_object_grip(debuggee, client, threadClient, test);
   }
-}));
+}, { doNotRunWorker: true }));
 
 async function test_object_grip(debuggee, dbgClient, threadClient, testData = {}) {
   const {
     evaledObject,
     expectedIndexedProperties,
     expectedNonIndexedProperties,
   } = testData;
 
--- a/devtools/shared/DevToolsUtils.js
+++ b/devtools/shared/DevToolsUtils.js
@@ -793,8 +793,22 @@ function callPropertyOnObject(object, na
   }
   if ("throw" in result) {
     throw result.throw;
   }
   return result.return;
 }
 
 exports.callPropertyOnObject = callPropertyOnObject;
+
+// Convert a Debugger.Object wrapping an iterator into an iterator in the
+// debugger's realm.
+function* makeDebuggeeIterator(object) {
+  while (true) {
+    const nextValue = callPropertyOnObject(object, "next");
+    if (exports.getProperty(nextValue, "done")) {
+      break;
+    }
+    yield exports.getProperty(nextValue, "value");
+  }
+}
+
+exports.makeDebuggeeIterator = makeDebuggeeIterator;
--- a/dom/tests/browser/perfmetrics/browser.ini
+++ b/dom/tests/browser/perfmetrics/browser.ini
@@ -5,16 +5,17 @@ prefs =
 
 support-files =
   dummy.html
   ping_worker.html
   ping_worker2.html
   ping_worker.js
   setinterval.html
   settimeout.html
+  shared_worker.js
   unresponsive.html
   hello.ogg
   sound.html
 
 [browser_test_performance_metrics.js]
 skip-if = verify
 
 [browser_test_unresponsive.js]
--- a/dom/tests/browser/perfmetrics/browser_test_performance_metrics.js
+++ b/dom/tests/browser/perfmetrics/browser_test_performance_metrics.js
@@ -133,19 +133,17 @@ add_task(async function test() {
     Assert.ok(workerDuration > 0, "Worker duration should be positive");
     Assert.ok(workerTotal > 0, "Worker count should be positive");
     Assert.ok(duration > 0, "Duration should be positive");
     Assert.ok(total > 0, "Should get a positive count");
     Assert.ok(parentProcessEvent, "parent process sent back some events");
     Assert.ok(isTopLevel, "example.com as a top level window");
     Assert.ok(aboutMemoryFound, "about:memory");
     Assert.ok(heapUsage > 0, "got some memory value reported");
-    // FIXME bug 1522246. the worker performance improvements in this bug cause
-    // the shared worker to shut down too quickly to report any info.
-    // Assert.ok(sharedWorker, "We got some info from a shared worker");
+    Assert.ok(sharedWorker, "We got some info from a shared worker");
     let numCounters = counterIds.length;
     Assert.ok(numCounters > 5, "This test generated at least " + numCounters + " unique counters");
 
     // checking that subframes are not orphans
     for (let frameId of subFrameIds) {
       Assert.ok(topLevelIds.includes(frameId), "subframe is not orphan ");
     }
 
--- a/dom/tests/browser/perfmetrics/ping_worker2.html
+++ b/dom/tests/browser/perfmetrics/ping_worker2.html
@@ -4,17 +4,17 @@
   <meta charset="utf-8">
   <script type="text/javascript">
 
   var shared;
 
   function init() {
    shared = new SharedWorker("shared_worker.js");
    shared.port.start();
-   for (let i = 0; i++; i < 10) shared.port.postMessage(["ok"]);
+   for (let i = 0; i < 10; i++) shared.port.postMessage(["ok"]);
   }
 
   </script>
 </head>
 <body onload="init()">
   <h1>A page with a shared worker</h1>
 </body>
 </html>
rename from dom/tests/browser/shared_worker.js
rename to dom/tests/browser/perfmetrics/shared_worker.js
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -817,17 +817,18 @@ static bool PreserveWrapper(JSContext* c
 
   return mozilla::dom::TryPreserveWrapper(obj);
 }
 
 JSObject* Wrap(JSContext* cx, JS::HandleObject existing, JS::HandleObject obj) {
   JSObject* targetGlobal = JS::CurrentGlobalOrNull(cx);
   if (!IsWorkerDebuggerGlobal(targetGlobal) &&
       !IsWorkerDebuggerSandbox(targetGlobal)) {
-    MOZ_CRASH("There should be no edges from the debuggee to the debugger.");
+    JS_ReportErrorASCII(cx, "There should be no edges from the debuggee to the debugger.");
+    return nullptr;
   }
 
   // Note: the JS engine unwraps CCWs before calling this callback.
   JSObject* originGlobal = JS::GetNonCCWObjectGlobal(obj);
 
   const js::Wrapper* wrapper = nullptr;
   if (IsWorkerDebuggerGlobal(originGlobal) ||
       IsWorkerDebuggerSandbox(originGlobal)) {