dom/workers/ScriptLoader.cpp

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

Integrate suggestion from review to improve consistency with existing code.

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

mercurial