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