|
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/. */ |
|
5 |
|
6 #include "ScriptLoader.h" |
|
7 |
|
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" |
|
18 |
|
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" |
|
33 |
|
34 #include "mozilla/dom/Exceptions.h" |
|
35 #include "Principal.h" |
|
36 #include "WorkerFeature.h" |
|
37 #include "WorkerPrivate.h" |
|
38 #include "WorkerRunnable.h" |
|
39 |
|
40 #define MAX_CONCURRENT_SCRIPTS 1000 |
|
41 |
|
42 USING_WORKERS_NAMESPACE |
|
43 |
|
44 using mozilla::dom::workers::exceptions::ThrowDOMExceptionForNSResult; |
|
45 |
|
46 namespace { |
|
47 |
|
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(); |
|
60 |
|
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 } |
|
69 |
|
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 } |
|
86 |
|
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); |
|
94 |
|
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 } |
|
106 |
|
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); |
|
111 |
|
112 nsCOMPtr<nsIChannelPolicy> channelPolicy; |
|
113 if (csp) { |
|
114 channelPolicy = do_CreateInstance(NSCHANNELPOLICY_CONTRACTID, &rv); |
|
115 NS_ENSURE_SUCCESS(rv, rv); |
|
116 |
|
117 rv = channelPolicy->SetContentSecurityPolicy(csp); |
|
118 NS_ENSURE_SUCCESS(rv, rv); |
|
119 |
|
120 rv = channelPolicy->SetLoadType(nsIContentPolicy::TYPE_SCRIPT); |
|
121 NS_ENSURE_SUCCESS(rv, rv); |
|
122 } |
|
123 |
|
124 uint32_t flags = nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI; |
|
125 |
|
126 nsCOMPtr<nsIChannel> channel; |
|
127 rv = NS_NewChannel(getter_AddRefs(channel), uri, ios, loadGroup, nullptr, |
|
128 flags, channelPolicy); |
|
129 NS_ENSURE_SUCCESS(rv, rv); |
|
130 |
|
131 channel.forget(aChannel); |
|
132 return rv; |
|
133 } |
|
134 |
|
135 struct ScriptLoadInfo |
|
136 { |
|
137 ScriptLoadInfo() |
|
138 : mScriptTextBuf(nullptr) |
|
139 , mScriptTextLength(0) |
|
140 , mLoadResult(NS_ERROR_NOT_INITIALIZED), mExecutionScheduled(false) |
|
141 , mExecutionResult(false) |
|
142 { } |
|
143 |
|
144 ~ScriptLoadInfo() |
|
145 { |
|
146 if (mScriptTextBuf) { |
|
147 js_free(mScriptTextBuf); |
|
148 } |
|
149 } |
|
150 |
|
151 bool |
|
152 ReadyToExecute() |
|
153 { |
|
154 return !mChannel && NS_SUCCEEDED(mLoadResult) && !mExecutionScheduled; |
|
155 } |
|
156 |
|
157 nsString mURL; |
|
158 nsCOMPtr<nsIChannel> mChannel; |
|
159 jschar* mScriptTextBuf; |
|
160 size_t mScriptTextLength; |
|
161 |
|
162 nsresult mLoadResult; |
|
163 bool mExecutionScheduled; |
|
164 bool mExecutionResult; |
|
165 }; |
|
166 |
|
167 class ScriptLoaderRunnable; |
|
168 |
|
169 class ScriptExecutorRunnable MOZ_FINAL : public MainThreadWorkerSyncRunnable |
|
170 { |
|
171 ScriptLoaderRunnable& mScriptLoader; |
|
172 uint32_t mFirstIndex; |
|
173 uint32_t mLastIndex; |
|
174 |
|
175 public: |
|
176 ScriptExecutorRunnable(ScriptLoaderRunnable& aScriptLoader, |
|
177 nsIEventTarget* aSyncLoopTarget, uint32_t aFirstIndex, |
|
178 uint32_t aLastIndex); |
|
179 |
|
180 private: |
|
181 ~ScriptExecutorRunnable() |
|
182 { } |
|
183 |
|
184 virtual bool |
|
185 WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) MOZ_OVERRIDE; |
|
186 |
|
187 virtual void |
|
188 PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, bool aRunResult) |
|
189 MOZ_OVERRIDE; |
|
190 |
|
191 NS_DECL_NSICANCELABLERUNNABLE |
|
192 |
|
193 void |
|
194 ShutdownScriptLoader(JSContext* aCx, |
|
195 WorkerPrivate* aWorkerPrivate, |
|
196 bool aResult); |
|
197 }; |
|
198 |
|
199 class ScriptLoaderRunnable MOZ_FINAL : public WorkerFeature, |
|
200 public nsIRunnable, |
|
201 public nsIStreamLoaderObserver |
|
202 { |
|
203 friend class ScriptExecutorRunnable; |
|
204 |
|
205 WorkerPrivate* mWorkerPrivate; |
|
206 nsCOMPtr<nsIEventTarget> mSyncLoopTarget; |
|
207 nsTArray<ScriptLoadInfo> mLoadInfos; |
|
208 bool mIsWorkerScript; |
|
209 bool mCanceled; |
|
210 bool mCanceledMainThread; |
|
211 |
|
212 public: |
|
213 NS_DECL_THREADSAFE_ISUPPORTS |
|
214 |
|
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); |
|
226 |
|
227 mLoadInfos.SwapElements(aLoadInfos); |
|
228 } |
|
229 |
|
230 private: |
|
231 ~ScriptLoaderRunnable() |
|
232 { } |
|
233 |
|
234 NS_IMETHOD |
|
235 Run() MOZ_OVERRIDE |
|
236 { |
|
237 AssertIsOnMainThread(); |
|
238 |
|
239 if (NS_FAILED(RunInternal())) { |
|
240 CancelMainThread(); |
|
241 } |
|
242 |
|
243 return NS_OK; |
|
244 } |
|
245 |
|
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(); |
|
252 |
|
253 nsCOMPtr<nsISupportsPRUint32> indexSupports(do_QueryInterface(aContext)); |
|
254 NS_ASSERTION(indexSupports, "This should never fail!"); |
|
255 |
|
256 uint32_t index = UINT32_MAX; |
|
257 if (NS_FAILED(indexSupports->GetData(&index)) || |
|
258 index >= mLoadInfos.Length()) { |
|
259 NS_ERROR("Bad index!"); |
|
260 } |
|
261 |
|
262 ScriptLoadInfo& loadInfo = mLoadInfos[index]; |
|
263 |
|
264 loadInfo.mLoadResult = OnStreamCompleteInternal(aLoader, aContext, aStatus, |
|
265 aStringLen, aString, |
|
266 loadInfo); |
|
267 |
|
268 ExecuteFinishedScripts(); |
|
269 |
|
270 return NS_OK; |
|
271 } |
|
272 |
|
273 virtual bool |
|
274 Notify(JSContext* aCx, Status aStatus) MOZ_OVERRIDE |
|
275 { |
|
276 mWorkerPrivate->AssertIsOnWorkerThread(); |
|
277 |
|
278 if (aStatus >= Terminating && !mCanceled) { |
|
279 mCanceled = true; |
|
280 |
|
281 nsCOMPtr<nsIRunnable> runnable = |
|
282 NS_NewRunnableMethod(this, &ScriptLoaderRunnable::CancelMainThread); |
|
283 NS_ASSERTION(runnable, "This should never fail!"); |
|
284 |
|
285 if (NS_FAILED(NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL))) { |
|
286 JS_ReportError(aCx, "Failed to cancel script loader!"); |
|
287 return false; |
|
288 } |
|
289 } |
|
290 |
|
291 return true; |
|
292 } |
|
293 |
|
294 void |
|
295 CancelMainThread() |
|
296 { |
|
297 AssertIsOnMainThread(); |
|
298 |
|
299 if (mCanceledMainThread) { |
|
300 return; |
|
301 } |
|
302 |
|
303 mCanceledMainThread = true; |
|
304 |
|
305 // Cancel all the channels that were already opened. |
|
306 for (uint32_t index = 0; index < mLoadInfos.Length(); index++) { |
|
307 ScriptLoadInfo& loadInfo = mLoadInfos[index]; |
|
308 |
|
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 } |
|
316 |
|
317 ExecuteFinishedScripts(); |
|
318 } |
|
319 |
|
320 nsresult |
|
321 RunInternal() |
|
322 { |
|
323 AssertIsOnMainThread(); |
|
324 |
|
325 WorkerPrivate* parentWorker = mWorkerPrivate->GetParent(); |
|
326 |
|
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!"); |
|
332 |
|
333 principal = parentWorker->GetPrincipal(); |
|
334 } |
|
335 NS_ASSERTION(principal, "This should never be null here!"); |
|
336 |
|
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 } |
|
353 |
|
354 // May be null. |
|
355 nsCOMPtr<nsIDocument> parentDoc = mWorkerPrivate->GetDocument(); |
|
356 |
|
357 nsCOMPtr<nsIChannel> channel; |
|
358 if (mIsWorkerScript) { |
|
359 // May be null. |
|
360 channel = mWorkerPrivate->ForgetWorkerChannel(); |
|
361 } |
|
362 |
|
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 } |
|
369 |
|
370 nsCOMPtr<nsIIOService> ios(do_GetIOService()); |
|
371 |
|
372 nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); |
|
373 NS_ASSERTION(secMan, "This should never be null!"); |
|
374 |
|
375 for (uint32_t index = 0; index < mLoadInfos.Length(); index++) { |
|
376 ScriptLoadInfo& loadInfo = mLoadInfos[index]; |
|
377 nsresult& rv = loadInfo.mLoadResult; |
|
378 |
|
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 } |
|
387 |
|
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); |
|
393 |
|
394 rv = indexSupports->SetData(index); |
|
395 NS_ENSURE_SUCCESS(rv, rv); |
|
396 |
|
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); |
|
402 |
|
403 rv = channel->AsyncOpen(loader, indexSupports); |
|
404 NS_ENSURE_SUCCESS(rv, rv); |
|
405 |
|
406 loadInfo.mChannel.swap(channel); |
|
407 } |
|
408 |
|
409 return NS_OK; |
|
410 } |
|
411 |
|
412 nsresult |
|
413 OnStreamCompleteInternal(nsIStreamLoader* aLoader, nsISupports* aContext, |
|
414 nsresult aStatus, uint32_t aStringLen, |
|
415 const uint8_t* aString, ScriptLoadInfo& aLoadInfo) |
|
416 { |
|
417 AssertIsOnMainThread(); |
|
418 |
|
419 if (!aLoadInfo.mChannel) { |
|
420 return NS_BINDING_ABORTED; |
|
421 } |
|
422 |
|
423 aLoadInfo.mChannel = nullptr; |
|
424 |
|
425 if (NS_FAILED(aStatus)) { |
|
426 return aStatus; |
|
427 } |
|
428 |
|
429 if (!aStringLen) { |
|
430 return NS_OK; |
|
431 } |
|
432 |
|
433 NS_ASSERTION(aString, "This should never be null!"); |
|
434 |
|
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); |
|
440 |
|
441 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request); |
|
442 if (httpChannel) { |
|
443 bool requestSucceeded; |
|
444 rv = httpChannel->GetRequestSucceeded(&requestSucceeded); |
|
445 NS_ENSURE_SUCCESS(rv, rv); |
|
446 |
|
447 if (!requestSucceeded) { |
|
448 return NS_ERROR_NOT_AVAILABLE; |
|
449 } |
|
450 } |
|
451 |
|
452 // May be null. |
|
453 nsIDocument* parentDoc = mWorkerPrivate->GetDocument(); |
|
454 |
|
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 } |
|
466 |
|
467 if (!aLoadInfo.mScriptTextBuf || !aLoadInfo.mScriptTextLength) { |
|
468 return NS_ERROR_FAILURE; |
|
469 } |
|
470 |
|
471 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); |
|
472 NS_ASSERTION(channel, "This should never fail!"); |
|
473 |
|
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); |
|
478 |
|
479 nsCString filename; |
|
480 rv = finalURI->GetSpec(filename); |
|
481 NS_ENSURE_SUCCESS(rv, rv); |
|
482 |
|
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 } |
|
488 |
|
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); |
|
494 |
|
495 // Now to figure out which principal to give this worker. |
|
496 WorkerPrivate* parent = mWorkerPrivate->GetParent(); |
|
497 |
|
498 NS_ASSERTION(mWorkerPrivate->GetPrincipal() || parent, |
|
499 "Must have one of these!"); |
|
500 |
|
501 nsCOMPtr<nsIPrincipal> loadPrincipal = mWorkerPrivate->GetPrincipal() ? |
|
502 mWorkerPrivate->GetPrincipal() : |
|
503 parent->GetPrincipal(); |
|
504 |
|
505 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); |
|
506 NS_ASSERTION(ssm, "Should never be null!"); |
|
507 |
|
508 nsCOMPtr<nsIPrincipal> channelPrincipal; |
|
509 rv = ssm->GetChannelPrincipal(channel, getter_AddRefs(channelPrincipal)); |
|
510 NS_ENSURE_SUCCESS(rv, rv); |
|
511 |
|
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); |
|
521 |
|
522 if (isResource) { |
|
523 rv = ssm->GetSystemPrincipal(getter_AddRefs(channelPrincipal)); |
|
524 NS_ENSURE_SUCCESS(rv, rv); |
|
525 } |
|
526 } |
|
527 |
|
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); |
|
542 |
|
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 } |
|
549 |
|
550 mWorkerPrivate->SetPrincipal(channelPrincipal); |
|
551 |
|
552 if (parent) { |
|
553 // XHR Params Allowed |
|
554 mWorkerPrivate->SetXHRParamsAllowed(parent->XHRParamsAllowed()); |
|
555 |
|
556 // Set Eval and ContentSecurityPolicy |
|
557 mWorkerPrivate->SetCSP(parent->GetCSP()); |
|
558 mWorkerPrivate->SetEvalAllowed(parent->IsEvalAllowed()); |
|
559 } |
|
560 } |
|
561 |
|
562 return NS_OK; |
|
563 } |
|
564 |
|
565 void |
|
566 ExecuteFinishedScripts() |
|
567 { |
|
568 AssertIsOnMainThread(); |
|
569 |
|
570 if (mIsWorkerScript) { |
|
571 mWorkerPrivate->WorkerScriptLoaded(); |
|
572 } |
|
573 |
|
574 uint32_t firstIndex = UINT32_MAX; |
|
575 uint32_t lastIndex = UINT32_MAX; |
|
576 |
|
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 } |
|
584 |
|
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]; |
|
590 |
|
591 // If we still have a channel then the load is not complete. |
|
592 if (loadInfo.mChannel) { |
|
593 break; |
|
594 } |
|
595 |
|
596 // We can execute this one. |
|
597 loadInfo.mExecutionScheduled = true; |
|
598 |
|
599 lastIndex = index; |
|
600 } |
|
601 } |
|
602 |
|
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 }; |
|
613 |
|
614 NS_IMPL_ISUPPORTS(ScriptLoaderRunnable, nsIRunnable, nsIStreamLoaderObserver) |
|
615 |
|
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; |
|
623 |
|
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 } |
|
635 |
|
636 NS_IMETHOD |
|
637 Run() MOZ_OVERRIDE |
|
638 { |
|
639 AssertIsOnMainThread(); |
|
640 |
|
641 nsIPrincipal* principal = mParentWorker->GetPrincipal(); |
|
642 NS_ASSERTION(principal, "This should never be null here!"); |
|
643 |
|
644 // Figure out our base URI. |
|
645 nsCOMPtr<nsIURI> baseURI = mParentWorker->GetBaseURI(); |
|
646 NS_ASSERTION(baseURI, "Should have been set already!"); |
|
647 |
|
648 // May be null. |
|
649 nsCOMPtr<nsIDocument> parentDoc = mParentWorker->GetDocument(); |
|
650 |
|
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 } |
|
659 |
|
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 } |
|
666 |
|
667 return NS_OK; |
|
668 } |
|
669 |
|
670 nsresult |
|
671 GetResult() const |
|
672 { |
|
673 return mResult; |
|
674 } |
|
675 |
|
676 private: |
|
677 virtual ~ChannelGetterRunnable() |
|
678 { } |
|
679 }; |
|
680 |
|
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 } |
|
692 |
|
693 bool |
|
694 ScriptExecutorRunnable::WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) |
|
695 { |
|
696 nsTArray<ScriptLoadInfo>& loadInfos = mScriptLoader.mLoadInfos; |
|
697 |
|
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); |
|
701 |
|
702 NS_ASSERTION(!loadInfo.mChannel, "Should no longer have a channel!"); |
|
703 NS_ASSERTION(loadInfo.mExecutionScheduled, "Should be scheduled!"); |
|
704 |
|
705 if (!loadInfo.mExecutionResult) { |
|
706 return true; |
|
707 } |
|
708 } |
|
709 |
|
710 JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx)); |
|
711 NS_ASSERTION(global, "Must have a global by now!"); |
|
712 |
|
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 } |
|
726 |
|
727 for (uint32_t index = mFirstIndex; index <= mLastIndex; index++) { |
|
728 ScriptLoadInfo& loadInfo = loadInfos.ElementAt(index); |
|
729 |
|
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!"); |
|
733 |
|
734 if (NS_FAILED(loadInfo.mLoadResult)) { |
|
735 scriptloader::ReportLoadError(aCx, loadInfo.mURL, loadInfo.mLoadResult, |
|
736 false); |
|
737 return true; |
|
738 } |
|
739 |
|
740 NS_ConvertUTF16toUTF8 filename(loadInfo.mURL); |
|
741 |
|
742 JS::CompileOptions options(aCx); |
|
743 options.setFileAndLine(filename.get(), 1); |
|
744 |
|
745 JS::SourceBufferHolder srcBuf(loadInfo.mScriptTextBuf, |
|
746 loadInfo.mScriptTextLength, |
|
747 JS::SourceBufferHolder::GiveOwnership); |
|
748 loadInfo.mScriptTextBuf = nullptr; |
|
749 loadInfo.mScriptTextLength = 0; |
|
750 |
|
751 if (!JS::Evaluate(aCx, global, options, srcBuf)) { |
|
752 return true; |
|
753 } |
|
754 |
|
755 loadInfo.mExecutionResult = true; |
|
756 } |
|
757 |
|
758 return true; |
|
759 } |
|
760 |
|
761 void |
|
762 ScriptExecutorRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, |
|
763 bool aRunResult) |
|
764 { |
|
765 nsTArray<ScriptLoadInfo>& loadInfos = mScriptLoader.mLoadInfos; |
|
766 |
|
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 } |
|
776 |
|
777 ShutdownScriptLoader(aCx, aWorkerPrivate, result); |
|
778 } |
|
779 } |
|
780 |
|
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 } |
|
789 |
|
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 } |
|
798 |
|
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!"); |
|
805 |
|
806 AutoSyncLoopHolder syncLoop(aWorkerPrivate); |
|
807 |
|
808 nsRefPtr<ScriptLoaderRunnable> loader = |
|
809 new ScriptLoaderRunnable(aWorkerPrivate, syncLoop.EventTarget(), |
|
810 aLoadInfos, aIsWorkerScript); |
|
811 |
|
812 NS_ASSERTION(aLoadInfos.IsEmpty(), "Should have swapped!"); |
|
813 |
|
814 if (!aWorkerPrivate->AddFeature(aCx, loader)) { |
|
815 return false; |
|
816 } |
|
817 |
|
818 if (NS_FAILED(NS_DispatchToMainThread(loader, NS_DISPATCH_NORMAL))) { |
|
819 NS_ERROR("Failed to dispatch!"); |
|
820 |
|
821 aWorkerPrivate->RemoveFeature(aCx, loader); |
|
822 return false; |
|
823 } |
|
824 |
|
825 return syncLoop.Run(); |
|
826 } |
|
827 |
|
828 } /* anonymous namespace */ |
|
829 |
|
830 BEGIN_WORKERS_NAMESPACE |
|
831 |
|
832 namespace scriptloader { |
|
833 |
|
834 nsresult |
|
835 ChannelFromScriptURLMainThread(nsIPrincipal* aPrincipal, |
|
836 nsIURI* aBaseURI, |
|
837 nsIDocument* aParentDoc, |
|
838 const nsAString& aScriptURL, |
|
839 nsIChannel** aChannel) |
|
840 { |
|
841 AssertIsOnMainThread(); |
|
842 |
|
843 nsCOMPtr<nsILoadGroup> loadGroup; |
|
844 if (aParentDoc) { |
|
845 loadGroup = aParentDoc->GetDocumentLoadGroup(); |
|
846 } |
|
847 |
|
848 nsCOMPtr<nsIIOService> ios(do_GetIOService()); |
|
849 |
|
850 nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); |
|
851 NS_ASSERTION(secMan, "This should never be null!"); |
|
852 |
|
853 return ChannelFromScriptURL(aPrincipal, aBaseURI, aParentDoc, loadGroup, |
|
854 ios, secMan, aScriptURL, true, aChannel); |
|
855 } |
|
856 |
|
857 nsresult |
|
858 ChannelFromScriptURLWorkerThread(JSContext* aCx, |
|
859 WorkerPrivate* aParent, |
|
860 const nsAString& aScriptURL, |
|
861 nsIChannel** aChannel) |
|
862 { |
|
863 aParent->AssertIsOnWorkerThread(); |
|
864 |
|
865 AutoSyncLoopHolder syncLoop(aParent); |
|
866 |
|
867 nsRefPtr<ChannelGetterRunnable> getter = |
|
868 new ChannelGetterRunnable(aParent, syncLoop.EventTarget(), aScriptURL, |
|
869 aChannel); |
|
870 |
|
871 if (NS_FAILED(NS_DispatchToMainThread(getter, NS_DISPATCH_NORMAL))) { |
|
872 NS_ERROR("Failed to dispatch!"); |
|
873 return NS_ERROR_FAILURE; |
|
874 } |
|
875 |
|
876 if (!syncLoop.Run()) { |
|
877 return NS_ERROR_FAILURE; |
|
878 } |
|
879 |
|
880 return getter->GetResult(); |
|
881 } |
|
882 |
|
883 void ReportLoadError(JSContext* aCx, const nsAString& aURL, |
|
884 nsresult aLoadResult, bool aIsMainThread) |
|
885 { |
|
886 NS_LossyConvertUTF16toASCII url(aURL); |
|
887 |
|
888 switch (aLoadResult) { |
|
889 case NS_BINDING_ABORTED: |
|
890 // Canceled, don't set an exception. |
|
891 break; |
|
892 |
|
893 case NS_ERROR_MALFORMED_URI: |
|
894 JS_ReportError(aCx, "Malformed script URI: %s", url.get()); |
|
895 break; |
|
896 |
|
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; |
|
901 |
|
902 case NS_ERROR_DOM_SECURITY_ERR: |
|
903 case NS_ERROR_DOM_SYNTAX_ERR: |
|
904 Throw(aCx, aLoadResult); |
|
905 break; |
|
906 |
|
907 default: |
|
908 JS_ReportError(aCx, "Failed to load script (nsresult = 0x%x)", aLoadResult); |
|
909 } |
|
910 } |
|
911 |
|
912 bool |
|
913 LoadWorkerScript(JSContext* aCx) |
|
914 { |
|
915 WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx); |
|
916 NS_ASSERTION(worker, "This should never be null!"); |
|
917 |
|
918 nsTArray<ScriptLoadInfo> loadInfos; |
|
919 |
|
920 ScriptLoadInfo* info = loadInfos.AppendElement(); |
|
921 info->mURL = worker->ScriptURL(); |
|
922 |
|
923 return LoadAllScripts(aCx, worker, loadInfos, true); |
|
924 } |
|
925 |
|
926 void |
|
927 Load(JSContext* aCx, WorkerPrivate* aWorkerPrivate, |
|
928 const Sequence<nsString>& aScriptURLs, ErrorResult& aRv) |
|
929 { |
|
930 const uint32_t urlCount = aScriptURLs.Length(); |
|
931 |
|
932 if (!urlCount) { |
|
933 return; |
|
934 } |
|
935 |
|
936 if (urlCount > MAX_CONCURRENT_SCRIPTS) { |
|
937 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); |
|
938 return; |
|
939 } |
|
940 |
|
941 nsTArray<ScriptLoadInfo> loadInfos; |
|
942 loadInfos.SetLength(urlCount); |
|
943 |
|
944 for (uint32_t index = 0; index < urlCount; index++) { |
|
945 loadInfos[index].mURL = aScriptURLs[index]; |
|
946 } |
|
947 |
|
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 } |
|
953 |
|
954 } // namespace scriptloader |
|
955 |
|
956 END_WORKERS_NAMESPACE |