Merge inbound to mozilla-central. a=merge
authorBrindusan Cristian <cbrindusan@mozilla.com>
Wed, 17 Apr 2019 12:37:47 +0300
changeset 469758 bbca68b2af262ffbbf2e3a2d2e77a16c999f479a
parent 469757 79e6ed0b08d67523705ca61981437874340a38f6 (current diff)
parent 469674 f236858f5add6ebbe570dba0289a2758da91d6b5 (diff)
child 469759 e3cd7a936c717b1a5791a78c0fe626f1fe8ee9de
child 469798 769b2f5bff8822bd8eae5e2ebca907853ee2c9c8
push id112816
push usercbrindusan@mozilla.com
push dateWed, 17 Apr 2019 09:52:45 +0000
treeherdermozilla-inbound@bbca68b2af26 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.0a1
first release with
nightly linux32
bbca68b2af26 / 68.0a1 / 20190417093858 / files
nightly linux64
bbca68b2af26 / 68.0a1 / 20190417093858 / files
nightly mac
bbca68b2af26 / 68.0a1 / 20190417093858 / files
nightly win32
bbca68b2af26 / 68.0a1 / 20190417093858 / files
nightly win64
bbca68b2af26 / 68.0a1 / 20190417093858 / 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
devtools/client/webconsole/test/mochitest/browser.ini
--- a/caps/nsJSPrincipals.cpp
+++ b/caps/nsJSPrincipals.cpp
@@ -109,17 +109,18 @@ bool nsJSPrincipals::ReadPrincipals(JSCo
   uint32_t tag;
   uint32_t unused;
   if (!JS_ReadUint32Pair(aReader, &tag, &unused)) {
     return false;
   }
 
   if (!(tag == SCTAG_DOM_NULL_PRINCIPAL || tag == SCTAG_DOM_SYSTEM_PRINCIPAL ||
         tag == SCTAG_DOM_CONTENT_PRINCIPAL ||
-        tag == SCTAG_DOM_EXPANDED_PRINCIPAL)) {
+        tag == SCTAG_DOM_EXPANDED_PRINCIPAL ||
+        tag == SCTAG_DOM_WORKER_PRINCIPAL)) {
     xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
     return false;
   }
 
   return ReadKnownPrincipalType(aCx, aReader, tag, aOutPrincipals);
 }
 
 static bool ReadPrincipalInfo(
@@ -281,31 +282,57 @@ static bool ReadPrincipalInfo(JSStructur
 #else
     MOZ_CRASH("unexpected principal structured clone tag");
 #endif
   }
 
   return true;
 }
 
+static StaticRefPtr<nsIPrincipal> sActiveWorkerPrincipal;
+
+nsJSPrincipals::AutoSetActiveWorkerPrincipal::AutoSetActiveWorkerPrincipal
+    (nsIPrincipal* aPrincipal) {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_RELEASE_ASSERT(!sActiveWorkerPrincipal);
+  sActiveWorkerPrincipal = aPrincipal;
+}
+
+nsJSPrincipals::AutoSetActiveWorkerPrincipal::~AutoSetActiveWorkerPrincipal() {
+  sActiveWorkerPrincipal = nullptr;
+}
+
 /* static */
 bool nsJSPrincipals::ReadKnownPrincipalType(JSContext* aCx,
                                             JSStructuredCloneReader* aReader,
                                             uint32_t aTag,
                                             JSPrincipals** aOutPrincipals) {
   MOZ_ASSERT(aTag == SCTAG_DOM_NULL_PRINCIPAL ||
              aTag == SCTAG_DOM_SYSTEM_PRINCIPAL ||
              aTag == SCTAG_DOM_CONTENT_PRINCIPAL ||
-             aTag == SCTAG_DOM_EXPANDED_PRINCIPAL);
+             aTag == SCTAG_DOM_EXPANDED_PRINCIPAL ||
+             aTag == SCTAG_DOM_WORKER_PRINCIPAL);
 
   if (NS_WARN_IF(!NS_IsMainThread())) {
     xpc::Throw(aCx, NS_ERROR_UNCATCHABLE_EXCEPTION);
     return false;
   }
 
+  if (aTag == SCTAG_DOM_WORKER_PRINCIPAL) {
+    // When reading principals which were written on a worker thread, we need to
+    // know the principal of the worker which did the write.
+    if (!sActiveWorkerPrincipal) {
+      xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
+      return false;
+    }
+    RefPtr<nsJSPrincipals> retval = get(sActiveWorkerPrincipal);
+    retval.forget(aOutPrincipals);
+    return true;
+  }
+
   PrincipalInfo info;
   if (!ReadPrincipalInfo(aReader, aTag, info)) {
     return false;
   }
 
   nsresult rv;
   nsCOMPtr<nsIPrincipal> prin = PrincipalInfoToPrincipal(info, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
--- a/caps/nsJSPrincipals.h
+++ b/caps/nsJSPrincipals.h
@@ -19,16 +19,27 @@ class nsJSPrincipals : public nsIPrincip
   static bool ReadPrincipals(JSContext* aCx, JSStructuredCloneReader* aReader,
                              JSPrincipals** aOutPrincipals);
 
   static bool ReadKnownPrincipalType(JSContext* aCx,
                                      JSStructuredCloneReader* aReader,
                                      uint32_t aTag,
                                      JSPrincipals** aOutPrincipals);
 
+  // This class is used on the main thread to specify which principal to use
+  // when reading principals data that was set on a DOM worker thread.
+  // DOM workers do not use principals from Gecko's point of view, and any
+  // JSPrincipals used internally will be a shared singleton object. When that
+  // singleton is written out and later read on the main thread, we substitute
+  // the principal specified with this class.
+  struct MOZ_RAII AutoSetActiveWorkerPrincipal {
+    explicit AutoSetActiveWorkerPrincipal(nsIPrincipal* aPrincipal);
+    ~AutoSetActiveWorkerPrincipal();
+  };
+
   bool write(JSContext* aCx, JSStructuredCloneWriter* aWriter) final;
 
   /*
    * Get a weak reference to nsIPrincipal associated with the given JS
    * principal, and vice-versa.
    */
   static nsJSPrincipals* get(JSPrincipals* principals) {
     nsJSPrincipals* self = static_cast<nsJSPrincipals*>(principals);
--- a/devtools/client/debugger/src/utils/editor/index.js
+++ b/devtools/client/debugger/src/utils/editor/index.js
@@ -32,17 +32,17 @@ export function getEditor() {
   return editor;
 }
 
 export function removeEditor() {
   editor = null;
 }
 
 function getCodeMirror() {
-  return editor && editor.codeMirror;
+  return editor && editor.hasCodeMirror ? editor.codeMirror : null;
 }
 
 export function startOperation() {
   const codeMirror = getCodeMirror();
   if (!codeMirror) {
     return;
   }
 
--- a/devtools/client/debugger/src/utils/prefs.js
+++ b/devtools/client/debugger/src/utils/prefs.js
@@ -4,16 +4,19 @@
 
 // @flow
 
 import { PrefsHelper } from "devtools-modules";
 import { isDevelopment } from "devtools-environment";
 import Services from "devtools-services";
 import { asyncStoreHelper } from "./asyncStoreHelper";
 
+// Schema version to bump when the async store format has changed incompatibly
+// and old stores should be cleared. This needs to match the prefs schema
+// version in devtools/client/preferences/debugger.js.
 const prefsSchemaVersion = "1.0.9";
 const pref = Services.pref;
 
 if (isDevelopment()) {
   pref("devtools.debugger.logging", false);
   pref("devtools.debugger.alphabetize-outline", false);
   pref("devtools.debugger.auto-pretty-print", false);
   pref("devtools.source-map.client-service.enabled", true);
@@ -38,17 +41,17 @@ if (isDevelopment()) {
   pref("devtools.debugger.ui.framework-grouping-on", true);
   pref("devtools.debugger.pending-selected-location", "{}");
   pref("devtools.debugger.expressions", "[]");
   pref("devtools.debugger.file-search-case-sensitive", false);
   pref("devtools.debugger.file-search-whole-word", false);
   pref("devtools.debugger.file-search-regex-match", false);
   pref("devtools.debugger.project-directory-root", "");
   pref("devtools.debugger.map-scopes-enabled", false);
-  pref("devtools.debugger.prefs-schema-version", "1.0.1");
+  pref("devtools.debugger.prefs-schema-version", prefsSchemaVersion);
   pref("devtools.debugger.skip-pausing", false);
   pref("devtools.debugger.features.workers", true);
   pref("devtools.debugger.features.async-stepping", true);
   pref("devtools.debugger.features.wasm", true);
   pref("devtools.debugger.features.shortcuts", true);
   pref("devtools.debugger.features.root", true);
   pref("devtools.debugger.features.map-scopes", true);
   pref("devtools.debugger.features.remove-command-bar-options", true);
--- a/devtools/client/debugger/test/mochitest/browser.ini
+++ b/devtools/client/debugger/test/mochitest/browser.ini
@@ -784,8 +784,9 @@ skip-if = os == "win"
 skip-if = os == "win"
 [browser_dbg-wasm-sourcemaps.js]
 skip-if = true
 [browser_dbg-windowless-workers.js]
 [browser_dbg-windowless-workers-early-breakpoint.js]
 [browser_dbg-event-handler.js]
 [browser_dbg-eval-throw.js]
 [browser_dbg-sourceURL-breakpoint.js]
+[browser_dbg-old-breakpoint.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-old-breakpoint.js
@@ -0,0 +1,90 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we show a breakpoint in the UI when there is an old pending
+// breakpoint with an invalid original location.
+add_task(async function() {
+  clearDebuggerPreferences();
+
+  const pending = {
+    bp1: {
+      location: {
+        sourceId: "",
+        sourceUrl: EXAMPLE_URL + "nowhere2.js",
+        line: 5
+      },
+      generatedLocation: {
+        sourceUrl: EXAMPLE_URL + "simple1.js",
+        line: 4
+      },
+      options: {},
+      disabled: false
+    },
+    bp2: {
+      location: {
+        sourceId: "",
+        sourceUrl: EXAMPLE_URL + "nowhere.js",
+        line: 5
+      },
+      generatedLocation: {
+        sourceUrl: EXAMPLE_URL + "simple3.js",
+        line: 2
+      },
+      options: {},
+      disabled: false
+    },
+  };
+  asyncStorage.setItem("debugger.pending-breakpoints", pending);
+
+  const toolbox = await openNewTabAndToolbox(EXAMPLE_URL + "doc-scripts.html", "jsdebugger");
+  const dbg = createDebuggerContext(toolbox);
+
+  // Pending breakpoints are installed asynchronously, keep invoking the entry
+  // function until the debugger pauses.
+  await waitUntil(() => {
+    invokeInTab("main");
+    return isPaused(dbg);
+  });
+
+  ok(true, "paused at unmapped breakpoint");
+  await waitForState(dbg, state => dbg.selectors.getBreakpointCount(state) == 2);
+  ok(true, "unmapped breakpoints shown in UI");
+});
+
+// Test that if we show a breakpoint with an old generated location, it is
+// removed after we load the original source and find the new generated
+// location.
+add_task(async function() {
+  clearDebuggerPreferences();
+
+  const pending = {
+    bp1: {
+      location: {
+        sourceId: "",
+        sourceUrl: "webpack:///entry.js",
+        line: 15,
+        column: 0
+      },
+      generatedLocation: {
+        sourceUrl: EXAMPLE_URL + "sourcemaps/bundle.js",
+        line: 47,
+        column: 16
+      },
+      astLocation: {},
+      options: {},
+      disabled: false
+    },
+  };
+  asyncStorage.setItem("debugger.pending-breakpoints", pending);
+
+  const toolbox = await openNewTabAndToolbox(EXAMPLE_URL + "doc-sourcemaps.html", "jsdebugger");
+  const dbg = createDebuggerContext(toolbox);
+
+  await waitForState(dbg, state => {
+    const bps = dbg.selectors.getBreakpointsList(state);
+    return bps.length == 1
+        && bps[0].location.sourceUrl.includes("entry.js")
+        && bps[0].location.line == 15;
+  });
+  ok(true, "removed old breakpoint during sync");
+});
--- a/devtools/client/preferences/debugger.js
+++ b/devtools/client/preferences/debugger.js
@@ -14,17 +14,18 @@ pref("devtools.debugger.pause-on-caught-
 pref("devtools.debugger.source-maps-enabled", true);
 pref("devtools.debugger.pretty-print-enabled", true);
 pref("devtools.debugger.alphabetize-outline", false);
 pref("devtools.debugger.auto-pretty-print", false);
 pref("devtools.debugger.auto-black-box", true);
 pref("devtools.debugger.workers", false);
 
 // The default Debugger UI settings
-pref("devtools.debugger.prefs-schema-version", "1.0.0");
+// This schema version needs to match that in devtools/client/debugger/src/utils/prefs.js.
+pref("devtools.debugger.prefs-schema-version", "1.0.9");
 pref("devtools.debugger.ui.panes-workers-and-sources-width", 200);
 pref("devtools.debugger.ui.panes-instruments-width", 300);
 pref("devtools.debugger.ui.panes-visible-on-startup", false);
 pref("devtools.debugger.ui.variables-sorting-enabled", true);
 pref("devtools.debugger.ui.variables-only-enum-visible", false);
 pref("devtools.debugger.ui.variables-searchbox-visible", false);
 pref("devtools.debugger.ui.framework-grouping-on", true);
 pref("devtools.debugger.ui.editor-wrapping", false);
--- a/devtools/client/shared/sourceeditor/editor.js
+++ b/devtools/client/shared/sourceeditor/editor.js
@@ -258,16 +258,23 @@ Editor.prototype = {
         "CodeMirror instance does not exist. You must wait " +
           "for it to be appended to the DOM."
       );
     }
     return editors.get(this);
   },
 
   /**
+   * Return whether there is a CodeMirror instance associated with this Editor.
+   */
+  get hasCodeMirror() {
+    return editors.has(this);
+  },
+
+  /**
    * Appends the current Editor instance to the element specified by
    * 'el'. You can also provide your own iframe to host the editor as
    * an optional second parameter. This method actually creates and
    * loads CodeMirror and all its dependencies.
    *
    * This method is asynchronous and returns a promise.
    */
   appendTo: function(el, env) {
--- a/devtools/client/webconsole/test/fixtures/stubs/pageError.js
+++ b/devtools/client/webconsole/test/fixtures/stubs/pageError.js
@@ -200,17 +200,17 @@ stubPreparedMessages.set(`throw ""`, new
   "source": "javascript",
   "timeStamp": 1517942398629,
   "type": "log",
   "helperType": null,
   "level": "error",
   "category": "content javascript",
   "messageText": "uncaught exception: ",
   "parameters": null,
-  "repeatId": "{\"frame\":null,\"groupId\":null,\"indent\":0,\"level\":\"error\",\"messageText\":\"uncaught exception: \",\"parameters\":null,\"source\":\"javascript\",\"type\":\"log\",\"userProvidedStyles\":null,\"private\":false,\"stacktrace\":[{\"filename\":\"http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html\",\"sourceId\":\"server1.conn0.child1/source24\",\"lineNumber\":1,\"columnNumber\":1,\"functionName\":null},{\"filename\":\"resource://testing-common/content-task.js line 59 > eval\",\"sourceId\":null,\"lineNumber\":7,\"columnNumber\":31,\"functionName\":null},{\"filename\":\"resource://testing-common/content-task.js\",\"sourceId\":null,\"lineNumber\":60,\"columnNumber\":29,\"functionName\":null}]}",
+  "repeatId": "{\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html\",\"sourceId\":null,\"line\":1,\"column\":1},\"groupId\":null,\"indent\":0,\"level\":\"error\",\"messageText\":\"uncaught exception: \",\"parameters\":null,\"source\":\"javascript\",\"type\":\"log\",\"userProvidedStyles\":null,\"private\":false,\"stacktrace\":[{\"filename\":\"http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html\",\"sourceId\":\"server1.conn0.child1/source24\",\"lineNumber\":1,\"columnNumber\":1,\"functionName\":null},{\"filename\":\"resource://testing-common/content-task.js line 59 > eval\",\"sourceId\":null,\"lineNumber\":7,\"columnNumber\":31,\"functionName\":null},{\"filename\":\"resource://testing-common/content-task.js\",\"sourceId\":null,\"lineNumber\":60,\"columnNumber\":29,\"functionName\":null}]}",
   "stacktrace": [
     {
       "filename": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html",
       "sourceId": "server1.conn0.child1/source24",
       "lineNumber": 1,
       "columnNumber": 1,
       "functionName": null
     },
@@ -224,17 +224,22 @@ stubPreparedMessages.set(`throw ""`, new
     {
       "filename": "resource://testing-common/content-task.js",
       "sourceId": null,
       "lineNumber": 60,
       "columnNumber": 29,
       "functionName": null
     }
   ],
-  "frame": null,
+  "frame": {
+    "source": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html",
+    "sourceId": null,
+    "line": 1,
+    "column": 1
+  },
   "groupId": null,
   "errorMessageName": "JSMSG_UNCAUGHT_EXCEPTION",
   "userProvidedStyles": null,
   "notes": null,
   "indent": 0,
   "prefix": "",
   "private": false,
   "chromeContext": false
@@ -247,17 +252,17 @@ stubPreparedMessages.set(`throw "tomato"
   "source": "javascript",
   "timeStamp": 1517942398637,
   "type": "log",
   "helperType": null,
   "level": "error",
   "category": "content javascript",
   "messageText": "uncaught exception: tomato",
   "parameters": null,
-  "repeatId": "{\"frame\":null,\"groupId\":null,\"indent\":0,\"level\":\"error\",\"messageText\":\"uncaught exception: tomato\",\"parameters\":null,\"source\":\"javascript\",\"type\":\"log\",\"userProvidedStyles\":null,\"private\":false,\"stacktrace\":[{\"filename\":\"http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html\",\"sourceId\":\"server1.conn0.child1/source24\",\"lineNumber\":1,\"columnNumber\":1,\"functionName\":null},{\"filename\":\"resource://testing-common/content-task.js line 59 > eval\",\"sourceId\":null,\"lineNumber\":7,\"columnNumber\":31,\"functionName\":null},{\"filename\":\"resource://testing-common/content-task.js\",\"sourceId\":null,\"lineNumber\":60,\"columnNumber\":29,\"functionName\":null}]}",
+  "repeatId": "{\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html\",\"sourceId\":null,\"line\":1,\"column\":1},\"groupId\":null,\"indent\":0,\"level\":\"error\",\"messageText\":\"uncaught exception: tomato\",\"parameters\":null,\"source\":\"javascript\",\"type\":\"log\",\"userProvidedStyles\":null,\"private\":false,\"stacktrace\":[{\"filename\":\"http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html\",\"sourceId\":\"server1.conn0.child1/source24\",\"lineNumber\":1,\"columnNumber\":1,\"functionName\":null},{\"filename\":\"resource://testing-common/content-task.js line 59 > eval\",\"sourceId\":null,\"lineNumber\":7,\"columnNumber\":31,\"functionName\":null},{\"filename\":\"resource://testing-common/content-task.js\",\"sourceId\":null,\"lineNumber\":60,\"columnNumber\":29,\"functionName\":null}]}",
   "stacktrace": [
     {
       "filename": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html",
       "sourceId": "server1.conn0.child1/source24",
       "lineNumber": 1,
       "columnNumber": 1,
       "functionName": null
     },
@@ -271,17 +276,22 @@ stubPreparedMessages.set(`throw "tomato"
     {
       "filename": "resource://testing-common/content-task.js",
       "sourceId": null,
       "lineNumber": 60,
       "columnNumber": 29,
       "functionName": null
     }
   ],
-  "frame": null,
+  "frame": {
+    "source": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html",
+    "sourceId": null,
+    "line": 1,
+    "column": 1
+  },
   "groupId": null,
   "errorMessageName": "JSMSG_UNCAUGHT_EXCEPTION",
   "userProvidedStyles": null,
   "notes": null,
   "indent": 0,
   "prefix": "",
   "private": false,
   "chromeContext": false
@@ -454,21 +464,21 @@ stubPackets.set(`TypeError longString me
   "type": "pageError",
   "from": "server1.conn0.child1/consoleActor2"
 });
 
 stubPackets.set(`throw ""`, {
   "pageError": {
     "errorMessage": "uncaught exception: ",
     "errorMessageName": "JSMSG_UNCAUGHT_EXCEPTION",
-    "sourceName": "",
+    "sourceName": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html",
     "sourceId": null,
     "lineText": "",
-    "lineNumber": 0,
-    "columnNumber": 0,
+    "lineNumber": 1,
+    "columnNumber": 1,
     "category": "content javascript",
     "innerWindowID": 6442450949,
     "timeStamp": 1517942398629,
     "warning": false,
     "error": false,
     "exception": false,
     "strict": false,
     "info": false,
@@ -502,21 +512,21 @@ stubPackets.set(`throw ""`, {
   "type": "pageError",
   "from": "server1.conn0.child1/consoleActor2"
 });
 
 stubPackets.set(`throw "tomato"`, {
   "pageError": {
     "errorMessage": "uncaught exception: tomato",
     "errorMessageName": "JSMSG_UNCAUGHT_EXCEPTION",
-    "sourceName": "",
+    "sourceName": "http://example.com/browser/devtools/client/webconsole/test/fixtures/stub-generators/test-console-api.html",
     "sourceId": null,
     "lineText": "",
-    "lineNumber": 0,
-    "columnNumber": 0,
+    "lineNumber": 1,
+    "columnNumber": 1,
     "category": "content javascript",
     "innerWindowID": 6442450949,
     "timeStamp": 1517942398637,
     "warning": false,
     "error": false,
     "exception": false,
     "strict": false,
     "info": false,
--- a/devtools/client/webconsole/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/test/mochitest/browser.ini
@@ -40,16 +40,19 @@ support-files =
   test-console.html
   test-cu-reporterror.js
   test-data.json
   test-data.json^headers^
   test-duplicate-error.html
   test-dynamic-import.html
   test-dynamic-import.js
   test-error.html
+  test-error-worker.html
+  test-error-worker.js
+  test-error-worker2.js
   test-eval-in-stackframe.html
   test-eval-sources.html
   test-external-script-errors.html
   test-external-script-errors.js
   test-iframe-insecure-form-action.html
   test-iframe1.html
   test-iframe2.html
   test-iframe3.html
@@ -403,8 +406,9 @@ skip-if = verify
 tags = trackingprotection
 [browser_webconsole_view_source.js]
 [browser_webconsole_visibility_messages.js]
 [browser_webconsole_warn_about_replaced_api.js]
 [browser_webconsole_warning_group_content_blocking.js]
 [browser_webconsole_warning_groups_outside_console_group.js]
 [browser_webconsole_warning_groups.js]
 [browser_webconsole_websocket.js]
+[browser_webconsole_worker_error.js]
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_primitive_stacktrace.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_primitive_stacktrace.js
@@ -10,24 +10,11 @@
 
 const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
                  "test/mochitest/" +
                  "test-primitive-stacktrace.html";
 
 add_task(async function() {
   const hud = await openNewTabAndConsole(TEST_URI);
 
-  await checkMessage("hello", 14, 3);
-  await checkMessage("1,2,3", 20, 1);
-
-  async function checkMessage(text, line, numFrames) {
-    const msgNode = await waitFor(() => findMessage(hud, text));
-    ok(!msgNode.classList.contains("open"), `Error logged not expanded`);
-
-    const button = msgNode.querySelector(".collapse-button");
-    button.click();
-
-    const framesNode = await waitFor(() => msgNode.querySelector(".frames"));
-    const frameNodes = framesNode.querySelectorAll(".frame");
-    ok(frameNodes.length == numFrames);
-    ok(frameNodes[0].querySelector(".line").textContent == "" + line);
-  }
+  await checkMessageStack(hud, "hello", [14, 10, 7]);
+  await checkMessageStack(hud, "1,2,3", [20]);
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_worker_error.js
@@ -0,0 +1,21 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that throwing uncaught errors and primitive values in workers shows a
+// stack in the console.
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
+                 "test/mochitest/test-error-worker.html";
+
+add_task(async function() {
+  const hud = await openNewTabAndConsole(TEST_URI);
+
+  await checkMessageStack(hud, "hello", [11, 3]);
+  await checkMessageStack(hud, "there", [14, 3]);
+  await checkMessageStack(hud, "dom", [16, 3]);
+  await checkMessageStack(hud, "worker2", [6, 3]);
+});
--- a/devtools/client/webconsole/test/mochitest/head.js
+++ b/devtools/client/webconsole/test/mochitest/head.js
@@ -1315,8 +1315,33 @@ function checkConsoleOutputForWarningGro
 
       expectedMessage = expectedMessage.replace("| ", "");
     }
 
     ok(message.textContent.trim().includes(expectedMessage.trim()), `Message includes ` +
       `the expected "${expectedMessage}" content - "${message.textContent.trim()}"`);
   });
 }
+
+/**
+ * Check that there is a message with the specified text that has the specified
+ * stack information.
+ * @param {WebConsole} hud
+ * @param {string} text
+ *        message substring to look for
+ * @param {Array<number>} frameLines
+ *        line numbers of the frames expected in the stack
+ */
+async function checkMessageStack(hud, text, frameLines) {
+  const msgNode = await waitFor(() => findMessage(hud, text));
+  ok(!msgNode.classList.contains("open"), `Error logged not expanded`);
+
+  const button = msgNode.querySelector(".collapse-button");
+  button.click();
+
+  const framesNode = await waitFor(() => msgNode.querySelector(".frames"));
+  const frameNodes = framesNode.querySelectorAll(".frame");
+  ok(frameNodes.length == frameLines.length, `Found ${frameLines.length} frames`);
+  for (let i = 0; i < frameLines.length; i++) {
+    ok(frameNodes[i].querySelector(".line").textContent == "" + frameLines[i],
+       `Found line ${frameLines[i]} for frame #${i}`);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/test-error-worker.html
@@ -0,0 +1,7 @@
+<script>
+"use strict";
+var w = new Worker("test-error-worker.js");
+w.postMessage(1);
+w.postMessage(2);
+w.postMessage(3);
+</script>
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/test-error-worker.js
@@ -0,0 +1,18 @@
+"use strict";
+
+self.addEventListener("message", ({ data }) => foo(data));
+
+var w = new Worker("test-error-worker2.js");
+w.postMessage({});
+
+function foo(data) {
+  switch (data) {
+    case 1:
+      throw new Error("hello");
+    case 2:
+      /* eslint-disable */
+      throw "there";
+    case 3:
+      throw new DOMException("dom");
+  }
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/test-error-worker2.js
@@ -0,0 +1,7 @@
+"use strict";
+
+self.addEventListener("message", ({ data }) => foo(data));
+
+function foo(data) {
+  throw new Error("worker2");
+}
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -1510,25 +1510,40 @@ WebConsoleActor.prototype =
             sourceId: this.getActorIdForInternalSourceId(note.sourceId),
             line: note.lineNumber,
             column: note.columnNumber,
           },
         });
       }
     }
 
+    // If there is no location information in the error but we have a stack,
+    // fill in the location with the first frame on the stack.
+    let {
+      sourceName,
+      sourceId,
+      lineNumber,
+      columnNumber,
+    } = pageError;
+    if (!sourceName && !sourceId && !lineNumber && !columnNumber && stack) {
+      sourceName = stack[0].filename;
+      sourceId = stack[0].sourceId;
+      lineNumber = stack[0].lineNumber;
+      columnNumber = stack[0].columnNumber;
+    }
+
     return {
       errorMessage: this._createStringGrip(pageError.errorMessage),
       errorMessageName: pageError.errorMessageName,
       exceptionDocURL: ErrorDocs.GetURL(pageError),
-      sourceName: pageError.sourceName,
-      sourceId: this.getActorIdForInternalSourceId(pageError.sourceId),
-      lineText: lineText,
-      lineNumber: pageError.lineNumber,
-      columnNumber: pageError.columnNumber,
+      sourceName,
+      sourceId: this.getActorIdForInternalSourceId(sourceId),
+      lineText,
+      lineNumber,
+      columnNumber,
       category: pageError.category,
       innerWindowID: pageError.innerWindowID,
       timeStamp: pageError.timeStamp,
       warning: !!(pageError.flags & pageError.warningFlag),
       error: !!(pageError.flags & pageError.errorFlag),
       exception: !!(pageError.flags & pageError.exceptionFlag),
       strict: !!(pageError.flags & pageError.strictFlag),
       info: !!(pageError.flags & pageError.infoFlag),
--- a/dom/base/StructuredCloneTags.h
+++ b/dom/base/StructuredCloneTags.h
@@ -67,15 +67,20 @@ enum StructuredCloneTags {
   // When adding a new tag for IDB, please don't add it to the end of the list!
   // Tags that are supported by IDB must not ever change. See the static assert
   // in IDBObjectStore.cpp, method CommonStructuredCloneReadCallback.
   // Adding to the end of the list would make removing of other tags harder in
   // future.
 
   SCTAG_DOM_MAX,
 
-  SCTAG_DOM_STRUCTURED_CLONE_TESTER
+  SCTAG_DOM_STRUCTURED_CLONE_TESTER,
+
+  // Principal written out by worker threads when serializing objects. When
+  // reading on the main thread this principal will be converted to a normal
+  // principal object using nsJSPrincipals::AutoSetActiveWorkerPrincipal.
+  SCTAG_DOM_WORKER_PRINCIPAL
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // StructuredCloneTags_h__
--- a/dom/localstorage/ActorsParent.cpp
+++ b/dom/localstorage/ActorsParent.cpp
@@ -7682,17 +7682,17 @@ nsresult QuotaClient::InitOrigin(Persist
 
       rv = usageJournalFile->Remove(false);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_Remove3);
         return rv;
       }
     }
 
-    MOZ_DIAGNOSTIC_ASSERT(usage >= 0);
+    MOZ_ASSERT(usage >= 0);
 
     InitUsageForOrigin(aOrigin, usage);
 
     aUsageInfo->AppendToDatabaseUsage(uint64_t(usage));
   } else if (usageFileExists) {
     rv = usageFile->Remove(false);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       REPORT_TELEMETRY_INIT_ERR(kExternalError, LS_Remove4);
--- a/dom/script/ScriptSettings.cpp
+++ b/dom/script/ScriptSettings.cpp
@@ -523,17 +523,17 @@ void AutoJSAPI::ReportException() {
       // fired.
       WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
       MOZ_ASSERT(worker);
       MOZ_ASSERT(worker->GetJSContext() == cx());
       // Before invoking ReportError, put the exception back on the context,
       // because it may want to put it in its error events and has no other way
       // to get hold of it.  After we invoke ReportError, clear the exception on
       // cx(), just in case ReportError didn't.
-      JS_SetPendingException(cx(), exn);
+      JS::SetPendingExceptionAndStack(cx(), exn, exnStack);
       worker->ReportError(cx(), jsReport.toStringResult(), jsReport.report());
       ClearException();
     }
   } else {
     NS_WARNING("OOMed while acquiring uncaught exception from JSAPI");
     ClearException();
   }
 }
--- a/dom/workers/Principal.cpp
+++ b/dom/workers/Principal.cpp
@@ -3,24 +3,24 @@
 /* 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/. */
 
 #include "Principal.h"
 
 #include "jsapi.h"
 #include "mozilla/Assertions.h"
+#include "mozilla/dom/StructuredCloneTags.h"
 
 namespace mozilla {
 namespace dom {
 
 struct WorkerPrincipal final : public JSPrincipals {
   bool write(JSContext* aCx, JSStructuredCloneWriter* aWriter) override {
-    MOZ_CRASH("WorkerPrincipal::write not implemented");
-    return false;
+    return JS_WriteUint32Pair(aWriter, SCTAG_DOM_WORKER_PRINCIPAL, 0);
   }
 };
 
 JSPrincipals* GetWorkerPrincipal() {
   static WorkerPrincipal sPrincipal;
 
   /*
    * To make sure the the principals refcount is initialized to one, atomically
--- a/dom/workers/WorkerDebugger.cpp
+++ b/dom/workers/WorkerDebugger.cpp
@@ -432,20 +432,26 @@ void WorkerDebugger::ReportErrorToDebugg
     const nsAString& aFilename, uint32_t aLineno, const nsAString& aMessage) {
   AssertIsOnMainThread();
 
   nsTArray<nsCOMPtr<nsIWorkerDebuggerListener>> listeners(mListeners);
   for (size_t index = 0; index < listeners.Length(); ++index) {
     listeners[index]->OnError(aFilename, aLineno, aMessage);
   }
 
-  WorkerErrorReport report;
+  // We need a JSContext to be able to read any stack associated with the error.
+  // This will not run any scripts.
+  AutoJSAPI jsapi;
+  DebugOnly<bool> ok = jsapi.Init(xpc::UnprivilegedJunkScope());
+  MOZ_ASSERT(ok, "UnprivilegedJunkScope should exist");
+
+  WorkerErrorReport report(nullptr);
   report.mMessage = aMessage;
   report.mFilename = aFilename;
-  WorkerErrorReport::LogErrorToConsole(report, 0);
+  WorkerErrorReport::LogErrorToConsole(jsapi.cx(), report, 0);
 }
 
 RefPtr<PerformanceInfoPromise> WorkerDebugger::ReportPerformanceInfo() {
   AssertIsOnMainThread();
   nsCOMPtr<nsPIDOMWindowOuter> top;
   RefPtr<WorkerDebugger> self = this;
 
 #if defined(XP_WIN)
--- a/dom/workers/WorkerError.cpp
+++ b/dom/workers/WorkerError.cpp
@@ -23,145 +23,22 @@
 #include "WorkerScope.h"
 
 namespace mozilla {
 namespace dom {
 
 namespace {
 
 class ReportErrorRunnable final : public WorkerDebuggeeRunnable {
-  WorkerErrorReport mReport;
+  UniquePtr<WorkerErrorReport> mReport;
 
  public:
-  // aWorkerPrivate is the worker thread we're on (or the main thread, if null)
-  // aTarget is the worker object that we are going to fire an error at
-  // (if any).
-  static void ReportError(
-      JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aFireAtScope,
-      DOMEventTargetHelper* aTarget, const WorkerErrorReport& aReport,
-      uint64_t aInnerWindowId,
-      JS::Handle<JS::Value> aException = JS::NullHandleValue) {
-    if (aWorkerPrivate) {
-      aWorkerPrivate->AssertIsOnWorkerThread();
-    } else {
-      AssertIsOnMainThread();
-    }
-
-    // We should not fire error events for warnings but instead make sure that
-    // they show up in the error console.
-    if (!JSREPORT_IS_WARNING(aReport.mFlags)) {
-      // First fire an ErrorEvent at the worker.
-      RootedDictionary<ErrorEventInit> init(aCx);
-
-      if (aReport.mMutedError) {
-        init.mMessage.AssignLiteral("Script error.");
-      } else {
-        init.mMessage = aReport.mMessage;
-        init.mFilename = aReport.mFilename;
-        init.mLineno = aReport.mLineNumber;
-        init.mError = aException;
-      }
-
-      init.mCancelable = true;
-      init.mBubbles = false;
-
-      if (aTarget) {
-        RefPtr<ErrorEvent> event =
-            ErrorEvent::Constructor(aTarget, NS_LITERAL_STRING("error"), init);
-        event->SetTrusted(true);
-
-        bool defaultActionEnabled =
-            aTarget->DispatchEvent(*event, CallerType::System, IgnoreErrors());
-        if (!defaultActionEnabled) {
-          return;
-        }
-      }
-
-      // Now fire an event at the global object, but don't do that if the error
-      // code is too much recursion and this is the same script threw the error.
-      // XXXbz the interaction of this with worker errors seems kinda broken.
-      // An overrecursion in the debugger or debugger sandbox will get turned
-      // into an error event on our parent worker!
-      // https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks making this
-      // better.
-      if (aFireAtScope &&
-          (aTarget || aReport.mErrorNumber != JSMSG_OVER_RECURSED)) {
-        JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
-        NS_ASSERTION(global, "This should never be null!");
-
-        nsEventStatus status = nsEventStatus_eIgnore;
-
-        if (aWorkerPrivate) {
-          WorkerGlobalScope* globalScope = nullptr;
-          UNWRAP_OBJECT(WorkerGlobalScope, &global, globalScope);
-
-          if (!globalScope) {
-            WorkerDebuggerGlobalScope* globalScope = nullptr;
-            UNWRAP_OBJECT(WorkerDebuggerGlobalScope, &global, globalScope);
-
-            MOZ_ASSERT_IF(globalScope,
-                          globalScope->GetWrapperPreserveColor() == global);
-            if (globalScope || IsWorkerDebuggerSandbox(global)) {
-              aWorkerPrivate->ReportErrorToDebugger(
-                  aReport.mFilename, aReport.mLineNumber, aReport.mMessage);
-              return;
-            }
-
-            MOZ_ASSERT(SimpleGlobalObject::SimpleGlobalType(global) ==
-                       SimpleGlobalObject::GlobalType::BindingDetail);
-            // XXXbz We should really log this to console, but unwinding out of
-            // this stuff without ending up firing any events is ... hard.  Just
-            // return for now.
-            // https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks
-            // making this better.
-            return;
-          }
-
-          MOZ_ASSERT(globalScope->GetWrapperPreserveColor() == global);
-
-          RefPtr<ErrorEvent> event = ErrorEvent::Constructor(
-              aTarget, NS_LITERAL_STRING("error"), init);
-          event->SetTrusted(true);
-
-          if (NS_FAILED(EventDispatcher::DispatchDOMEvent(
-                  ToSupports(globalScope), nullptr, event, nullptr, &status))) {
-            NS_WARNING("Failed to dispatch worker thread error event!");
-            status = nsEventStatus_eIgnore;
-          }
-        } else if (nsGlobalWindowInner* win = xpc::WindowOrNull(global)) {
-          MOZ_ASSERT(NS_IsMainThread());
-
-          if (!win->HandleScriptError(init, &status)) {
-            NS_WARNING("Failed to dispatch main thread error event!");
-            status = nsEventStatus_eIgnore;
-          }
-        }
-
-        // Was preventDefault() called?
-        if (status == nsEventStatus_eConsumeNoDefault) {
-          return;
-        }
-      }
-    }
-
-    // Now fire a runnable to do the same on the parent's thread if we can.
-    if (aWorkerPrivate) {
-      RefPtr<ReportErrorRunnable> runnable =
-          new ReportErrorRunnable(aWorkerPrivate, aReport);
-      runnable->Dispatch();
-      return;
-    }
-
-    // Otherwise log an error to the error console.
-    WorkerErrorReport::LogErrorToConsole(aReport, aInnerWindowId);
-  }
-
   ReportErrorRunnable(WorkerPrivate* aWorkerPrivate,
-                      const WorkerErrorReport& aReport)
-      : WorkerDebuggeeRunnable(aWorkerPrivate), mReport(aReport) {}
+                      UniquePtr<WorkerErrorReport> aReport)
+      : WorkerDebuggeeRunnable(aWorkerPrivate), mReport(std::move(aReport)) {}
 
  private:
   virtual void PostDispatch(WorkerPrivate* aWorkerPrivate,
                             bool aDispatchResult) override {
     aWorkerPrivate->AssertIsOnWorkerThread();
 
     // Dispatch may fail if the worker was canceled, no need to report that as
     // an error, so don't call base class PostDispatch.
@@ -189,33 +66,33 @@ class ReportErrorRunnable final : public
       MOZ_ASSERT(!aWorkerPrivate->IsFrozen());
 
       // Similarly for paused windows; all its workers should have been
       // informed. (Subworkers are unaffected by paused windows.)
       MOZ_ASSERT(!aWorkerPrivate->IsParentWindowPaused());
 
       if (aWorkerPrivate->IsSharedWorker()) {
         aWorkerPrivate->GetRemoteWorkerController()
-            ->ErrorPropagationOnMainThread(&mReport,
+            ->ErrorPropagationOnMainThread(mReport.get(),
                                            /* isErrorEvent */ true);
         return true;
       }
 
       // Service workers do not have a main thread parent global, so normal
       // worker error reporting will crash.  Instead, pass the error to
       // the ServiceWorkerManager to report on any controlled documents.
       if (aWorkerPrivate->IsServiceWorker()) {
         RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
         if (swm) {
           swm->HandleError(aCx, aWorkerPrivate->GetPrincipal(),
                            aWorkerPrivate->ServiceWorkerScope(),
-                           aWorkerPrivate->ScriptURL(), mReport.mMessage,
-                           mReport.mFilename, mReport.mLine,
-                           mReport.mLineNumber, mReport.mColumnNumber,
-                           mReport.mFlags, mReport.mExnType);
+                           aWorkerPrivate->ScriptURL(), mReport->mMessage,
+                           mReport->mFilename, mReport->mLine,
+                           mReport->mLineNumber, mReport->mColumnNumber,
+                           mReport->mFlags, mReport->mExnType);
         }
         return true;
       }
 
       // The innerWindowId is only required if we are going to ReportError
       // below, which is gated on this condition. The inner window correctness
       // check is only going to succeed when the worker is accepting events.
       if (workerIsAcceptingEvents) {
@@ -225,18 +102,19 @@ class ReportErrorRunnable final : public
     }
 
     // Don't fire this event if the JS object has been disconnected from the
     // private object.
     if (!workerIsAcceptingEvents) {
       return true;
     }
 
-    ReportError(aCx, parent, fireAtScope,
-                aWorkerPrivate->ParentEventTargetRef(), mReport, innerWindowId);
+    WorkerErrorReport::ReportError(aCx, parent, fireAtScope,
+                                   aWorkerPrivate->ParentEventTargetRef(),
+                                   std::move(mReport), innerWindowId);
     return true;
   }
 };
 
 class ReportGenericErrorRunnable final : public WorkerDebuggeeRunnable {
  public:
   static void CreateAndDispatch(WorkerPrivate* aWorkerPrivate) {
     MOZ_ASSERT(aWorkerPrivate);
@@ -316,16 +194,29 @@ void WorkerErrorBase::AssignErrorBase(JS
   mErrorNumber = aReport->errorNumber;
 }
 
 void WorkerErrorNote::AssignErrorNote(JSErrorNotes::Note* aNote) {
   WorkerErrorBase::AssignErrorBase(aNote);
   xpc::ErrorNote::ErrorNoteToMessageString(aNote, mMessage);
 }
 
+WorkerErrorReport::WorkerErrorReport(WorkerPrivate* aWorkerPrivate)
+  : StructuredCloneHolder(CloningSupported, TransferringNotSupported,
+                          StructuredCloneScope::SameProcessDifferentThread),
+    mFlags(0), mExnType(JSEXN_ERR), mMutedError(false) {
+  if (aWorkerPrivate) {
+    RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
+        aWorkerPrivate, "WorkerErrorReport");
+    if (workerRef) {
+      mWorkerRef = new ThreadSafeWorkerRef(workerRef);
+    }
+  }
+}
+
 void WorkerErrorReport::AssignErrorReport(JSErrorReport* aReport) {
   WorkerErrorBase::AssignErrorBase(aReport);
   xpc::ErrorReport::ErrorReportToMessageString(aReport, mMessage);
 
   mLine.Assign(aReport->linebuf(), aReport->linebufLength());
   mFlags = aReport->flags;
   MOZ_ASSERT(aReport->exnType >= JSEXN_FIRST && aReport->exnType < JSEXN_LIMIT);
   mExnType = JSExnType(aReport->exnType);
@@ -345,36 +236,36 @@ void WorkerErrorReport::AssignErrorRepor
 }
 
 // aWorkerPrivate is the worker thread we're on (or the main thread, if null)
 // aTarget is the worker object that we are going to fire an error at
 // (if any).
 /* static */
 void WorkerErrorReport::ReportError(
     JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aFireAtScope,
-    DOMEventTargetHelper* aTarget, const WorkerErrorReport& aReport,
+    DOMEventTargetHelper* aTarget, UniquePtr<WorkerErrorReport> aReport,
     uint64_t aInnerWindowId, JS::Handle<JS::Value> aException) {
   if (aWorkerPrivate) {
     aWorkerPrivate->AssertIsOnWorkerThread();
   } else {
     AssertIsOnMainThread();
   }
 
   // We should not fire error events for warnings but instead make sure that
   // they show up in the error console.
-  if (!JSREPORT_IS_WARNING(aReport.mFlags)) {
+  if (!JSREPORT_IS_WARNING(aReport->mFlags)) {
     // First fire an ErrorEvent at the worker.
     RootedDictionary<ErrorEventInit> init(aCx);
 
-    if (aReport.mMutedError) {
+    if (aReport->mMutedError) {
       init.mMessage.AssignLiteral("Script error.");
     } else {
-      init.mMessage = aReport.mMessage;
-      init.mFilename = aReport.mFilename;
-      init.mLineno = aReport.mLineNumber;
+      init.mMessage = aReport->mMessage;
+      init.mFilename = aReport->mFilename;
+      init.mLineno = aReport->mLineNumber;
       init.mError = aException;
     }
 
     init.mCancelable = true;
     init.mBubbles = false;
 
     if (aTarget) {
       RefPtr<ErrorEvent> event =
@@ -391,17 +282,17 @@ void WorkerErrorReport::ReportError(
     // Now fire an event at the global object, but don't do that if the error
     // code is too much recursion and this is the same script threw the error.
     // XXXbz the interaction of this with worker errors seems kinda broken.
     // An overrecursion in the debugger or debugger sandbox will get turned
     // into an error event on our parent worker!
     // https://bugzilla.mozilla.org/show_bug.cgi?id=1271441 tracks making this
     // better.
     if (aFireAtScope &&
-        (aTarget || aReport.mErrorNumber != JSMSG_OVER_RECURSED)) {
+        (aTarget || aReport->mErrorNumber != JSMSG_OVER_RECURSED)) {
       JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
       NS_ASSERTION(global, "This should never be null!");
 
       nsEventStatus status = nsEventStatus_eIgnore;
 
       if (aWorkerPrivate) {
         WorkerGlobalScope* globalScope = nullptr;
         UNWRAP_OBJECT(WorkerGlobalScope, &global, globalScope);
@@ -409,17 +300,17 @@ void WorkerErrorReport::ReportError(
         if (!globalScope) {
           WorkerDebuggerGlobalScope* globalScope = nullptr;
           UNWRAP_OBJECT(WorkerDebuggerGlobalScope, &global, globalScope);
 
           MOZ_ASSERT_IF(globalScope,
                         globalScope->GetWrapperPreserveColor() == global);
           if (globalScope || IsWorkerDebuggerSandbox(global)) {
             aWorkerPrivate->ReportErrorToDebugger(
-                aReport.mFilename, aReport.mLineNumber, aReport.mMessage);
+                aReport->mFilename, aReport->mLineNumber, aReport->mMessage);
             return;
           }
 
           MOZ_ASSERT(SimpleGlobalObject::SimpleGlobalType(global) ==
                      SimpleGlobalObject::GlobalType::BindingDetail);
           // XXXbz We should really log this to console, but unwinding out of
           // this stuff without ending up firing any events is ... hard.  Just
           // return for now.
@@ -453,47 +344,72 @@ void WorkerErrorReport::ReportError(
         return;
       }
     }
   }
 
   // Now fire a runnable to do the same on the parent's thread if we can.
   if (aWorkerPrivate) {
     RefPtr<ReportErrorRunnable> runnable =
-        new ReportErrorRunnable(aWorkerPrivate, aReport);
+        new ReportErrorRunnable(aWorkerPrivate, std::move(aReport));
     runnable->Dispatch();
     return;
   }
 
   // Otherwise log an error to the error console.
-  WorkerErrorReport::LogErrorToConsole(aReport, aInnerWindowId);
+  WorkerErrorReport::LogErrorToConsole(aCx, *aReport, aInnerWindowId);
 }
 
 /* static */
-void WorkerErrorReport::LogErrorToConsole(const WorkerErrorReport& aReport,
+void WorkerErrorReport::LogErrorToConsole(JSContext* aCx,
+                                          WorkerErrorReport& aReport,
                                           uint64_t aInnerWindowId) {
   nsTArray<ErrorDataNote> notes;
   for (size_t i = 0, len = aReport.mNotes.Length(); i < len; i++) {
     const WorkerErrorNote& note = aReport.mNotes.ElementAt(i);
     notes.AppendElement(ErrorDataNote(note.mLineNumber, note.mColumnNumber,
                                       note.mMessage, note.mFilename));
   }
 
+  // Read any stack associated with the report.
+  JS::RootedValue stackValue(aCx);
+  if (aReport.HasData() && aReport.mWorkerRef) {
+    nsIPrincipal* principal = aReport.mWorkerRef->Private()->GetPrincipal();
+    nsJSPrincipals::AutoSetActiveWorkerPrincipal set(principal);
+    aReport.Read(xpc::CurrentNativeGlobal(aCx), aCx, &stackValue,
+                 IgnoreErrors());
+  }
+  JS::RootedObject stack(aCx);
+  JS::RootedObject stackGlobal(aCx);
+  if (stackValue.isObject()) {
+    stack = &stackValue.toObject();
+    stackGlobal = JS::CurrentGlobalOrNull(aCx);
+    MOZ_ASSERT(stackGlobal);
+  }
+
   ErrorData errorData(aReport.mLineNumber, aReport.mColumnNumber,
                       aReport.mFlags, aReport.mMessage, aReport.mFilename,
                       aReport.mLine, notes);
-  LogErrorToConsole(errorData, aInnerWindowId);
+  LogErrorToConsole(errorData, aInnerWindowId, stack, stackGlobal);
 }
 
 /* static */
 void WorkerErrorReport::LogErrorToConsole(const ErrorData& aReport,
-                                          uint64_t aInnerWindowId) {
+                                          uint64_t aInnerWindowId,
+                                          JS::HandleObject aStack,
+                                          JS::HandleObject aStackGlobal) {
   AssertIsOnMainThread();
 
-  RefPtr<nsScriptErrorBase> scriptError = new nsScriptError();
+  RefPtr<nsScriptErrorBase> scriptError;
+  if (aStack) {
+    scriptError = new nsScriptErrorWithStack(aStack, aStackGlobal);
+  } else {
+    scriptError = new nsScriptError();
+  }
+
   NS_WARNING_ASSERTION(scriptError, "Failed to create script error!");
 
   if (scriptError) {
     nsAutoCString category("Web Worker");
     if (NS_FAILED(scriptError->InitWithWindowID(
             aReport.message(), aReport.filename(), aReport.line(),
             aReport.lineNumber(), aReport.columnNumber(), aReport.flags(),
             category, aInnerWindowId))) {
--- a/dom/workers/WorkerError.h
+++ b/dom/workers/WorkerError.h
@@ -4,16 +4,17 @@
  * 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/. */
 
 #ifndef mozilla_dom_workers_WorkerError_h
 #define mozilla_dom_workers_WorkerError_h
 
 #include "mozilla/dom/WorkerCommon.h"
 #include "jsapi.h"
+#include "WorkerRef.h"
 
 namespace mozilla {
 
 class DOMEventTargetHelper;
 
 namespace dom {
 
 class ErrorData;
@@ -32,42 +33,52 @@ class WorkerErrorBase {
 
 class WorkerErrorNote : public WorkerErrorBase {
  public:
   void AssignErrorNote(JSErrorNotes::Note* aNote);
 };
 
 class WorkerPrivate;
 
-class WorkerErrorReport : public WorkerErrorBase {
+// The StructuredCloneHolder superclass is used to encode the error's stack
+// data, if there is any.
+class WorkerErrorReport : public WorkerErrorBase, public StructuredCloneHolder {
  public:
   nsString mLine;
   uint32_t mFlags;
   JSExnType mExnType;
   bool mMutedError;
   nsTArray<WorkerErrorNote> mNotes;
 
-  WorkerErrorReport() : mFlags(0), mExnType(JSEXN_ERR), mMutedError(false) {}
+  // Hold a reference on the originating worker until the error has been
+  // processed.
+  RefPtr<ThreadSafeWorkerRef> mWorkerRef;
+
+  // Create a new error report. aWorkerPrivate represents the worker where the
+  // error originated.
+  explicit WorkerErrorReport(WorkerPrivate* aWorkerPrivate);
 
   void AssignErrorReport(JSErrorReport* aReport);
 
   // aWorkerPrivate is the worker thread we're on (or the main thread, if null)
   // aTarget is the worker object that we are going to fire an error at
   // (if any).
   static void ReportError(
       JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aFireAtScope,
-      DOMEventTargetHelper* aTarget, const WorkerErrorReport& aReport,
+      DOMEventTargetHelper* aTarget, UniquePtr<WorkerErrorReport> aReport,
       uint64_t aInnerWindowId,
       JS::Handle<JS::Value> aException = JS::NullHandleValue);
 
-  static void LogErrorToConsole(const WorkerErrorReport& aReport,
+  static void LogErrorToConsole(JSContext* aCx, WorkerErrorReport& aReport,
                                 uint64_t aInnerWindowId);
 
   static void LogErrorToConsole(const mozilla::dom::ErrorData& aReport,
-                                uint64_t aInnerWindowId);
+                                uint64_t aInnerWindowId,
+                                JS::HandleObject aStack = nullptr,
+                                JS::HandleObject aStackGlobal = nullptr);
 
   static void CreateAndDispatchGenericErrorRunnableToParent(
       WorkerPrivate* aWorkerPrivate);
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -4043,52 +4043,61 @@ void WorkerPrivate::ReportError(JSContex
                "Bad recursion logic!");
 
   JS::Rooted<JS::Value> exn(aCx);
   if (!JS_GetPendingException(aCx, &exn)) {
     // Probably shouldn't actually happen?  But let's go ahead and just use null
     // for lack of anything better.
     exn.setNull();
   }
+  JS::RootedObject exnStack(aCx, JS::GetPendingExceptionStack(aCx));
   JS_ClearPendingException(aCx);
 
-  WorkerErrorReport report;
+  UniquePtr<WorkerErrorReport> report = MakeUnique<WorkerErrorReport>(this);
   if (aReport) {
-    report.AssignErrorReport(aReport);
+    report->AssignErrorReport(aReport);
   } else {
-    report.mFlags = nsIScriptError::errorFlag | nsIScriptError::exceptionFlag;
-  }
-
-  if (report.mMessage.IsEmpty() && aToStringResult) {
+    report->mFlags = nsIScriptError::errorFlag | nsIScriptError::exceptionFlag;
+  }
+
+  JS::RootedObject stack(aCx), stackGlobal(aCx);
+  xpc::FindExceptionStackForConsoleReport(nullptr, exn, exnStack, &stack, &stackGlobal);
+
+  if (stack) {
+    JS::RootedValue stackValue(aCx, JS::ObjectValue(*stack));
+    report->Write(aCx, stackValue, IgnoreErrors());
+  }
+
+  if (report->mMessage.IsEmpty() && aToStringResult) {
     nsDependentCString toStringResult(aToStringResult.c_str());
-    if (!AppendUTF8toUTF16(toStringResult, report.mMessage,
+    if (!AppendUTF8toUTF16(toStringResult, report->mMessage,
                            mozilla::fallible)) {
       // Try again, with only a 1 KB string. Do this infallibly this time.
       // If the user doesn't have 1 KB to spare we're done anyways.
       uint32_t index = std::min(uint32_t(1024), toStringResult.Length());
 
       // Drop the last code point that may be cropped.
       index = RewindToPriorUTF8Codepoint(toStringResult.BeginReading(), index);
 
       nsDependentCString truncatedToStringResult(aToStringResult.c_str(),
                                                  index);
-      AppendUTF8toUTF16(truncatedToStringResult, report.mMessage);
+      AppendUTF8toUTF16(truncatedToStringResult, report->mMessage);
     }
   }
 
   data->mErrorHandlerRecursionCount++;
 
   // Don't want to run the scope's error handler if this is a recursive error or
   // if we ran out of memory.
   bool fireAtScope = data->mErrorHandlerRecursionCount == 1 &&
-                     report.mErrorNumber != JSMSG_OUT_OF_MEMORY &&
+                     report->mErrorNumber != JSMSG_OUT_OF_MEMORY &&
                      JS::CurrentGlobalOrNull(aCx);
 
-  WorkerErrorReport::ReportError(aCx, this, fireAtScope, nullptr, report, 0,
-                                 exn);
+  WorkerErrorReport::ReportError(aCx, this, fireAtScope,
+                                 nullptr, std::move(report), 0, exn);
 
   data->mErrorHandlerRecursionCount--;
 }
 
 // static
 void WorkerPrivate::ReportErrorToConsole(const char* aMessage) {
   nsTArray<nsString> emptyParams;
   WorkerPrivate::ReportErrorToConsole(aMessage, emptyParams);
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -5003,16 +5003,32 @@ JS_PUBLIC_API void JS_SetPendingExceptio
   }
 }
 
 JS_PUBLIC_API void JS_ClearPendingException(JSContext* cx) {
   AssertHeapIsIdle();
   cx->clearPendingException();
 }
 
+JS_PUBLIC_API void
+JS::SetPendingExceptionAndStack(JSContext* cx, HandleValue value,
+                                HandleObject stack)
+{
+  AssertHeapIsIdle();
+  CHECK_THREAD(cx);
+  cx->releaseCheck(value);
+  cx->releaseCheck(stack);
+
+  RootedSavedFrame nstack(cx);
+  if (stack) {
+    nstack = &UncheckedUnwrap(stack)->as<SavedFrame>();
+  }
+  cx->setPendingException(value, nstack);
+}
+
 JS_PUBLIC_API JSObject* JS::GetPendingExceptionStack(JSContext* cx) {
   AssertHeapIsIdle();
   CHECK_THREAD(cx);
   return cx->getPendingExceptionStack();
 }
 
 JS::AutoSaveExceptionState::AutoSaveExceptionState(JSContext* cx)
     : context(cx),
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -2843,16 +2843,21 @@ class JS_PUBLIC_API AutoSaveExceptionSta
   /*
    * Replace cx's exception state with the stored exception state. Then
    * discard the stored exception state. If this is called, the
    * destructor is a no-op.
    */
   void restore();
 };
 
+// Set both the exception and its associated stack on the context. The stack
+// must be a SavedFrame.
+JS_PUBLIC_API void SetPendingExceptionAndStack(JSContext* cx, HandleValue value,
+                                               HandleObject stack);
+
 /**
  * Get the SavedFrame stack object captured when the pending exception was set
  * on the JSContext. This fuzzily correlates with a `throw` statement in JS,
  * although arbitrary JSAPI consumers or VM code may also set pending exceptions
  * via `JS_SetPendingException`.
  *
  * This is not the same stack as `e.stack` when `e` is an `Error` object. (That
  * would be JS::ExceptionStackOrNull).
--- a/js/src/vm/JSScript.h
+++ b/js/src/vm/JSScript.h
@@ -248,21 +248,20 @@ using ScriptVTuneIdMap =
     HashMap<JSScript*, uint32_t, DefaultHasher<JSScript*>, SystemAllocPolicy>;
 #endif
 
 class DebugScript {
   friend class ::JSScript;
   friend class JS::Realm;
 
   /*
-   * When non-zero, compile script in single-step mode. The top bit is set and
-   * cleared by setStepMode, as used by JSD. The lower bits are a count,
-   * adjusted by changeStepModeCount, used by the Debugger object. Only
-   * when the bit is clear and the count is zero may we compile the script
-   * without single-step support.
+   * When greater than zero, compile script in single-step mode, with VM calls
+   * to HandleDebugTrap before each bytecode instruction's code. This is a
+   * counter, adjusted by the incrementStepModeCount and decrementStepModeCount
+   * methods.
    */
   uint32_t stepMode;
 
   /*
    * Number of breakpoint sites at opcodes in the script. This is the number
    * of populated entries in DebugScript::breakpoints, below.
    */
   uint32_t numSites;