1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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 "ClientSourceParent.h"
8 
9 #include "ClientHandleParent.h"
10 #include "ClientManagerService.h"
11 #include "ClientSourceOpParent.h"
12 #include "ClientValidation.h"
13 #include "mozilla/dom/ClientIPCTypes.h"
14 #include "mozilla/dom/ContentParent.h"
15 #include "mozilla/dom/PClientManagerParent.h"
16 #include "mozilla/dom/ServiceWorkerManager.h"
17 #include "mozilla/dom/ServiceWorkerUtils.h"
18 #include "mozilla/ipc/BackgroundParent.h"
19 #include "mozilla/SchedulerGroup.h"
20 #include "mozilla/Unused.h"
21 
22 namespace mozilla::dom {
23 
24 using mozilla::ipc::AssertIsOnBackgroundThread;
25 using mozilla::ipc::BackgroundParent;
26 using mozilla::ipc::IPCResult;
27 using mozilla::ipc::PrincipalInfo;
28 
29 namespace {
30 
31 // It would be nice to use a lambda instead of this class, but we cannot
32 // move capture in lambdas yet and ContentParent cannot be AddRef'd off
33 // the main thread.
34 class KillContentParentRunnable final : public Runnable {
35   RefPtr<ContentParent> mContentParent;
36 
37  public:
KillContentParentRunnable(RefPtr<ContentParent> && aContentParent)38   explicit KillContentParentRunnable(RefPtr<ContentParent>&& aContentParent)
39       : Runnable("KillContentParentRunnable"),
40         mContentParent(std::move(aContentParent)) {
41     MOZ_ASSERT(mContentParent);
42   }
43 
44   NS_IMETHOD
Run()45   Run() override {
46     MOZ_ASSERT(NS_IsMainThread());
47     mContentParent->KillHard("invalid ClientSourceParent actor");
48     mContentParent = nullptr;
49     return NS_OK;
50   }
51 };
52 
53 }  // anonymous namespace
54 
KillInvalidChild()55 void ClientSourceParent::KillInvalidChild() {
56   // Try to get the content process before we destroy the actor below.
57   RefPtr<ContentParent> process =
58       BackgroundParent::GetContentParent(Manager()->Manager());
59 
60   // First, immediately teardown the ClientSource actor.  No matter what
61   // we want to start this process as soon as possible.
62   Unused << ClientSourceParent::Send__delete__(this);
63 
64   // If we are running in non-e10s, then there is nothing else to do here.
65   // There is no child process and we don't want to crash the entire browser
66   // in release builds.  In general, though, this should not happen in non-e10s
67   // so we do assert this condition.
68   if (!process) {
69     MOZ_DIAGNOSTIC_ASSERT(false, "invalid ClientSourceParent in non-e10s");
70     return;
71   }
72 
73   // In e10s mode we also want to kill the child process.  Validation failures
74   // typically mean someone sent us bogus data over the IPC link.  We can't
75   // trust that process any more.  We have to do this on the main thread, so
76   // there is a small window of time before we kill the process.  This is why
77   // we start the actor destruction immediately above.
78   nsCOMPtr<nsIRunnable> r = new KillContentParentRunnable(std::move(process));
79   MOZ_ALWAYS_SUCCEEDS(
80       SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
81 }
82 
RecvWorkerSyncPing()83 mozilla::ipc::IPCResult ClientSourceParent::RecvWorkerSyncPing() {
84   AssertIsOnBackgroundThread();
85   // Do nothing here.  This is purely a sync message allowing the child to
86   // confirm that the actor has been created on the parent process.
87   return IPC_OK();
88 }
89 
RecvTeardown()90 IPCResult ClientSourceParent::RecvTeardown() {
91   Unused << Send__delete__(this);
92   return IPC_OK();
93 }
94 
RecvExecutionReady(const ClientSourceExecutionReadyArgs & aArgs)95 IPCResult ClientSourceParent::RecvExecutionReady(
96     const ClientSourceExecutionReadyArgs& aArgs) {
97   // Now that we have the creation URL for the Client we can do some validation
98   // to make sure the child actor is not giving us garbage.  Since we validate
99   // on the child side as well we treat a failure here as fatal.
100   if (!ClientIsValidCreationURL(mClientInfo.PrincipalInfo(), aArgs.url())) {
101     KillInvalidChild();
102     return IPC_OK();
103   }
104 
105   mClientInfo.SetURL(aArgs.url());
106   mClientInfo.SetFrameType(aArgs.frameType());
107   mExecutionReady = true;
108 
109   for (ClientHandleParent* handle : mHandleList) {
110     Unused << handle->SendExecutionReady(mClientInfo.ToIPC());
111   }
112 
113   mExecutionReadyPromise.ResolveIfExists(true, __func__);
114 
115   return IPC_OK();
116 };
117 
RecvFreeze()118 IPCResult ClientSourceParent::RecvFreeze() {
119   MOZ_DIAGNOSTIC_ASSERT(!mFrozen);
120   mFrozen = true;
121 
122   // Frozen clients should not be observable.  Act as if the client has
123   // been destroyed.
124   for (ClientHandleParent* handle : mHandleList.Clone()) {
125     Unused << ClientHandleParent::Send__delete__(handle);
126   }
127 
128   return IPC_OK();
129 }
130 
RecvThaw()131 IPCResult ClientSourceParent::RecvThaw() {
132   MOZ_DIAGNOSTIC_ASSERT(mFrozen);
133   mFrozen = false;
134   return IPC_OK();
135 }
136 
RecvInheritController(const ClientControlledArgs & aArgs)137 IPCResult ClientSourceParent::RecvInheritController(
138     const ClientControlledArgs& aArgs) {
139   mController.reset();
140   mController.emplace(aArgs.serviceWorker());
141 
142   // We must tell the parent-side SWM about this controller inheritance.
143   nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
144       "ClientSourceParent::RecvInheritController",
145       [clientInfo = mClientInfo, controller = mController.ref()]() {
146         RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
147         NS_ENSURE_TRUE_VOID(swm);
148 
149         swm->NoteInheritedController(clientInfo, controller);
150       });
151 
152   MOZ_ALWAYS_SUCCEEDS(
153       SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
154 
155   return IPC_OK();
156 }
157 
RecvNoteDOMContentLoaded()158 IPCResult ClientSourceParent::RecvNoteDOMContentLoaded() {
159   if (mController.isSome()) {
160     nsCOMPtr<nsIRunnable> r =
161         NS_NewRunnableFunction("ClientSourceParent::RecvNoteDOMContentLoaded",
162                                [clientInfo = mClientInfo]() {
163                                  RefPtr<ServiceWorkerManager> swm =
164                                      ServiceWorkerManager::GetInstance();
165                                  NS_ENSURE_TRUE_VOID(swm);
166 
167                                  swm->MaybeCheckNavigationUpdate(clientInfo);
168                                });
169 
170     MOZ_ALWAYS_SUCCEEDS(
171         SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
172   }
173   return IPC_OK();
174 }
175 
ActorDestroy(ActorDestroyReason aReason)176 void ClientSourceParent::ActorDestroy(ActorDestroyReason aReason) {
177   DebugOnly<bool> removed = mService->RemoveSource(this);
178   MOZ_ASSERT(removed);
179 
180   for (ClientHandleParent* handle : mHandleList.Clone()) {
181     // This should trigger DetachHandle() to be called removing
182     // the entry from the mHandleList.
183     Unused << ClientHandleParent::Send__delete__(handle);
184   }
185   MOZ_DIAGNOSTIC_ASSERT(mHandleList.IsEmpty());
186 }
187 
AllocPClientSourceOpParent(const ClientOpConstructorArgs & aArgs)188 PClientSourceOpParent* ClientSourceParent::AllocPClientSourceOpParent(
189     const ClientOpConstructorArgs& aArgs) {
190   MOZ_ASSERT_UNREACHABLE(
191       "ClientSourceOpParent should be explicitly constructed.");
192   return nullptr;
193 }
194 
DeallocPClientSourceOpParent(PClientSourceOpParent * aActor)195 bool ClientSourceParent::DeallocPClientSourceOpParent(
196     PClientSourceOpParent* aActor) {
197   delete aActor;
198   return true;
199 }
200 
ClientSourceParent(const ClientSourceConstructorArgs & aArgs,const Maybe<ContentParentId> & aContentParentId)201 ClientSourceParent::ClientSourceParent(
202     const ClientSourceConstructorArgs& aArgs,
203     const Maybe<ContentParentId>& aContentParentId)
204     : mClientInfo(aArgs.id(), aArgs.type(), aArgs.principalInfo(),
205                   aArgs.creationTime()),
206       mContentParentId(aContentParentId),
207       mService(ClientManagerService::GetOrCreateInstance()),
208       mExecutionReady(false),
209       mFrozen(false) {}
210 
~ClientSourceParent()211 ClientSourceParent::~ClientSourceParent() {
212   MOZ_DIAGNOSTIC_ASSERT(mHandleList.IsEmpty());
213 
214   mExecutionReadyPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
215 }
216 
Init()217 void ClientSourceParent::Init() {
218   // Ensure the principal is reasonable before adding ourself to the service.
219   // Since we validate the principal on the child side as well, any failure
220   // here is treated as fatal.
221   if (NS_WARN_IF(!ClientIsValidPrincipalInfo(mClientInfo.PrincipalInfo()))) {
222     KillInvalidChild();
223     return;
224   }
225 
226   // Its possible for AddSource() to fail if there is already an entry for
227   // our UUID.  This should not normally happen, but could if someone is
228   // spoofing IPC messages.
229   if (NS_WARN_IF(!mService->AddSource(this))) {
230     KillInvalidChild();
231     return;
232   }
233 }
234 
Info() const235 const ClientInfo& ClientSourceParent::Info() const { return mClientInfo; }
236 
IsFrozen() const237 bool ClientSourceParent::IsFrozen() const { return mFrozen; }
238 
ExecutionReady() const239 bool ClientSourceParent::ExecutionReady() const { return mExecutionReady; }
240 
ExecutionReadyPromise()241 RefPtr<GenericNonExclusivePromise> ClientSourceParent::ExecutionReadyPromise() {
242   // Only call if ClientSourceParent::ExecutionReady() is false; otherwise,
243   // the promise will never resolve
244   MOZ_ASSERT(!mExecutionReady);
245   return mExecutionReadyPromise.Ensure(__func__);
246 }
247 
GetController() const248 const Maybe<ServiceWorkerDescriptor>& ClientSourceParent::GetController()
249     const {
250   return mController;
251 }
252 
ClearController()253 void ClientSourceParent::ClearController() { mController.reset(); }
254 
AttachHandle(ClientHandleParent * aClientHandle)255 void ClientSourceParent::AttachHandle(ClientHandleParent* aClientHandle) {
256   MOZ_DIAGNOSTIC_ASSERT(aClientHandle);
257   MOZ_DIAGNOSTIC_ASSERT(!mFrozen);
258   MOZ_ASSERT(!mHandleList.Contains(aClientHandle));
259   mHandleList.AppendElement(aClientHandle);
260 }
261 
DetachHandle(ClientHandleParent * aClientHandle)262 void ClientSourceParent::DetachHandle(ClientHandleParent* aClientHandle) {
263   MOZ_DIAGNOSTIC_ASSERT(aClientHandle);
264   MOZ_ASSERT(mHandleList.Contains(aClientHandle));
265   mHandleList.RemoveElement(aClientHandle);
266 }
267 
StartOp(ClientOpConstructorArgs && aArgs)268 RefPtr<ClientOpPromise> ClientSourceParent::StartOp(
269     ClientOpConstructorArgs&& aArgs) {
270   RefPtr<ClientOpPromise::Private> promise =
271       new ClientOpPromise::Private(__func__);
272 
273   // If we are being controlled, remember that data before propagating
274   // on to the ClientSource.  This must be set prior to triggering
275   // the controllerchange event from the ClientSource since some tests
276   // expect matchAll() to find the controlled client immediately after.
277   // If the control operation fails, then we reset the controller value
278   // to reflect the final state.
279   if (aArgs.type() == ClientOpConstructorArgs::TClientControlledArgs) {
280     mController.reset();
281     mController.emplace(aArgs.get_ClientControlledArgs().serviceWorker());
282   }
283 
284   // Constructor failure will reject the promise via ActorDestroy().
285   ClientSourceOpParent* actor =
286       new ClientSourceOpParent(std::move(aArgs), promise);
287   Unused << SendPClientSourceOpConstructor(actor, actor->Args());
288 
289   return promise;
290 }
291 
292 }  // namespace mozilla::dom
293