/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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 "Fetch.h" #include "FetchConsumer.h" #include "nsIInputStreamPump.h" #include "nsProxyRelease.h" #include "WorkerPrivate.h" #include "WorkerRunnable.h" #include "WorkerScope.h" #include "Workers.h" namespace mozilla { namespace dom { using namespace workers; namespace { template class FetchBodyWorkerHolder final : public workers::WorkerHolder { RefPtr> mConsumer; bool mWasNotified; public: explicit FetchBodyWorkerHolder(FetchBodyConsumer* aConsumer) : workers::WorkerHolder("FetchBodyWorkerHolder") , mConsumer(aConsumer) , mWasNotified(false) { MOZ_ASSERT(aConsumer); } ~FetchBodyWorkerHolder() = default; bool Notify(workers::Status aStatus) override { MOZ_ASSERT(aStatus > workers::Running); if (!mWasNotified) { mWasNotified = true; mConsumer->ShutDownMainThreadConsuming(); } return true; } }; template class BeginConsumeBodyRunnable final : public Runnable { RefPtr> mFetchBodyConsumer; public: explicit BeginConsumeBodyRunnable(FetchBodyConsumer* aConsumer) : Runnable("BeginConsumeBodyRunnable") , mFetchBodyConsumer(aConsumer) { } NS_IMETHOD Run() override { mFetchBodyConsumer->BeginConsumeBodyMainThread(); return NS_OK; } }; /* * Called on successfully reading the complete stream. */ template class ContinueConsumeBodyRunnable final : public MainThreadWorkerRunnable { RefPtr> mFetchBodyConsumer; nsresult mStatus; uint32_t mLength; uint8_t* mResult; public: ContinueConsumeBodyRunnable(FetchBodyConsumer* aFetchBodyConsumer, nsresult aStatus, uint32_t aLength, uint8_t* aResult) : MainThreadWorkerRunnable(aFetchBodyConsumer->GetWorkerPrivate()) , mFetchBodyConsumer(aFetchBodyConsumer) , mStatus(aStatus) , mLength(aLength) , mResult(aResult) { MOZ_ASSERT(NS_IsMainThread()); } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { mFetchBodyConsumer->ContinueConsumeBody(mStatus, mLength, mResult); return true; } }; template class FailConsumeBodyWorkerRunnable : public MainThreadWorkerControlRunnable { RefPtr> mBodyConsumer; public: explicit FailConsumeBodyWorkerRunnable(FetchBodyConsumer* aBodyConsumer) : MainThreadWorkerControlRunnable(aBodyConsumer->GetWorkerPrivate()) , mBodyConsumer(aBodyConsumer) { AssertIsOnMainThread(); } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { mBodyConsumer->ContinueConsumeBody(NS_ERROR_FAILURE, 0, nullptr); return true; } }; /* * In case of failure to create a stream pump or dispatch stream completion to * worker, ensure we cleanup properly. Thread agnostic. */ template class MOZ_STACK_CLASS AutoFailConsumeBody final { RefPtr> mBodyConsumer; public: explicit AutoFailConsumeBody(FetchBodyConsumer* aBodyConsumer) : mBodyConsumer(aBodyConsumer) {} ~AutoFailConsumeBody() { AssertIsOnMainThread(); if (mBodyConsumer) { if (mBodyConsumer->GetWorkerPrivate()) { RefPtr> r = new FailConsumeBodyWorkerRunnable(mBodyConsumer); if (!r->Dispatch()) { MOZ_CRASH("We are going to leak"); } } else { mBodyConsumer->ContinueConsumeBody(NS_ERROR_FAILURE, 0, nullptr); } } } void DontFail() { mBodyConsumer = nullptr; } }; /* * Called on successfully reading the complete stream for Blob. */ template class ContinueConsumeBlobBodyRunnable final : public MainThreadWorkerRunnable { RefPtr> mFetchBodyConsumer; RefPtr mBlobImpl; public: ContinueConsumeBlobBodyRunnable(FetchBodyConsumer* aFetchBodyConsumer, BlobImpl* aBlobImpl) : MainThreadWorkerRunnable(aFetchBodyConsumer->GetWorkerPrivate()) , mFetchBodyConsumer(aFetchBodyConsumer) , mBlobImpl(aBlobImpl) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mBlobImpl); } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { mFetchBodyConsumer->ContinueConsumeBlobBody(mBlobImpl); return true; } }; template class ConsumeBodyDoneObserver : public nsIStreamLoaderObserver , public MutableBlobStorageCallback { RefPtr> mFetchBodyConsumer; public: NS_DECL_THREADSAFE_ISUPPORTS explicit ConsumeBodyDoneObserver(FetchBodyConsumer* aFetchBodyConsumer) : mFetchBodyConsumer(aFetchBodyConsumer) { } NS_IMETHOD OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aCtxt, nsresult aStatus, uint32_t aResultLength, const uint8_t* aResult) override { MOZ_ASSERT(NS_IsMainThread()); // The loading is completed. Let's nullify the pump before continuing the // consuming of the body. mFetchBodyConsumer->NullifyConsumeBodyPump(); uint8_t* nonconstResult = const_cast(aResult); if (mFetchBodyConsumer->GetWorkerPrivate()) { RefPtr> r = new ContinueConsumeBodyRunnable(mFetchBodyConsumer, aStatus, aResultLength, nonconstResult); if (!r->Dispatch()) { NS_WARNING("Could not dispatch ConsumeBodyRunnable"); // Return failure so that aResult is freed. return NS_ERROR_FAILURE; } } else { mFetchBodyConsumer->ContinueConsumeBody(aStatus, aResultLength, nonconstResult); } // FetchBody is responsible for data. return NS_SUCCESS_ADOPTED_DATA; } virtual void BlobStoreCompleted(MutableBlobStorage* aBlobStorage, Blob* aBlob, nsresult aRv) override { // On error. if (NS_FAILED(aRv)) { OnStreamComplete(nullptr, nullptr, aRv, 0, nullptr); return; } // The loading is completed. Let's nullify the pump before continuing the // consuming of the body. mFetchBodyConsumer->NullifyConsumeBodyPump(); MOZ_ASSERT(aBlob); if (mFetchBodyConsumer->GetWorkerPrivate()) { RefPtr> r = new ContinueConsumeBlobBodyRunnable(mFetchBodyConsumer, aBlob->Impl()); if (!r->Dispatch()) { NS_WARNING("Could not dispatch ConsumeBlobBodyRunnable"); return; } } else { mFetchBodyConsumer->ContinueConsumeBlobBody(aBlob->Impl()); } } private: virtual ~ConsumeBodyDoneObserver() { } }; template NS_IMPL_ADDREF(ConsumeBodyDoneObserver) template NS_IMPL_RELEASE(ConsumeBodyDoneObserver) template NS_INTERFACE_MAP_BEGIN(ConsumeBodyDoneObserver) NS_INTERFACE_MAP_ENTRY(nsIStreamLoaderObserver) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamLoaderObserver) NS_INTERFACE_MAP_END } // anonymous template /* static */ already_AddRefed FetchBodyConsumer::Create(nsIGlobalObject* aGlobal, nsIEventTarget* aMainThreadEventTarget, FetchBody* aBody, AbortSignal* aSignal, FetchConsumeType aType, ErrorResult& aRv) { MOZ_ASSERT(aBody); MOZ_ASSERT(aMainThreadEventTarget); nsCOMPtr bodyStream; aBody->DerivedClass()->GetBody(getter_AddRefs(bodyStream)); if (!bodyStream) { aRv = NS_NewCStringInputStream(getter_AddRefs(bodyStream), EmptyCString()); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } } RefPtr promise = Promise::Create(aGlobal, aRv); if (aRv.Failed()) { return nullptr; } WorkerPrivate* workerPrivate = nullptr; if (!NS_IsMainThread()) { workerPrivate = GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(workerPrivate); } RefPtr> consumer = new FetchBodyConsumer(aMainThreadEventTarget, aGlobal, workerPrivate, aBody, bodyStream, promise, aType); if (!NS_IsMainThread()) { MOZ_ASSERT(workerPrivate); if (NS_WARN_IF(!consumer->RegisterWorkerHolder())) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } } else { nsCOMPtr os = mozilla::services::GetObserverService(); if (NS_WARN_IF(!os)) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } aRv = os->AddObserver(consumer, DOM_WINDOW_DESTROYED_TOPIC, true); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } aRv = os->AddObserver(consumer, DOM_WINDOW_FROZEN_TOPIC, true); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } } nsCOMPtr r = new BeginConsumeBodyRunnable(consumer); aRv = aMainThreadEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } if (aSignal) { consumer->Follow(aSignal); } return promise.forget(); } template void FetchBodyConsumer::ReleaseObject() { AssertIsOnTargetThread(); if (NS_IsMainThread()) { nsCOMPtr os = mozilla::services::GetObserverService(); if (os) { os->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC); os->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC); } } mGlobal = nullptr; mWorkerHolder = nullptr; #ifdef DEBUG mBody = nullptr; #endif Unfollow(); } template FetchBodyConsumer::FetchBodyConsumer(nsIEventTarget* aMainThreadEventTarget, nsIGlobalObject* aGlobalObject, WorkerPrivate* aWorkerPrivate, FetchBody* aBody, nsIInputStream* aBodyStream, Promise* aPromise, FetchConsumeType aType) : mTargetThread(NS_GetCurrentThread()) , mMainThreadEventTarget(aMainThreadEventTarget) #ifdef DEBUG , mBody(aBody) #endif , mBodyStream(aBodyStream) , mBlobStorageType(MutableBlobStorage::eOnlyInMemory) , mGlobal(aGlobalObject) , mWorkerPrivate(aWorkerPrivate) , mConsumeType(aType) , mConsumePromise(aPromise) , mBodyConsumed(false) , mShuttingDown(false) { MOZ_ASSERT(aMainThreadEventTarget); MOZ_ASSERT(aBody); MOZ_ASSERT(aBodyStream); MOZ_ASSERT(aPromise); const mozilla::UniquePtr& principalInfo = aBody->DerivedClass()->GetPrincipalInfo(); // We support temporary file for blobs only if the principal is known and // it's system or content not in private Browsing. if (principalInfo && (principalInfo->type() == mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo || (principalInfo->type() == mozilla::ipc::PrincipalInfo::TContentPrincipalInfo && principalInfo->get_ContentPrincipalInfo().attrs().mPrivateBrowsingId == 0))) { mBlobStorageType = MutableBlobStorage::eCouldBeInTemporaryFile; } mBodyMimeType = aBody->MimeType(); } template FetchBodyConsumer::~FetchBodyConsumer() { } template void FetchBodyConsumer::AssertIsOnTargetThread() const { MOZ_ASSERT(NS_GetCurrentThread() == mTargetThread); } template bool FetchBodyConsumer::RegisterWorkerHolder() { MOZ_ASSERT(mWorkerPrivate); mWorkerPrivate->AssertIsOnWorkerThread(); MOZ_ASSERT(!mWorkerHolder); mWorkerHolder.reset(new FetchBodyWorkerHolder(this)); if (!mWorkerHolder->HoldWorker(mWorkerPrivate, Closing)) { NS_WARNING("Failed to add workerHolder"); mWorkerHolder = nullptr; return false; } return true; } /* * BeginConsumeBodyMainThread() will automatically reject the consume promise * and clean up on any failures, so there is no need for callers to do so, * reflected in a lack of error return code. */ template void FetchBodyConsumer::BeginConsumeBodyMainThread() { AssertIsOnMainThread(); AutoFailConsumeBody autoReject(this); if (mShuttingDown) { // We haven't started yet, but we have been terminated. AutoFailConsumeBody // will dispatch a runnable to release resources. return; } nsCOMPtr pump; nsresult rv = NS_NewInputStreamPump(getter_AddRefs(pump), mBodyStream, 0, 0, false, mMainThreadEventTarget); if (NS_WARN_IF(NS_FAILED(rv))) { return; } RefPtr> p = new ConsumeBodyDoneObserver(this); nsCOMPtr listener; if (mConsumeType == CONSUME_BLOB) { listener = new MutableBlobStreamListener(mBlobStorageType, nullptr, mBodyMimeType, p, mMainThreadEventTarget); } else { nsCOMPtr loader; rv = NS_NewStreamLoader(getter_AddRefs(loader), p); if (NS_WARN_IF(NS_FAILED(rv))) { return; } listener = loader; } rv = pump->AsyncRead(listener, nullptr); if (NS_WARN_IF(NS_FAILED(rv))) { return; } // Now that everything succeeded, we can assign the pump to a pointer that // stays alive for the lifetime of the FetchConsumer. mConsumeBodyPump = pump; // It is ok for retargeting to fail and reads to happen on the main thread. autoReject.DontFail(); // Try to retarget, otherwise fall back to main thread. nsCOMPtr rr = do_QueryInterface(pump); if (rr) { nsCOMPtr sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); rv = rr->RetargetDeliveryTo(sts); if (NS_WARN_IF(NS_FAILED(rv))) { NS_WARNING("Retargeting failed"); } } } template void FetchBodyConsumer::ContinueConsumeBody(nsresult aStatus, uint32_t aResultLength, uint8_t* aResult) { AssertIsOnTargetThread(); if (mBodyConsumed) { return; } mBodyConsumed = true; // Just a precaution to ensure ContinueConsumeBody is not called out of // sync with a body read. MOZ_ASSERT(mBody->BodyUsed()); auto autoFree = mozilla::MakeScopeExit([&] { free(aResult); }); MOZ_ASSERT(mConsumePromise); RefPtr localPromise = mConsumePromise.forget(); RefPtr> self = this; auto autoReleaseObject = mozilla::MakeScopeExit([&] { self->ReleaseObject(); }); if (NS_WARN_IF(NS_FAILED(aStatus))) { localPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); } // Don't warn here since we warned above. if (NS_FAILED(aStatus)) { return; } // Finish successfully consuming body according to type. MOZ_ASSERT(aResult); AutoJSAPI jsapi; if (!jsapi.Init(mGlobal)) { localPromise->MaybeReject(NS_ERROR_UNEXPECTED); return; } JSContext* cx = jsapi.cx(); ErrorResult error; switch (mConsumeType) { case CONSUME_ARRAYBUFFER: { JS::Rooted arrayBuffer(cx); BodyUtil::ConsumeArrayBuffer(cx, &arrayBuffer, aResultLength, aResult, error); if (!error.Failed()) { JS::Rooted val(cx); val.setObjectOrNull(arrayBuffer); localPromise->MaybeResolve(cx, val); // ArrayBuffer takes over ownership. aResult = nullptr; } break; } case CONSUME_BLOB: { MOZ_CRASH("This should not happen."); break; } case CONSUME_FORMDATA: { nsCString data; data.Adopt(reinterpret_cast(aResult), aResultLength); aResult = nullptr; RefPtr fd = BodyUtil::ConsumeFormData(mGlobal, mBodyMimeType, data, error); if (!error.Failed()) { localPromise->MaybeResolve(fd); } break; } case CONSUME_TEXT: // fall through handles early exit. case CONSUME_JSON: { nsString decoded; if (NS_SUCCEEDED(BodyUtil::ConsumeText(aResultLength, aResult, decoded))) { if (mConsumeType == CONSUME_TEXT) { localPromise->MaybeResolve(decoded); } else { JS::Rooted json(cx); BodyUtil::ConsumeJson(cx, &json, decoded, error); if (!error.Failed()) { localPromise->MaybeResolve(cx, json); } } }; break; } default: NS_NOTREACHED("Unexpected consume body type"); } error.WouldReportJSException(); if (error.Failed()) { localPromise->MaybeReject(error); } } template void FetchBodyConsumer::ContinueConsumeBlobBody(BlobImpl* aBlobImpl) { AssertIsOnTargetThread(); MOZ_ASSERT(mConsumeType == CONSUME_BLOB); if (mBodyConsumed) { return; } mBodyConsumed = true; // Just a precaution to ensure ContinueConsumeBody is not called out of // sync with a body read. MOZ_ASSERT(mBody->BodyUsed()); MOZ_ASSERT(mConsumePromise); RefPtr localPromise = mConsumePromise.forget(); RefPtr blob = dom::Blob::Create(mGlobal, aBlobImpl); MOZ_ASSERT(blob); localPromise->MaybeResolve(blob); ReleaseObject(); } template void FetchBodyConsumer::ShutDownMainThreadConsuming() { if (!NS_IsMainThread()) { RefPtr> self = this; nsCOMPtr r = NS_NewRunnableFunction( "FetchBodyConsumer::ShutDownMainThreadConsuming", [self] () { self->ShutDownMainThreadConsuming(); }); mMainThreadEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL); return; } // We need this because maybe, mConsumeBodyPump has not been created yet. We // must be sure that we don't try to do it. mShuttingDown = true; if (mConsumeBodyPump) { mConsumeBodyPump->Cancel(NS_BINDING_ABORTED); mConsumeBodyPump = nullptr; } } template NS_IMETHODIMP FetchBodyConsumer::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { AssertIsOnMainThread(); MOZ_ASSERT((strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) || (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0)); nsCOMPtr window = do_QueryInterface(mGlobal); if (SameCOMIdentity(aSubject, window)) { ContinueConsumeBody(NS_BINDING_ABORTED, 0, nullptr); } return NS_OK; } template void FetchBodyConsumer::Abort() { AssertIsOnTargetThread(); ContinueConsumeBody(NS_ERROR_DOM_ABORT_ERR, 0, nullptr); } template NS_IMPL_ADDREF(FetchBodyConsumer) template NS_IMPL_RELEASE(FetchBodyConsumer) template NS_IMPL_QUERY_INTERFACE(FetchBodyConsumer, nsIObserver, nsISupportsWeakReference) } // namespace dom } // namespace mozilla