Bug 1392408 Part 2 - Encapsulate threadsafe main/worker stacks in WorkerStackHolder, r=bzbarsky.
authorBrian Hackett <bhackett1024@gmail.com>
Thu, 02 May 2019 08:25:43 -1000
changeset 531551 7cebca65fa314ed2de10a68badf691563d3b2e58
parent 531550 7494e80f5b3e1af69e599908feac1bd21ccbac31
child 531552 fdff2d33f119d6ea14594af60846df80c76506d2
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbzbarsky
bugs1392408
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 1392408 Part 2 - Encapsulate threadsafe main/worker stacks in WorkerStackHolder, r=bzbarsky.
dom/base/SerializedStackHolder.cpp
dom/base/SerializedStackHolder.h
dom/base/moz.build
dom/base/nsContentPolicy.cpp
dom/base/nsFrameLoaderOwner.cpp
dom/workers/WorkerDebugger.cpp
dom/workers/WorkerError.cpp
dom/workers/WorkerError.h
dom/workers/WorkerPrivate.cpp
new file mode 100644
--- /dev/null
+++ b/dom/base/SerializedStackHolder.cpp
@@ -0,0 +1,147 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "SerializedStackHolder.h"
+
+#include "js/SavedFrameAPI.h"
+#include "mozilla/dom/WorkerPrivate.h"
+
+namespace mozilla {
+namespace dom {
+
+SerializedStackHolder::SerializedStackHolder()
+  : mHolder(StructuredCloneHolder::CloningSupported,
+            StructuredCloneHolder::TransferringNotSupported,
+            StructuredCloneHolder::StructuredCloneScope::SameProcessDifferentThread) {}
+
+void SerializedStackHolder::WriteStack(JSContext* aCx,
+                                       JS::HandleObject aStack) {
+  JS::RootedValue stackValue(aCx, JS::ObjectValue(*aStack));
+  mHolder.Write(aCx, stackValue, IgnoreErrors());
+
+  // StructuredCloneHolder::Write can leave a pending exception on the context.
+  JS_ClearPendingException(aCx);
+}
+
+void SerializedStackHolder::SerializeMainThreadStack(JSContext* aCx,
+                                                     JS::HandleObject aStack) {
+  MOZ_ASSERT(NS_IsMainThread());
+  WriteStack(aCx, aStack);
+}
+
+void SerializedStackHolder::SerializeWorkerStack(JSContext* aCx,
+                                                 WorkerPrivate* aWorkerPrivate,
+                                                 JS::HandleObject aStack) {
+  MOZ_ASSERT(aWorkerPrivate->IsOnCurrentThread());
+
+  RefPtr<StrongWorkerRef> workerRef = StrongWorkerRef::Create(
+      aWorkerPrivate, "WorkerErrorReport");
+  if (workerRef) {
+    mWorkerRef = new ThreadSafeWorkerRef(workerRef);
+  } else {
+    // Don't write the stack if we can't create a ref to the worker.
+    return;
+  }
+
+  WriteStack(aCx, aStack);
+}
+
+void SerializedStackHolder::SerializeCurrentStack(JSContext* aCx) {
+  JS::RootedObject stack(aCx);
+  if (JS::CurrentGlobalOrNull(aCx) && !JS::CaptureCurrentStack(aCx, &stack)) {
+    JS_ClearPendingException(aCx);
+    return;
+  }
+
+  if (stack) {
+    if (NS_IsMainThread()) {
+      SerializeMainThreadStack(aCx, stack);
+    } else {
+      WorkerPrivate* currentWorker = GetCurrentThreadWorkerPrivate();
+      SerializeWorkerStack(aCx, currentWorker, stack);
+    }
+  }
+}
+
+JSObject* SerializedStackHolder::ReadStack(JSContext* aCx) {
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!mHolder.HasData()) {
+    return nullptr;
+  }
+
+  Maybe<nsJSPrincipals::AutoSetActiveWorkerPrincipal> set;
+  if (mWorkerRef) {
+    set.emplace(mWorkerRef->Private()->GetPrincipal());
+  }
+
+  JS::RootedValue stackValue(aCx);
+  mHolder.Read(xpc::CurrentNativeGlobal(aCx), aCx, &stackValue,
+               IgnoreErrors());
+  return stackValue.isObject() ? &stackValue.toObject() : nullptr;
+}
+
+UniquePtr<SerializedStackHolder> GetCurrentStackForNetMonitor(JSContext* aCx) {
+  UniquePtr<SerializedStackHolder> stack = MakeUnique<SerializedStackHolder>();
+  stack->SerializeCurrentStack(aCx);
+  return stack;
+}
+
+void NotifyNetworkMonitorAlternateStack(nsISupports* aChannel,
+                                        UniquePtr<SerializedStackHolder> aStackHolder) {
+  if (!aStackHolder) {
+    return;
+  }
+
+  nsString stackString;
+  ConvertSerializedStackToJSON(std::move(aStackHolder), stackString);
+
+  if (!stackString.IsEmpty()) {
+    NotifyNetworkMonitorAlternateStack(aChannel, stackString);
+  }
+}
+
+void ConvertSerializedStackToJSON(UniquePtr<SerializedStackHolder> aStackHolder,
+                                  nsAString& aStackString) {
+  // We need a JSContext to be able to stringify the SavedFrame stack.
+  // This will not run any scripts. A privileged scope is needed to fully
+  // inspect all stack frames we find.
+  AutoJSAPI jsapi;
+  DebugOnly<bool> ok = jsapi.Init(xpc::PrivilegedJunkScope());
+  JSContext* cx = jsapi.cx();
+
+  JS::RootedObject savedFrame(cx, aStackHolder->ReadStack(cx));
+  if (!savedFrame) {
+    return;
+  }
+
+  JS::RootedObject converted(cx);
+  converted = JS::ConvertSavedFrameToPlainObject(cx, savedFrame,
+                                                 JS::SavedFrameSelfHosted::Exclude);
+  if (!converted) {
+    JS_ClearPendingException(cx);
+    return;
+  }
+
+  JS::RootedValue convertedValue(cx, JS::ObjectValue(*converted));
+  if (!nsContentUtils::StringifyJSON(cx, &convertedValue, aStackString)) {
+    JS_ClearPendingException(cx);
+    return;
+  }
+}
+
+void NotifyNetworkMonitorAlternateStack(nsISupports* aChannel,
+                                        const nsAString& aStackJSON) {
+  nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+  if (!obsService) {
+    return;
+  }
+
+  obsService->NotifyObservers(aChannel, "network-monitor-alternate-stack",
+                              PromiseFlatString(aStackJSON).get());
+}
+
+}  // namespace dom
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/base/SerializedStackHolder.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_SerializedStackHolder_h
+#define mozilla_dom_SerializedStackHolder_h
+
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "mozilla/dom/WorkerRef.h"
+
+namespace mozilla {
+namespace dom {
+
+// Information about a main or worker thread stack trace that can be accessed
+// from either kind of thread. When a worker thread stack is serialized, the
+// worker is held alive until this holder is destroyed.
+class SerializedStackHolder {
+  // Holds any encoded stack data.
+  StructuredCloneHolder mHolder;
+
+  // The worker associated with this stack, or null if this is a main thread
+  // stack.
+  RefPtr<ThreadSafeWorkerRef> mWorkerRef;
+
+  // Write aStack's data into mHolder.
+  void WriteStack(JSContext* aCx, JS::HandleObject aStack);
+
+ public:
+  SerializedStackHolder();
+
+  // Fill this holder with a main thread stack.
+  void SerializeMainThreadStack(JSContext* aCx, JS::HandleObject aStack);
+
+  // Fill this holder with a worker thread stack.
+  void SerializeWorkerStack(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
+                            JS::HandleObject aStack);
+
+  // Fill this holder with the current thread's current stack.
+  void SerializeCurrentStack(JSContext* aCx);
+
+  // Read back a saved frame stack. This must be called on the main thread.
+  // This returns null on failure, and does not leave an exception on aCx.
+  JSObject* ReadStack(JSContext* aCx);
+};
+
+// Construct a stack for the current thread, which may be consumed by the net
+// monitor later on. This may be called on either the main or a worker thread.
+//
+// This always creates a stack, even if the net monitor isn't active for the
+// associated window. Ideally we would only create the stack if the net monitor
+// was active, but there doesn't seem to be an easy way to determine this.
+// The operations this is used with should be rare enough and/or have enough
+// other associated costs that the perf impact is low. See bug 1546736.
+UniquePtr<SerializedStackHolder> GetCurrentStackForNetMonitor(JSContext* aCx);
+
+// If aStackHolder is non-null, this notifies the net monitor that aStackHolder
+// is the stack from which aChannel originates. This must be called on the main
+// thread. This call is synchronous, and aChannel and aStackHolder will not be
+// used afterward. aChannel is an nsISupports object because this can be used
+// with either nsIChannel or nsIWebSocketChannel.
+void NotifyNetworkMonitorAlternateStack(nsISupports* aChannel,
+                                        UniquePtr<SerializedStackHolder> aStackHolder);
+
+// Read back the saved frame stack and store it in a string as JSON.
+// This must be called on the main thread.
+void ConvertSerializedStackToJSON(UniquePtr<SerializedStackHolder> aStackHolder,
+                                  nsAString& aStackString);
+
+// As above, notify the net monitor for a stack that has already been converted
+// to JSON. This can be used with ConvertSerializedStackToJSON when multiple
+// notifications might be needed for a single stack.
+void NotifyNetworkMonitorAlternateStack(nsISupports* aChannel,
+                                        const nsAString& aStackJSON);
+
+}  // namespace dom
+}  // namespace mozilla
+
+#endif  // mozilla_dom_SerializedStackHolder_h
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -218,16 +218,17 @@ EXPORTS.mozilla.dom += [
     'ProcessMessageManager.h',
     'ResizeObserver.h',
     'ResizeObserverController.h',
     'ResponsiveImageSelector.h',
     'SameProcessMessageQueue.h',
     'ScreenLuminance.h',
     'ScreenOrientation.h',
     'Selection.h',
+    'SerializedStackHolder.h',
     'ShadowIncludingTreeIterator.h',
     'ShadowRoot.h',
     'StructuredCloneBlob.h',
     'StructuredCloneHolder.h',
     'StructuredCloneTags.h',
     'StructuredCloneTester.h',
     'StyleSheetList.h',
     'SubtleCrypto.h',
@@ -387,16 +388,17 @@ UNIFIED_SOURCES += [
     'ResizeObserverController.cpp',
     'ResponsiveImageSelector.cpp',
     'SameProcessMessageQueue.cpp',
     'ScreenLuminance.cpp',
     'ScreenOrientation.cpp',
     'ScriptableContentIterator.cpp',
     'Selection.cpp',
     'SelectionChangeEventDispatcher.cpp',
+    'SerializedStackHolder.cpp',
     'ShadowRoot.cpp',
     'StorageAccessPermissionRequest.cpp',
     'StructuredCloneBlob.cpp',
     'StructuredCloneHolder.cpp',
     'StructuredCloneTester.cpp',
     'StyleSheetList.cpp',
     'SubtleCrypto.cpp',
     'TabGroup.cpp',
--- a/dom/base/nsContentPolicy.cpp
+++ b/dom/base/nsContentPolicy.cpp
@@ -94,17 +94,17 @@ inline nsresult nsContentPolicy::CheckPo
 #endif
 
   /*
    * There might not be a requestinglocation. This can happen for
    * iframes with an image as src. Get the uri from the dom node.
    * See bug 254510
    */
   if (!requestingLocation) {
-    nsCOMPtr<Document> doc;
+    nsCOMPtr<mozilla::dom::Document> doc;
     nsCOMPtr<nsIContent> node = do_QueryInterface(requestingContext);
     if (node) {
       doc = node->OwnerDoc();
     }
     if (!doc) {
       doc = do_QueryInterface(requestingContext);
     }
     if (doc) {
--- a/dom/base/nsFrameLoaderOwner.cpp
+++ b/dom/base/nsFrameLoaderOwner.cpp
@@ -7,16 +7,17 @@
 #include "nsFrameLoaderOwner.h"
 #include "nsFrameLoader.h"
 #include "nsFocusManager.h"
 #include "nsSubDocumentFrame.h"
 #include "nsQueryObject.h"
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/dom/BrowsingContext.h"
 #include "mozilla/dom/FrameLoaderBinding.h"
+#include "mozilla/dom/MozFrameLoaderOwnerBinding.h"
 
 already_AddRefed<nsFrameLoader> nsFrameLoaderOwner::GetFrameLoader() {
   return do_AddRef(mFrameLoader);
 }
 
 void nsFrameLoaderOwner::SetFrameLoader(nsFrameLoader* aNewFrameLoader) {
   mFrameLoader = aNewFrameLoader;
 }
@@ -64,12 +65,14 @@ void nsFrameLoaderOwner::ChangeRemotenes
       fm->ActivateRemoteFrameIfNeeded(*owner);
     }
   }
 
   // Assuming this element is a XULFrameElement, once we've reset our
   // FrameLoader, fire an event to act like we've recreated ourselves, similar
   // to what XULFrameElement does after rebinding to the tree.
   // ChromeOnlyDispatch is turns on to make sure this isn't fired into content.
-  (new AsyncEventDispatcher(owner, NS_LITERAL_STRING("XULFrameLoaderCreated"),
-                            CanBubble::eYes, ChromeOnlyDispatch::eYes))
+  (new mozilla::AsyncEventDispatcher(owner,
+                                     NS_LITERAL_STRING("XULFrameLoaderCreated"),
+                                     mozilla::CanBubble::eYes,
+                                     mozilla::ChromeOnlyDispatch::eYes))
       ->RunDOMEventWhenSafe();
 }
--- a/dom/workers/WorkerDebugger.cpp
+++ b/dom/workers/WorkerDebugger.cpp
@@ -450,17 +450,17 @@ void WorkerDebugger::ReportErrorToDebugg
   }
 
   // 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);
+  WorkerErrorReport report;
   report.mMessage = aMessage;
   report.mFilename = aFilename;
   WorkerErrorReport::LogErrorToConsole(jsapi.cx(), report, 0);
 }
 
 RefPtr<PerformanceInfoPromise> WorkerDebugger::ReportPerformanceInfo() {
   AssertIsOnMainThread();
   nsCOMPtr<nsPIDOMWindowOuter> top;
--- a/dom/workers/WorkerError.cpp
+++ b/dom/workers/WorkerError.cpp
@@ -194,30 +194,18 @@ 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);
-    }
-  }
-}
+WorkerErrorReport::WorkerErrorReport()
+    : mFlags(0), mExnType(JSEXN_ERR), mMutedError(false) {}
 
 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);
@@ -366,31 +354,18 @@ void WorkerErrorReport::LogErrorToConsol
                                           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);
-  }
+  JS::RootedObject stack(aCx, aReport.ReadStack(aCx));
+  JS::RootedObject stackGlobal(aCx, JS::CurrentGlobalOrNull(aCx));
 
   ErrorData errorData(aReport.mLineNumber, aReport.mColumnNumber,
                       aReport.mFlags, aReport.mMessage, aReport.mFilename,
                       aReport.mLine, notes);
   LogErrorToConsole(errorData, aInnerWindowId, stack, stackGlobal);
 }
 
 /* static */
--- a/dom/workers/WorkerError.h
+++ b/dom/workers/WorkerError.h
@@ -2,19 +2,19 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #ifndef mozilla_dom_workers_WorkerError_h
 #define mozilla_dom_workers_WorkerError_h
 
+#include "mozilla/dom/SerializedStackHolder.h"
 #include "mozilla/dom/WorkerCommon.h"
 #include "jsapi.h"
-#include "WorkerRef.h"
 
 namespace mozilla {
 
 class DOMEventTargetHelper;
 
 namespace dom {
 
 class ErrorData;
@@ -33,33 +33,25 @@ class WorkerErrorBase {
 
 class WorkerErrorNote : public WorkerErrorBase {
  public:
   void AssignErrorNote(JSErrorNotes::Note* aNote);
 };
 
 class WorkerPrivate;
 
-// The StructuredCloneHolder superclass is used to encode the error's stack
-// data, if there is any.
-class WorkerErrorReport : public WorkerErrorBase, public StructuredCloneHolder {
+class WorkerErrorReport : public WorkerErrorBase, public SerializedStackHolder {
  public:
   nsString mLine;
   uint32_t mFlags;
   JSExnType mExnType;
   bool mMutedError;
   nsTArray<WorkerErrorNote> mNotes;
 
-  // 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);
+  WorkerErrorReport();
 
   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,
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -4048,30 +4048,29 @@ void WorkerPrivate::ReportError(JSContex
   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);
 
-  UniquePtr<WorkerErrorReport> report = MakeUnique<WorkerErrorReport>(this);
+  UniquePtr<WorkerErrorReport> report = MakeUnique<WorkerErrorReport>();
   if (aReport) {
     report->AssignErrorReport(aReport);
   } else {
     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());
+    report->SerializeWorkerStack(aCx, this, stack);
   }
 
   if (report->mMessage.IsEmpty() && aToStringResult) {
     nsDependentCString toStringResult(aToStringResult.c_str());
     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.