1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/workers/ScriptLoader.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,956 @@ 1.4 +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "ScriptLoader.h" 1.10 + 1.11 +#include "nsIChannel.h" 1.12 +#include "nsIChannelPolicy.h" 1.13 +#include "nsIContentPolicy.h" 1.14 +#include "nsIContentSecurityPolicy.h" 1.15 +#include "nsIHttpChannel.h" 1.16 +#include "nsIIOService.h" 1.17 +#include "nsIProtocolHandler.h" 1.18 +#include "nsIScriptSecurityManager.h" 1.19 +#include "nsIStreamLoader.h" 1.20 +#include "nsIURI.h" 1.21 + 1.22 +#include "jsapi.h" 1.23 +#include "nsChannelPolicy.h" 1.24 +#include "nsError.h" 1.25 +#include "nsContentPolicyUtils.h" 1.26 +#include "nsContentUtils.h" 1.27 +#include "nsDocShellCID.h" 1.28 +#include "nsISupportsPrimitives.h" 1.29 +#include "nsNetUtil.h" 1.30 +#include "nsScriptLoader.h" 1.31 +#include "nsString.h" 1.32 +#include "nsTArray.h" 1.33 +#include "nsThreadUtils.h" 1.34 +#include "nsXPCOM.h" 1.35 +#include "xpcpublic.h" 1.36 + 1.37 +#include "mozilla/dom/Exceptions.h" 1.38 +#include "Principal.h" 1.39 +#include "WorkerFeature.h" 1.40 +#include "WorkerPrivate.h" 1.41 +#include "WorkerRunnable.h" 1.42 + 1.43 +#define MAX_CONCURRENT_SCRIPTS 1000 1.44 + 1.45 +USING_WORKERS_NAMESPACE 1.46 + 1.47 +using mozilla::dom::workers::exceptions::ThrowDOMExceptionForNSResult; 1.48 + 1.49 +namespace { 1.50 + 1.51 +nsresult 1.52 +ChannelFromScriptURL(nsIPrincipal* principal, 1.53 + nsIURI* baseURI, 1.54 + nsIDocument* parentDoc, 1.55 + nsILoadGroup* loadGroup, 1.56 + nsIIOService* ios, 1.57 + nsIScriptSecurityManager* secMan, 1.58 + const nsAString& aScriptURL, 1.59 + bool aIsWorkerScript, 1.60 + nsIChannel** aChannel) 1.61 +{ 1.62 + AssertIsOnMainThread(); 1.63 + 1.64 + nsresult rv; 1.65 + nsCOMPtr<nsIURI> uri; 1.66 + rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), 1.67 + aScriptURL, parentDoc, 1.68 + baseURI); 1.69 + if (NS_FAILED(rv)) { 1.70 + return NS_ERROR_DOM_SYNTAX_ERR; 1.71 + } 1.72 + 1.73 + // If we're part of a document then check the content load policy. 1.74 + if (parentDoc) { 1.75 + int16_t shouldLoad = nsIContentPolicy::ACCEPT; 1.76 + rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_SCRIPT, uri, 1.77 + principal, parentDoc, 1.78 + NS_LITERAL_CSTRING("text/javascript"), 1.79 + nullptr, &shouldLoad, 1.80 + nsContentUtils::GetContentPolicy(), 1.81 + secMan); 1.82 + if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) { 1.83 + if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) { 1.84 + return rv = NS_ERROR_CONTENT_BLOCKED; 1.85 + } 1.86 + return rv = NS_ERROR_CONTENT_BLOCKED_SHOW_ALT; 1.87 + } 1.88 + } 1.89 + 1.90 + // If this script loader is being used to make a new worker then we need 1.91 + // to do a same-origin check. Otherwise we need to clear the load with the 1.92 + // security manager. 1.93 + if (aIsWorkerScript) { 1.94 + nsCString scheme; 1.95 + rv = uri->GetScheme(scheme); 1.96 + NS_ENSURE_SUCCESS(rv, rv); 1.97 + 1.98 + // We pass true as the 3rd argument to checkMayLoad here. 1.99 + // This allows workers in sandboxed documents to load data URLs 1.100 + // (and other URLs that inherit their principal from their 1.101 + // creator.) 1.102 + rv = principal->CheckMayLoad(uri, false, true); 1.103 + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR); 1.104 + } 1.105 + else { 1.106 + rv = secMan->CheckLoadURIWithPrincipal(principal, uri, 0); 1.107 + NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SECURITY_ERR); 1.108 + } 1.109 + 1.110 + // Get Content Security Policy from parent document to pass into channel. 1.111 + nsCOMPtr<nsIContentSecurityPolicy> csp; 1.112 + rv = principal->GetCsp(getter_AddRefs(csp)); 1.113 + NS_ENSURE_SUCCESS(rv, rv); 1.114 + 1.115 + nsCOMPtr<nsIChannelPolicy> channelPolicy; 1.116 + if (csp) { 1.117 + channelPolicy = do_CreateInstance(NSCHANNELPOLICY_CONTRACTID, &rv); 1.118 + NS_ENSURE_SUCCESS(rv, rv); 1.119 + 1.120 + rv = channelPolicy->SetContentSecurityPolicy(csp); 1.121 + NS_ENSURE_SUCCESS(rv, rv); 1.122 + 1.123 + rv = channelPolicy->SetLoadType(nsIContentPolicy::TYPE_SCRIPT); 1.124 + NS_ENSURE_SUCCESS(rv, rv); 1.125 + } 1.126 + 1.127 + uint32_t flags = nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI; 1.128 + 1.129 + nsCOMPtr<nsIChannel> channel; 1.130 + rv = NS_NewChannel(getter_AddRefs(channel), uri, ios, loadGroup, nullptr, 1.131 + flags, channelPolicy); 1.132 + NS_ENSURE_SUCCESS(rv, rv); 1.133 + 1.134 + channel.forget(aChannel); 1.135 + return rv; 1.136 +} 1.137 + 1.138 +struct ScriptLoadInfo 1.139 +{ 1.140 + ScriptLoadInfo() 1.141 + : mScriptTextBuf(nullptr) 1.142 + , mScriptTextLength(0) 1.143 + , mLoadResult(NS_ERROR_NOT_INITIALIZED), mExecutionScheduled(false) 1.144 + , mExecutionResult(false) 1.145 + { } 1.146 + 1.147 + ~ScriptLoadInfo() 1.148 + { 1.149 + if (mScriptTextBuf) { 1.150 + js_free(mScriptTextBuf); 1.151 + } 1.152 + } 1.153 + 1.154 + bool 1.155 + ReadyToExecute() 1.156 + { 1.157 + return !mChannel && NS_SUCCEEDED(mLoadResult) && !mExecutionScheduled; 1.158 + } 1.159 + 1.160 + nsString mURL; 1.161 + nsCOMPtr<nsIChannel> mChannel; 1.162 + jschar* mScriptTextBuf; 1.163 + size_t mScriptTextLength; 1.164 + 1.165 + nsresult mLoadResult; 1.166 + bool mExecutionScheduled; 1.167 + bool mExecutionResult; 1.168 +}; 1.169 + 1.170 +class ScriptLoaderRunnable; 1.171 + 1.172 +class ScriptExecutorRunnable MOZ_FINAL : public MainThreadWorkerSyncRunnable 1.173 +{ 1.174 + ScriptLoaderRunnable& mScriptLoader; 1.175 + uint32_t mFirstIndex; 1.176 + uint32_t mLastIndex; 1.177 + 1.178 +public: 1.179 + ScriptExecutorRunnable(ScriptLoaderRunnable& aScriptLoader, 1.180 + nsIEventTarget* aSyncLoopTarget, uint32_t aFirstIndex, 1.181 + uint32_t aLastIndex); 1.182 + 1.183 +private: 1.184 + ~ScriptExecutorRunnable() 1.185 + { } 1.186 + 1.187 + virtual bool 1.188 + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE; 1.189 + 1.190 + virtual void 1.191 + PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult) 1.192 + MOZ_OVERRIDE; 1.193 + 1.194 + NS_DECL_NSICANCELABLERUNNABLE 1.195 + 1.196 + void 1.197 + ShutdownScriptLoader(JSContext* aCx, 1.198 + WorkerPrivate* aWorkerPrivate, 1.199 + bool aResult); 1.200 +}; 1.201 + 1.202 +class ScriptLoaderRunnable MOZ_FINAL : public WorkerFeature, 1.203 + public nsIRunnable, 1.204 + public nsIStreamLoaderObserver 1.205 +{ 1.206 + friend class ScriptExecutorRunnable; 1.207 + 1.208 + WorkerPrivate* mWorkerPrivate; 1.209 + nsCOMPtr<nsIEventTarget> mSyncLoopTarget; 1.210 + nsTArray<ScriptLoadInfo> mLoadInfos; 1.211 + bool mIsWorkerScript; 1.212 + bool mCanceled; 1.213 + bool mCanceledMainThread; 1.214 + 1.215 +public: 1.216 + NS_DECL_THREADSAFE_ISUPPORTS 1.217 + 1.218 + ScriptLoaderRunnable(WorkerPrivate* aWorkerPrivate, 1.219 + nsIEventTarget* aSyncLoopTarget, 1.220 + nsTArray<ScriptLoadInfo>& aLoadInfos, 1.221 + bool aIsWorkerScript) 1.222 + : mWorkerPrivate(aWorkerPrivate), mSyncLoopTarget(aSyncLoopTarget), 1.223 + mIsWorkerScript(aIsWorkerScript), mCanceled(false), 1.224 + mCanceledMainThread(false) 1.225 + { 1.226 + aWorkerPrivate->AssertIsOnWorkerThread(); 1.227 + MOZ_ASSERT(aSyncLoopTarget); 1.228 + MOZ_ASSERT_IF(aIsWorkerScript, aLoadInfos.Length() == 1); 1.229 + 1.230 + mLoadInfos.SwapElements(aLoadInfos); 1.231 + } 1.232 + 1.233 +private: 1.234 + ~ScriptLoaderRunnable() 1.235 + { } 1.236 + 1.237 + NS_IMETHOD 1.238 + Run() MOZ_OVERRIDE 1.239 + { 1.240 + AssertIsOnMainThread(); 1.241 + 1.242 + if (NS_FAILED(RunInternal())) { 1.243 + CancelMainThread(); 1.244 + } 1.245 + 1.246 + return NS_OK; 1.247 + } 1.248 + 1.249 + NS_IMETHOD 1.250 + OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext, 1.251 + nsresult aStatus, uint32_t aStringLen, 1.252 + const uint8_t* aString) MOZ_OVERRIDE 1.253 + { 1.254 + AssertIsOnMainThread(); 1.255 + 1.256 + nsCOMPtr<nsISupportsPRUint32> indexSupports(do_QueryInterface(aContext)); 1.257 + NS_ASSERTION(indexSupports, "This should never fail!"); 1.258 + 1.259 + uint32_t index = UINT32_MAX; 1.260 + if (NS_FAILED(indexSupports->GetData(&index)) || 1.261 + index >= mLoadInfos.Length()) { 1.262 + NS_ERROR("Bad index!"); 1.263 + } 1.264 + 1.265 + ScriptLoadInfo& loadInfo = mLoadInfos[index]; 1.266 + 1.267 + loadInfo.mLoadResult = OnStreamCompleteInternal(aLoader, aContext, aStatus, 1.268 + aStringLen, aString, 1.269 + loadInfo); 1.270 + 1.271 + ExecuteFinishedScripts(); 1.272 + 1.273 + return NS_OK; 1.274 + } 1.275 + 1.276 + virtual bool 1.277 + Notify(JSContext* aCx, Status aStatus) MOZ_OVERRIDE 1.278 + { 1.279 + mWorkerPrivate->AssertIsOnWorkerThread(); 1.280 + 1.281 + if (aStatus >= Terminating && !mCanceled) { 1.282 + mCanceled = true; 1.283 + 1.284 + nsCOMPtr<nsIRunnable> runnable = 1.285 + NS_NewRunnableMethod(this, &ScriptLoaderRunnable::CancelMainThread); 1.286 + NS_ASSERTION(runnable, "This should never fail!"); 1.287 + 1.288 + if (NS_FAILED(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL))) { 1.289 + JS_ReportError(aCx, "Failed to cancel script loader!"); 1.290 + return false; 1.291 + } 1.292 + } 1.293 + 1.294 + return true; 1.295 + } 1.296 + 1.297 + void 1.298 + CancelMainThread() 1.299 + { 1.300 + AssertIsOnMainThread(); 1.301 + 1.302 + if (mCanceledMainThread) { 1.303 + return; 1.304 + } 1.305 + 1.306 + mCanceledMainThread = true; 1.307 + 1.308 + // Cancel all the channels that were already opened. 1.309 + for (uint32_t index = 0; index < mLoadInfos.Length(); index++) { 1.310 + ScriptLoadInfo& loadInfo = mLoadInfos[index]; 1.311 + 1.312 + if (loadInfo.mChannel && 1.313 + NS_FAILED(loadInfo.mChannel->Cancel(NS_BINDING_ABORTED))) { 1.314 + NS_WARNING("Failed to cancel channel!"); 1.315 + loadInfo.mChannel = nullptr; 1.316 + loadInfo.mLoadResult = NS_BINDING_ABORTED; 1.317 + } 1.318 + } 1.319 + 1.320 + ExecuteFinishedScripts(); 1.321 + } 1.322 + 1.323 + nsresult 1.324 + RunInternal() 1.325 + { 1.326 + AssertIsOnMainThread(); 1.327 + 1.328 + WorkerPrivate* parentWorker = mWorkerPrivate->GetParent(); 1.329 + 1.330 + // Figure out which principal to use. 1.331 + nsIPrincipal* principal = mWorkerPrivate->GetPrincipal(); 1.332 + if (!principal) { 1.333 + NS_ASSERTION(parentWorker, "Must have a principal!"); 1.334 + NS_ASSERTION(mIsWorkerScript, "Must have a principal for importScripts!"); 1.335 + 1.336 + principal = parentWorker->GetPrincipal(); 1.337 + } 1.338 + NS_ASSERTION(principal, "This should never be null here!"); 1.339 + 1.340 + // Figure out our base URI. 1.341 + nsCOMPtr<nsIURI> baseURI; 1.342 + if (mIsWorkerScript) { 1.343 + if (parentWorker) { 1.344 + baseURI = parentWorker->GetBaseURI(); 1.345 + NS_ASSERTION(baseURI, "Should have been set already!"); 1.346 + } 1.347 + else { 1.348 + // May be null. 1.349 + baseURI = mWorkerPrivate->GetBaseURI(); 1.350 + } 1.351 + } 1.352 + else { 1.353 + baseURI = mWorkerPrivate->GetBaseURI(); 1.354 + NS_ASSERTION(baseURI, "Should have been set already!"); 1.355 + } 1.356 + 1.357 + // May be null. 1.358 + nsCOMPtr<nsIDocument> parentDoc = mWorkerPrivate->GetDocument(); 1.359 + 1.360 + nsCOMPtr<nsIChannel> channel; 1.361 + if (mIsWorkerScript) { 1.362 + // May be null. 1.363 + channel = mWorkerPrivate->ForgetWorkerChannel(); 1.364 + } 1.365 + 1.366 + // All of these can potentially be null, but that should be ok. We'll either 1.367 + // succeed without them or fail below. 1.368 + nsCOMPtr<nsILoadGroup> loadGroup; 1.369 + if (parentDoc) { 1.370 + loadGroup = parentDoc->GetDocumentLoadGroup(); 1.371 + } 1.372 + 1.373 + nsCOMPtr<nsIIOService> ios(do_GetIOService()); 1.374 + 1.375 + nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); 1.376 + NS_ASSERTION(secMan, "This should never be null!"); 1.377 + 1.378 + for (uint32_t index = 0; index < mLoadInfos.Length(); index++) { 1.379 + ScriptLoadInfo& loadInfo = mLoadInfos[index]; 1.380 + nsresult& rv = loadInfo.mLoadResult; 1.381 + 1.382 + if (!channel) { 1.383 + rv = ChannelFromScriptURL(principal, baseURI, parentDoc, loadGroup, ios, 1.384 + secMan, loadInfo.mURL, mIsWorkerScript, 1.385 + getter_AddRefs(channel)); 1.386 + if (NS_FAILED(rv)) { 1.387 + return rv; 1.388 + } 1.389 + } 1.390 + 1.391 + // We need to know which index we're on in OnStreamComplete so we know 1.392 + // where to put the result. 1.393 + nsCOMPtr<nsISupportsPRUint32> indexSupports = 1.394 + do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv); 1.395 + NS_ENSURE_SUCCESS(rv, rv); 1.396 + 1.397 + rv = indexSupports->SetData(index); 1.398 + NS_ENSURE_SUCCESS(rv, rv); 1.399 + 1.400 + // We don't care about progress so just use the simple stream loader for 1.401 + // OnStreamComplete notification only. 1.402 + nsCOMPtr<nsIStreamLoader> loader; 1.403 + rv = NS_NewStreamLoader(getter_AddRefs(loader), this); 1.404 + NS_ENSURE_SUCCESS(rv, rv); 1.405 + 1.406 + rv = channel->AsyncOpen(loader, indexSupports); 1.407 + NS_ENSURE_SUCCESS(rv, rv); 1.408 + 1.409 + loadInfo.mChannel.swap(channel); 1.410 + } 1.411 + 1.412 + return NS_OK; 1.413 + } 1.414 + 1.415 + nsresult 1.416 + OnStreamCompleteInternal(nsIStreamLoader* aLoader, nsISupports* aContext, 1.417 + nsresult aStatus, uint32_t aStringLen, 1.418 + const uint8_t* aString, ScriptLoadInfo& aLoadInfo) 1.419 + { 1.420 + AssertIsOnMainThread(); 1.421 + 1.422 + if (!aLoadInfo.mChannel) { 1.423 + return NS_BINDING_ABORTED; 1.424 + } 1.425 + 1.426 + aLoadInfo.mChannel = nullptr; 1.427 + 1.428 + if (NS_FAILED(aStatus)) { 1.429 + return aStatus; 1.430 + } 1.431 + 1.432 + if (!aStringLen) { 1.433 + return NS_OK; 1.434 + } 1.435 + 1.436 + NS_ASSERTION(aString, "This should never be null!"); 1.437 + 1.438 + // Make sure we're not seeing the result of a 404 or something by checking 1.439 + // the 'requestSucceeded' attribute on the http channel. 1.440 + nsCOMPtr<nsIRequest> request; 1.441 + nsresult rv = aLoader->GetRequest(getter_AddRefs(request)); 1.442 + NS_ENSURE_SUCCESS(rv, rv); 1.443 + 1.444 + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request); 1.445 + if (httpChannel) { 1.446 + bool requestSucceeded; 1.447 + rv = httpChannel->GetRequestSucceeded(&requestSucceeded); 1.448 + NS_ENSURE_SUCCESS(rv, rv); 1.449 + 1.450 + if (!requestSucceeded) { 1.451 + return NS_ERROR_NOT_AVAILABLE; 1.452 + } 1.453 + } 1.454 + 1.455 + // May be null. 1.456 + nsIDocument* parentDoc = mWorkerPrivate->GetDocument(); 1.457 + 1.458 + // Use the regular nsScriptLoader for this grunt work! Should be just fine 1.459 + // because we're running on the main thread. 1.460 + // Unlike <script> tags, Worker scripts are always decoded as UTF-8, 1.461 + // per spec. So we explicitly pass in the charset hint. 1.462 + rv = nsScriptLoader::ConvertToUTF16(aLoadInfo.mChannel, aString, aStringLen, 1.463 + NS_LITERAL_STRING("UTF-8"), parentDoc, 1.464 + aLoadInfo.mScriptTextBuf, 1.465 + aLoadInfo.mScriptTextLength); 1.466 + if (NS_FAILED(rv)) { 1.467 + return rv; 1.468 + } 1.469 + 1.470 + if (!aLoadInfo.mScriptTextBuf || !aLoadInfo.mScriptTextLength) { 1.471 + return NS_ERROR_FAILURE; 1.472 + } 1.473 + 1.474 + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); 1.475 + NS_ASSERTION(channel, "This should never fail!"); 1.476 + 1.477 + // Figure out what we actually loaded. 1.478 + nsCOMPtr<nsIURI> finalURI; 1.479 + rv = NS_GetFinalChannelURI(channel, getter_AddRefs(finalURI)); 1.480 + NS_ENSURE_SUCCESS(rv, rv); 1.481 + 1.482 + nsCString filename; 1.483 + rv = finalURI->GetSpec(filename); 1.484 + NS_ENSURE_SUCCESS(rv, rv); 1.485 + 1.486 + if (!filename.IsEmpty()) { 1.487 + // This will help callers figure out what their script url resolved to in 1.488 + // case of errors. 1.489 + aLoadInfo.mURL.Assign(NS_ConvertUTF8toUTF16(filename)); 1.490 + } 1.491 + 1.492 + // Update the principal of the worker and its base URI if we just loaded the 1.493 + // worker's primary script. 1.494 + if (mIsWorkerScript) { 1.495 + // Take care of the base URI first. 1.496 + mWorkerPrivate->SetBaseURI(finalURI); 1.497 + 1.498 + // Now to figure out which principal to give this worker. 1.499 + WorkerPrivate* parent = mWorkerPrivate->GetParent(); 1.500 + 1.501 + NS_ASSERTION(mWorkerPrivate->GetPrincipal() || parent, 1.502 + "Must have one of these!"); 1.503 + 1.504 + nsCOMPtr<nsIPrincipal> loadPrincipal = mWorkerPrivate->GetPrincipal() ? 1.505 + mWorkerPrivate->GetPrincipal() : 1.506 + parent->GetPrincipal(); 1.507 + 1.508 + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); 1.509 + NS_ASSERTION(ssm, "Should never be null!"); 1.510 + 1.511 + nsCOMPtr<nsIPrincipal> channelPrincipal; 1.512 + rv = ssm->GetChannelPrincipal(channel, getter_AddRefs(channelPrincipal)); 1.513 + NS_ENSURE_SUCCESS(rv, rv); 1.514 + 1.515 + // See if this is a resource URI. Since JSMs usually come from resource:// 1.516 + // URIs we're currently considering all URIs with the URI_IS_UI_RESOURCE 1.517 + // flag as valid for creating privileged workers. 1.518 + if (!nsContentUtils::IsSystemPrincipal(channelPrincipal)) { 1.519 + bool isResource; 1.520 + rv = NS_URIChainHasFlags(finalURI, 1.521 + nsIProtocolHandler::URI_IS_UI_RESOURCE, 1.522 + &isResource); 1.523 + NS_ENSURE_SUCCESS(rv, rv); 1.524 + 1.525 + if (isResource) { 1.526 + rv = ssm->GetSystemPrincipal(getter_AddRefs(channelPrincipal)); 1.527 + NS_ENSURE_SUCCESS(rv, rv); 1.528 + } 1.529 + } 1.530 + 1.531 + // If the load principal is the system principal then the channel 1.532 + // principal must also be the system principal (we do not allow chrome 1.533 + // code to create workers with non-chrome scripts). Otherwise this channel 1.534 + // principal must be same origin with the load principal (we check again 1.535 + // here in case redirects changed the location of the script). 1.536 + if (nsContentUtils::IsSystemPrincipal(loadPrincipal)) { 1.537 + if (!nsContentUtils::IsSystemPrincipal(channelPrincipal)) { 1.538 + return NS_ERROR_DOM_BAD_URI; 1.539 + } 1.540 + } 1.541 + else { 1.542 + nsCString scheme; 1.543 + rv = finalURI->GetScheme(scheme); 1.544 + NS_ENSURE_SUCCESS(rv, rv); 1.545 + 1.546 + // We exempt data urls and other URI's that inherit their 1.547 + // principal again. 1.548 + if (NS_FAILED(loadPrincipal->CheckMayLoad(finalURI, false, true))) { 1.549 + return NS_ERROR_DOM_BAD_URI; 1.550 + } 1.551 + } 1.552 + 1.553 + mWorkerPrivate->SetPrincipal(channelPrincipal); 1.554 + 1.555 + if (parent) { 1.556 + // XHR Params Allowed 1.557 + mWorkerPrivate->SetXHRParamsAllowed(parent->XHRParamsAllowed()); 1.558 + 1.559 + // Set Eval and ContentSecurityPolicy 1.560 + mWorkerPrivate->SetCSP(parent->GetCSP()); 1.561 + mWorkerPrivate->SetEvalAllowed(parent->IsEvalAllowed()); 1.562 + } 1.563 + } 1.564 + 1.565 + return NS_OK; 1.566 + } 1.567 + 1.568 + void 1.569 + ExecuteFinishedScripts() 1.570 + { 1.571 + AssertIsOnMainThread(); 1.572 + 1.573 + if (mIsWorkerScript) { 1.574 + mWorkerPrivate->WorkerScriptLoaded(); 1.575 + } 1.576 + 1.577 + uint32_t firstIndex = UINT32_MAX; 1.578 + uint32_t lastIndex = UINT32_MAX; 1.579 + 1.580 + // Find firstIndex based on whether mExecutionScheduled is unset. 1.581 + for (uint32_t index = 0; index < mLoadInfos.Length(); index++) { 1.582 + if (!mLoadInfos[index].mExecutionScheduled) { 1.583 + firstIndex = index; 1.584 + break; 1.585 + } 1.586 + } 1.587 + 1.588 + // Find lastIndex based on whether mChannel is set, and update 1.589 + // mExecutionScheduled on the ones we're about to schedule. 1.590 + if (firstIndex != UINT32_MAX) { 1.591 + for (uint32_t index = firstIndex; index < mLoadInfos.Length(); index++) { 1.592 + ScriptLoadInfo& loadInfo = mLoadInfos[index]; 1.593 + 1.594 + // If we still have a channel then the load is not complete. 1.595 + if (loadInfo.mChannel) { 1.596 + break; 1.597 + } 1.598 + 1.599 + // We can execute this one. 1.600 + loadInfo.mExecutionScheduled = true; 1.601 + 1.602 + lastIndex = index; 1.603 + } 1.604 + } 1.605 + 1.606 + if (firstIndex != UINT32_MAX && lastIndex != UINT32_MAX) { 1.607 + nsRefPtr<ScriptExecutorRunnable> runnable = 1.608 + new ScriptExecutorRunnable(*this, mSyncLoopTarget, firstIndex, 1.609 + lastIndex); 1.610 + if (!runnable->Dispatch(nullptr)) { 1.611 + MOZ_ASSERT(false, "This should never fail!"); 1.612 + } 1.613 + } 1.614 + } 1.615 +}; 1.616 + 1.617 +NS_IMPL_ISUPPORTS(ScriptLoaderRunnable, nsIRunnable, nsIStreamLoaderObserver) 1.618 + 1.619 +class ChannelGetterRunnable MOZ_FINAL : public nsRunnable 1.620 +{ 1.621 + WorkerPrivate* mParentWorker; 1.622 + nsCOMPtr<nsIEventTarget> mSyncLoopTarget; 1.623 + const nsAString& mScriptURL; 1.624 + nsIChannel** mChannel; 1.625 + nsresult mResult; 1.626 + 1.627 +public: 1.628 + ChannelGetterRunnable(WorkerPrivate* aParentWorker, 1.629 + nsIEventTarget* aSyncLoopTarget, 1.630 + const nsAString& aScriptURL, 1.631 + nsIChannel** aChannel) 1.632 + : mParentWorker(aParentWorker), mSyncLoopTarget(aSyncLoopTarget), 1.633 + mScriptURL(aScriptURL), mChannel(aChannel), mResult(NS_ERROR_FAILURE) 1.634 + { 1.635 + aParentWorker->AssertIsOnWorkerThread(); 1.636 + MOZ_ASSERT(aSyncLoopTarget); 1.637 + } 1.638 + 1.639 + NS_IMETHOD 1.640 + Run() MOZ_OVERRIDE 1.641 + { 1.642 + AssertIsOnMainThread(); 1.643 + 1.644 + nsIPrincipal* principal = mParentWorker->GetPrincipal(); 1.645 + NS_ASSERTION(principal, "This should never be null here!"); 1.646 + 1.647 + // Figure out our base URI. 1.648 + nsCOMPtr<nsIURI> baseURI = mParentWorker->GetBaseURI(); 1.649 + NS_ASSERTION(baseURI, "Should have been set already!"); 1.650 + 1.651 + // May be null. 1.652 + nsCOMPtr<nsIDocument> parentDoc = mParentWorker->GetDocument(); 1.653 + 1.654 + nsCOMPtr<nsIChannel> channel; 1.655 + mResult = 1.656 + scriptloader::ChannelFromScriptURLMainThread(principal, baseURI, 1.657 + parentDoc, mScriptURL, 1.658 + getter_AddRefs(channel)); 1.659 + if (NS_SUCCEEDED(mResult)) { 1.660 + channel.forget(mChannel); 1.661 + } 1.662 + 1.663 + nsRefPtr<MainThreadStopSyncLoopRunnable> runnable = 1.664 + new MainThreadStopSyncLoopRunnable(mParentWorker, 1.665 + mSyncLoopTarget.forget(), true); 1.666 + if (!runnable->Dispatch(nullptr)) { 1.667 + NS_ERROR("This should never fail!"); 1.668 + } 1.669 + 1.670 + return NS_OK; 1.671 + } 1.672 + 1.673 + nsresult 1.674 + GetResult() const 1.675 + { 1.676 + return mResult; 1.677 + } 1.678 + 1.679 +private: 1.680 + virtual ~ChannelGetterRunnable() 1.681 + { } 1.682 +}; 1.683 + 1.684 +ScriptExecutorRunnable::ScriptExecutorRunnable( 1.685 + ScriptLoaderRunnable& aScriptLoader, 1.686 + nsIEventTarget* aSyncLoopTarget, 1.687 + uint32_t aFirstIndex, 1.688 + uint32_t aLastIndex) 1.689 +: MainThreadWorkerSyncRunnable(aScriptLoader.mWorkerPrivate, aSyncLoopTarget), 1.690 + mScriptLoader(aScriptLoader), mFirstIndex(aFirstIndex), mLastIndex(aLastIndex) 1.691 +{ 1.692 + MOZ_ASSERT(aFirstIndex <= aLastIndex); 1.693 + MOZ_ASSERT(aLastIndex < aScriptLoader.mLoadInfos.Length()); 1.694 +} 1.695 + 1.696 +bool 1.697 +ScriptExecutorRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) 1.698 +{ 1.699 + nsTArray<ScriptLoadInfo>& loadInfos = mScriptLoader.mLoadInfos; 1.700 + 1.701 + // Don't run if something else has already failed. 1.702 + for (uint32_t index = 0; index < mFirstIndex; index++) { 1.703 + ScriptLoadInfo& loadInfo = loadInfos.ElementAt(index); 1.704 + 1.705 + NS_ASSERTION(!loadInfo.mChannel, "Should no longer have a channel!"); 1.706 + NS_ASSERTION(loadInfo.mExecutionScheduled, "Should be scheduled!"); 1.707 + 1.708 + if (!loadInfo.mExecutionResult) { 1.709 + return true; 1.710 + } 1.711 + } 1.712 + 1.713 + JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx)); 1.714 + NS_ASSERTION(global, "Must have a global by now!"); 1.715 + 1.716 + // Determine whether we want to be discarding source on this global to save 1.717 + // memory. It would make more sense to do this when we create the global, but 1.718 + // the information behind UsesSystemPrincipal() et al isn't finalized until 1.719 + // the call to SetPrincipal during the first script load. After that, however, 1.720 + // it never changes. So we can just idempotently set the bits here. 1.721 + // 1.722 + // Note that we read a pref that is cached on the main thread. This is benignly 1.723 + // racey. 1.724 + if (xpc::ShouldDiscardSystemSource()) { 1.725 + bool discard = aWorkerPrivate->UsesSystemPrincipal() || 1.726 + aWorkerPrivate->IsInPrivilegedApp(); 1.727 + JS::CompartmentOptionsRef(global).setDiscardSource(discard); 1.728 + } 1.729 + 1.730 + for (uint32_t index = mFirstIndex; index <= mLastIndex; index++) { 1.731 + ScriptLoadInfo& loadInfo = loadInfos.ElementAt(index); 1.732 + 1.733 + NS_ASSERTION(!loadInfo.mChannel, "Should no longer have a channel!"); 1.734 + NS_ASSERTION(loadInfo.mExecutionScheduled, "Should be scheduled!"); 1.735 + NS_ASSERTION(!loadInfo.mExecutionResult, "Should not have executed yet!"); 1.736 + 1.737 + if (NS_FAILED(loadInfo.mLoadResult)) { 1.738 + scriptloader::ReportLoadError(aCx, loadInfo.mURL, loadInfo.mLoadResult, 1.739 + false); 1.740 + return true; 1.741 + } 1.742 + 1.743 + NS_ConvertUTF16toUTF8 filename(loadInfo.mURL); 1.744 + 1.745 + JS::CompileOptions options(aCx); 1.746 + options.setFileAndLine(filename.get(), 1); 1.747 + 1.748 + JS::SourceBufferHolder srcBuf(loadInfo.mScriptTextBuf, 1.749 + loadInfo.mScriptTextLength, 1.750 + JS::SourceBufferHolder::GiveOwnership); 1.751 + loadInfo.mScriptTextBuf = nullptr; 1.752 + loadInfo.mScriptTextLength = 0; 1.753 + 1.754 + if (!JS::Evaluate(aCx, global, options, srcBuf)) { 1.755 + return true; 1.756 + } 1.757 + 1.758 + loadInfo.mExecutionResult = true; 1.759 + } 1.760 + 1.761 + return true; 1.762 +} 1.763 + 1.764 +void 1.765 +ScriptExecutorRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, 1.766 + bool aRunResult) 1.767 +{ 1.768 + nsTArray<ScriptLoadInfo>& loadInfos = mScriptLoader.mLoadInfos; 1.769 + 1.770 + if (mLastIndex == loadInfos.Length() - 1) { 1.771 + // All done. If anything failed then return false. 1.772 + bool result = true; 1.773 + for (uint32_t index = 0; index < loadInfos.Length(); index++) { 1.774 + if (!loadInfos[index].mExecutionResult) { 1.775 + result = false; 1.776 + break; 1.777 + } 1.778 + } 1.779 + 1.780 + ShutdownScriptLoader(aCx, aWorkerPrivate, result); 1.781 + } 1.782 +} 1.783 + 1.784 +NS_IMETHODIMP 1.785 +ScriptExecutorRunnable::Cancel() 1.786 +{ 1.787 + if (mLastIndex == mScriptLoader.mLoadInfos.Length() - 1) { 1.788 + ShutdownScriptLoader(mWorkerPrivate->GetJSContext(), mWorkerPrivate, false); 1.789 + } 1.790 + return MainThreadWorkerSyncRunnable::Cancel(); 1.791 +} 1.792 + 1.793 +void 1.794 +ScriptExecutorRunnable::ShutdownScriptLoader(JSContext* aCx, 1.795 + WorkerPrivate* aWorkerPrivate, 1.796 + bool aResult) 1.797 +{ 1.798 + aWorkerPrivate->RemoveFeature(aCx, &mScriptLoader); 1.799 + aWorkerPrivate->StopSyncLoop(mSyncLoopTarget, aResult); 1.800 +} 1.801 + 1.802 +bool 1.803 +LoadAllScripts(JSContext* aCx, WorkerPrivate* aWorkerPrivate, 1.804 + nsTArray<ScriptLoadInfo>& aLoadInfos, bool aIsWorkerScript) 1.805 +{ 1.806 + aWorkerPrivate->AssertIsOnWorkerThread(); 1.807 + NS_ASSERTION(!aLoadInfos.IsEmpty(), "Bad arguments!"); 1.808 + 1.809 + AutoSyncLoopHolder syncLoop(aWorkerPrivate); 1.810 + 1.811 + nsRefPtr<ScriptLoaderRunnable> loader = 1.812 + new ScriptLoaderRunnable(aWorkerPrivate, syncLoop.EventTarget(), 1.813 + aLoadInfos, aIsWorkerScript); 1.814 + 1.815 + NS_ASSERTION(aLoadInfos.IsEmpty(), "Should have swapped!"); 1.816 + 1.817 + if (!aWorkerPrivate->AddFeature(aCx, loader)) { 1.818 + return false; 1.819 + } 1.820 + 1.821 + if (NS_FAILED(NS_DispatchToMainThread(loader, NS_DISPATCH_NORMAL))) { 1.822 + NS_ERROR("Failed to dispatch!"); 1.823 + 1.824 + aWorkerPrivate->RemoveFeature(aCx, loader); 1.825 + return false; 1.826 + } 1.827 + 1.828 + return syncLoop.Run(); 1.829 +} 1.830 + 1.831 +} /* anonymous namespace */ 1.832 + 1.833 +BEGIN_WORKERS_NAMESPACE 1.834 + 1.835 +namespace scriptloader { 1.836 + 1.837 +nsresult 1.838 +ChannelFromScriptURLMainThread(nsIPrincipal* aPrincipal, 1.839 + nsIURI* aBaseURI, 1.840 + nsIDocument* aParentDoc, 1.841 + const nsAString& aScriptURL, 1.842 + nsIChannel** aChannel) 1.843 +{ 1.844 + AssertIsOnMainThread(); 1.845 + 1.846 + nsCOMPtr<nsILoadGroup> loadGroup; 1.847 + if (aParentDoc) { 1.848 + loadGroup = aParentDoc->GetDocumentLoadGroup(); 1.849 + } 1.850 + 1.851 + nsCOMPtr<nsIIOService> ios(do_GetIOService()); 1.852 + 1.853 + nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); 1.854 + NS_ASSERTION(secMan, "This should never be null!"); 1.855 + 1.856 + return ChannelFromScriptURL(aPrincipal, aBaseURI, aParentDoc, loadGroup, 1.857 + ios, secMan, aScriptURL, true, aChannel); 1.858 +} 1.859 + 1.860 +nsresult 1.861 +ChannelFromScriptURLWorkerThread(JSContext* aCx, 1.862 + WorkerPrivate* aParent, 1.863 + const nsAString& aScriptURL, 1.864 + nsIChannel** aChannel) 1.865 +{ 1.866 + aParent->AssertIsOnWorkerThread(); 1.867 + 1.868 + AutoSyncLoopHolder syncLoop(aParent); 1.869 + 1.870 + nsRefPtr<ChannelGetterRunnable> getter = 1.871 + new ChannelGetterRunnable(aParent, syncLoop.EventTarget(), aScriptURL, 1.872 + aChannel); 1.873 + 1.874 + if (NS_FAILED(NS_DispatchToMainThread(getter, NS_DISPATCH_NORMAL))) { 1.875 + NS_ERROR("Failed to dispatch!"); 1.876 + return NS_ERROR_FAILURE; 1.877 + } 1.878 + 1.879 + if (!syncLoop.Run()) { 1.880 + return NS_ERROR_FAILURE; 1.881 + } 1.882 + 1.883 + return getter->GetResult(); 1.884 +} 1.885 + 1.886 +void ReportLoadError(JSContext* aCx, const nsAString& aURL, 1.887 + nsresult aLoadResult, bool aIsMainThread) 1.888 +{ 1.889 + NS_LossyConvertUTF16toASCII url(aURL); 1.890 + 1.891 + switch (aLoadResult) { 1.892 + case NS_BINDING_ABORTED: 1.893 + // Canceled, don't set an exception. 1.894 + break; 1.895 + 1.896 + case NS_ERROR_MALFORMED_URI: 1.897 + JS_ReportError(aCx, "Malformed script URI: %s", url.get()); 1.898 + break; 1.899 + 1.900 + case NS_ERROR_FILE_NOT_FOUND: 1.901 + case NS_ERROR_NOT_AVAILABLE: 1.902 + JS_ReportError(aCx, "Script file not found: %s", url.get()); 1.903 + break; 1.904 + 1.905 + case NS_ERROR_DOM_SECURITY_ERR: 1.906 + case NS_ERROR_DOM_SYNTAX_ERR: 1.907 + Throw(aCx, aLoadResult); 1.908 + break; 1.909 + 1.910 + default: 1.911 + JS_ReportError(aCx, "Failed to load script (nsresult = 0x%x)", aLoadResult); 1.912 + } 1.913 +} 1.914 + 1.915 +bool 1.916 +LoadWorkerScript(JSContext* aCx) 1.917 +{ 1.918 + WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); 1.919 + NS_ASSERTION(worker, "This should never be null!"); 1.920 + 1.921 + nsTArray<ScriptLoadInfo> loadInfos; 1.922 + 1.923 + ScriptLoadInfo* info = loadInfos.AppendElement(); 1.924 + info->mURL = worker->ScriptURL(); 1.925 + 1.926 + return LoadAllScripts(aCx, worker, loadInfos, true); 1.927 +} 1.928 + 1.929 +void 1.930 +Load(JSContext* aCx, WorkerPrivate* aWorkerPrivate, 1.931 + const Sequence<nsString>& aScriptURLs, ErrorResult& aRv) 1.932 +{ 1.933 + const uint32_t urlCount = aScriptURLs.Length(); 1.934 + 1.935 + if (!urlCount) { 1.936 + return; 1.937 + } 1.938 + 1.939 + if (urlCount > MAX_CONCURRENT_SCRIPTS) { 1.940 + aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 1.941 + return; 1.942 + } 1.943 + 1.944 + nsTArray<ScriptLoadInfo> loadInfos; 1.945 + loadInfos.SetLength(urlCount); 1.946 + 1.947 + for (uint32_t index = 0; index < urlCount; index++) { 1.948 + loadInfos[index].mURL = aScriptURLs[index]; 1.949 + } 1.950 + 1.951 + if (!LoadAllScripts(aCx, aWorkerPrivate, loadInfos, false)) { 1.952 + // LoadAllScripts can fail if we're shutting down. 1.953 + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 1.954 + } 1.955 +} 1.956 + 1.957 +} // namespace scriptloader 1.958 + 1.959 +END_WORKERS_NAMESPACE