diff options
Diffstat (limited to '')
-rw-r--r-- | dom/base/SerializedStackHolder.cpp | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/dom/base/SerializedStackHolder.cpp b/dom/base/SerializedStackHolder.cpp new file mode 100644 index 0000000000..199879e226 --- /dev/null +++ b/dom/base/SerializedStackHolder.cpp @@ -0,0 +1,154 @@ +/* -*- 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" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/Services.h" +#include "nsJSPrincipals.h" +#include "nsIObserverService.h" +#include "xpcpublic.h" + +namespace mozilla::dom { + +SerializedStackHolder::SerializedStackHolder() + : mHolder(StructuredCloneHolder::CloningSupported, + StructuredCloneHolder::TransferringNotSupported, + StructuredCloneHolder::StructuredCloneScope::SameProcess) {} + +void SerializedStackHolder::WriteStack(JSContext* aCx, + JS::Handle<JSObject*> aStack) { + JS::Rooted<JS::Value> 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::SerializeMainThreadOrWorkletStack( + JSContext* aCx, JS::Handle<JSObject*> aStack) { + MOZ_ASSERT(!IsCurrentThreadRunningWorker()); + WriteStack(aCx, aStack); +} + +void SerializedStackHolder::SerializeWorkerStack(JSContext* aCx, + WorkerPrivate* aWorkerPrivate, + JS::Handle<JSObject*> 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::Rooted<JSObject*> stack(aCx); + if (JS::CurrentGlobalOrNull(aCx) && !JS::CaptureCurrentStack(aCx, &stack)) { + JS_ClearPendingException(aCx); + return; + } + + if (stack) { + if (NS_IsMainThread()) { + SerializeMainThreadOrWorkletStack(aCx, stack); + } else { + WorkerPrivate* currentWorker = GetCurrentThreadWorkerPrivate(); + SerializeWorkerStack(aCx, currentWorker, stack); + } + } +} + +JSObject* SerializedStackHolder::ReadStack(JSContext* aCx) { + MOZ_ASSERT(NS_IsMainThread()); + if (!mHolder.HasData()) { + return nullptr; + } + + JS::Rooted<JS::Value> stackValue(aCx); + + mHolder.Read(xpc::CurrentNativeGlobal(aCx), aCx, &stackValue, IgnoreErrors()); + + return stackValue.isObject() ? &stackValue.toObject() : nullptr; +} + +UniquePtr<SerializedStackHolder> GetCurrentStackForNetMonitor(JSContext* aCx) { + MOZ_ASSERT_IF(!NS_IsMainThread(), + GetCurrentThreadWorkerPrivate()->IsWatchedByDevTools()); + + return GetCurrentStack(aCx); +} + +UniquePtr<SerializedStackHolder> GetCurrentStack(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::Rooted<JSObject*> savedFrame(cx, aStackHolder->ReadStack(cx)); + if (!savedFrame) { + return; + } + + JS::Rooted<JSObject*> converted(cx); + converted = JS::ConvertSavedFrameToPlainObject( + cx, savedFrame, JS::SavedFrameSelfHosted::Exclude); + if (!converted) { + JS_ClearPendingException(cx); + return; + } + + JS::Rooted<JS::Value> convertedValue(cx, JS::ObjectValue(*converted)); + if (!nsContentUtils::StringifyJSON(cx, convertedValue, aStackString, + UndefinedIsNullStringLiteral)) { + 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 mozilla::dom |