summaryrefslogtreecommitdiffstats
path: root/dom/workers/loader
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/workers/loader
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/workers/loader')
-rw-r--r--dom/workers/loader/CacheLoadHandler.cpp651
-rw-r--r--dom/workers/loader/CacheLoadHandler.h221
-rw-r--r--dom/workers/loader/NetworkLoadHandler.cpp393
-rw-r--r--dom/workers/loader/NetworkLoadHandler.h79
-rw-r--r--dom/workers/loader/ScriptResponseHeaderProcessor.cpp79
-rw-r--r--dom/workers/loader/ScriptResponseHeaderProcessor.h94
-rw-r--r--dom/workers/loader/WorkerLoadContext.cpp78
-rw-r--r--dom/workers/loader/WorkerLoadContext.h219
-rw-r--r--dom/workers/loader/WorkerModuleLoader.cpp225
-rw-r--r--dom/workers/loader/WorkerModuleLoader.h83
-rw-r--r--dom/workers/loader/moz.build34
11 files changed, 2156 insertions, 0 deletions
diff --git a/dom/workers/loader/CacheLoadHandler.cpp b/dom/workers/loader/CacheLoadHandler.cpp
new file mode 100644
index 0000000000..16f992e837
--- /dev/null
+++ b/dom/workers/loader/CacheLoadHandler.cpp
@@ -0,0 +1,651 @@
+/* -*- 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 "CacheLoadHandler.h"
+#include "ScriptResponseHeaderProcessor.h" // ScriptResponseHeaderProcessor
+#include "WorkerLoadContext.h" // WorkerLoadContext
+
+#include "nsIPrincipal.h"
+
+#include "nsIThreadRetargetableRequest.h"
+#include "nsIXPConnect.h"
+
+#include "jsapi.h"
+#include "nsNetUtil.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/dom/CacheBinding.h"
+#include "mozilla/dom/cache/CacheTypes.h"
+#include "mozilla/dom/Response.h"
+#include "mozilla/dom/ServiceWorkerBinding.h" // ServiceWorkerState
+#include "mozilla/Result.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/WorkerScope.h"
+
+#include "mozilla/dom/workerinternals/ScriptLoader.h" // WorkerScriptLoader
+
+namespace mozilla {
+namespace dom {
+
+namespace workerinternals::loader {
+
+NS_IMPL_ISUPPORTS0(CacheCreator)
+
+NS_IMPL_ISUPPORTS(CacheLoadHandler, nsIStreamLoaderObserver)
+
+NS_IMPL_ISUPPORTS0(CachePromiseHandler)
+
+CachePromiseHandler::CachePromiseHandler(
+ WorkerScriptLoader* aLoader, ThreadSafeRequestHandle* aRequestHandle)
+ : mLoader(aLoader), mRequestHandle(aRequestHandle) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mLoader);
+}
+
+void CachePromiseHandler::ResolvedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ AssertIsOnMainThread();
+ if (mRequestHandle->IsEmpty()) {
+ return;
+ }
+ WorkerLoadContext* loadContext = mRequestHandle->GetContext();
+
+ // May already have been canceled by CacheLoadHandler::Fail from
+ // CancelMainThread.
+ MOZ_ASSERT(loadContext->mCacheStatus == WorkerLoadContext::WritingToCache ||
+ loadContext->mCacheStatus == WorkerLoadContext::Cancel);
+ MOZ_ASSERT_IF(loadContext->mCacheStatus == WorkerLoadContext::Cancel,
+ !loadContext->mCachePromise);
+
+ if (loadContext->mCachePromise) {
+ loadContext->mCacheStatus = WorkerLoadContext::Cached;
+ loadContext->mCachePromise = nullptr;
+ mRequestHandle->MaybeExecuteFinishedScripts();
+ }
+}
+
+void CachePromiseHandler::RejectedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ AssertIsOnMainThread();
+ if (mRequestHandle->IsEmpty()) {
+ return;
+ }
+ WorkerLoadContext* loadContext = mRequestHandle->GetContext();
+
+ // May already have been canceled by CacheLoadHandler::Fail from
+ // CancelMainThread.
+ MOZ_ASSERT(loadContext->mCacheStatus == WorkerLoadContext::WritingToCache ||
+ loadContext->mCacheStatus == WorkerLoadContext::Cancel);
+ loadContext->mCacheStatus = WorkerLoadContext::Cancel;
+
+ loadContext->mCachePromise = nullptr;
+
+ // This will delete the cache object and will call LoadingFinished() with an
+ // error for each ongoing operation.
+ auto* cacheCreator = mRequestHandle->GetCacheCreator();
+ if (cacheCreator) {
+ cacheCreator->DeleteCache(NS_ERROR_FAILURE);
+ }
+}
+
+CacheCreator::CacheCreator(WorkerPrivate* aWorkerPrivate)
+ : mCacheName(aWorkerPrivate->ServiceWorkerCacheName()),
+ mOriginAttributes(aWorkerPrivate->GetOriginAttributes()) {
+ MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
+}
+
+nsresult CacheCreator::CreateCacheStorage(nsIPrincipal* aPrincipal) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mCacheStorage);
+ MOZ_ASSERT(aPrincipal);
+
+ nsIXPConnect* xpc = nsContentUtils::XPConnect();
+ MOZ_ASSERT(xpc, "This should never be null!");
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> sandbox(cx);
+ nsresult rv = xpc->CreateSandbox(cx, aPrincipal, sandbox.address());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // The JSContext is not in a realm, so CreateSandbox returned an unwrapped
+ // global.
+ MOZ_ASSERT(JS_IsGlobalObject(sandbox));
+
+ mSandboxGlobalObject = xpc::NativeGlobal(sandbox);
+ if (NS_WARN_IF(!mSandboxGlobalObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // If we're in private browsing mode, don't even try to create the
+ // CacheStorage. Instead, just fail immediately to terminate the
+ // ServiceWorker load.
+ if (NS_WARN_IF(mOriginAttributes.mPrivateBrowsingId > 0)) {
+ return NS_ERROR_DOM_SECURITY_ERR;
+ }
+
+ // Create a CacheStorage bypassing its trusted origin checks. The
+ // ServiceWorker has already performed its own checks before getting
+ // to this point.
+ ErrorResult error;
+ mCacheStorage = CacheStorage::CreateOnMainThread(
+ mozilla::dom::cache::CHROME_ONLY_NAMESPACE, mSandboxGlobalObject,
+ aPrincipal, true /* force trusted origin */, error);
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+
+ return NS_OK;
+}
+
+nsresult CacheCreator::Load(nsIPrincipal* aPrincipal) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mLoaders.IsEmpty());
+
+ nsresult rv = CreateCacheStorage(aPrincipal);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ ErrorResult error;
+ MOZ_ASSERT(!mCacheName.IsEmpty());
+ RefPtr<Promise> promise = mCacheStorage->Open(mCacheName, error);
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+
+ promise->AppendNativeHandler(this);
+ return NS_OK;
+}
+
+void CacheCreator::FailLoaders(nsresult aRv) {
+ AssertIsOnMainThread();
+
+ // Fail() can call LoadingFinished() which may call ExecuteFinishedScripts()
+ // which sets mCacheCreator to null, so hold a ref.
+ RefPtr<CacheCreator> kungfuDeathGrip = this;
+
+ for (uint32_t i = 0, len = mLoaders.Length(); i < len; ++i) {
+ mLoaders[i]->Fail(aRv);
+ }
+
+ mLoaders.Clear();
+}
+
+void CacheCreator::RejectedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ AssertIsOnMainThread();
+ FailLoaders(NS_ERROR_FAILURE);
+}
+
+void CacheCreator::ResolvedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ AssertIsOnMainThread();
+ if (!aValue.isObject()) {
+ FailLoaders(NS_ERROR_FAILURE);
+ return;
+ }
+
+ JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+ Cache* cache = nullptr;
+ nsresult rv = UNWRAP_OBJECT(Cache, &obj, cache);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ FailLoaders(NS_ERROR_FAILURE);
+ return;
+ }
+
+ mCache = cache;
+ MOZ_DIAGNOSTIC_ASSERT(mCache);
+
+ // If the worker is canceled, CancelMainThread() will have cleared the
+ // loaders via DeleteCache().
+ for (uint32_t i = 0, len = mLoaders.Length(); i < len; ++i) {
+ mLoaders[i]->Load(cache);
+ }
+}
+
+void CacheCreator::DeleteCache(nsresult aReason) {
+ AssertIsOnMainThread();
+
+ // This is called when the load is canceled which can occur before
+ // mCacheStorage is initialized.
+ if (mCacheStorage) {
+ // It's safe to do this while Cache::Match() and Cache::Put() calls are
+ // running.
+ RefPtr<Promise> promise = mCacheStorage->Delete(mCacheName, IgnoreErrors());
+
+ // We don't care to know the result of the promise object.
+ }
+
+ // Always call this here to ensure the loaders array is cleared.
+ FailLoaders(NS_ERROR_FAILURE);
+}
+
+CacheLoadHandler::CacheLoadHandler(ThreadSafeWorkerRef* aWorkerRef,
+ ThreadSafeRequestHandle* aRequestHandle,
+ bool aIsWorkerScript,
+ bool aOnlyExistingCachedResourcesAllowed,
+ WorkerScriptLoader* aLoader)
+ : mRequestHandle(aRequestHandle),
+ mLoader(aLoader),
+ mWorkerRef(aWorkerRef),
+ mIsWorkerScript(aIsWorkerScript),
+ mFailed(false),
+ mOnlyExistingCachedResourcesAllowed(aOnlyExistingCachedResourcesAllowed) {
+ MOZ_ASSERT(aWorkerRef);
+ MOZ_ASSERT(aWorkerRef->Private()->IsServiceWorker());
+ mMainThreadEventTarget = aWorkerRef->Private()->MainThreadEventTarget();
+ MOZ_ASSERT(mMainThreadEventTarget);
+ mBaseURI = mLoader->GetBaseURI();
+ AssertIsOnMainThread();
+
+ // Worker scripts are always decoded as UTF-8 per spec.
+ mDecoder = MakeUnique<ScriptDecoder>(UTF_8_ENCODING,
+ ScriptDecoder::BOMHandling::Remove);
+}
+
+void CacheLoadHandler::Fail(nsresult aRv) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(NS_FAILED(aRv));
+
+ if (mFailed) {
+ return;
+ }
+
+ mFailed = true;
+
+ if (mPump) {
+ MOZ_ASSERT_IF(!mRequestHandle->IsEmpty(),
+ mRequestHandle->GetContext()->mCacheStatus ==
+ WorkerLoadContext::ReadingFromCache);
+ mPump->Cancel(aRv);
+ mPump = nullptr;
+ }
+ if (mRequestHandle->IsEmpty()) {
+ return;
+ }
+
+ WorkerLoadContext* loadContext = mRequestHandle->GetContext();
+
+ loadContext->mCacheStatus = WorkerLoadContext::Cancel;
+
+ if (loadContext->mCachePromise) {
+ loadContext->mCachePromise->MaybeReject(aRv);
+ }
+
+ loadContext->mCachePromise = nullptr;
+
+ mRequestHandle->LoadingFinished(aRv);
+}
+
+void CacheLoadHandler::Load(Cache* aCache) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(aCache);
+ MOZ_ASSERT(!mRequestHandle->IsEmpty());
+ WorkerLoadContext* loadContext = mRequestHandle->GetContext();
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), loadContext->mRequest->mURL,
+ nullptr, mBaseURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Fail(rv);
+ return;
+ }
+
+ nsAutoCString spec;
+ rv = uri->GetSpec(spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Fail(rv);
+ return;
+ }
+
+ MOZ_ASSERT(loadContext->mFullURL.IsEmpty());
+ CopyUTF8toUTF16(spec, loadContext->mFullURL);
+
+ mozilla::dom::RequestOrUSVString request;
+ request.SetAsUSVString().ShareOrDependUpon(loadContext->mFullURL);
+
+ mozilla::dom::CacheQueryOptions params;
+
+ // This JSContext will not end up executing JS code because here there are
+ // no ReadableStreams involved.
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ ErrorResult error;
+ RefPtr<Promise> promise = aCache->Match(jsapi.cx(), request, params, error);
+ if (NS_WARN_IF(error.Failed())) {
+ Fail(error.StealNSResult());
+ return;
+ }
+
+ promise->AppendNativeHandler(this);
+}
+
+void CacheLoadHandler::RejectedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mRequestHandle->IsEmpty());
+
+ MOZ_ASSERT(mRequestHandle->GetContext()->mCacheStatus ==
+ WorkerLoadContext::Uncached);
+ Fail(NS_ERROR_FAILURE);
+}
+
+void CacheLoadHandler::ResolvedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mRequestHandle->IsEmpty());
+ WorkerLoadContext* loadContext = mRequestHandle->GetContext();
+
+ // If we have already called 'Fail', we should not proceed. If we cancelled,
+ // we should similarily not proceed.
+ if (mFailed) {
+ return;
+ }
+
+ MOZ_ASSERT(loadContext->mCacheStatus == WorkerLoadContext::Uncached);
+
+ nsresult rv;
+
+ // The ServiceWorkerScriptCache will store data for any scripts it
+ // it knows about. This is always at least the top level script.
+ // Depending on if a previous version of the service worker has
+ // been installed or not it may also know about importScripts(). We
+ // must handle loading and offlining new importScripts() here, however.
+ if (aValue.isUndefined()) {
+ // If this is the main script or we're not loading a new service worker
+ // then this is an error. This can happen for internal reasons, like
+ // storage was probably wiped without removing the service worker
+ // registration. It can also happen for exposed reasons like the
+ // service worker script calling importScripts() after install.
+ if (NS_WARN_IF(mIsWorkerScript || mOnlyExistingCachedResourcesAllowed)) {
+ Fail(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ loadContext->mCacheStatus = WorkerLoadContext::ToBeCached;
+ rv = mLoader->LoadScript(mRequestHandle);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Fail(rv);
+ }
+ return;
+ }
+
+ MOZ_ASSERT(aValue.isObject());
+
+ JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+ mozilla::dom::Response* response = nullptr;
+ rv = UNWRAP_OBJECT(Response, &obj, response);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Fail(rv);
+ return;
+ }
+
+ InternalHeaders* headers = response->GetInternalHeaders();
+
+ headers->Get("content-security-policy"_ns, mCSPHeaderValue, IgnoreErrors());
+ headers->Get("content-security-policy-report-only"_ns,
+ mCSPReportOnlyHeaderValue, IgnoreErrors());
+ headers->Get("referrer-policy"_ns, mReferrerPolicyHeaderValue,
+ IgnoreErrors());
+
+ nsAutoCString coepHeader;
+ headers->Get("cross-origin-embedder-policy"_ns, coepHeader, IgnoreErrors());
+
+ nsILoadInfo::CrossOriginEmbedderPolicy coep =
+ NS_GetCrossOriginEmbedderPolicyFromHeader(
+ coepHeader, mWorkerRef->Private()->Trials().IsEnabled(
+ OriginTrial::CoepCredentialless));
+
+ rv = ScriptResponseHeaderProcessor::ProcessCrossOriginEmbedderPolicyHeader(
+ mWorkerRef->Private(), coep, loadContext->IsTopLevel());
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Fail(rv);
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ response->GetBody(getter_AddRefs(inputStream));
+ mChannelInfo = response->GetChannelInfo();
+ const UniquePtr<PrincipalInfo>& pInfo = response->GetPrincipalInfo();
+ if (pInfo) {
+ mPrincipalInfo = mozilla::MakeUnique<PrincipalInfo>(*pInfo);
+ }
+
+ if (!inputStream) {
+ loadContext->mCacheStatus = WorkerLoadContext::Cached;
+
+ if (mRequestHandle->IsCancelled()) {
+ auto* cacheCreator = mRequestHandle->GetCacheCreator();
+ if (cacheCreator) {
+ cacheCreator->DeleteCache(mRequestHandle->GetCancelResult());
+ }
+ return;
+ }
+
+ nsresult rv = DataReceivedFromCache(
+ (uint8_t*)"", 0, mChannelInfo, std::move(mPrincipalInfo),
+ mCSPHeaderValue, mCSPReportOnlyHeaderValue, mReferrerPolicyHeaderValue);
+
+ mRequestHandle->OnStreamComplete(rv);
+ return;
+ }
+
+ MOZ_ASSERT(!mPump);
+ rv = NS_NewInputStreamPump(getter_AddRefs(mPump), inputStream.forget(),
+ 0, /* default segsize */
+ 0, /* default segcount */
+ false, /* default closeWhenDone */
+ mMainThreadEventTarget);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Fail(rv);
+ return;
+ }
+
+ nsCOMPtr<nsIStreamLoader> loader;
+ rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Fail(rv);
+ return;
+ }
+
+ rv = mPump->AsyncRead(loader);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mPump = nullptr;
+ Fail(rv);
+ return;
+ }
+
+ nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(mPump);
+ if (rr) {
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ RefPtr<TaskQueue> queue =
+ TaskQueue::Create(sts.forget(), "CacheLoadHandler STS Delivery Queue");
+ rv = rr->RetargetDeliveryTo(queue);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to dispatch the nsIInputStreamPump to a IO thread.");
+ }
+ }
+
+ loadContext->mCacheStatus = WorkerLoadContext::ReadingFromCache;
+}
+
+NS_IMETHODIMP
+CacheLoadHandler::OnStreamComplete(nsIStreamLoader* aLoader,
+ nsISupports* aContext, nsresult aStatus,
+ uint32_t aStringLen,
+ const uint8_t* aString) {
+ AssertIsOnMainThread();
+ if (mRequestHandle->IsEmpty()) {
+ return NS_OK;
+ }
+ WorkerLoadContext* loadContext = mRequestHandle->GetContext();
+
+ mPump = nullptr;
+
+ if (NS_FAILED(aStatus)) {
+ MOZ_ASSERT(loadContext->mCacheStatus ==
+ WorkerLoadContext::ReadingFromCache ||
+ loadContext->mCacheStatus == WorkerLoadContext::Cancel);
+ Fail(aStatus);
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(loadContext->mCacheStatus == WorkerLoadContext::ReadingFromCache);
+ loadContext->mCacheStatus = WorkerLoadContext::Cached;
+
+ MOZ_ASSERT(mPrincipalInfo);
+
+ nsresult rv = DataReceivedFromCache(
+ aString, aStringLen, mChannelInfo, std::move(mPrincipalInfo),
+ mCSPHeaderValue, mCSPReportOnlyHeaderValue, mReferrerPolicyHeaderValue);
+ return mRequestHandle->OnStreamComplete(rv);
+}
+
+nsresult CacheLoadHandler::DataReceivedFromCache(
+ const uint8_t* aString, uint32_t aStringLen,
+ const mozilla::dom::ChannelInfo& aChannelInfo,
+ UniquePtr<PrincipalInfo> aPrincipalInfo, const nsACString& aCSPHeaderValue,
+ const nsACString& aCSPReportOnlyHeaderValue,
+ const nsACString& aReferrerPolicyHeaderValue) {
+ AssertIsOnMainThread();
+ if (mRequestHandle->IsEmpty()) {
+ return NS_OK;
+ }
+ WorkerLoadContext* loadContext = mRequestHandle->GetContext();
+
+ MOZ_ASSERT(loadContext->mCacheStatus == WorkerLoadContext::Cached);
+ MOZ_ASSERT(loadContext->mRequest);
+
+ auto responsePrincipalOrErr = PrincipalInfoToPrincipal(*aPrincipalInfo);
+ MOZ_DIAGNOSTIC_ASSERT(responsePrincipalOrErr.isOk());
+
+ nsIPrincipal* principal = mWorkerRef->Private()->GetPrincipal();
+ if (!principal) {
+ WorkerPrivate* parentWorker = mWorkerRef->Private()->GetParent();
+ MOZ_ASSERT(parentWorker, "Must have a parent!");
+ principal = parentWorker->GetPrincipal();
+ }
+
+ nsCOMPtr<nsIPrincipal> responsePrincipal = responsePrincipalOrErr.unwrap();
+
+ loadContext->mMutedErrorFlag.emplace(!principal->Subsumes(responsePrincipal));
+
+ // May be null.
+ Document* parentDoc = mWorkerRef->Private()->GetDocument();
+
+ // Use the regular ScriptDecoder Decoder for this grunt work! Should be just
+ // fine because we're running on the main thread.
+ nsresult rv;
+
+ // Set the Source type to "text" for decoding.
+ loadContext->mRequest->SetTextSource(loadContext);
+
+ rv = mDecoder->DecodeRawData(loadContext->mRequest, aString, aStringLen,
+ /* aEndOfStream = */ true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!loadContext->mRequest->ScriptTextLength()) {
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
+ parentDoc, nsContentUtils::eDOM_PROPERTIES,
+ "EmptyWorkerSourceWarning");
+ }
+
+ nsCOMPtr<nsIURI> finalURI;
+ rv = NS_NewURI(getter_AddRefs(finalURI), loadContext->mFullURL);
+ if (!loadContext->mRequest->mBaseURL) {
+ loadContext->mRequest->mBaseURL = finalURI;
+ }
+ if (loadContext->IsTopLevel()) {
+ if (NS_SUCCEEDED(rv)) {
+ mWorkerRef->Private()->SetBaseURI(finalURI);
+ }
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ nsIPrincipal* principal = mWorkerRef->Private()->GetPrincipal();
+ MOZ_DIAGNOSTIC_ASSERT(principal);
+
+ bool equal = false;
+ MOZ_ALWAYS_SUCCEEDS(responsePrincipal->Equals(principal, &equal));
+ MOZ_DIAGNOSTIC_ASSERT(equal);
+
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ if (parentDoc) {
+ csp = parentDoc->GetCsp();
+ }
+ MOZ_DIAGNOSTIC_ASSERT(!csp);
+#endif
+
+ mWorkerRef->Private()->InitChannelInfo(aChannelInfo);
+
+ nsILoadGroup* loadGroup = mWorkerRef->Private()->GetLoadGroup();
+ MOZ_DIAGNOSTIC_ASSERT(loadGroup);
+
+ // Override the principal on the WorkerPrivate. This is only necessary
+ // in order to get a principal with exactly the correct URL. The fetch
+ // referrer logic depends on the WorkerPrivate principal having a URL
+ // that matches the worker script URL. If bug 1340694 is ever fixed
+ // this can be removed.
+ // XXX: force the partitionedPrincipal to be equal to the response one.
+ // This is OK for now because we don't want to expose partitionedPrincipal
+ // functionality in ServiceWorkers yet.
+ rv = mWorkerRef->Private()->SetPrincipalsAndCSPOnMainThread(
+ responsePrincipal, responsePrincipal, loadGroup, nullptr);
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ rv = mWorkerRef->Private()->SetCSPFromHeaderValues(
+ aCSPHeaderValue, aCSPReportOnlyHeaderValue);
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+
+ mWorkerRef->Private()->UpdateReferrerInfoFromHeader(
+ aReferrerPolicyHeaderValue);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ DataReceived();
+ }
+
+ return rv;
+}
+
+void CacheLoadHandler::DataReceived() {
+ MOZ_ASSERT(!mRequestHandle->IsEmpty());
+ WorkerLoadContext* loadContext = mRequestHandle->GetContext();
+
+ if (loadContext->IsTopLevel()) {
+ WorkerPrivate* parent = mWorkerRef->Private()->GetParent();
+
+ if (parent) {
+ // XHR Params Allowed
+ mWorkerRef->Private()->SetXHRParamsAllowed(parent->XHRParamsAllowed());
+
+ // Set Eval and ContentSecurityPolicy
+ mWorkerRef->Private()->SetCsp(parent->GetCsp());
+ mWorkerRef->Private()->SetEvalAllowed(parent->IsEvalAllowed());
+ mWorkerRef->Private()->SetWasmEvalAllowed(parent->IsWasmEvalAllowed());
+ }
+ }
+}
+
+} // namespace workerinternals::loader
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/loader/CacheLoadHandler.h b/dom/workers/loader/CacheLoadHandler.h
new file mode 100644
index 0000000000..b1e164b79e
--- /dev/null
+++ b/dom/workers/loader/CacheLoadHandler.h
@@ -0,0 +1,221 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_workers_CacheLoadHandler_h__
+#define mozilla_dom_workers_CacheLoadHandler_h__
+
+#include "nsIContentPolicy.h"
+#include "nsIInputStreamPump.h"
+#include "nsIStreamLoader.h"
+#include "nsStringFwd.h"
+#include "nsStreamUtils.h"
+
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/dom/CacheBinding.h"
+#include "mozilla/dom/ChannelInfo.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/ScriptLoadHandler.h"
+#include "mozilla/dom/cache/Cache.h"
+#include "mozilla/dom/cache/CacheStorage.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerRef.h"
+
+#include "mozilla/dom/workerinternals/ScriptLoader.h"
+
+using mozilla::dom::cache::Cache;
+using mozilla::dom::cache::CacheStorage;
+using mozilla::ipc::PrincipalInfo;
+
+namespace mozilla::dom {
+
+class WorkerLoadContext;
+
+namespace workerinternals::loader {
+
+/*
+ * [DOMDOC] CacheLoadHandler for Workers
+ *
+ * A LoadHandler is a ScriptLoader helper class that reacts to an
+ * nsIStreamLoader's events for loading JS scripts. It is primarily responsible
+ * for decoding the stream into UTF8 or UTF16. Additionally, it takes care of
+ * any work that needs to follow the completion of a stream. Every LoadHandler
+ * also manages additional tasks for the type of load that it is doing.
+ *
+ * CacheLoadHandler is a specialized LoadHandler used by ServiceWorkers to
+ * implement the installation model used by ServiceWorkers to support running
+ * offline. When a ServiceWorker is installed, its main script is evaluated and
+ * all script resources that are loaded are saved. The spec does not specify the
+ * storage mechanism for this, but we chose to reuse the Cache API[1] mechanism
+ * that we expose to content to also store the script and its dependencies. We
+ * store the script resources in a special chrome namespace CacheStorage that is
+ * not visible to content. Each distinct ServiceWorker installation gets its own
+ * Cache keyed by a randomly-generated UUID.
+ *
+ * In terms of specification, this class implements step 4 of
+ * https://w3c.github.io/ServiceWorker/#importscripts
+ *
+ * Relationship to NetworkLoadHandler
+ *
+ * During ServiceWorker installation, the CacheLoadHandler falls back on the
+ * NetworkLoadHandler by calling `mLoader->LoadScript(...)`. If a script has not
+ * been seen before, then we will fall back on loading from the network.
+ * However, if the ServiceWorker is already installed, an error will be
+ * generated and the ServiceWorker will fail to load, per spec.
+ *
+ * CacheLoadHandler does not persist some pieces of information, such as the
+ * sourceMapUrl. Also, the DOM Cache API storage does not yet support alternate
+ * data streams for JS Bytecode or WASM caching; this is tracked by Bug 1336199.
+ *
+ * [1]: https://developer.mozilla.org/en-US/docs/Web/API/caches
+ *
+ */
+
+class CacheLoadHandler final : public PromiseNativeHandler,
+ public nsIStreamLoaderObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLOADEROBSERVER
+
+ CacheLoadHandler(ThreadSafeWorkerRef* aWorkerRef,
+ ThreadSafeRequestHandle* aRequestHandle,
+ bool aIsWorkerScript,
+ bool aOnlyExistingCachedResourcesAllowed,
+ WorkerScriptLoader* aLoader);
+
+ void Fail(nsresult aRv);
+
+ void Load(Cache* aCache);
+
+ virtual void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override;
+
+ virtual void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override;
+
+ private:
+ ~CacheLoadHandler() { AssertIsOnMainThread(); }
+
+ nsresult DataReceivedFromCache(const uint8_t* aString, uint32_t aStringLen,
+ const mozilla::dom::ChannelInfo& aChannelInfo,
+ UniquePtr<PrincipalInfo> aPrincipalInfo,
+ const nsACString& aCSPHeaderValue,
+ const nsACString& aCSPReportOnlyHeaderValue,
+ const nsACString& aReferrerPolicyHeaderValue);
+ void DataReceived();
+
+ RefPtr<ThreadSafeRequestHandle> mRequestHandle;
+ const RefPtr<WorkerScriptLoader> mLoader;
+ RefPtr<ThreadSafeWorkerRef> mWorkerRef;
+ const bool mIsWorkerScript;
+ bool mFailed;
+ bool mOnlyExistingCachedResourcesAllowed;
+ nsCOMPtr<nsIInputStreamPump> mPump;
+ nsCOMPtr<nsIURI> mBaseURI;
+ mozilla::dom::ChannelInfo mChannelInfo;
+ UniquePtr<PrincipalInfo> mPrincipalInfo;
+ UniquePtr<ScriptDecoder> mDecoder;
+ nsCString mCSPHeaderValue;
+ nsCString mCSPReportOnlyHeaderValue;
+ nsCString mReferrerPolicyHeaderValue;
+ nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
+};
+
+/*
+ * CacheCreator
+ *
+ * The CacheCreator is responsible for maintaining a CacheStorage for the
+ * purposes of caching ServiceWorkers (see comment on CacheLoadHandler). In
+ * addition, it tracks all CacheLoadHandlers and is used for cleanup once
+ * loading has finished.
+ *
+ */
+
+class CacheCreator final : public PromiseNativeHandler {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit CacheCreator(WorkerPrivate* aWorkerPrivate);
+
+ void AddLoader(MovingNotNull<RefPtr<CacheLoadHandler>> aLoader) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mCacheStorage);
+ mLoaders.AppendElement(std::move(aLoader));
+ }
+
+ virtual void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override;
+
+ virtual void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override;
+
+ // Try to load from cache with aPrincipal used for cache access.
+ nsresult Load(nsIPrincipal* aPrincipal);
+
+ Cache* Cache_() const {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mCache);
+ return mCache;
+ }
+
+ nsIGlobalObject* Global() const {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mSandboxGlobalObject);
+ return mSandboxGlobalObject;
+ }
+
+ void DeleteCache(nsresult aReason);
+
+ private:
+ ~CacheCreator() = default;
+
+ nsresult CreateCacheStorage(nsIPrincipal* aPrincipal);
+
+ void FailLoaders(nsresult aRv);
+
+ RefPtr<Cache> mCache;
+ RefPtr<CacheStorage> mCacheStorage;
+ nsCOMPtr<nsIGlobalObject> mSandboxGlobalObject;
+ nsTArray<NotNull<RefPtr<CacheLoadHandler>>> mLoaders;
+
+ nsString mCacheName;
+ OriginAttributes mOriginAttributes;
+};
+
+/*
+ * CachePromiseHandler
+ *
+ * This promise handler is used to track if a ServiceWorker has been written to
+ * Cache. It is responsible for tracking the state of the ServiceWorker being
+ * cached. It also handles cancelling caching of a ServiceWorker if loading is
+ * interrupted. It is initialized by the NetworkLoadHandler as part of the first
+ * load of a ServiceWorker.
+ *
+ */
+class CachePromiseHandler final : public PromiseNativeHandler {
+ public:
+ NS_DECL_ISUPPORTS
+
+ CachePromiseHandler(WorkerScriptLoader* aLoader,
+ ThreadSafeRequestHandle* aRequestHandle);
+
+ virtual void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override;
+
+ virtual void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override;
+
+ private:
+ ~CachePromiseHandler() { AssertIsOnMainThread(); }
+
+ RefPtr<WorkerScriptLoader> mLoader;
+ RefPtr<ThreadSafeRequestHandle> mRequestHandle;
+};
+
+} // namespace workerinternals::loader
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_workers_CacheLoadHandler_h__ */
diff --git a/dom/workers/loader/NetworkLoadHandler.cpp b/dom/workers/loader/NetworkLoadHandler.cpp
new file mode 100644
index 0000000000..9c2c243066
--- /dev/null
+++ b/dom/workers/loader/NetworkLoadHandler.cpp
@@ -0,0 +1,393 @@
+/* -*- 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 "NetworkLoadHandler.h"
+#include "CacheLoadHandler.h" // CachePromiseHandler
+
+#include "nsContentUtils.h"
+#include "nsIChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIPrincipal.h"
+#include "nsIScriptError.h"
+#include "nsNetUtil.h"
+
+#include "mozilla/Encoding.h"
+#include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "mozilla/dom/InternalResponse.h"
+#include "mozilla/dom/ServiceWorkerBinding.h"
+#include "mozilla/dom/ServiceWorkerManager.h"
+#include "mozilla/dom/ScriptLoader.h"
+#include "mozilla/dom/Response.h"
+#include "mozilla/dom/WorkerScope.h"
+
+#include "mozilla/dom/workerinternals/ScriptLoader.h" // WorkerScriptLoader
+
+using mozilla::ipc::PrincipalInfo;
+
+namespace mozilla {
+namespace dom {
+
+namespace workerinternals::loader {
+
+NS_IMPL_ISUPPORTS(NetworkLoadHandler, nsIStreamLoaderObserver,
+ nsIRequestObserver)
+
+NetworkLoadHandler::NetworkLoadHandler(WorkerScriptLoader* aLoader,
+ ThreadSafeRequestHandle* aRequestHandle)
+ : mLoader(aLoader),
+ mWorkerRef(aLoader->mWorkerRef),
+ mRequestHandle(aRequestHandle) {
+ MOZ_ASSERT(mLoader);
+
+ // Worker scripts are always decoded as UTF-8 per spec.
+ mDecoder = MakeUnique<ScriptDecoder>(UTF_8_ENCODING,
+ ScriptDecoder::BOMHandling::Remove);
+}
+
+NS_IMETHODIMP
+NetworkLoadHandler::OnStreamComplete(nsIStreamLoader* aLoader,
+ nsISupports* aContext, nsresult aStatus,
+ uint32_t aStringLen,
+ const uint8_t* aString) {
+ // If we have cancelled, or we have no mRequest, it means that the loader has
+ // shut down and we can exit early. If the cancel result is still NS_OK
+ if (mRequestHandle->IsEmpty()) {
+ return NS_OK;
+ }
+ nsresult rv = DataReceivedFromNetwork(aLoader, aStatus, aStringLen, aString);
+ return mRequestHandle->OnStreamComplete(rv);
+}
+
+nsresult NetworkLoadHandler::DataReceivedFromNetwork(nsIStreamLoader* aLoader,
+ nsresult aStatus,
+ uint32_t aStringLen,
+ const uint8_t* aString) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mRequestHandle->IsEmpty());
+ WorkerLoadContext* loadContext = mRequestHandle->GetContext();
+
+ if (!loadContext->mChannel) {
+ return NS_BINDING_ABORTED;
+ }
+
+ loadContext->mChannel = nullptr;
+
+ if (NS_FAILED(aStatus)) {
+ return aStatus;
+ }
+
+ if (mRequestHandle->IsCancelled()) {
+ return mRequestHandle->GetCancelResult();
+ }
+
+ NS_ASSERTION(aString, "This should never be null!");
+
+ nsCOMPtr<nsIRequest> request;
+ nsresult rv = aLoader->GetRequest(getter_AddRefs(request));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ MOZ_ASSERT(channel);
+
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ NS_ASSERTION(ssm, "Should never be null!");
+
+ nsCOMPtr<nsIPrincipal> channelPrincipal;
+ rv =
+ ssm->GetChannelResultPrincipal(channel, getter_AddRefs(channelPrincipal));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsIPrincipal* principal = mWorkerRef->Private()->GetPrincipal();
+ if (!principal) {
+ WorkerPrivate* parentWorker = mWorkerRef->Private()->GetParent();
+ MOZ_ASSERT(parentWorker, "Must have a parent!");
+ principal = parentWorker->GetPrincipal();
+ }
+
+#ifdef DEBUG
+ if (loadContext->IsTopLevel()) {
+ nsCOMPtr<nsIPrincipal> loadingPrincipal =
+ mWorkerRef->Private()->GetLoadingPrincipal();
+ // if we are not in a ServiceWorker, and the principal is not null, then
+ // the loading principal must subsume the worker principal if it is not a
+ // nullPrincipal (sandbox).
+ MOZ_ASSERT(!loadingPrincipal || loadingPrincipal->GetIsNullPrincipal() ||
+ principal->GetIsNullPrincipal() ||
+ loadingPrincipal->Subsumes(principal));
+ }
+#endif
+
+ // We don't mute the main worker script becase we've already done
+ // same-origin checks on them so we should be able to see their errors.
+ // Note that for data: url, where we allow it through the same-origin check
+ // but then give it a different origin.
+ loadContext->mMutedErrorFlag.emplace(!loadContext->IsTopLevel() &&
+ !principal->Subsumes(channelPrincipal));
+
+ // Make sure we're not seeing the result of a 404 or something by checking
+ // the 'requestSucceeded' attribute on the http channel.
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
+ nsAutoCString tCspHeaderValue, tCspROHeaderValue, tRPHeaderCValue;
+
+ if (httpChannel) {
+ bool requestSucceeded;
+ rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!requestSucceeded) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ Unused << httpChannel->GetResponseHeader("content-security-policy"_ns,
+ tCspHeaderValue);
+
+ Unused << httpChannel->GetResponseHeader(
+ "content-security-policy-report-only"_ns, tCspROHeaderValue);
+
+ Unused << httpChannel->GetResponseHeader("referrer-policy"_ns,
+ tRPHeaderCValue);
+
+ nsAutoCString sourceMapURL;
+ if (nsContentUtils::GetSourceMapURL(httpChannel, sourceMapURL)) {
+ loadContext->mRequest->mSourceMapURL =
+ Some(NS_ConvertUTF8toUTF16(sourceMapURL));
+ }
+ }
+
+ // May be null.
+ Document* parentDoc = mWorkerRef->Private()->GetDocument();
+
+ // Set the Source type to "text" for decoding.
+ loadContext->mRequest->SetTextSource(loadContext);
+
+ // Use the regular ScriptDecoder Decoder for this grunt work! Should be just
+ // fine because we're running on the main thread.
+ rv = mDecoder->DecodeRawData(loadContext->mRequest, aString, aStringLen,
+ /* aEndOfStream = */ true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!loadContext->mRequest->ScriptTextLength()) {
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns,
+ parentDoc, nsContentUtils::eDOM_PROPERTIES,
+ "EmptyWorkerSourceWarning");
+ }
+
+ // For modules, we need to store the base URI on the module request object,
+ // rather than on the worker private (as we do for classic scripts). This is
+ // because module loading is shared across multiple components, with
+ // ScriptLoadRequests being the common structure among them. This specific
+ // use of the base url is used when resolving the module specifier for child
+ // modules.
+ nsCOMPtr<nsIURI> uri;
+ rv = channel->GetOriginalURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ loadContext->mRequest->SetBaseURLFromChannelAndOriginalURI(channel, uri);
+
+ // Figure out what we actually loaded.
+ nsCOMPtr<nsIURI> finalURI;
+ rv = NS_GetFinalChannelURI(channel, getter_AddRefs(finalURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (principal->IsSameOrigin(finalURI)) {
+ nsCString filename;
+ rv = finalURI->GetSpec(filename);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!filename.IsEmpty()) {
+ // This will help callers figure out what their script url resolved to
+ // in case of errors, and is used for debugging.
+ // The full URL shouldn't be exposed to the debugger if cross origin.
+ // See Bug 1634872.
+ loadContext->mRequest->mURL = filename;
+ }
+ }
+
+ // Update the principal of the worker and its base URI if we just loaded the
+ // worker's primary script.
+ bool isDynamic = loadContext->mRequest->IsModuleRequest() &&
+ loadContext->mRequest->AsModuleRequest()->IsDynamicImport();
+ if (loadContext->IsTopLevel() && !isDynamic) {
+ // Take care of the base URI first.
+ mWorkerRef->Private()->SetBaseURI(finalURI);
+
+ // Store the channel info if needed.
+ mWorkerRef->Private()->InitChannelInfo(channel);
+
+ // Our final channel principal should match the loading principal
+ // in terms of the origin. This used to be an assert, but it seems
+ // there are some rare cases where this check can fail in practice.
+ // Perhaps some browser script setting nsIChannel.owner, etc.
+ NS_ENSURE_TRUE(mWorkerRef->Private()->FinalChannelPrincipalIsValid(channel),
+ NS_ERROR_FAILURE);
+
+ // However, we must still override the principal since the nsIPrincipal
+ // URL may be different due to same-origin redirects. Unfortunately this
+ // URL must exactly match the final worker script URL in order to
+ // properly set the referrer header on fetch/xhr requests. If bug 1340694
+ // is ever fixed this can be removed.
+ rv = mWorkerRef->Private()->SetPrincipalsAndCSPFromChannel(channel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIContentSecurityPolicy> csp = mWorkerRef->Private()->GetCsp();
+ // We did inherit CSP in bug 1223647. If we do not already have a CSP, we
+ // should get it from the HTTP headers on the worker script.
+ if (!csp) {
+ rv = mWorkerRef->Private()->SetCSPFromHeaderValues(tCspHeaderValue,
+ tCspROHeaderValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ csp->EnsureEventTarget(mWorkerRef->Private()->MainThreadEventTarget());
+ }
+
+ mWorkerRef->Private()->UpdateReferrerInfoFromHeader(tRPHeaderCValue);
+
+ WorkerPrivate* parent = mWorkerRef->Private()->GetParent();
+ if (parent) {
+ // XHR Params Allowed
+ mWorkerRef->Private()->SetXHRParamsAllowed(parent->XHRParamsAllowed());
+ }
+
+ nsCOMPtr<nsILoadInfo> chanLoadInfo = channel->LoadInfo();
+ if (chanLoadInfo) {
+ mLoader->SetController(chanLoadInfo->GetController());
+ }
+
+ // If we are loading a blob URL we must inherit the controller
+ // from the parent. This is a bit odd as the blob URL may have
+ // been created in a different context with a different controller.
+ // For now, though, this is what the spec says. See:
+ //
+ // https://github.com/w3c/ServiceWorker/issues/1261
+ //
+ if (IsBlobURI(mWorkerRef->Private()->GetBaseURI())) {
+ MOZ_DIAGNOSTIC_ASSERT(mLoader->GetController().isNothing());
+ mLoader->SetController(mWorkerRef->Private()->GetParentController());
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+NetworkLoadHandler::OnStartRequest(nsIRequest* aRequest) {
+ nsresult rv = PrepareForRequest(aRequest);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->Cancel(rv);
+ }
+
+ return rv;
+}
+
+nsresult NetworkLoadHandler::PrepareForRequest(nsIRequest* aRequest) {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(!mRequestHandle->IsEmpty());
+ WorkerLoadContext* loadContext = mRequestHandle->GetContext();
+
+ // If one load info cancels or hits an error, it can race with the start
+ // callback coming from another load info.
+ if (mRequestHandle->IsCancelled()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+
+ // Checking the MIME type is only required for ServiceWorkers'
+ // importScripts, per step 10 of
+ // https://w3c.github.io/ServiceWorker/#importscripts
+ //
+ // "Extract a MIME type from the response’s header list. If this MIME type
+ // (ignoring parameters) is not a JavaScript MIME type, return a network
+ // error."
+ if (mWorkerRef->Private()->IsServiceWorker()) {
+ nsAutoCString mimeType;
+ channel->GetContentType(mimeType);
+
+ if (!nsContentUtils::IsJavascriptMIMEType(
+ NS_ConvertUTF8toUTF16(mimeType))) {
+ const nsCString& scope = mWorkerRef->Private()
+ ->GetServiceWorkerRegistrationDescriptor()
+ .Scope();
+
+ ServiceWorkerManager::LocalizeAndReportToAllClients(
+ scope, "ServiceWorkerRegisterMimeTypeError2",
+ nsTArray<nsString>{
+ NS_ConvertUTF8toUTF16(scope), NS_ConvertUTF8toUTF16(mimeType),
+ NS_ConvertUTF8toUTF16(loadContext->mRequest->mURL)});
+
+ return NS_ERROR_DOM_NETWORK_ERR;
+ }
+ }
+
+ // We synthesize the result code, but its never exposed to content.
+ SafeRefPtr<mozilla::dom::InternalResponse> ir =
+ MakeSafeRefPtr<mozilla::dom::InternalResponse>(200, "OK"_ns);
+ ir->SetBody(loadContext->mCacheReadStream,
+ InternalResponse::UNKNOWN_BODY_SIZE);
+
+ // Drop our reference to the stream now that we've passed it along, so it
+ // doesn't hang around once the cache is done with it and keep data alive.
+ loadContext->mCacheReadStream = nullptr;
+
+ // Set the channel info of the channel on the response so that it's
+ // saved in the cache.
+ ir->InitChannelInfo(channel);
+
+ // Save the principal of the channel since its URI encodes the script URI
+ // rather than the ServiceWorkerRegistrationInfo URI.
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ NS_ASSERTION(ssm, "Should never be null!");
+
+ nsCOMPtr<nsIPrincipal> channelPrincipal;
+ MOZ_TRY(ssm->GetChannelResultPrincipal(channel,
+ getter_AddRefs(channelPrincipal)));
+
+ UniquePtr<PrincipalInfo> principalInfo(new PrincipalInfo());
+ MOZ_TRY(PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get()));
+
+ ir->SetPrincipalInfo(std::move(principalInfo));
+ ir->Headers()->FillResponseHeaders(channel);
+
+ RefPtr<mozilla::dom::Response> response = new mozilla::dom::Response(
+ mRequestHandle->GetCacheCreator()->Global(), std::move(ir), nullptr);
+
+ mozilla::dom::RequestOrUSVString request;
+
+ MOZ_ASSERT(!loadContext->mFullURL.IsEmpty());
+ request.SetAsUSVString().ShareOrDependUpon(loadContext->mFullURL);
+
+ // This JSContext will not end up executing JS code because here there are
+ // no ReadableStreams involved.
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
+ ErrorResult error;
+ RefPtr<Promise> cachePromise =
+ mRequestHandle->GetCacheCreator()->Cache_()->Put(jsapi.cx(), request,
+ *response, error);
+ error.WouldReportJSException();
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+
+ RefPtr<CachePromiseHandler> promiseHandler =
+ new CachePromiseHandler(mLoader, mRequestHandle);
+ cachePromise->AppendNativeHandler(promiseHandler);
+
+ loadContext->mCachePromise.swap(cachePromise);
+ loadContext->mCacheStatus = WorkerLoadContext::WritingToCache;
+
+ return NS_OK;
+}
+
+} // namespace workerinternals::loader
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/loader/NetworkLoadHandler.h b/dom/workers/loader/NetworkLoadHandler.h
new file mode 100644
index 0000000000..b32c9d8d8e
--- /dev/null
+++ b/dom/workers/loader/NetworkLoadHandler.h
@@ -0,0 +1,79 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_workers_NetworkLoadHandler_h__
+#define mozilla_dom_workers_NetworkLoadHandler_h__
+
+#include "nsIStreamLoader.h"
+#include "mozilla/dom/WorkerLoadContext.h"
+#include "mozilla/dom/ScriptLoadHandler.h"
+#include "mozilla/dom/WorkerRef.h"
+
+namespace mozilla::dom::workerinternals::loader {
+
+class WorkerScriptLoader;
+
+/*
+ * [DOMDOC] NetworkLoadHandler for Workers
+ *
+ * A LoadHandler is a ScriptLoader helper class that reacts to an
+ * nsIStreamLoader's events for
+ * loading JS scripts. It is primarily responsible for decoding the stream into
+ * UTF8 or UTF16. Additionally, it takes care of any work that needs to follow
+ * the completion of a stream. Every LoadHandler also manages additional tasks
+ * for the type of load that it is doing.
+ *
+ * As part of worker loading we have an number of tasks that we need to take
+ * care of after a successfully completed stream, including setting a final URI
+ * on the WorkerPrivate if we have loaded a main script, or handling CSP issues.
+ * These are handled in DataReceivedFromNetwork, and implement roughly the same
+ * set of tasks as you will find in the CacheLoadhandler, which has a companion
+ * method DataReceivedFromcache.
+ *
+ * In the worker context, the LoadHandler is run on the main thread, and all
+ * work in this file ultimately is done by the main thread, including decoding.
+ *
+ */
+
+class NetworkLoadHandler final : public nsIStreamLoaderObserver,
+ public nsIRequestObserver {
+ public:
+ NS_DECL_ISUPPORTS
+
+ NetworkLoadHandler(WorkerScriptLoader* aLoader,
+ ThreadSafeRequestHandle* aRequestHandle);
+
+ NS_IMETHOD
+ OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
+ nsresult aStatus, uint32_t aStringLen,
+ const uint8_t* aString) override;
+
+ nsresult DataReceivedFromNetwork(nsIStreamLoader* aLoader, nsresult aStatus,
+ uint32_t aStringLen, const uint8_t* aString);
+
+ NS_IMETHOD
+ OnStartRequest(nsIRequest* aRequest) override;
+
+ nsresult PrepareForRequest(nsIRequest* aRequest);
+
+ NS_IMETHOD
+ OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) override {
+ // Nothing to do here!
+ return NS_OK;
+ }
+
+ private:
+ ~NetworkLoadHandler() = default;
+
+ RefPtr<WorkerScriptLoader> mLoader;
+ UniquePtr<ScriptDecoder> mDecoder;
+ RefPtr<ThreadSafeWorkerRef> mWorkerRef;
+ RefPtr<ThreadSafeRequestHandle> mRequestHandle;
+};
+
+} // namespace mozilla::dom::workerinternals::loader
+
+#endif /* mozilla_dom_workers_NetworkLoadHandler_h__ */
diff --git a/dom/workers/loader/ScriptResponseHeaderProcessor.cpp b/dom/workers/loader/ScriptResponseHeaderProcessor.cpp
new file mode 100644
index 0000000000..6dc5c9e1ef
--- /dev/null
+++ b/dom/workers/loader/ScriptResponseHeaderProcessor.cpp
@@ -0,0 +1,79 @@
+/* -*- 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 "ScriptResponseHeaderProcessor.h"
+#include "mozilla/Try.h"
+#include "mozilla/dom/WorkerScope.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace workerinternals {
+
+namespace loader {
+
+NS_IMPL_ISUPPORTS(ScriptResponseHeaderProcessor, nsIRequestObserver);
+
+nsresult ScriptResponseHeaderProcessor::ProcessCrossOriginEmbedderPolicyHeader(
+ WorkerPrivate* aWorkerPrivate,
+ nsILoadInfo::CrossOriginEmbedderPolicy aPolicy, bool aIsMainScript) {
+ MOZ_ASSERT(aWorkerPrivate);
+
+ if (aIsMainScript) {
+ MOZ_TRY(aWorkerPrivate->SetEmbedderPolicy(aPolicy));
+ } else {
+ // NOTE: Spec doesn't mention non-main scripts must match COEP header with
+ // the main script, but it must pass CORP checking.
+ // see: wpt window-simple-success.https.html, the worker import script
+ // test-incrementer.js without coep header.
+ Unused << NS_WARN_IF(!aWorkerPrivate->MatchEmbedderPolicy(aPolicy));
+ }
+
+ return NS_OK;
+}
+
+// Enforce strict MIME type checks for worker-imported scripts
+// https://github.com/whatwg/html/pull/4001
+nsresult ScriptResponseHeaderProcessor::EnsureJavaScriptMimeType(
+ nsIRequest* aRequest) {
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ MOZ_ASSERT(channel);
+ nsAutoCString mimeType;
+ channel->GetContentType(mimeType);
+ if (!nsContentUtils::IsJavascriptMIMEType(NS_ConvertUTF8toUTF16(mimeType))) {
+ return NS_ERROR_DOM_NETWORK_ERR;
+ }
+ return NS_OK;
+}
+
+nsresult ScriptResponseHeaderProcessor::ProcessCrossOriginEmbedderPolicyHeader(
+ nsIRequest* aRequest) {
+ nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aRequest);
+
+ // NOTE: the spec doesn't say what to do with non-HTTP workers.
+ // See: https://github.com/whatwg/html/issues/4916
+ if (!httpChannel) {
+ if (mIsMainScript) {
+ mWorkerPrivate->InheritOwnerEmbedderPolicyOrNull(aRequest);
+ }
+
+ return NS_OK;
+ }
+
+ nsILoadInfo::CrossOriginEmbedderPolicy coep;
+ MOZ_TRY(httpChannel->GetResponseEmbedderPolicy(
+ mWorkerPrivate->Trials().IsEnabled(OriginTrial::CoepCredentialless),
+ &coep));
+
+ return ProcessCrossOriginEmbedderPolicyHeader(mWorkerPrivate, coep,
+ mIsMainScript);
+}
+
+} // namespace loader
+} // namespace workerinternals
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/loader/ScriptResponseHeaderProcessor.h b/dom/workers/loader/ScriptResponseHeaderProcessor.h
new file mode 100644
index 0000000000..43a9bfde42
--- /dev/null
+++ b/dom/workers/loader/ScriptResponseHeaderProcessor.h
@@ -0,0 +1,94 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_workers_ScriptResponseHeaderProcessor_h__
+#define mozilla_dom_workers_ScriptResponseHeaderProcessor_h__
+
+#include "mozilla/dom/WorkerCommon.h"
+
+#include "nsIHttpChannel.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIStreamLoader.h"
+#include "nsStreamUtils.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_dom.h"
+
+namespace mozilla::dom {
+
+class WorkerPrivate;
+
+namespace workerinternals::loader {
+
+/* ScriptResponseHeaderProcessor
+ *
+ * This class handles Policy headers. It can be used as a RequestObserver in a
+ * Tee, as it is for NetworkLoadHandler in WorkerScriptLoader, or the static
+ * method can be called directly, as it is in CacheLoadHandler.
+ *
+ */
+
+class ScriptResponseHeaderProcessor final : public nsIRequestObserver {
+ public:
+ NS_DECL_ISUPPORTS
+
+ ScriptResponseHeaderProcessor(WorkerPrivate* aWorkerPrivate,
+ bool aIsMainScript, bool aIsImportScript)
+ : mWorkerPrivate(aWorkerPrivate),
+ mIsMainScript(aIsMainScript),
+ mIsImportScript(aIsImportScript) {
+ AssertIsOnMainThread();
+ }
+
+ NS_IMETHOD OnStartRequest(nsIRequest* aRequest) override {
+ nsresult rv = NS_OK;
+ if (mIsImportScript &&
+ StaticPrefs::dom_workers_importScripts_enforceStrictMimeType()) {
+ rv = EnsureJavaScriptMimeType(aRequest);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->Cancel(rv);
+ return NS_OK;
+ }
+ }
+
+ if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
+ return NS_OK;
+ }
+
+ rv = ProcessCrossOriginEmbedderPolicyHeader(aRequest);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRequest->Cancel(rv);
+ }
+
+ return rv;
+ }
+
+ NS_IMETHOD OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) override {
+ return NS_OK;
+ }
+
+ static nsresult ProcessCrossOriginEmbedderPolicyHeader(
+ WorkerPrivate* aWorkerPrivate,
+ nsILoadInfo::CrossOriginEmbedderPolicy aPolicy, bool aIsMainScript);
+
+ private:
+ ~ScriptResponseHeaderProcessor() = default;
+
+ nsresult EnsureJavaScriptMimeType(nsIRequest* aRequest);
+
+ nsresult ProcessCrossOriginEmbedderPolicyHeader(nsIRequest* aRequest);
+
+ WorkerPrivate* const mWorkerPrivate;
+ const bool mIsMainScript;
+ const bool mIsImportScript;
+};
+
+} // namespace workerinternals::loader
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_workers_ScriptResponseHeaderProcessor_h__ */
diff --git a/dom/workers/loader/WorkerLoadContext.cpp b/dom/workers/loader/WorkerLoadContext.cpp
new file mode 100644
index 0000000000..a788d5173c
--- /dev/null
+++ b/dom/workers/loader/WorkerLoadContext.cpp
@@ -0,0 +1,78 @@
+/* -*- 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 "WorkerLoadContext.h"
+#include "mozilla/dom/workerinternals/ScriptLoader.h"
+#include "CacheLoadHandler.h" // CacheCreator
+
+namespace mozilla {
+namespace dom {
+
+WorkerLoadContext::WorkerLoadContext(
+ Kind aKind, const Maybe<ClientInfo>& aClientInfo,
+ workerinternals::loader::WorkerScriptLoader* aScriptLoader,
+ bool aOnlyExistingCachedResourcesAllowed)
+ : JS::loader::LoadContextBase(JS::loader::ContextKind::Worker),
+ mKind(aKind),
+ mClientInfo(aClientInfo),
+ mScriptLoader(aScriptLoader),
+ mOnlyExistingCachedResourcesAllowed(
+ aOnlyExistingCachedResourcesAllowed){};
+
+ThreadSafeRequestHandle::ThreadSafeRequestHandle(
+ JS::loader::ScriptLoadRequest* aRequest, nsISerialEventTarget* aSyncTarget)
+ : mRequest(aRequest), mOwningEventTarget(aSyncTarget) {}
+
+already_AddRefed<JS::loader::ScriptLoadRequest>
+ThreadSafeRequestHandle::ReleaseRequest() {
+ RefPtr<JS::loader::ScriptLoadRequest> request;
+ mRequest.swap(request);
+ mRunnable = nullptr;
+ return request.forget();
+}
+
+nsresult ThreadSafeRequestHandle::OnStreamComplete(nsresult aStatus) {
+ return mRunnable->OnStreamComplete(this, aStatus);
+}
+
+void ThreadSafeRequestHandle::LoadingFinished(nsresult aRv) {
+ mRunnable->LoadingFinished(this, aRv);
+}
+
+void ThreadSafeRequestHandle::MaybeExecuteFinishedScripts() {
+ mRunnable->MaybeExecuteFinishedScripts(this);
+}
+
+bool ThreadSafeRequestHandle::IsCancelled() { return mRunnable->IsCancelled(); }
+
+nsresult ThreadSafeRequestHandle::GetCancelResult() {
+ return mRunnable->GetCancelResult();
+}
+
+workerinternals::loader::CacheCreator*
+ThreadSafeRequestHandle::GetCacheCreator() {
+ AssertIsOnMainThread();
+ return mRunnable->GetCacheCreator();
+}
+
+ThreadSafeRequestHandle::~ThreadSafeRequestHandle() {
+ // Normally we only touch mStrongRef on the owning thread. This is safe,
+ // however, because when we do use mStrongRef on the owning thread we are
+ // always holding a strong ref to the ThreadsafeHandle via the owning
+ // runnable. So we cannot run the ThreadsafeHandle destructor simultaneously.
+ if (!mRequest || mOwningEventTarget->IsOnCurrentThread()) {
+ return;
+ }
+
+ // Dispatch in NS_ProxyRelease is guaranteed to succeed here because we block
+ // shutdown until all Contexts have been destroyed. Therefore it is ok to have
+ // MOZ_ALWAYS_SUCCEED here.
+ MOZ_ALWAYS_SUCCEEDS(NS_ProxyRelease("ThreadSafeRequestHandle::mRequest",
+ mOwningEventTarget, mRequest.forget()));
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/workers/loader/WorkerLoadContext.h b/dom/workers/loader/WorkerLoadContext.h
new file mode 100644
index 0000000000..97362f2871
--- /dev/null
+++ b/dom/workers/loader/WorkerLoadContext.h
@@ -0,0 +1,219 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_workers_WorkerLoadContext_h__
+#define mozilla_dom_workers_WorkerLoadContext_h__
+
+#include "nsIChannel.h"
+#include "nsIInputStream.h"
+#include "nsIRequest.h"
+#include "mozilla/CORSMode.h"
+#include "mozilla/dom/Promise.h"
+#include "js/loader/ScriptKind.h"
+#include "js/loader/ScriptLoadRequest.h"
+#include "js/loader/LoadContextBase.h"
+
+class nsIReferrerInfo;
+class nsIURI;
+
+namespace mozilla::dom {
+
+class ClientInfo;
+class WorkerPrivate;
+
+namespace workerinternals::loader {
+class CacheCreator;
+class ScriptLoaderRunnable;
+class WorkerScriptLoader;
+} // namespace workerinternals::loader
+
+/*
+ * WorkerLoadContext (for all workers)
+ *
+ * LoadContexts augment the loading of a ScriptLoadRequest. They
+ * describe how a ScriptLoadRequests loading and evaluation needs to be
+ * augmented, based on the information provided by the loading context. The
+ * WorkerLoadContext has the following generic fields applied to all worker
+ * ScriptLoadRequests (and primarily used for error handling):
+ *
+ * * mMutedErrorFlag
+ * Set when we finish loading a script, and used to determine whether a
+ * given error is thrown or muted.
+ * * mLoadResult
+ * In order to report errors correctly in the worker thread, we need to
+ * move them from the main thread to the worker. This field records the
+ * load error, for throwing when we return to the worker thread.
+ * * mKind
+ * See documentation of WorkerLoadContext::Kind.
+ * * mClientInfo
+ * A snapshot of a global living in the system (see documentation for
+ * ClientInfo). In worker loading, this field is important for CSP
+ * information and knowing what to intercept for Service Worker
+ * interception.
+ * * mChannel
+ * The channel used by this request for it's load. Used for cancellation,
+ * in order to cancel the stream.
+ *
+ * The rest of the fields on this class focus on enabling the ServiceWorker
+ * usecase, in particular -- using the Cache API to store the worker so that
+ * in the case of (for example) a page refresh, the service worker itself is
+ * persisted so that it can do other work. For more details see the
+ * CacheLoadHandler.h file.
+ *
+ */
+
+class WorkerLoadContext : public JS::loader::LoadContextBase {
+ public:
+ /* Worker Load Context Kinds
+ *
+ * A script that is loaded and run as a worker can be one of several species.
+ * Each may have slightly different behavior, but they fall into roughly two
+ * categories: the Main Worker Script (the script that triggers the first
+ * load) and scripts that are attached to this main worker script.
+ *
+ * In the specification, the Main Worker Script is referred to as the "top
+ * level script" and is defined here:
+ * https://html.spec.whatwg.org/multipage/webappapis.html#fetching-scripts-is-top-level
+ */
+
+ enum Kind {
+ // Indicates that the is-top-level bit is true. This may be a Classic script
+ // or a Module script.
+ MainScript,
+ // We are importing a script from the worker via ImportScript. This may only
+ // be a Classic script.
+ ImportScript,
+ // We are importing a script from the worker via a Static Import. This may
+ // only
+ // be a Module script.
+ StaticImport,
+ DynamicImport,
+ // We have an attached debugger, and these should be treated specially and
+ // not like a main script (regardless of their type). This is not part of
+ // the specification.
+ DebuggerScript
+ };
+
+ WorkerLoadContext(Kind aKind, const Maybe<ClientInfo>& aClientInfo,
+ workerinternals::loader::WorkerScriptLoader* aScriptLoader,
+ bool aOnlyExistingCachedResourcesAllowed);
+
+ // Used to detect if the `is top-level` bit is set on a given module.
+ bool IsTopLevel() {
+ return mRequest->IsTopLevel() && (mKind == Kind::MainScript);
+ };
+
+ static Kind GetKind(bool isMainScript, bool isDebuggerScript) {
+ if (isDebuggerScript) {
+ return Kind::DebuggerScript;
+ }
+ if (isMainScript) {
+ return Kind::MainScript;
+ }
+ return Kind::ImportScript;
+ };
+
+ /* These fields are used by all workers */
+ Maybe<bool> mMutedErrorFlag;
+ nsresult mLoadResult = NS_ERROR_NOT_INITIALIZED;
+ bool mLoadingFinished = false;
+ bool mIsTopLevel = true;
+ Kind mKind;
+ Maybe<ClientInfo> mClientInfo;
+ nsCOMPtr<nsIChannel> mChannel;
+ RefPtr<workerinternals::loader::WorkerScriptLoader> mScriptLoader;
+
+ /* These fields are only used by service workers */
+ /* TODO: Split out a ServiceWorkerLoadContext */
+ // This full URL string is populated only if this object is used in a
+ // ServiceWorker.
+ nsString mFullURL;
+
+ // This promise is set only when the script is for a ServiceWorker but
+ // it's not in the cache yet. The promise is resolved when the full body is
+ // stored into the cache. mCachePromise will be set to nullptr after
+ // resolution.
+ RefPtr<Promise> mCachePromise;
+
+ // The reader stream the cache entry should be filled from, for those cases
+ // when we're going to have an mCachePromise.
+ nsCOMPtr<nsIInputStream> mCacheReadStream;
+
+ enum CacheStatus {
+ // By default a normal script is just loaded from the network. But for
+ // ServiceWorkers, we have to check if the cache contains the script and
+ // load it from the cache.
+ Uncached,
+
+ WritingToCache,
+
+ ReadingFromCache,
+
+ // This script has been loaded from the ServiceWorker cache.
+ Cached,
+
+ // This script must be stored in the ServiceWorker cache.
+ ToBeCached,
+
+ // Something went wrong or the worker went away.
+ Cancel
+ };
+
+ CacheStatus mCacheStatus = Uncached;
+
+ // If the requested script is not currently in the cache, should we initiate
+ // a request to fetch and cache it? Only ServiceWorkers that are being
+ // installed are allowed to go to the network (and then cache the result).
+ bool mOnlyExistingCachedResourcesAllowed = false;
+
+ bool IsAwaitingPromise() const { return bool(mCachePromise); }
+};
+
+class ThreadSafeRequestHandle final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ThreadSafeRequestHandle)
+
+ ThreadSafeRequestHandle(JS::loader::ScriptLoadRequest* aRequest,
+ nsISerialEventTarget* aSyncTarget);
+
+ JS::loader::ScriptLoadRequest* GetRequest() const { return mRequest; }
+
+ WorkerLoadContext* GetContext() { return mRequest->GetWorkerLoadContext(); }
+
+ bool IsEmpty() { return !mRequest; }
+
+ // Runnable controls
+ nsresult OnStreamComplete(nsresult aStatus);
+
+ void LoadingFinished(nsresult aRv);
+
+ void MaybeExecuteFinishedScripts();
+
+ bool IsCancelled();
+
+ bool Finished() {
+ return GetContext()->mLoadingFinished && !GetContext()->IsAwaitingPromise();
+ }
+
+ nsresult GetCancelResult();
+
+ already_AddRefed<JS::loader::ScriptLoadRequest> ReleaseRequest();
+
+ workerinternals::loader::CacheCreator* GetCacheCreator();
+
+ RefPtr<workerinternals::loader::ScriptLoaderRunnable> mRunnable;
+
+ bool mExecutionScheduled = false;
+
+ private:
+ ~ThreadSafeRequestHandle();
+
+ RefPtr<JS::loader::ScriptLoadRequest> mRequest;
+ nsCOMPtr<nsISerialEventTarget> mOwningEventTarget;
+};
+
+} // namespace mozilla::dom
+#endif /* mozilla_dom_workers_WorkerLoadContext_h__ */
diff --git a/dom/workers/loader/WorkerModuleLoader.cpp b/dom/workers/loader/WorkerModuleLoader.cpp
new file mode 100644
index 0000000000..da340c89bd
--- /dev/null
+++ b/dom/workers/loader/WorkerModuleLoader.cpp
@@ -0,0 +1,225 @@
+/* -*- 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 "js/experimental/JSStencil.h" // JS::Stencil, JS::CompileModuleScriptToStencil, JS::InstantiateModuleStencil
+#include "js/loader/ModuleLoadRequest.h"
+#include "mozilla/dom/RequestBinding.h"
+#include "mozilla/dom/WorkerLoadContext.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/workerinternals/ScriptLoader.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "WorkerModuleLoader.h"
+
+#include "nsISupportsImpl.h"
+
+namespace mozilla::dom::workerinternals::loader {
+
+//////////////////////////////////////////////////////////////
+// WorkerModuleLoader
+//////////////////////////////////////////////////////////////
+
+NS_IMPL_ADDREF_INHERITED(WorkerModuleLoader, JS::loader::ModuleLoaderBase)
+NS_IMPL_RELEASE_INHERITED(WorkerModuleLoader, JS::loader::ModuleLoaderBase)
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(WorkerModuleLoader,
+ JS::loader::ModuleLoaderBase)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WorkerModuleLoader)
+NS_INTERFACE_MAP_END_INHERITING(JS::loader::ModuleLoaderBase)
+
+WorkerModuleLoader::WorkerModuleLoader(WorkerScriptLoader* aScriptLoader,
+ nsIGlobalObject* aGlobalObject)
+ : ModuleLoaderBase(aScriptLoader, aGlobalObject) {}
+
+nsIURI* WorkerModuleLoader::GetBaseURI() const {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ return workerPrivate->GetBaseURI();
+}
+
+already_AddRefed<ModuleLoadRequest> WorkerModuleLoader::CreateStaticImport(
+ nsIURI* aURI, ModuleLoadRequest* aParent) {
+ // We are intentionally deviating from the specification here and using the
+ // worker's CSP rather than the document CSP. The spec otherwise requires our
+ // service worker integration to be changed, and additionally the decision
+ // here did not make sense as we are treating static imports as different from
+ // other kinds of subresources.
+ // See Discussion in https://github.com/w3c/webappsec-csp/issues/336
+ Maybe<ClientInfo> clientInfo = GetGlobalObject()->GetClientInfo();
+
+ RefPtr<WorkerLoadContext> loadContext = new WorkerLoadContext(
+ WorkerLoadContext::Kind::StaticImport, clientInfo,
+ aParent->GetWorkerLoadContext()->mScriptLoader,
+ aParent->GetWorkerLoadContext()->mOnlyExistingCachedResourcesAllowed);
+ RefPtr<ModuleLoadRequest> request = new ModuleLoadRequest(
+ aURI, aParent->ReferrerPolicy(), aParent->mFetchOptions, SRIMetadata(),
+ aParent->mURI, loadContext, false, /* is top level */
+ false, /* is dynamic import */
+ this, aParent->mVisitedSet, aParent->GetRootModule());
+
+ request->mURL = request->mURI->GetSpecOrDefault();
+ request->NoCacheEntryFound();
+ return request.forget();
+}
+
+bool WorkerModuleLoader::CreateDynamicImportLoader() {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ workerPrivate->AssertIsOnWorkerThread();
+
+ IgnoredErrorResult rv;
+ RefPtr<WorkerScriptLoader> loader = loader::WorkerScriptLoader::Create(
+ workerPrivate, nullptr, nullptr,
+ GetCurrentScriptLoader()->GetWorkerScriptType(), rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ return false;
+ }
+
+ SetScriptLoader(loader);
+ return true;
+}
+
+already_AddRefed<ModuleLoadRequest> WorkerModuleLoader::CreateDynamicImport(
+ JSContext* aCx, nsIURI* aURI, LoadedScript* aMaybeActiveScript,
+ JS::Handle<JSString*> aSpecifier, JS::Handle<JSObject*> aPromise) {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+
+ if (!CreateDynamicImportLoader()) {
+ return nullptr;
+ }
+
+ // Not supported for Service Workers.
+ // https://github.com/w3c/ServiceWorker/issues/1585 covers existing discussion
+ // about potentially supporting use of import().
+ if (workerPrivate->IsServiceWorker()) {
+ return nullptr;
+ }
+ MOZ_ASSERT(aSpecifier);
+ MOZ_ASSERT(aPromise);
+
+ RefPtr<ScriptFetchOptions> options;
+ nsIURI* baseURL = nullptr;
+ if (aMaybeActiveScript) {
+ // https://html.spec.whatwg.org/multipage/webappapis.html#hostloadimportedmodule
+ // Step 6.3. Set fetchOptions to the new descendant script fetch options for
+ // referencingScript's fetch options.
+ options = aMaybeActiveScript->GetFetchOptions();
+ baseURL = aMaybeActiveScript->BaseURL();
+ } else {
+ // https://html.spec.whatwg.org/multipage/webappapis.html#hostloadimportedmodule
+ // Step 4. Let fetchOptions be the default classic script fetch options.
+ //
+ // https://html.spec.whatwg.org/multipage/webappapis.html#default-classic-script-fetch-options
+ // The default classic script fetch options are a script fetch options whose
+ // cryptographic nonce is the empty string, integrity metadata is the empty
+ // string, parser metadata is "not-parser-inserted", credentials mode is
+ // "same-origin", referrer policy is the empty string, and fetch priority is
+ // "auto".
+ options = new ScriptFetchOptions(
+ CORSMode::CORS_NONE, /* aNonce = */ u""_ns, RequestPriority::Auto,
+ JS::loader::ParserMetadata::NotParserInserted, nullptr);
+ baseURL = GetBaseURI();
+ }
+
+ Maybe<ClientInfo> clientInfo = GetGlobalObject()->GetClientInfo();
+
+ RefPtr<WorkerLoadContext> context = new WorkerLoadContext(
+ WorkerLoadContext::Kind::DynamicImport, clientInfo,
+ GetCurrentScriptLoader(),
+ // When dynamic import is supported in ServiceWorkers,
+ // the current plan in onlyExistingCachedResourcesAllowed
+ // is that only existing cached resources will be
+ // allowed. (`import()` will not be used for caching
+ // side effects, but instead a specific method will be
+ // used during installation.)
+ true);
+
+ ReferrerPolicy referrerPolicy = workerPrivate->GetReferrerPolicy();
+ RefPtr<ModuleLoadRequest> request = new ModuleLoadRequest(
+ aURI, referrerPolicy, options, SRIMetadata(), baseURL, context, true,
+ /* is top level */ true, /* is dynamic import */
+ this, ModuleLoadRequest::NewVisitedSetForTopLevelImport(aURI), nullptr);
+
+ request->SetDynamicImport(aMaybeActiveScript, aSpecifier, aPromise);
+ request->NoCacheEntryFound();
+
+ return request.forget();
+}
+
+bool WorkerModuleLoader::CanStartLoad(ModuleLoadRequest* aRequest,
+ nsresult* aRvOut) {
+ return true;
+}
+
+nsresult WorkerModuleLoader::StartFetch(ModuleLoadRequest* aRequest) {
+ if (!GetScriptLoaderFor(aRequest)->DispatchLoadScript(aRequest)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult WorkerModuleLoader::CompileFetchedModule(
+ JSContext* aCx, JS::Handle<JSObject*> aGlobal, JS::CompileOptions& aOptions,
+ ModuleLoadRequest* aRequest, JS::MutableHandle<JSObject*> aModuleScript) {
+ RefPtr<JS::Stencil> stencil;
+ MOZ_ASSERT(aRequest->IsTextSource());
+ MaybeSourceText maybeSource;
+ nsresult rv = aRequest->GetScriptSource(aCx, &maybeSource,
+ aRequest->mLoadContext.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ auto compile = [&](auto& source) {
+ return JS::CompileModuleScriptToStencil(aCx, aOptions, source);
+ };
+ stencil = maybeSource.mapNonEmpty(compile);
+
+ if (!stencil) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::InstantiateOptions instantiateOptions(aOptions);
+ aModuleScript.set(
+ JS::InstantiateModuleStencil(aCx, instantiateOptions, stencil));
+ if (!aModuleScript) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+WorkerScriptLoader* WorkerModuleLoader::GetCurrentScriptLoader() {
+ return static_cast<WorkerScriptLoader*>(mLoader.get());
+}
+
+WorkerScriptLoader* WorkerModuleLoader::GetScriptLoaderFor(
+ ModuleLoadRequest* aRequest) {
+ return aRequest->GetWorkerLoadContext()->mScriptLoader;
+}
+
+void WorkerModuleLoader::OnModuleLoadComplete(ModuleLoadRequest* aRequest) {
+ if (aRequest->IsTopLevel()) {
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(GetGlobalObject()))) {
+ return;
+ }
+ RefPtr<WorkerScriptLoader> requestScriptLoader =
+ GetScriptLoaderFor(aRequest);
+ if (aRequest->IsDynamicImport()) {
+ aRequest->ProcessDynamicImport();
+ requestScriptLoader->TryShutdown();
+ } else {
+ requestScriptLoader->MaybeMoveToLoadedList(aRequest);
+ requestScriptLoader->ProcessPendingRequests(jsapi.cx());
+ }
+ }
+}
+
+bool WorkerModuleLoader::IsModuleEvaluationAborted(
+ ModuleLoadRequest* aRequest) {
+ WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
+ return !workerPrivate || !workerPrivate->GlobalScope() ||
+ workerPrivate->GlobalScope()->IsDying();
+}
+
+} // namespace mozilla::dom::workerinternals::loader
diff --git a/dom/workers/loader/WorkerModuleLoader.h b/dom/workers/loader/WorkerModuleLoader.h
new file mode 100644
index 0000000000..6ad45b42a0
--- /dev/null
+++ b/dom/workers/loader/WorkerModuleLoader.h
@@ -0,0 +1,83 @@
+/* -*- 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/. */
+
+#ifndef mozilla_loader_WorkerModuleLoader_h
+#define mozilla_loader_WorkerModuleLoader_h
+
+#include "js/loader/ModuleLoaderBase.h"
+#include "js/loader/ScriptFetchOptions.h"
+#include "mozilla/dom/SerializedStackHolder.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla::dom::workerinternals::loader {
+class WorkerScriptLoader;
+
+// alias common classes
+using ScriptFetchOptions = JS::loader::ScriptFetchOptions;
+using ScriptKind = JS::loader::ScriptKind;
+using ScriptLoadRequest = JS::loader::ScriptLoadRequest;
+using ScriptLoadRequestList = JS::loader::ScriptLoadRequestList;
+using ModuleLoadRequest = JS::loader::ModuleLoadRequest;
+
+// WorkerModuleLoader
+//
+// The WorkerModuleLoader provides the methods that implement specification
+// step 5 from "To fetch a worklet/module worker script graph", specifically for
+// workers. In addition, this implements worker specific initialization for
+// Static imports and Dynamic imports.
+//
+// The steps are outlined in "To fetch the descendants of and link a module
+// script" and are common for all Modules. Thus we delegate to ModuleLoaderBase
+// for those steps.
+class WorkerModuleLoader : public JS::loader::ModuleLoaderBase {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(WorkerModuleLoader,
+ JS::loader::ModuleLoaderBase)
+
+ WorkerModuleLoader(WorkerScriptLoader* aScriptLoader,
+ nsIGlobalObject* aGlobalObject);
+
+ private:
+ ~WorkerModuleLoader() = default;
+
+ bool CreateDynamicImportLoader();
+ void SetScriptLoader(JS::loader::ScriptLoaderInterface* aLoader) {
+ mLoader = aLoader;
+ }
+
+ WorkerScriptLoader* GetCurrentScriptLoader();
+
+ WorkerScriptLoader* GetScriptLoaderFor(ModuleLoadRequest* aRequest);
+
+ nsIURI* GetBaseURI() const override;
+
+ already_AddRefed<ModuleLoadRequest> CreateStaticImport(
+ nsIURI* aURI, ModuleLoadRequest* aParent) override;
+
+ already_AddRefed<ModuleLoadRequest> CreateDynamicImport(
+ JSContext* aCx, nsIURI* aURI, LoadedScript* aMaybeActiveScript,
+ JS::Handle<JSString*> aSpecifier,
+ JS::Handle<JSObject*> aPromise) override;
+
+ bool CanStartLoad(ModuleLoadRequest* aRequest, nsresult* aRvOut) override;
+
+ // StartFetch is special for worker modules, as we need to move back to the
+ // main thread to start a new load.
+ nsresult StartFetch(ModuleLoadRequest* aRequest) override;
+
+ nsresult CompileFetchedModule(
+ JSContext* aCx, JS::Handle<JSObject*> aGlobal,
+ JS::CompileOptions& aOptions, ModuleLoadRequest* aRequest,
+ JS::MutableHandle<JSObject*> aModuleScript) override;
+
+ void OnModuleLoadComplete(ModuleLoadRequest* aRequest) override;
+
+ bool IsModuleEvaluationAborted(ModuleLoadRequest* aRequest) override;
+};
+
+} // namespace mozilla::dom::workerinternals::loader
+#endif // mozilla_loader_WorkerModuleLoader_h
diff --git a/dom/workers/loader/moz.build b/dom/workers/loader/moz.build
new file mode 100644
index 0000000000..d39a6acb24
--- /dev/null
+++ b/dom/workers/loader/moz.build
@@ -0,0 +1,34 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: Workers")
+
+# Public stuff.
+EXPORTS.mozilla.dom += [
+ "WorkerLoadContext.h",
+]
+
+# Private stuff.
+EXPORTS.mozilla.dom.workerinternals += [
+ "CacheLoadHandler.h",
+ "NetworkLoadHandler.h",
+ "ScriptResponseHeaderProcessor.h",
+ "WorkerModuleLoader.h",
+]
+
+UNIFIED_SOURCES += [
+ "CacheLoadHandler.cpp",
+ "NetworkLoadHandler.cpp",
+ "ScriptResponseHeaderProcessor.cpp",
+ "WorkerLoadContext.cpp",
+ "WorkerModuleLoader.cpp",
+]
+
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"