/* -*- 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 "nsIHttpChannel.h" #include "nsIHttpChannelInternal.h" #include "nsIInputStreamPump.h" #include "nsIIOService.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 "nsError.h" #include "nsContentPolicyUtils.h" #include "nsContentUtils.h" #include "nsDocShellCID.h" #include "nsISupportsPrimitives.h" #include "nsNetUtil.h" #include "nsIPipe.h" #include "nsIOutputStream.h" #include "nsScriptLoader.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/ipc/BackgroundUtils.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/Exceptions.h" #include "mozilla/dom/InternalResponse.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/PromiseNativeHandler.h" #include "mozilla/dom/Response.h" #include "mozilla/UniquePtr.h" #include "Principal.h" #include "WorkerFeature.h" #include "WorkerPrivate.h" #include "WorkerRunnable.h" #include "WorkerScope.h" #define MAX_CONCURRENT_SCRIPTS 1000 USING_WORKERS_NAMESPACE using mozilla::dom::cache::Cache; using mozilla::dom::cache::CacheStorage; using mozilla::dom::Promise; using mozilla::dom::PromiseNativeHandler; using mozilla::ErrorResult; using mozilla::ipc::PrincipalInfo; using mozilla::UniquePtr; 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, nsILoadGroup* loadGroup, nsIIOService* ios, nsIScriptSecurityManager* secMan, const nsAString& aScriptURL, bool aIsMainScript, WorkerScriptType aWorkerScriptType, nsContentPolicyType aContentPolicyType, nsLoadFlags aLoadFlags, nsIChannel** aChannel) { AssertIsOnMainThread(); nsresult rv; nsCOMPtr uri; 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; 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. bool isData = false; if (aIsMainScript && NS_SUCCEEDED(uri->SchemeIs("data", &isData)) && isData) { secFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL; } 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, aContentPolicyType, loadGroup, nullptr, // aCallbacks aLoadFlags, ios); } 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)); rv = NS_NewChannel(getter_AddRefs(channel), uri, principal, secFlags, aContentPolicyType, loadGroup, nullptr, // aCallbacks aLoadFlags, ios); } NS_ENSURE_SUCCESS(rv, rv); if (nsCOMPtr httpChannel = do_QueryInterface(channel)) { rv = nsContentUtils::SetFetchReferrerURIWithPolicy(principal, parentDoc, httpChannel); 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) { } ~ScriptLoadInfo() { if (mScriptTextBuf) { js_free(mScriptTextBuf); } } bool ReadyToExecute() { return !mChannel && NS_SUCCEEDED(mLoadResult) && !mExecutionScheduled; } 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; 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; 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 WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override; virtual void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult) override; NS_DECL_NSICANCELABLERUNNABLE void ShutdownScriptLoader(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aResult, nsresult aLoadResult, 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()) , mPrivateBrowsing(aWorkerPrivate->IsInPrivateBrowsing()) { MOZ_ASSERT(aWorkerPrivate->IsServiceWorker()); MOZ_ASSERT(aWorkerPrivate->LoadScriptAsPartOfLoadingServiceWorkerScript()); 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; bool mPrivateBrowsing; }; 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) { MOZ_ASSERT(aWorkerPrivate->IsServiceWorker()); 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; nsCOMPtr mPump; nsCOMPtr mBaseURI; mozilla::dom::ChannelInfo mChannelInfo; UniquePtr mPrincipalInfo; }; 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 WorkerFeature , public nsIRunnable { friend class ScriptExecutorRunnable; friend class CachePromiseHandler; friend class CacheScriptLoader; friend class LoaderListener; WorkerPrivate* mWorkerPrivate; nsCOMPtr mSyncLoopTarget; nsTArray mLoadInfos; RefPtr mCacheCreator; bool mIsMainScript; WorkerScriptType mWorkerScriptType; bool mCanceled; bool mCanceledMainThread; ErrorResult& mRv; public: NS_DECL_THREADSAFE_ISUPPORTS ScriptLoaderRunnable(WorkerPrivate* aWorkerPrivate, nsIEventTarget* aSyncLoopTarget, nsTArray& aLoadInfos, bool aIsMainScript, WorkerScriptType aWorkerScriptType, ErrorResult& aRv) : mWorkerPrivate(aWorkerPrivate), mSyncLoopTarget(aSyncLoopTarget), mIsMainScript(aIsMainScript), mWorkerScriptType(aWorkerScriptType), mCanceled(false), mCanceledMainThread(false), mRv(aRv) { aWorkerPrivate->AssertIsOnWorkerThread(); MOZ_ASSERT(aSyncLoopTarget); MOZ_ASSERT_IF(aIsMainScript, aLoadInfos.Length() == 1); mLoadInfos.SwapElements(aLoadInfos); } private: ~ScriptLoaderRunnable() { } NS_IMETHOD Run() override { AssertIsOnMainThread(); if (NS_FAILED(RunInternal())) { CancelMainThread(); } 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; 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) { return NS_ERROR_FAILURE; } ScriptLoadInfo& loadInfo = mLoadInfos[aIndex]; nsCOMPtr channel = do_QueryInterface(aRequest); MOZ_ASSERT(channel == loadInfo.mChannel); // 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); // 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(Move(principalInfo)); RefPtr response = new mozilla::dom::Response(mCacheCreator->Global(), ir); mozilla::dom::RequestOrUSVString request; MOZ_ASSERT(!loadInfo.mFullURL.IsEmpty()); request.SetAsUSVString().Rebind(loadInfo.mFullURL.Data(), loadInfo.mFullURL.Length()); ErrorResult error; RefPtr cachePromise = mCacheCreator->Cache_()->Put(request, *response, error); 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; } virtual bool Notify(JSContext* aCx, Status aStatus) override { mWorkerPrivate->AssertIsOnWorkerThread(); if (aStatus >= Terminating && !mCanceled) { mCanceled = true; nsCOMPtr runnable = NS_NewRunnableMethod(this, &ScriptLoaderRunnable::CancelMainThread); NS_ASSERTION(runnable, "This should never fail!"); if (NS_FAILED(NS_DispatchToMainThread(runnable))) { JS_ReportError(aCx, "Failed to cancel script loader!"); return false; } } return true; } bool IsMainWorkerScript() const { return mIsMainScript && mWorkerScriptType == WorkerScript; } void CancelMainThread() { 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(NS_BINDING_ABORTED); loadInfo.mCachePromise = nullptr; callLoadingFinished = false; } if (loadInfo.mChannel) { if (NS_SUCCEEDED(loadInfo.mChannel->Cancel(NS_BINDING_ABORTED))) { callLoadingFinished = false; } else { NS_WARNING("Failed to cancel channel!"); } } if (callLoadingFinished && !loadInfo.Finished()) { LoadingFinished(index, NS_BINDING_ABORTED); } } ExecuteFinishedScripts(); } void DeleteCache() { AssertIsOnMainThread(); if (!mCacheCreator) { return; } mCacheCreator->DeleteCache(); mCacheCreator = nullptr; } nsresult RunInternal() { AssertIsOnMainThread(); if (IsMainWorkerScript() && mWorkerPrivate->IsServiceWorker()) { mWorkerPrivate->SetLoadingWorkerScript(true); } if (!mWorkerPrivate->IsServiceWorker() || !mWorkerPrivate->LoadScriptAsPartOfLoadingServiceWorkerScript()) { 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()); WorkerPrivate* parentWorker = mWorkerPrivate->GetParent(); // Figure out which principal to use. nsIPrincipal* principal = mWorkerPrivate->GetPrincipal(); nsCOMPtr loadGroup = mWorkerPrivate->GetLoadGroup(); if (!principal) { NS_ASSERTION(parentWorker, "Must have a principal!"); NS_ASSERTION(mIsMainScript, "Must have a principal for importScripts!"); principal = parentWorker->GetPrincipal(); loadGroup = parentWorker->GetLoadGroup(); } NS_ASSERTION(principal, "This should never be null here!"); MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(loadGroup, principal)); // 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 = nsIRequest::LOAD_NORMAL; // If we are loading a script for a ServiceWorker then we must not // try to intercept it. If the interception matches the current // ServiceWorker's scope then we could deadlock the load. if (mWorkerPrivate->IsServiceWorker()) { loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER; } if (!channel) { rv = ChannelFromScriptURL(principal, baseURI, parentDoc, loadGroup, ios, secMan, loadInfo.mURL, IsMainWorkerScript(), mWorkerScriptType, mWorkerPrivate->ContentPolicyType(), loadFlags, 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 (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(); } // 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); if (httpChannel) { bool requestSucceeded; rv = httpChannel->GetRequestSucceeded(&requestSucceeded); NS_ENSURE_SUCCESS(rv, rv); if (!requestSucceeded) { return NS_ERROR_NOT_AVAILABLE; } } // May be null. nsIDocument* parentDoc = mWorkerPrivate->GetDocument(); // Use the regular nsScriptLoader for this grunt work! Should be just fine // because we're running on the main thread. // Unlike