Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
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