Thu, 15 Jan 2015 21:03:48 +0100
Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 // vim: ft=cpp tw=78 sw=2 et ts=2
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /*
8 * A class that handles loading and evaluation of <script> elements.
9 */
11 #include "nsScriptLoader.h"
13 #include "jsapi.h"
14 #include "jsfriendapi.h"
15 #include "nsIUnicodeDecoder.h"
16 #include "nsIContent.h"
17 #include "nsJSUtils.h"
18 #include "mozilla/dom/ScriptSettings.h"
19 #include "mozilla/dom/Element.h"
20 #include "nsGkAtoms.h"
21 #include "nsNetUtil.h"
22 #include "nsIJSRuntimeService.h"
23 #include "nsIScriptGlobalObject.h"
24 #include "nsIScriptContext.h"
25 #include "nsIScriptSecurityManager.h"
26 #include "nsIPrincipal.h"
27 #include "nsJSPrincipals.h"
28 #include "nsContentPolicyUtils.h"
29 #include "nsIHttpChannel.h"
30 #include "nsIHttpChannelInternal.h"
31 #include "nsITimedChannel.h"
32 #include "nsIScriptElement.h"
33 #include "nsIDOMHTMLScriptElement.h"
34 #include "nsIDocShell.h"
35 #include "nsContentUtils.h"
36 #include "nsCxPusher.h"
37 #include "nsUnicharUtils.h"
38 #include "nsAutoPtr.h"
39 #include "nsIXPConnect.h"
40 #include "nsError.h"
41 #include "nsThreadUtils.h"
42 #include "nsDocShellCID.h"
43 #include "nsIContentSecurityPolicy.h"
44 #include "prlog.h"
45 #include "nsIChannelPolicy.h"
46 #include "nsChannelPolicy.h"
47 #include "nsCRT.h"
48 #include "nsContentCreatorFunctions.h"
49 #include "nsCrossSiteListenerProxy.h"
50 #include "nsSandboxFlags.h"
51 #include "nsContentTypeParser.h"
52 #include "nsINetworkSeer.h"
53 #include "mozilla/dom/EncodingUtils.h"
55 #include "mozilla/CORSMode.h"
56 #include "mozilla/Attributes.h"
57 #include "mozilla/unused.h"
59 #ifdef PR_LOGGING
60 static PRLogModuleInfo* gCspPRLog;
61 #endif
63 using namespace mozilla;
64 using namespace mozilla::dom;
66 //////////////////////////////////////////////////////////////
67 // Per-request data structure
68 //////////////////////////////////////////////////////////////
70 class nsScriptLoadRequest MOZ_FINAL : public nsISupports {
71 public:
72 nsScriptLoadRequest(nsIScriptElement* aElement,
73 uint32_t aVersion,
74 CORSMode aCORSMode)
75 : mElement(aElement),
76 mLoading(true),
77 mIsInline(true),
78 mHasSourceMapURL(false),
79 mScriptTextBuf(nullptr),
80 mScriptTextLength(0),
81 mJSVersion(aVersion),
82 mLineNo(1),
83 mCORSMode(aCORSMode)
84 {
85 }
87 ~nsScriptLoadRequest()
88 {
89 if (mScriptTextBuf) {
90 js_free(mScriptTextBuf);
91 }
92 }
94 NS_DECL_THREADSAFE_ISUPPORTS
96 void FireScriptAvailable(nsresult aResult)
97 {
98 mElement->ScriptAvailable(aResult, mElement, mIsInline, mURI, mLineNo);
99 }
100 void FireScriptEvaluated(nsresult aResult)
101 {
102 mElement->ScriptEvaluated(aResult, mElement, mIsInline);
103 }
105 bool IsPreload()
106 {
107 return mElement == nullptr;
108 }
110 nsCOMPtr<nsIScriptElement> mElement;
111 bool mLoading; // Are we still waiting for a load to complete?
112 bool mIsInline; // Is the script inline or loaded?
113 bool mHasSourceMapURL; // Does the HTTP header have a source map url?
114 nsString mSourceMapURL; // Holds source map url for loaded scripts
115 jschar* mScriptTextBuf; // Holds script text for non-inline scripts. Don't
116 size_t mScriptTextLength; // use nsString so we can give ownership to jsapi.
117 uint32_t mJSVersion;
118 nsCOMPtr<nsIURI> mURI;
119 nsCOMPtr<nsIPrincipal> mOriginPrincipal;
120 nsAutoCString mURL; // Keep the URI's filename alive during off thread parsing.
121 int32_t mLineNo;
122 const CORSMode mCORSMode;
123 };
125 // The nsScriptLoadRequest is passed as the context to necko, and thus
126 // it needs to be threadsafe. Necko won't do anything with this
127 // context, but it will AddRef and Release it on other threads.
128 NS_IMPL_ISUPPORTS0(nsScriptLoadRequest)
130 //////////////////////////////////////////////////////////////
131 //
132 //////////////////////////////////////////////////////////////
134 nsScriptLoader::nsScriptLoader(nsIDocument *aDocument)
135 : mDocument(aDocument),
136 mBlockerCount(0),
137 mEnabled(true),
138 mDeferEnabled(false),
139 mDocumentParsingDone(false),
140 mBlockingDOMContentLoaded(false)
141 {
142 // enable logging for CSP
143 #ifdef PR_LOGGING
144 if (!gCspPRLog)
145 gCspPRLog = PR_NewLogModule("CSP");
146 #endif
147 }
149 nsScriptLoader::~nsScriptLoader()
150 {
151 mObservers.Clear();
153 if (mParserBlockingRequest) {
154 mParserBlockingRequest->FireScriptAvailable(NS_ERROR_ABORT);
155 }
157 for (uint32_t i = 0; i < mXSLTRequests.Length(); i++) {
158 mXSLTRequests[i]->FireScriptAvailable(NS_ERROR_ABORT);
159 }
161 for (uint32_t i = 0; i < mDeferRequests.Length(); i++) {
162 mDeferRequests[i]->FireScriptAvailable(NS_ERROR_ABORT);
163 }
165 for (uint32_t i = 0; i < mAsyncRequests.Length(); i++) {
166 mAsyncRequests[i]->FireScriptAvailable(NS_ERROR_ABORT);
167 }
169 for (uint32_t i = 0; i < mNonAsyncExternalScriptInsertedRequests.Length(); i++) {
170 mNonAsyncExternalScriptInsertedRequests[i]->FireScriptAvailable(NS_ERROR_ABORT);
171 }
173 // Unblock the kids, in case any of them moved to a different document
174 // subtree in the meantime and therefore aren't actually going away.
175 for (uint32_t j = 0; j < mPendingChildLoaders.Length(); ++j) {
176 mPendingChildLoaders[j]->RemoveExecuteBlocker();
177 }
178 }
180 NS_IMPL_ISUPPORTS(nsScriptLoader, nsIStreamLoaderObserver)
182 // Helper method for checking if the script element is an event-handler
183 // This means that it has both a for-attribute and a event-attribute.
184 // Also, if the for-attribute has a value that matches "\s*window\s*",
185 // and the event-attribute matches "\s*onload([ \(].*)?" then it isn't an
186 // eventhandler. (both matches are case insensitive).
187 // This is how IE seems to filter out a window's onload handler from a
188 // <script for=... event=...> element.
190 static bool
191 IsScriptEventHandler(nsIContent* aScriptElement)
192 {
193 if (!aScriptElement->IsHTML()) {
194 return false;
195 }
197 nsAutoString forAttr, eventAttr;
198 if (!aScriptElement->GetAttr(kNameSpaceID_None, nsGkAtoms::_for, forAttr) ||
199 !aScriptElement->GetAttr(kNameSpaceID_None, nsGkAtoms::event, eventAttr)) {
200 return false;
201 }
203 const nsAString& for_str =
204 nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(forAttr);
205 if (!for_str.LowerCaseEqualsLiteral("window")) {
206 return true;
207 }
209 // We found for="window", now check for event="onload".
210 const nsAString& event_str =
211 nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(eventAttr, false);
212 if (!StringBeginsWith(event_str, NS_LITERAL_STRING("onload"),
213 nsCaseInsensitiveStringComparator())) {
214 // It ain't "onload.*".
216 return true;
217 }
219 nsAutoString::const_iterator start, end;
220 event_str.BeginReading(start);
221 event_str.EndReading(end);
223 start.advance(6); // advance past "onload"
225 if (start != end && *start != '(' && *start != ' ') {
226 // We got onload followed by something other than space or
227 // '('. Not good enough.
229 return true;
230 }
232 return false;
233 }
235 nsresult
236 nsScriptLoader::CheckContentPolicy(nsIDocument* aDocument,
237 nsISupports *aContext,
238 nsIURI *aURI,
239 const nsAString &aType)
240 {
241 int16_t shouldLoad = nsIContentPolicy::ACCEPT;
242 nsresult rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_SCRIPT,
243 aURI,
244 aDocument->NodePrincipal(),
245 aContext,
246 NS_LossyConvertUTF16toASCII(aType),
247 nullptr, //extra
248 &shouldLoad,
249 nsContentUtils::GetContentPolicy(),
250 nsContentUtils::GetSecurityManager());
251 if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
252 if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) {
253 return NS_ERROR_CONTENT_BLOCKED;
254 }
255 return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
256 }
258 return NS_OK;
259 }
261 nsresult
262 nsScriptLoader::ShouldLoadScript(nsIDocument* aDocument,
263 nsISupports* aContext,
264 nsIURI* aURI,
265 const nsAString &aType)
266 {
267 // Check that the containing page is allowed to load this URI.
268 nsresult rv = nsContentUtils::GetSecurityManager()->
269 CheckLoadURIWithPrincipal(aDocument->NodePrincipal(), aURI,
270 nsIScriptSecurityManager::ALLOW_CHROME);
272 NS_ENSURE_SUCCESS(rv, rv);
274 // After the security manager, the content-policy stuff gets a veto
275 rv = CheckContentPolicy(aDocument, aContext, aURI, aType);
276 if (NS_FAILED(rv)) {
277 return rv;
278 }
280 return NS_OK;
281 }
283 nsresult
284 nsScriptLoader::StartLoad(nsScriptLoadRequest *aRequest, const nsAString &aType,
285 bool aScriptFromHead)
286 {
287 nsISupports *context = aRequest->mElement.get()
288 ? static_cast<nsISupports *>(aRequest->mElement.get())
289 : static_cast<nsISupports *>(mDocument);
290 nsresult rv = ShouldLoadScript(mDocument, context, aRequest->mURI, aType);
291 if (NS_FAILED(rv)) {
292 return rv;
293 }
295 nsCOMPtr<nsILoadGroup> loadGroup = mDocument->GetDocumentLoadGroup();
297 nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(mDocument->GetWindow()));
298 if (!window) {
299 return NS_ERROR_NULL_POINTER;
300 }
302 nsIDocShell *docshell = window->GetDocShell();
304 nsCOMPtr<nsIInterfaceRequestor> prompter(do_QueryInterface(docshell));
306 // If this document is sandboxed without 'allow-scripts', abort.
307 if (mDocument->GetSandboxFlags() & SANDBOXED_SCRIPTS) {
308 return NS_OK;
309 }
311 // check for a Content Security Policy to pass down to the channel
312 // that will be created to load the script
313 nsCOMPtr<nsIChannelPolicy> channelPolicy;
314 nsCOMPtr<nsIContentSecurityPolicy> csp;
315 rv = mDocument->NodePrincipal()->GetCsp(getter_AddRefs(csp));
316 NS_ENSURE_SUCCESS(rv, rv);
317 if (csp) {
318 channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1");
319 channelPolicy->SetContentSecurityPolicy(csp);
320 channelPolicy->SetLoadType(nsIContentPolicy::TYPE_SCRIPT);
321 }
323 nsCOMPtr<nsIChannel> channel;
324 rv = NS_NewChannel(getter_AddRefs(channel),
325 aRequest->mURI, nullptr, loadGroup, prompter,
326 nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI,
327 channelPolicy);
328 NS_ENSURE_SUCCESS(rv, rv);
330 nsIScriptElement *script = aRequest->mElement;
331 if (aScriptFromHead &&
332 !(script && (script->GetScriptAsync() || script->GetScriptDeferred()))) {
333 nsCOMPtr<nsIHttpChannelInternal>
334 internalHttpChannel(do_QueryInterface(channel));
335 if (internalHttpChannel)
336 internalHttpChannel->SetLoadAsBlocking(true);
337 }
339 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
340 if (httpChannel) {
341 // HTTP content negotation has little value in this context.
342 httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
343 NS_LITERAL_CSTRING("*/*"),
344 false);
345 httpChannel->SetReferrer(mDocument->GetDocumentURI());
346 }
348 nsCOMPtr<nsILoadContext> loadContext(do_QueryInterface(docshell));
349 mozilla::net::SeerLearn(aRequest->mURI, mDocument->GetDocumentURI(),
350 nsINetworkSeer::LEARN_LOAD_SUBRESOURCE, loadContext);
352 // Set the initiator type
353 nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
354 if (timedChannel) {
355 timedChannel->SetInitiatorType(NS_LITERAL_STRING("script"));
356 }
358 nsCOMPtr<nsIStreamLoader> loader;
359 rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
360 NS_ENSURE_SUCCESS(rv, rv);
362 nsCOMPtr<nsIStreamListener> listener = loader.get();
364 if (aRequest->mCORSMode != CORS_NONE) {
365 bool withCredentials = (aRequest->mCORSMode == CORS_USE_CREDENTIALS);
366 nsRefPtr<nsCORSListenerProxy> corsListener =
367 new nsCORSListenerProxy(listener, mDocument->NodePrincipal(),
368 withCredentials);
369 rv = corsListener->Init(channel);
370 NS_ENSURE_SUCCESS(rv, rv);
371 listener = corsListener;
372 }
374 rv = channel->AsyncOpen(listener, aRequest);
375 NS_ENSURE_SUCCESS(rv, rv);
377 return NS_OK;
378 }
380 bool
381 nsScriptLoader::PreloadURIComparator::Equals(const PreloadInfo &aPi,
382 nsIURI * const &aURI) const
383 {
384 bool same;
385 return NS_SUCCEEDED(aPi.mRequest->mURI->Equals(aURI, &same)) &&
386 same;
387 }
389 class nsScriptRequestProcessor : public nsRunnable
390 {
391 private:
392 nsRefPtr<nsScriptLoader> mLoader;
393 nsRefPtr<nsScriptLoadRequest> mRequest;
394 public:
395 nsScriptRequestProcessor(nsScriptLoader* aLoader,
396 nsScriptLoadRequest* aRequest)
397 : mLoader(aLoader)
398 , mRequest(aRequest)
399 {}
400 NS_IMETHODIMP Run()
401 {
402 return mLoader->ProcessRequest(mRequest);
403 }
404 };
406 static inline bool
407 ParseTypeAttribute(const nsAString& aType, JSVersion* aVersion)
408 {
409 MOZ_ASSERT(!aType.IsEmpty());
410 MOZ_ASSERT(aVersion);
411 MOZ_ASSERT(*aVersion == JSVERSION_DEFAULT);
413 nsContentTypeParser parser(aType);
415 nsAutoString mimeType;
416 nsresult rv = parser.GetType(mimeType);
417 NS_ENSURE_SUCCESS(rv, false);
419 if (!nsContentUtils::IsJavascriptMIMEType(mimeType)) {
420 return false;
421 }
423 // Get the version string, and ensure the language supports it.
424 nsAutoString versionName;
425 rv = parser.GetParameter("version", versionName);
427 if (NS_SUCCEEDED(rv)) {
428 *aVersion = nsContentUtils::ParseJavascriptVersion(versionName);
429 } else if (rv != NS_ERROR_INVALID_ARG) {
430 return false;
431 }
433 return true;
434 }
436 bool
437 CSPAllowsInlineScript(nsIScriptElement *aElement, nsIDocument *aDocument)
438 {
439 nsCOMPtr<nsIContentSecurityPolicy> csp;
440 nsresult rv = aDocument->NodePrincipal()->GetCsp(getter_AddRefs(csp));
441 NS_ENSURE_SUCCESS(rv, false);
443 if (!csp) {
444 // no CSP --> allow
445 return true;
446 }
448 // An inline script can be allowed because all inline scripts are allowed,
449 // or else because it is whitelisted by a nonce-source or hash-source. This
450 // is a logical OR between whitelisting methods, so the allowInlineScript
451 // outparam can be reused for each check as long as we stop checking as soon
452 // as it is set to true. This also optimizes performance by avoiding the
453 // overhead of unnecessary checks.
454 bool allowInlineScript = true;
455 nsAutoTArray<unsigned short, 3> violations;
457 bool reportInlineViolation = false;
458 rv = csp->GetAllowsInlineScript(&reportInlineViolation, &allowInlineScript);
459 NS_ENSURE_SUCCESS(rv, false);
460 if (reportInlineViolation) {
461 violations.AppendElement(static_cast<unsigned short>(
462 nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT));
463 }
465 nsAutoString nonce;
466 if (!allowInlineScript) {
467 nsCOMPtr<nsIContent> scriptContent = do_QueryInterface(aElement);
468 bool foundNonce = scriptContent->GetAttr(kNameSpaceID_None,
469 nsGkAtoms::nonce, nonce);
470 if (foundNonce) {
471 bool reportNonceViolation;
472 rv = csp->GetAllowsNonce(nonce, nsIContentPolicy::TYPE_SCRIPT,
473 &reportNonceViolation, &allowInlineScript);
474 NS_ENSURE_SUCCESS(rv, false);
475 if (reportNonceViolation) {
476 violations.AppendElement(static_cast<unsigned short>(
477 nsIContentSecurityPolicy::VIOLATION_TYPE_NONCE_SCRIPT));
478 }
479 }
480 }
482 if (!allowInlineScript) {
483 bool reportHashViolation;
484 nsAutoString scriptText;
485 aElement->GetScriptText(scriptText);
486 rv = csp->GetAllowsHash(scriptText, nsIContentPolicy::TYPE_SCRIPT,
487 &reportHashViolation, &allowInlineScript);
488 NS_ENSURE_SUCCESS(rv, false);
489 if (reportHashViolation) {
490 violations.AppendElement(static_cast<unsigned short>(
491 nsIContentSecurityPolicy::VIOLATION_TYPE_HASH_SCRIPT));
492 }
493 }
495 // What violation(s) should be reported?
496 //
497 // 1. If the script tag has a nonce attribute, and the nonce does not match
498 // the policy, report VIOLATION_TYPE_NONCE_SCRIPT.
499 // 2. If the policy has at least one hash-source, and the hashed contents of
500 // the script tag did not match any of them, report VIOLATION_TYPE_HASH_SCRIPT
501 // 3. Otherwise, report VIOLATION_TYPE_INLINE_SCRIPT if appropriate.
502 //
503 // 1 and 2 may occur together, 3 should only occur by itself. Naturally,
504 // every VIOLATION_TYPE_NONCE_SCRIPT and VIOLATION_TYPE_HASH_SCRIPT are also
505 // VIOLATION_TYPE_INLINE_SCRIPT, but reporting the
506 // VIOLATION_TYPE_INLINE_SCRIPT is redundant and does not help the developer.
507 if (!violations.IsEmpty()) {
508 MOZ_ASSERT(violations[0] == nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT,
509 "How did we get any violations without an initial inline script violation?");
510 // gather information to log with violation report
511 nsIURI* uri = aDocument->GetDocumentURI();
512 nsAutoCString asciiSpec;
513 uri->GetAsciiSpec(asciiSpec);
514 nsAutoString scriptText;
515 aElement->GetScriptText(scriptText);
516 nsAutoString scriptSample(scriptText);
518 // cap the length of the script sample at 40 chars
519 if (scriptSample.Length() > 40) {
520 scriptSample.Truncate(40);
521 scriptSample.AppendLiteral("...");
522 }
524 for (uint32_t i = 0; i < violations.Length(); i++) {
525 // Skip reporting the redundant inline script violation if there are
526 // other (nonce and/or hash violations) as well.
527 if (i > 0 || violations.Length() == 1) {
528 csp->LogViolationDetails(violations[i], NS_ConvertUTF8toUTF16(asciiSpec),
529 scriptSample, aElement->GetScriptLineNumber(),
530 nonce, scriptText);
531 }
532 }
533 }
535 if (!allowInlineScript) {
536 NS_ASSERTION(!violations.IsEmpty(),
537 "CSP blocked inline script but is not reporting a violation");
538 return false;
539 }
540 return true;
541 }
543 bool
544 nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
545 {
546 // We need a document to evaluate scripts.
547 NS_ENSURE_TRUE(mDocument, false);
549 // Check to see if scripts has been turned off.
550 if (!mEnabled || !mDocument->IsScriptEnabled()) {
551 return false;
552 }
554 NS_ASSERTION(!aElement->IsMalformed(), "Executing malformed script");
556 nsCOMPtr<nsIContent> scriptContent = do_QueryInterface(aElement);
558 // Step 12. Check that the script is not an eventhandler
559 if (IsScriptEventHandler(scriptContent)) {
560 return false;
561 }
563 JSVersion version = JSVERSION_DEFAULT;
565 // Check the type attribute to determine language and version.
566 // If type exists, it trumps the deprecated 'language='
567 nsAutoString type;
568 aElement->GetScriptType(type);
569 if (!type.IsEmpty()) {
570 NS_ENSURE_TRUE(ParseTypeAttribute(type, &version), false);
571 } else {
572 // no 'type=' element
573 // "language" is a deprecated attribute of HTML, so we check it only for
574 // HTML script elements.
575 if (scriptContent->IsHTML()) {
576 nsAutoString language;
577 scriptContent->GetAttr(kNameSpaceID_None, nsGkAtoms::language, language);
578 if (!language.IsEmpty()) {
579 if (!nsContentUtils::IsJavaScriptLanguage(language)) {
580 return false;
581 }
582 }
583 }
584 }
586 // Step 14. in the HTML5 spec
587 nsresult rv = NS_OK;
588 nsRefPtr<nsScriptLoadRequest> request;
589 if (aElement->GetScriptExternal()) {
590 // external script
591 nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI();
592 if (!scriptURI) {
593 // Asynchronously report the failure to create a URI object
594 NS_DispatchToCurrentThread(
595 NS_NewRunnableMethod(aElement,
596 &nsIScriptElement::FireErrorEvent));
597 return false;
598 }
599 CORSMode ourCORSMode = aElement->GetCORSMode();
600 nsTArray<PreloadInfo>::index_type i =
601 mPreloads.IndexOf(scriptURI.get(), 0, PreloadURIComparator());
602 if (i != nsTArray<PreloadInfo>::NoIndex) {
603 // preloaded
604 // note that a script-inserted script can steal a preload!
605 request = mPreloads[i].mRequest;
606 request->mElement = aElement;
607 nsString preloadCharset(mPreloads[i].mCharset);
608 mPreloads.RemoveElementAt(i);
610 // Double-check that the charset the preload used is the same as
611 // the charset we have now.
612 nsAutoString elementCharset;
613 aElement->GetScriptCharset(elementCharset);
614 if (elementCharset.Equals(preloadCharset) &&
615 ourCORSMode == request->mCORSMode) {
616 rv = CheckContentPolicy(mDocument, aElement, request->mURI, type);
617 NS_ENSURE_SUCCESS(rv, false);
618 } else {
619 // Drop the preload
620 request = nullptr;
621 }
622 }
624 if (!request) {
625 // no usable preload
626 request = new nsScriptLoadRequest(aElement, version, ourCORSMode);
627 request->mURI = scriptURI;
628 request->mIsInline = false;
629 request->mLoading = true;
631 // set aScriptFromHead to false so we don't treat non preloaded scripts as
632 // blockers for full page load. See bug 792438.
633 rv = StartLoad(request, type, false);
634 if (NS_FAILED(rv)) {
635 // Asynchronously report the load failure
636 NS_DispatchToCurrentThread(
637 NS_NewRunnableMethod(aElement,
638 &nsIScriptElement::FireErrorEvent));
639 return false;
640 }
641 }
643 request->mJSVersion = version;
645 if (aElement->GetScriptAsync()) {
646 mAsyncRequests.AppendElement(request);
647 if (!request->mLoading) {
648 // The script is available already. Run it ASAP when the event
649 // loop gets a chance to spin.
650 ProcessPendingRequestsAsync();
651 }
652 return false;
653 }
654 if (!aElement->GetParserCreated()) {
655 // Violate the HTML5 spec in order to make LABjs and the "order" plug-in
656 // for RequireJS work with their Gecko-sniffed code path. See
657 // http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html
658 mNonAsyncExternalScriptInsertedRequests.AppendElement(request);
659 if (!request->mLoading) {
660 // The script is available already. Run it ASAP when the event
661 // loop gets a chance to spin.
662 ProcessPendingRequestsAsync();
663 }
664 return false;
665 }
666 // we now have a parser-inserted request that may or may not be still
667 // loading
668 if (aElement->GetScriptDeferred()) {
669 // We don't want to run this yet.
670 // If we come here, the script is a parser-created script and it has
671 // the defer attribute but not the async attribute. Since a
672 // a parser-inserted script is being run, we came here by the parser
673 // running the script, which means the parser is still alive and the
674 // parse is ongoing.
675 NS_ASSERTION(mDocument->GetCurrentContentSink() ||
676 aElement->GetParserCreated() == FROM_PARSER_XSLT,
677 "Non-XSLT Defer script on a document without an active parser; bug 592366.");
678 AddDeferRequest(request);
679 return false;
680 }
682 if (aElement->GetParserCreated() == FROM_PARSER_XSLT) {
683 // Need to maintain order for XSLT-inserted scripts
684 NS_ASSERTION(!mParserBlockingRequest,
685 "Parser-blocking scripts and XSLT scripts in the same doc!");
686 mXSLTRequests.AppendElement(request);
687 if (!request->mLoading) {
688 // The script is available already. Run it ASAP when the event
689 // loop gets a chance to spin.
690 ProcessPendingRequestsAsync();
691 }
692 return true;
693 }
694 if (!request->mLoading && ReadyToExecuteScripts()) {
695 // The request has already been loaded and there are no pending style
696 // sheets. If the script comes from the network stream, cheat for
697 // performance reasons and avoid a trip through the event loop.
698 if (aElement->GetParserCreated() == FROM_PARSER_NETWORK) {
699 return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK;
700 }
701 // Otherwise, we've got a document.written script, make a trip through
702 // the event loop to hide the preload effects from the scripts on the
703 // Web page.
704 NS_ASSERTION(!mParserBlockingRequest,
705 "There can be only one parser-blocking script at a time");
706 NS_ASSERTION(mXSLTRequests.IsEmpty(),
707 "Parser-blocking scripts and XSLT scripts in the same doc!");
708 mParserBlockingRequest = request;
709 ProcessPendingRequestsAsync();
710 return true;
711 }
712 // The script hasn't loaded yet or there's a style sheet blocking it.
713 // The script will be run when it loads or the style sheet loads.
714 NS_ASSERTION(!mParserBlockingRequest,
715 "There can be only one parser-blocking script at a time");
716 NS_ASSERTION(mXSLTRequests.IsEmpty(),
717 "Parser-blocking scripts and XSLT scripts in the same doc!");
718 mParserBlockingRequest = request;
719 return true;
720 }
722 // inline script
723 // Is this document sandboxed without 'allow-scripts'?
724 if (mDocument->GetSandboxFlags() & SANDBOXED_SCRIPTS) {
725 return false;
726 }
728 // Does CSP allow this inline script to run?
729 if (!CSPAllowsInlineScript(aElement, mDocument)) {
730 return false;
731 }
733 // Inline scripts ignore ther CORS mode and are always CORS_NONE
734 request = new nsScriptLoadRequest(aElement, version, CORS_NONE);
735 request->mJSVersion = version;
736 request->mLoading = false;
737 request->mIsInline = true;
738 request->mURI = mDocument->GetDocumentURI();
739 request->mLineNo = aElement->GetScriptLineNumber();
741 if (aElement->GetParserCreated() == FROM_PARSER_XSLT &&
742 (!ReadyToExecuteScripts() || !mXSLTRequests.IsEmpty())) {
743 // Need to maintain order for XSLT-inserted scripts
744 NS_ASSERTION(!mParserBlockingRequest,
745 "Parser-blocking scripts and XSLT scripts in the same doc!");
746 mXSLTRequests.AppendElement(request);
747 return true;
748 }
749 if (aElement->GetParserCreated() == NOT_FROM_PARSER) {
750 NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
751 "A script-inserted script is inserted without an update batch?");
752 nsContentUtils::AddScriptRunner(new nsScriptRequestProcessor(this,
753 request));
754 return false;
755 }
756 if (aElement->GetParserCreated() == FROM_PARSER_NETWORK &&
757 !ReadyToExecuteScripts()) {
758 NS_ASSERTION(!mParserBlockingRequest,
759 "There can be only one parser-blocking script at a time");
760 mParserBlockingRequest = request;
761 NS_ASSERTION(mXSLTRequests.IsEmpty(),
762 "Parser-blocking scripts and XSLT scripts in the same doc!");
763 return true;
764 }
765 // We now have a document.written inline script or we have an inline script
766 // from the network but there is no style sheet that is blocking scripts.
767 // Don't check for style sheets blocking scripts in the document.write
768 // case to avoid style sheet network activity affecting when
769 // document.write returns. It's not really necessary to do this if
770 // there's no document.write currently on the call stack. However,
771 // this way matches IE more closely than checking if document.write
772 // is on the call stack.
773 NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
774 "Not safe to run a parser-inserted script?");
775 return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK;
776 }
778 namespace {
780 class NotifyOffThreadScriptLoadCompletedRunnable : public nsRunnable
781 {
782 nsRefPtr<nsScriptLoadRequest> mRequest;
783 nsRefPtr<nsScriptLoader> mLoader;
784 void *mToken;
786 public:
787 NotifyOffThreadScriptLoadCompletedRunnable(nsScriptLoadRequest* aRequest,
788 nsScriptLoader* aLoader)
789 : mRequest(aRequest), mLoader(aLoader), mToken(nullptr)
790 {}
792 void SetToken(void* aToken) {
793 MOZ_ASSERT(aToken && !mToken);
794 mToken = aToken;
795 }
797 NS_DECL_NSIRUNNABLE
798 };
800 } /* anonymous namespace */
802 nsresult
803 nsScriptLoader::ProcessOffThreadRequest(nsScriptLoadRequest* aRequest, void **aOffThreadToken)
804 {
805 nsresult rv = ProcessRequest(aRequest, aOffThreadToken);
806 mDocument->UnblockOnload(false);
807 return rv;
808 }
810 NS_IMETHODIMP
811 NotifyOffThreadScriptLoadCompletedRunnable::Run()
812 {
813 MOZ_ASSERT(NS_IsMainThread());
815 nsresult rv = mLoader->ProcessOffThreadRequest(mRequest, &mToken);
817 if (mToken) {
818 // The result of the off thread parse was not actually needed to process
819 // the request (disappearing window, some other error, ...). Finish the
820 // request to avoid leaks in the JS engine.
821 nsCOMPtr<nsIJSRuntimeService> svc = do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
822 NS_ENSURE_TRUE(svc, NS_ERROR_FAILURE);
823 JSRuntime *rt;
824 svc->GetRuntime(&rt);
825 NS_ENSURE_TRUE(rt, NS_ERROR_FAILURE);
826 JS::FinishOffThreadScript(nullptr, rt, mToken);
827 }
829 return rv;
830 }
832 static void
833 OffThreadScriptLoaderCallback(void *aToken, void *aCallbackData)
834 {
835 nsRefPtr<NotifyOffThreadScriptLoadCompletedRunnable> aRunnable =
836 dont_AddRef(static_cast<NotifyOffThreadScriptLoadCompletedRunnable*>(aCallbackData));
837 aRunnable->SetToken(aToken);
838 NS_DispatchToMainThread(aRunnable);
839 }
841 nsresult
842 nsScriptLoader::AttemptAsyncScriptParse(nsScriptLoadRequest* aRequest)
843 {
844 if (!aRequest->mElement->GetScriptAsync() || aRequest->mIsInline) {
845 return NS_ERROR_FAILURE;
846 }
848 nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
849 if (!globalObject) {
850 return NS_ERROR_FAILURE;
851 }
853 nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext();
854 if (!context) {
855 return NS_ERROR_FAILURE;
856 }
858 AutoPushJSContext cx(context->GetNativeContext());
859 JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
861 JS::CompileOptions options(cx);
862 FillCompileOptionsForRequest(aRequest, global, &options);
864 if (!JS::CanCompileOffThread(cx, options, aRequest->mScriptTextLength)) {
865 return NS_ERROR_FAILURE;
866 }
868 nsRefPtr<NotifyOffThreadScriptLoadCompletedRunnable> runnable =
869 new NotifyOffThreadScriptLoadCompletedRunnable(aRequest, this);
871 if (!JS::CompileOffThread(cx, options,
872 aRequest->mScriptTextBuf, aRequest->mScriptTextLength,
873 OffThreadScriptLoaderCallback,
874 static_cast<void*>(runnable))) {
875 return NS_ERROR_OUT_OF_MEMORY;
876 }
878 mDocument->BlockOnload();
880 unused << runnable.forget();
881 return NS_OK;
882 }
884 nsresult
885 nsScriptLoader::ProcessRequest(nsScriptLoadRequest* aRequest, void **aOffThreadToken)
886 {
887 NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
888 "Processing requests when running scripts is unsafe.");
890 if (!aOffThreadToken) {
891 nsresult rv = AttemptAsyncScriptParse(aRequest);
892 if (rv != NS_ERROR_FAILURE)
893 return rv;
894 }
896 NS_ENSURE_ARG(aRequest);
897 nsAutoString textData;
898 const jschar* scriptBuf = nullptr;
899 size_t scriptLength = 0;
900 JS::SourceBufferHolder::Ownership giveScriptOwnership =
901 JS::SourceBufferHolder::NoOwnership;
903 nsCOMPtr<nsIDocument> doc;
905 nsCOMPtr<nsINode> scriptElem = do_QueryInterface(aRequest->mElement);
907 // If there's no script text, we try to get it from the element
908 if (aRequest->mIsInline) {
909 // XXX This is inefficient - GetText makes multiple
910 // copies.
911 aRequest->mElement->GetScriptText(textData);
913 scriptBuf = textData.get();
914 scriptLength = textData.Length();
915 giveScriptOwnership = JS::SourceBufferHolder::NoOwnership;
916 }
917 else {
918 scriptBuf = aRequest->mScriptTextBuf;
919 scriptLength = aRequest->mScriptTextLength;
921 giveScriptOwnership = JS::SourceBufferHolder::GiveOwnership;
922 aRequest->mScriptTextBuf = nullptr;
923 aRequest->mScriptTextLength = 0;
925 doc = scriptElem->OwnerDoc();
926 }
928 JS::SourceBufferHolder srcBuf(scriptBuf, scriptLength, giveScriptOwnership);
930 nsCOMPtr<nsIScriptElement> oldParserInsertedScript;
931 uint32_t parserCreated = aRequest->mElement->GetParserCreated();
932 if (parserCreated) {
933 oldParserInsertedScript = mCurrentParserInsertedScript;
934 mCurrentParserInsertedScript = aRequest->mElement;
935 }
937 FireScriptAvailable(NS_OK, aRequest);
939 // The window may have gone away by this point, in which case there's no point
940 // in trying to run the script.
941 nsPIDOMWindow *pwin = mDocument->GetInnerWindow();
942 bool runScript = !!pwin;
943 if (runScript) {
944 nsContentUtils::DispatchTrustedEvent(scriptElem->OwnerDoc(),
945 scriptElem,
946 NS_LITERAL_STRING("beforescriptexecute"),
947 true, true, &runScript);
948 }
950 // Inner window could have gone away after firing beforescriptexecute
951 pwin = mDocument->GetInnerWindow();
952 if (!pwin) {
953 runScript = false;
954 }
956 nsresult rv = NS_OK;
957 if (runScript) {
958 if (doc) {
959 doc->BeginEvaluatingExternalScript();
960 }
961 aRequest->mElement->BeginEvaluating();
962 rv = EvaluateScript(aRequest, srcBuf, aOffThreadToken);
963 aRequest->mElement->EndEvaluating();
964 if (doc) {
965 doc->EndEvaluatingExternalScript();
966 }
968 nsContentUtils::DispatchTrustedEvent(scriptElem->OwnerDoc(),
969 scriptElem,
970 NS_LITERAL_STRING("afterscriptexecute"),
971 true, false);
972 }
974 FireScriptEvaluated(rv, aRequest);
976 if (parserCreated) {
977 mCurrentParserInsertedScript = oldParserInsertedScript;
978 }
980 return rv;
981 }
983 void
984 nsScriptLoader::FireScriptAvailable(nsresult aResult,
985 nsScriptLoadRequest* aRequest)
986 {
987 for (int32_t i = 0; i < mObservers.Count(); i++) {
988 nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i];
989 obs->ScriptAvailable(aResult, aRequest->mElement,
990 aRequest->mIsInline, aRequest->mURI,
991 aRequest->mLineNo);
992 }
994 aRequest->FireScriptAvailable(aResult);
995 }
997 void
998 nsScriptLoader::FireScriptEvaluated(nsresult aResult,
999 nsScriptLoadRequest* aRequest)
1000 {
1001 for (int32_t i = 0; i < mObservers.Count(); i++) {
1002 nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i];
1003 obs->ScriptEvaluated(aResult, aRequest->mElement,
1004 aRequest->mIsInline);
1005 }
1007 aRequest->FireScriptEvaluated(aResult);
1008 }
1010 already_AddRefed<nsIScriptGlobalObject>
1011 nsScriptLoader::GetScriptGlobalObject()
1012 {
1013 nsPIDOMWindow *pwin = mDocument->GetInnerWindow();
1014 if (!pwin) {
1015 return nullptr;
1016 }
1018 nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(pwin);
1019 NS_ASSERTION(globalObject, "windows must be global objects");
1021 // and make sure we are setup for this type of script.
1022 nsresult rv = globalObject->EnsureScriptEnvironment();
1023 if (NS_FAILED(rv)) {
1024 return nullptr;
1025 }
1027 return globalObject.forget();
1028 }
1030 void
1031 nsScriptLoader::FillCompileOptionsForRequest(nsScriptLoadRequest *aRequest,
1032 JS::Handle<JSObject *> aScopeChain,
1033 JS::CompileOptions *aOptions)
1034 {
1035 // It's very important to use aRequest->mURI, not the final URI of the channel
1036 // aRequest ended up getting script data from, as the script filename.
1037 nsContentUtils::GetWrapperSafeScriptFilename(mDocument, aRequest->mURI, aRequest->mURL);
1039 aOptions->setIntroductionType("scriptElement");
1040 aOptions->setFileAndLine(aRequest->mURL.get(), aRequest->mLineNo);
1041 aOptions->setVersion(JSVersion(aRequest->mJSVersion));
1042 aOptions->setCompileAndGo(JS_IsGlobalObject(aScopeChain));
1043 if (aRequest->mHasSourceMapURL) {
1044 aOptions->setSourceMapURL(aRequest->mSourceMapURL.get());
1045 }
1046 if (aRequest->mOriginPrincipal) {
1047 aOptions->setOriginPrincipals(nsJSPrincipals::get(aRequest->mOriginPrincipal));
1048 }
1050 AutoJSContext cx;
1051 JS::Rooted<JS::Value> elementVal(cx);
1052 MOZ_ASSERT(aRequest->mElement);
1053 // XXXbz this is super-fragile, because the code that _uses_ the
1054 // JS::CompileOptions is nowhere near us, but we have to coordinate
1055 // compartments with it... and in particular, it will compile in the
1056 // compartment of aScopeChain, so we want to wrap into that compartment as
1057 // well.
1058 JSAutoCompartment ac(cx, aScopeChain);
1059 if (NS_SUCCEEDED(nsContentUtils::WrapNative(cx, aRequest->mElement,
1060 &elementVal,
1061 /* aAllowWrapping = */ true))) {
1062 MOZ_ASSERT(elementVal.isObject());
1063 aOptions->setElement(&elementVal.toObject());
1064 }
1065 }
1067 nsresult
1068 nsScriptLoader::EvaluateScript(nsScriptLoadRequest* aRequest,
1069 JS::SourceBufferHolder& aSrcBuf,
1070 void** aOffThreadToken)
1071 {
1072 // We need a document to evaluate scripts.
1073 if (!mDocument) {
1074 return NS_ERROR_FAILURE;
1075 }
1077 nsCOMPtr<nsIContent> scriptContent(do_QueryInterface(aRequest->mElement));
1078 nsIDocument* ownerDoc = scriptContent->OwnerDoc();
1079 if (ownerDoc != mDocument) {
1080 // Willful violation of HTML5 as of 2010-12-01
1081 return NS_ERROR_FAILURE;
1082 }
1084 // Get the script-type to be used by this element.
1085 NS_ASSERTION(scriptContent, "no content - what is default script-type?");
1087 nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
1088 if (!globalObject) {
1089 return NS_ERROR_FAILURE;
1090 }
1092 // Make sure context is a strong reference since we access it after
1093 // we've executed a script, which may cause all other references to
1094 // the context to go away.
1095 nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext();
1096 if (!context) {
1097 return NS_ERROR_FAILURE;
1098 }
1100 JSVersion version = JSVersion(aRequest->mJSVersion);
1101 if (version == JSVERSION_UNKNOWN) {
1102 return NS_OK;
1103 }
1105 // New script entry point required, due to the "Create a script" sub-step of
1106 // http://www.whatwg.org/specs/web-apps/current-work/#execute-the-script-block
1107 AutoEntryScript entryScript(globalObject, true, context->GetNativeContext());
1108 JS::Rooted<JSObject*> global(entryScript.cx(),
1109 globalObject->GetGlobalJSObject());
1111 bool oldProcessingScriptTag = context->GetProcessingScriptTag();
1112 context->SetProcessingScriptTag(true);
1114 // Update our current script.
1115 nsCOMPtr<nsIScriptElement> oldCurrent = mCurrentScript;
1116 mCurrentScript = aRequest->mElement;
1118 JS::CompileOptions options(entryScript.cx());
1119 FillCompileOptionsForRequest(aRequest, global, &options);
1120 nsresult rv = nsJSUtils::EvaluateString(entryScript.cx(), aSrcBuf, global, options,
1121 aOffThreadToken);
1123 // Put the old script back in case it wants to do anything else.
1124 mCurrentScript = oldCurrent;
1126 context->SetProcessingScriptTag(oldProcessingScriptTag);
1127 return rv;
1128 }
1130 void
1131 nsScriptLoader::ProcessPendingRequestsAsync()
1132 {
1133 if (mParserBlockingRequest || !mPendingChildLoaders.IsEmpty()) {
1134 nsCOMPtr<nsIRunnable> ev = NS_NewRunnableMethod(this,
1135 &nsScriptLoader::ProcessPendingRequests);
1137 NS_DispatchToCurrentThread(ev);
1138 }
1139 }
1141 void
1142 nsScriptLoader::ProcessPendingRequests()
1143 {
1144 nsRefPtr<nsScriptLoadRequest> request;
1145 if (mParserBlockingRequest &&
1146 !mParserBlockingRequest->mLoading &&
1147 ReadyToExecuteScripts()) {
1148 request.swap(mParserBlockingRequest);
1149 UnblockParser(request);
1150 ProcessRequest(request);
1151 ContinueParserAsync(request);
1152 }
1154 while (ReadyToExecuteScripts() &&
1155 !mXSLTRequests.IsEmpty() &&
1156 !mXSLTRequests[0]->mLoading) {
1157 request.swap(mXSLTRequests[0]);
1158 mXSLTRequests.RemoveElementAt(0);
1159 ProcessRequest(request);
1160 }
1162 uint32_t i = 0;
1163 while (mEnabled && i < mAsyncRequests.Length()) {
1164 if (!mAsyncRequests[i]->mLoading) {
1165 request.swap(mAsyncRequests[i]);
1166 mAsyncRequests.RemoveElementAt(i);
1167 ProcessRequest(request);
1168 continue;
1169 }
1170 ++i;
1171 }
1173 while (mEnabled && !mNonAsyncExternalScriptInsertedRequests.IsEmpty() &&
1174 !mNonAsyncExternalScriptInsertedRequests[0]->mLoading) {
1175 // Violate the HTML5 spec and execute these in the insertion order in
1176 // order to make LABjs and the "order" plug-in for RequireJS work with
1177 // their Gecko-sniffed code path. See
1178 // http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html
1179 request.swap(mNonAsyncExternalScriptInsertedRequests[0]);
1180 mNonAsyncExternalScriptInsertedRequests.RemoveElementAt(0);
1181 ProcessRequest(request);
1182 }
1184 if (mDocumentParsingDone && mXSLTRequests.IsEmpty()) {
1185 while (!mDeferRequests.IsEmpty() && !mDeferRequests[0]->mLoading) {
1186 request.swap(mDeferRequests[0]);
1187 mDeferRequests.RemoveElementAt(0);
1188 ProcessRequest(request);
1189 }
1190 }
1192 while (!mPendingChildLoaders.IsEmpty() && ReadyToExecuteScripts()) {
1193 nsRefPtr<nsScriptLoader> child = mPendingChildLoaders[0];
1194 mPendingChildLoaders.RemoveElementAt(0);
1195 child->RemoveExecuteBlocker();
1196 }
1198 if (mDocumentParsingDone && mDocument &&
1199 !mParserBlockingRequest && mAsyncRequests.IsEmpty() &&
1200 mNonAsyncExternalScriptInsertedRequests.IsEmpty() &&
1201 mXSLTRequests.IsEmpty() && mDeferRequests.IsEmpty()) {
1202 if (MaybeRemovedDeferRequests()) {
1203 return ProcessPendingRequests();
1204 }
1205 // No more pending scripts; time to unblock onload.
1206 // OK to unblock onload synchronously here, since callers must be
1207 // prepared for the world changing anyway.
1208 mDocumentParsingDone = false;
1209 mDocument->UnblockOnload(true);
1210 }
1211 }
1213 bool
1214 nsScriptLoader::ReadyToExecuteScripts()
1215 {
1216 // Make sure the SelfReadyToExecuteScripts check is first, so that
1217 // we don't block twice on an ancestor.
1218 if (!SelfReadyToExecuteScripts()) {
1219 return false;
1220 }
1222 for (nsIDocument* doc = mDocument; doc; doc = doc->GetParentDocument()) {
1223 nsScriptLoader* ancestor = doc->ScriptLoader();
1224 if (!ancestor->SelfReadyToExecuteScripts() &&
1225 ancestor->AddPendingChildLoader(this)) {
1226 AddExecuteBlocker();
1227 return false;
1228 }
1229 }
1231 return true;
1232 }
1235 // This function was copied from nsParser.cpp. It was simplified a bit.
1236 static bool
1237 DetectByteOrderMark(const unsigned char* aBytes, int32_t aLen, nsCString& oCharset)
1238 {
1239 if (aLen < 2)
1240 return false;
1242 switch(aBytes[0]) {
1243 case 0xEF:
1244 if (aLen >= 3 && 0xBB == aBytes[1] && 0xBF == aBytes[2]) {
1245 // EF BB BF
1246 // Win2K UTF-8 BOM
1247 oCharset.Assign("UTF-8");
1248 }
1249 break;
1250 case 0xFE:
1251 if (0xFF == aBytes[1]) {
1252 // FE FF
1253 // UTF-16, big-endian
1254 oCharset.Assign("UTF-16BE");
1255 }
1256 break;
1257 case 0xFF:
1258 if (0xFE == aBytes[1]) {
1259 // FF FE
1260 // UTF-16, little-endian
1261 oCharset.Assign("UTF-16LE");
1262 }
1263 break;
1264 }
1265 return !oCharset.IsEmpty();
1266 }
1268 /* static */ nsresult
1269 nsScriptLoader::ConvertToUTF16(nsIChannel* aChannel, const uint8_t* aData,
1270 uint32_t aLength, const nsAString& aHintCharset,
1271 nsIDocument* aDocument,
1272 jschar*& aBufOut, size_t& aLengthOut)
1273 {
1274 if (!aLength) {
1275 aBufOut = nullptr;
1276 aLengthOut = 0;
1277 return NS_OK;
1278 }
1280 // The encoding info precedence is as follows from high to low:
1281 // The BOM
1282 // HTTP Content-Type (if name recognized)
1283 // charset attribute (if name recognized)
1284 // The encoding of the document
1286 nsAutoCString charset;
1288 nsCOMPtr<nsIUnicodeDecoder> unicodeDecoder;
1290 if (DetectByteOrderMark(aData, aLength, charset)) {
1291 // charset is now "UTF-8" or "UTF-16". The UTF-16 decoder will re-sniff
1292 // the BOM for endianness. Both the UTF-16 and the UTF-8 decoder will
1293 // take care of swallowing the BOM.
1294 unicodeDecoder = EncodingUtils::DecoderForEncoding(charset);
1295 }
1297 if (!unicodeDecoder &&
1298 aChannel &&
1299 NS_SUCCEEDED(aChannel->GetContentCharset(charset)) &&
1300 EncodingUtils::FindEncodingForLabel(charset, charset)) {
1301 unicodeDecoder = EncodingUtils::DecoderForEncoding(charset);
1302 }
1304 if (!unicodeDecoder &&
1305 EncodingUtils::FindEncodingForLabel(aHintCharset, charset)) {
1306 unicodeDecoder = EncodingUtils::DecoderForEncoding(charset);
1307 }
1309 if (!unicodeDecoder && aDocument) {
1310 charset = aDocument->GetDocumentCharacterSet();
1311 unicodeDecoder = EncodingUtils::DecoderForEncoding(charset);
1312 }
1314 if (!unicodeDecoder) {
1315 // Curiously, there are various callers that don't pass aDocument. The
1316 // fallback in the old code was ISO-8859-1, which behaved like
1317 // windows-1252. Saying windows-1252 for clarity and for compliance
1318 // with the Encoding Standard.
1319 unicodeDecoder = EncodingUtils::DecoderForEncoding("windows-1252");
1320 }
1322 int32_t unicodeLength = 0;
1324 nsresult rv =
1325 unicodeDecoder->GetMaxLength(reinterpret_cast<const char*>(aData),
1326 aLength, &unicodeLength);
1327 NS_ENSURE_SUCCESS(rv, rv);
1329 aBufOut = static_cast<jschar*>(js_malloc(unicodeLength * sizeof(jschar)));
1330 if (!aBufOut) {
1331 aLengthOut = 0;
1332 return NS_ERROR_OUT_OF_MEMORY;
1333 }
1334 aLengthOut = unicodeLength;
1336 rv = unicodeDecoder->Convert(reinterpret_cast<const char*>(aData),
1337 (int32_t *) &aLength, aBufOut,
1338 &unicodeLength);
1339 MOZ_ASSERT(NS_SUCCEEDED(rv));
1340 aLengthOut = unicodeLength;
1341 if (NS_FAILED(rv)) {
1342 js_free(aBufOut);
1343 aBufOut = nullptr;
1344 aLengthOut = 0;
1345 }
1346 return rv;
1347 }
1349 NS_IMETHODIMP
1350 nsScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader,
1351 nsISupports* aContext,
1352 nsresult aStatus,
1353 uint32_t aStringLen,
1354 const uint8_t* aString)
1355 {
1356 nsScriptLoadRequest* request = static_cast<nsScriptLoadRequest*>(aContext);
1357 NS_ASSERTION(request, "null request in stream complete handler");
1358 NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
1360 nsresult rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen,
1361 aString);
1362 if (NS_FAILED(rv)) {
1363 if (mDeferRequests.RemoveElement(request) ||
1364 mAsyncRequests.RemoveElement(request) ||
1365 mNonAsyncExternalScriptInsertedRequests.RemoveElement(request) ||
1366 mXSLTRequests.RemoveElement(request)) {
1367 FireScriptAvailable(rv, request);
1368 } else if (mParserBlockingRequest == request) {
1369 mParserBlockingRequest = nullptr;
1370 UnblockParser(request);
1371 FireScriptAvailable(rv, request);
1372 ContinueParserAsync(request);
1373 } else {
1374 mPreloads.RemoveElement(request, PreloadRequestComparator());
1375 }
1376 rv = NS_OK;
1377 } else {
1378 NS_Free(const_cast<uint8_t *>(aString));
1379 rv = NS_SUCCESS_ADOPTED_DATA;
1380 }
1382 // Process our request and/or any pending ones
1383 ProcessPendingRequests();
1385 return rv;
1386 }
1388 void
1389 nsScriptLoader::UnblockParser(nsScriptLoadRequest* aParserBlockingRequest)
1390 {
1391 aParserBlockingRequest->mElement->UnblockParser();
1392 }
1394 void
1395 nsScriptLoader::ContinueParserAsync(nsScriptLoadRequest* aParserBlockingRequest)
1396 {
1397 aParserBlockingRequest->mElement->ContinueParserAsync();
1398 }
1400 nsresult
1401 nsScriptLoader::PrepareLoadedRequest(nsScriptLoadRequest* aRequest,
1402 nsIStreamLoader* aLoader,
1403 nsresult aStatus,
1404 uint32_t aStringLen,
1405 const uint8_t* aString)
1406 {
1407 if (NS_FAILED(aStatus)) {
1408 return aStatus;
1409 }
1411 // If we don't have a document, then we need to abort further
1412 // evaluation.
1413 if (!mDocument) {
1414 return NS_ERROR_NOT_AVAILABLE;
1415 }
1417 // If the load returned an error page, then we need to abort
1418 nsCOMPtr<nsIRequest> req;
1419 nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
1420 NS_ASSERTION(req, "StreamLoader's request went away prematurely");
1421 NS_ENSURE_SUCCESS(rv, rv);
1423 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(req);
1424 if (httpChannel) {
1425 bool requestSucceeded;
1426 rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
1427 if (NS_SUCCEEDED(rv) && !requestSucceeded) {
1428 return NS_ERROR_NOT_AVAILABLE;
1429 }
1431 nsAutoCString sourceMapURL;
1432 httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("X-SourceMap"), sourceMapURL);
1433 aRequest->mHasSourceMapURL = true;
1434 aRequest->mSourceMapURL = NS_ConvertUTF8toUTF16(sourceMapURL);
1435 }
1437 nsCOMPtr<nsIChannel> channel = do_QueryInterface(req);
1438 // If this load was subject to a CORS check; don't flag it with a
1439 // separate origin principal, so that it will treat our document's
1440 // principal as the origin principal
1441 if (aRequest->mCORSMode == CORS_NONE) {
1442 rv = nsContentUtils::GetSecurityManager()->
1443 GetChannelPrincipal(channel, getter_AddRefs(aRequest->mOriginPrincipal));
1444 NS_ENSURE_SUCCESS(rv, rv);
1445 }
1447 if (aStringLen) {
1448 // Check the charset attribute to determine script charset.
1449 nsAutoString hintCharset;
1450 if (!aRequest->IsPreload()) {
1451 aRequest->mElement->GetScriptCharset(hintCharset);
1452 } else {
1453 nsTArray<PreloadInfo>::index_type i =
1454 mPreloads.IndexOf(aRequest, 0, PreloadRequestComparator());
1455 NS_ASSERTION(i != mPreloads.NoIndex, "Incorrect preload bookkeeping");
1456 hintCharset = mPreloads[i].mCharset;
1457 }
1458 rv = ConvertToUTF16(channel, aString, aStringLen, hintCharset, mDocument,
1459 aRequest->mScriptTextBuf, aRequest->mScriptTextLength);
1461 NS_ENSURE_SUCCESS(rv, rv);
1462 }
1464 // This assertion could fire errorously if we ran out of memory when
1465 // inserting the request in the array. However it's an unlikely case
1466 // so if you see this assertion it is likely something else that is
1467 // wrong, especially if you see it more than once.
1468 NS_ASSERTION(mDeferRequests.Contains(aRequest) ||
1469 mAsyncRequests.Contains(aRequest) ||
1470 mNonAsyncExternalScriptInsertedRequests.Contains(aRequest) ||
1471 mXSLTRequests.Contains(aRequest) ||
1472 mPreloads.Contains(aRequest, PreloadRequestComparator()) ||
1473 mParserBlockingRequest,
1474 "aRequest should be pending!");
1476 // Mark this as loaded
1477 aRequest->mLoading = false;
1479 return NS_OK;
1480 }
1482 void
1483 nsScriptLoader::ParsingComplete(bool aTerminated)
1484 {
1485 if (mDeferEnabled) {
1486 // Have to check because we apparently get ParsingComplete
1487 // without BeginDeferringScripts in some cases
1488 mDocumentParsingDone = true;
1489 }
1490 mDeferEnabled = false;
1491 if (aTerminated) {
1492 mDeferRequests.Clear();
1493 mAsyncRequests.Clear();
1494 mNonAsyncExternalScriptInsertedRequests.Clear();
1495 mXSLTRequests.Clear();
1496 mParserBlockingRequest = nullptr;
1497 }
1499 // Have to call this even if aTerminated so we'll correctly unblock
1500 // onload and all.
1501 ProcessPendingRequests();
1502 }
1504 void
1505 nsScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset,
1506 const nsAString &aType,
1507 const nsAString &aCrossOrigin,
1508 bool aScriptFromHead)
1509 {
1510 // Check to see if scripts has been turned off.
1511 if (!mEnabled || !mDocument->IsScriptEnabled()) {
1512 return;
1513 }
1515 nsRefPtr<nsScriptLoadRequest> request =
1516 new nsScriptLoadRequest(nullptr, 0,
1517 Element::StringToCORSMode(aCrossOrigin));
1518 request->mURI = aURI;
1519 request->mIsInline = false;
1520 request->mLoading = true;
1521 nsresult rv = StartLoad(request, aType, aScriptFromHead);
1522 if (NS_FAILED(rv)) {
1523 return;
1524 }
1526 PreloadInfo *pi = mPreloads.AppendElement();
1527 pi->mRequest = request;
1528 pi->mCharset = aCharset;
1529 }
1531 void
1532 nsScriptLoader::AddDeferRequest(nsScriptLoadRequest* aRequest)
1533 {
1534 mDeferRequests.AppendElement(aRequest);
1535 if (mDeferEnabled && mDeferRequests.Length() == 1 && mDocument &&
1536 !mBlockingDOMContentLoaded) {
1537 MOZ_ASSERT(mDocument->GetReadyStateEnum() == nsIDocument::READYSTATE_LOADING);
1538 mBlockingDOMContentLoaded = true;
1539 mDocument->BlockDOMContentLoaded();
1540 }
1541 }
1543 bool
1544 nsScriptLoader::MaybeRemovedDeferRequests()
1545 {
1546 if (mDeferRequests.Length() == 0 && mDocument &&
1547 mBlockingDOMContentLoaded) {
1548 mBlockingDOMContentLoaded = false;
1549 mDocument->UnblockDOMContentLoaded();
1550 return true;
1551 }
1552 return false;
1553 }