michael@0: /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "ScriptLoader.h" michael@0: michael@0: #include "nsIChannel.h" michael@0: #include "nsIChannelPolicy.h" michael@0: #include "nsIContentPolicy.h" michael@0: #include "nsIContentSecurityPolicy.h" michael@0: #include "nsIHttpChannel.h" michael@0: #include "nsIIOService.h" michael@0: #include "nsIProtocolHandler.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsIStreamLoader.h" michael@0: #include "nsIURI.h" michael@0: michael@0: #include "jsapi.h" michael@0: #include "nsChannelPolicy.h" michael@0: #include "nsError.h" michael@0: #include "nsContentPolicyUtils.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsDocShellCID.h" michael@0: #include "nsISupportsPrimitives.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsScriptLoader.h" michael@0: #include "nsString.h" michael@0: #include "nsTArray.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsXPCOM.h" michael@0: #include "xpcpublic.h" michael@0: michael@0: #include "mozilla/dom/Exceptions.h" michael@0: #include "Principal.h" michael@0: #include "WorkerFeature.h" michael@0: #include "WorkerPrivate.h" michael@0: #include "WorkerRunnable.h" michael@0: michael@0: #define MAX_CONCURRENT_SCRIPTS 1000 michael@0: michael@0: USING_WORKERS_NAMESPACE michael@0: michael@0: using mozilla::dom::workers::exceptions::ThrowDOMExceptionForNSResult; michael@0: michael@0: namespace { michael@0: michael@0: nsresult michael@0: ChannelFromScriptURL(nsIPrincipal* principal, michael@0: nsIURI* baseURI, michael@0: nsIDocument* parentDoc, michael@0: nsILoadGroup* loadGroup, michael@0: nsIIOService* ios, michael@0: nsIScriptSecurityManager* secMan, michael@0: const nsAString& aScriptURL, michael@0: bool aIsWorkerScript, michael@0: nsIChannel** aChannel) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr uri; michael@0: rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), michael@0: aScriptURL, parentDoc, michael@0: baseURI); michael@0: if (NS_FAILED(rv)) { michael@0: return NS_ERROR_DOM_SYNTAX_ERR; michael@0: } michael@0: michael@0: // If we're part of a document then check the content load policy. michael@0: if (parentDoc) { michael@0: int16_t shouldLoad = nsIContentPolicy::ACCEPT; michael@0: rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_SCRIPT, uri, michael@0: principal, parentDoc, michael@0: NS_LITERAL_CSTRING("text/javascript"), michael@0: nullptr, &shouldLoad, michael@0: nsContentUtils::GetContentPolicy(), michael@0: secMan); michael@0: if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) { michael@0: if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) { michael@0: return rv = NS_ERROR_CONTENT_BLOCKED; michael@0: } michael@0: return rv = NS_ERROR_CONTENT_BLOCKED_SHOW_ALT; michael@0: } michael@0: } michael@0: michael@0: // If this script loader is being used to make a new worker then we need michael@0: // to do a same-origin check. Otherwise we need to clear the load with the michael@0: // security manager. michael@0: if (aIsWorkerScript) { michael@0: nsCString scheme; michael@0: rv = uri->GetScheme(scheme); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // We pass true as the 3rd argument to checkMayLoad here. michael@0: // This allows workers in sandboxed documents to load data URLs michael@0: // (and other URLs that inherit their principal from their michael@0: // creator.) michael@0: rv = principal->CheckMayLoad(uri, false, true); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR); michael@0: } michael@0: else { michael@0: rv = secMan->CheckLoadURIWithPrincipal(principal, uri, 0); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR); michael@0: } michael@0: michael@0: // Get Content Security Policy from parent document to pass into channel. michael@0: nsCOMPtr csp; michael@0: rv = principal->GetCsp(getter_AddRefs(csp)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr channelPolicy; michael@0: if (csp) { michael@0: channelPolicy = do_CreateInstance(NSCHANNELPOLICY_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = channelPolicy->SetContentSecurityPolicy(csp); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = channelPolicy->SetLoadType(nsIContentPolicy::TYPE_SCRIPT); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: uint32_t flags = nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI; michael@0: michael@0: nsCOMPtr channel; michael@0: rv = NS_NewChannel(getter_AddRefs(channel), uri, ios, loadGroup, nullptr, michael@0: flags, channelPolicy); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: channel.forget(aChannel); michael@0: return rv; michael@0: } michael@0: michael@0: struct ScriptLoadInfo michael@0: { michael@0: ScriptLoadInfo() michael@0: : mScriptTextBuf(nullptr) michael@0: , mScriptTextLength(0) michael@0: , mLoadResult(NS_ERROR_NOT_INITIALIZED), mExecutionScheduled(false) michael@0: , mExecutionResult(false) michael@0: { } michael@0: michael@0: ~ScriptLoadInfo() michael@0: { michael@0: if (mScriptTextBuf) { michael@0: js_free(mScriptTextBuf); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: ReadyToExecute() michael@0: { michael@0: return !mChannel && NS_SUCCEEDED(mLoadResult) && !mExecutionScheduled; michael@0: } michael@0: michael@0: nsString mURL; michael@0: nsCOMPtr mChannel; michael@0: jschar* mScriptTextBuf; michael@0: size_t mScriptTextLength; michael@0: michael@0: nsresult mLoadResult; michael@0: bool mExecutionScheduled; michael@0: bool mExecutionResult; michael@0: }; michael@0: michael@0: class ScriptLoaderRunnable; michael@0: michael@0: class ScriptExecutorRunnable MOZ_FINAL : public MainThreadWorkerSyncRunnable michael@0: { michael@0: ScriptLoaderRunnable& mScriptLoader; michael@0: uint32_t mFirstIndex; michael@0: uint32_t mLastIndex; michael@0: michael@0: public: michael@0: ScriptExecutorRunnable(ScriptLoaderRunnable& aScriptLoader, michael@0: nsIEventTarget* aSyncLoopTarget, uint32_t aFirstIndex, michael@0: uint32_t aLastIndex); michael@0: michael@0: private: michael@0: ~ScriptExecutorRunnable() michael@0: { } michael@0: michael@0: virtual bool michael@0: WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE; michael@0: michael@0: virtual void michael@0: PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult) michael@0: MOZ_OVERRIDE; michael@0: michael@0: NS_DECL_NSICANCELABLERUNNABLE michael@0: michael@0: void michael@0: ShutdownScriptLoader(JSContext* aCx, michael@0: WorkerPrivate* aWorkerPrivate, michael@0: bool aResult); michael@0: }; michael@0: michael@0: class ScriptLoaderRunnable MOZ_FINAL : public WorkerFeature, michael@0: public nsIRunnable, michael@0: public nsIStreamLoaderObserver michael@0: { michael@0: friend class ScriptExecutorRunnable; michael@0: michael@0: WorkerPrivate* mWorkerPrivate; michael@0: nsCOMPtr mSyncLoopTarget; michael@0: nsTArray mLoadInfos; michael@0: bool mIsWorkerScript; michael@0: bool mCanceled; michael@0: bool mCanceledMainThread; michael@0: michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: michael@0: ScriptLoaderRunnable(WorkerPrivate* aWorkerPrivate, michael@0: nsIEventTarget* aSyncLoopTarget, michael@0: nsTArray& aLoadInfos, michael@0: bool aIsWorkerScript) michael@0: : mWorkerPrivate(aWorkerPrivate), mSyncLoopTarget(aSyncLoopTarget), michael@0: mIsWorkerScript(aIsWorkerScript), mCanceled(false), michael@0: mCanceledMainThread(false) michael@0: { michael@0: aWorkerPrivate->AssertIsOnWorkerThread(); michael@0: MOZ_ASSERT(aSyncLoopTarget); michael@0: MOZ_ASSERT_IF(aIsWorkerScript, aLoadInfos.Length() == 1); michael@0: michael@0: mLoadInfos.SwapElements(aLoadInfos); michael@0: } michael@0: michael@0: private: michael@0: ~ScriptLoaderRunnable() michael@0: { } michael@0: michael@0: NS_IMETHOD michael@0: Run() MOZ_OVERRIDE michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: if (NS_FAILED(RunInternal())) { michael@0: CancelMainThread(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHOD michael@0: OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext, michael@0: nsresult aStatus, uint32_t aStringLen, michael@0: const uint8_t* aString) MOZ_OVERRIDE michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: nsCOMPtr indexSupports(do_QueryInterface(aContext)); michael@0: NS_ASSERTION(indexSupports, "This should never fail!"); michael@0: michael@0: uint32_t index = UINT32_MAX; michael@0: if (NS_FAILED(indexSupports->GetData(&index)) || michael@0: index >= mLoadInfos.Length()) { michael@0: NS_ERROR("Bad index!"); michael@0: } michael@0: michael@0: ScriptLoadInfo& loadInfo = mLoadInfos[index]; michael@0: michael@0: loadInfo.mLoadResult = OnStreamCompleteInternal(aLoader, aContext, aStatus, michael@0: aStringLen, aString, michael@0: loadInfo); michael@0: michael@0: ExecuteFinishedScripts(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: virtual bool michael@0: Notify(JSContext* aCx, Status aStatus) MOZ_OVERRIDE michael@0: { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: if (aStatus >= Terminating && !mCanceled) { michael@0: mCanceled = true; michael@0: michael@0: nsCOMPtr runnable = michael@0: NS_NewRunnableMethod(this, &ScriptLoaderRunnable::CancelMainThread); michael@0: NS_ASSERTION(runnable, "This should never fail!"); michael@0: michael@0: if (NS_FAILED(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL))) { michael@0: JS_ReportError(aCx, "Failed to cancel script loader!"); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: CancelMainThread() michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: if (mCanceledMainThread) { michael@0: return; michael@0: } michael@0: michael@0: mCanceledMainThread = true; michael@0: michael@0: // Cancel all the channels that were already opened. michael@0: for (uint32_t index = 0; index < mLoadInfos.Length(); index++) { michael@0: ScriptLoadInfo& loadInfo = mLoadInfos[index]; michael@0: michael@0: if (loadInfo.mChannel && michael@0: NS_FAILED(loadInfo.mChannel->Cancel(NS_BINDING_ABORTED))) { michael@0: NS_WARNING("Failed to cancel channel!"); michael@0: loadInfo.mChannel = nullptr; michael@0: loadInfo.mLoadResult = NS_BINDING_ABORTED; michael@0: } michael@0: } michael@0: michael@0: ExecuteFinishedScripts(); michael@0: } michael@0: michael@0: nsresult michael@0: RunInternal() michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: WorkerPrivate* parentWorker = mWorkerPrivate->GetParent(); michael@0: michael@0: // Figure out which principal to use. michael@0: nsIPrincipal* principal = mWorkerPrivate->GetPrincipal(); michael@0: if (!principal) { michael@0: NS_ASSERTION(parentWorker, "Must have a principal!"); michael@0: NS_ASSERTION(mIsWorkerScript, "Must have a principal for importScripts!"); michael@0: michael@0: principal = parentWorker->GetPrincipal(); michael@0: } michael@0: NS_ASSERTION(principal, "This should never be null here!"); michael@0: michael@0: // Figure out our base URI. michael@0: nsCOMPtr baseURI; michael@0: if (mIsWorkerScript) { michael@0: if (parentWorker) { michael@0: baseURI = parentWorker->GetBaseURI(); michael@0: NS_ASSERTION(baseURI, "Should have been set already!"); michael@0: } michael@0: else { michael@0: // May be null. michael@0: baseURI = mWorkerPrivate->GetBaseURI(); michael@0: } michael@0: } michael@0: else { michael@0: baseURI = mWorkerPrivate->GetBaseURI(); michael@0: NS_ASSERTION(baseURI, "Should have been set already!"); michael@0: } michael@0: michael@0: // May be null. michael@0: nsCOMPtr parentDoc = mWorkerPrivate->GetDocument(); michael@0: michael@0: nsCOMPtr channel; michael@0: if (mIsWorkerScript) { michael@0: // May be null. michael@0: channel = mWorkerPrivate->ForgetWorkerChannel(); michael@0: } michael@0: michael@0: // All of these can potentially be null, but that should be ok. We'll either michael@0: // succeed without them or fail below. michael@0: nsCOMPtr loadGroup; michael@0: if (parentDoc) { michael@0: loadGroup = parentDoc->GetDocumentLoadGroup(); michael@0: } michael@0: michael@0: nsCOMPtr ios(do_GetIOService()); michael@0: michael@0: nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); michael@0: NS_ASSERTION(secMan, "This should never be null!"); michael@0: michael@0: for (uint32_t index = 0; index < mLoadInfos.Length(); index++) { michael@0: ScriptLoadInfo& loadInfo = mLoadInfos[index]; michael@0: nsresult& rv = loadInfo.mLoadResult; michael@0: michael@0: if (!channel) { michael@0: rv = ChannelFromScriptURL(principal, baseURI, parentDoc, loadGroup, ios, michael@0: secMan, loadInfo.mURL, mIsWorkerScript, michael@0: getter_AddRefs(channel)); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: // We need to know which index we're on in OnStreamComplete so we know michael@0: // where to put the result. michael@0: nsCOMPtr indexSupports = michael@0: do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = indexSupports->SetData(index); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // We don't care about progress so just use the simple stream loader for michael@0: // OnStreamComplete notification only. michael@0: nsCOMPtr loader; michael@0: rv = NS_NewStreamLoader(getter_AddRefs(loader), this); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = channel->AsyncOpen(loader, indexSupports); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: loadInfo.mChannel.swap(channel); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: OnStreamCompleteInternal(nsIStreamLoader* aLoader, nsISupports* aContext, michael@0: nsresult aStatus, uint32_t aStringLen, michael@0: const uint8_t* aString, ScriptLoadInfo& aLoadInfo) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: if (!aLoadInfo.mChannel) { michael@0: return NS_BINDING_ABORTED; michael@0: } michael@0: michael@0: aLoadInfo.mChannel = nullptr; michael@0: michael@0: if (NS_FAILED(aStatus)) { michael@0: return aStatus; michael@0: } michael@0: michael@0: if (!aStringLen) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_ASSERTION(aString, "This should never be null!"); michael@0: michael@0: // Make sure we're not seeing the result of a 404 or something by checking michael@0: // the 'requestSucceeded' attribute on the http channel. michael@0: nsCOMPtr request; michael@0: nsresult rv = aLoader->GetRequest(getter_AddRefs(request)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr httpChannel = do_QueryInterface(request); michael@0: if (httpChannel) { michael@0: bool requestSucceeded; michael@0: rv = httpChannel->GetRequestSucceeded(&requestSucceeded); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!requestSucceeded) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: } michael@0: michael@0: // May be null. michael@0: nsIDocument* parentDoc = mWorkerPrivate->GetDocument(); michael@0: michael@0: // Use the regular nsScriptLoader for this grunt work! Should be just fine michael@0: // because we're running on the main thread. michael@0: // Unlike