mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-01-26 23:23:33 +00:00
Bug 1392408 Part 2 - Encapsulate threadsafe main/worker stacks in WorkerStackHolder, r=bzbarsky.
--HG-- extra : rebase_source : ea846926ba4c7f2caca45d56004644f03bdeeb7f
This commit is contained in:
parent
0af78c59e0
commit
d5333b6d62
147
dom/base/SerializedStackHolder.cpp
Normal file
147
dom/base/SerializedStackHolder.cpp
Normal file
@ -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
|
80
dom/base/SerializedStackHolder.h
Normal file
80
dom/base/SerializedStackHolder.h
Normal file
@ -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
|
@ -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',
|
||||
|
@ -99,7 +99,7 @@ inline nsresult nsContentPolicy::CheckPolicy(CPMethod policyMethod,
|
||||
* See bug 254510
|
||||
*/
|
||||
if (!requestingLocation) {
|
||||
nsCOMPtr<Document> doc;
|
||||
nsCOMPtr<mozilla::dom::Document> doc;
|
||||
nsCOMPtr<nsIContent> node = do_QueryInterface(requestingContext);
|
||||
if (node) {
|
||||
doc = node->OwnerDoc();
|
||||
|
@ -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<nsFrameLoader> 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();
|
||||
}
|
||||
|
@ -455,7 +455,7 @@ void WorkerDebugger::ReportErrorToDebuggerOnMainThread(
|
||||
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);
|
||||
|
@ -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<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);
|
||||
@ -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,
|
||||
|
@ -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<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);
|
||||
|
||||
|
@ -4053,7 +4053,7 @@ void WorkerPrivate::ReportError(JSContext* aCx,
|
||||
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 {
|
||||
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user