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