Bug 1392408 Part 2 - Encapsulate threadsafe main/worker stacks in WorkerStackHolder, r=bzbarsky.

--HG--
extra : rebase_source : ea846926ba4c7f2caca45d56004644f03bdeeb7f
This commit is contained in:
Brian Hackett 2019-05-02 08:25:43 -10:00
Родитель 0af78c59e0
Коммит d5333b6d62
9 изменённых файлов: 245 добавлений и 47 удалений

Просмотреть файл

@ -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

Просмотреть файл

@ -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) {