123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659 |
- /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- #include "mozilla/dom/cache/Cache.h"
- #include "mozilla/dom/Headers.h"
- #include "mozilla/dom/InternalResponse.h"
- #include "mozilla/dom/Promise.h"
- #include "mozilla/dom/PromiseNativeHandler.h"
- #include "mozilla/dom/Response.h"
- #include "mozilla/dom/WorkerPrivate.h"
- #include "mozilla/dom/CacheBinding.h"
- #include "mozilla/dom/cache/AutoUtils.h"
- #include "mozilla/dom/cache/CacheChild.h"
- #include "mozilla/dom/cache/CacheWorkerHolder.h"
- #include "mozilla/dom/cache/ReadStream.h"
- #include "mozilla/ErrorResult.h"
- #include "mozilla/Preferences.h"
- #include "mozilla/Unused.h"
- #include "nsIGlobalObject.h"
- namespace mozilla {
- namespace dom {
- namespace cache {
- using mozilla::dom::workers::GetCurrentThreadWorkerPrivate;
- using mozilla::dom::workers::WorkerPrivate;
- using mozilla::ipc::PBackgroundChild;
- namespace {
- bool
- IsValidPutRequestURL(const nsAString& aUrl, ErrorResult& aRv)
- {
- bool validScheme = false;
- // make a copy because ProcessURL strips the fragmet
- NS_ConvertUTF16toUTF8 url(aUrl);
- TypeUtils::ProcessURL(url, &validScheme, nullptr, nullptr, aRv);
- if (aRv.Failed()) {
- return false;
- }
- if (!validScheme) {
- aRv.ThrowTypeError<MSG_INVALID_URL_SCHEME>(NS_LITERAL_STRING("Request"),
- aUrl);
- return false;
- }
- return true;
- }
- static bool
- IsValidPutRequestMethod(const Request& aRequest, ErrorResult& aRv)
- {
- nsAutoCString method;
- aRequest.GetMethod(method);
- if (!method.LowerCaseEqualsLiteral("get")) {
- NS_ConvertASCIItoUTF16 label(method);
- aRv.ThrowTypeError<MSG_INVALID_REQUEST_METHOD>(label);
- return false;
- }
- return true;
- }
- static bool
- IsValidPutRequestMethod(const RequestOrUSVString& aRequest, ErrorResult& aRv)
- {
- // If the provided request is a string URL, then it will default to
- // a valid http method automatically.
- if (!aRequest.IsRequest()) {
- return true;
- }
- return IsValidPutRequestMethod(aRequest.GetAsRequest(), aRv);
- }
- } // namespace
- // Helper class to wait for Add()/AddAll() fetch requests to complete and
- // then perform a PutAll() with the responses. This class holds a WorkerHolder
- // to keep the Worker thread alive. This is mainly to ensure that Add/AddAll
- // act the same as other Cache operations that directly create a CacheOpChild
- // actor.
- class Cache::FetchHandler final : public PromiseNativeHandler
- {
- public:
- FetchHandler(CacheWorkerHolder* aWorkerHolder, Cache* aCache,
- nsTArray<RefPtr<Request>>&& aRequestList, Promise* aPromise)
- : mWorkerHolder(aWorkerHolder)
- , mCache(aCache)
- , mRequestList(Move(aRequestList))
- , mPromise(aPromise)
- {
- MOZ_ASSERT_IF(!NS_IsMainThread(), mWorkerHolder);
- MOZ_DIAGNOSTIC_ASSERT(mCache);
- MOZ_DIAGNOSTIC_ASSERT(mPromise);
- }
- virtual void
- ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
- {
- NS_ASSERT_OWNINGTHREAD(FetchHandler);
- // Stop holding the worker alive when we leave this method.
- RefPtr<CacheWorkerHolder> workerHolder;
- workerHolder.swap(mWorkerHolder);
- // Promise::All() passed an array of fetch() Promises should give us
- // an Array of Response objects. The following code unwraps these
- // JS values back to an nsTArray<RefPtr<Response>>.
- AutoTArray<RefPtr<Response>, 256> responseList;
- responseList.SetCapacity(mRequestList.Length());
- bool isArray;
- if (NS_WARN_IF(!JS_IsArrayObject(aCx, aValue, &isArray) || !isArray)) {
- Fail();
- return;
- }
- JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
- uint32_t length;
- if (NS_WARN_IF(!JS_GetArrayLength(aCx, obj, &length))) {
- Fail();
- return;
- }
- for (uint32_t i = 0; i < length; ++i) {
- JS::Rooted<JS::Value> value(aCx);
- if (NS_WARN_IF(!JS_GetElement(aCx, obj, i, &value))) {
- Fail();
- return;
- }
- if (NS_WARN_IF(!value.isObject())) {
- Fail();
- return;
- }
- JS::Rooted<JSObject*> responseObj(aCx, &value.toObject());
- RefPtr<Response> response;
- nsresult rv = UNWRAP_OBJECT(Response, responseObj, response);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- Fail();
- return;
- }
- if (NS_WARN_IF(response->Type() == ResponseType::Error)) {
- Fail();
- return;
- }
- // Do not allow the convenience methods .add()/.addAll() to store failed
- // responses. A consequence of this is that these methods cannot be
- // used to store opaque or opaqueredirect responses since they always
- // expose a 0 status value.
- if (!response->Ok()) {
- uint32_t t = static_cast<uint32_t>(response->Type());
- NS_ConvertASCIItoUTF16 type(ResponseTypeValues::strings[t].value,
- ResponseTypeValues::strings[t].length);
- nsAutoString status;
- status.AppendInt(response->Status());
- nsAutoString url;
- mRequestList[i]->GetUrl(url);
- ErrorResult rv;
- rv.ThrowTypeError<MSG_CACHE_ADD_FAILED_RESPONSE>(type, status, url);
- // TODO: abort the fetch requests we have running (bug 1157434)
- mPromise->MaybeReject(rv);
- return;
- }
- responseList.AppendElement(Move(response));
- }
- MOZ_DIAGNOSTIC_ASSERT(mRequestList.Length() == responseList.Length());
- // Now store the unwrapped Response list in the Cache.
- ErrorResult result;
- RefPtr<Promise> put = mCache->PutAll(mRequestList, responseList, result);
- if (NS_WARN_IF(result.Failed())) {
- // TODO: abort the fetch requests we have running (bug 1157434)
- mPromise->MaybeReject(result);
- return;
- }
- // Chain the Cache::Put() promise to the original promise returned to
- // the content script.
- mPromise->MaybeResolve(put);
- }
- virtual void
- RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
- {
- NS_ASSERT_OWNINGTHREAD(FetchHandler);
- Fail();
- }
- private:
- ~FetchHandler()
- {
- }
- void
- Fail()
- {
- ErrorResult rv;
- rv.ThrowTypeError<MSG_FETCH_FAILED>();
- mPromise->MaybeReject(rv);
- }
- RefPtr<CacheWorkerHolder> mWorkerHolder;
- RefPtr<Cache> mCache;
- nsTArray<RefPtr<Request>> mRequestList;
- RefPtr<Promise> mPromise;
- NS_DECL_ISUPPORTS
- };
- NS_IMPL_ISUPPORTS0(Cache::FetchHandler)
- NS_IMPL_CYCLE_COLLECTING_ADDREF(mozilla::dom::cache::Cache);
- NS_IMPL_CYCLE_COLLECTING_RELEASE(mozilla::dom::cache::Cache);
- NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(mozilla::dom::cache::Cache, mGlobal);
- NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Cache)
- NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
- NS_INTERFACE_MAP_ENTRY(nsISupports)
- NS_INTERFACE_MAP_END
- Cache::Cache(nsIGlobalObject* aGlobal, CacheChild* aActor)
- : mGlobal(aGlobal)
- , mActor(aActor)
- {
- MOZ_DIAGNOSTIC_ASSERT(mGlobal);
- MOZ_DIAGNOSTIC_ASSERT(mActor);
- mActor->SetListener(this);
- }
- already_AddRefed<Promise>
- Cache::Match(const RequestOrUSVString& aRequest,
- const CacheQueryOptions& aOptions, ErrorResult& aRv)
- {
- if (NS_WARN_IF(!mActor)) {
- aRv.Throw(NS_ERROR_UNEXPECTED);
- return nullptr;
- }
- CacheChild::AutoLock actorLock(mActor);
- RefPtr<InternalRequest> ir = ToInternalRequest(aRequest, IgnoreBody, aRv);
- if (NS_WARN_IF(aRv.Failed())) {
- return nullptr;
- }
- CacheQueryParams params;
- ToCacheQueryParams(params, aOptions);
- AutoChildOpArgs args(this, CacheMatchArgs(CacheRequest(), params), 1);
- args.Add(ir, IgnoreBody, IgnoreInvalidScheme, aRv);
- if (NS_WARN_IF(aRv.Failed())) {
- return nullptr;
- }
- return ExecuteOp(args, aRv);
- }
- already_AddRefed<Promise>
- Cache::MatchAll(const Optional<RequestOrUSVString>& aRequest,
- const CacheQueryOptions& aOptions, ErrorResult& aRv)
- {
- if (NS_WARN_IF(!mActor)) {
- aRv.Throw(NS_ERROR_UNEXPECTED);
- return nullptr;
- }
- CacheChild::AutoLock actorLock(mActor);
- CacheQueryParams params;
- ToCacheQueryParams(params, aOptions);
- AutoChildOpArgs args(this, CacheMatchAllArgs(void_t(), params), 1);
- if (aRequest.WasPassed()) {
- RefPtr<InternalRequest> ir = ToInternalRequest(aRequest.Value(),
- IgnoreBody, aRv);
- if (aRv.Failed()) {
- return nullptr;
- }
- args.Add(ir, IgnoreBody, IgnoreInvalidScheme, aRv);
- if (aRv.Failed()) {
- return nullptr;
- }
- }
- return ExecuteOp(args, aRv);
- }
- already_AddRefed<Promise>
- Cache::Add(JSContext* aContext, const RequestOrUSVString& aRequest,
- ErrorResult& aRv)
- {
- if (NS_WARN_IF(!mActor)) {
- aRv.Throw(NS_ERROR_UNEXPECTED);
- return nullptr;
- }
- CacheChild::AutoLock actorLock(mActor);
- if (!IsValidPutRequestMethod(aRequest, aRv)) {
- return nullptr;
- }
- GlobalObject global(aContext, mGlobal->GetGlobalJSObject());
- MOZ_DIAGNOSTIC_ASSERT(!global.Failed());
- nsTArray<RefPtr<Request>> requestList(1);
- RefPtr<Request> request = Request::Constructor(global, aRequest,
- RequestInit(), aRv);
- if (NS_WARN_IF(aRv.Failed())) {
- return nullptr;
- }
- nsAutoString url;
- request->GetUrl(url);
- if (NS_WARN_IF(!IsValidPutRequestURL(url, aRv))) {
- return nullptr;
- }
- requestList.AppendElement(Move(request));
- return AddAll(global, Move(requestList), aRv);
- }
- already_AddRefed<Promise>
- Cache::AddAll(JSContext* aContext,
- const Sequence<OwningRequestOrUSVString>& aRequestList,
- ErrorResult& aRv)
- {
- if (NS_WARN_IF(!mActor)) {
- aRv.Throw(NS_ERROR_UNEXPECTED);
- return nullptr;
- }
- CacheChild::AutoLock actorLock(mActor);
- GlobalObject global(aContext, mGlobal->GetGlobalJSObject());
- MOZ_DIAGNOSTIC_ASSERT(!global.Failed());
- nsTArray<RefPtr<Request>> requestList(aRequestList.Length());
- for (uint32_t i = 0; i < aRequestList.Length(); ++i) {
- RequestOrUSVString requestOrString;
- if (aRequestList[i].IsRequest()) {
- requestOrString.SetAsRequest() = aRequestList[i].GetAsRequest();
- if (NS_WARN_IF(!IsValidPutRequestMethod(requestOrString.GetAsRequest(),
- aRv))) {
- return nullptr;
- }
- } else {
- requestOrString.SetAsUSVString().Rebind(
- aRequestList[i].GetAsUSVString().Data(),
- aRequestList[i].GetAsUSVString().Length());
- }
- RefPtr<Request> request = Request::Constructor(global, requestOrString,
- RequestInit(), aRv);
- if (NS_WARN_IF(aRv.Failed())) {
- return nullptr;
- }
- nsAutoString url;
- request->GetUrl(url);
- if (NS_WARN_IF(!IsValidPutRequestURL(url, aRv))) {
- return nullptr;
- }
- requestList.AppendElement(Move(request));
- }
- return AddAll(global, Move(requestList), aRv);
- }
- already_AddRefed<Promise>
- Cache::Put(const RequestOrUSVString& aRequest, Response& aResponse,
- ErrorResult& aRv)
- {
- if (NS_WARN_IF(!mActor)) {
- aRv.Throw(NS_ERROR_UNEXPECTED);
- return nullptr;
- }
- CacheChild::AutoLock actorLock(mActor);
- if (NS_WARN_IF(!IsValidPutRequestMethod(aRequest, aRv))) {
- return nullptr;
- }
- RefPtr<InternalRequest> ir = ToInternalRequest(aRequest, ReadBody, aRv);
- if (NS_WARN_IF(aRv.Failed())) {
- return nullptr;
- }
- AutoChildOpArgs args(this, CachePutAllArgs(), 1);
- args.Add(ir, ReadBody, TypeErrorOnInvalidScheme,
- aResponse, aRv);
- if (NS_WARN_IF(aRv.Failed())) {
- return nullptr;
- }
- return ExecuteOp(args, aRv);
- }
- already_AddRefed<Promise>
- Cache::Delete(const RequestOrUSVString& aRequest,
- const CacheQueryOptions& aOptions, ErrorResult& aRv)
- {
- if (NS_WARN_IF(!mActor)) {
- aRv.Throw(NS_ERROR_UNEXPECTED);
- return nullptr;
- }
- CacheChild::AutoLock actorLock(mActor);
- RefPtr<InternalRequest> ir = ToInternalRequest(aRequest, IgnoreBody, aRv);
- if (NS_WARN_IF(aRv.Failed())) {
- return nullptr;
- }
- CacheQueryParams params;
- ToCacheQueryParams(params, aOptions);
- AutoChildOpArgs args(this, CacheDeleteArgs(CacheRequest(), params), 1);
- args.Add(ir, IgnoreBody, IgnoreInvalidScheme, aRv);
- if (NS_WARN_IF(aRv.Failed())) {
- return nullptr;
- }
- return ExecuteOp(args, aRv);
- }
- already_AddRefed<Promise>
- Cache::Keys(const Optional<RequestOrUSVString>& aRequest,
- const CacheQueryOptions& aOptions, ErrorResult& aRv)
- {
- if (NS_WARN_IF(!mActor)) {
- aRv.Throw(NS_ERROR_UNEXPECTED);
- return nullptr;
- }
- CacheChild::AutoLock actorLock(mActor);
- CacheQueryParams params;
- ToCacheQueryParams(params, aOptions);
- AutoChildOpArgs args(this, CacheKeysArgs(void_t(), params), 1);
- if (aRequest.WasPassed()) {
- RefPtr<InternalRequest> ir = ToInternalRequest(aRequest.Value(),
- IgnoreBody, aRv);
- if (NS_WARN_IF(aRv.Failed())) {
- return nullptr;
- }
- args.Add(ir, IgnoreBody, IgnoreInvalidScheme, aRv);
- if (NS_WARN_IF(aRv.Failed())) {
- return nullptr;
- }
- }
- return ExecuteOp(args, aRv);
- }
- // static
- bool
- Cache::PrefEnabled(JSContext* aCx, JSObject* aObj)
- {
- using mozilla::dom::workers::WorkerPrivate;
- using mozilla::dom::workers::GetWorkerPrivateFromContext;
- // If we're on the main thread, then check the pref directly.
- if (NS_IsMainThread()) {
- bool enabled = false;
- Preferences::GetBool("dom.caches.enabled", &enabled);
- return enabled;
- }
- // Otherwise check the pref via the work private helper
- WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
- if (!workerPrivate) {
- return false;
- }
- return workerPrivate->DOMCachesEnabled();
- }
- nsISupports*
- Cache::GetParentObject() const
- {
- return mGlobal;
- }
- JSObject*
- Cache::WrapObject(JSContext* aContext, JS::Handle<JSObject*> aGivenProto)
- {
- return CacheBinding::Wrap(aContext, this, aGivenProto);
- }
- void
- Cache::DestroyInternal(CacheChild* aActor)
- {
- MOZ_DIAGNOSTIC_ASSERT(mActor);
- MOZ_DIAGNOSTIC_ASSERT(mActor == aActor);
- mActor->ClearListener();
- mActor = nullptr;
- }
- nsIGlobalObject*
- Cache::GetGlobalObject() const
- {
- return mGlobal;
- }
- #ifdef DEBUG
- void
- Cache::AssertOwningThread() const
- {
- NS_ASSERT_OWNINGTHREAD(Cache);
- }
- #endif
- PBackgroundChild*
- Cache::GetIPCManager()
- {
- NS_ASSERT_OWNINGTHREAD(Cache);
- MOZ_DIAGNOSTIC_ASSERT(mActor);
- return mActor->Manager();
- }
- Cache::~Cache()
- {
- NS_ASSERT_OWNINGTHREAD(Cache);
- if (mActor) {
- mActor->StartDestroyFromListener();
- // DestroyInternal() is called synchronously by StartDestroyFromListener().
- // So we should have already cleared the mActor.
- MOZ_DIAGNOSTIC_ASSERT(!mActor);
- }
- }
- already_AddRefed<Promise>
- Cache::ExecuteOp(AutoChildOpArgs& aOpArgs, ErrorResult& aRv)
- {
- MOZ_DIAGNOSTIC_ASSERT(mActor);
- RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
- if (NS_WARN_IF(!promise)) {
- return nullptr;
- }
- mActor->ExecuteOp(mGlobal, promise, this, aOpArgs.SendAsOpArgs());
- return promise.forget();
- }
- already_AddRefed<Promise>
- Cache::AddAll(const GlobalObject& aGlobal,
- nsTArray<RefPtr<Request>>&& aRequestList, ErrorResult& aRv)
- {
- MOZ_DIAGNOSTIC_ASSERT(mActor);
- // If there is no work to do, then resolve immediately
- if (aRequestList.IsEmpty()) {
- RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
- if (NS_WARN_IF(!promise)) {
- return nullptr;
- }
- promise->MaybeResolveWithUndefined();
- return promise.forget();
- }
- AutoTArray<RefPtr<Promise>, 256> fetchList;
- fetchList.SetCapacity(aRequestList.Length());
- // Begin fetching each request in parallel. For now, if an error occurs just
- // abandon our previous fetch calls. In theory we could cancel them in the
- // future once fetch supports it.
- for (uint32_t i = 0; i < aRequestList.Length(); ++i) {
- RequestOrUSVString requestOrString;
- requestOrString.SetAsRequest() = aRequestList[i];
- RefPtr<Promise> fetch = FetchRequest(mGlobal, requestOrString,
- RequestInit(), aRv);
- if (NS_WARN_IF(aRv.Failed())) {
- return nullptr;
- }
- fetchList.AppendElement(Move(fetch));
- }
- RefPtr<Promise> promise = Promise::Create(mGlobal, aRv);
- if (NS_WARN_IF(aRv.Failed())) {
- return nullptr;
- }
- RefPtr<FetchHandler> handler =
- new FetchHandler(mActor->GetWorkerHolder(), this,
- Move(aRequestList), promise);
- RefPtr<Promise> fetchPromise = Promise::All(aGlobal.Context(), fetchList, aRv);
- if (NS_WARN_IF(aRv.Failed())) {
- return nullptr;
- }
- fetchPromise->AppendNativeHandler(handler);
- return promise.forget();
- }
- already_AddRefed<Promise>
- Cache::PutAll(const nsTArray<RefPtr<Request>>& aRequestList,
- const nsTArray<RefPtr<Response>>& aResponseList,
- ErrorResult& aRv)
- {
- MOZ_DIAGNOSTIC_ASSERT(aRequestList.Length() == aResponseList.Length());
- if (NS_WARN_IF(!mActor)) {
- aRv.Throw(NS_ERROR_UNEXPECTED);
- return nullptr;
- }
- CacheChild::AutoLock actorLock(mActor);
- AutoChildOpArgs args(this, CachePutAllArgs(), aRequestList.Length());
- for (uint32_t i = 0; i < aRequestList.Length(); ++i) {
- RefPtr<InternalRequest> ir = aRequestList[i]->GetInternalRequest();
- args.Add(ir, ReadBody, TypeErrorOnInvalidScheme, *aResponseList[i], aRv);
- if (NS_WARN_IF(aRv.Failed())) {
- return nullptr;
- }
- }
- return ExecuteOp(args, aRv);
- }
- } // namespace cache
- } // namespace dom
- } // namespace mozilla
|