SerializedStackHolder.cpp (5047B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "SerializedStackHolder.h" 8 9 #include "js/SavedFrameAPI.h" 10 #include "mozilla/Services.h" 11 #include "mozilla/dom/ScriptSettings.h" 12 #include "mozilla/dom/WorkerPrivate.h" 13 #include "nsIObserverService.h" 14 #include "nsJSPrincipals.h" 15 #include "xpcpublic.h" 16 17 namespace mozilla::dom { 18 19 SerializedStackHolder::SerializedStackHolder() 20 : mHolder(StructuredCloneHolder::CloningSupported, 21 StructuredCloneHolder::TransferringNotSupported, 22 StructuredCloneHolder::StructuredCloneScope::SameProcess) {} 23 24 void SerializedStackHolder::WriteStack(JSContext* aCx, 25 JS::Handle<JSObject*> aStack) { 26 JS::Rooted<JS::Value> stackValue(aCx, JS::ObjectValue(*aStack)); 27 mHolder.Write(aCx, stackValue, IgnoreErrors()); 28 29 // StructuredCloneHolder::Write can leave a pending exception on the context. 30 JS_ClearPendingException(aCx); 31 } 32 33 void SerializedStackHolder::SerializeMainThreadOrWorkletStack( 34 JSContext* aCx, JS::Handle<JSObject*> aStack) { 35 MOZ_ASSERT(!IsCurrentThreadRunningWorker()); 36 WriteStack(aCx, aStack); 37 } 38 39 void SerializedStackHolder::SerializeWorkerStack(JSContext* aCx, 40 WorkerPrivate* aWorkerPrivate, 41 JS::Handle<JSObject*> aStack) { 42 MOZ_ASSERT(aWorkerPrivate->IsOnCurrentThread()); 43 44 RefPtr<StrongWorkerRef> workerRef = 45 StrongWorkerRef::Create(aWorkerPrivate, "WorkerErrorReport"); 46 if (workerRef) { 47 mWorkerRef = new ThreadSafeWorkerRef(workerRef); 48 } else { 49 // Don't write the stack if we can't create a ref to the worker. 50 return; 51 } 52 53 WriteStack(aCx, aStack); 54 } 55 56 void SerializedStackHolder::SerializeCurrentStack(JSContext* aCx) { 57 JS::Rooted<JSObject*> stack(aCx); 58 if (JS::CurrentGlobalOrNull(aCx) && !JS::CaptureCurrentStack(aCx, &stack)) { 59 JS_ClearPendingException(aCx); 60 return; 61 } 62 63 if (stack) { 64 if (NS_IsMainThread()) { 65 SerializeMainThreadOrWorkletStack(aCx, stack); 66 } else { 67 WorkerPrivate* currentWorker = GetCurrentThreadWorkerPrivate(); 68 SerializeWorkerStack(aCx, currentWorker, stack); 69 } 70 } 71 } 72 73 JSObject* SerializedStackHolder::ReadStack(JSContext* aCx) { 74 MOZ_ASSERT(NS_IsMainThread()); 75 if (!mHolder.HasData()) { 76 return nullptr; 77 } 78 79 JS::Rooted<JS::Value> stackValue(aCx); 80 81 mHolder.Read(xpc::CurrentNativeGlobal(aCx), aCx, &stackValue, IgnoreErrors()); 82 83 return stackValue.isObject() ? &stackValue.toObject() : nullptr; 84 } 85 86 UniquePtr<SerializedStackHolder> GetCurrentStackForNetMonitor(JSContext* aCx) { 87 MOZ_ASSERT_IF(!NS_IsMainThread(), 88 GetCurrentThreadWorkerPrivate()->IsWatchedByDevTools()); 89 90 return GetCurrentStack(aCx); 91 } 92 93 UniquePtr<SerializedStackHolder> GetCurrentStack(JSContext* aCx) { 94 UniquePtr<SerializedStackHolder> stack = MakeUnique<SerializedStackHolder>(); 95 stack->SerializeCurrentStack(aCx); 96 return stack; 97 } 98 99 void NotifyNetworkMonitorAlternateStack( 100 nsISupports* aChannel, UniquePtr<SerializedStackHolder> aStackHolder) { 101 if (!aStackHolder) { 102 return; 103 } 104 105 nsString stackString; 106 ConvertSerializedStackToJSON(std::move(aStackHolder), stackString); 107 108 if (!stackString.IsEmpty()) { 109 NotifyNetworkMonitorAlternateStack(aChannel, stackString); 110 } 111 } 112 113 void ConvertSerializedStackToJSON(UniquePtr<SerializedStackHolder> aStackHolder, 114 nsAString& aStackString) { 115 // We need a JSContext to be able to stringify the SavedFrame stack. 116 // This will not run any scripts. A privileged scope is needed to fully 117 // inspect all stack frames we find. 118 AutoJSAPI jsapi; 119 DebugOnly<bool> ok = jsapi.Init(xpc::PrivilegedJunkScope()); 120 JSContext* cx = jsapi.cx(); 121 122 JS::Rooted<JSObject*> savedFrame(cx, aStackHolder->ReadStack(cx)); 123 if (!savedFrame) { 124 return; 125 } 126 127 JS::Rooted<JSObject*> converted(cx); 128 converted = JS::ConvertSavedFrameToPlainObject( 129 cx, savedFrame, JS::SavedFrameSelfHosted::Exclude); 130 if (!converted) { 131 JS_ClearPendingException(cx); 132 return; 133 } 134 135 JS::Rooted<JS::Value> convertedValue(cx, JS::ObjectValue(*converted)); 136 if (!nsContentUtils::StringifyJSON(cx, convertedValue, aStackString, 137 UndefinedIsNullStringLiteral)) { 138 JS_ClearPendingException(cx); 139 return; 140 } 141 } 142 143 void NotifyNetworkMonitorAlternateStack(nsISupports* aChannel, 144 const nsAString& aStackJSON) { 145 nsCOMPtr<nsIObserverService> obsService = services::GetObserverService(); 146 if (!obsService) { 147 return; 148 } 149 150 obsService->NotifyObservers(aChannel, "network-monitor-alternate-stack", 151 PromiseFlatString(aStackJSON).get()); 152 } 153 154 } // namespace mozilla::dom