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/. */
7 #include "mozilla/dom/cache/Cache.h"
9 #include "js/Array.h" // JS::GetArrayLength, JS::IsArrayObject
10 #include "js/PropertyAndElement.h" // JS_GetElement
11 #include "mozilla/dom/Headers.h"
12 #include "mozilla/dom/InternalResponse.h"
13 #include "mozilla/dom/Promise.h"
14 #include "mozilla/dom/PromiseNativeHandler.h"
15 #include "mozilla/dom/Response.h"
16 #include "mozilla/dom/RootedDictionary.h"
17 #include "mozilla/dom/WorkerPrivate.h"
18 #include "mozilla/dom/CacheBinding.h"
19 #include "mozilla/dom/cache/AutoUtils.h"
20 #include "mozilla/dom/cache/CacheChild.h"
21 #include "mozilla/dom/cache/CacheCommon.h"
22 #include "mozilla/dom/cache/CacheWorkerRef.h"
23 #include "mozilla/dom/quota/ResultExtensions.h"
24 #include "mozilla/ErrorResult.h"
25 #include "mozilla/Preferences.h"
26 #include "mozilla/Unused.h"
27 #include "nsIGlobalObject.h"
29 namespace mozilla::dom::cache
{
31 using mozilla::ipc::PBackgroundChild
;
35 enum class PutStatusPolicy
{ Default
, RequireOK
};
37 bool IsValidPutRequestURL(const nsACString
& aUrl
, ErrorResult
& aRv
) {
38 bool validScheme
= false;
40 // make a copy because ProcessURL strips the fragmet
41 nsAutoCString
url(aUrl
);
42 TypeUtils::ProcessURL(url
, &validScheme
, nullptr, nullptr, aRv
);
48 // `url` has been modified, so don't use it here.
49 aRv
.ThrowTypeError
<MSG_INVALID_URL_SCHEME
>("Request", aUrl
);
56 static bool IsValidPutRequestMethod(const Request
& aRequest
, ErrorResult
& aRv
) {
58 aRequest
.GetMethod(method
);
59 if (!method
.LowerCaseEqualsLiteral("get")) {
60 aRv
.ThrowTypeError
<MSG_INVALID_REQUEST_METHOD
>(method
);
67 static bool IsValidPutRequestMethod(const RequestOrUTF8String
& aRequest
,
69 // If the provided request is a string URL, then it will default to
70 // a valid http method automatically.
71 if (!aRequest
.IsRequest()) {
74 return IsValidPutRequestMethod(aRequest
.GetAsRequest(), aRv
);
77 static bool IsValidPutResponseStatus(Response
& aResponse
,
78 PutStatusPolicy aPolicy
,
80 if ((aPolicy
== PutStatusPolicy::RequireOK
&& !aResponse
.Ok()) ||
81 aResponse
.Status() == 206) {
83 aResponse
.GetUrl(url
);
84 aRv
.ThrowTypeError
<MSG_CACHE_ADD_FAILED_RESPONSE
>(
85 GetEnumString(aResponse
.Type()), IntToCString(aResponse
.Status()), url
);
94 // Helper class to wait for Add()/AddAll() fetch requests to complete and
95 // then perform a PutAll() with the responses. This class holds a WorkerRef
96 // to keep the Worker thread alive. This is mainly to ensure that Add/AddAll
97 // act the same as other Cache operations that directly create a CacheOpChild
99 class Cache::FetchHandler final
: public PromiseNativeHandler
{
101 FetchHandler(SafeRefPtr
<CacheWorkerRef
> aWorkerRef
, Cache
* aCache
,
102 nsTArray
<SafeRefPtr
<Request
>>&& aRequestList
, Promise
* aPromise
)
103 : mWorkerRef(std::move(aWorkerRef
)),
105 mRequestList(std::move(aRequestList
)),
107 MOZ_ASSERT_IF(!NS_IsMainThread(), mWorkerRef
);
108 MOZ_DIAGNOSTIC_ASSERT(mCache
);
109 MOZ_DIAGNOSTIC_ASSERT(mPromise
);
112 virtual void ResolvedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
,
113 ErrorResult
& aRv
) override
{
114 NS_ASSERT_OWNINGTHREAD(FetchHandler
);
116 // Stop holding the worker alive when we leave this method.
117 const SafeRefPtr
<CacheWorkerRef
> workerRef
= std::move(mWorkerRef
);
119 // Promise::All() passed an array of fetch() Promises should give us
120 // an Array of Response objects. The following code unwraps these
121 // JS values back to an nsTArray<RefPtr<Response>>.
123 AutoTArray
<RefPtr
<Response
>, 256> responseList
;
124 responseList
.SetCapacity(mRequestList
.Length());
126 const auto failOnErr
= [this](const auto) { Fail(); };
129 QM_TRY(OkIf(JS::IsArrayObject(aCx
, aValue
, &isArray
)), QM_VOID
, failOnErr
);
130 QM_TRY(OkIf(isArray
), QM_VOID
, failOnErr
);
132 JS::Rooted
<JSObject
*> obj(aCx
, &aValue
.toObject());
135 QM_TRY(OkIf(JS::GetArrayLength(aCx
, obj
, &length
)), QM_VOID
, failOnErr
);
137 for (uint32_t i
= 0; i
< length
; ++i
) {
138 JS::Rooted
<JS::Value
> value(aCx
);
140 QM_TRY(OkIf(JS_GetElement(aCx
, obj
, i
, &value
)), QM_VOID
, failOnErr
);
142 QM_TRY(OkIf(value
.isObject()), QM_VOID
, failOnErr
);
144 JS::Rooted
<JSObject
*> responseObj(aCx
, &value
.toObject());
146 RefPtr
<Response
> response
;
147 QM_TRY(MOZ_TO_RESULT(UNWRAP_OBJECT(Response
, responseObj
, response
)),
150 QM_TRY(OkIf(response
->Type() != ResponseType::Error
), QM_VOID
, failOnErr
);
152 // Do not allow the convenience methods .add()/.addAll() to store failed
153 // or invalid responses. A consequence of this is that these methods
154 // cannot be used to store opaque or opaqueredirect responses since they
155 // always expose a 0 status value.
156 ErrorResult errorResult
;
157 if (!IsValidPutResponseStatus(*response
, PutStatusPolicy::RequireOK
,
159 // TODO: abort the fetch requests we have running (bug 1157434)
160 mPromise
->MaybeReject(std::move(errorResult
));
164 responseList
.AppendElement(std::move(response
));
167 MOZ_DIAGNOSTIC_ASSERT(mRequestList
.Length() == responseList
.Length());
169 // Now store the unwrapped Response list in the Cache.
171 // TODO: Here we use the JSContext as received by the ResolvedCallback, and
172 // its state could be the wrong one. The spec doesn't say anything
173 // about it, yet (bug 1384006)
174 RefPtr
<Promise
> put
=
175 mCache
->PutAll(aCx
, mRequestList
, responseList
, result
);
176 result
.WouldReportJSException();
177 if (NS_WARN_IF(result
.Failed())) {
178 // TODO: abort the fetch requests we have running (bug 1157434)
179 mPromise
->MaybeReject(std::move(result
));
183 // Chain the Cache::Put() promise to the original promise returned to
184 // the content script.
185 mPromise
->MaybeResolve(put
);
188 virtual void RejectedCallback(JSContext
* aCx
, JS::Handle
<JS::Value
> aValue
,
189 ErrorResult
& aRv
) override
{
190 NS_ASSERT_OWNINGTHREAD(FetchHandler
);
195 ~FetchHandler() = default;
197 void Fail() { mPromise
->MaybeRejectWithTypeError
<MSG_FETCH_FAILED
>(); }
199 SafeRefPtr
<CacheWorkerRef
> mWorkerRef
;
200 RefPtr
<Cache
> mCache
;
201 nsTArray
<SafeRefPtr
<Request
>> mRequestList
;
202 RefPtr
<Promise
> mPromise
;
207 NS_IMPL_ISUPPORTS0(Cache::FetchHandler
)
209 NS_IMPL_CYCLE_COLLECTING_ADDREF(mozilla::dom::cache::Cache
);
210 NS_IMPL_CYCLE_COLLECTING_RELEASE(mozilla::dom::cache::Cache
);
211 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(mozilla::dom::cache::Cache
, mGlobal
);
213 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Cache
)
214 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
215 NS_INTERFACE_MAP_ENTRY(nsISupports
)
218 Cache::Cache(nsIGlobalObject
* aGlobal
, CacheChild
* aActor
, Namespace aNamespace
)
219 : mGlobal(aGlobal
), mActor(aActor
), mNamespace(aNamespace
) {
220 MOZ_DIAGNOSTIC_ASSERT(mGlobal
);
221 MOZ_DIAGNOSTIC_ASSERT(mActor
);
222 MOZ_DIAGNOSTIC_ASSERT(mNamespace
!= INVALID_NAMESPACE
);
223 mActor
->SetListener(this);
226 already_AddRefed
<Promise
> Cache::Match(JSContext
* aCx
,
227 const RequestOrUTF8String
& aRequest
,
228 const CacheQueryOptions
& aOptions
,
230 if (NS_WARN_IF(!mActor
)) {
231 aRv
.Throw(NS_ERROR_UNEXPECTED
);
235 CacheChild::AutoLock
actorLock(*mActor
);
237 SafeRefPtr
<InternalRequest
> ir
=
238 ToInternalRequest(aCx
, aRequest
, IgnoreBody
, aRv
);
239 if (NS_WARN_IF(aRv
.Failed())) {
243 CacheQueryParams params
;
244 ToCacheQueryParams(params
, aOptions
);
246 AutoChildOpArgs
args(
247 this, CacheMatchArgs(CacheRequest(), params
, GetOpenMode()), 1);
249 args
.Add(*ir
, IgnoreBody
, IgnoreInvalidScheme
, aRv
);
250 if (NS_WARN_IF(aRv
.Failed())) {
254 return ExecuteOp(args
, aRv
);
257 already_AddRefed
<Promise
> Cache::MatchAll(
258 JSContext
* aCx
, const Optional
<RequestOrUTF8String
>& aRequest
,
259 const CacheQueryOptions
& aOptions
, ErrorResult
& aRv
) {
260 if (NS_WARN_IF(!mActor
)) {
261 aRv
.Throw(NS_ERROR_UNEXPECTED
);
265 CacheChild::AutoLock
actorLock(*mActor
);
267 CacheQueryParams params
;
268 ToCacheQueryParams(params
, aOptions
);
270 AutoChildOpArgs
args(this,
271 CacheMatchAllArgs(Nothing(), params
, GetOpenMode()), 1);
273 if (aRequest
.WasPassed()) {
274 SafeRefPtr
<InternalRequest
> ir
=
275 ToInternalRequest(aCx
, aRequest
.Value(), IgnoreBody
, aRv
);
280 args
.Add(*ir
, IgnoreBody
, IgnoreInvalidScheme
, aRv
);
286 return ExecuteOp(args
, aRv
);
289 already_AddRefed
<Promise
> Cache::Add(JSContext
* aContext
,
290 const RequestOrUTF8String
& aRequest
,
291 CallerType aCallerType
, ErrorResult
& aRv
) {
292 if (NS_WARN_IF(!mActor
)) {
293 aRv
.Throw(NS_ERROR_UNEXPECTED
);
297 CacheChild::AutoLock
actorLock(*mActor
);
299 if (!IsValidPutRequestMethod(aRequest
, aRv
)) {
303 GlobalObject
global(aContext
, mGlobal
->GetGlobalJSObject());
304 MOZ_DIAGNOSTIC_ASSERT(!global
.Failed());
306 nsTArray
<SafeRefPtr
<Request
>> requestList(1);
307 RootedDictionary
<RequestInit
> requestInit(aContext
);
308 SafeRefPtr
<Request
> request
=
309 Request::Constructor(global
, aRequest
, requestInit
, aRv
);
310 if (NS_WARN_IF(aRv
.Failed())) {
315 request
->GetUrl(url
);
316 if (NS_WARN_IF(!IsValidPutRequestURL(url
, aRv
))) {
320 requestList
.AppendElement(std::move(request
));
321 return AddAll(global
, std::move(requestList
), aCallerType
, aRv
);
324 already_AddRefed
<Promise
> Cache::AddAll(
326 const Sequence
<OwningRequestOrUTF8String
>& aRequestList
,
327 CallerType aCallerType
, ErrorResult
& aRv
) {
328 if (NS_WARN_IF(!mActor
)) {
329 aRv
.Throw(NS_ERROR_UNEXPECTED
);
333 CacheChild::AutoLock
actorLock(*mActor
);
335 GlobalObject
global(aContext
, mGlobal
->GetGlobalJSObject());
336 MOZ_DIAGNOSTIC_ASSERT(!global
.Failed());
338 nsTArray
<SafeRefPtr
<Request
>> requestList(aRequestList
.Length());
339 for (uint32_t i
= 0; i
< aRequestList
.Length(); ++i
) {
340 RequestOrUTF8String requestOrString
;
342 if (aRequestList
[i
].IsRequest()) {
343 requestOrString
.SetAsRequest() = aRequestList
[i
].GetAsRequest();
345 !IsValidPutRequestMethod(requestOrString
.GetAsRequest(), aRv
))) {
349 requestOrString
.SetAsUTF8String().ShareOrDependUpon(
350 aRequestList
[i
].GetAsUTF8String());
353 RootedDictionary
<RequestInit
> requestInit(aContext
);
354 SafeRefPtr
<Request
> request
=
355 Request::Constructor(global
, requestOrString
, requestInit
, aRv
);
356 if (NS_WARN_IF(aRv
.Failed())) {
361 request
->GetUrl(url
);
362 if (NS_WARN_IF(!IsValidPutRequestURL(url
, aRv
))) {
366 requestList
.AppendElement(std::move(request
));
369 return AddAll(global
, std::move(requestList
), aCallerType
, aRv
);
372 already_AddRefed
<Promise
> Cache::Put(JSContext
* aCx
,
373 const RequestOrUTF8String
& aRequest
,
374 Response
& aResponse
, ErrorResult
& aRv
) {
375 if (NS_WARN_IF(!mActor
)) {
376 aRv
.Throw(NS_ERROR_UNEXPECTED
);
380 CacheChild::AutoLock
actorLock(*mActor
);
382 if (NS_WARN_IF(!IsValidPutRequestMethod(aRequest
, aRv
))) {
386 if (!IsValidPutResponseStatus(aResponse
, PutStatusPolicy::Default
, aRv
)) {
390 if (NS_WARN_IF(aResponse
.GetPrincipalInfo() &&
391 aResponse
.GetPrincipalInfo()->type() ==
392 mozilla::ipc::PrincipalInfo::TExpandedPrincipalInfo
)) {
393 // WebExtensions Content Scripts can currently run fetch from their global
394 // which will end up to have an expanded principal, but we require that the
395 // contents of Cache storage for the content origin to be same-origin, and
396 // never an expanded principal (See Bug 1753810).
397 aRv
.ThrowSecurityError("Disallowed on WebExtension ContentScript Request");
401 SafeRefPtr
<InternalRequest
> ir
=
402 ToInternalRequest(aCx
, aRequest
, ReadBody
, aRv
);
403 if (NS_WARN_IF(aRv
.Failed())) {
407 AutoChildOpArgs
args(this, CachePutAllArgs(), 1);
409 args
.Add(aCx
, *ir
, ReadBody
, TypeErrorOnInvalidScheme
, aResponse
, aRv
);
410 if (NS_WARN_IF(aRv
.Failed())) {
414 return ExecuteOp(args
, aRv
);
417 already_AddRefed
<Promise
> Cache::Delete(JSContext
* aCx
,
418 const RequestOrUTF8String
& aRequest
,
419 const CacheQueryOptions
& aOptions
,
421 if (NS_WARN_IF(!mActor
)) {
422 aRv
.Throw(NS_ERROR_UNEXPECTED
);
426 CacheChild::AutoLock
actorLock(*mActor
);
428 SafeRefPtr
<InternalRequest
> ir
=
429 ToInternalRequest(aCx
, aRequest
, IgnoreBody
, aRv
);
430 if (NS_WARN_IF(aRv
.Failed())) {
434 CacheQueryParams params
;
435 ToCacheQueryParams(params
, aOptions
);
437 AutoChildOpArgs
args(this, CacheDeleteArgs(CacheRequest(), params
), 1);
439 args
.Add(*ir
, IgnoreBody
, IgnoreInvalidScheme
, aRv
);
440 if (NS_WARN_IF(aRv
.Failed())) {
444 return ExecuteOp(args
, aRv
);
447 already_AddRefed
<Promise
> Cache::Keys(
448 JSContext
* aCx
, const Optional
<RequestOrUTF8String
>& aRequest
,
449 const CacheQueryOptions
& aOptions
, ErrorResult
& aRv
) {
450 if (NS_WARN_IF(!mActor
)) {
451 aRv
.Throw(NS_ERROR_UNEXPECTED
);
455 CacheChild::AutoLock
actorLock(*mActor
);
457 CacheQueryParams params
;
458 ToCacheQueryParams(params
, aOptions
);
460 AutoChildOpArgs
args(this, CacheKeysArgs(Nothing(), params
, GetOpenMode()),
463 if (aRequest
.WasPassed()) {
464 SafeRefPtr
<InternalRequest
> ir
=
465 ToInternalRequest(aCx
, aRequest
.Value(), IgnoreBody
, aRv
);
466 if (NS_WARN_IF(aRv
.Failed())) {
470 args
.Add(*ir
, IgnoreBody
, IgnoreInvalidScheme
, aRv
);
471 if (NS_WARN_IF(aRv
.Failed())) {
476 return ExecuteOp(args
, aRv
);
479 nsISupports
* Cache::GetParentObject() const { return mGlobal
; }
481 JSObject
* Cache::WrapObject(JSContext
* aContext
,
482 JS::Handle
<JSObject
*> aGivenProto
) {
483 return Cache_Binding::Wrap(aContext
, this, aGivenProto
);
486 void Cache::DestroyInternal(CacheChild
* aActor
) {
487 MOZ_DIAGNOSTIC_ASSERT(mActor
);
488 MOZ_DIAGNOSTIC_ASSERT(mActor
== aActor
);
489 mActor
->ClearListener();
493 nsIGlobalObject
* Cache::GetGlobalObject() const { return mGlobal
; }
496 void Cache::AssertOwningThread() const { NS_ASSERT_OWNINGTHREAD(Cache
); }
499 PBackgroundChild
* Cache::GetIPCManager() {
500 NS_ASSERT_OWNINGTHREAD(Cache
);
501 MOZ_DIAGNOSTIC_ASSERT(mActor
);
502 return mActor
->Manager();
506 NS_ASSERT_OWNINGTHREAD(Cache
);
508 mActor
->StartDestroyFromListener();
509 // DestroyInternal() is called synchronously by StartDestroyFromListener().
510 // So we should have already cleared the mActor.
511 MOZ_DIAGNOSTIC_ASSERT(!mActor
);
515 already_AddRefed
<Promise
> Cache::ExecuteOp(AutoChildOpArgs
& aOpArgs
,
517 MOZ_DIAGNOSTIC_ASSERT(mActor
);
519 RefPtr
<Promise
> promise
= Promise::Create(mGlobal
, aRv
);
520 if (NS_WARN_IF(!promise
)) {
524 mActor
->ExecuteOp(mGlobal
, promise
, this, aOpArgs
.SendAsOpArgs());
525 return promise
.forget();
528 already_AddRefed
<Promise
> Cache::AddAll(
529 const GlobalObject
& aGlobal
, nsTArray
<SafeRefPtr
<Request
>>&& aRequestList
,
530 CallerType aCallerType
, ErrorResult
& aRv
) {
531 MOZ_DIAGNOSTIC_ASSERT(mActor
);
533 // If there is no work to do, then resolve immediately
534 if (aRequestList
.IsEmpty()) {
535 RefPtr
<Promise
> promise
= Promise::Create(mGlobal
, aRv
);
536 if (NS_WARN_IF(!promise
)) {
540 promise
->MaybeResolveWithUndefined();
541 return promise
.forget();
544 AutoTArray
<RefPtr
<Promise
>, 256> fetchList
;
545 fetchList
.SetCapacity(aRequestList
.Length());
547 // Begin fetching each request in parallel. For now, if an error occurs just
548 // abandon our previous fetch calls. In theory we could cancel them in the
549 // future once fetch supports it.
551 for (uint32_t i
= 0; i
< aRequestList
.Length(); ++i
) {
552 RequestOrUTF8String requestOrString
;
553 requestOrString
.SetAsRequest() = aRequestList
[i
].unsafeGetRawPtr();
554 RootedDictionary
<RequestInit
> requestInit(aGlobal
.Context());
555 RefPtr
<Promise
> fetch
=
556 FetchRequest(mGlobal
, requestOrString
, requestInit
, aCallerType
, aRv
);
557 if (NS_WARN_IF(aRv
.Failed())) {
561 fetchList
.AppendElement(std::move(fetch
));
564 RefPtr
<Promise
> promise
= Promise::Create(mGlobal
, aRv
);
565 if (NS_WARN_IF(aRv
.Failed())) {
569 RefPtr
<FetchHandler
> handler
=
570 new FetchHandler(mActor
->GetWorkerRefPtr().clonePtr(), this,
571 std::move(aRequestList
), promise
);
573 RefPtr
<Promise
> fetchPromise
=
574 Promise::All(aGlobal
.Context(), fetchList
, aRv
);
575 if (NS_WARN_IF(aRv
.Failed())) {
578 fetchPromise
->AppendNativeHandler(handler
);
580 return promise
.forget();
583 already_AddRefed
<Promise
> Cache::PutAll(
584 JSContext
* aCx
, const nsTArray
<SafeRefPtr
<Request
>>& aRequestList
,
585 const nsTArray
<RefPtr
<Response
>>& aResponseList
, ErrorResult
& aRv
) {
586 MOZ_DIAGNOSTIC_ASSERT(aRequestList
.Length() == aResponseList
.Length());
588 if (NS_WARN_IF(!mActor
)) {
589 aRv
.Throw(NS_ERROR_UNEXPECTED
);
593 CacheChild::AutoLock
actorLock(*mActor
);
595 AutoChildOpArgs
args(this, CachePutAllArgs(), aRequestList
.Length());
597 for (uint32_t i
= 0; i
< aRequestList
.Length(); ++i
) {
598 SafeRefPtr
<InternalRequest
> ir
= aRequestList
[i
]->GetInternalRequest();
599 args
.Add(aCx
, *ir
, ReadBody
, TypeErrorOnInvalidScheme
, *aResponseList
[i
],
601 if (NS_WARN_IF(aRv
.Failed())) {
606 return ExecuteOp(args
, aRv
);
609 OpenMode
Cache::GetOpenMode() const {
610 return mNamespace
== CHROME_ONLY_NAMESPACE
? OpenMode::Eager
: OpenMode::Lazy
;
613 } // namespace mozilla::dom::cache