diff --git a/dom/base/SerializedStackHolder.cpp b/dom/base/SerializedStackHolder.cpp new file mode 100644 index 000000000000..5ebacaceef26 --- /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 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 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 GetCurrentStackForNetMonitor(JSContext* aCx) { + UniquePtr stack = MakeUnique(); + stack->SerializeCurrentStack(aCx); + return stack; +} + +void NotifyNetworkMonitorAlternateStack(nsISupports* aChannel, + UniquePtr aStackHolder) { + if (!aStackHolder) { + return; + } + + nsString stackString; + ConvertSerializedStackToJSON(std::move(aStackHolder), stackString); + + if (!stackString.IsEmpty()) { + NotifyNetworkMonitorAlternateStack(aChannel, stackString); + } +} + +void ConvertSerializedStackToJSON(UniquePtr 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 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 obsService = services::GetObserverService(); + if (!obsService) { + return; + } + + obsService->NotifyObservers(aChannel, "network-monitor-alternate-stack", + PromiseFlatString(aStackJSON).get()); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/base/SerializedStackHolder.h b/dom/base/SerializedStackHolder.h new file mode 100644 index 000000000000..2fbc8d56a78e --- /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 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 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 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 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 diff --git a/dom/base/moz.build b/dom/base/moz.build index 2274ad779ada..d68e58603d08 100644 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -223,6 +223,7 @@ EXPORTS.mozilla.dom += [ 'ScreenLuminance.h', 'ScreenOrientation.h', 'Selection.h', + 'SerializedStackHolder.h', 'ShadowIncludingTreeIterator.h', 'ShadowRoot.h', 'StructuredCloneBlob.h', @@ -392,6 +393,7 @@ UNIFIED_SOURCES += [ 'ScriptableContentIterator.cpp', 'Selection.cpp', 'SelectionChangeEventDispatcher.cpp', + 'SerializedStackHolder.cpp', 'ShadowRoot.cpp', 'StorageAccessPermissionRequest.cpp', 'StructuredCloneBlob.cpp', diff --git a/dom/base/nsContentPolicy.cpp b/dom/base/nsContentPolicy.cpp index 1d943d5b0efd..776c4bbf651e 100644 --- a/dom/base/nsContentPolicy.cpp +++ b/dom/base/nsContentPolicy.cpp @@ -99,7 +99,7 @@ inline nsresult nsContentPolicy::CheckPolicy(CPMethod policyMethod, * See bug 254510 */ if (!requestingLocation) { - nsCOMPtr doc; + nsCOMPtr doc; nsCOMPtr node = do_QueryInterface(requestingContext); if (node) { doc = node->OwnerDoc(); diff --git a/dom/base/nsFrameLoaderOwner.cpp b/dom/base/nsFrameLoaderOwner.cpp index 1b98fbf37c9e..5532f0462a69 100644 --- a/dom/base/nsFrameLoaderOwner.cpp +++ b/dom/base/nsFrameLoaderOwner.cpp @@ -12,6 +12,7 @@ #include "mozilla/AsyncEventDispatcher.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/FrameLoaderBinding.h" +#include "mozilla/dom/MozFrameLoaderOwnerBinding.h" already_AddRefed nsFrameLoaderOwner::GetFrameLoader() { return do_AddRef(mFrameLoader); @@ -69,7 +70,9 @@ void nsFrameLoaderOwner::ChangeRemoteness( // 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(); } diff --git a/dom/workers/WorkerDebugger.cpp b/dom/workers/WorkerDebugger.cpp index 74c25a5c3d96..c28995a5daa0 100644 --- a/dom/workers/WorkerDebugger.cpp +++ b/dom/workers/WorkerDebugger.cpp @@ -455,7 +455,7 @@ void WorkerDebugger::ReportErrorToDebuggerOnMainThread( DebugOnly 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); diff --git a/dom/workers/WorkerError.cpp b/dom/workers/WorkerError.cpp index 0d91624eb7fd..c134b6546f5a 100644 --- a/dom/workers/WorkerError.cpp +++ b/dom/workers/WorkerError.cpp @@ -199,20 +199,8 @@ void WorkerErrorNote::AssignErrorNote(JSErrorNotes::Note* 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 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); @@ -371,21 +359,8 @@ void WorkerErrorReport::LogErrorToConsole(JSContext* aCx, 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, diff --git a/dom/workers/WorkerError.h b/dom/workers/WorkerError.h index 6c2e1f7be203..79ab7f476f6f 100644 --- a/dom/workers/WorkerError.h +++ b/dom/workers/WorkerError.h @@ -7,9 +7,9 @@ #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 { @@ -38,9 +38,7 @@ class WorkerErrorNote : public WorkerErrorBase { 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; @@ -48,13 +46,7 @@ class WorkerErrorReport : public WorkerErrorBase, public StructuredCloneHolder { bool mMutedError; nsTArray mNotes; - // Hold a reference on the originating worker until the error has been - // processed. - RefPtr mWorkerRef; - - // Create a new error report. aWorkerPrivate represents the worker where the - // error originated. - explicit WorkerErrorReport(WorkerPrivate* aWorkerPrivate); + WorkerErrorReport(); void AssignErrorReport(JSErrorReport* aReport); diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index c94d22ef51ab..607fe88d6ca1 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -4053,7 +4053,7 @@ void WorkerPrivate::ReportError(JSContext* aCx, JS::RootedObject exnStack(aCx, JS::GetPendingExceptionStack(aCx)); JS_ClearPendingException(aCx); - UniquePtr report = MakeUnique(this); + UniquePtr report = MakeUnique(); if (aReport) { report->AssignErrorReport(aReport); } else { @@ -4065,8 +4065,7 @@ void WorkerPrivate::ReportError(JSContext* aCx, &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) {