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/. */
7 #include "LocalStorageManager2.h"
10 #include "ActorsChild.h"
15 #include "MainThreadUtils.h"
17 #include "mozilla/Assertions.h"
18 #include "mozilla/ErrorResult.h"
19 #include "mozilla/MacroForEach.h"
20 #include "mozilla/OriginAttributes.h"
21 #include "mozilla/RefPtr.h"
22 #include "mozilla/RemoteLazyInputStreamThread.h"
23 #include "mozilla/dom/LocalStorageCommon.h"
24 #include "mozilla/dom/PBackgroundLSRequest.h"
25 #include "mozilla/dom/PBackgroundLSSharedTypes.h"
26 #include "mozilla/dom/PBackgroundLSSimpleRequest.h"
27 #include "mozilla/dom/Promise.h"
28 #include "mozilla/dom/quota/PromiseUtils.h"
29 #include "mozilla/dom/quota/QuotaManager.h"
30 #include "mozilla/ipc/BackgroundChild.h"
31 #include "mozilla/ipc/BackgroundUtils.h"
32 #include "mozilla/ipc/PBackgroundChild.h"
33 #include "mozilla/ipc/PBackgroundSharedTypes.h"
37 #include "nsIEventTarget.h"
38 #include "nsILocalStorageManager.h"
39 #include "nsIPrincipal.h"
40 #include "nsIRunnable.h"
41 #include "nsPIDOMWindow.h"
42 #include "nsStringFwd.h"
43 #include "nsThreadUtils.h"
45 #include "xpcpublic.h"
47 namespace mozilla::dom
{
51 class AsyncRequestHelper final
: public Runnable
,
52 public LSRequestChildCallback
{
55 * The AsyncRequestHelper has been created and dispatched to the
56 * RemoteLazyInputStream Thread.
60 * Start() has been invoked on the RemoteLazyInputStream Thread and
61 * LocalStorageManager2::StartRequest has been invoked from there, sending
62 * an IPC message to PBackground to service the request. We stay in this
63 * state until a response is received.
67 * A response has been received and AsyncRequestHelper has been dispatched
68 * back to the owning event target to call Finish().
72 * Finish() has been called on the main thread. The promise will be resolved
73 * according to the received response.
78 // The object we are issuing a request on behalf of. Present because of the
79 // need to invoke LocalStorageManager2::StartRequest off the main thread.
80 // Dropped on return to the main-thread in Finish().
81 RefPtr
<LocalStorageManager2
> mManager
;
82 // The thread the AsyncRequestHelper was created on. This should be the main
84 nsCOMPtr
<nsIEventTarget
> mOwningEventTarget
;
85 // The IPC actor handling the request with standard IPC allocation rules.
86 // Our reference is nulled in OnResponse which corresponds to the actor's
87 // __destroy__ method.
88 LSRequestChild
* mActor
;
89 RefPtr
<Promise
> mPromise
;
90 const LSRequestParams mParams
;
91 LSRequestResponse mResponse
;
96 AsyncRequestHelper(LocalStorageManager2
* aManager
, Promise
* aPromise
,
97 const LSRequestParams
& aParams
)
98 : Runnable("dom::LocalStorageManager2::AsyncRequestHelper"),
100 mOwningEventTarget(GetCurrentSerialEventTarget()),
105 mState(State::Initial
) {}
107 bool IsOnOwningThread() const {
108 MOZ_ASSERT(mOwningEventTarget
);
111 return NS_SUCCEEDED(mOwningEventTarget
->IsOnCurrentThread(¤t
)) &&
115 void AssertIsOnOwningThread() const {
116 MOZ_ASSERT(NS_IsMainThread());
117 MOZ_ASSERT(IsOnOwningThread());
123 ~AsyncRequestHelper() = default;
129 NS_DECL_ISUPPORTS_INHERITED
133 // LSRequestChildCallback
134 void OnResponse(const LSRequestResponse
& aResponse
) override
;
137 class SimpleRequestResolver final
: public LSSimpleRequestChildCallback
{
138 RefPtr
<Promise
> mPromise
;
141 explicit SimpleRequestResolver(Promise
* aPromise
) : mPromise(aPromise
) {}
143 NS_INLINE_DECL_REFCOUNTING(SimpleRequestResolver
, override
);
146 ~SimpleRequestResolver() = default;
148 void HandleResponse(nsresult aResponse
);
150 void HandleResponse(bool aResponse
);
152 void HandleResponse(const nsTArray
<LSItemInfo
>& aResponse
);
154 // LSRequestChildCallback
155 void OnResponse(const LSSimpleRequestResponse
& aResponse
) override
;
158 nsresult
CheckedPrincipalToPrincipalInfo(
159 nsIPrincipal
* aPrincipal
, mozilla::ipc::PrincipalInfo
& aPrincipalInfo
) {
160 MOZ_ASSERT(NS_IsMainThread());
161 MOZ_ASSERT(aPrincipal
);
163 nsresult rv
= PrincipalToPrincipalInfo(aPrincipal
, &aPrincipalInfo
);
164 if (NS_WARN_IF(NS_FAILED(rv
))) {
168 if (NS_WARN_IF(!quota::QuotaManager::IsPrincipalInfoValid(aPrincipalInfo
))) {
169 return NS_ERROR_FAILURE
;
172 if (aPrincipalInfo
.type() !=
173 mozilla::ipc::PrincipalInfo::TContentPrincipalInfo
&&
174 aPrincipalInfo
.type() !=
175 mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo
) {
176 return NS_ERROR_UNEXPECTED
;
184 LocalStorageManager2::LocalStorageManager2() {
185 MOZ_ASSERT(NS_IsMainThread());
186 MOZ_ASSERT(NextGenLocalStorageEnabled());
189 LocalStorageManager2::~LocalStorageManager2() { MOZ_ASSERT(NS_IsMainThread()); }
191 NS_IMPL_ISUPPORTS(LocalStorageManager2
, nsIDOMStorageManager
,
192 nsILocalStorageManager
)
195 LocalStorageManager2::PrecacheStorage(nsIPrincipal
* aPrincipal
,
196 nsIPrincipal
* aStoragePrincipal
,
198 MOZ_ASSERT(NS_IsMainThread());
199 MOZ_ASSERT(aPrincipal
);
200 MOZ_ASSERT(aStoragePrincipal
);
203 // This method was created as part of the e10s-ification of the old LS
204 // implementation to perform a preload in the content/current process. That's
205 // not how things work in LSNG. Instead everything happens in the parent
206 // process, triggered by the official preloading spot,
207 // ContentParent::AboutToLoadHttpDocumentForChild.
208 return NS_ERROR_NOT_IMPLEMENTED
;
212 LocalStorageManager2::CreateStorage(mozIDOMWindow
* aWindow
,
213 nsIPrincipal
* aPrincipal
,
214 nsIPrincipal
* aStoragePrincipal
,
215 const nsAString
& aDocumentURI
,
216 bool aPrivate
, Storage
** _retval
) {
217 MOZ_ASSERT(NS_IsMainThread());
218 MOZ_ASSERT(aPrincipal
);
219 MOZ_ASSERT(aStoragePrincipal
);
222 nsCOMPtr
<nsPIDOMWindowInner
> inner
= nsPIDOMWindowInner::From(aWindow
);
224 RefPtr
<LSObject
> object
;
225 nsresult rv
= LSObject::CreateForPrincipal(inner
, aPrincipal
,
226 aStoragePrincipal
, aDocumentURI
,
227 aPrivate
, getter_AddRefs(object
));
228 if (NS_WARN_IF(NS_FAILED(rv
))) {
232 object
.forget(_retval
);
237 LocalStorageManager2::GetStorage(mozIDOMWindow
* aWindow
,
238 nsIPrincipal
* aPrincipal
,
239 nsIPrincipal
* aStoragePrincipal
, bool aPrivate
,
241 MOZ_ASSERT(NS_IsMainThread());
242 MOZ_ASSERT(aPrincipal
);
243 MOZ_ASSERT(aStoragePrincipal
);
246 return NS_ERROR_NOT_IMPLEMENTED
;
250 LocalStorageManager2::CloneStorage(Storage
* aStorageToCloneFrom
) {
251 MOZ_ASSERT(NS_IsMainThread());
252 MOZ_ASSERT(aStorageToCloneFrom
);
254 // Cloning is specific to sessionStorage; state is forked when a new tab is
255 // opened from an existing tab.
256 return NS_ERROR_NOT_IMPLEMENTED
;
260 LocalStorageManager2::CheckStorage(nsIPrincipal
* aPrincipal
, Storage
* aStorage
,
262 MOZ_ASSERT(NS_IsMainThread());
263 MOZ_ASSERT(aPrincipal
);
264 MOZ_ASSERT(aStorage
);
267 // Only used by sessionStorage.
268 return NS_ERROR_NOT_IMPLEMENTED
;
272 LocalStorageManager2::GetNextGenLocalStorageEnabled(bool* aResult
) {
273 MOZ_ASSERT(NS_IsMainThread());
276 *aResult
= NextGenLocalStorageEnabled();
281 LocalStorageManager2::Preload(nsIPrincipal
* aPrincipal
, JSContext
* aContext
,
283 MOZ_ASSERT(NS_IsMainThread());
284 MOZ_ASSERT(aPrincipal
);
287 nsCString originAttrSuffix
;
289 nsresult rv
= aPrincipal
->GetStorageOriginKey(originKey
);
290 aPrincipal
->OriginAttributesRef().CreateSuffix(originAttrSuffix
);
292 return NS_ERROR_NOT_AVAILABLE
;
295 mozilla::ipc::PrincipalInfo principalInfo
;
296 rv
= CheckedPrincipalToPrincipalInfo(aPrincipal
, principalInfo
);
297 if (NS_WARN_IF(NS_FAILED(rv
))) {
301 RefPtr
<Promise
> promise
;
304 rv
= quota::CreatePromise(aContext
, getter_AddRefs(promise
));
305 if (NS_WARN_IF(NS_FAILED(rv
))) {
310 LSRequestCommonParams commonParams
;
311 commonParams
.principalInfo() = principalInfo
;
312 commonParams
.storagePrincipalInfo() = principalInfo
;
313 commonParams
.originKey() = originKey
;
315 LSRequestPreloadDatastoreParams
params(commonParams
);
317 RefPtr
<AsyncRequestHelper
> helper
=
318 new AsyncRequestHelper(this, promise
, params
);
320 // This will start and finish the async request on the RemoteLazyInputStream
322 // This must be done on RemoteLazyInputStream Thread because it's very likely
323 // that a content process will issue a prepare datastore request for the same
324 // principal while blocking the content process on the main thread.
325 // There would be a potential for deadlock if the preloading was initialized
326 // from the main thread of the parent process and a11y issued a synchronous
327 // message from the parent process to the content process (approximately at
328 // the same time) because the preload request wouldn't be able to respond
329 // to the Ready message by sending the Finish message which is needed to
330 // finish the preload request and unblock the prepare datastore request.
331 rv
= helper
->Dispatch();
332 if (NS_WARN_IF(NS_FAILED(rv
))) {
336 promise
.forget(_retval
);
341 LocalStorageManager2::IsPreloaded(nsIPrincipal
* aPrincipal
, JSContext
* aContext
,
343 MOZ_ASSERT(NS_IsMainThread());
344 MOZ_ASSERT(aPrincipal
);
347 RefPtr
<Promise
> promise
;
348 nsresult rv
= quota::CreatePromise(aContext
, getter_AddRefs(promise
));
349 if (NS_WARN_IF(NS_FAILED(rv
))) {
353 LSSimpleRequestPreloadedParams params
;
355 rv
= CheckedPrincipalToPrincipalInfo(aPrincipal
, params
.principalInfo());
356 if (NS_WARN_IF(NS_FAILED(rv
))) {
360 params
.storagePrincipalInfo() = params
.principalInfo();
362 rv
= StartSimpleRequest(promise
, params
);
363 if (NS_WARN_IF(NS_FAILED(rv
))) {
367 promise
.forget(_retval
);
372 LocalStorageManager2::GetState(nsIPrincipal
* aPrincipal
, JSContext
* aContext
,
374 MOZ_ASSERT(NS_IsMainThread());
375 MOZ_ASSERT(aPrincipal
);
378 RefPtr
<Promise
> promise
;
379 nsresult rv
= quota::CreatePromise(aContext
, getter_AddRefs(promise
));
380 if (NS_WARN_IF(NS_FAILED(rv
))) {
384 LSSimpleRequestGetStateParams params
;
386 rv
= CheckedPrincipalToPrincipalInfo(aPrincipal
, params
.principalInfo());
387 if (NS_WARN_IF(NS_FAILED(rv
))) {
391 params
.storagePrincipalInfo() = params
.principalInfo();
393 rv
= StartSimpleRequest(promise
, params
);
394 if (NS_WARN_IF(NS_FAILED(rv
))) {
398 promise
.forget(_retval
);
402 LSRequestChild
* LocalStorageManager2::StartRequest(
403 const LSRequestParams
& aParams
, LSRequestChildCallback
* aCallback
) {
404 AssertIsOnDOMFileThread();
406 mozilla::ipc::PBackgroundChild
* backgroundActor
=
407 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
408 if (NS_WARN_IF(!backgroundActor
)) {
412 auto actor
= new LSRequestChild();
414 if (!backgroundActor
->SendPBackgroundLSRequestConstructor(actor
, aParams
)) {
418 // Must set callback after calling SendPBackgroundLSRequestConstructor since
419 // it can be called synchronously when SendPBackgroundLSRequestConstructor
421 actor
->SetCallback(aCallback
);
426 nsresult
LocalStorageManager2::StartSimpleRequest(
427 Promise
* aPromise
, const LSSimpleRequestParams
& aParams
) {
428 MOZ_ASSERT(NS_IsMainThread());
429 MOZ_ASSERT(aPromise
);
431 mozilla::ipc::PBackgroundChild
* backgroundActor
=
432 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
433 if (NS_WARN_IF(!backgroundActor
)) {
434 return NS_ERROR_FAILURE
;
437 auto actor
= new LSSimpleRequestChild();
439 if (!backgroundActor
->SendPBackgroundLSSimpleRequestConstructor(actor
,
441 return NS_ERROR_FAILURE
;
444 RefPtr
<SimpleRequestResolver
> resolver
= new SimpleRequestResolver(aPromise
);
446 // Must set callback after calling SendPBackgroundLSRequestConstructor since
447 // it can be called synchronously when SendPBackgroundLSRequestConstructor
449 actor
->SetCallback(resolver
);
454 nsresult
AsyncRequestHelper::Dispatch() {
455 AssertIsOnOwningThread();
457 nsCOMPtr
<nsIEventTarget
> domFileThread
=
458 RemoteLazyInputStreamThread::GetOrCreate();
459 if (NS_WARN_IF(!domFileThread
)) {
460 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN
;
463 nsresult rv
= domFileThread
->Dispatch(this, NS_DISPATCH_NORMAL
);
464 if (NS_WARN_IF(NS_FAILED(rv
))) {
471 nsresult
AsyncRequestHelper::Start() {
472 AssertIsOnDOMFileThread();
473 MOZ_ASSERT(mState
== State::Initial
);
475 mState
= State::ResponsePending
;
477 LSRequestChild
* actor
= mManager
->StartRequest(mParams
, this);
478 if (NS_WARN_IF(!actor
)) {
479 return NS_ERROR_FAILURE
;
487 void AsyncRequestHelper::Finish() {
488 AssertIsOnOwningThread();
489 MOZ_ASSERT(mState
== State::Finishing
);
491 if (NS_WARN_IF(NS_FAILED(mResultCode
))) {
493 mPromise
->MaybeReject(mResultCode
);
496 switch (mResponse
.type()) {
497 case LSRequestResponse::Tnsresult
:
499 mPromise
->MaybeReject(mResponse
.get_nsresult());
503 case LSRequestResponse::TLSRequestPreloadDatastoreResponse
:
505 mPromise
->MaybeResolveWithUndefined();
509 MOZ_CRASH("Unknown response type!");
516 mState
= State::Complete
;
519 NS_IMPL_ISUPPORTS_INHERITED0(AsyncRequestHelper
, Runnable
)
522 AsyncRequestHelper::Run() {
530 case State::Finishing
:
535 MOZ_CRASH("Bad state!");
538 if (NS_WARN_IF(NS_FAILED(rv
)) && mState
!= State::Finishing
) {
539 if (NS_SUCCEEDED(mResultCode
)) {
543 mState
= State::Finishing
;
545 if (IsOnOwningThread()) {
549 mOwningEventTarget
->Dispatch(this, NS_DISPATCH_NORMAL
));
556 void AsyncRequestHelper::OnResponse(const LSRequestResponse
& aResponse
) {
557 AssertIsOnDOMFileThread();
558 MOZ_ASSERT(mState
== State::ResponsePending
);
562 mResponse
= aResponse
;
564 mState
= State::Finishing
;
566 MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget
->Dispatch(this, NS_DISPATCH_NORMAL
));
569 void SimpleRequestResolver::HandleResponse(nsresult aResponse
) {
570 MOZ_ASSERT(NS_IsMainThread());
571 MOZ_ASSERT(mPromise
);
573 mPromise
->MaybeReject(aResponse
);
576 void SimpleRequestResolver::HandleResponse(bool aResponse
) {
577 MOZ_ASSERT(NS_IsMainThread());
578 MOZ_ASSERT(mPromise
);
580 mPromise
->MaybeResolve(aResponse
);
583 [[nodiscard
]] static bool ToJSValue(JSContext
* aCx
,
584 const nsTArray
<LSItemInfo
>& aArgument
,
585 JS::MutableHandle
<JS::Value
> aValue
) {
586 JS::Rooted
<JSObject
*> obj(aCx
, JS_NewPlainObject(aCx
));
591 for (size_t i
= 0; i
< aArgument
.Length(); ++i
) {
592 const LSItemInfo
& itemInfo
= aArgument
[i
];
594 const nsString
& key
= itemInfo
.key();
596 JS::Rooted
<JS::Value
> value(aCx
);
597 if (!ToJSValue(aCx
, itemInfo
.value().AsString(), &value
)) {
601 if (!JS_DefineUCProperty(aCx
, obj
, key
.BeginReading(), key
.Length(), value
,
607 aValue
.setObject(*obj
);
611 void SimpleRequestResolver::HandleResponse(
612 const nsTArray
<LSItemInfo
>& aResponse
) {
613 MOZ_ASSERT(NS_IsMainThread());
614 MOZ_ASSERT(mPromise
);
616 mPromise
->MaybeResolve(aResponse
);
619 void SimpleRequestResolver::OnResponse(
620 const LSSimpleRequestResponse
& aResponse
) {
621 MOZ_ASSERT(NS_IsMainThread());
623 switch (aResponse
.type()) {
624 case LSSimpleRequestResponse::Tnsresult
:
625 HandleResponse(aResponse
.get_nsresult());
628 case LSSimpleRequestResponse::TLSSimpleRequestPreloadedResponse
:
630 aResponse
.get_LSSimpleRequestPreloadedResponse().preloaded());
633 case LSSimpleRequestResponse::TLSSimpleRequestGetStateResponse
:
635 aResponse
.get_LSSimpleRequestGetStateResponse().itemInfos());
639 MOZ_CRASH("Unknown response type!");
643 } // namespace mozilla::dom