/* -*- 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/. */
// Step 17.2.1.2 and 17.2.2 of // https://fetch.spec.whatwg.org/#concept-http-network-fetch // If stream is readable, then error stream with ... void AbortStream(JSContext* aCx, ReadableStream* aReadableStream,
ErrorResult& aRv, JS::Handle<JS::Value> aReasonDetails) { if (aReadableStream->State() != ReadableStream::ReaderState::Readable) { return;
}
JS::Rooted<JS::Value> value(aCx, aReasonDetails);
if (aReasonDetails.isUndefined()) {
RefPtr<DOMException> e = DOMException::Create(NS_ERROR_DOM_ABORT_ERR); if (!GetOrCreateDOMReflector(aCx, e, &value)) { return;
}
}
aReadableStream->ErrorNative(aCx, value, aRv);
}
} // namespace
class AbortSignalMainThread final : public AbortSignalImpl { public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AbortSignalMainThread)
// This runnable propagates changes from the AbortSignalImpl on workers to the // AbortSignalImpl on main-thread. class AbortSignalProxyRunnable final : public Runnable {
RefPtr<AbortSignalProxy> mProxy;
// This class orchestrates the proxying of AbortSignal operations between the // main thread and a worker thread. class AbortSignalProxy final : public AbortFollower { // This is created and released on the main-thread.
RefPtr<AbortSignalImpl> mSignalImplMainThread;
// The main-thread event target for runnable dispatching.
nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
// This value is used only when creating mSignalImplMainThread on the main // thread, to create it in already-aborted state if necessary. It does *not* // reflect the instantaneous is-aborted status of the worker thread's // AbortSignal. constbool mAborted;
class WorkerFetchResolver final : public FetchDriverObserver { // Thread-safe:
RefPtr<PromiseWorkerProxy> mPromiseProxy;
RefPtr<AbortSignalProxy> mSignalProxy;
// Touched only on the worker thread.
RefPtr<FetchObserver> mFetchObserver;
RefPtr<WeakWorkerRef> mWorkerRef; bool mIsShutdown;
{ // 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.clonePtr(), principal, loadGroup,
workerPrivate->MainThreadEventTarget(),
workerPrivate->CookieJarSettings(),
workerPrivate->GetPerformanceStorage(), false);
nsAutoCString spec; if (proxy->GetWorkerPrivate()->GetBaseURI()) {
proxy->GetWorkerPrivate()->GetBaseURI()->GetAsciiSpec(spec);
}
fetch->SetWorkerScript(spec);
// ...but release it before calling Fetch, because mResolver's callback can // be called synchronously and they want the mutex, too. return fetch->Fetch(signalImpl, mResolver);
}
};
// 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;
}
// Restore information of InterceptedHttpChannel if they are passed with the // Request. Since Request::Constructor would not copy these members. if (aInput.IsRequest()) {
RefPtr<Request> inputReq = &aInput.GetAsRequest();
SafeRefPtr<InternalRequest> inputInReq = inputReq->GetInternalRequest(); if (inputInReq->GetInterceptionTriggeringPrincipalInfo()) {
internalRequest->SetInterceptionContentPolicyType(
inputInReq->InterceptionContentPolicyType());
internalRequest->SetInterceptionTriggeringPrincipalInfo(
MakeUnique<mozilla::ipc::PrincipalInfo>(
*(inputInReq->GetInterceptionTriggeringPrincipalInfo().get()))); if (!inputInReq->InterceptionRedirectChain().IsEmpty()) {
internalRequest->SetInterceptionRedirectChain(
inputInReq->InterceptionRedirectChain());
}
internalRequest->SetInterceptionFromThirdParty(
inputInReq->InterceptionFromThirdParty());
}
}
if (!loadGroup) {
nsresult rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), principal); if (NS_WARN_IF(NS_FAILED(rv))) {
aRv.Throw(rv); return nullptr;
}
}
RefPtr<MainThreadFetchResolver> resolver = new MainThreadFetchResolver(
p, observer, signalImpl, request->MozErrors());
RefPtr<FetchDriver> fetch = new FetchDriver(std::move(internalRequest), principal, loadGroup,
aGlobal->SerialEventTarget(), cookieJarSettings,
nullptr, // PerformanceStorage
isTrackingFetch);
fetch->SetDocument(doc);
resolver->SetLoadGroup(loadGroup);
aRv = fetch->Fetch(signalImpl, resolver); if (NS_WARN_IF(aRv.Failed())) { return nullptr;
}
} elseif (NS_IsMainThread() && internalRequest->GetKeepalive()) { // keepalive is set to true, route the request through PFetch // We plan to route all main-thread fetch request through PFetch. // See Bug 1897129.
if (!loadGroup) { // if there is no load group for this request ensure that the request // size does not exceed FETCH_KEEPALIVE_MAX_SIZE if (bodyLength > FETCH_KEEPALIVE_MAX_SIZE) {
p->MaybeRejectWithTypeError<MSG_FETCH_FAILED>(); return p.forget();
}
}
RefPtr<FetchChild> actor =
FetchChild::CreateForMainThread(p, signalImpl, observer); if (!actor) {
NS_WARNING("Could not start keepalive request.");
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return nullptr;
}
Maybe<ClientInfo> clientInfo(aGlobal->GetClientInfo()); if (clientInfo.isNothing()) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return nullptr;
}
ipcArgs.clientInfo() = clientInfo.ref().ToIPC();
nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal);
nsCOMPtr<Document> doc;
nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
nsIPrincipal* principal; // we don't check if we this request is invoked from a tracking script // we might add this capability in future. // See Bug 1892406 if (window) {
doc = window->GetExtantDoc(); if (!doc) {
aRv.Throw(NS_ERROR_FAILURE); return nullptr;
}
principal = doc->NodePrincipal();
cookieJarSettings = doc->CookieJarSettings(); // fetch the thirdparty context from the document
if (worker->IsServiceWorker()) {
internalRequest->SetSkipServiceWorker();
}
// PFetch gives no benefit for the fetch in the parent process. // Dispatch fetch to the parent process main thread directly for that case. // For child process, dispatch fetch op to the parent. if (StaticPrefs::dom_workers_pFetch_enabled() && !XRE_IsParentProcess()) { if (internalRequest->GetKeepalive()) {
uint64_t bodyLength = internalRequest->BodyLength() > 0
? internalRequest->BodyLength()
: 0;
// We differ from the fetch spec and main thread fetch here. // We do not limit the keepalive size per loadgroup(but instead per // request). This is due to the fact that loadgroup is not accessible on // the worker thread and we dont want to introduce async to introduce // this check. if (bodyLength > FETCH_KEEPALIVE_MAX_SIZE) {
p->MaybeRejectWithTypeError<MSG_FETCH_FAILED>(); return p.forget();
}
}
RefPtr<FetchChild> actor =
FetchChild::CreateForWorker(worker, p, signalImpl, observer); if (!actor) {
NS_WARNING("Could not keep the worker alive.");
aRv.Throw(NS_ERROR_DOM_ABORT_ERR); return nullptr;
}
Maybe<ClientInfo> clientInfo(worker->GlobalScope()->GetClientInfo()); if (clientInfo.isNothing()) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return nullptr;
}
if (internalRequest->GetKeepalive()) {
mozilla::glean::networking::fetch_keepalive_request_count
.Get("worker"_ns)
.Add(1);
}
return p.forget();
} // Dispatch worker fetch to the main thread // We do not check if keepalive flag is set for ChromeWorkers // See Bug 1898664
RefPtr<WorkerFetchResolver> resolver =
WorkerFetchResolver::Create(worker, p, signalImpl, observer); if (!resolver) {
NS_WARNING("Could not keep the worker alive.");
aRv.Throw(NS_ERROR_DOM_ABORT_ERR); return nullptr;
}
Maybe<ClientInfo> clientInfo(worker->GlobalScope()->GetClientInfo()); if (clientInfo.isNothing()) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return nullptr;
}
UniquePtr<SerializedStackHolder> stack; if (worker->IsWatchedByDevTools()) {
stack = GetCurrentStackForNetMonitor(cx);
}
RefPtr<MainThreadFetchRunnable> run = new MainThreadFetchRunnable(
resolver, clientInfo.ref(), worker->GlobalScope()->GetController(),
worker->CSPEventListener(), std::move(internalRequest),
std::move(stack));
worker->DispatchToMainThread(run.forget());
}
return p.forget();
}
class ResolveFetchPromise : public Runnable { public:
ResolveFetchPromise(Promise* aPromise, Response* aResponse)
: Runnable("ResolveFetchPromise"),
mPromise(aPromise),
mResponse(aResponse) {}
if (aResponse->Type() != ResponseType::Error) {
nsCOMPtr<nsIGlobalObject> go = mPromise->GetParentObject();
nsCOMPtr<nsPIDOMWindowInner> inner = do_QueryInterface(go);
// Notify the document when a fetch completes successfully. This is // used by the password manager as a hint to observe DOM mutations. // Call this prior to setting state to Complete so we can set up the // observer before mutations occurs.
Document* doc = inner ? inner->GetExtantDoc() : nullptr; if (doc) {
doc->NotifyFetchOrXHRSuccess();
}
if (mFetchObserver) {
mFetchObserver->SetState(FetchState::Complete);
}
mResponse = new Response(go, std::move(aResponse), mSignalImpl); // response headers received from the network should be immutable // all response header settings must be done before this point // see Bug 1574174
ErrorResult result;
mResponse->Headers_()->SetGuard(HeadersGuardEnum::Immutable, result);
MOZ_ASSERT(!result.Failed());
BrowsingContext* bc = inner ? inner->GetBrowsingContext() : nullptr;
bc = bc ? bc->Top() : nullptr; if (bc && bc->IsLoading()) {
bc->AddDeprioritizedLoadRunner( new ResolveFetchPromise(mPromise, mResponse));
} else {
mPromise->MaybeResolve(mResponse);
}
} else { if (mFetchObserver) {
mFetchObserver->SetState(FetchState::Errored);
}
if (mMozErrors) {
mPromise->MaybeReject(aResponse->GetErrorCode()); return;
}
class WorkerFetchResponseRunnable final : public MainThreadWorkerRunnable {
RefPtr<WorkerFetchResolver> mResolver; // Passed from main thread to worker thread after being initialized.
SafeRefPtr<InternalResponse> mInternalResponse;
RefPtr<Promise> promise = mResolver->WorkerPromise(aWorkerPrivate); // Once Worker had already started shutdown, workerPromise would be nullptr if (!promise) { returntrue;
}
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.clonePtr(),
mResolver->GetAbortSignalForTargetThread());
// response headers received from the network should be immutable, // all response header settings must be done before this point // see Bug 1574174
ErrorResult result;
response->Headers_()->SetGuard(HeadersGuardEnum::Immutable, result);
MOZ_ASSERT(!result.Failed());
promise->MaybeResolve(response);
} else { if (fetchObserver) {
fetchObserver->SetState(FetchState::Errored);
}
class WorkerFetchResponseEndRunnable final : public MainThreadWorkerRunnable, public WorkerFetchResponseEndBase {
FetchDriverObserver::EndReason mReason;
if (mReason == FetchDriverObserver::eAborted) {
mResolver->WorkerPromise(aWorkerPrivate)
->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
}
WorkerRunInternal(aWorkerPrivate); returntrue;
}
nsresult Cancel() override { return Run(); }
};
class WorkerFetchResponseEndControlRunnable final
: public MainThreadWorkerControlRunnable, public WorkerFetchResponseEndBase { public:
WorkerFetchResponseEndControlRunnable(WorkerPrivate* aWorkerPrivate,
WorkerFetchResolver* aResolver)
: MainThreadWorkerControlRunnable( "WorkerFetchResponseEndControlRunnable"),
WorkerFetchResponseEndBase(aResolver) {}
MutexAutoLock lock(mPromiseProxy->Lock()); if (mPromiseProxy->CleanedUp()) { return;
}
RefPtr<WorkerDataAvailableRunnable> r = new WorkerDataAvailableRunnable(mPromiseProxy->GetWorkerPrivate(), this);
Unused << r->Dispatch(mPromiseProxy->GetWorkerPrivate());
}
RefPtr<WorkerFetchResponseEndRunnable> r = new WorkerFetchResponseEndRunnable(
mPromiseProxy->GetWorkerPrivate(), this, aReason);
if (!r->Dispatch(mPromiseProxy->GetWorkerPrivate())) {
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 WorkerRef immediately, // allowing the worker thread to become Dead. if (!cr->Dispatch(mPromiseProxy->GetWorkerPrivate())) {
NS_WARNING("Failed to dispatch WorkerFetchResponseEndControlRunnable");
}
}
}
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->GetRemoteWorkerController()->FlushReportsOnMainThread(mReporter); return;
}
// Flush to dedicated worker
mReporter->FlushConsoleReports(worker->GetLoadGroup());
}
MOZ_DIAGNOSTIC_ASSERT(!BodyUsed(), "Consuming already used body?"); if (BodyUsed()) { 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) { if (mFetchStreamReader) { // Having FetchStreamReader means there's no nsIInputStream underlying it
MOZ_ASSERT(!mReadableStreamBody->MaybeGetInputStreamIfUnread());
mFetchStreamReader->StartConsuming(aCx, mReadableStreamBody, aRv); return;
} // We should have nsIInputStream at this point as long as it's still // readable
MOZ_ASSERT_IF(
mReadableStreamBody->State() == ReadableStream::ReaderState::Readable,
mReadableStreamBody->MaybeGetInputStreamIfUnread());
LockStream(aCx, mReadableStreamBody, aRv);
}
}
// Null bodies are a special-case in the fetch spec. The Body mix-in can only // be "disturbed" or "locked" if its associated "body" is non-null. // Additionally, the Body min-in's "consume body" algorithm explicitly creates // a fresh empty ReadableStream object in step 2. This means that `bodyUsed` // will never return true for a null body. // // To this end, we create a fresh (empty) body every time a request is made // and consume its body here, without marking this FetchBody consumed via // SetBodyUsed.
nsCOMPtr<nsIInputStream> bodyStream;
DerivedClass()->GetBody(getter_AddRefs(bodyStream)); if (!bodyStream) {
RefPtr<EmptyBody> emptyBody =
EmptyBody::Create(DerivedClass()->GetParentObject(),
DerivedClass()->GetPrincipalInfo().get(), signalImpl,
mimeType, mixedCaseMimeType, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr;
}
return emptyBody->ConsumeBody(aCx, aType, aRv);
}
SetBodyUsed(aCx, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr;
}
nsCOMPtr<nsIGlobalObject> global = DerivedClass()->GetParentObject();
MutableBlobStorage::MutableBlobStorageType blobStorageType =
MutableBlobStorage::eOnlyInMemory; const mozilla::UniquePtr<mozilla::ipc::PrincipalInfo>& principalInfo =
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()
.IsPrivateBrowsing()))) {
blobStorageType = MutableBlobStorage::eCouldBeInTemporaryFile;
}
// 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) { // Convert from a bytestring to a UTF8 CString.
CopyLatin1toUTF8(contentTypeValues, aMimeType);
aMixedCaseMimeType = aMimeType;
ToLowerCase(aMimeType);
}
}
// The spec immediately creates ReadableStream on Response/Request constructor // via https://fetch.spec.whatwg.org/#concept-bodyinit-extract, but Gecko // creates nsIInputStream there instead and creates ReadableStream only when // .body is accessed. Thus we only follow step 4 of it here. // // Step 4: Otherwise, set stream to a new ReadableStream object, and set up // stream with byte reading support. auto algorithms =
MakeRefPtr<NonAsyncInputToReadableStreamAlgorithms>(*inputStream);
RefPtr<ReadableStream> body = ReadableStream::CreateByteNative(
aCx, DerivedClass()->GetParentObject(), *algorithms, Nothing(), aRv); if (aRv.Failed()) { return nullptr;
}
mReadableStreamBody = 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 nullptr;
}
}
RefPtr<AbortSignalImpl> signalImpl = DerivedClass()->GetSignalImpl(); if (signalImpl) { if (signalImpl->Aborted()) {
JS::Rooted<JS::Value> abortReason(aCx, signalImpl->RawReason());
AbortStream(aCx, body, aRv, abortReason); if (NS_WARN_IF(aRv.Failed())) { return nullptr;
}
} elseif (!IsFollowing()) {
Follow(signalImpl);
}
}
template <class Derived> void FetchBody<Derived>::LockStream(JSContext* aCx, ReadableStream* aStream,
ErrorResult& aRv) { // This is native stream, creating a reader will not execute any JS code.
RefPtr<ReadableStreamDefaultReader> reader = aStream->GetReader(aRv); if (aRv.Failed()) { return;
}
}
// If this is a ReadableStream with an native source, this has been // generated by a Fetch. In this case, Fetch will be able to recreate it // again when GetBody() is called. if (mReadableStreamBody->MaybeGetInputStreamIfUnread()) {
*aBodyOut = nullptr; return;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.