1 : /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Web Workers.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * The Mozilla Foundation.
19 : * Portions created by the Initial Developer are Copyright (C) 2011
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Ben Turner <bent.mozilla@gmail.com> (Original Author)
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either the GNU General Public License Version 2 or later (the "GPL"), or
27 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : #include "ScriptLoader.h"
40 :
41 : #include "nsIChannel.h"
42 : #include "nsIChannelPolicy.h"
43 : #include "nsIContentPolicy.h"
44 : #include "nsIContentSecurityPolicy.h"
45 : #include "nsIHttpChannel.h"
46 : #include "nsIIOService.h"
47 : #include "nsIProtocolHandler.h"
48 : #include "nsIScriptSecurityManager.h"
49 : #include "nsIStreamLoader.h"
50 : #include "nsIURI.h"
51 :
52 : #include "jsapi.h"
53 : #include "nsChannelPolicy.h"
54 : #include "nsContentErrors.h"
55 : #include "nsContentPolicyUtils.h"
56 : #include "nsContentUtils.h"
57 : #include "nsDocShellCID.h"
58 : #include "nsISupportsPrimitives.h"
59 : #include "nsNetError.h"
60 : #include "nsNetUtil.h"
61 : #include "nsScriptLoader.h"
62 : #include "nsStringGlue.h"
63 : #include "nsTArray.h"
64 : #include "nsThreadUtils.h"
65 : #include "nsXPCOM.h"
66 :
67 : #include "Principal.h"
68 : #include "WorkerFeature.h"
69 : #include "WorkerPrivate.h"
70 :
71 : #define MAX_CONCURRENT_SCRIPTS 1000
72 :
73 : USING_WORKERS_NAMESPACE
74 :
75 : namespace {
76 :
77 : class ScriptLoaderRunnable;
78 :
79 : struct ScriptLoadInfo
80 0 : {
81 0 : ScriptLoadInfo()
82 : : mLoadResult(NS_ERROR_NOT_INITIALIZED), mExecutionScheduled(false),
83 0 : mExecutionResult(false)
84 0 : { }
85 :
86 : bool
87 : ReadyToExecute()
88 : {
89 : return !mChannel && NS_SUCCEEDED(mLoadResult) && !mExecutionScheduled;
90 : }
91 :
92 : nsString mURL;
93 : nsCOMPtr<nsIChannel> mChannel;
94 : nsString mScriptText;
95 :
96 : nsresult mLoadResult;
97 : bool mExecutionScheduled;
98 : bool mExecutionResult;
99 : };
100 :
101 : class ScriptExecutorRunnable : public WorkerSyncRunnable
102 0 : {
103 : ScriptLoaderRunnable& mScriptLoader;
104 : PRUint32 mFirstIndex;
105 : PRUint32 mLastIndex;
106 :
107 : public:
108 : ScriptExecutorRunnable(ScriptLoaderRunnable& aScriptLoader,
109 : PRUint32 aSyncQueueKey, PRUint32 aFirstIndex,
110 : PRUint32 aLastIndex);
111 :
112 : bool
113 0 : PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
114 : {
115 0 : AssertIsOnMainThread();
116 0 : return true;
117 : }
118 :
119 : void
120 0 : PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
121 : bool aDispatchResult)
122 : {
123 0 : AssertIsOnMainThread();
124 0 : }
125 :
126 : bool
127 : WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate);
128 :
129 : void
130 : PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult);
131 : };
132 :
133 : class ScriptLoaderRunnable : public WorkerFeature,
134 : public nsIRunnable,
135 : public nsIStreamLoaderObserver
136 0 : {
137 : friend class ScriptExecutorRunnable;
138 :
139 : WorkerPrivate* mWorkerPrivate;
140 : PRUint32 mSyncQueueKey;
141 : nsTArray<ScriptLoadInfo> mLoadInfos;
142 : bool mIsWorkerScript;
143 : bool mCanceled;
144 : bool mCanceledMainThread;
145 :
146 : public:
147 : NS_DECL_ISUPPORTS
148 :
149 0 : ScriptLoaderRunnable(WorkerPrivate* aWorkerPrivate,
150 : PRUint32 aSyncQueueKey,
151 : nsTArray<ScriptLoadInfo>& aLoadInfos,
152 : bool aIsWorkerScript)
153 : : mWorkerPrivate(aWorkerPrivate), mSyncQueueKey(aSyncQueueKey),
154 : mIsWorkerScript(aIsWorkerScript), mCanceled(false),
155 0 : mCanceledMainThread(false)
156 : {
157 0 : aWorkerPrivate->AssertIsOnWorkerThread();
158 0 : NS_ASSERTION(!aIsWorkerScript || aLoadInfos.Length() == 1, "Bad args!");
159 :
160 0 : if (!mLoadInfos.SwapElements(aLoadInfos)) {
161 0 : NS_ERROR("This should never fail!");
162 : }
163 0 : }
164 :
165 : NS_IMETHOD
166 0 : Run()
167 : {
168 0 : AssertIsOnMainThread();
169 :
170 0 : if (NS_FAILED(RunInternal())) {
171 0 : CancelMainThread();
172 : }
173 :
174 0 : return NS_OK;
175 : }
176 :
177 : NS_IMETHOD
178 0 : OnStreamComplete(nsIStreamLoader* aLoader, nsISupports* aContext,
179 : nsresult aStatus, PRUint32 aStringLen,
180 : const PRUint8* aString)
181 : {
182 0 : AssertIsOnMainThread();
183 :
184 0 : nsCOMPtr<nsISupportsPRUint32> indexSupports(do_QueryInterface(aContext));
185 0 : NS_ASSERTION(indexSupports, "This should never fail!");
186 :
187 0 : PRUint32 index = PR_UINT32_MAX;
188 0 : if (NS_FAILED(indexSupports->GetData(&index)) ||
189 0 : index >= mLoadInfos.Length()) {
190 0 : NS_ERROR("Bad index!");
191 : }
192 :
193 0 : ScriptLoadInfo& loadInfo = mLoadInfos[index];
194 :
195 : loadInfo.mLoadResult = OnStreamCompleteInternal(aLoader, aContext, aStatus,
196 : aStringLen, aString,
197 0 : loadInfo);
198 :
199 0 : ExecuteFinishedScripts();
200 :
201 0 : return NS_OK;
202 : }
203 :
204 : bool
205 0 : Notify(JSContext* aCx, Status aStatus)
206 : {
207 0 : mWorkerPrivate->AssertIsOnWorkerThread();
208 :
209 0 : if (aStatus >= Terminating && !mCanceled) {
210 0 : mCanceled = true;
211 :
212 : nsCOMPtr<nsIRunnable> runnable =
213 0 : NS_NewRunnableMethod(this, &ScriptLoaderRunnable::CancelMainThread);
214 0 : NS_ASSERTION(runnable, "This should never fail!");
215 :
216 0 : if (NS_FAILED(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL))) {
217 0 : JS_ReportError(aCx, "Failed to cancel script loader!");
218 0 : return false;
219 : }
220 : }
221 :
222 0 : return true;
223 : }
224 :
225 : void
226 0 : CancelMainThread()
227 : {
228 0 : AssertIsOnMainThread();
229 :
230 0 : if (mCanceledMainThread) {
231 0 : return;
232 : }
233 :
234 0 : mCanceledMainThread = true;
235 :
236 : // Cancel all the channels that were already opened.
237 0 : for (PRUint32 index = 0; index < mLoadInfos.Length(); index++) {
238 0 : ScriptLoadInfo& loadInfo = mLoadInfos[index];
239 :
240 0 : if (loadInfo.mChannel &&
241 0 : NS_FAILED(loadInfo.mChannel->Cancel(NS_BINDING_ABORTED))) {
242 0 : NS_WARNING("Failed to cancel channel!");
243 0 : loadInfo.mChannel = nsnull;
244 0 : loadInfo.mLoadResult = NS_BINDING_ABORTED;
245 : }
246 : }
247 :
248 0 : ExecuteFinishedScripts();
249 : }
250 :
251 : nsresult
252 0 : RunInternal()
253 : {
254 0 : AssertIsOnMainThread();
255 :
256 0 : WorkerPrivate* parentWorker = mWorkerPrivate->GetParent();
257 :
258 : // Figure out which principal to use.
259 0 : nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
260 0 : if (!principal) {
261 0 : NS_ASSERTION(parentWorker, "Must have a principal!");
262 0 : NS_ASSERTION(mIsWorkerScript, "Must have a principal for importScripts!");
263 :
264 0 : principal = parentWorker->GetPrincipal();
265 : }
266 0 : NS_ASSERTION(principal, "This should never be null here!");
267 :
268 : // Figure out our base URI.
269 0 : nsCOMPtr<nsIURI> baseURI;
270 0 : if (mIsWorkerScript) {
271 0 : if (parentWorker) {
272 0 : baseURI = parentWorker->GetBaseURI();
273 0 : NS_ASSERTION(baseURI, "Should have been set already!");
274 : }
275 : else {
276 : // May be null.
277 0 : baseURI = mWorkerPrivate->GetBaseURI();
278 : }
279 : }
280 : else {
281 0 : baseURI = mWorkerPrivate->GetBaseURI();
282 0 : NS_ASSERTION(baseURI, "Should have been set already!");
283 : }
284 :
285 : // May be null.
286 0 : nsCOMPtr<nsIDocument> parentDoc = mWorkerPrivate->GetDocument();
287 :
288 : // All of these can potentially be null, but that should be ok. We'll either
289 : // succeed without them or fail below.
290 0 : nsCOMPtr<nsILoadGroup> loadGroup;
291 0 : if (parentDoc) {
292 0 : loadGroup = parentDoc->GetDocumentLoadGroup();
293 : }
294 :
295 0 : nsCOMPtr<nsIIOService> ios(do_GetIOService());
296 :
297 0 : nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
298 0 : NS_ASSERTION(secMan, "This should never be null!");
299 :
300 0 : for (PRUint32 index = 0; index < mLoadInfos.Length(); index++) {
301 0 : ScriptLoadInfo& loadInfo = mLoadInfos[index];
302 0 : nsresult& rv = loadInfo.mLoadResult;
303 :
304 0 : nsCOMPtr<nsIURI> uri;
305 0 : rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri),
306 : loadInfo.mURL, parentDoc,
307 0 : baseURI);
308 0 : if (NS_FAILED(rv)) {
309 0 : return rv;
310 : }
311 :
312 : // If we're part of a document then check the content load policy.
313 0 : if (parentDoc) {
314 0 : PRInt16 shouldLoad = nsIContentPolicy::ACCEPT;
315 : rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_SCRIPT, uri,
316 : principal, parentDoc,
317 0 : NS_LITERAL_CSTRING("text/javascript"),
318 : nsnull, &shouldLoad,
319 : nsContentUtils::GetContentPolicy(),
320 0 : secMan);
321 0 : if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
322 0 : if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) {
323 0 : return rv = NS_ERROR_CONTENT_BLOCKED;
324 : }
325 0 : return rv = NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
326 : }
327 : }
328 :
329 : // If this script loader is being used to make a new worker then we need
330 : // to do a same-origin check. Otherwise we need to clear the load with the
331 : // security manager.
332 0 : if (mIsWorkerScript) {
333 0 : nsCString scheme;
334 0 : rv = uri->GetScheme(scheme);
335 0 : NS_ENSURE_SUCCESS(rv, rv);
336 :
337 : // We exempt data URLs from the same origin check.
338 0 : if (!scheme.EqualsLiteral("data")) {
339 0 : rv = principal->CheckMayLoad(uri, false);
340 0 : NS_ENSURE_SUCCESS(rv, rv);
341 : }
342 : }
343 : else {
344 0 : rv = secMan->CheckLoadURIWithPrincipal(principal, uri, 0);
345 0 : NS_ENSURE_SUCCESS(rv, rv);
346 : }
347 :
348 : // We need to know which index we're on in OnStreamComplete so we know
349 : // where to put the result.
350 : nsCOMPtr<nsISupportsPRUint32> indexSupports =
351 0 : do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
352 0 : NS_ENSURE_SUCCESS(rv, rv);
353 :
354 0 : rv = indexSupports->SetData(index);
355 0 : NS_ENSURE_SUCCESS(rv, rv);
356 :
357 : // We don't care about progress so just use the simple stream loader for
358 : // OnStreamComplete notification only.
359 0 : nsCOMPtr<nsIStreamLoader> loader;
360 0 : rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
361 0 : NS_ENSURE_SUCCESS(rv, rv);
362 :
363 : // Get Content Security Policy from parent document to pass into channel.
364 0 : nsCOMPtr<nsIContentSecurityPolicy> csp;
365 0 : rv = principal->GetCsp(getter_AddRefs(csp));
366 0 : NS_ENSURE_SUCCESS(rv, rv);
367 :
368 0 : nsCOMPtr<nsIChannelPolicy> channelPolicy;
369 0 : if (csp) {
370 0 : channelPolicy = do_CreateInstance(NSCHANNELPOLICY_CONTRACTID, &rv);
371 0 : NS_ENSURE_SUCCESS(rv, rv);
372 :
373 0 : rv = channelPolicy->SetContentSecurityPolicy(csp);
374 0 : NS_ENSURE_SUCCESS(rv, rv);
375 :
376 0 : rv = channelPolicy->SetLoadType(nsIContentPolicy::TYPE_SCRIPT);
377 0 : NS_ENSURE_SUCCESS(rv, rv);
378 : }
379 :
380 0 : PRUint32 flags = nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI;
381 :
382 0 : nsCOMPtr<nsIChannel> channel;
383 0 : rv = NS_NewChannel(getter_AddRefs(channel), uri, ios, loadGroup, nsnull,
384 0 : flags, channelPolicy);
385 0 : NS_ENSURE_SUCCESS(rv, rv);
386 :
387 0 : rv = channel->AsyncOpen(loader, indexSupports);
388 0 : NS_ENSURE_SUCCESS(rv, rv);
389 :
390 0 : loadInfo.mChannel.swap(channel);
391 : }
392 :
393 0 : return NS_OK;
394 : }
395 :
396 : nsresult
397 0 : OnStreamCompleteInternal(nsIStreamLoader* aLoader, nsISupports* aContext,
398 : nsresult aStatus, PRUint32 aStringLen,
399 : const PRUint8* aString, ScriptLoadInfo& aLoadInfo)
400 : {
401 0 : AssertIsOnMainThread();
402 :
403 0 : if (!aLoadInfo.mChannel) {
404 0 : return NS_BINDING_ABORTED;
405 : }
406 :
407 0 : aLoadInfo.mChannel = nsnull;
408 :
409 0 : if (NS_FAILED(aStatus)) {
410 0 : return aStatus;
411 : }
412 :
413 0 : if (!aStringLen) {
414 0 : return NS_OK;
415 : }
416 :
417 0 : NS_ASSERTION(aString, "This should never be null!");
418 :
419 : // Make sure we're not seeing the result of a 404 or something by checking
420 : // the 'requestSucceeded' attribute on the http channel.
421 0 : nsCOMPtr<nsIRequest> request;
422 0 : nsresult rv = aLoader->GetRequest(getter_AddRefs(request));
423 0 : NS_ENSURE_SUCCESS(rv, rv);
424 :
425 0 : nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
426 0 : if (httpChannel) {
427 : bool requestSucceeded;
428 0 : rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
429 0 : NS_ENSURE_SUCCESS(rv, rv);
430 :
431 0 : if (!requestSucceeded) {
432 0 : return NS_ERROR_NOT_AVAILABLE;
433 : }
434 : }
435 :
436 : // May be null.
437 0 : nsIDocument* parentDoc = mWorkerPrivate->GetDocument();
438 :
439 : // Use the regular nsScriptLoader for this grunt work! Should be just fine
440 : // because we're running on the main thread.
441 : rv = nsScriptLoader::ConvertToUTF16(aLoadInfo.mChannel, aString, aStringLen,
442 0 : EmptyString(), parentDoc,
443 0 : aLoadInfo.mScriptText);
444 0 : if (NS_FAILED(rv)) {
445 0 : return rv;
446 : }
447 :
448 0 : if (aLoadInfo.mScriptText.IsEmpty()) {
449 0 : return NS_ERROR_FAILURE;
450 : }
451 :
452 0 : nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
453 0 : NS_ASSERTION(channel, "This should never fail!");
454 :
455 : // Figure out what we actually loaded.
456 0 : nsCOMPtr<nsIURI> finalURI;
457 0 : rv = NS_GetFinalChannelURI(channel, getter_AddRefs(finalURI));
458 0 : NS_ENSURE_SUCCESS(rv, rv);
459 :
460 0 : nsCString filename;
461 0 : rv = finalURI->GetSpec(filename);
462 0 : NS_ENSURE_SUCCESS(rv, rv);
463 :
464 0 : if (!filename.IsEmpty()) {
465 : // This will help callers figure out what their script url resolved to in
466 : // case of errors.
467 0 : aLoadInfo.mURL.Assign(NS_ConvertUTF8toUTF16(filename));
468 : }
469 :
470 : // Update the principal of the worker and its base URI if we just loaded the
471 : // worker's primary script.
472 0 : if (mIsWorkerScript) {
473 : // Take care of the base URI first.
474 0 : mWorkerPrivate->SetBaseURI(finalURI);
475 :
476 : // Now to figure out which principal to give this worker.
477 0 : WorkerPrivate* parent = mWorkerPrivate->GetParent();
478 :
479 0 : NS_ASSERTION(mWorkerPrivate->GetPrincipal() || parent,
480 : "Must have one of these!");
481 :
482 0 : nsCOMPtr<nsIPrincipal> loadPrincipal = mWorkerPrivate->GetPrincipal() ?
483 0 : mWorkerPrivate->GetPrincipal() :
484 0 : parent->GetPrincipal();
485 :
486 0 : nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
487 0 : NS_ASSERTION(ssm, "Should never be null!");
488 :
489 0 : nsCOMPtr<nsIPrincipal> channelPrincipal;
490 0 : rv = ssm->GetChannelPrincipal(channel, getter_AddRefs(channelPrincipal));
491 0 : NS_ENSURE_SUCCESS(rv, rv);
492 :
493 : // See if this is a resource URI. Since JSMs usually come from resource://
494 : // URIs we're currently considering all URIs with the URI_IS_UI_RESOURCE
495 : // flag as valid for creating privileged workers.
496 0 : if (!nsContentUtils::IsSystemPrincipal(channelPrincipal)) {
497 : bool isResource;
498 : rv = NS_URIChainHasFlags(finalURI,
499 : nsIProtocolHandler::URI_IS_UI_RESOURCE,
500 0 : &isResource);
501 0 : NS_ENSURE_SUCCESS(rv, rv);
502 :
503 0 : if (isResource) {
504 0 : rv = ssm->GetSystemPrincipal(getter_AddRefs(channelPrincipal));
505 0 : NS_ENSURE_SUCCESS(rv, rv);
506 : }
507 : }
508 :
509 : // If the load principal is the system principal then the channel
510 : // principal must also be the system principal (we do not allow chrome
511 : // code to create workers with non-chrome scripts). Otherwise this channel
512 : // principal must be same origin with the load principal (we check again
513 : // here in case redirects changed the location of the script).
514 0 : if (nsContentUtils::IsSystemPrincipal(loadPrincipal)) {
515 0 : if (!nsContentUtils::IsSystemPrincipal(channelPrincipal)) {
516 0 : return NS_ERROR_DOM_BAD_URI;
517 : }
518 : }
519 : else {
520 0 : nsCString scheme;
521 0 : rv = finalURI->GetScheme(scheme);
522 0 : NS_ENSURE_SUCCESS(rv, rv);
523 :
524 : // We exempt data urls again.
525 0 : if (!scheme.EqualsLiteral("data") &&
526 0 : NS_FAILED(loadPrincipal->CheckMayLoad(finalURI, false))) {
527 0 : return NS_ERROR_DOM_BAD_URI;
528 : }
529 : }
530 :
531 0 : mWorkerPrivate->SetPrincipal(channelPrincipal);
532 : }
533 :
534 0 : return NS_OK;
535 : }
536 :
537 : void
538 0 : ExecuteFinishedScripts()
539 : {
540 0 : PRUint32 firstIndex = PR_UINT32_MAX;
541 0 : PRUint32 lastIndex = PR_UINT32_MAX;
542 :
543 : // Find firstIndex based on whether mExecutionScheduled is unset.
544 0 : for (PRUint32 index = 0; index < mLoadInfos.Length(); index++) {
545 0 : if (!mLoadInfos[index].mExecutionScheduled) {
546 0 : firstIndex = index;
547 0 : break;
548 : }
549 : }
550 :
551 : // Find lastIndex based on whether mChannel is set, and update
552 : // mExecutionScheduled on the ones we're about to schedule.
553 0 : if (firstIndex != PR_UINT32_MAX) {
554 0 : for (PRUint32 index = firstIndex; index < mLoadInfos.Length(); index++) {
555 0 : ScriptLoadInfo& loadInfo = mLoadInfos[index];
556 :
557 : // If we still have a channel then the load is not complete.
558 0 : if (loadInfo.mChannel) {
559 0 : break;
560 : }
561 :
562 : // We can execute this one.
563 0 : loadInfo.mExecutionScheduled = true;
564 :
565 0 : lastIndex = index;
566 : }
567 : }
568 :
569 0 : if (firstIndex != PR_UINT32_MAX && lastIndex != PR_UINT32_MAX) {
570 : nsRefPtr<ScriptExecutorRunnable> runnable =
571 0 : new ScriptExecutorRunnable(*this, mSyncQueueKey, firstIndex, lastIndex);
572 0 : if (!runnable->Dispatch(nsnull)) {
573 0 : NS_ERROR("This should never fail!");
574 : }
575 : }
576 0 : }
577 : };
578 :
579 0 : NS_IMPL_THREADSAFE_ISUPPORTS2(ScriptLoaderRunnable, nsIRunnable,
580 : nsIStreamLoaderObserver)
581 :
582 0 : ScriptExecutorRunnable::ScriptExecutorRunnable(
583 : ScriptLoaderRunnable& aScriptLoader,
584 : PRUint32 aSyncQueueKey,
585 : PRUint32 aFirstIndex,
586 : PRUint32 aLastIndex)
587 : : WorkerSyncRunnable(aScriptLoader.mWorkerPrivate, aSyncQueueKey),
588 0 : mScriptLoader(aScriptLoader), mFirstIndex(aFirstIndex), mLastIndex(aLastIndex)
589 : {
590 0 : NS_ASSERTION(aFirstIndex <= aLastIndex, "Bad first index!");
591 0 : NS_ASSERTION(aLastIndex < aScriptLoader.mLoadInfos.Length(),
592 : "Bad last index!");
593 0 : }
594 :
595 : bool
596 0 : ScriptExecutorRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
597 : {
598 0 : nsTArray<ScriptLoadInfo>& loadInfos = mScriptLoader.mLoadInfos;
599 :
600 : // Don't run if something else has already failed.
601 0 : for (PRUint32 index = 0; index < mFirstIndex; index++) {
602 0 : ScriptLoadInfo& loadInfo = loadInfos.ElementAt(index);
603 :
604 0 : NS_ASSERTION(!loadInfo.mChannel, "Should no longer have a channel!");
605 0 : NS_ASSERTION(loadInfo.mExecutionScheduled, "Should be scheduled!");
606 :
607 0 : if (!loadInfo.mExecutionResult) {
608 0 : return true;
609 : }
610 : }
611 :
612 0 : JSObject* global = JS_GetGlobalObject(aCx);
613 0 : NS_ASSERTION(global, "Must have a global by now!");
614 :
615 0 : JSPrincipals* principal = GetWorkerPrincipal();
616 0 : NS_ASSERTION(principal, "This should never be null!");
617 :
618 0 : for (PRUint32 index = mFirstIndex; index <= mLastIndex; index++) {
619 0 : ScriptLoadInfo& loadInfo = loadInfos.ElementAt(index);
620 :
621 0 : NS_ASSERTION(!loadInfo.mChannel, "Should no longer have a channel!");
622 0 : NS_ASSERTION(loadInfo.mExecutionScheduled, "Should be scheduled!");
623 0 : NS_ASSERTION(!loadInfo.mExecutionResult, "Should not have executed yet!");
624 :
625 0 : if (NS_FAILED(loadInfo.mLoadResult)) {
626 0 : NS_ConvertUTF16toUTF8 url(loadInfo.mURL);
627 :
628 0 : switch (loadInfo.mLoadResult) {
629 : case NS_BINDING_ABORTED:
630 : // Canceled, don't set an exception.
631 0 : break;
632 :
633 : case NS_ERROR_MALFORMED_URI:
634 0 : JS_ReportError(aCx, "Malformed script URI: %s", url.get());
635 0 : break;
636 :
637 : case NS_ERROR_FILE_NOT_FOUND:
638 : case NS_ERROR_NOT_AVAILABLE:
639 0 : JS_ReportError(aCx, "Script file not found: %s", url.get());
640 0 : break;
641 :
642 : default:
643 : JS_ReportError(aCx, "Failed to load script: %s (nsresult = 0x%x)",
644 0 : url.get(), loadInfo.mLoadResult);
645 : }
646 0 : return true;
647 : }
648 :
649 0 : NS_ConvertUTF16toUTF8 filename(loadInfo.mURL);
650 :
651 0 : if (!JS_EvaluateUCScriptForPrincipals(aCx, global, principal,
652 : loadInfo.mScriptText.get(),
653 : loadInfo.mScriptText.Length(),
654 0 : filename.get(), 1, nsnull)) {
655 0 : return true;
656 : }
657 :
658 0 : loadInfo.mExecutionResult = true;
659 : }
660 :
661 0 : return true;
662 : }
663 :
664 : void
665 0 : ScriptExecutorRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
666 : bool aRunResult)
667 : {
668 0 : nsTArray<ScriptLoadInfo>& loadInfos = mScriptLoader.mLoadInfos;
669 :
670 0 : if (mLastIndex == loadInfos.Length() - 1) {
671 : // All done. If anything failed then return false.
672 0 : bool result = true;
673 0 : for (PRUint32 index = 0; index < loadInfos.Length(); index++) {
674 0 : if (!loadInfos[index].mExecutionResult) {
675 0 : result = false;
676 0 : break;
677 : }
678 : }
679 :
680 0 : aWorkerPrivate->RemoveFeature(aCx, &mScriptLoader);
681 0 : aWorkerPrivate->StopSyncLoop(mSyncQueueKey, result);
682 : }
683 0 : }
684 :
685 : bool
686 0 : LoadAllScripts(JSContext* aCx, WorkerPrivate* aWorkerPrivate,
687 : nsTArray<ScriptLoadInfo>& aLoadInfos, bool aIsWorkerScript)
688 : {
689 0 : aWorkerPrivate->AssertIsOnWorkerThread();
690 0 : NS_ASSERTION(!aLoadInfos.IsEmpty(), "Bad arguments!");
691 :
692 0 : PRUint32 syncQueueKey = aWorkerPrivate->CreateNewSyncLoop();
693 :
694 : nsRefPtr<ScriptLoaderRunnable> loader =
695 : new ScriptLoaderRunnable(aWorkerPrivate, syncQueueKey, aLoadInfos,
696 0 : aIsWorkerScript);
697 :
698 0 : NS_ASSERTION(aLoadInfos.IsEmpty(), "Should have swapped!");
699 :
700 0 : if (!aWorkerPrivate->AddFeature(aCx, loader)) {
701 0 : return false;
702 : }
703 :
704 0 : if (NS_FAILED(NS_DispatchToMainThread(loader, NS_DISPATCH_NORMAL))) {
705 0 : NS_ERROR("Failed to dispatch!");
706 :
707 0 : aWorkerPrivate->RemoveFeature(aCx, loader);
708 0 : return false;
709 : }
710 :
711 0 : return aWorkerPrivate->RunSyncLoop(aCx, syncQueueKey);
712 : }
713 :
714 : } /* anonymous namespace */
715 :
716 : BEGIN_WORKERS_NAMESPACE
717 :
718 : namespace scriptloader {
719 :
720 : bool
721 0 : LoadWorkerScript(JSContext* aCx)
722 : {
723 0 : WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
724 0 : NS_ASSERTION(worker, "This should never be null!");
725 :
726 0 : nsTArray<ScriptLoadInfo> loadInfos;
727 :
728 0 : ScriptLoadInfo* info = loadInfos.AppendElement();
729 0 : info->mURL = worker->ScriptURL();
730 :
731 0 : return LoadAllScripts(aCx, worker, loadInfos, true);
732 : }
733 :
734 : bool
735 0 : Load(JSContext* aCx, unsigned aURLCount, jsval* aURLs)
736 : {
737 0 : WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
738 0 : NS_ASSERTION(worker, "This should never be null!");
739 :
740 0 : if (!aURLCount) {
741 0 : return true;
742 : }
743 :
744 0 : if (aURLCount > MAX_CONCURRENT_SCRIPTS) {
745 : JS_ReportError(aCx, "Cannot load more than %d scripts at one time!",
746 0 : MAX_CONCURRENT_SCRIPTS);
747 0 : return false;
748 : }
749 :
750 0 : nsTArray<ScriptLoadInfo> loadInfos;
751 0 : loadInfos.SetLength(PRUint32(aURLCount));
752 :
753 0 : for (unsigned index = 0; index < aURLCount; index++) {
754 0 : JSString* str = JS_ValueToString(aCx, aURLs[index]);
755 0 : if (!str) {
756 0 : return false;
757 : }
758 :
759 : size_t length;
760 0 : const jschar* buffer = JS_GetStringCharsAndLength(aCx, str, &length);
761 0 : if (!buffer) {
762 0 : return false;
763 : }
764 :
765 0 : loadInfos[index].mURL.Assign(buffer, length);
766 : }
767 :
768 0 : return LoadAllScripts(aCx, worker, loadInfos, false);
769 : }
770 :
771 : } // namespace scriptloader
772 :
773 : END_WORKERS_NAMESPACE
|