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