Merge mozilla-central to autoland. a=merge CLOSED TREE
authorGurzau Raul <rgurzau@mozilla.com>
Mon, 04 Mar 2019 00:09:40 +0200
changeset 520068 f9fb3fea307246f272cce497d34e818f060b5515
parent 520067 eb002115d6be1e8a06b3f97c717b90a55e39c441 (current diff)
parent 520066 0d261741c46150c46144dd5ef382d57e40e45279 (diff)
child 520069 ab0fd75fd14200dc24e08d6c5b67e044c09bd59f
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
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland. a=merge CLOSED TREE
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)) {