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