JSActorManager.cpp (8849B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 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 "mozilla/dom/JSActorManager.h" 8 9 #include "js/CallAndConstruct.h" // JS::Construct 10 #include "js/PropertyAndElement.h" // JS_GetProperty 11 #include "jsapi.h" 12 #include "mozJSModuleLoader.h" 13 #include "mozilla/AppShutdown.h" 14 #include "mozilla/CycleCollectedJSRuntime.h" 15 #include "mozilla/ScopeExit.h" 16 #include "mozilla/dom/AutoEntryScript.h" 17 #include "mozilla/dom/JSActorService.h" 18 #include "mozilla/dom/JSIPCValue.h" 19 #include "mozilla/dom/JSIPCValueUtils.h" 20 #include "mozilla/dom/JSProcessActorProtocol.h" 21 #include "mozilla/dom/JSWindowActorProtocol.h" 22 #include "mozilla/dom/MessagePort.h" 23 #include "mozilla/dom/PWindowGlobal.h" 24 #include "mozilla/ipc/ProtocolUtils.h" 25 #include "nsContentUtils.h" 26 27 namespace mozilla::dom { 28 29 already_AddRefed<JSActor> JSActorManager::GetActor(JSContext* aCx, 30 const nsACString& aName, 31 ErrorResult& aRv) { 32 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); 33 34 // If our connection has been closed, return an error. 35 mozilla::ipc::IProtocol* nativeActor = AsNativeActor(); 36 if (!nativeActor->CanSend()) { 37 aRv.ThrowInvalidStateError(nsPrintfCString( 38 "Cannot get actor '%s'. Native '%s' actor is destroyed.", 39 PromiseFlatCString(aName).get(), nativeActor->GetProtocolName())); 40 return nullptr; 41 } 42 43 // Check if this actor has already been created, and return it if it has. 44 if (RefPtr<JSActor> actor = mJSActors.Get(aName)) { 45 return actor.forget(); 46 } 47 48 RefPtr<JSActorService> actorSvc = JSActorService::GetSingleton(); 49 if (!actorSvc) { 50 aRv.ThrowInvalidStateError("JSActorService hasn't been initialized"); 51 return nullptr; 52 } 53 54 // Check if this actor satisfies the requirements of the protocol 55 // corresponding to `aName`, and get the module which implements it. 56 RefPtr<JSActorProtocol> protocol = 57 MatchingJSActorProtocol(actorSvc, aName, aRv); 58 if (!protocol) { 59 return nullptr; 60 } 61 62 auto& side = nativeActor->GetSide() == mozilla::ipc::ParentSide 63 ? protocol->Parent() 64 : protocol->Child(); 65 66 // Load the module using mozJSModuleLoader. 67 // If the JSActor uses `loadInDevToolsLoader`, force loading in the DevTools 68 // specific's loader. 69 RefPtr loader = protocol->mLoadInDevToolsLoader 70 ? mozJSModuleLoader::GetOrCreateDevToolsLoader(aCx) 71 : mozJSModuleLoader::Get(); 72 MOZ_ASSERT(loader); 73 74 // We're about to construct the actor, so make sure we're in the loader realm 75 // while importing etc. 76 JSAutoRealm ar(aCx, loader->GetSharedGlobal()); 77 78 // If a module URI was provided, use it to construct an instance of the actor. 79 JS::Rooted<JSObject*> actorObj(aCx); 80 if (side.mESModuleURI) { 81 JS::Rooted<JSObject*> exports(aCx); 82 aRv = loader->ImportESModule(aCx, side.mESModuleURI.ref(), &exports); 83 if (aRv.Failed()) { 84 return nullptr; 85 } 86 MOZ_ASSERT(exports, "null exports!"); 87 88 // Load the specific property from our module. 89 JS::Rooted<JS::Value> ctor(aCx); 90 nsAutoCString ctorName(aName); 91 ctorName.Append(StringFromIPCSide(nativeActor->GetSide())); 92 if (!JS_GetProperty(aCx, exports, ctorName.get(), &ctor)) { 93 aRv.NoteJSContextException(aCx); 94 return nullptr; 95 } 96 97 if (NS_WARN_IF(!ctor.isObject())) { 98 aRv.ThrowNotFoundError(nsPrintfCString( 99 "Could not find actor constructor '%s'", ctorName.get())); 100 return nullptr; 101 } 102 103 // Invoke the constructor loaded from the module. 104 if (!JS::Construct(aCx, ctor, JS::HandleValueArray::empty(), &actorObj)) { 105 aRv.NoteJSContextException(aCx); 106 return nullptr; 107 } 108 } 109 110 // Initialize our newly-constructed actor, and return it. 111 RefPtr<JSActor> actor = InitJSActor(actorObj, aName, aRv); 112 if (aRv.Failed()) { 113 return nullptr; 114 } 115 mJSActors.InsertOrUpdate(aName, RefPtr{actor}); 116 return actor.forget(); 117 } 118 119 already_AddRefed<JSActor> JSActorManager::GetExistingActor( 120 const nsACString& aName) { 121 if (!AsNativeActor()->CanSend()) { 122 return nullptr; 123 } 124 return mJSActors.Get(aName); 125 } 126 127 #define CHILD_DIAGNOSTIC_ASSERT(test, msg) \ 128 do { \ 129 if (XRE_IsParentProcess()) { \ 130 MOZ_ASSERT(test, msg); \ 131 } else { \ 132 MOZ_DIAGNOSTIC_ASSERT(test, msg); \ 133 } \ 134 } while (0) 135 136 void JSActorManager::ReceiveRawMessage( 137 const JSActorMessageMeta& aMetadata, JSIPCValue&& aData, 138 UniquePtr<ipc::StructuredCloneData> aStack) { 139 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); 140 141 CrashReporter::AutoRecordAnnotation autoActorName( 142 CrashReporter::Annotation::JSActorName, aMetadata.actorName()); 143 CrashReporter::AutoRecordAnnotation autoMessageName( 144 CrashReporter::Annotation::JSActorMessage, 145 NS_LossyConvertUTF16toASCII(aMetadata.messageName())); 146 147 // We're going to be running JS. Enter the privileged junk realm so we can set 148 // up our JS state correctly. 149 AutoEntryScript aes(xpc::PrivilegedJunkScope(), "JSActor message handler"); 150 JSContext* cx = aes.cx(); 151 152 // Ensure any errors reported to `error` are set on the scope, so they're 153 // reported. 154 ErrorResult error; 155 auto autoSetException = 156 MakeScopeExit([&] { (void)error.MaybeSetPendingException(cx); }); 157 158 // If an async stack was provided, set up our async stack state. 159 JS::Rooted<JSObject*> stack(cx); 160 Maybe<JS::AutoSetAsyncStackForNewCalls> stackSetter; 161 { 162 JS::Rooted<JS::Value> stackVal(cx); 163 if (aStack) { 164 aStack->Read(cx, &stackVal, error); 165 if (error.Failed()) { 166 error.SuppressException(); 167 JS_ClearPendingException(cx); 168 stackVal.setUndefined(); 169 } 170 } 171 172 if (stackVal.isObject()) { 173 stack = &stackVal.toObject(); 174 if (!js::IsSavedFrame(stack)) { 175 CHILD_DIAGNOSTIC_ASSERT(false, "Stack must be a SavedFrame object"); 176 error.ThrowDataError("Actor async stack must be a SavedFrame object"); 177 return; 178 } 179 stackSetter.emplace(cx, stack, "JSActor query"); 180 } 181 } 182 183 RefPtr<JSActor> actor = GetActor(cx, aMetadata.actorName(), error); 184 if (error.Failed()) { 185 return; 186 } 187 188 #ifdef DEBUG 189 { 190 RefPtr<JSActorService> actorSvc = JSActorService::GetSingleton(); 191 RefPtr windowProtocol( 192 actorSvc->GetJSWindowActorProtocol(aMetadata.actorName())); 193 RefPtr processProtocol( 194 actorSvc->GetJSProcessActorProtocol(aMetadata.actorName())); 195 MOZ_ASSERT(windowProtocol || processProtocol, 196 "The protocol of this actor should exist"); 197 } 198 #endif // DEBUG 199 200 JS::Rooted<JS::Value> data(cx); 201 JSIPCValueUtils::ToJSVal(cx, std::move(aData), &data, error); 202 if (error.Failed()) { 203 CHILD_DIAGNOSTIC_ASSERT(CycleCollectedJSRuntime::Get()->OOMReported(), 204 "Should not receive non-decodable data"); 205 return; 206 } 207 208 switch (aMetadata.kind()) { 209 case JSActorMessageKind::QueryResolve: 210 case JSActorMessageKind::QueryReject: 211 actor->ReceiveQueryReply(cx, aMetadata, data, error); 212 break; 213 214 case JSActorMessageKind::Message: 215 actor->ReceiveMessage(cx, aMetadata, data, error); 216 break; 217 218 case JSActorMessageKind::Query: 219 actor->ReceiveQuery(cx, aMetadata, data, error); 220 break; 221 222 default: 223 MOZ_ASSERT_UNREACHABLE(); 224 } 225 } 226 227 void JSActorManager::JSActorWillDestroy() { 228 for (const auto& entry : mJSActors.Values()) { 229 entry->StartDestroy(); 230 } 231 } 232 233 void JSActorManager::JSActorDidDestroy() { 234 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); 235 CrashReporter::AutoRecordAnnotation autoMessageName( 236 CrashReporter::Annotation::JSActorMessage, "<DidDestroy>"_ns); 237 238 // Swap the table with `mJSActors` so that we don't invalidate it while 239 // iterating. 240 const nsRefPtrHashtable<nsCStringHashKey, JSActor> actors = 241 std::move(mJSActors); 242 for (const auto& entry : actors.Values()) { 243 CrashReporter::AutoRecordAnnotation autoActorName( 244 CrashReporter::Annotation::JSActorName, entry->Name()); 245 // Do not risk to run script very late in shutdown 246 if (!AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal)) { 247 entry->AfterDestroy(); 248 } 249 } 250 } 251 252 void JSActorManager::JSActorUnregister(const nsACString& aName) { 253 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); 254 255 RefPtr<JSActor> actor; 256 if (mJSActors.Remove(aName, getter_AddRefs(actor))) { 257 actor->AfterDestroy(); 258 } 259 } 260 261 } // namespace mozilla::dom