/* -*- 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 "FetchStream.h" #include "nsIDocument.h" #include "nsIGlobalObject.h" #include "nsIStreamLoader.h" #include "nsCharSeparatedTokenizer.h" #include "nsDOMString.h" #include "nsJSUtils.h" #include "nsNetUtil.h" #include "nsReadableUtils.h" #include "nsStreamUtils.h" #include "nsStringStream.h" #include "nsProxyRelease.h" #include "mozilla/ErrorResult.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/BodyUtil.h" #include "mozilla/dom/Exceptions.h" #include "mozilla/dom/DOMException.h" #include "mozilla/dom/FetchDriver.h" #include "mozilla/dom/File.h" #include "mozilla/dom/FormData.h" #include "mozilla/dom/Headers.h" #include "mozilla/dom/MutableBlobStreamListener.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/PromiseWorkerProxy.h" #include "mozilla/dom/Request.h" #include "mozilla/dom/Response.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/URLSearchParams.h" #include "mozilla/Telemetry.h" #include "BodyExtractor.h" #include "FetchObserver.h" #include "InternalRequest.h" #include "InternalResponse.h" #include "mozilla/dom/WorkerCommon.h" #include "mozilla/dom/WorkerPrivate.h" #include "mozilla/dom/WorkerRef.h" #include "mozilla/dom/WorkerRunnable.h" #include "mozilla/dom/WorkerScope.h" namespace mozilla { namespace dom { namespace { void AbortStream(JSContext* aCx, JS::Handle aStream) { if (!JS::ReadableStreamIsReadable(aStream)) { return; } RefPtr e = DOMException::Create(NS_ERROR_DOM_ABORT_ERR); JS::Rooted value(aCx); if (!GetOrCreateDOMReflector(aCx, e, &value)) { return; } JS::ReadableStreamError(aCx, aStream, value); } } // anonymous // This class helps the proxying of AbortSignal changes cross threads. class AbortSignalProxy final : public AbortFollower { // This is created and released on the main-thread. RefPtr mSignalMainThread; // The main-thread event target for runnable dispatching. nsCOMPtr mMainThreadEventTarget; // This value is used only for the creation of AbortSignal on the // main-thread. They are not updated. const bool mAborted; // This runnable propagates changes from the AbortSignal on workers to the // AbortSignal on main-thread. class AbortSignalProxyRunnable final : public Runnable { RefPtr mProxy; public: explicit AbortSignalProxyRunnable(AbortSignalProxy* aProxy) : Runnable("dom::AbortSignalProxy::AbortSignalProxyRunnable") , mProxy(aProxy) {} NS_IMETHOD Run() override { MOZ_ASSERT(NS_IsMainThread()); AbortSignal* signal = mProxy->GetOrCreateSignalForMainThread(); signal->Abort(); return NS_OK; } }; public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbortSignalProxy) AbortSignalProxy(AbortSignal* aSignal, nsIEventTarget* aMainThreadEventTarget) : mMainThreadEventTarget(aMainThreadEventTarget) , mAborted(aSignal->Aborted()) { MOZ_ASSERT(mMainThreadEventTarget); Follow(aSignal); } void Abort() override { RefPtr runnable = new AbortSignalProxyRunnable(this); mMainThreadEventTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); } AbortSignal* GetOrCreateSignalForMainThread() { MOZ_ASSERT(NS_IsMainThread()); if (!mSignalMainThread) { mSignalMainThread = new AbortSignal(mAborted); } return mSignalMainThread; } AbortSignal* GetSignalForTargetThread() { return mFollowingSignal; } void Shutdown() { Unfollow(); } private: ~AbortSignalProxy() { NS_ProxyRelease( "AbortSignalProxy::mSignalMainThread", mMainThreadEventTarget, mSignalMainThread.forget()); } }; class WorkerFetchResolver final : public FetchDriverObserver { // Thread-safe: RefPtr mPromiseProxy; RefPtr mSignalProxy; // Touched only on the worker thread. RefPtr mFetchObserver; RefPtr mWorkerRef; public: // Returns null if worker is shutting down. static already_AddRefed Create(WorkerPrivate* aWorkerPrivate, Promise* aPromise, AbortSignal* aSignal, FetchObserver* aObserver) { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); RefPtr proxy = PromiseWorkerProxy::Create(aWorkerPrivate, aPromise); if (!proxy) { return nullptr; } RefPtr signalProxy; if (aSignal) { signalProxy = new AbortSignalProxy(aSignal, aWorkerPrivate->MainThreadEventTarget()); } RefPtr r = new WorkerFetchResolver(proxy, signalProxy, aObserver); RefPtr workerRef = WeakWorkerRef::Create(aWorkerPrivate, [r]() { r->Shutdown(r->mWorkerRef->GetPrivate()); }); if (NS_WARN_IF(!workerRef)) { return nullptr; } r->mWorkerRef = Move(workerRef); return r.forget(); } AbortSignal* GetAbortSignalForMainThread() { MOZ_ASSERT(NS_IsMainThread()); if (!mSignalProxy) { return nullptr; } return mSignalProxy->GetOrCreateSignalForMainThread(); } AbortSignal* GetAbortSignalForTargetThread() { mPromiseProxy->GetWorkerPrivate()->AssertIsOnWorkerThread(); if (!mSignalProxy) { return nullptr; } return mSignalProxy->GetSignalForTargetThread(); } PromiseWorkerProxy* PromiseProxy() const { MOZ_ASSERT(NS_IsMainThread()); return mPromiseProxy; } Promise* WorkerPromise(WorkerPrivate* aWorkerPrivate) const { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); return mPromiseProxy->WorkerPromise(); } FetchObserver* GetFetchObserver(WorkerPrivate* aWorkerPrivate) const { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); return mFetchObserver; } void OnResponseAvailableInternal(InternalResponse* aResponse) override; void OnResponseEnd(FetchDriverObserver::EndReason eReason) override; bool NeedOnDataAvailable() override; void OnDataAvailable() override; void Shutdown(WorkerPrivate* aWorkerPrivate) { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); mPromiseProxy->CleanUp(); mFetchObserver = nullptr; if (mSignalProxy) { mSignalProxy->Shutdown(); } mWorkerRef = nullptr; } private: WorkerFetchResolver(PromiseWorkerProxy* aProxy, AbortSignalProxy* aSignalProxy, FetchObserver* aObserver) : mPromiseProxy(aProxy) , mSignalProxy(aSignalProxy) , mFetchObserver(aObserver) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(mPromiseProxy); } ~WorkerFetchResolver() {} virtual void FlushConsoleReport() override; }; class MainThreadFetchResolver final : public FetchDriverObserver { RefPtr mPromise; RefPtr mResponse; RefPtr mFetchObserver; RefPtr mSignal; const bool mMozErrors; nsCOMPtr mLoadGroup; NS_DECL_OWNINGTHREAD public: MainThreadFetchResolver(Promise* aPromise, FetchObserver* aObserver, AbortSignal* aSignal, bool aMozErrors) : mPromise(aPromise) , mFetchObserver(aObserver) , mSignal(aSignal) , mMozErrors(aMozErrors) {} void OnResponseAvailableInternal(InternalResponse* aResponse) override; void SetLoadGroup(nsILoadGroup* aLoadGroup) { mLoadGroup = aLoadGroup; } void OnResponseEnd(FetchDriverObserver::EndReason aReason) override { if (aReason == eAborted) { mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); } mFetchObserver = nullptr; FlushConsoleReport(); } bool NeedOnDataAvailable() override; void OnDataAvailable() override; private: ~MainThreadFetchResolver(); void FlushConsoleReport() override { mReporter->FlushConsoleReports(mLoadGroup); } }; class MainThreadFetchRunnable : public Runnable { RefPtr mResolver; const ClientInfo mClientInfo; const Maybe mController; RefPtr mRequest; public: MainThreadFetchRunnable(WorkerFetchResolver* aResolver, const ClientInfo& aClientInfo, const Maybe& aController, InternalRequest* aRequest) : Runnable("dom::MainThreadFetchRunnable") , mResolver(aResolver) , mClientInfo(aClientInfo) , mController(aController) , mRequest(aRequest) { MOZ_ASSERT(mResolver); } NS_IMETHOD Run() override { AssertIsOnMainThread(); RefPtr fetch; RefPtr proxy = mResolver->PromiseProxy(); { // Acquire the proxy mutex while getting data from the WorkerPrivate... MutexAutoLock lock(proxy->Lock()); if (proxy->CleanedUp()) { NS_WARNING("Aborting Fetch because worker already shut down"); return NS_OK; } WorkerPrivate* workerPrivate = proxy->GetWorkerPrivate(); MOZ_ASSERT(workerPrivate); nsCOMPtr principal = workerPrivate->GetPrincipal(); MOZ_ASSERT(principal); nsCOMPtr loadGroup = workerPrivate->GetLoadGroup(); MOZ_ASSERT(loadGroup); // We don't track if a worker is spawned from a tracking script for now, // so pass false as the last argument to FetchDriver(). fetch = new FetchDriver(mRequest, principal, loadGroup, workerPrivate->MainThreadEventTarget(), workerPrivate->GetPerformanceStorage(), false); nsAutoCString spec; if (proxy->GetWorkerPrivate()->GetBaseURI()) { proxy->GetWorkerPrivate()->GetBaseURI()->GetAsciiSpec(spec); } fetch->SetWorkerScript(spec); fetch->SetClientInfo(mClientInfo); fetch->SetController(mController); } RefPtr signal = mResolver->GetAbortSignalForMainThread(); // ...but release it before calling Fetch, because mResolver's callback can // be called synchronously and they want the mutex, too. return fetch->Fetch(signal, mResolver); } }; already_AddRefed FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput, const RequestInit& aInit, CallerType aCallerType, ErrorResult& aRv) { RefPtr p = Promise::Create(aGlobal, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } MOZ_ASSERT(aGlobal); // Double check that we have chrome privileges if the Request's content // policy type has been overridden. MOZ_ASSERT_IF(aInput.IsRequest() && aInput.GetAsRequest().IsContentPolicyTypeOverridden(), aCallerType == CallerType::System); AutoJSAPI jsapi; if (!jsapi.Init(aGlobal)) { aRv.Throw(NS_ERROR_NOT_AVAILABLE); return nullptr; } JSContext* cx = jsapi.cx(); JS::Rooted jsGlobal(cx, aGlobal->GetGlobalJSObject()); GlobalObject global(cx, jsGlobal); RefPtr request = Request::Constructor(global, aInput, aInit, aRv); if (aRv.Failed()) { return nullptr; } RefPtr r = request->GetInternalRequest(); RefPtr signal = request->GetSignal(); if (signal && signal->Aborted()) { // Already aborted signal rejects immediately. aRv.Throw(NS_ERROR_DOM_ABORT_ERR); return nullptr; } RefPtr observer; if (aInit.mObserve.WasPassed()) { observer = new FetchObserver(aGlobal, signal); aInit.mObserve.Value().HandleEvent(*observer); } if (NS_IsMainThread()) { nsCOMPtr window = do_QueryInterface(aGlobal); nsCOMPtr doc; nsCOMPtr loadGroup; nsIPrincipal* principal; bool isTrackingFetch = false; if (window) { doc = window->GetExtantDoc(); if (!doc) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } principal = doc->NodePrincipal(); loadGroup = doc->GetDocumentLoadGroup(); nsAutoCString fileNameString; if (nsJSUtils::GetCallingLocation(cx, fileNameString)) { isTrackingFetch = doc->IsScriptTracking(fileNameString); } } else { principal = aGlobal->PrincipalOrNull(); if (NS_WARN_IF(!principal)) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } nsresult rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), principal); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return nullptr; } } Telemetry::Accumulate(Telemetry::FETCH_IS_MAINTHREAD, 1); RefPtr resolver = new MainThreadFetchResolver(p, observer, signal, request->MozErrors()); RefPtr fetch = new FetchDriver(r, principal, loadGroup, aGlobal->EventTargetFor(TaskCategory::Other), nullptr, // PerformanceStorage isTrackingFetch); fetch->SetDocument(doc); resolver->SetLoadGroup(loadGroup); aRv = fetch->Fetch(signal, resolver); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } } else { WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(worker); Telemetry::Accumulate(Telemetry::FETCH_IS_MAINTHREAD, 0); if (worker->IsServiceWorker()) { r->SetSkipServiceWorker(); } RefPtr resolver = WorkerFetchResolver::Create(worker, p, signal, observer); if (!resolver) { NS_WARNING("Could not keep the worker alive."); aRv.Throw(NS_ERROR_DOM_ABORT_ERR); return nullptr; } RefPtr run = new MainThreadFetchRunnable(resolver, worker->GetClientInfo(), worker->GetController(), r); worker->DispatchToMainThread(run.forget()); } return p.forget(); } void MainThreadFetchResolver::OnResponseAvailableInternal(InternalResponse* aResponse) { NS_ASSERT_OWNINGTHREAD(MainThreadFetchResolver); AssertIsOnMainThread(); if (aResponse->Type() != ResponseType::Error) { if (mFetchObserver) { mFetchObserver->SetState(FetchState::Complete); } nsCOMPtr go = mPromise->GetParentObject(); mResponse = new Response(go, aResponse, mSignal); mPromise->MaybeResolve(mResponse); } else { if (mFetchObserver) { mFetchObserver->SetState(FetchState::Errored); } if (mMozErrors) { mPromise->MaybeReject(aResponse->GetErrorCode()); return; } ErrorResult result; result.ThrowTypeError(); mPromise->MaybeReject(result); } } bool MainThreadFetchResolver::NeedOnDataAvailable() { NS_ASSERT_OWNINGTHREAD(MainThreadFetchResolver); return !!mFetchObserver; } void MainThreadFetchResolver::OnDataAvailable() { NS_ASSERT_OWNINGTHREAD(MainThreadFetchResolver); AssertIsOnMainThread(); if (!mFetchObserver) { return; } if (mFetchObserver->State() == FetchState::Requesting) { mFetchObserver->SetState(FetchState::Responding); } } MainThreadFetchResolver::~MainThreadFetchResolver() { NS_ASSERT_OWNINGTHREAD(MainThreadFetchResolver); } class WorkerFetchResponseRunnable final : public MainThreadWorkerRunnable { RefPtr mResolver; // Passed from main thread to worker thread after being initialized. RefPtr mInternalResponse; public: WorkerFetchResponseRunnable(WorkerPrivate* aWorkerPrivate, WorkerFetchResolver* aResolver, InternalResponse* aResponse) : MainThreadWorkerRunnable(aWorkerPrivate) , mResolver(aResolver) , mInternalResponse(aResponse) { MOZ_ASSERT(mResolver); } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); RefPtr promise = mResolver->WorkerPromise(aWorkerPrivate); RefPtr fetchObserver = mResolver->GetFetchObserver(aWorkerPrivate); if (mInternalResponse->Type() != ResponseType::Error) { if (fetchObserver) { fetchObserver->SetState(FetchState::Complete); } RefPtr global = aWorkerPrivate->GlobalScope(); RefPtr response = new Response(global, mInternalResponse, mResolver->GetAbortSignalForTargetThread()); promise->MaybeResolve(response); } else { if (fetchObserver) { fetchObserver->SetState(FetchState::Errored); } ErrorResult result; result.ThrowTypeError(); promise->MaybeReject(result); } return true; } }; class WorkerDataAvailableRunnable final : public MainThreadWorkerRunnable { RefPtr mResolver; public: WorkerDataAvailableRunnable(WorkerPrivate* aWorkerPrivate, WorkerFetchResolver* aResolver) : MainThreadWorkerRunnable(aWorkerPrivate) , mResolver(aResolver) { } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); RefPtr fetchObserver = mResolver->GetFetchObserver(aWorkerPrivate); if (fetchObserver && fetchObserver->State() == FetchState::Requesting) { fetchObserver->SetState(FetchState::Responding); } return true; } }; class WorkerFetchResponseEndBase { protected: RefPtr mResolver; public: explicit WorkerFetchResponseEndBase(WorkerFetchResolver* aResolver) : mResolver(aResolver) { MOZ_ASSERT(aResolver); } void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) { mResolver->Shutdown(aWorkerPrivate); } }; class WorkerFetchResponseEndRunnable final : public MainThreadWorkerRunnable , public WorkerFetchResponseEndBase { FetchDriverObserver::EndReason mReason; public: WorkerFetchResponseEndRunnable(WorkerPrivate* aWorkerPrivate, WorkerFetchResolver* aResolver, FetchDriverObserver::EndReason aReason) : MainThreadWorkerRunnable(aWorkerPrivate) , WorkerFetchResponseEndBase(aResolver) , mReason(aReason) { } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { if (mReason == FetchDriverObserver::eAborted) { mResolver->WorkerPromise(aWorkerPrivate)->MaybeReject(NS_ERROR_DOM_ABORT_ERR); } WorkerRunInternal(aWorkerPrivate); return true; } nsresult Cancel() override { // Execute Run anyway to make sure we cleanup our promise proxy to avoid // leaking the worker thread Run(); return WorkerRunnable::Cancel(); } }; class WorkerFetchResponseEndControlRunnable final : public MainThreadWorkerControlRunnable , public WorkerFetchResponseEndBase { public: WorkerFetchResponseEndControlRunnable(WorkerPrivate* aWorkerPrivate, WorkerFetchResolver* aResolver) : MainThreadWorkerControlRunnable(aWorkerPrivate) , WorkerFetchResponseEndBase(aResolver) { } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { WorkerRunInternal(aWorkerPrivate); return true; } // Control runnable cancel already calls Run(). }; void WorkerFetchResolver::OnResponseAvailableInternal(InternalResponse* aResponse) { AssertIsOnMainThread(); MutexAutoLock lock(mPromiseProxy->Lock()); if (mPromiseProxy->CleanedUp()) { return; } RefPtr r = new WorkerFetchResponseRunnable(mPromiseProxy->GetWorkerPrivate(), this, aResponse); if (!r->Dispatch()) { NS_WARNING("Could not dispatch fetch response"); } } bool WorkerFetchResolver::NeedOnDataAvailable() { AssertIsOnMainThread(); MutexAutoLock lock(mPromiseProxy->Lock()); return !!mFetchObserver; } void WorkerFetchResolver::OnDataAvailable() { AssertIsOnMainThread(); MutexAutoLock lock(mPromiseProxy->Lock()); if (mPromiseProxy->CleanedUp()) { return; } RefPtr r = new WorkerDataAvailableRunnable(mPromiseProxy->GetWorkerPrivate(), this); Unused << r->Dispatch(); } void WorkerFetchResolver::OnResponseEnd(FetchDriverObserver::EndReason aReason) { AssertIsOnMainThread(); MutexAutoLock lock(mPromiseProxy->Lock()); if (mPromiseProxy->CleanedUp()) { return; } FlushConsoleReport(); RefPtr r = new WorkerFetchResponseEndRunnable(mPromiseProxy->GetWorkerPrivate(), this, aReason); if (!r->Dispatch()) { RefPtr cr = new WorkerFetchResponseEndControlRunnable(mPromiseProxy->GetWorkerPrivate(), this); // This can fail if the worker thread is canceled or killed causing // the PromiseWorkerProxy to give up its WorkerRef immediately, // allowing the worker thread to become Dead. if (!cr->Dispatch()) { NS_WARNING("Failed to dispatch WorkerFetchResponseEndControlRunnable"); } } } void WorkerFetchResolver::FlushConsoleReport() { AssertIsOnMainThread(); MOZ_ASSERT(mPromiseProxy); if(!mReporter) { return; } WorkerPrivate* worker = mPromiseProxy->GetWorkerPrivate(); if (!worker) { mReporter->FlushReportsToConsole(0); return; } if (worker->IsServiceWorker()) { // Flush to service worker mReporter->FlushReportsToConsoleForServiceWorkerScope(worker->ServiceWorkerScope()); return; } if (worker->IsSharedWorker()) { // Flush to shared worker worker->FlushReportsToSharedWorkers(mReporter); return; } // Flush to dedicated worker mReporter->FlushConsoleReports(worker->GetLoadGroup()); } nsresult ExtractByteStreamFromBody(const fetch::OwningBodyInit& aBodyInit, nsIInputStream** aStream, nsCString& aContentTypeWithCharset, uint64_t& aContentLength) { MOZ_ASSERT(aStream); nsAutoCString charset; aContentTypeWithCharset.SetIsVoid(true); if (aBodyInit.IsArrayBuffer()) { BodyExtractor body(&aBodyInit.GetAsArrayBuffer()); return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, charset); } if (aBodyInit.IsArrayBufferView()) { BodyExtractor body(&aBodyInit.GetAsArrayBufferView()); return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, charset); } if (aBodyInit.IsBlob()) { Blob& blob = aBodyInit.GetAsBlob(); BodyExtractor body(&blob); return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, charset); } if (aBodyInit.IsFormData()) { FormData& formData = aBodyInit.GetAsFormData(); BodyExtractor body(&formData); return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, charset); } if (aBodyInit.IsUSVString()) { BodyExtractor body(&aBodyInit.GetAsUSVString()); return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, charset); } if (aBodyInit.IsURLSearchParams()) { URLSearchParams& usp = aBodyInit.GetAsURLSearchParams(); BodyExtractor body(&usp); return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, charset); } NS_NOTREACHED("Should never reach here"); return NS_ERROR_FAILURE; } nsresult ExtractByteStreamFromBody(const fetch::BodyInit& aBodyInit, nsIInputStream** aStream, nsCString& aContentTypeWithCharset, uint64_t& aContentLength) { MOZ_ASSERT(aStream); MOZ_ASSERT(!*aStream); nsAutoCString charset; aContentTypeWithCharset.SetIsVoid(true); if (aBodyInit.IsArrayBuffer()) { BodyExtractor body(&aBodyInit.GetAsArrayBuffer()); return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, charset); } if (aBodyInit.IsArrayBufferView()) { BodyExtractor body(&aBodyInit.GetAsArrayBufferView()); return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, charset); } if (aBodyInit.IsBlob()) { BodyExtractor body(&aBodyInit.GetAsBlob()); return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, charset); } if (aBodyInit.IsFormData()) { BodyExtractor body(&aBodyInit.GetAsFormData()); return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, charset); } if (aBodyInit.IsUSVString()) { BodyExtractor body(&aBodyInit.GetAsUSVString()); return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, charset); } if (aBodyInit.IsURLSearchParams()) { BodyExtractor body(&aBodyInit.GetAsURLSearchParams()); return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, charset); } NS_NOTREACHED("Should never reach here"); return NS_ERROR_FAILURE; } nsresult ExtractByteStreamFromBody(const fetch::ResponseBodyInit& aBodyInit, nsIInputStream** aStream, nsCString& aContentTypeWithCharset, uint64_t& aContentLength) { MOZ_ASSERT(aStream); MOZ_ASSERT(!*aStream); // ReadableStreams should be handled by // BodyExtractorReadableStream::GetAsStream. MOZ_ASSERT(!aBodyInit.IsReadableStream()); nsAutoCString charset; aContentTypeWithCharset.SetIsVoid(true); if (aBodyInit.IsArrayBuffer()) { BodyExtractor body(&aBodyInit.GetAsArrayBuffer()); return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, charset); } if (aBodyInit.IsArrayBufferView()) { BodyExtractor body(&aBodyInit.GetAsArrayBufferView()); return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, charset); } if (aBodyInit.IsBlob()) { BodyExtractor body(&aBodyInit.GetAsBlob()); return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, charset); } if (aBodyInit.IsFormData()) { BodyExtractor body(&aBodyInit.GetAsFormData()); return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, charset); } if (aBodyInit.IsUSVString()) { BodyExtractor body(&aBodyInit.GetAsUSVString()); return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, charset); } if (aBodyInit.IsURLSearchParams()) { BodyExtractor body(&aBodyInit.GetAsURLSearchParams()); return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset, charset); } NS_NOTREACHED("Should never reach here"); return NS_ERROR_FAILURE; } template FetchBody::FetchBody(nsIGlobalObject* aOwner) : mOwner(aOwner) , mWorkerPrivate(nullptr) , mReadableStreamBody(nullptr) , mReadableStreamReader(nullptr) , mBodyUsed(false) { MOZ_ASSERT(aOwner); if (!NS_IsMainThread()) { mWorkerPrivate = GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(mWorkerPrivate); mMainThreadEventTarget = mWorkerPrivate->MainThreadEventTarget(); } else { mMainThreadEventTarget = aOwner->EventTargetFor(TaskCategory::Other); } MOZ_ASSERT(mMainThreadEventTarget); } template FetchBody::FetchBody(nsIGlobalObject* aOwner); template FetchBody::FetchBody(nsIGlobalObject* aOwner); template FetchBody::~FetchBody() { Unfollow(); } template FetchBody::~FetchBody(); template FetchBody::~FetchBody(); template bool FetchBody::BodyUsed() const { if (mBodyUsed) { return true; } // If this object is disturbed or locked, return false. if (mReadableStreamBody) { AutoJSAPI jsapi; if (!jsapi.Init(mOwner)) { return true; } JSContext* cx = jsapi.cx(); JS::Rooted body(cx, mReadableStreamBody); if (JS::ReadableStreamIsDisturbed(body) || JS::ReadableStreamIsLocked(body) || !JS::ReadableStreamIsReadable(body)) { return true; } } return false; } template bool FetchBody::BodyUsed() const; template bool FetchBody::BodyUsed() const; template void FetchBody::SetBodyUsed(JSContext* aCx, ErrorResult& aRv) { MOZ_ASSERT(aCx); MOZ_ASSERT(mOwner->EventTargetFor(TaskCategory::Other)->IsOnCurrentThread()); if (mBodyUsed) { return; } mBodyUsed = true; // If we already have a ReadableStreamBody and it has been created by DOM, we // have to lock it now because it can have been shared with other objects. if (mReadableStreamBody) { JS::Rooted readableStreamObj(aCx, mReadableStreamBody); if (JS::ReadableStreamGetMode(readableStreamObj) == JS::ReadableStreamMode::ExternalSource) { LockStream(aCx, readableStreamObj, aRv); if (NS_WARN_IF(aRv.Failed())) { return; } } else { // If this is not a native ReadableStream, let's activate the // FetchStreamReader. MOZ_ASSERT(mFetchStreamReader); JS::Rooted reader(aCx); mFetchStreamReader->StartConsuming(aCx, readableStreamObj, &reader, aRv); if (NS_WARN_IF(aRv.Failed())) { return; } mReadableStreamReader = reader; } } } template void FetchBody::SetBodyUsed(JSContext* aCx, ErrorResult& aRv); template void FetchBody::SetBodyUsed(JSContext* aCx, ErrorResult& aRv); template already_AddRefed FetchBody::ConsumeBody(JSContext* aCx, FetchConsumeType aType, ErrorResult& aRv) { RefPtr signal = DerivedClass()->GetSignal(); if (signal && signal->Aborted()) { aRv.Throw(NS_ERROR_DOM_ABORT_ERR); return nullptr; } if (BodyUsed()) { aRv.ThrowTypeError(); return nullptr; } SetBodyUsed(aCx, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } nsCOMPtr global = DerivedClass()->GetParentObject(); RefPtr promise = FetchBodyConsumer::Create(global, mMainThreadEventTarget, this, signal, aType, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } return promise.forget(); } template already_AddRefed FetchBody::ConsumeBody(JSContext* aCx, FetchConsumeType aType, ErrorResult& aRv); template already_AddRefed FetchBody::ConsumeBody(JSContext* aCx, FetchConsumeType aType, ErrorResult& aRv); template void FetchBody::SetMimeType() { // Extract mime type. ErrorResult result; nsCString contentTypeValues; MOZ_ASSERT(DerivedClass()->GetInternalHeaders()); DerivedClass()->GetInternalHeaders()->Get(NS_LITERAL_CSTRING("Content-Type"), contentTypeValues, result); MOZ_ALWAYS_TRUE(!result.Failed()); // HTTP ABNF states Content-Type may have only one value. // This is from the "parse a header value" of the fetch spec. if (!contentTypeValues.IsVoid() && contentTypeValues.Find(",") == -1) { mMimeType = contentTypeValues; ToLowerCase(mMimeType); } } template void FetchBody::SetMimeType(); template void FetchBody::SetMimeType(); template void FetchBody::SetReadableStreamBody(JSContext* aCx, JSObject* aBody) { MOZ_ASSERT(!mReadableStreamBody); MOZ_ASSERT(aBody); mReadableStreamBody = aBody; RefPtr signal = DerivedClass()->GetSignal(); if (!signal) { return; } bool aborted = signal->Aborted(); if (aborted) { JS::Rooted body(aCx, mReadableStreamBody); AbortStream(aCx, body); } else if (!IsFollowing()) { Follow(signal); } } template void FetchBody::SetReadableStreamBody(JSContext* aCx, JSObject* aBody); template void FetchBody::SetReadableStreamBody(JSContext* aCx, JSObject* aBody); template void FetchBody::GetBody(JSContext* aCx, JS::MutableHandle aBodyOut, ErrorResult& aRv) { if (mReadableStreamBody) { aBodyOut.set(mReadableStreamBody); return; } nsCOMPtr inputStream; DerivedClass()->GetBody(getter_AddRefs(inputStream)); if (!inputStream) { aBodyOut.set(nullptr); return; } JS::Rooted body(aCx); FetchStream::Create(aCx, this, DerivedClass()->GetParentObject(), inputStream, &body, aRv); if (NS_WARN_IF(aRv.Failed())) { return; } MOZ_ASSERT(body); // If the body has been already consumed, we lock the stream. if (BodyUsed()) { LockStream(aCx, body, aRv); if (NS_WARN_IF(aRv.Failed())) { return; } } RefPtr signal = DerivedClass()->GetSignal(); if (signal) { if (signal->Aborted()) { AbortStream(aCx, body); } else if (!IsFollowing()) { Follow(signal); } } mReadableStreamBody = body; aBodyOut.set(mReadableStreamBody); } template void FetchBody::GetBody(JSContext* aCx, JS::MutableHandle aMessage, ErrorResult& aRv); template void FetchBody::GetBody(JSContext* aCx, JS::MutableHandle aMessage, ErrorResult& aRv); template void FetchBody::LockStream(JSContext* aCx, JS::HandleObject aStream, ErrorResult& aRv) { MOZ_ASSERT(JS::ReadableStreamGetMode(aStream) == JS::ReadableStreamMode::ExternalSource); // This is native stream, creating a reader will not execute any JS code. JS::Rooted reader(aCx, JS::ReadableStreamGetReader(aCx, aStream, JS::ReadableStreamReaderMode::Default)); if (!reader) { aRv.StealExceptionFromJSContext(aCx); return; } mReadableStreamReader = reader; } template void FetchBody::LockStream(JSContext* aCx, JS::HandleObject aStream, ErrorResult& aRv); template void FetchBody::LockStream(JSContext* aCx, JS::HandleObject aStream, ErrorResult& aRv); template void FetchBody::MaybeTeeReadableStreamBody(JSContext* aCx, JS::MutableHandle aBodyOut, FetchStreamReader** aStreamReader, nsIInputStream** aInputStream, ErrorResult& aRv) { MOZ_DIAGNOSTIC_ASSERT(aStreamReader); MOZ_DIAGNOSTIC_ASSERT(aInputStream); MOZ_DIAGNOSTIC_ASSERT(!BodyUsed()); aBodyOut.set(nullptr); *aStreamReader = nullptr; *aInputStream = nullptr; if (!mReadableStreamBody) { return; } JS::Rooted stream(aCx, mReadableStreamBody); // If this is a ReadableStream with an external source, this has been // generated by a Fetch. In this case, Fetch will be able to recreate it // again when GetBody() is called. if (JS::ReadableStreamGetMode(stream) == JS::ReadableStreamMode::ExternalSource) { aBodyOut.set(nullptr); return; } JS::Rooted branch1(aCx); JS::Rooted branch2(aCx); if (!JS::ReadableStreamTee(aCx, stream, &branch1, &branch2)) { aRv.StealExceptionFromJSContext(aCx); return; } mReadableStreamBody = branch1; aBodyOut.set(branch2); aRv = FetchStreamReader::Create(aCx, mOwner, aStreamReader, aInputStream); if (NS_WARN_IF(aRv.Failed())) { return; } } template void FetchBody::MaybeTeeReadableStreamBody(JSContext* aCx, JS::MutableHandle aMessage, FetchStreamReader** aStreamReader, nsIInputStream** aInputStream, ErrorResult& aRv); template void FetchBody::MaybeTeeReadableStreamBody(JSContext* aCx, JS::MutableHandle aMessage, FetchStreamReader** aStreamReader, nsIInputStream** aInputStream, ErrorResult& aRv); template void FetchBody::Abort() { MOZ_ASSERT(mReadableStreamBody); AutoJSAPI jsapi; if (!jsapi.Init(mOwner)) { return; } JSContext* cx = jsapi.cx(); JS::Rooted body(cx, mReadableStreamBody); AbortStream(cx, body); } template void FetchBody::Abort(); template void FetchBody::Abort(); } // namespace dom } // namespace mozilla