dom/workers/ScriptLoader.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "ScriptLoader.h"
     8 #include "nsIChannel.h"
     9 #include "nsIChannelPolicy.h"
    10 #include "nsIContentPolicy.h"
    11 #include "nsIContentSecurityPolicy.h"
    12 #include "nsIHttpChannel.h"
    13 #include "nsIIOService.h"
    14 #include "nsIProtocolHandler.h"
    15 #include "nsIScriptSecurityManager.h"
    16 #include "nsIStreamLoader.h"
    17 #include "nsIURI.h"
    19 #include "jsapi.h"
    20 #include "nsChannelPolicy.h"
    21 #include "nsError.h"
    22 #include "nsContentPolicyUtils.h"
    23 #include "nsContentUtils.h"
    24 #include "nsDocShellCID.h"
    25 #include "nsISupportsPrimitives.h"
    26 #include "nsNetUtil.h"
    27 #include "nsScriptLoader.h"
    28 #include "nsString.h"
    29 #include "nsTArray.h"
    30 #include "nsThreadUtils.h"
    31 #include "nsXPCOM.h"
    32 #include "xpcpublic.h"
    34 #include "mozilla/dom/Exceptions.h"
    35 #include "Principal.h"
    36 #include "WorkerFeature.h"
    37 #include "WorkerPrivate.h"
    38 #include "WorkerRunnable.h"
    40 #define MAX_CONCURRENT_SCRIPTS 1000
    42 USING_WORKERS_NAMESPACE
    44 using mozilla::dom::workers::exceptions::ThrowDOMExceptionForNSResult;
    46 namespace {
    48 nsresult
    49 ChannelFromScriptURL(nsIPrincipal* principal,
    50                      nsIURI* baseURI,
    51                      nsIDocument* parentDoc,
    52                      nsILoadGroup* loadGroup,
    53                      nsIIOService* ios,
    54                      nsIScriptSecurityManager* secMan,
    55                      const nsAString& aScriptURL,
    56                      bool aIsWorkerScript,
    57                      nsIChannel** aChannel)
    58 {
    59   AssertIsOnMainThread();
    61   nsresult rv;
    62   nsCOMPtr<nsIURI> uri;
    63   rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri),
    64                                                  aScriptURL, parentDoc,
    65                                                  baseURI);
    66   if (NS_FAILED(rv)) {
    67     return NS_ERROR_DOM_SYNTAX_ERR;
    68   }
    70   // If we're part of a document then check the content load policy.
    71   if (parentDoc) {
    72     int16_t shouldLoad = nsIContentPolicy::ACCEPT;
    73     rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_SCRIPT, uri,
    74                                    principal, parentDoc,
    75                                    NS_LITERAL_CSTRING("text/javascript"),
    76                                    nullptr, &shouldLoad,
    77                                    nsContentUtils::GetContentPolicy(),
    78                                    secMan);
    79     if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
    80       if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) {
    81         return rv = NS_ERROR_CONTENT_BLOCKED;
    82       }
    83       return rv = NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
    84     }
    85   }
    87   // If this script loader is being used to make a new worker then we need
    88   // to do a same-origin check. Otherwise we need to clear the load with the
    89   // security manager.
    90   if (aIsWorkerScript) {
    91     nsCString scheme;
    92     rv = uri->GetScheme(scheme);
    93     NS_ENSURE_SUCCESS(rv, rv);
    95     // We pass true as the 3rd argument to checkMayLoad here.
    96     // This allows workers in sandboxed documents to load data URLs
    97     // (and other URLs that inherit their principal from their
    98     // creator.)
    99     rv = principal->CheckMayLoad(uri, false, true);
   100     NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR);
   101   }
   102   else {
   103     rv = secMan->CheckLoadURIWithPrincipal(principal, uri, 0);
   104     NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR);
   105   }
   107   // Get Content Security Policy from parent document to pass into channel.
   108   nsCOMPtr<nsIContentSecurityPolicy> csp;
   109   rv = principal->GetCsp(getter_AddRefs(csp));
   110   NS_ENSURE_SUCCESS(rv, rv);
   112   nsCOMPtr<nsIChannelPolicy> channelPolicy;
   113   if (csp) {
   114     channelPolicy = do_CreateInstance(NSCHANNELPOLICY_CONTRACTID, &rv);
   115     NS_ENSURE_SUCCESS(rv, rv);
   117     rv = channelPolicy->SetContentSecurityPolicy(csp);
   118     NS_ENSURE_SUCCESS(rv, rv);
   120     rv = channelPolicy->SetLoadType(nsIContentPolicy::TYPE_SCRIPT);
   121     NS_ENSURE_SUCCESS(rv, rv);
   122   }
   124   uint32_t flags = nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI;
   126   nsCOMPtr<nsIChannel> channel;
   127   rv = NS_NewChannel(getter_AddRefs(channel), uri, ios, loadGroup, nullptr,
   128                      flags, channelPolicy);
   129   NS_ENSURE_SUCCESS(rv, rv);
   131   channel.forget(aChannel);
   132   return rv;
   133 }
   135 struct ScriptLoadInfo
   136 {
   137   ScriptLoadInfo()
   138   : mScriptTextBuf(nullptr)
   139   , mScriptTextLength(0)
   140   , mLoadResult(NS_ERROR_NOT_INITIALIZED), mExecutionScheduled(false)
   141   , mExecutionResult(false)
   142   { }
   144   ~ScriptLoadInfo()
   145   {
   146     if (mScriptTextBuf) {
   147       js_free(mScriptTextBuf);
   148     }
   149   }
   151   bool
   152   ReadyToExecute()
   153   {
   154     return !mChannel && NS_SUCCEEDED(mLoadResult) && !mExecutionScheduled;
   155   }
   157   nsString mURL;
   158   nsCOMPtr<nsIChannel> mChannel;
   159   jschar* mScriptTextBuf;
   160   size_t mScriptTextLength;
   162   nsresult mLoadResult;
   163   bool mExecutionScheduled;
   164   bool mExecutionResult;
   165 };
   167 class ScriptLoaderRunnable;
   169 class ScriptExecutorRunnable MOZ_FINAL : public MainThreadWorkerSyncRunnable
   170 {
   171   ScriptLoaderRunnable& mScriptLoader;
   172   uint32_t mFirstIndex;
   173   uint32_t mLastIndex;
   175 public:
   176   ScriptExecutorRunnable(ScriptLoaderRunnable& aScriptLoader,
   177                          nsIEventTarget* aSyncLoopTarget, uint32_t aFirstIndex,
   178                          uint32_t aLastIndex);
   180 private:
   181   ~ScriptExecutorRunnable()
   182   { }
   184   virtual bool
   185   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE;
   187   virtual void
   188   PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult)
   189           MOZ_OVERRIDE;
   191   NS_DECL_NSICANCELABLERUNNABLE
   193   void
   194   ShutdownScriptLoader(JSContext* aCx,
   195                        WorkerPrivate* aWorkerPrivate,
   196                        bool aResult);
   197 };
   199 class ScriptLoaderRunnable MOZ_FINAL : public WorkerFeature,
   200                                        public nsIRunnable,
   201                                        public nsIStreamLoaderObserver
   202 {
   203   friend class ScriptExecutorRunnable;
   205   WorkerPrivate* mWorkerPrivate;
   206   nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
   207   nsTArray<ScriptLoadInfo> mLoadInfos;
   208   bool mIsWorkerScript;
   209   bool mCanceled;
   210   bool mCanceledMainThread;
   212 public:
   213   NS_DECL_THREADSAFE_ISUPPORTS
   215   ScriptLoaderRunnable(WorkerPrivate* aWorkerPrivate,
   216                        nsIEventTarget* aSyncLoopTarget,
   217                        nsTArray<ScriptLoadInfo>& aLoadInfos,
   218                        bool aIsWorkerScript)
   219   : mWorkerPrivate(aWorkerPrivate), mSyncLoopTarget(aSyncLoopTarget),
   220     mIsWorkerScript(aIsWorkerScript), mCanceled(false),
   221     mCanceledMainThread(false)
   222   {
   223     aWorkerPrivate->AssertIsOnWorkerThread();
   224     MOZ_ASSERT(aSyncLoopTarget);
   225     MOZ_ASSERT_IF(aIsWorkerScript, aLoadInfos.Length() == 1);
   227     mLoadInfos.SwapElements(aLoadInfos);
   228   }
   230 private:
   231   ~ScriptLoaderRunnable()
   232   { }
   234   NS_IMETHOD
   235   Run() MOZ_OVERRIDE
   236   {
   237     AssertIsOnMainThread();
   239     if (NS_FAILED(RunInternal())) {
   240       CancelMainThread();
   241     }
   243     return NS_OK;
   244   }
   246   NS_IMETHOD
   247   OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
   248                    nsresult aStatus, uint32_t aStringLen,
   249                    const uint8_t* aString) MOZ_OVERRIDE
   250   {
   251     AssertIsOnMainThread();
   253     nsCOMPtr<nsISupportsPRUint32> indexSupports(do_QueryInterface(aContext));
   254     NS_ASSERTION(indexSupports, "This should never fail!");
   256     uint32_t index = UINT32_MAX;
   257     if (NS_FAILED(indexSupports->GetData(&index)) ||
   258         index >= mLoadInfos.Length()) {
   259       NS_ERROR("Bad index!");
   260     }
   262     ScriptLoadInfo& loadInfo = mLoadInfos[index];
   264     loadInfo.mLoadResult = OnStreamCompleteInternal(aLoader, aContext, aStatus,
   265                                                     aStringLen, aString,
   266                                                     loadInfo);
   268     ExecuteFinishedScripts();
   270     return NS_OK;
   271   }
   273   virtual bool
   274   Notify(JSContext* aCx, Status aStatus) MOZ_OVERRIDE
   275   {
   276     mWorkerPrivate->AssertIsOnWorkerThread();
   278     if (aStatus >= Terminating && !mCanceled) {
   279       mCanceled = true;
   281       nsCOMPtr<nsIRunnable> runnable =
   282         NS_NewRunnableMethod(this, &ScriptLoaderRunnable::CancelMainThread);
   283       NS_ASSERTION(runnable, "This should never fail!");
   285       if (NS_FAILED(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL))) {
   286         JS_ReportError(aCx, "Failed to cancel script loader!");
   287         return false;
   288       }
   289     }
   291     return true;
   292   }
   294   void
   295   CancelMainThread()
   296   {
   297     AssertIsOnMainThread();
   299     if (mCanceledMainThread) {
   300       return;
   301     }
   303     mCanceledMainThread = true;
   305     // Cancel all the channels that were already opened.
   306     for (uint32_t index = 0; index < mLoadInfos.Length(); index++) {
   307       ScriptLoadInfo& loadInfo = mLoadInfos[index];
   309       if (loadInfo.mChannel &&
   310           NS_FAILED(loadInfo.mChannel->Cancel(NS_BINDING_ABORTED))) {
   311         NS_WARNING("Failed to cancel channel!");
   312         loadInfo.mChannel = nullptr;
   313         loadInfo.mLoadResult = NS_BINDING_ABORTED;
   314       }
   315     }
   317     ExecuteFinishedScripts();
   318   }
   320   nsresult
   321   RunInternal()
   322   {
   323     AssertIsOnMainThread();
   325     WorkerPrivate* parentWorker = mWorkerPrivate->GetParent();
   327     // Figure out which principal to use.
   328     nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
   329     if (!principal) {
   330       NS_ASSERTION(parentWorker, "Must have a principal!");
   331       NS_ASSERTION(mIsWorkerScript, "Must have a principal for importScripts!");
   333       principal = parentWorker->GetPrincipal();
   334     }
   335     NS_ASSERTION(principal, "This should never be null here!");
   337     // Figure out our base URI.
   338     nsCOMPtr<nsIURI> baseURI;
   339     if (mIsWorkerScript) {
   340       if (parentWorker) {
   341         baseURI = parentWorker->GetBaseURI();
   342         NS_ASSERTION(baseURI, "Should have been set already!");
   343       }
   344       else {
   345         // May be null.
   346         baseURI = mWorkerPrivate->GetBaseURI();
   347       }
   348     }
   349     else {
   350       baseURI = mWorkerPrivate->GetBaseURI();
   351       NS_ASSERTION(baseURI, "Should have been set already!");
   352     }
   354     // May be null.
   355     nsCOMPtr<nsIDocument> parentDoc = mWorkerPrivate->GetDocument();
   357     nsCOMPtr<nsIChannel> channel;
   358     if (mIsWorkerScript) {
   359       // May be null.
   360       channel = mWorkerPrivate->ForgetWorkerChannel();
   361     }
   363     // All of these can potentially be null, but that should be ok. We'll either
   364     // succeed without them or fail below.
   365     nsCOMPtr<nsILoadGroup> loadGroup;
   366     if (parentDoc) {
   367       loadGroup = parentDoc->GetDocumentLoadGroup();
   368     }
   370     nsCOMPtr<nsIIOService> ios(do_GetIOService());
   372     nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
   373     NS_ASSERTION(secMan, "This should never be null!");
   375     for (uint32_t index = 0; index < mLoadInfos.Length(); index++) {
   376       ScriptLoadInfo& loadInfo = mLoadInfos[index];
   377       nsresult& rv = loadInfo.mLoadResult;
   379       if (!channel) {
   380         rv = ChannelFromScriptURL(principal, baseURI, parentDoc, loadGroup, ios,
   381                                   secMan, loadInfo.mURL, mIsWorkerScript,
   382                                                 getter_AddRefs(channel));
   383         if (NS_FAILED(rv)) {
   384           return rv;
   385         }
   386       }
   388       // We need to know which index we're on in OnStreamComplete so we know
   389       // where to put the result.
   390       nsCOMPtr<nsISupportsPRUint32> indexSupports =
   391         do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
   392       NS_ENSURE_SUCCESS(rv, rv);
   394       rv = indexSupports->SetData(index);
   395       NS_ENSURE_SUCCESS(rv, rv);
   397       // We don't care about progress so just use the simple stream loader for
   398       // OnStreamComplete notification only.
   399       nsCOMPtr<nsIStreamLoader> loader;
   400       rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
   401       NS_ENSURE_SUCCESS(rv, rv);
   403       rv = channel->AsyncOpen(loader, indexSupports);
   404       NS_ENSURE_SUCCESS(rv, rv);
   406       loadInfo.mChannel.swap(channel);
   407     }
   409     return NS_OK;
   410   }
   412   nsresult
   413   OnStreamCompleteInternal(nsIStreamLoader* aLoader, nsISupports* aContext,
   414                            nsresult aStatus, uint32_t aStringLen,
   415                            const uint8_t* aString, ScriptLoadInfo& aLoadInfo)
   416   {
   417     AssertIsOnMainThread();
   419     if (!aLoadInfo.mChannel) {
   420       return NS_BINDING_ABORTED;
   421     }
   423     aLoadInfo.mChannel = nullptr;
   425     if (NS_FAILED(aStatus)) {
   426       return aStatus;
   427     }
   429     if (!aStringLen) {
   430       return NS_OK;
   431     }
   433     NS_ASSERTION(aString, "This should never be null!");
   435     // Make sure we're not seeing the result of a 404 or something by checking
   436     // the 'requestSucceeded' attribute on the http channel.
   437     nsCOMPtr<nsIRequest> request;
   438     nsresult rv = aLoader->GetRequest(getter_AddRefs(request));
   439     NS_ENSURE_SUCCESS(rv, rv);
   441     nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
   442     if (httpChannel) {
   443       bool requestSucceeded;
   444       rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
   445       NS_ENSURE_SUCCESS(rv, rv);
   447       if (!requestSucceeded) {
   448         return NS_ERROR_NOT_AVAILABLE;
   449       }
   450     }
   452     // May be null.
   453     nsIDocument* parentDoc = mWorkerPrivate->GetDocument();
   455     // Use the regular nsScriptLoader for this grunt work! Should be just fine
   456     // because we're running on the main thread.
   457     // Unlike <script> tags, Worker scripts are always decoded as UTF-8,
   458     // per spec. So we explicitly pass in the charset hint.
   459     rv = nsScriptLoader::ConvertToUTF16(aLoadInfo.mChannel, aString, aStringLen,
   460                                         NS_LITERAL_STRING("UTF-8"), parentDoc,
   461                                         aLoadInfo.mScriptTextBuf,
   462                                         aLoadInfo.mScriptTextLength);
   463     if (NS_FAILED(rv)) {
   464       return rv;
   465     }
   467     if (!aLoadInfo.mScriptTextBuf || !aLoadInfo.mScriptTextLength) {
   468       return NS_ERROR_FAILURE;
   469     }
   471     nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
   472     NS_ASSERTION(channel, "This should never fail!");
   474     // Figure out what we actually loaded.
   475     nsCOMPtr<nsIURI> finalURI;
   476     rv = NS_GetFinalChannelURI(channel, getter_AddRefs(finalURI));
   477     NS_ENSURE_SUCCESS(rv, rv);
   479     nsCString filename;
   480     rv = finalURI->GetSpec(filename);
   481     NS_ENSURE_SUCCESS(rv, rv);
   483     if (!filename.IsEmpty()) {
   484       // This will help callers figure out what their script url resolved to in
   485       // case of errors.
   486       aLoadInfo.mURL.Assign(NS_ConvertUTF8toUTF16(filename));
   487     }
   489     // Update the principal of the worker and its base URI if we just loaded the
   490     // worker's primary script.
   491     if (mIsWorkerScript) {
   492       // Take care of the base URI first.
   493       mWorkerPrivate->SetBaseURI(finalURI);
   495       // Now to figure out which principal to give this worker.
   496       WorkerPrivate* parent = mWorkerPrivate->GetParent();
   498       NS_ASSERTION(mWorkerPrivate->GetPrincipal() || parent,
   499                    "Must have one of these!");
   501       nsCOMPtr<nsIPrincipal> loadPrincipal = mWorkerPrivate->GetPrincipal() ?
   502                                              mWorkerPrivate->GetPrincipal() :
   503                                              parent->GetPrincipal();
   505       nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
   506       NS_ASSERTION(ssm, "Should never be null!");
   508       nsCOMPtr<nsIPrincipal> channelPrincipal;
   509       rv = ssm->GetChannelPrincipal(channel, getter_AddRefs(channelPrincipal));
   510       NS_ENSURE_SUCCESS(rv, rv);
   512       // See if this is a resource URI. Since JSMs usually come from resource://
   513       // URIs we're currently considering all URIs with the URI_IS_UI_RESOURCE
   514       // flag as valid for creating privileged workers.
   515       if (!nsContentUtils::IsSystemPrincipal(channelPrincipal)) {
   516         bool isResource;
   517         rv = NS_URIChainHasFlags(finalURI,
   518                                  nsIProtocolHandler::URI_IS_UI_RESOURCE,
   519                                  &isResource);
   520         NS_ENSURE_SUCCESS(rv, rv);
   522         if (isResource) {
   523           rv = ssm->GetSystemPrincipal(getter_AddRefs(channelPrincipal));
   524           NS_ENSURE_SUCCESS(rv, rv);
   525         }
   526       }
   528       // If the load principal is the system principal then the channel
   529       // principal must also be the system principal (we do not allow chrome
   530       // code to create workers with non-chrome scripts). Otherwise this channel
   531       // principal must be same origin with the load principal (we check again
   532       // here in case redirects changed the location of the script).
   533       if (nsContentUtils::IsSystemPrincipal(loadPrincipal)) {
   534         if (!nsContentUtils::IsSystemPrincipal(channelPrincipal)) {
   535           return NS_ERROR_DOM_BAD_URI;
   536         }
   537       }
   538       else  {
   539         nsCString scheme;
   540         rv = finalURI->GetScheme(scheme);
   541         NS_ENSURE_SUCCESS(rv, rv);
   543         // We exempt data urls and other URI's that inherit their
   544         // principal again.
   545         if (NS_FAILED(loadPrincipal->CheckMayLoad(finalURI, false, true))) {
   546           return NS_ERROR_DOM_BAD_URI;
   547         }
   548       }
   550       mWorkerPrivate->SetPrincipal(channelPrincipal);
   552       if (parent) {
   553         // XHR Params Allowed
   554         mWorkerPrivate->SetXHRParamsAllowed(parent->XHRParamsAllowed());
   556         // Set Eval and ContentSecurityPolicy
   557         mWorkerPrivate->SetCSP(parent->GetCSP());
   558         mWorkerPrivate->SetEvalAllowed(parent->IsEvalAllowed());
   559       }
   560     }
   562     return NS_OK;
   563   }
   565   void
   566   ExecuteFinishedScripts()
   567   {
   568     AssertIsOnMainThread();
   570     if (mIsWorkerScript) {
   571       mWorkerPrivate->WorkerScriptLoaded();
   572     }
   574     uint32_t firstIndex = UINT32_MAX;
   575     uint32_t lastIndex = UINT32_MAX;
   577     // Find firstIndex based on whether mExecutionScheduled is unset.
   578     for (uint32_t index = 0; index < mLoadInfos.Length(); index++) {
   579       if (!mLoadInfos[index].mExecutionScheduled) {
   580         firstIndex = index;
   581         break;
   582       }
   583     }
   585     // Find lastIndex based on whether mChannel is set, and update
   586     // mExecutionScheduled on the ones we're about to schedule.
   587     if (firstIndex != UINT32_MAX) {
   588       for (uint32_t index = firstIndex; index < mLoadInfos.Length(); index++) {
   589         ScriptLoadInfo& loadInfo = mLoadInfos[index];
   591         // If we still have a channel then the load is not complete.
   592         if (loadInfo.mChannel) {
   593           break;
   594         }
   596         // We can execute this one.
   597         loadInfo.mExecutionScheduled = true;
   599         lastIndex = index;
   600       }
   601     }
   603     if (firstIndex != UINT32_MAX && lastIndex != UINT32_MAX) {
   604       nsRefPtr<ScriptExecutorRunnable> runnable =
   605         new ScriptExecutorRunnable(*this, mSyncLoopTarget, firstIndex,
   606                                    lastIndex);
   607       if (!runnable->Dispatch(nullptr)) {
   608         MOZ_ASSERT(false, "This should never fail!");
   609       }
   610     }
   611   }
   612 };
   614 NS_IMPL_ISUPPORTS(ScriptLoaderRunnable, nsIRunnable, nsIStreamLoaderObserver)
   616 class ChannelGetterRunnable MOZ_FINAL : public nsRunnable
   617 {
   618   WorkerPrivate* mParentWorker;
   619   nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
   620   const nsAString& mScriptURL;
   621   nsIChannel** mChannel;
   622   nsresult mResult;
   624 public:
   625   ChannelGetterRunnable(WorkerPrivate* aParentWorker,
   626                         nsIEventTarget* aSyncLoopTarget,
   627                         const nsAString& aScriptURL,
   628                         nsIChannel** aChannel)
   629   : mParentWorker(aParentWorker), mSyncLoopTarget(aSyncLoopTarget),
   630     mScriptURL(aScriptURL), mChannel(aChannel), mResult(NS_ERROR_FAILURE)
   631   {
   632     aParentWorker->AssertIsOnWorkerThread();
   633     MOZ_ASSERT(aSyncLoopTarget);
   634   }
   636   NS_IMETHOD
   637   Run() MOZ_OVERRIDE
   638   {
   639     AssertIsOnMainThread();
   641     nsIPrincipal* principal = mParentWorker->GetPrincipal();
   642     NS_ASSERTION(principal, "This should never be null here!");
   644     // Figure out our base URI.
   645     nsCOMPtr<nsIURI> baseURI = mParentWorker->GetBaseURI();
   646     NS_ASSERTION(baseURI, "Should have been set already!");
   648     // May be null.
   649     nsCOMPtr<nsIDocument> parentDoc = mParentWorker->GetDocument();
   651     nsCOMPtr<nsIChannel> channel;
   652     mResult =
   653       scriptloader::ChannelFromScriptURLMainThread(principal, baseURI,
   654                                                    parentDoc, mScriptURL,
   655                                                    getter_AddRefs(channel));
   656     if (NS_SUCCEEDED(mResult)) {
   657       channel.forget(mChannel);
   658     }
   660     nsRefPtr<MainThreadStopSyncLoopRunnable> runnable =
   661       new MainThreadStopSyncLoopRunnable(mParentWorker,
   662                                          mSyncLoopTarget.forget(), true);
   663     if (!runnable->Dispatch(nullptr)) {
   664       NS_ERROR("This should never fail!");
   665     }
   667     return NS_OK;
   668   }
   670   nsresult
   671   GetResult() const
   672   {
   673     return mResult;
   674   }
   676 private:
   677   virtual ~ChannelGetterRunnable()
   678   { }
   679 };
   681 ScriptExecutorRunnable::ScriptExecutorRunnable(
   682                                             ScriptLoaderRunnable& aScriptLoader,
   683                                             nsIEventTarget* aSyncLoopTarget,
   684                                             uint32_t aFirstIndex,
   685                                             uint32_t aLastIndex)
   686 : MainThreadWorkerSyncRunnable(aScriptLoader.mWorkerPrivate, aSyncLoopTarget),
   687   mScriptLoader(aScriptLoader), mFirstIndex(aFirstIndex), mLastIndex(aLastIndex)
   688 {
   689   MOZ_ASSERT(aFirstIndex <= aLastIndex);
   690   MOZ_ASSERT(aLastIndex < aScriptLoader.mLoadInfos.Length());
   691 }
   693 bool
   694 ScriptExecutorRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
   695 {
   696   nsTArray<ScriptLoadInfo>& loadInfos = mScriptLoader.mLoadInfos;
   698   // Don't run if something else has already failed.
   699   for (uint32_t index = 0; index < mFirstIndex; index++) {
   700     ScriptLoadInfo& loadInfo = loadInfos.ElementAt(index);
   702     NS_ASSERTION(!loadInfo.mChannel, "Should no longer have a channel!");
   703     NS_ASSERTION(loadInfo.mExecutionScheduled, "Should be scheduled!");
   705     if (!loadInfo.mExecutionResult) {
   706       return true;
   707     }
   708   }
   710   JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
   711   NS_ASSERTION(global, "Must have a global by now!");
   713   // Determine whether we want to be discarding source on this global to save
   714   // memory. It would make more sense to do this when we create the global, but
   715   // the information behind UsesSystemPrincipal() et al isn't finalized until
   716   // the call to SetPrincipal during the first script load. After that, however,
   717   // it never changes. So we can just idempotently set the bits here.
   718   //
   719   // Note that we read a pref that is cached on the main thread. This is benignly
   720   // racey.
   721   if (xpc::ShouldDiscardSystemSource()) {
   722     bool discard = aWorkerPrivate->UsesSystemPrincipal() ||
   723                    aWorkerPrivate->IsInPrivilegedApp();
   724     JS::CompartmentOptionsRef(global).setDiscardSource(discard);
   725   }
   727   for (uint32_t index = mFirstIndex; index <= mLastIndex; index++) {
   728     ScriptLoadInfo& loadInfo = loadInfos.ElementAt(index);
   730     NS_ASSERTION(!loadInfo.mChannel, "Should no longer have a channel!");
   731     NS_ASSERTION(loadInfo.mExecutionScheduled, "Should be scheduled!");
   732     NS_ASSERTION(!loadInfo.mExecutionResult, "Should not have executed yet!");
   734     if (NS_FAILED(loadInfo.mLoadResult)) {
   735       scriptloader::ReportLoadError(aCx, loadInfo.mURL, loadInfo.mLoadResult,
   736                                     false);
   737       return true;
   738     }
   740     NS_ConvertUTF16toUTF8 filename(loadInfo.mURL);
   742     JS::CompileOptions options(aCx);
   743     options.setFileAndLine(filename.get(), 1);
   745     JS::SourceBufferHolder srcBuf(loadInfo.mScriptTextBuf,
   746                                   loadInfo.mScriptTextLength,
   747                                   JS::SourceBufferHolder::GiveOwnership);
   748     loadInfo.mScriptTextBuf = nullptr;
   749     loadInfo.mScriptTextLength = 0;
   751     if (!JS::Evaluate(aCx, global, options, srcBuf)) {
   752       return true;
   753     }
   755     loadInfo.mExecutionResult = true;
   756   }
   758   return true;
   759 }
   761 void
   762 ScriptExecutorRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
   763                                 bool aRunResult)
   764 {
   765   nsTArray<ScriptLoadInfo>& loadInfos = mScriptLoader.mLoadInfos;
   767   if (mLastIndex == loadInfos.Length() - 1) {
   768     // All done. If anything failed then return false.
   769     bool result = true;
   770     for (uint32_t index = 0; index < loadInfos.Length(); index++) {
   771       if (!loadInfos[index].mExecutionResult) {
   772         result = false;
   773         break;
   774       }
   775     }
   777     ShutdownScriptLoader(aCx, aWorkerPrivate, result);
   778   }
   779 }
   781 NS_IMETHODIMP
   782 ScriptExecutorRunnable::Cancel()
   783 {
   784   if (mLastIndex == mScriptLoader.mLoadInfos.Length() - 1) {
   785     ShutdownScriptLoader(mWorkerPrivate->GetJSContext(), mWorkerPrivate, false);
   786   }
   787   return MainThreadWorkerSyncRunnable::Cancel();
   788 }
   790 void
   791 ScriptExecutorRunnable::ShutdownScriptLoader(JSContext* aCx,
   792                                              WorkerPrivate* aWorkerPrivate,
   793                                              bool aResult)
   794 {
   795   aWorkerPrivate->RemoveFeature(aCx, &mScriptLoader);
   796   aWorkerPrivate->StopSyncLoop(mSyncLoopTarget, aResult);
   797 }
   799 bool
   800 LoadAllScripts(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
   801                nsTArray<ScriptLoadInfo>& aLoadInfos, bool aIsWorkerScript)
   802 {
   803   aWorkerPrivate->AssertIsOnWorkerThread();
   804   NS_ASSERTION(!aLoadInfos.IsEmpty(), "Bad arguments!");
   806   AutoSyncLoopHolder syncLoop(aWorkerPrivate);
   808   nsRefPtr<ScriptLoaderRunnable> loader =
   809     new ScriptLoaderRunnable(aWorkerPrivate, syncLoop.EventTarget(),
   810                              aLoadInfos, aIsWorkerScript);
   812   NS_ASSERTION(aLoadInfos.IsEmpty(), "Should have swapped!");
   814   if (!aWorkerPrivate->AddFeature(aCx, loader)) {
   815     return false;
   816   }
   818   if (NS_FAILED(NS_DispatchToMainThread(loader, NS_DISPATCH_NORMAL))) {
   819     NS_ERROR("Failed to dispatch!");
   821     aWorkerPrivate->RemoveFeature(aCx, loader);
   822     return false;
   823   }
   825   return syncLoop.Run();
   826 }
   828 } /* anonymous namespace */
   830 BEGIN_WORKERS_NAMESPACE
   832 namespace scriptloader {
   834 nsresult
   835 ChannelFromScriptURLMainThread(nsIPrincipal* aPrincipal,
   836                                nsIURI* aBaseURI,
   837                                nsIDocument* aParentDoc,
   838                                const nsAString& aScriptURL,
   839                                nsIChannel** aChannel)
   840 {
   841   AssertIsOnMainThread();
   843   nsCOMPtr<nsILoadGroup> loadGroup;
   844   if (aParentDoc) {
   845     loadGroup = aParentDoc->GetDocumentLoadGroup();
   846   }
   848   nsCOMPtr<nsIIOService> ios(do_GetIOService());
   850   nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
   851   NS_ASSERTION(secMan, "This should never be null!");
   853   return ChannelFromScriptURL(aPrincipal, aBaseURI, aParentDoc, loadGroup,
   854                               ios, secMan, aScriptURL, true, aChannel);
   855 }
   857 nsresult
   858 ChannelFromScriptURLWorkerThread(JSContext* aCx,
   859                                  WorkerPrivate* aParent,
   860                                  const nsAString& aScriptURL,
   861                                  nsIChannel** aChannel)
   862 {
   863   aParent->AssertIsOnWorkerThread();
   865   AutoSyncLoopHolder syncLoop(aParent);
   867   nsRefPtr<ChannelGetterRunnable> getter =
   868     new ChannelGetterRunnable(aParent, syncLoop.EventTarget(), aScriptURL,
   869                               aChannel);
   871   if (NS_FAILED(NS_DispatchToMainThread(getter, NS_DISPATCH_NORMAL))) {
   872     NS_ERROR("Failed to dispatch!");
   873     return NS_ERROR_FAILURE;
   874   }
   876   if (!syncLoop.Run()) {
   877     return NS_ERROR_FAILURE;
   878   }
   880   return getter->GetResult();
   881 }
   883 void ReportLoadError(JSContext* aCx, const nsAString& aURL,
   884                      nsresult aLoadResult, bool aIsMainThread)
   885 {
   886   NS_LossyConvertUTF16toASCII url(aURL);
   888   switch (aLoadResult) {
   889     case NS_BINDING_ABORTED:
   890       // Canceled, don't set an exception.
   891       break;
   893     case NS_ERROR_MALFORMED_URI:
   894       JS_ReportError(aCx, "Malformed script URI: %s", url.get());
   895       break;
   897     case NS_ERROR_FILE_NOT_FOUND:
   898     case NS_ERROR_NOT_AVAILABLE:
   899       JS_ReportError(aCx, "Script file not found: %s", url.get());
   900       break;
   902     case NS_ERROR_DOM_SECURITY_ERR:
   903     case NS_ERROR_DOM_SYNTAX_ERR:
   904       Throw(aCx, aLoadResult);
   905       break;
   907     default:
   908       JS_ReportError(aCx, "Failed to load script (nsresult = 0x%x)", aLoadResult);
   909   }
   910 }
   912 bool
   913 LoadWorkerScript(JSContext* aCx)
   914 {
   915   WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
   916   NS_ASSERTION(worker, "This should never be null!");
   918   nsTArray<ScriptLoadInfo> loadInfos;
   920   ScriptLoadInfo* info = loadInfos.AppendElement();
   921   info->mURL = worker->ScriptURL();
   923   return LoadAllScripts(aCx, worker, loadInfos, true);
   924 }
   926 void
   927 Load(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
   928      const Sequence<nsString>& aScriptURLs, ErrorResult& aRv)
   929 {
   930   const uint32_t urlCount = aScriptURLs.Length();
   932   if (!urlCount) {
   933     return;
   934   }
   936   if (urlCount > MAX_CONCURRENT_SCRIPTS) {
   937     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
   938     return;
   939   }
   941   nsTArray<ScriptLoadInfo> loadInfos;
   942   loadInfos.SetLength(urlCount);
   944   for (uint32_t index = 0; index < urlCount; index++) {
   945     loadInfos[index].mURL = aScriptURLs[index];
   946   }
   948   if (!LoadAllScripts(aCx, aWorkerPrivate, loadInfos, false)) {
   949     // LoadAllScripts can fail if we're shutting down.
   950     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
   951   }
   952 }
   954 } // namespace scriptloader
   956 END_WORKERS_NAMESPACE

mercurial