Bug 1543751 Part 2 - Include stack in worker error reports, r=bz.
authorBrian Hackett <bhackett1024@gmail.com>
Mon, 15 Apr 2019 13:46:05 -1000
changeset 469755 39a877972476
parent 469754 57904fc5ae57
child 469756 e38792ce2185
push id35881
push usercbrindusan@mozilla.com
push dateWed, 17 Apr 2019 09:38:58 +0000
treeherdermozilla-central@bbca68b2af26 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1543751
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
Bug 1543751 Part 2 - Include stack in worker error reports, r=bz.
caps/nsJSPrincipals.cpp
caps/nsJSPrincipals.h
dom/base/StructuredCloneTags.h
dom/script/ScriptSettings.cpp
dom/workers/Principal.cpp
dom/workers/WorkerDebugger.cpp
dom/workers/WorkerError.cpp
dom/workers/WorkerError.h
dom/workers/WorkerPrivate.cpp
--- 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/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/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);