1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=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 file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "LocalStorageManager2.h"
8
9 #include "LSObject.h"
10 #include "mozilla/dom/Promise.h"
11 #include "mozilla/UniquePtr.h"
12
13 namespace mozilla {
14 namespace dom {
15
16 namespace {
17
18 class AsyncRequestHelper final : public Runnable,
19 public LSRequestChildCallback {
20 enum class State {
21 /**
22 * The AsyncRequestHelper has been created and dispatched to the DOM File
23 * Thread.
24 */
25 Initial,
26 /**
27 * Start() has been invoked on the DOM File Thread and
28 * LocalStorageManager2::StartRequest has been invoked from there, sending
29 * an IPC message to PBackground to service the request. We stay in this
30 * state until a response is received.
31 */
32 ResponsePending,
33 /**
34 * A response has been received and AsyncRequestHelper has been dispatched
35 * back to the owning event target to call Finish().
36 */
37 Finishing,
38 /**
39 * Finish() has been called on the main thread. The promise will be resolved
40 * according to the received response.
41 */
42 Complete
43 };
44
45 // The object we are issuing a request on behalf of. Present because of the
46 // need to invoke LocalStorageManager2::StartRequest off the main thread.
47 // Dropped on return to the main-thread in Finish().
48 RefPtr<LocalStorageManager2> mManager;
49 // The thread the AsyncRequestHelper was created on. This should be the main
50 // thread.
51 nsCOMPtr<nsIEventTarget> mOwningEventTarget;
52 // The IPC actor handling the request with standard IPC allocation rules.
53 // Our reference is nulled in OnResponse which corresponds to the actor's
54 // __destroy__ method.
55 LSRequestChild* mActor;
56 RefPtr<Promise> mPromise;
57 const LSRequestParams mParams;
58 LSRequestResponse mResponse;
59 nsresult mResultCode;
60 State mState;
61
62 public:
AsyncRequestHelper(LocalStorageManager2 * aManager,Promise * aPromise,const LSRequestParams & aParams)63 AsyncRequestHelper(LocalStorageManager2* aManager, Promise* aPromise,
64 const LSRequestParams& aParams)
65 : Runnable("dom::LocalStorageManager2::AsyncRequestHelper"),
66 mManager(aManager),
67 mOwningEventTarget(GetCurrentThreadEventTarget()),
68 mActor(nullptr),
69 mPromise(aPromise),
70 mParams(aParams),
71 mResultCode(NS_OK),
72 mState(State::Initial) {}
73
IsOnOwningThread() const74 bool IsOnOwningThread() const {
75 MOZ_ASSERT(mOwningEventTarget);
76
77 bool current;
78 return NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(¤t)) &&
79 current;
80 }
81
AssertIsOnOwningThread() const82 void AssertIsOnOwningThread() const {
83 MOZ_ASSERT(NS_IsMainThread());
84 MOZ_ASSERT(IsOnOwningThread());
85 }
86
87 nsresult Dispatch();
88
89 private:
90 ~AsyncRequestHelper() = default;
91
92 nsresult Start();
93
94 void Finish();
95
96 NS_DECL_ISUPPORTS_INHERITED
97
98 NS_DECL_NSIRUNNABLE
99
100 // LSRequestChildCallback
101 void OnResponse(const LSRequestResponse& aResponse) override;
102 };
103
104 class SimpleRequestResolver final : public LSSimpleRequestChildCallback {
105 RefPtr<Promise> mPromise;
106
107 public:
SimpleRequestResolver(Promise * aPromise)108 explicit SimpleRequestResolver(Promise* aPromise) : mPromise(aPromise) {}
109
110 NS_INLINE_DECL_REFCOUNTING(SimpleRequestResolver, override);
111
112 private:
113 ~SimpleRequestResolver() = default;
114
115 void HandleResponse(nsresult aResponse);
116
117 void HandleResponse(bool aResponse);
118
119 // LSRequestChildCallback
120 void OnResponse(const LSSimpleRequestResponse& aResponse) override;
121 };
122
CreatePromise(JSContext * aContext,Promise ** aPromise)123 nsresult CreatePromise(JSContext* aContext, Promise** aPromise) {
124 MOZ_ASSERT(NS_IsMainThread());
125 MOZ_ASSERT(aContext);
126
127 nsIGlobalObject* global =
128 xpc::NativeGlobal(JS::CurrentGlobalOrNull(aContext));
129 if (NS_WARN_IF(!global)) {
130 return NS_ERROR_FAILURE;
131 }
132
133 ErrorResult result;
134 RefPtr<Promise> promise = Promise::Create(global, result);
135 if (result.Failed()) {
136 return result.StealNSResult();
137 }
138
139 promise.forget(aPromise);
140 return NS_OK;
141 }
142
CheckedPrincipalToPrincipalInfo(nsIPrincipal * aPrincipal,PrincipalInfo & aPrincipalInfo)143 nsresult CheckedPrincipalToPrincipalInfo(nsIPrincipal* aPrincipal,
144 PrincipalInfo& aPrincipalInfo) {
145 MOZ_ASSERT(NS_IsMainThread());
146 MOZ_ASSERT(aPrincipal);
147
148 nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &aPrincipalInfo);
149 if (NS_WARN_IF(NS_FAILED(rv))) {
150 return rv;
151 }
152
153 if (NS_WARN_IF(!QuotaManager::IsPrincipalInfoValid(aPrincipalInfo))) {
154 return NS_ERROR_FAILURE;
155 }
156
157 if (aPrincipalInfo.type() != PrincipalInfo::TContentPrincipalInfo &&
158 aPrincipalInfo.type() != PrincipalInfo::TSystemPrincipalInfo) {
159 return NS_ERROR_UNEXPECTED;
160 }
161
162 return NS_OK;
163 }
164
165 } // namespace
166
LocalStorageManager2()167 LocalStorageManager2::LocalStorageManager2() {
168 MOZ_ASSERT(NS_IsMainThread());
169 MOZ_ASSERT(NextGenLocalStorageEnabled());
170 }
171
~LocalStorageManager2()172 LocalStorageManager2::~LocalStorageManager2() { MOZ_ASSERT(NS_IsMainThread()); }
173
NS_IMPL_ISUPPORTS(LocalStorageManager2,nsIDOMStorageManager,nsILocalStorageManager)174 NS_IMPL_ISUPPORTS(LocalStorageManager2, nsIDOMStorageManager,
175 nsILocalStorageManager)
176
177 NS_IMETHODIMP
178 LocalStorageManager2::PrecacheStorage(nsIPrincipal* aPrincipal,
179 nsIPrincipal* aStoragePrincipal,
180 Storage** _retval) {
181 MOZ_ASSERT(NS_IsMainThread());
182 MOZ_ASSERT(aPrincipal);
183 MOZ_ASSERT(aStoragePrincipal);
184 MOZ_ASSERT(_retval);
185
186 // This method was created as part of the e10s-ification of the old LS
187 // implementation to perform a preload in the content/current process. That's
188 // not how things work in LSNG. Instead everything happens in the parent
189 // process, triggered by the official preloading spot,
190 // ContentParent::AboutToLoadHttpFtpDocumentForChild.
191 return NS_ERROR_NOT_IMPLEMENTED;
192 }
193
194 NS_IMETHODIMP
CreateStorage(mozIDOMWindow * aWindow,nsIPrincipal * aPrincipal,nsIPrincipal * aStoragePrincipal,const nsAString & aDocumentURI,bool aPrivate,Storage ** _retval)195 LocalStorageManager2::CreateStorage(mozIDOMWindow* aWindow,
196 nsIPrincipal* aPrincipal,
197 nsIPrincipal* aStoragePrincipal,
198 const nsAString& aDocumentURI,
199 bool aPrivate, Storage** _retval) {
200 MOZ_ASSERT(NS_IsMainThread());
201 MOZ_ASSERT(aPrincipal);
202 MOZ_ASSERT(aStoragePrincipal);
203 MOZ_ASSERT(_retval);
204
205 nsCOMPtr<nsPIDOMWindowInner> inner = nsPIDOMWindowInner::From(aWindow);
206
207 RefPtr<LSObject> object;
208 nsresult rv = LSObject::CreateForPrincipal(inner, aPrincipal,
209 aStoragePrincipal, aDocumentURI,
210 aPrivate, getter_AddRefs(object));
211 if (NS_WARN_IF(NS_FAILED(rv))) {
212 return rv;
213 }
214
215 object.forget(_retval);
216 return NS_OK;
217 }
218
219 NS_IMETHODIMP
GetStorage(mozIDOMWindow * aWindow,nsIPrincipal * aPrincipal,nsIPrincipal * aStoragePrincipal,bool aPrivate,Storage ** _retval)220 LocalStorageManager2::GetStorage(mozIDOMWindow* aWindow,
221 nsIPrincipal* aPrincipal,
222 nsIPrincipal* aStoragePrincipal, bool aPrivate,
223 Storage** _retval) {
224 MOZ_ASSERT(NS_IsMainThread());
225 MOZ_ASSERT(aPrincipal);
226 MOZ_ASSERT(aStoragePrincipal);
227 MOZ_ASSERT(_retval);
228
229 return NS_ERROR_NOT_IMPLEMENTED;
230 }
231
232 NS_IMETHODIMP
CloneStorage(Storage * aStorageToCloneFrom)233 LocalStorageManager2::CloneStorage(Storage* aStorageToCloneFrom) {
234 MOZ_ASSERT(NS_IsMainThread());
235 MOZ_ASSERT(aStorageToCloneFrom);
236
237 // Cloning is specific to sessionStorage; state is forked when a new tab is
238 // opened from an existing tab.
239 return NS_ERROR_NOT_IMPLEMENTED;
240 }
241
242 NS_IMETHODIMP
CheckStorage(nsIPrincipal * aPrincipal,Storage * aStorage,bool * _retval)243 LocalStorageManager2::CheckStorage(nsIPrincipal* aPrincipal, Storage* aStorage,
244 bool* _retval) {
245 MOZ_ASSERT(NS_IsMainThread());
246 MOZ_ASSERT(aPrincipal);
247 MOZ_ASSERT(aStorage);
248 MOZ_ASSERT(_retval);
249
250 // Only used by sessionStorage.
251 return NS_ERROR_NOT_IMPLEMENTED;
252 }
253
254 NS_IMETHODIMP
GetNextGenLocalStorageEnabled(bool * aResult)255 LocalStorageManager2::GetNextGenLocalStorageEnabled(bool* aResult) {
256 MOZ_ASSERT(NS_IsMainThread());
257 MOZ_ASSERT(aResult);
258
259 *aResult = NextGenLocalStorageEnabled();
260 return NS_OK;
261 }
262
263 NS_IMETHODIMP
Preload(nsIPrincipal * aPrincipal,JSContext * aContext,nsISupports ** _retval)264 LocalStorageManager2::Preload(nsIPrincipal* aPrincipal, JSContext* aContext,
265 nsISupports** _retval) {
266 MOZ_ASSERT(NS_IsMainThread());
267 MOZ_ASSERT(aPrincipal);
268 MOZ_ASSERT(_retval);
269
270 nsCString originAttrSuffix;
271 nsCString originKey;
272 nsresult rv = aPrincipal->GetStorageOriginKey(originKey);
273 aPrincipal->OriginAttributesRef().CreateSuffix(originAttrSuffix);
274 if (NS_FAILED(rv)) {
275 return NS_ERROR_NOT_AVAILABLE;
276 }
277
278 PrincipalInfo principalInfo;
279 rv = CheckedPrincipalToPrincipalInfo(aPrincipal, principalInfo);
280 if (NS_WARN_IF(NS_FAILED(rv))) {
281 return rv;
282 }
283
284 RefPtr<Promise> promise;
285
286 if (aContext) {
287 rv = CreatePromise(aContext, getter_AddRefs(promise));
288 if (NS_WARN_IF(NS_FAILED(rv))) {
289 return rv;
290 }
291 }
292
293 LSRequestCommonParams commonParams;
294 commonParams.principalInfo() = principalInfo;
295 commonParams.storagePrincipalInfo() = principalInfo;
296 commonParams.originKey() = originKey;
297
298 LSRequestPreloadDatastoreParams params(commonParams);
299
300 RefPtr<AsyncRequestHelper> helper =
301 new AsyncRequestHelper(this, promise, params);
302
303 // This will start and finish the async request on the DOM File thread.
304 // This must be done on DOM File Thread because it's very likely that a
305 // content process will issue a prepare datastore request for the same
306 // principal while blocking the content process on the main thread.
307 // There would be a potential for deadlock if the preloading was initialized
308 // from the main thread of the parent process and a11y issued a synchronous
309 // message from the parent process to the content process (approximately at
310 // the same time) because the preload request wouldn't be able to respond
311 // to the Ready message by sending the Finish message which is needed to
312 // finish the preload request and unblock the prepare datastore request.
313 rv = helper->Dispatch();
314 if (NS_WARN_IF(NS_FAILED(rv))) {
315 return rv;
316 }
317
318 promise.forget(_retval);
319 return NS_OK;
320 }
321
322 NS_IMETHODIMP
IsPreloaded(nsIPrincipal * aPrincipal,JSContext * aContext,nsISupports ** _retval)323 LocalStorageManager2::IsPreloaded(nsIPrincipal* aPrincipal, JSContext* aContext,
324 nsISupports** _retval) {
325 MOZ_ASSERT(NS_IsMainThread());
326 MOZ_ASSERT(aPrincipal);
327 MOZ_ASSERT(_retval);
328
329 RefPtr<Promise> promise;
330 nsresult rv = CreatePromise(aContext, getter_AddRefs(promise));
331 if (NS_WARN_IF(NS_FAILED(rv))) {
332 return rv;
333 }
334
335 LSSimpleRequestPreloadedParams params;
336
337 rv = CheckedPrincipalToPrincipalInfo(aPrincipal, params.principalInfo());
338 if (NS_WARN_IF(NS_FAILED(rv))) {
339 return rv;
340 }
341
342 params.storagePrincipalInfo() = params.principalInfo();
343
344 rv = StartSimpleRequest(promise, params);
345 if (NS_WARN_IF(NS_FAILED(rv))) {
346 return rv;
347 }
348
349 promise.forget(_retval);
350 return NS_OK;
351 }
352
StartRequest(const LSRequestParams & aParams,LSRequestChildCallback * aCallback)353 LSRequestChild* LocalStorageManager2::StartRequest(
354 const LSRequestParams& aParams, LSRequestChildCallback* aCallback) {
355 AssertIsOnDOMFileThread();
356
357 PBackgroundChild* backgroundActor =
358 BackgroundChild::GetOrCreateForCurrentThread();
359 if (NS_WARN_IF(!backgroundActor)) {
360 return nullptr;
361 }
362
363 auto actor = new LSRequestChild();
364
365 if (!backgroundActor->SendPBackgroundLSRequestConstructor(actor, aParams)) {
366 return nullptr;
367 }
368
369 // Must set callback after calling SendPBackgroundLSRequestConstructor since
370 // it can be called synchronously when SendPBackgroundLSRequestConstructor
371 // fails.
372 actor->SetCallback(aCallback);
373
374 return actor;
375 }
376
StartSimpleRequest(Promise * aPromise,const LSSimpleRequestParams & aParams)377 nsresult LocalStorageManager2::StartSimpleRequest(
378 Promise* aPromise, const LSSimpleRequestParams& aParams) {
379 MOZ_ASSERT(NS_IsMainThread());
380 MOZ_ASSERT(aPromise);
381
382 PBackgroundChild* backgroundActor =
383 BackgroundChild::GetOrCreateForCurrentThread();
384 if (NS_WARN_IF(!backgroundActor)) {
385 return NS_ERROR_FAILURE;
386 }
387
388 auto actor = new LSSimpleRequestChild();
389
390 if (!backgroundActor->SendPBackgroundLSSimpleRequestConstructor(actor,
391 aParams)) {
392 return NS_ERROR_FAILURE;
393 }
394
395 RefPtr<SimpleRequestResolver> resolver = new SimpleRequestResolver(aPromise);
396
397 // Must set callback after calling SendPBackgroundLSRequestConstructor since
398 // it can be called synchronously when SendPBackgroundLSRequestConstructor
399 // fails.
400 actor->SetCallback(resolver);
401
402 return NS_OK;
403 }
404
Dispatch()405 nsresult AsyncRequestHelper::Dispatch() {
406 AssertIsOnOwningThread();
407
408 nsCOMPtr<nsIEventTarget> domFileThread =
409 IPCBlobInputStreamThread::GetOrCreate();
410 if (NS_WARN_IF(!domFileThread)) {
411 return NS_ERROR_FAILURE;
412 }
413
414 nsresult rv = domFileThread->Dispatch(this, NS_DISPATCH_NORMAL);
415 if (NS_WARN_IF(NS_FAILED(rv))) {
416 return rv;
417 }
418
419 return NS_OK;
420 }
421
Start()422 nsresult AsyncRequestHelper::Start() {
423 AssertIsOnDOMFileThread();
424 MOZ_ASSERT(mState == State::Initial);
425
426 mState = State::ResponsePending;
427
428 LSRequestChild* actor = mManager->StartRequest(mParams, this);
429 if (NS_WARN_IF(!actor)) {
430 return NS_ERROR_FAILURE;
431 }
432
433 mActor = actor;
434
435 return NS_OK;
436 }
437
Finish()438 void AsyncRequestHelper::Finish() {
439 AssertIsOnOwningThread();
440 MOZ_ASSERT(mState == State::Finishing);
441
442 if (NS_WARN_IF(NS_FAILED(mResultCode))) {
443 if (mPromise) {
444 mPromise->MaybeReject(mResultCode);
445 }
446 } else {
447 switch (mResponse.type()) {
448 case LSRequestResponse::Tnsresult:
449 if (mPromise) {
450 mPromise->MaybeReject(mResponse.get_nsresult());
451 }
452 break;
453
454 case LSRequestResponse::TLSRequestPreloadDatastoreResponse:
455 if (mPromise) {
456 mPromise->MaybeResolveWithUndefined();
457 }
458 break;
459 default:
460 MOZ_CRASH("Unknown response type!");
461 }
462 }
463
464 mManager = nullptr;
465
466 mState = State::Complete;
467 }
468
NS_IMPL_ISUPPORTS_INHERITED0(AsyncRequestHelper,Runnable)469 NS_IMPL_ISUPPORTS_INHERITED0(AsyncRequestHelper, Runnable)
470
471 NS_IMETHODIMP
472 AsyncRequestHelper::Run() {
473 nsresult rv;
474
475 switch (mState) {
476 case State::Initial:
477 rv = Start();
478 break;
479
480 case State::Finishing:
481 Finish();
482 return NS_OK;
483
484 default:
485 MOZ_CRASH("Bad state!");
486 }
487
488 if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::Finishing) {
489 if (NS_SUCCEEDED(mResultCode)) {
490 mResultCode = rv;
491 }
492
493 mState = State::Finishing;
494
495 if (IsOnOwningThread()) {
496 Finish();
497 } else {
498 MOZ_ALWAYS_SUCCEEDS(
499 mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
500 }
501 }
502
503 return NS_OK;
504 }
505
OnResponse(const LSRequestResponse & aResponse)506 void AsyncRequestHelper::OnResponse(const LSRequestResponse& aResponse) {
507 AssertIsOnDOMFileThread();
508 MOZ_ASSERT(mState == State::ResponsePending);
509
510 mActor = nullptr;
511
512 mResponse = aResponse;
513
514 mState = State::Finishing;
515
516 MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
517 }
518
HandleResponse(nsresult aResponse)519 void SimpleRequestResolver::HandleResponse(nsresult aResponse) {
520 MOZ_ASSERT(NS_IsMainThread());
521 MOZ_ASSERT(mPromise);
522
523 mPromise->MaybeReject(aResponse);
524 }
525
HandleResponse(bool aResponse)526 void SimpleRequestResolver::HandleResponse(bool aResponse) {
527 MOZ_ASSERT(NS_IsMainThread());
528 MOZ_ASSERT(mPromise);
529
530 mPromise->MaybeResolve(aResponse);
531 }
532
OnResponse(const LSSimpleRequestResponse & aResponse)533 void SimpleRequestResolver::OnResponse(
534 const LSSimpleRequestResponse& aResponse) {
535 MOZ_ASSERT(NS_IsMainThread());
536
537 switch (aResponse.type()) {
538 case LSSimpleRequestResponse::Tnsresult:
539 HandleResponse(aResponse.get_nsresult());
540 break;
541
542 case LSSimpleRequestResponse::TLSSimpleRequestPreloadedResponse:
543 HandleResponse(
544 aResponse.get_LSSimpleRequestPreloadedResponse().preloaded());
545 break;
546
547 default:
548 MOZ_CRASH("Unknown response type!");
549 }
550 }
551
552 } // namespace dom
553 } // namespace mozilla
554