Merge mozilla-central to autoland. a=merge CLOSED TREE
authorBrindusan Cristian <cbrindusan@mozilla.com>
Wed, 17 Apr 2019 12:49:37 +0300
changeset 469781 769b2f5bff88
parent 469780 bdc09ee18819 (current diff)
parent 469758 bbca68b2af26 (diff)
child 469782 638af359adcb
push id35882
push usercbrindusan@mozilla.com
push dateWed, 17 Apr 2019 15:54:01 +0000
treeherdermozilla-central@37185c0ae520 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.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
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -1431,19 +1431,16 @@ var gMainPane = {
   },
 
 
   // EventListener
 
   handleEvent(aEvent) {
     if (aEvent.type == "unload") {
       this.destroy();
-      if (AppConstants.MOZ_UPDATER) {
-        onUnload();
-      }
     }
   },
 
 
   // Composed Model Construction
 
   _loadData() {
     this._loadInternalHandlers();
--- 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;