1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
3 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7
8 #include "mozilla/dom/ContentParent.h"
9 #include "mozilla/dom/Event.h"
10 #include "mozilla/dom/JSActorBinding.h"
11 #include "mozilla/dom/JSActorService.h"
12 #include "mozilla/dom/JSWindowActorBinding.h"
13 #include "mozilla/dom/JSWindowActorChild.h"
14 #include "mozilla/dom/JSWindowActorProtocol.h"
15 #include "mozilla/dom/PContent.h"
16 #include "mozilla/dom/WindowGlobalChild.h"
17
18 #include "nsContentUtils.h"
19
20 namespace mozilla::dom {
21
22 NS_IMPL_CYCLE_COLLECTING_ADDREF(JSWindowActorProtocol)
NS_IMPL_CYCLE_COLLECTING_RELEASE(JSWindowActorProtocol)23 NS_IMPL_CYCLE_COLLECTING_RELEASE(JSWindowActorProtocol)
24
25 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSWindowActorProtocol)
26 NS_INTERFACE_MAP_ENTRY(nsIObserver)
27 NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
28 NS_INTERFACE_MAP_END
29
30 NS_IMPL_CYCLE_COLLECTION(JSWindowActorProtocol, mURIMatcher)
31
32 /* static */ already_AddRefed<JSWindowActorProtocol>
33 JSWindowActorProtocol::FromIPC(const JSWindowActorInfo& aInfo) {
34 MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess());
35
36 RefPtr<JSWindowActorProtocol> proto = new JSWindowActorProtocol(aInfo.name());
37 // Content processes cannot load chrome browsing contexts, so this flag is
38 // irrelevant and not propagated.
39 proto->mIncludeChrome = false;
40 proto->mAllFrames = aInfo.allFrames();
41 proto->mMatches = aInfo.matches().Clone();
42 proto->mRemoteTypes = aInfo.remoteTypes().Clone();
43 proto->mMessageManagerGroups = aInfo.messageManagerGroups().Clone();
44 proto->mChild.mModuleURI = aInfo.url();
45
46 proto->mChild.mEvents.SetCapacity(aInfo.events().Length());
47 for (auto& ipc : aInfo.events()) {
48 auto event = proto->mChild.mEvents.AppendElement();
49 event->mName.Assign(ipc.name());
50 event->mFlags.mCapture = ipc.capture();
51 event->mFlags.mInSystemGroup = ipc.systemGroup();
52 event->mFlags.mAllowUntrustedEvents = ipc.allowUntrusted();
53 if (ipc.passive()) {
54 event->mPassive.Construct(ipc.passive().value());
55 }
56 event->mCreateActor = ipc.createActor();
57 }
58
59 proto->mChild.mObservers = aInfo.observers().Clone();
60 return proto.forget();
61 }
62
ToIPC()63 JSWindowActorInfo JSWindowActorProtocol::ToIPC() {
64 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
65
66 JSWindowActorInfo info;
67 info.name() = mName;
68 info.allFrames() = mAllFrames;
69 info.matches() = mMatches.Clone();
70 info.remoteTypes() = mRemoteTypes.Clone();
71 info.messageManagerGroups() = mMessageManagerGroups.Clone();
72 info.url() = mChild.mModuleURI;
73
74 info.events().SetCapacity(mChild.mEvents.Length());
75 for (auto& event : mChild.mEvents) {
76 auto ipc = info.events().AppendElement();
77 ipc->name().Assign(event.mName);
78 ipc->capture() = event.mFlags.mCapture;
79 ipc->systemGroup() = event.mFlags.mInSystemGroup;
80 ipc->allowUntrusted() = event.mFlags.mAllowUntrustedEvents;
81 if (event.mPassive.WasPassed()) {
82 ipc->passive() = Some(event.mPassive.Value());
83 }
84 ipc->createActor() = event.mCreateActor;
85 }
86
87 info.observers() = mChild.mObservers.Clone();
88 return info;
89 }
90
91 already_AddRefed<JSWindowActorProtocol>
FromWebIDLOptions(const nsACString & aName,const WindowActorOptions & aOptions,ErrorResult & aRv)92 JSWindowActorProtocol::FromWebIDLOptions(const nsACString& aName,
93 const WindowActorOptions& aOptions,
94 ErrorResult& aRv) {
95 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
96
97 RefPtr<JSWindowActorProtocol> proto = new JSWindowActorProtocol(aName);
98 proto->mAllFrames = aOptions.mAllFrames;
99 proto->mIncludeChrome = aOptions.mIncludeChrome;
100
101 if (aOptions.mMatches.WasPassed()) {
102 MOZ_ASSERT(aOptions.mMatches.Value().Length());
103 proto->mMatches = aOptions.mMatches.Value();
104 }
105
106 if (aOptions.mRemoteTypes.WasPassed()) {
107 MOZ_ASSERT(aOptions.mRemoteTypes.Value().Length());
108 proto->mRemoteTypes = aOptions.mRemoteTypes.Value();
109 }
110
111 if (aOptions.mMessageManagerGroups.WasPassed()) {
112 proto->mMessageManagerGroups = aOptions.mMessageManagerGroups.Value();
113 }
114
115 if (aOptions.mParent.WasPassed()) {
116 proto->mParent.mModuleURI.emplace(aOptions.mParent.Value().mModuleURI);
117 }
118 if (aOptions.mChild.WasPassed()) {
119 proto->mChild.mModuleURI.emplace(aOptions.mChild.Value().mModuleURI);
120 }
121
122 if (!aOptions.mChild.WasPassed() && !aOptions.mParent.WasPassed()) {
123 aRv.ThrowNotSupportedError(
124 "No point registering an actor with neither child nor parent "
125 "specifications.");
126 return nullptr;
127 }
128
129 // For each event declared in the source dictionary, initialize the
130 // corresponding event declaration entry in the JSWindowActorProtocol.
131 if (aOptions.mChild.WasPassed() &&
132 aOptions.mChild.Value().mEvents.WasPassed()) {
133 auto& entries = aOptions.mChild.Value().mEvents.Value().Entries();
134 proto->mChild.mEvents.SetCapacity(entries.Length());
135
136 for (auto& entry : entries) {
137 // We don't support the mOnce field, as it doesn't work well in this
138 // environment. For now, throw an error in that case.
139 if (entry.mValue.mOnce) {
140 aRv.ThrowNotSupportedError("mOnce is not supported");
141 return nullptr;
142 }
143
144 // Add the EventDecl to our list of events.
145 EventDecl* evt = proto->mChild.mEvents.AppendElement();
146 evt->mName = entry.mKey;
147 evt->mFlags.mCapture = entry.mValue.mCapture;
148 evt->mFlags.mInSystemGroup = entry.mValue.mMozSystemGroup;
149 evt->mFlags.mAllowUntrustedEvents =
150 entry.mValue.mWantUntrusted.WasPassed()
151 ? entry.mValue.mWantUntrusted.Value()
152 : false;
153 if (entry.mValue.mPassive.WasPassed()) {
154 evt->mPassive.Construct(entry.mValue.mPassive.Value());
155 }
156 evt->mCreateActor = entry.mValue.mCreateActor;
157 }
158 }
159
160 if (aOptions.mChild.WasPassed() &&
161 aOptions.mChild.Value().mObservers.WasPassed()) {
162 proto->mChild.mObservers = aOptions.mChild.Value().mObservers.Value();
163 }
164
165 return proto.forget();
166 }
167
168 /**
169 * This listener only listens for events for the child side of the protocol.
170 * This will work in both content and parent processes.
171 */
HandleEvent(Event * aEvent)172 NS_IMETHODIMP JSWindowActorProtocol::HandleEvent(Event* aEvent) {
173 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
174
175 // Determine which inner window we're associated with, and get its
176 // WindowGlobalChild actor.
177 EventTarget* target = aEvent->GetOriginalTarget();
178 if (NS_WARN_IF(!target)) {
179 return NS_ERROR_FAILURE;
180 }
181
182 nsCOMPtr<nsPIDOMWindowInner> inner =
183 do_QueryInterface(target->GetOwnerGlobal());
184 if (NS_WARN_IF(!inner)) {
185 return NS_ERROR_FAILURE;
186 }
187
188 RefPtr<WindowGlobalChild> wgc = inner->GetWindowGlobalChild();
189 if (NS_WARN_IF(!wgc)) {
190 return NS_ERROR_FAILURE;
191 }
192
193 // Ensure our actor is present.
194 RefPtr<JSActor> actor = wgc->GetExistingActor(mName);
195 if (!actor) {
196 // Check if we're supposed to create the actor when this event is fired.
197 bool createActor = true;
198 nsAutoString typeStr;
199 aEvent->GetType(typeStr);
200 for (auto& event : mChild.mEvents) {
201 if (event.mName == typeStr) {
202 createActor = event.mCreateActor;
203 break;
204 }
205 }
206
207 // If we're supposed to create the actor, call GetActor to cause it to be
208 // created.
209 if (createActor) {
210 AutoJSAPI jsapi;
211 jsapi.Init();
212 actor = wgc->GetActor(jsapi.cx(), mName, IgnoreErrors());
213 }
214 }
215 if (!actor || NS_WARN_IF(!actor->GetWrapperPreserveColor())) {
216 return NS_OK;
217 }
218
219 // Build our event listener & call it.
220 JS::Rooted<JSObject*> global(RootingCx(),
221 JS::GetNonCCWObjectGlobal(actor->GetWrapper()));
222 RefPtr<EventListener> eventListener =
223 new EventListener(actor->GetWrapper(), global, nullptr, nullptr);
224 eventListener->HandleEvent(*aEvent, "JSWindowActorProtocol::HandleEvent");
225 return NS_OK;
226 }
227
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)228 NS_IMETHODIMP JSWindowActorProtocol::Observe(nsISupports* aSubject,
229 const char* aTopic,
230 const char16_t* aData) {
231 MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
232
233 nsCOMPtr<nsPIDOMWindowInner> inner = do_QueryInterface(aSubject);
234 RefPtr<WindowGlobalChild> wgc;
235
236 if (!inner) {
237 nsCOMPtr<nsPIDOMWindowOuter> outer = do_QueryInterface(aSubject);
238 if (NS_WARN_IF(!outer)) {
239 nsContentUtils::LogSimpleConsoleError(
240 NS_ConvertUTF8toUTF16(nsPrintfCString(
241 "JSWindowActor %s: expected window subject for topic '%s'.",
242 mName.get(), aTopic)),
243 "JSActor",
244 /* aFromPrivateWindow */ false,
245 /* aFromChromeContext */ true);
246 return NS_ERROR_FAILURE;
247 }
248 if (NS_WARN_IF(!outer->GetCurrentInnerWindow())) {
249 return NS_ERROR_FAILURE;
250 }
251 wgc = outer->GetCurrentInnerWindow()->GetWindowGlobalChild();
252 } else {
253 wgc = inner->GetWindowGlobalChild();
254 }
255
256 if (NS_WARN_IF(!wgc)) {
257 return NS_ERROR_FAILURE;
258 }
259
260 // Ensure our actor is present.
261 AutoJSAPI jsapi;
262 jsapi.Init();
263 RefPtr<JSActor> actor = wgc->GetActor(jsapi.cx(), mName, IgnoreErrors());
264 if (!actor || NS_WARN_IF(!actor->GetWrapperPreserveColor())) {
265 return NS_OK;
266 }
267
268 // Build a observer callback.
269 JS::Rooted<JSObject*> global(jsapi.cx(),
270 JS::GetNonCCWObjectGlobal(actor->GetWrapper()));
271 RefPtr<MozObserverCallback> observerCallback =
272 new MozObserverCallback(actor->GetWrapper(), global, nullptr, nullptr);
273 observerCallback->Observe(aSubject, nsDependentCString(aTopic),
274 aData ? nsDependentString(aData) : VoidString());
275 return NS_OK;
276 }
277
RegisterListenersFor(EventTarget * aTarget)278 void JSWindowActorProtocol::RegisterListenersFor(EventTarget* aTarget) {
279 EventListenerManager* elm = aTarget->GetOrCreateListenerManager();
280
281 for (auto& event : mChild.mEvents) {
282 elm->AddEventListenerByType(EventListenerHolder(this), event.mName,
283 event.mFlags, event.mPassive);
284 }
285 }
286
UnregisterListenersFor(EventTarget * aTarget)287 void JSWindowActorProtocol::UnregisterListenersFor(EventTarget* aTarget) {
288 EventListenerManager* elm = aTarget->GetOrCreateListenerManager();
289
290 for (auto& event : mChild.mEvents) {
291 elm->RemoveEventListenerByType(EventListenerHolder(this), event.mName,
292 event.mFlags);
293 }
294 }
295
AddObservers()296 void JSWindowActorProtocol::AddObservers() {
297 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
298 for (auto& topic : mChild.mObservers) {
299 // This makes the observer service hold an owning reference to the
300 // JSWindowActorProtocol. The JSWindowActorProtocol objects will be living
301 // for the full lifetime of the content process, thus the extra strong
302 // referencec doesn't have a negative impact.
303 os->AddObserver(this, topic.get(), false);
304 }
305 }
306
RemoveObservers()307 void JSWindowActorProtocol::RemoveObservers() {
308 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
309 for (auto& topic : mChild.mObservers) {
310 os->RemoveObserver(this, topic.get());
311 }
312 }
313
GetURIMatcher()314 extensions::MatchPatternSet* JSWindowActorProtocol::GetURIMatcher() {
315 // If we've already created the pattern set, return it.
316 if (mURIMatcher || mMatches.IsEmpty()) {
317 return mURIMatcher;
318 }
319
320 // Constructing the MatchPatternSet requires a JS environment to be run in.
321 // We can construct it here in the JSM scope, as we will be keeping it around.
322 AutoJSAPI jsapi;
323 MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
324 GlobalObject global(jsapi.cx(), xpc::PrivilegedJunkScope());
325
326 nsTArray<OwningStringOrMatchPattern> patterns;
327 patterns.SetCapacity(mMatches.Length());
328 for (nsString& s : mMatches) {
329 auto entry = patterns.AppendElement();
330 entry->SetAsString() = s;
331 }
332
333 MatchPatternOptions matchPatternOptions;
334 // Make MatchPattern's mSchemes create properly.
335 matchPatternOptions.mRestrictSchemes = false;
336 mURIMatcher = extensions::MatchPatternSet::Constructor(
337 global, patterns, matchPatternOptions, IgnoreErrors());
338 return mURIMatcher;
339 }
340
RemoteTypePrefixMatches(const nsDependentCSubstring & aRemoteType)341 bool JSWindowActorProtocol::RemoteTypePrefixMatches(
342 const nsDependentCSubstring& aRemoteType) {
343 for (auto& remoteType : mRemoteTypes) {
344 if (StringBeginsWith(aRemoteType, remoteType)) {
345 return true;
346 }
347 }
348 return false;
349 }
350
MessageManagerGroupMatches(BrowsingContext * aBrowsingContext)351 bool JSWindowActorProtocol::MessageManagerGroupMatches(
352 BrowsingContext* aBrowsingContext) {
353 BrowsingContext* top = aBrowsingContext->Top();
354 for (auto& group : mMessageManagerGroups) {
355 if (group == top->GetMessageManagerGroup()) {
356 return true;
357 }
358 }
359 return false;
360 }
361
Matches(BrowsingContext * aBrowsingContext,nsIURI * aURI,const nsACString & aRemoteType,ErrorResult & aRv)362 bool JSWindowActorProtocol::Matches(BrowsingContext* aBrowsingContext,
363 nsIURI* aURI, const nsACString& aRemoteType,
364 ErrorResult& aRv) {
365 MOZ_ASSERT(aBrowsingContext, "DocShell without a BrowsingContext!");
366 MOZ_ASSERT(aURI, "Must have URI!");
367
368 if (!mAllFrames && aBrowsingContext->GetParent()) {
369 aRv.ThrowNotSupportedError(nsPrintfCString(
370 "Window protocol '%s' doesn't match subframes", mName.get()));
371 return false;
372 }
373
374 if (!mIncludeChrome && !aBrowsingContext->IsContent()) {
375 aRv.ThrowNotSupportedError(nsPrintfCString(
376 "Window protocol '%s' doesn't match chrome browsing contexts",
377 mName.get()));
378 return false;
379 }
380
381 if (!mRemoteTypes.IsEmpty() &&
382 !RemoteTypePrefixMatches(RemoteTypePrefix(aRemoteType))) {
383 aRv.ThrowNotSupportedError(
384 nsPrintfCString("Window protocol '%s' doesn't match remote type '%s'",
385 mName.get(), PromiseFlatCString(aRemoteType).get()));
386 return false;
387 }
388
389 if (!mMessageManagerGroups.IsEmpty() &&
390 !MessageManagerGroupMatches(aBrowsingContext)) {
391 aRv.ThrowNotSupportedError(nsPrintfCString(
392 "Window protocol '%s' doesn't match message manager group",
393 mName.get()));
394 return false;
395 }
396
397 if (extensions::MatchPatternSet* uriMatcher = GetURIMatcher()) {
398 if (!uriMatcher->Matches(aURI)) {
399 aRv.ThrowNotSupportedError(nsPrintfCString(
400 "Window protocol '%s' doesn't match uri %s", mName.get(),
401 nsContentUtils::TruncatedURLForDisplay(aURI).get()));
402 return false;
403 }
404 }
405
406 return true;
407 }
408
409 } // namespace mozilla::dom
410