Files
tubestation/dom/fetch/Fetch.cpp
Brindusan Cristian 1153f2c09e Backed out 12 changesets (bug 1425458) for mochitest failures on WorkerPrivate.cpp on a CLOSED TREE
Backed out changeset 11997de13778 (bug 1425458)
Backed out changeset 100b9d4f36bc (bug 1425458)
Backed out changeset a29e9dbb8c42 (bug 1425458)
Backed out changeset b96d58fd945c (bug 1425458)
Backed out changeset f140da44ba68 (bug 1425458)
Backed out changeset af56400233d9 (bug 1425458)
Backed out changeset 7034af4332e4 (bug 1425458)
Backed out changeset f70500179140 (bug 1425458)
Backed out changeset 793bbfc23257 (bug 1425458)
Backed out changeset 2efb375a8ffc (bug 1425458)
Backed out changeset 07e781e37451 (bug 1425458)
Backed out changeset e875f3702a5f (bug 1425458)
2018-01-24 20:47:48 +02:00

1459 lines
38 KiB
C++

/* -*- 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 "nsIThreadRetargetableRequest.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 "WorkerPrivate.h"
#include "WorkerRunnable.h"
#include "WorkerScope.h"
#include "Workers.h"
namespace mozilla {
namespace dom {
using namespace workers;
namespace {
void
AbortStream(JSContext* aCx, JS::Handle<JSObject*> aStream)
{
if (!JS::ReadableStreamIsReadable(aStream)) {
return;
}
RefPtr<DOMException> e = DOMException::Create(NS_ERROR_DOM_ABORT_ERR);
JS::Rooted<JS::Value> 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<AbortSignal> mSignalMainThread;
// The main-thread event target for runnable dispatching.
nsCOMPtr<nsIEventTarget> 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<AbortSignalProxy> 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<AbortSignalProxyRunnable> 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;
class WorkerNotifier final : public WorkerHolder
{
RefPtr<WorkerFetchResolver> mResolver;
public:
explicit WorkerNotifier(WorkerFetchResolver* aResolver)
: WorkerHolder("WorkerNotifier", AllowIdleShutdownStart)
, mResolver(aResolver)
{}
bool
Notify(Status aStatus) override;
};
class WorkerFetchResolver final : public FetchDriverObserver
{
// Thread-safe:
RefPtr<PromiseWorkerProxy> mPromiseProxy;
RefPtr<AbortSignalProxy> mSignalProxy;
// Touched only on the worker thread.
RefPtr<FetchObserver> mFetchObserver;
UniquePtr<WorkerHolder> mWorkerHolder;
public:
// Returns null if worker is shutting down.
static already_AddRefed<WorkerFetchResolver>
Create(workers::WorkerPrivate* aWorkerPrivate, Promise* aPromise,
AbortSignal* aSignal, FetchObserver* aObserver)
{
MOZ_ASSERT(aWorkerPrivate);
aWorkerPrivate->AssertIsOnWorkerThread();
RefPtr<PromiseWorkerProxy> proxy =
PromiseWorkerProxy::Create(aWorkerPrivate, aPromise);
if (!proxy) {
return nullptr;
}
RefPtr<AbortSignalProxy> signalProxy;
if (aSignal) {
signalProxy =
new AbortSignalProxy(aSignal, aWorkerPrivate->MainThreadEventTarget());
}
RefPtr<WorkerFetchResolver> r =
new WorkerFetchResolver(proxy, signalProxy, aObserver);
if (NS_WARN_IF(!r->HoldWorker(aWorkerPrivate))) {
return nullptr;
}
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();
}
mWorkerHolder = 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;
bool
HoldWorker(WorkerPrivate* aWorkerPrivate)
{
UniquePtr<WorkerNotifier> wn(new WorkerNotifier(this));
if (NS_WARN_IF(!wn->HoldWorker(aWorkerPrivate, Canceling))) {
return false;
}
mWorkerHolder = Move(wn);
return true;
}
};
class MainThreadFetchResolver final : public FetchDriverObserver
{
RefPtr<Promise> mPromise;
RefPtr<Response> mResponse;
RefPtr<FetchObserver> mFetchObserver;
RefPtr<AbortSignal> mSignal;
const bool mMozErrors;
nsCOMPtr<nsILoadGroup> 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<WorkerFetchResolver> mResolver;
const ClientInfo mClientInfo;
const Maybe<ServiceWorkerDescriptor> mController;
RefPtr<InternalRequest> mRequest;
public:
MainThreadFetchRunnable(WorkerFetchResolver* aResolver,
const ClientInfo& aClientInfo,
const Maybe<ServiceWorkerDescriptor>& aController,
InternalRequest* aRequest)
: Runnable("dom::MainThreadFetchRunnable")
, mResolver(aResolver)
, mClientInfo(aClientInfo)
, mController(aController)
, mRequest(aRequest)
{
MOZ_ASSERT(mResolver);
}
NS_IMETHOD
Run() override
{
AssertIsOnMainThread();
RefPtr<FetchDriver> fetch;
RefPtr<PromiseWorkerProxy> 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<nsIPrincipal> principal = workerPrivate->GetPrincipal();
MOZ_ASSERT(principal);
nsCOMPtr<nsILoadGroup> 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(), false);
nsAutoCString spec;
if (proxy->GetWorkerPrivate()->GetBaseURI()) {
proxy->GetWorkerPrivate()->GetBaseURI()->GetAsciiSpec(spec);
}
fetch->SetWorkerScript(spec);
fetch->SetClientInfo(mClientInfo);
fetch->SetController(mController);
}
RefPtr<AbortSignal> 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<Promise>
FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
const RequestInit& aInit, CallerType aCallerType, ErrorResult& aRv)
{
RefPtr<Promise> 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<JSObject*> jsGlobal(cx, aGlobal->GetGlobalJSObject());
GlobalObject global(cx, jsGlobal);
RefPtr<Request> request = Request::Constructor(global, aInput, aInit, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
RefPtr<InternalRequest> r = request->GetInternalRequest();
RefPtr<AbortSignal> signal = request->GetSignal();
if (signal && signal->Aborted()) {
// Already aborted signal rejects immediately.
aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
return nullptr;
}
RefPtr<FetchObserver> observer;
if (aInit.mObserve.WasPassed()) {
observer = new FetchObserver(aGlobal, signal);
aInit.mObserve.Value().HandleEvent(*observer);
}
if (NS_IsMainThread()) {
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
nsCOMPtr<nsIDocument> doc;
nsCOMPtr<nsILoadGroup> 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<MainThreadFetchResolver> resolver =
new MainThreadFetchResolver(p, observer, signal, request->MozErrors());
RefPtr<FetchDriver> fetch =
new FetchDriver(r, principal, loadGroup,
aGlobal->EventTargetFor(TaskCategory::Other), 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<WorkerFetchResolver> resolver =
WorkerFetchResolver::Create(worker, p, signal, observer);
if (!resolver) {
NS_WARNING("Could not add WorkerFetchResolver workerHolder to worker");
aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
return nullptr;
}
RefPtr<MainThreadFetchRunnable> 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<nsIGlobalObject> 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<MSG_FETCH_FAILED>();
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<WorkerFetchResolver> mResolver;
// Passed from main thread to worker thread after being initialized.
RefPtr<InternalResponse> 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> promise = mResolver->WorkerPromise(aWorkerPrivate);
RefPtr<FetchObserver> fetchObserver =
mResolver->GetFetchObserver(aWorkerPrivate);
if (mInternalResponse->Type() != ResponseType::Error) {
if (fetchObserver) {
fetchObserver->SetState(FetchState::Complete);
}
RefPtr<nsIGlobalObject> global = aWorkerPrivate->GlobalScope();
RefPtr<Response> response =
new Response(global, mInternalResponse,
mResolver->GetAbortSignalForTargetThread());
promise->MaybeResolve(response);
} else {
if (fetchObserver) {
fetchObserver->SetState(FetchState::Errored);
}
ErrorResult result;
result.ThrowTypeError<MSG_FETCH_FAILED>();
promise->MaybeReject(result);
}
return true;
}
};
class WorkerDataAvailableRunnable final : public MainThreadWorkerRunnable
{
RefPtr<WorkerFetchResolver> 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> fetchObserver =
mResolver->GetFetchObserver(aWorkerPrivate);
if (fetchObserver &&
fetchObserver->State() == FetchState::Requesting) {
fetchObserver->SetState(FetchState::Responding);
}
return true;
}
};
class WorkerFetchResponseEndBase
{
protected:
RefPtr<WorkerFetchResolver> 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().
};
bool
WorkerNotifier::Notify(Status aStatus)
{
if (mResolver) {
// This will nullify this object.
// No additional operation after this line!
mResolver->Shutdown(mWorkerPrivate);
}
return true;
}
void
WorkerFetchResolver::OnResponseAvailableInternal(InternalResponse* aResponse)
{
AssertIsOnMainThread();
MutexAutoLock lock(mPromiseProxy->Lock());
if (mPromiseProxy->CleanedUp()) {
return;
}
RefPtr<WorkerFetchResponseRunnable> 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<WorkerDataAvailableRunnable> 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<WorkerFetchResponseEndRunnable> r =
new WorkerFetchResponseEndRunnable(mPromiseProxy->GetWorkerPrivate(),
this, aReason);
if (!r->Dispatch()) {
RefPtr<WorkerFetchResponseEndControlRunnable> cr =
new WorkerFetchResponseEndControlRunnable(mPromiseProxy->GetWorkerPrivate(),
this);
// This can fail if the worker thread is canceled or killed causing
// the PromiseWorkerProxy to give up its WorkerHolder 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;
}
workers::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<const ArrayBuffer> body(&aBodyInit.GetAsArrayBuffer());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsArrayBufferView()) {
BodyExtractor<const ArrayBufferView> body(&aBodyInit.GetAsArrayBufferView());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsBlob()) {
Blob& blob = aBodyInit.GetAsBlob();
BodyExtractor<nsIXHRSendable> body(&blob);
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsFormData()) {
FormData& formData = aBodyInit.GetAsFormData();
BodyExtractor<nsIXHRSendable> body(&formData);
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsUSVString()) {
BodyExtractor<const nsAString> body(&aBodyInit.GetAsUSVString());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsURLSearchParams()) {
URLSearchParams& usp = aBodyInit.GetAsURLSearchParams();
BodyExtractor<nsIXHRSendable> 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<const ArrayBuffer> body(&aBodyInit.GetAsArrayBuffer());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsArrayBufferView()) {
BodyExtractor<const ArrayBufferView> body(&aBodyInit.GetAsArrayBufferView());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsBlob()) {
BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsBlob());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsFormData()) {
BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsFormData());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsUSVString()) {
BodyExtractor<const nsAString> body(&aBodyInit.GetAsUSVString());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsURLSearchParams()) {
BodyExtractor<nsIXHRSendable> 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<const ArrayBuffer> body(&aBodyInit.GetAsArrayBuffer());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsArrayBufferView()) {
BodyExtractor<const ArrayBufferView> body(&aBodyInit.GetAsArrayBufferView());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsBlob()) {
BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsBlob());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsFormData()) {
BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsFormData());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsUSVString()) {
BodyExtractor<const nsAString> body(&aBodyInit.GetAsUSVString());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
if (aBodyInit.IsURLSearchParams()) {
BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsURLSearchParams());
return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
charset);
}
NS_NOTREACHED("Should never reach here");
return NS_ERROR_FAILURE;
}
template <class Derived>
FetchBody<Derived>::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<Request>::FetchBody(nsIGlobalObject* aOwner);
template
FetchBody<Response>::FetchBody(nsIGlobalObject* aOwner);
template <class Derived>
FetchBody<Derived>::~FetchBody()
{
Unfollow();
}
template
FetchBody<Request>::~FetchBody();
template
FetchBody<Response>::~FetchBody();
template <class Derived>
bool
FetchBody<Derived>::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<JSObject*> body(cx, mReadableStreamBody);
if (JS::ReadableStreamIsDisturbed(body) ||
JS::ReadableStreamIsLocked(body) ||
!JS::ReadableStreamIsReadable(body)) {
return true;
}
}
return false;
}
template
bool
FetchBody<Request>::BodyUsed() const;
template
bool
FetchBody<Response>::BodyUsed() const;
template <class Derived>
void
FetchBody<Derived>::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<JSObject*> 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<JSObject*> reader(aCx);
mFetchStreamReader->StartConsuming(aCx, readableStreamObj, &reader, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
mReadableStreamReader = reader;
}
}
}
template
void
FetchBody<Request>::SetBodyUsed(JSContext* aCx, ErrorResult& aRv);
template
void
FetchBody<Response>::SetBodyUsed(JSContext* aCx, ErrorResult& aRv);
template <class Derived>
already_AddRefed<Promise>
FetchBody<Derived>::ConsumeBody(JSContext* aCx, FetchConsumeType aType,
ErrorResult& aRv)
{
RefPtr<AbortSignal> signal = DerivedClass()->GetSignal();
if (signal && signal->Aborted()) {
aRv.Throw(NS_ERROR_DOM_ABORT_ERR);
return nullptr;
}
if (BodyUsed()) {
aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
return nullptr;
}
SetBodyUsed(aCx, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
nsCOMPtr<nsIGlobalObject> global = DerivedClass()->GetParentObject();
RefPtr<Promise> promise =
FetchBodyConsumer<Derived>::Create(global, mMainThreadEventTarget, this,
signal, aType, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
return promise.forget();
}
template
already_AddRefed<Promise>
FetchBody<Request>::ConsumeBody(JSContext* aCx, FetchConsumeType aType,
ErrorResult& aRv);
template
already_AddRefed<Promise>
FetchBody<Response>::ConsumeBody(JSContext* aCx, FetchConsumeType aType,
ErrorResult& aRv);
template <class Derived>
void
FetchBody<Derived>::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<Request>::SetMimeType();
template
void
FetchBody<Response>::SetMimeType();
template <class Derived>
void
FetchBody<Derived>::SetReadableStreamBody(JSContext* aCx, JSObject* aBody)
{
MOZ_ASSERT(!mReadableStreamBody);
MOZ_ASSERT(aBody);
mReadableStreamBody = aBody;
RefPtr<AbortSignal> signal = DerivedClass()->GetSignal();
if (!signal) {
return;
}
bool aborted = signal->Aborted();
if (aborted) {
JS::Rooted<JSObject*> body(aCx, mReadableStreamBody);
AbortStream(aCx, body);
} else if (!IsFollowing()) {
Follow(signal);
}
}
template
void
FetchBody<Request>::SetReadableStreamBody(JSContext* aCx, JSObject* aBody);
template
void
FetchBody<Response>::SetReadableStreamBody(JSContext* aCx, JSObject* aBody);
template <class Derived>
void
FetchBody<Derived>::GetBody(JSContext* aCx,
JS::MutableHandle<JSObject*> aBodyOut,
ErrorResult& aRv)
{
if (mReadableStreamBody) {
aBodyOut.set(mReadableStreamBody);
return;
}
nsCOMPtr<nsIInputStream> inputStream;
DerivedClass()->GetBody(getter_AddRefs(inputStream));
if (!inputStream) {
aBodyOut.set(nullptr);
return;
}
JS::Rooted<JSObject*> 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<AbortSignal> signal = DerivedClass()->GetSignal();
if (signal) {
if (signal->Aborted()) {
AbortStream(aCx, body);
} else if (!IsFollowing()) {
Follow(signal);
}
}
mReadableStreamBody = body;
aBodyOut.set(mReadableStreamBody);
}
template
void
FetchBody<Request>::GetBody(JSContext* aCx,
JS::MutableHandle<JSObject*> aMessage,
ErrorResult& aRv);
template
void
FetchBody<Response>::GetBody(JSContext* aCx,
JS::MutableHandle<JSObject*> aMessage,
ErrorResult& aRv);
template <class Derived>
void
FetchBody<Derived>::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<JSObject*> reader(aCx,
JS::ReadableStreamGetReader(aCx, aStream,
JS::ReadableStreamReaderMode::Default));
if (!reader) {
aRv.StealExceptionFromJSContext(aCx);
return;
}
mReadableStreamReader = reader;
}
template
void
FetchBody<Request>::LockStream(JSContext* aCx,
JS::HandleObject aStream,
ErrorResult& aRv);
template
void
FetchBody<Response>::LockStream(JSContext* aCx,
JS::HandleObject aStream,
ErrorResult& aRv);
template <class Derived>
void
FetchBody<Derived>::MaybeTeeReadableStreamBody(JSContext* aCx,
JS::MutableHandle<JSObject*> 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<JSObject*> 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<JSObject*> branch1(aCx);
JS::Rooted<JSObject*> 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<Request>::MaybeTeeReadableStreamBody(JSContext* aCx,
JS::MutableHandle<JSObject*> aMessage,
FetchStreamReader** aStreamReader,
nsIInputStream** aInputStream,
ErrorResult& aRv);
template
void
FetchBody<Response>::MaybeTeeReadableStreamBody(JSContext* aCx,
JS::MutableHandle<JSObject*> aMessage,
FetchStreamReader** aStreamReader,
nsIInputStream** aInputStream,
ErrorResult& aRv);
template <class Derived>
void
FetchBody<Derived>::Abort()
{
MOZ_ASSERT(mReadableStreamBody);
AutoJSAPI jsapi;
if (!jsapi.Init(mOwner)) {
return;
}
JSContext* cx = jsapi.cx();
JS::Rooted<JSObject*> body(cx, mReadableStreamBody);
AbortStream(cx, body);
}
template
void
FetchBody<Request>::Abort();
template
void
FetchBody<Response>::Abort();
} // namespace dom
} // namespace mozilla