/* -*- 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 "ScriptLoader.h" #include "nsIChannel.h" #include "nsIContentPolicy.h" #include "nsIContentSecurityPolicy.h" #include "nsIDocShell.h" #include "nsIHttpChannel.h" #include "nsIHttpChannelInternal.h" #include "nsIInputStreamPump.h" #include "nsIIOService.h" #include "nsIOService.h" #include "nsIProtocolHandler.h" #include "nsIScriptError.h" #include "nsIScriptSecurityManager.h" #include "nsIStreamLoader.h" #include "nsIStreamListenerTee.h" #include "nsIThreadRetargetableRequest.h" #include "nsIURI.h" #include "jsapi.h" #include "jsfriendapi.h" #include "js/CompilationAndEvaluation.h" #include "js/SourceBufferHolder.h" #include "nsError.h" #include "nsContentPolicyUtils.h" #include "nsContentUtils.h" #include "nsDocShellCID.h" #include "nsISupportsPrimitives.h" #include "nsNetUtil.h" #include "nsIPipe.h" #include "nsIOutputStream.h" #include "nsPrintfCString.h" #include "nsString.h" #include "nsStreamUtils.h" #include "nsTArray.h" #include "nsThreadUtils.h" #include "nsXPCOM.h" #include "xpcpublic.h" #include "mozilla/Assertions.h" #include "mozilla/LoadContext.h" #include "mozilla/Maybe.h" #include "mozilla/ipc/BackgroundUtils.h" #include "mozilla/dom/BlobURLProtocolHandler.h" #include "mozilla/dom/CacheBinding.h" #include "mozilla/dom/cache/CacheTypes.h" #include "mozilla/dom/cache/Cache.h" #include "mozilla/dom/cache/CacheStorage.h" #include "mozilla/dom/ChannelInfo.h" #include "mozilla/dom/ClientChannelHelper.h" #include "mozilla/dom/ClientInfo.h" #include "mozilla/dom/Exceptions.h" #include "mozilla/dom/InternalResponse.h" #include "mozilla/dom/nsCSPService.h" #include "mozilla/dom/nsCSPUtils.h" #include "mozilla/dom/PerformanceStorage.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/PromiseNativeHandler.h" #include "mozilla/dom/Response.h" #include "mozilla/dom/ScriptLoader.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/SRILogHelper.h" #include "mozilla/dom/ServiceWorkerBinding.h" #include "mozilla/dom/ServiceWorkerManager.h" #include "mozilla/UniquePtr.h" #include "Principal.h" #include "WorkerHolder.h" #include "WorkerPrivate.h" #include "WorkerRunnable.h" #include "WorkerScope.h" #define MAX_CONCURRENT_SCRIPTS 1000 using mozilla::dom::cache::Cache; using mozilla::dom::cache::CacheStorage; using mozilla::ipc::PrincipalInfo; namespace mozilla { namespace dom { namespace { nsIURI* GetBaseURI(bool aIsMainScript, WorkerPrivate* aWorkerPrivate) { MOZ_ASSERT(aWorkerPrivate); nsIURI* baseURI; WorkerPrivate* parentWorker = aWorkerPrivate->GetParent(); if (aIsMainScript) { if (parentWorker) { baseURI = parentWorker->GetBaseURI(); NS_ASSERTION(baseURI, "Should have been set already!"); } else { // May be null. baseURI = aWorkerPrivate->GetBaseURI(); } } else { baseURI = aWorkerPrivate->GetBaseURI(); NS_ASSERTION(baseURI, "Should have been set already!"); } return baseURI; } nsresult ChannelFromScriptURL(nsIPrincipal* principal, nsIURI* baseURI, nsIDocument* parentDoc, WorkerPrivate* aWorkerPrivate, nsILoadGroup* loadGroup, nsIIOService* ios, nsIScriptSecurityManager* secMan, const nsAString& aScriptURL, const Maybe& aClientInfo, const Maybe& aController, bool aIsMainScript, WorkerScriptType aWorkerScriptType, nsContentPolicyType aMainScriptContentPolicyType, nsLoadFlags aLoadFlags, bool aDefaultURIEncoding, nsIChannel** aChannel) { AssertIsOnMainThread(); nsresult rv; nsCOMPtr uri; if (aDefaultURIEncoding) { rv = NS_NewURI(getter_AddRefs(uri), aScriptURL, nullptr, baseURI); } else { rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), aScriptURL, parentDoc, baseURI); } if (NS_FAILED(rv)) { return NS_ERROR_DOM_SYNTAX_ERR; } // If we have the document, use it. Unfortunately, for dedicated workers // 'parentDoc' ends up being the parent document, which is not the document // that we want to use. So make sure to avoid using 'parentDoc' in that // situation. if (parentDoc && parentDoc->NodePrincipal() != principal) { parentDoc = nullptr; } aLoadFlags |= nsIChannel::LOAD_CLASSIFY_URI; uint32_t secFlags = aIsMainScript ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS; bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal( principal, uri, true /* aInheritForAboutBlank */, false /* aForceInherit */); bool isData = false; rv = uri->SchemeIs("data", &isData); NS_ENSURE_SUCCESS(rv, rv); bool isURIUniqueOrigin = net::nsIOService::IsDataURIUniqueOpaqueOrigin() && isData; if (inheritAttrs && !isURIUniqueOrigin) { secFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; } if (aWorkerScriptType == DebuggerScript) { // A DebuggerScript needs to be a local resource like chrome: or resource: bool isUIResource = false; rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_UI_RESOURCE, &isUIResource); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (!isUIResource) { return NS_ERROR_DOM_SECURITY_ERR; } secFlags |= nsILoadInfo::SEC_ALLOW_CHROME; } // Note: this is for backwards compatibility and goes against spec. // We should find a better solution. if (aIsMainScript && isData) { secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL; } nsContentPolicyType contentPolicyType = aIsMainScript ? aMainScriptContentPolicyType : nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS; // The main service worker script should never be loaded over the network // in this path. It should always be offlined by ServiceWorkerScriptCache. // We assert here since this error should also be caught by the runtime // check in CacheScriptLoader. // // Note, if we ever allow service worker scripts to be loaded from network // here we need to configure the channel properly. For example, it must // not allow redirects. MOZ_DIAGNOSTIC_ASSERT(contentPolicyType != nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER); nsCOMPtr channel; // If we have the document, use it. Unfortunately, for dedicated workers // 'parentDoc' ends up being the parent document, which is not the document // that we want to use. So make sure to avoid using 'parentDoc' in that // situation. if (parentDoc && parentDoc->NodePrincipal() == principal) { rv = NS_NewChannel(getter_AddRefs(channel), uri, parentDoc, secFlags, contentPolicyType, nullptr, // aPerformanceStorage loadGroup, nullptr, // aCallbacks aLoadFlags, ios); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR); } else { // We must have a loadGroup with a load context for the principal to // traverse the channel correctly. MOZ_ASSERT(loadGroup); MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadGroup, principal)); RefPtr performanceStorage; nsCOMPtr cspEventListener; if (aWorkerPrivate && !aIsMainScript) { performanceStorage = aWorkerPrivate->GetPerformanceStorage(); cspEventListener = aWorkerPrivate->CSPEventListener(); } if (aClientInfo.isSome()) { rv = NS_NewChannel(getter_AddRefs(channel), uri, principal, aClientInfo.ref(), aController, secFlags, contentPolicyType, performanceStorage, loadGroup, nullptr, // aCallbacks aLoadFlags, ios); } else { rv = NS_NewChannel(getter_AddRefs(channel), uri, principal, secFlags, contentPolicyType, performanceStorage, loadGroup, nullptr, // aCallbacks aLoadFlags, ios); } NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR); if (cspEventListener) { nsCOMPtr loadInfo = channel->GetLoadInfo(); if (NS_WARN_IF(!loadInfo)) { return NS_ERROR_UNEXPECTED; } rv = loadInfo->SetCspEventListener(cspEventListener); NS_ENSURE_SUCCESS(rv, rv); } } if (nsCOMPtr httpChannel = do_QueryInterface(channel)) { mozilla::net::ReferrerPolicy referrerPolicy = parentDoc ? parentDoc->GetReferrerPolicy() : mozilla::net::RP_Unset; rv = nsContentUtils::SetFetchReferrerURIWithPolicy(principal, parentDoc, httpChannel, referrerPolicy); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } channel.forget(aChannel); return rv; } struct ScriptLoadInfo { ScriptLoadInfo() : mScriptTextBuf(nullptr) , mScriptTextLength(0) , mLoadResult(NS_ERROR_NOT_INITIALIZED) , mLoadingFinished(false) , mExecutionScheduled(false) , mExecutionResult(false) , mCacheStatus(Uncached) , mLoadFlags(nsIRequest::LOAD_NORMAL) { } ~ScriptLoadInfo() { if (mScriptTextBuf) { js_free(mScriptTextBuf); } } nsString mURL; // 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 mCachePromise; // The reader stream the cache entry should be filled from, for those cases // when we're going to have an mCachePromise. nsCOMPtr mCacheReadStream; nsCOMPtr mChannel; Maybe mReservedClientInfo; char16_t* mScriptTextBuf; size_t mScriptTextLength; nsresult mLoadResult; bool mLoadingFinished; bool mExecutionScheduled; bool mExecutionResult; 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; nsLoadFlags mLoadFlags; Maybe mMutedErrorFlag; bool Finished() const { return mLoadingFinished && !mCachePromise && !mChannel; } }; class ScriptLoaderRunnable; class ScriptExecutorRunnable final : public MainThreadWorkerSyncRunnable { ScriptLoaderRunnable& mScriptLoader; bool mIsWorkerScript; uint32_t mFirstIndex; uint32_t mLastIndex; public: ScriptExecutorRunnable(ScriptLoaderRunnable& aScriptLoader, nsIEventTarget* aSyncLoopTarget, bool aIsWorkerScript, uint32_t aFirstIndex, uint32_t aLastIndex); private: ~ScriptExecutorRunnable() { } virtual bool IsDebuggerRunnable() const override; virtual bool PreRun(WorkerPrivate* aWorkerPrivate) override; virtual bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override; virtual void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult) override; nsresult Cancel() override; void ShutdownScriptLoader(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aResult, bool aMutedError); void LogExceptionToConsole(JSContext* aCx, WorkerPrivate* WorkerPrivate); }; class CacheScriptLoader; class CacheCreator final : public PromiseNativeHandler { public: NS_DECL_ISUPPORTS explicit CacheCreator(WorkerPrivate* aWorkerPrivate) : mCacheName(aWorkerPrivate->ServiceWorkerCacheName()) , mOriginAttributes(aWorkerPrivate->GetOriginAttributes()) { MOZ_ASSERT(aWorkerPrivate->IsServiceWorker()); AssertIsOnMainThread(); } void AddLoader(CacheScriptLoader* aLoader) { AssertIsOnMainThread(); MOZ_ASSERT(!mCacheStorage); mLoaders.AppendElement(aLoader); } virtual void ResolvedCallback(JSContext* aCx, JS::Handle aValue) override; virtual void RejectedCallback(JSContext* aCx, JS::Handle aValue) 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(); private: ~CacheCreator() { } nsresult CreateCacheStorage(nsIPrincipal* aPrincipal); void FailLoaders(nsresult aRv); RefPtr mCache; RefPtr mCacheStorage; nsCOMPtr mSandboxGlobalObject; nsTArray> mLoaders; nsString mCacheName; OriginAttributes mOriginAttributes; }; NS_IMPL_ISUPPORTS0(CacheCreator) class CacheScriptLoader final : public PromiseNativeHandler , public nsIStreamLoaderObserver { public: NS_DECL_ISUPPORTS NS_DECL_NSISTREAMLOADEROBSERVER CacheScriptLoader(WorkerPrivate* aWorkerPrivate, ScriptLoadInfo& aLoadInfo, uint32_t aIndex, bool aIsWorkerScript, ScriptLoaderRunnable* aRunnable) : mLoadInfo(aLoadInfo) , mIndex(aIndex) , mRunnable(aRunnable) , mIsWorkerScript(aIsWorkerScript) , mFailed(false) , mState(aWorkerPrivate->GetServiceWorkerDescriptor().State()) { MOZ_ASSERT(aWorkerPrivate); MOZ_ASSERT(aWorkerPrivate->IsServiceWorker()); mMainThreadEventTarget = aWorkerPrivate->MainThreadEventTarget(); MOZ_ASSERT(mMainThreadEventTarget); mBaseURI = GetBaseURI(mIsWorkerScript, aWorkerPrivate); AssertIsOnMainThread(); } void Fail(nsresult aRv); void Load(Cache* aCache); virtual void ResolvedCallback(JSContext* aCx, JS::Handle aValue) override; virtual void RejectedCallback(JSContext* aCx, JS::Handle aValue) override; private: ~CacheScriptLoader() { AssertIsOnMainThread(); } ScriptLoadInfo& mLoadInfo; uint32_t mIndex; RefPtr mRunnable; bool mIsWorkerScript; bool mFailed; const ServiceWorkerState mState; nsCOMPtr mPump; nsCOMPtr mBaseURI; mozilla::dom::ChannelInfo mChannelInfo; UniquePtr mPrincipalInfo; nsCString mCSPHeaderValue; nsCString mCSPReportOnlyHeaderValue; nsCString mReferrerPolicyHeaderValue; nsCOMPtr mMainThreadEventTarget; }; NS_IMPL_ISUPPORTS(CacheScriptLoader, nsIStreamLoaderObserver) class CachePromiseHandler final : public PromiseNativeHandler { public: NS_DECL_ISUPPORTS CachePromiseHandler(ScriptLoaderRunnable* aRunnable, ScriptLoadInfo& aLoadInfo, uint32_t aIndex) : mRunnable(aRunnable) , mLoadInfo(aLoadInfo) , mIndex(aIndex) { AssertIsOnMainThread(); MOZ_ASSERT(mRunnable); } virtual void ResolvedCallback(JSContext* aCx, JS::Handle aValue) override; virtual void RejectedCallback(JSContext* aCx, JS::Handle aValue) override; private: ~CachePromiseHandler() { AssertIsOnMainThread(); } RefPtr mRunnable; ScriptLoadInfo& mLoadInfo; uint32_t mIndex; }; NS_IMPL_ISUPPORTS0(CachePromiseHandler) class LoaderListener final : public nsIStreamLoaderObserver , public nsIRequestObserver { public: NS_DECL_ISUPPORTS LoaderListener(ScriptLoaderRunnable* aRunnable, uint32_t aIndex) : mRunnable(aRunnable) , mIndex(aIndex) { MOZ_ASSERT(mRunnable); } NS_IMETHOD OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext, nsresult aStatus, uint32_t aStringLen, const uint8_t* aString) override; NS_IMETHOD OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) override; NS_IMETHOD OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatusCode) override { // Nothing to do here! return NS_OK; } private: ~LoaderListener() {} RefPtr mRunnable; uint32_t mIndex; }; NS_IMPL_ISUPPORTS(LoaderListener, nsIStreamLoaderObserver, nsIRequestObserver) class ScriptLoaderRunnable final : public nsIRunnable, public nsINamed { friend class ScriptExecutorRunnable; friend class CachePromiseHandler; friend class CacheScriptLoader; friend class LoaderListener; WorkerPrivate* mWorkerPrivate; nsCOMPtr mSyncLoopTarget; nsTArray mLoadInfos; RefPtr mCacheCreator; Maybe mClientInfo; Maybe mController; bool mIsMainScript; WorkerScriptType mWorkerScriptType; bool mCanceledMainThread; ErrorResult& mRv; public: NS_DECL_THREADSAFE_ISUPPORTS ScriptLoaderRunnable(WorkerPrivate* aWorkerPrivate, nsIEventTarget* aSyncLoopTarget, nsTArray& aLoadInfos, const Maybe& aClientInfo, const Maybe& aController, bool aIsMainScript, WorkerScriptType aWorkerScriptType, ErrorResult& aRv) : mWorkerPrivate(aWorkerPrivate), mSyncLoopTarget(aSyncLoopTarget), mClientInfo(aClientInfo), mController(aController), mIsMainScript(aIsMainScript), mWorkerScriptType(aWorkerScriptType), mCanceledMainThread(false), mRv(aRv) { aWorkerPrivate->AssertIsOnWorkerThread(); MOZ_ASSERT(aSyncLoopTarget); MOZ_ASSERT_IF(aIsMainScript, aLoadInfos.Length() == 1); mLoadInfos.SwapElements(aLoadInfos); } void CancelMainThreadWithBindingAborted() { CancelMainThread(NS_BINDING_ABORTED); } private: ~ScriptLoaderRunnable() { } NS_IMETHOD Run() override { AssertIsOnMainThread(); nsresult rv = RunInternal(); if (NS_WARN_IF(NS_FAILED(rv))) { CancelMainThread(rv); } return NS_OK; } NS_IMETHOD GetName(nsACString& aName) override { aName.AssignLiteral("ScriptLoaderRunnable"); return NS_OK; } void LoadingFinished(uint32_t aIndex, nsresult aRv) { AssertIsOnMainThread(); MOZ_ASSERT(aIndex < mLoadInfos.Length()); ScriptLoadInfo& loadInfo = mLoadInfos[aIndex]; loadInfo.mLoadResult = aRv; MOZ_ASSERT(!loadInfo.mLoadingFinished); loadInfo.mLoadingFinished = true; if (IsMainWorkerScript() && NS_SUCCEEDED(aRv)) { MOZ_DIAGNOSTIC_ASSERT(mWorkerPrivate->PrincipalURIMatchesScriptURL()); } MaybeExecuteFinishedScripts(aIndex); } void MaybeExecuteFinishedScripts(uint32_t aIndex) { AssertIsOnMainThread(); MOZ_ASSERT(aIndex < mLoadInfos.Length()); ScriptLoadInfo& loadInfo = mLoadInfos[aIndex]; // We execute the last step if we don't have a pending operation with the // cache and the loading is completed. if (loadInfo.Finished()) { ExecuteFinishedScripts(); } } nsresult OnStreamComplete(nsIStreamLoader* aLoader, uint32_t aIndex, nsresult aStatus, uint32_t aStringLen, const uint8_t* aString) { AssertIsOnMainThread(); MOZ_ASSERT(aIndex < mLoadInfos.Length()); nsresult rv = OnStreamCompleteInternal(aLoader, aStatus, aStringLen, aString, mLoadInfos[aIndex]); LoadingFinished(aIndex, rv); return NS_OK; } nsresult OnStartRequest(nsIRequest* aRequest, uint32_t aIndex) { AssertIsOnMainThread(); MOZ_ASSERT(aIndex < mLoadInfos.Length()); // If one load info cancels or hits an error, it can race with the start // callback coming from another load info. if (mCanceledMainThread || !mCacheCreator) { aRequest->Cancel(NS_ERROR_FAILURE); return NS_ERROR_FAILURE; } ScriptLoadInfo& loadInfo = mLoadInfos[aIndex]; nsCOMPtr 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 (mWorkerPrivate->IsServiceWorker()) { nsAutoCString mimeType; channel->GetContentType(mimeType); if (!nsContentUtils::IsJavascriptMIMEType(NS_ConvertUTF8toUTF16(mimeType))) { const nsCString& scope = mWorkerPrivate->GetServiceWorkerRegistrationDescriptor().Scope(); ServiceWorkerManager::LocalizeAndReportToAllClients( scope, "ServiceWorkerRegisterMimeTypeError2", nsTArray { NS_ConvertUTF8toUTF16(scope), NS_ConvertUTF8toUTF16(mimeType), loadInfo.mURL } ); channel->Cancel(NS_ERROR_DOM_NETWORK_ERR); return NS_ERROR_DOM_NETWORK_ERR; } } // Note that importScripts() can redirect. In theory the main // script could also encounter an internal redirect, but currently // the assert does not allow that. MOZ_ASSERT_IF(mIsMainScript, channel == loadInfo.mChannel); loadInfo.mChannel = channel; // We synthesize the result code, but its never exposed to content. RefPtr ir = new mozilla::dom::InternalResponse(200, NS_LITERAL_CSTRING("OK")); ir->SetBody(loadInfo.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. loadInfo.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 channelPrincipal; nsresult rv = ssm->GetChannelResultPrincipal(channel, getter_AddRefs(channelPrincipal)); if (NS_WARN_IF(NS_FAILED(rv))) { channel->Cancel(rv); return rv; } UniquePtr principalInfo(new PrincipalInfo()); rv = PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get()); if (NS_WARN_IF(NS_FAILED(rv))) { channel->Cancel(rv); return rv; } ir->SetPrincipalInfo(std::move(principalInfo)); ir->Headers()->FillResponseHeaders(loadInfo.mChannel); RefPtr response = new mozilla::dom::Response(mCacheCreator->Global(), ir, nullptr); mozilla::dom::RequestOrUSVString request; MOZ_ASSERT(!loadInfo.mFullURL.IsEmpty()); request.SetAsUSVString().Rebind(loadInfo.mFullURL.Data(), loadInfo.mFullURL.Length()); // This JSContext will not end up executing JS code because here there are // no ReadableStreams involved. AutoJSAPI jsapi; jsapi.Init(); ErrorResult error; RefPtr cachePromise = mCacheCreator->Cache_()->Put(jsapi.cx(), request, *response, error); error.WouldReportJSException(); if (NS_WARN_IF(error.Failed())) { nsresult rv = error.StealNSResult(); channel->Cancel(rv); return rv; } RefPtr promiseHandler = new CachePromiseHandler(this, loadInfo, aIndex); cachePromise->AppendNativeHandler(promiseHandler); loadInfo.mCachePromise.swap(cachePromise); loadInfo.mCacheStatus = ScriptLoadInfo::WritingToCache; return NS_OK; } bool IsMainWorkerScript() const { return mIsMainScript && mWorkerScriptType == WorkerScript; } bool IsDebuggerScript() const { return mWorkerScriptType == DebuggerScript; } void CancelMainThread(nsresult aCancelResult) { AssertIsOnMainThread(); if (mCanceledMainThread) { return; } mCanceledMainThread = true; if (mCacheCreator) { MOZ_ASSERT(mWorkerPrivate->IsServiceWorker()); DeleteCache(); } // Cancel all the channels that were already opened. for (uint32_t index = 0; index < mLoadInfos.Length(); index++) { ScriptLoadInfo& loadInfo = mLoadInfos[index]; // If promise or channel is non-null, their failures will lead to // LoadingFinished being called. bool callLoadingFinished = true; if (loadInfo.mCachePromise) { MOZ_ASSERT(mWorkerPrivate->IsServiceWorker()); loadInfo.mCachePromise->MaybeReject(aCancelResult); loadInfo.mCachePromise = nullptr; callLoadingFinished = false; } if (loadInfo.mChannel) { if (NS_SUCCEEDED(loadInfo.mChannel->Cancel(aCancelResult))) { callLoadingFinished = false; } else { NS_WARNING("Failed to cancel channel!"); } } if (callLoadingFinished && !loadInfo.Finished()) { LoadingFinished(index, aCancelResult); } } ExecuteFinishedScripts(); } void DeleteCache() { AssertIsOnMainThread(); if (!mCacheCreator) { return; } mCacheCreator->DeleteCache(); mCacheCreator = nullptr; } nsresult RunInternal() { AssertIsOnMainThread(); if (IsMainWorkerScript()) { mWorkerPrivate->SetLoadingWorkerScript(true); } if (!mWorkerPrivate->IsServiceWorker() || IsDebuggerScript()) { for (uint32_t index = 0, len = mLoadInfos.Length(); index < len; ++index) { nsresult rv = LoadScript(index); if (NS_WARN_IF(NS_FAILED(rv))) { LoadingFinished(index, rv); return rv; } } return NS_OK; } MOZ_ASSERT(!mCacheCreator); mCacheCreator = new CacheCreator(mWorkerPrivate); for (uint32_t index = 0, len = mLoadInfos.Length(); index < len; ++index) { RefPtr loader = new CacheScriptLoader(mWorkerPrivate, mLoadInfos[index], index, IsMainWorkerScript(), this); mCacheCreator->AddLoader(loader); } // The worker may have a null principal on first load, but in that case its // parent definitely will have one. nsIPrincipal* principal = mWorkerPrivate->GetPrincipal(); if (!principal) { WorkerPrivate* parentWorker = mWorkerPrivate->GetParent(); MOZ_ASSERT(parentWorker, "Must have a parent!"); principal = parentWorker->GetPrincipal(); } nsresult rv = mCacheCreator->Load(principal); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult LoadScript(uint32_t aIndex) { AssertIsOnMainThread(); MOZ_ASSERT(aIndex < mLoadInfos.Length()); MOZ_ASSERT_IF(IsMainWorkerScript(), mWorkerScriptType != DebuggerScript); WorkerPrivate* parentWorker = mWorkerPrivate->GetParent(); // For JavaScript debugging, the devtools server must run on the same // thread as the debuggee, indicating the worker uses content principal. // However, in Bug 863246, web content will no longer be able to load // resource:// URIs by default, so we need system principal to load // debugger scripts. nsIPrincipal* principal = (mWorkerScriptType == DebuggerScript) ? nsContentUtils::GetSystemPrincipal() : mWorkerPrivate->GetPrincipal(); nsCOMPtr loadGroup = mWorkerPrivate->GetLoadGroup(); MOZ_DIAGNOSTIC_ASSERT(principal); NS_ENSURE_TRUE(NS_LoadGroupMatchesPrincipal(loadGroup, principal), NS_ERROR_FAILURE); // Figure out our base URI. nsCOMPtr baseURI = GetBaseURI(mIsMainScript, mWorkerPrivate); // May be null. nsCOMPtr parentDoc = mWorkerPrivate->GetDocument(); nsCOMPtr channel; if (IsMainWorkerScript()) { // May be null. channel = mWorkerPrivate->ForgetWorkerChannel(); } nsCOMPtr ios(do_GetIOService()); nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); NS_ASSERTION(secMan, "This should never be null!"); ScriptLoadInfo& loadInfo = mLoadInfos[aIndex]; nsresult& rv = loadInfo.mLoadResult; nsLoadFlags loadFlags = loadInfo.mLoadFlags; // Get the top-level worker. WorkerPrivate* topWorkerPrivate = mWorkerPrivate; WorkerPrivate* parent = topWorkerPrivate->GetParent(); while (parent) { topWorkerPrivate = parent; parent = topWorkerPrivate->GetParent(); } // If the top-level worker is a dedicated worker and has a window, and the // window has a docshell, the caching behavior of this worker should match // that of that docshell. if (topWorkerPrivate->IsDedicatedWorker()) { nsCOMPtr window = topWorkerPrivate->GetWindow(); if (window) { nsCOMPtr docShell = window->GetDocShell(); if (docShell) { nsresult rv = docShell->GetDefaultLoadFlags(&loadFlags); NS_ENSURE_SUCCESS(rv, rv); } } } if (!channel) { // Only top level workers' main script use the document charset for the // script uri encoding. Otherwise, default encoding (UTF-8) is applied. bool useDefaultEncoding = !(!parentWorker && IsMainWorkerScript()); rv = ChannelFromScriptURL(principal, baseURI, parentDoc, mWorkerPrivate, loadGroup, ios, secMan, loadInfo.mURL, mClientInfo, mController, IsMainWorkerScript(), mWorkerScriptType, mWorkerPrivate->ContentPolicyType(), loadFlags, useDefaultEncoding, getter_AddRefs(channel)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } // We need to know which index we're on in OnStreamComplete so we know // where to put the result. RefPtr listener = new LoaderListener(this, aIndex); // We don't care about progress so just use the simple stream loader for // OnStreamComplete notification only. nsCOMPtr loader; rv = NS_NewStreamLoader(getter_AddRefs(loader), listener); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (IsMainWorkerScript()) { MOZ_DIAGNOSTIC_ASSERT(loadInfo.mReservedClientInfo.isSome()); rv = AddClientChannelHelper(channel, std::move(loadInfo.mReservedClientInfo), Maybe(), mWorkerPrivate->HybridEventTarget()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } if (loadInfo.mCacheStatus != ScriptLoadInfo::ToBeCached) { rv = channel->AsyncOpen2(loader); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } else { nsCOMPtr writer; // In case we return early. loadInfo.mCacheStatus = ScriptLoadInfo::Cancel; rv = NS_NewPipe(getter_AddRefs(loadInfo.mCacheReadStream), getter_AddRefs(writer), 0, UINT32_MAX, // unlimited size to avoid writer WOULD_BLOCK case true, false); // non-blocking reader, blocking writer if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr tee = do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID); rv = tee->Init(loader, writer, listener); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsresult rv = channel->AsyncOpen2(tee); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } loadInfo.mChannel.swap(channel); return NS_OK; } nsresult OnStreamCompleteInternal(nsIStreamLoader* aLoader, nsresult aStatus, uint32_t aStringLen, const uint8_t* aString, ScriptLoadInfo& aLoadInfo) { AssertIsOnMainThread(); if (!aLoadInfo.mChannel) { return NS_BINDING_ABORTED; } aLoadInfo.mChannel = nullptr; if (NS_FAILED(aStatus)) { return aStatus; } NS_ASSERTION(aString, "This should never be null!"); nsCOMPtr request; nsresult rv = aLoader->GetRequest(getter_AddRefs(request)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr channel = do_QueryInterface(request); MOZ_ASSERT(channel); nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); NS_ASSERTION(ssm, "Should never be null!"); nsCOMPtr channelPrincipal; rv = ssm->GetChannelResultPrincipal(channel, getter_AddRefs(channelPrincipal)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsIPrincipal* principal = mWorkerPrivate->GetPrincipal(); if (!principal) { WorkerPrivate* parentWorker = mWorkerPrivate->GetParent(); MOZ_ASSERT(parentWorker, "Must have a parent!"); principal = parentWorker->GetPrincipal(); } #ifdef DEBUG if (IsMainWorkerScript()) { nsCOMPtr loadingPrincipal = mWorkerPrivate->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. aLoadInfo.mMutedErrorFlag.emplace(IsMainWorkerScript() ? false : !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 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( NS_LITERAL_CSTRING("content-security-policy"), tCspHeaderValue); Unused << httpChannel->GetResponseHeader( NS_LITERAL_CSTRING("content-security-policy-report-only"), tCspROHeaderValue); Unused << httpChannel->GetResponseHeader( NS_LITERAL_CSTRING("referrer-policy"), tRPHeaderCValue); } // May be null. nsIDocument* parentDoc = mWorkerPrivate->GetDocument(); // Use the regular ScriptLoader for this grunt work! Should be just fine // because we're running on the main thread. // Unlike