/* -*- 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 "nsIInputStreamPump.h" #include "nsIIOService.h" #include "nsIProtocolHandler.h" #include "nsIScriptSecurityManager.h" #include "nsIStreamLoader.h" #include "nsIStreamListenerTee.h" #include "nsIThreadRetargetableRequest.h" #include "nsIURI.h" #include "jsapi.h" #include "nsError.h" #include "nsContentPolicyUtils.h" #include "nsContentUtils.h" #include "nsDocShellCID.h" #include "nsISupportsPrimitives.h" #include "nsNetUtil.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/Exceptions.h" #include "mozilla/dom/InternalResponse.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/Response.h" #include "Principal.h" #include "WorkerFeature.h" #include "WorkerPrivate.h" #include "WorkerRunnable.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::dom::workers::exceptions::ThrowDOMExceptionForNSResult; 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, 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're part of a document then check the content load policy. if (parentDoc) { int16_t shouldLoad = nsIContentPolicy::ACCEPT; rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_SCRIPT, uri, principal, parentDoc, NS_LITERAL_CSTRING("text/javascript"), nullptr, &shouldLoad, nsContentUtils::GetContentPolicy(), secMan); if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) { if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) { return rv = NS_ERROR_CONTENT_BLOCKED; } return rv = NS_ERROR_CONTENT_BLOCKED_SHOW_ALT; } } if (aWorkerScriptType == DebuggerScript) { bool isChrome = false; NS_ENSURE_SUCCESS(uri->SchemeIs("chrome", &isChrome), NS_ERROR_DOM_SECURITY_ERR); bool isResource = false; NS_ENSURE_SUCCESS(uri->SchemeIs("resource", &isResource), NS_ERROR_DOM_SECURITY_ERR); if (!isChrome && !isResource) { return NS_ERROR_DOM_SECURITY_ERR; } } else if (aIsMainScript) { // If this script loader is being used to make a new worker then we need // to do a same-origin check. Otherwise we need to clear the load with the // security manager. nsCString scheme; rv = uri->GetScheme(scheme); NS_ENSURE_SUCCESS(rv, rv); // We pass true as the 3rd argument to checkMayLoad here. // This allows workers in sandboxed documents to load data URLs // (and other URLs that inherit their principal from their // creator.) rv = principal->CheckMayLoad(uri, false, true); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR); } else { rv = secMan->CheckLoadURIWithPrincipal(principal, uri, 0); NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR); } uint32_t flags = nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI; nsCOMPtr channel; // If we have the document, use it if (parentDoc) { rv = NS_NewChannel(getter_AddRefs(channel), uri, parentDoc, nsILoadInfo::SEC_NORMAL, nsIContentPolicy::TYPE_SCRIPT, loadGroup, nullptr, // aCallbacks flags, 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, nsILoadInfo::SEC_NORMAL, nsIContentPolicy::TYPE_SCRIPT, loadGroup, nullptr, // aCallbacks flags, ios); } NS_ENSURE_SUCCESS(rv, 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. nsRefPtr mCachePromise; 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; 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); }; class CacheScriptLoader; class CacheCreator final : public PromiseNativeHandler { public: explicit CacheCreator(WorkerPrivate* aWorkerPrivate) : mCacheName(aWorkerPrivate->ServiceWorkerCacheName()) { 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); nsRefPtr mCache; nsRefPtr mCacheStorage; nsCOMPtr mSandboxGlobalObject; nsTArray> mLoaders; nsString mCacheName; }; class CacheScriptLoader final : public PromiseNativeHandler , public nsIStreamLoaderObserver { public: NS_DECL_ISUPPORTS_INHERITED 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; nsRefPtr mRunnable; bool mIsWorkerScript; bool mFailed; nsCOMPtr mPump; nsCOMPtr mBaseURI; ChannelInfo mChannelInfo; UniquePtr mPrincipalInfo; }; NS_IMPL_ISUPPORTS(CacheScriptLoader, nsIStreamLoaderObserver) class CachePromiseHandler final : public PromiseNativeHandler { public: 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(); } nsRefPtr mRunnable; ScriptLoadInfo& mLoadInfo; uint32_t mIndex; }; class ScriptLoaderRunnable final : public WorkerFeature, public nsIRunnable, public nsIStreamLoaderObserver, public nsIRequestObserver { friend class ScriptExecutorRunnable; friend class CachePromiseHandler; friend class CacheScriptLoader; WorkerPrivate* mWorkerPrivate; nsCOMPtr mSyncLoopTarget; nsTArray mLoadInfos; nsRefPtr mCacheCreator; nsCOMPtr mReader; bool mIsMainScript; WorkerScriptType mWorkerScriptType; bool mCanceled; bool mCanceledMainThread; public: NS_DECL_THREADSAFE_ISUPPORTS ScriptLoaderRunnable(WorkerPrivate* aWorkerPrivate, nsIEventTarget* aSyncLoopTarget, nsTArray& aLoadInfos, bool aIsMainScript, WorkerScriptType aWorkerScriptType) : mWorkerPrivate(aWorkerPrivate), mSyncLoopTarget(aSyncLoopTarget), mIsMainScript(aIsMainScript), mWorkerScriptType(aWorkerScriptType), mCanceled(false), mCanceledMainThread(false) { 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(); } } NS_IMETHOD OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext, nsresult aStatus, uint32_t aStringLen, const uint8_t* aString) override { AssertIsOnMainThread(); nsCOMPtr indexSupports(do_QueryInterface(aContext)); NS_ASSERTION(indexSupports, "This should never fail!"); uint32_t index = UINT32_MAX; if (NS_FAILED(indexSupports->GetData(&index)) || index >= mLoadInfos.Length()) { NS_ERROR("Bad index!"); } ScriptLoadInfo& loadInfo = mLoadInfos[index]; nsresult rv = OnStreamCompleteInternal(aLoader, aContext, aStatus, aStringLen, aString, loadInfo); LoadingFinished(index, rv); return NS_OK; } NS_IMETHOD OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) override { AssertIsOnMainThread(); nsCOMPtr indexSupports(do_QueryInterface(aContext)); MOZ_ASSERT(indexSupports, "This should never fail!"); uint32_t index = UINT32_MAX; if (NS_FAILED(indexSupports->GetData(&index)) || index >= mLoadInfos.Length()) { MOZ_CRASH("Bad index!"); } ScriptLoadInfo& loadInfo = mLoadInfos[index]; nsCOMPtr channel = do_QueryInterface(aRequest); MOZ_ASSERT(channel == loadInfo.mChannel); // We synthesize the result code, but its never exposed to content. nsRefPtr ir = new InternalResponse(200, NS_LITERAL_CSTRING("OK")); ir->SetBody(mReader); // 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)); nsRefPtr response = new Response(mCacheCreator->Global(), ir); RequestOrUSVString request; MOZ_ASSERT(!loadInfo.mFullURL.IsEmpty()); request.SetAsUSVString().Rebind(loadInfo.mFullURL.Data(), loadInfo.mFullURL.Length()); ErrorResult error; nsRefPtr cachePromise = mCacheCreator->Cache_()->Put(request, *response, error); if (NS_WARN_IF(error.Failed())) { nsresult rv = error.StealNSResult(); channel->Cancel(rv); return rv; } nsRefPtr promiseHandler = new CachePromiseHandler(this, loadInfo, index); cachePromise->AppendNativeHandler(promiseHandler); loadInfo.mCachePromise.swap(cachePromise); loadInfo.mCacheStatus = ScriptLoadInfo::WritingToCache; return NS_OK; } NS_IMETHOD OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatusCode) override { // Nothing to do here! 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))) { return rv; } } return NS_OK; } MOZ_ASSERT(!mCacheCreator); mCacheCreator = new CacheCreator(mWorkerPrivate); for (uint32_t index = 0, len = mLoadInfos.Length(); index < len; ++index) { nsRefPtr 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; if (!channel) { rv = ChannelFromScriptURL(principal, baseURI, parentDoc, loadGroup, ios, secMan, loadInfo.mURL, IsMainWorkerScript(), mWorkerScriptType, 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. nsCOMPtr indexSupports = do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = indexSupports->SetData(aIndex); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // 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), this); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (loadInfo.mCacheStatus != ScriptLoadInfo::ToBeCached) { rv = channel->AsyncOpen(loader, indexSupports); 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(mReader), 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, this); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsresult rv = channel->AsyncOpen(tee, indexSupports); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } loadInfo.mChannel.swap(channel); return NS_OK; } nsresult OnStreamCompleteInternal(nsIStreamLoader* aLoader, nsISupports* aContext, 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!"); // Make sure we're not seeing the result of a 404 or something by checking // the 'requestSucceeded' attribute on the http channel. nsCOMPtr request; nsresult rv = aLoader->GetRequest(getter_AddRefs(request)); NS_ENSURE_SUCCESS(rv, rv); 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