Bumping gaia.json for 8 gaia revision(s) a=gaia-bump
[gecko.git] / dom / base / nsScriptLoader.cpp
blob93c9d4c2fa22455a13f3a6f540f65d68fc7d9d6d
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 "nsIClassOfService.h"
31 #include "nsITimedChannel.h"
32 #include "nsIScriptElement.h"
33 #include "nsIDOMHTMLScriptElement.h"
34 #include "nsIDocShell.h"
35 #include "nsContentUtils.h"
36 #include "nsUnicharUtils.h"
37 #include "nsAutoPtr.h"
38 #include "nsIXPConnect.h"
39 #include "nsError.h"
40 #include "nsThreadUtils.h"
41 #include "nsDocShellCID.h"
42 #include "nsIContentSecurityPolicy.h"
43 #include "prlog.h"
44 #include "nsCRT.h"
45 #include "nsContentCreatorFunctions.h"
46 #include "nsCORSListenerProxy.h"
47 #include "nsSandboxFlags.h"
48 #include "nsContentTypeParser.h"
49 #include "nsINetworkPredictor.h"
50 #include "ImportManager.h"
51 #include "mozilla/dom/EncodingUtils.h"
53 #include "mozilla/CORSMode.h"
54 #include "mozilla/Attributes.h"
55 #include "mozilla/unused.h"
57 #ifdef PR_LOGGING
58 static PRLogModuleInfo* gCspPRLog;
59 #endif
61 using namespace mozilla;
62 using namespace mozilla::dom;
64 //////////////////////////////////////////////////////////////
65 // Per-request data structure
66 //////////////////////////////////////////////////////////////
68 class nsScriptLoadRequest MOZ_FINAL : public nsISupports {
69 ~nsScriptLoadRequest()
71 js_free(mScriptTextBuf);
74 public:
75 nsScriptLoadRequest(nsIScriptElement* aElement,
76 uint32_t aVersion,
77 CORSMode aCORSMode)
78 : mElement(aElement),
79 mLoading(true),
80 mIsInline(true),
81 mHasSourceMapURL(false),
82 mScriptTextBuf(nullptr),
83 mScriptTextLength(0),
84 mJSVersion(aVersion),
85 mLineNo(1),
86 mCORSMode(aCORSMode),
87 mReferrerPolicy(mozilla::net::RP_Default)
91 NS_DECL_THREADSAFE_ISUPPORTS
93 void FireScriptAvailable(nsresult aResult)
95 mElement->ScriptAvailable(aResult, mElement, mIsInline, mURI, mLineNo);
97 void FireScriptEvaluated(nsresult aResult)
99 mElement->ScriptEvaluated(aResult, mElement, mIsInline);
102 bool IsPreload()
104 return mElement == nullptr;
107 nsCOMPtr<nsIScriptElement> mElement;
108 bool mLoading; // Are we still waiting for a load to complete?
109 bool mIsInline; // Is the script inline or loaded?
110 bool mHasSourceMapURL; // Does the HTTP header have a source map url?
111 nsString mSourceMapURL; // Holds source map url for loaded scripts
112 char16_t* mScriptTextBuf; // Holds script text for non-inline scripts. Don't
113 size_t mScriptTextLength; // use nsString so we can give ownership to jsapi.
114 uint32_t mJSVersion;
115 nsCOMPtr<nsIURI> mURI;
116 nsCOMPtr<nsIPrincipal> mOriginPrincipal;
117 nsAutoCString mURL; // Keep the URI's filename alive during off thread parsing.
118 int32_t mLineNo;
119 const CORSMode mCORSMode;
120 mozilla::net::ReferrerPolicy mReferrerPolicy;
123 // The nsScriptLoadRequest is passed as the context to necko, and thus
124 // it needs to be threadsafe. Necko won't do anything with this
125 // context, but it will AddRef and Release it on other threads.
126 NS_IMPL_ISUPPORTS0(nsScriptLoadRequest)
128 //////////////////////////////////////////////////////////////
130 //////////////////////////////////////////////////////////////
132 nsScriptLoader::nsScriptLoader(nsIDocument *aDocument)
133 : mDocument(aDocument),
134 mBlockerCount(0),
135 mEnabled(true),
136 mDeferEnabled(false),
137 mDocumentParsingDone(false),
138 mBlockingDOMContentLoaded(false)
140 // enable logging for CSP
141 #ifdef PR_LOGGING
142 if (!gCspPRLog)
143 gCspPRLog = PR_NewLogModule("CSP");
144 #endif
147 nsScriptLoader::~nsScriptLoader()
149 mObservers.Clear();
151 if (mParserBlockingRequest) {
152 mParserBlockingRequest->FireScriptAvailable(NS_ERROR_ABORT);
155 for (uint32_t i = 0; i < mXSLTRequests.Length(); i++) {
156 mXSLTRequests[i]->FireScriptAvailable(NS_ERROR_ABORT);
159 for (uint32_t i = 0; i < mDeferRequests.Length(); i++) {
160 mDeferRequests[i]->FireScriptAvailable(NS_ERROR_ABORT);
163 for (uint32_t i = 0; i < mAsyncRequests.Length(); i++) {
164 mAsyncRequests[i]->FireScriptAvailable(NS_ERROR_ABORT);
167 for (uint32_t i = 0; i < mNonAsyncExternalScriptInsertedRequests.Length(); i++) {
168 mNonAsyncExternalScriptInsertedRequests[i]->FireScriptAvailable(NS_ERROR_ABORT);
171 // Unblock the kids, in case any of them moved to a different document
172 // subtree in the meantime and therefore aren't actually going away.
173 for (uint32_t j = 0; j < mPendingChildLoaders.Length(); ++j) {
174 mPendingChildLoaders[j]->RemoveExecuteBlocker();
178 NS_IMPL_ISUPPORTS(nsScriptLoader, nsIStreamLoaderObserver)
180 // Helper method for checking if the script element is an event-handler
181 // This means that it has both a for-attribute and a event-attribute.
182 // Also, if the for-attribute has a value that matches "\s*window\s*",
183 // and the event-attribute matches "\s*onload([ \(].*)?" then it isn't an
184 // eventhandler. (both matches are case insensitive).
185 // This is how IE seems to filter out a window's onload handler from a
186 // <script for=... event=...> element.
188 static bool
189 IsScriptEventHandler(nsIContent* aScriptElement)
191 if (!aScriptElement->IsHTML()) {
192 return false;
195 nsAutoString forAttr, eventAttr;
196 if (!aScriptElement->GetAttr(kNameSpaceID_None, nsGkAtoms::_for, forAttr) ||
197 !aScriptElement->GetAttr(kNameSpaceID_None, nsGkAtoms::event, eventAttr)) {
198 return false;
201 const nsAString& for_str =
202 nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(forAttr);
203 if (!for_str.LowerCaseEqualsLiteral("window")) {
204 return true;
207 // We found for="window", now check for event="onload".
208 const nsAString& event_str =
209 nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(eventAttr, false);
210 if (!StringBeginsWith(event_str, NS_LITERAL_STRING("onload"),
211 nsCaseInsensitiveStringComparator())) {
212 // It ain't "onload.*".
214 return true;
217 nsAutoString::const_iterator start, end;
218 event_str.BeginReading(start);
219 event_str.EndReading(end);
221 start.advance(6); // advance past "onload"
223 if (start != end && *start != '(' && *start != ' ') {
224 // We got onload followed by something other than space or
225 // '('. Not good enough.
227 return true;
230 return false;
233 nsresult
234 nsScriptLoader::CheckContentPolicy(nsIDocument* aDocument,
235 nsISupports *aContext,
236 nsIURI *aURI,
237 const nsAString &aType)
239 int16_t shouldLoad = nsIContentPolicy::ACCEPT;
240 nsresult rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_SCRIPT,
241 aURI,
242 aDocument->NodePrincipal(),
243 aContext,
244 NS_LossyConvertUTF16toASCII(aType),
245 nullptr, //extra
246 &shouldLoad,
247 nsContentUtils::GetContentPolicy(),
248 nsContentUtils::GetSecurityManager());
249 if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
250 if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) {
251 return NS_ERROR_CONTENT_BLOCKED;
253 return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
256 return NS_OK;
259 nsresult
260 nsScriptLoader::ShouldLoadScript(nsIDocument* aDocument,
261 nsISupports* aContext,
262 nsIURI* aURI,
263 const nsAString &aType)
265 // Check that the containing page is allowed to load this URI.
266 nsresult rv = nsContentUtils::GetSecurityManager()->
267 CheckLoadURIWithPrincipal(aDocument->NodePrincipal(), aURI,
268 nsIScriptSecurityManager::ALLOW_CHROME);
270 NS_ENSURE_SUCCESS(rv, rv);
272 // After the security manager, the content-policy stuff gets a veto
273 rv = CheckContentPolicy(aDocument, aContext, aURI, aType);
274 if (NS_FAILED(rv)) {
275 return rv;
278 return NS_OK;
281 nsresult
282 nsScriptLoader::StartLoad(nsScriptLoadRequest *aRequest, const nsAString &aType,
283 bool aScriptFromHead)
285 nsISupports *context = aRequest->mElement.get()
286 ? static_cast<nsISupports *>(aRequest->mElement.get())
287 : static_cast<nsISupports *>(mDocument);
288 nsresult rv = ShouldLoadScript(mDocument, context, aRequest->mURI, aType);
289 if (NS_FAILED(rv)) {
290 return rv;
293 nsCOMPtr<nsILoadGroup> loadGroup = mDocument->GetDocumentLoadGroup();
295 nsCOMPtr<nsPIDOMWindow> window(do_QueryInterface(mDocument->MasterDocument()->GetWindow()));
297 if (!window) {
298 return NS_ERROR_NULL_POINTER;
301 nsIDocShell *docshell = window->GetDocShell();
303 nsCOMPtr<nsIInterfaceRequestor> prompter(do_QueryInterface(docshell));
305 // If this document is sandboxed without 'allow-scripts', abort.
306 if (mDocument->GetSandboxFlags() & SANDBOXED_SCRIPTS) {
307 return NS_OK;
310 nsCOMPtr<nsIChannel> channel;
311 rv = NS_NewChannel(getter_AddRefs(channel),
312 aRequest->mURI,
313 mDocument,
314 nsILoadInfo::SEC_NORMAL,
315 nsIContentPolicy::TYPE_SCRIPT,
316 loadGroup,
317 prompter,
318 nsIRequest::LOAD_NORMAL |
319 nsIChannel::LOAD_CLASSIFY_URI);
321 NS_ENSURE_SUCCESS(rv, rv);
323 nsIScriptElement *script = aRequest->mElement;
324 nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
326 if (cos) {
327 if (aScriptFromHead &&
328 !(script && (script->GetScriptAsync() || script->GetScriptDeferred()))) {
329 // synchronous head scripts block lading of most other non js/css
330 // content such as images
331 cos->AddClassFlags(nsIClassOfService::Leader);
332 } else if (!(script && script->GetScriptDeferred())) {
333 // other scripts are neither blocked nor prioritized unless marked deferred
334 cos->AddClassFlags(nsIClassOfService::Unblocked);
338 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
339 if (httpChannel) {
340 // HTTP content negotation has little value in this context.
341 httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
342 NS_LITERAL_CSTRING("*/*"),
343 false);
344 httpChannel->SetReferrerWithPolicy(mDocument->GetDocumentURI(),
345 aRequest->mReferrerPolicy);
348 nsCOMPtr<nsILoadContext> loadContext(do_QueryInterface(docshell));
349 mozilla::net::PredictorLearn(aRequest->mURI, mDocument->GetDocumentURI(),
350 nsINetworkPredictor::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"));
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;
374 rv = channel->AsyncOpen(listener, aRequest);
375 NS_ENSURE_SUCCESS(rv, rv);
377 return NS_OK;
380 bool
381 nsScriptLoader::PreloadURIComparator::Equals(const PreloadInfo &aPi,
382 nsIURI * const &aURI) const
384 bool same;
385 return NS_SUCCEEDED(aPi.mRequest->mURI->Equals(aURI, &same)) &&
386 same;
389 class nsScriptRequestProcessor : public nsRunnable
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)
400 NS_IMETHODIMP Run()
402 return mLoader->ProcessRequest(mRequest);
406 static inline bool
407 ParseTypeAttribute(const nsAString& aType, JSVersion* aVersion)
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;
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;
433 return true;
436 static bool
437 CSPAllowsInlineScript(nsIScriptElement *aElement, nsIDocument *aDocument)
439 nsCOMPtr<nsIContentSecurityPolicy> csp;
440 // Note: For imports NodePrincipal and the principal of the master are
441 // the same.
442 nsresult rv = aDocument->NodePrincipal()->GetCsp(getter_AddRefs(csp));
443 NS_ENSURE_SUCCESS(rv, false);
445 if (!csp) {
446 // no CSP --> allow
447 return true;
450 // An inline script can be allowed because all inline scripts are allowed,
451 // or else because it is whitelisted by a nonce-source or hash-source. This
452 // is a logical OR between whitelisting methods, so the allowInlineScript
453 // outparam can be reused for each check as long as we stop checking as soon
454 // as it is set to true. This also optimizes performance by avoiding the
455 // overhead of unnecessary checks.
456 bool allowInlineScript = true;
457 nsAutoTArray<unsigned short, 3> violations;
459 bool reportInlineViolation = false;
460 rv = csp->GetAllowsInlineScript(&reportInlineViolation, &allowInlineScript);
461 NS_ENSURE_SUCCESS(rv, false);
462 if (reportInlineViolation) {
463 violations.AppendElement(static_cast<unsigned short>(
464 nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT));
467 nsAutoString nonce;
468 if (!allowInlineScript) {
469 nsCOMPtr<nsIContent> scriptContent = do_QueryInterface(aElement);
470 bool foundNonce = scriptContent->GetAttr(kNameSpaceID_None,
471 nsGkAtoms::nonce, nonce);
472 if (foundNonce) {
473 bool reportNonceViolation;
474 rv = csp->GetAllowsNonce(nonce, nsIContentPolicy::TYPE_SCRIPT,
475 &reportNonceViolation, &allowInlineScript);
476 NS_ENSURE_SUCCESS(rv, false);
477 if (reportNonceViolation) {
478 violations.AppendElement(static_cast<unsigned short>(
479 nsIContentSecurityPolicy::VIOLATION_TYPE_NONCE_SCRIPT));
484 if (!allowInlineScript) {
485 bool reportHashViolation;
486 nsAutoString scriptText;
487 aElement->GetScriptText(scriptText);
488 rv = csp->GetAllowsHash(scriptText, nsIContentPolicy::TYPE_SCRIPT,
489 &reportHashViolation, &allowInlineScript);
490 NS_ENSURE_SUCCESS(rv, false);
491 if (reportHashViolation) {
492 violations.AppendElement(static_cast<unsigned short>(
493 nsIContentSecurityPolicy::VIOLATION_TYPE_HASH_SCRIPT));
497 // What violation(s) should be reported?
499 // 1. If the script tag has a nonce attribute, and the nonce does not match
500 // the policy, report VIOLATION_TYPE_NONCE_SCRIPT.
501 // 2. If the policy has at least one hash-source, and the hashed contents of
502 // the script tag did not match any of them, report VIOLATION_TYPE_HASH_SCRIPT
503 // 3. Otherwise, report VIOLATION_TYPE_INLINE_SCRIPT if appropriate.
505 // 1 and 2 may occur together, 3 should only occur by itself. Naturally,
506 // every VIOLATION_TYPE_NONCE_SCRIPT and VIOLATION_TYPE_HASH_SCRIPT are also
507 // VIOLATION_TYPE_INLINE_SCRIPT, but reporting the
508 // VIOLATION_TYPE_INLINE_SCRIPT is redundant and does not help the developer.
509 if (!violations.IsEmpty()) {
510 MOZ_ASSERT(violations[0] == nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT,
511 "How did we get any violations without an initial inline script violation?");
512 // gather information to log with violation report
513 nsIURI* uri = aDocument->GetDocumentURI();
514 nsAutoCString asciiSpec;
515 uri->GetAsciiSpec(asciiSpec);
516 nsAutoString scriptText;
517 aElement->GetScriptText(scriptText);
518 nsAutoString scriptSample(scriptText);
520 // cap the length of the script sample at 40 chars
521 if (scriptSample.Length() > 40) {
522 scriptSample.Truncate(40);
523 scriptSample.AppendLiteral("...");
526 for (uint32_t i = 0; i < violations.Length(); i++) {
527 // Skip reporting the redundant inline script violation if there are
528 // other (nonce and/or hash violations) as well.
529 if (i > 0 || violations.Length() == 1) {
530 csp->LogViolationDetails(violations[i], NS_ConvertUTF8toUTF16(asciiSpec),
531 scriptSample, aElement->GetScriptLineNumber(),
532 nonce, scriptText);
537 if (!allowInlineScript) {
538 NS_ASSERTION(!violations.IsEmpty(),
539 "CSP blocked inline script but is not reporting a violation");
540 return false;
542 return true;
545 bool
546 nsScriptLoader::ProcessScriptElement(nsIScriptElement *aElement)
548 // We need a document to evaluate scripts.
549 NS_ENSURE_TRUE(mDocument, false);
551 // Check to see if scripts has been turned off.
552 if (!mEnabled || !mDocument->IsScriptEnabled()) {
553 return false;
556 NS_ASSERTION(!aElement->IsMalformed(), "Executing malformed script");
558 nsCOMPtr<nsIContent> scriptContent = do_QueryInterface(aElement);
560 // Step 12. Check that the script is not an eventhandler
561 if (IsScriptEventHandler(scriptContent)) {
562 return false;
565 JSVersion version = JSVERSION_DEFAULT;
567 // Check the type attribute to determine language and version.
568 // If type exists, it trumps the deprecated 'language='
569 nsAutoString type;
570 aElement->GetScriptType(type);
571 if (!type.IsEmpty()) {
572 NS_ENSURE_TRUE(ParseTypeAttribute(type, &version), false);
573 } else {
574 // no 'type=' element
575 // "language" is a deprecated attribute of HTML, so we check it only for
576 // HTML script elements.
577 if (scriptContent->IsHTML()) {
578 nsAutoString language;
579 scriptContent->GetAttr(kNameSpaceID_None, nsGkAtoms::language, language);
580 if (!language.IsEmpty()) {
581 if (!nsContentUtils::IsJavaScriptLanguage(language)) {
582 return false;
588 // Step 14. in the HTML5 spec
589 nsresult rv = NS_OK;
590 nsRefPtr<nsScriptLoadRequest> request;
591 if (aElement->GetScriptExternal()) {
592 // external script
593 nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI();
594 if (!scriptURI) {
595 // Asynchronously report the failure to create a URI object
596 NS_DispatchToCurrentThread(
597 NS_NewRunnableMethod(aElement,
598 &nsIScriptElement::FireErrorEvent));
599 return false;
602 // Double-check that the preload matches what we're asked to load now.
603 mozilla::net::ReferrerPolicy ourRefPolicy = mDocument->GetReferrerPolicy();
604 CORSMode ourCORSMode = aElement->GetCORSMode();
605 nsTArray<PreloadInfo>::index_type i =
606 mPreloads.IndexOf(scriptURI.get(), 0, PreloadURIComparator());
607 if (i != nsTArray<PreloadInfo>::NoIndex) {
608 // preloaded
609 // note that a script-inserted script can steal a preload!
610 request = mPreloads[i].mRequest;
611 request->mElement = aElement;
612 nsString preloadCharset(mPreloads[i].mCharset);
613 mPreloads.RemoveElementAt(i);
615 // Double-check that the charset the preload used is the same as
616 // the charset we have now.
617 nsAutoString elementCharset;
618 aElement->GetScriptCharset(elementCharset);
619 if (elementCharset.Equals(preloadCharset) &&
620 ourCORSMode == request->mCORSMode &&
621 ourRefPolicy == request->mReferrerPolicy) {
622 rv = CheckContentPolicy(mDocument, aElement, request->mURI, type);
623 NS_ENSURE_SUCCESS(rv, false);
624 } else {
625 // Drop the preload
626 request = nullptr;
630 if (!request) {
631 // no usable preload
632 request = new nsScriptLoadRequest(aElement, version, ourCORSMode);
633 request->mURI = scriptURI;
634 request->mIsInline = false;
635 request->mLoading = true;
636 request->mReferrerPolicy = ourRefPolicy;
638 // set aScriptFromHead to false so we don't treat non preloaded scripts as
639 // blockers for full page load. See bug 792438.
640 rv = StartLoad(request, type, false);
641 if (NS_FAILED(rv)) {
642 // Asynchronously report the load failure
643 NS_DispatchToCurrentThread(
644 NS_NewRunnableMethod(aElement,
645 &nsIScriptElement::FireErrorEvent));
646 return false;
650 request->mJSVersion = version;
652 if (aElement->GetScriptAsync()) {
653 mAsyncRequests.AppendElement(request);
654 if (!request->mLoading) {
655 // The script is available already. Run it ASAP when the event
656 // loop gets a chance to spin.
657 ProcessPendingRequestsAsync();
659 return false;
661 if (!aElement->GetParserCreated()) {
662 // Violate the HTML5 spec in order to make LABjs and the "order" plug-in
663 // for RequireJS work with their Gecko-sniffed code path. See
664 // http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html
665 mNonAsyncExternalScriptInsertedRequests.AppendElement(request);
666 if (!request->mLoading) {
667 // The script is available already. Run it ASAP when the event
668 // loop gets a chance to spin.
669 ProcessPendingRequestsAsync();
671 return false;
673 // we now have a parser-inserted request that may or may not be still
674 // loading
675 if (aElement->GetScriptDeferred()) {
676 // We don't want to run this yet.
677 // If we come here, the script is a parser-created script and it has
678 // the defer attribute but not the async attribute. Since a
679 // a parser-inserted script is being run, we came here by the parser
680 // running the script, which means the parser is still alive and the
681 // parse is ongoing.
682 NS_ASSERTION(mDocument->GetCurrentContentSink() ||
683 aElement->GetParserCreated() == FROM_PARSER_XSLT,
684 "Non-XSLT Defer script on a document without an active parser; bug 592366.");
685 AddDeferRequest(request);
686 return false;
689 if (aElement->GetParserCreated() == FROM_PARSER_XSLT) {
690 // Need to maintain order for XSLT-inserted scripts
691 NS_ASSERTION(!mParserBlockingRequest,
692 "Parser-blocking scripts and XSLT scripts in the same doc!");
693 mXSLTRequests.AppendElement(request);
694 if (!request->mLoading) {
695 // The script is available already. Run it ASAP when the event
696 // loop gets a chance to spin.
697 ProcessPendingRequestsAsync();
699 return true;
701 if (!request->mLoading && ReadyToExecuteScripts()) {
702 // The request has already been loaded and there are no pending style
703 // sheets. If the script comes from the network stream, cheat for
704 // performance reasons and avoid a trip through the event loop.
705 if (aElement->GetParserCreated() == FROM_PARSER_NETWORK) {
706 return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK;
708 // Otherwise, we've got a document.written script, make a trip through
709 // the event loop to hide the preload effects from the scripts on the
710 // Web page.
711 NS_ASSERTION(!mParserBlockingRequest,
712 "There can be only one parser-blocking script at a time");
713 NS_ASSERTION(mXSLTRequests.IsEmpty(),
714 "Parser-blocking scripts and XSLT scripts in the same doc!");
715 mParserBlockingRequest = request;
716 ProcessPendingRequestsAsync();
717 return true;
719 // The script hasn't loaded yet or there's a style sheet blocking it.
720 // The script will be run when it loads or the style sheet loads.
721 NS_ASSERTION(!mParserBlockingRequest,
722 "There can be only one parser-blocking script at a time");
723 NS_ASSERTION(mXSLTRequests.IsEmpty(),
724 "Parser-blocking scripts and XSLT scripts in the same doc!");
725 mParserBlockingRequest = request;
726 return true;
729 // inline script
730 // Is this document sandboxed without 'allow-scripts'?
731 if (mDocument->GetSandboxFlags() & SANDBOXED_SCRIPTS) {
732 return false;
735 // Does CSP allow this inline script to run?
736 if (!CSPAllowsInlineScript(aElement, mDocument)) {
737 return false;
740 // Inline scripts ignore ther CORS mode and are always CORS_NONE
741 request = new nsScriptLoadRequest(aElement, version, CORS_NONE);
742 request->mJSVersion = version;
743 request->mLoading = false;
744 request->mIsInline = true;
745 request->mURI = mDocument->GetDocumentURI();
746 request->mLineNo = aElement->GetScriptLineNumber();
748 if (aElement->GetParserCreated() == FROM_PARSER_XSLT &&
749 (!ReadyToExecuteScripts() || !mXSLTRequests.IsEmpty())) {
750 // Need to maintain order for XSLT-inserted scripts
751 NS_ASSERTION(!mParserBlockingRequest,
752 "Parser-blocking scripts and XSLT scripts in the same doc!");
753 mXSLTRequests.AppendElement(request);
754 return true;
756 if (aElement->GetParserCreated() == NOT_FROM_PARSER) {
757 NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
758 "A script-inserted script is inserted without an update batch?");
759 nsContentUtils::AddScriptRunner(new nsScriptRequestProcessor(this,
760 request));
761 return false;
763 if (aElement->GetParserCreated() == FROM_PARSER_NETWORK &&
764 !ReadyToExecuteScripts()) {
765 NS_ASSERTION(!mParserBlockingRequest,
766 "There can be only one parser-blocking script at a time");
767 mParserBlockingRequest = request;
768 NS_ASSERTION(mXSLTRequests.IsEmpty(),
769 "Parser-blocking scripts and XSLT scripts in the same doc!");
770 return true;
772 // We now have a document.written inline script or we have an inline script
773 // from the network but there is no style sheet that is blocking scripts.
774 // Don't check for style sheets blocking scripts in the document.write
775 // case to avoid style sheet network activity affecting when
776 // document.write returns. It's not really necessary to do this if
777 // there's no document.write currently on the call stack. However,
778 // this way matches IE more closely than checking if document.write
779 // is on the call stack.
780 NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
781 "Not safe to run a parser-inserted script?");
782 return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK;
785 namespace {
787 class NotifyOffThreadScriptLoadCompletedRunnable : public nsRunnable
789 nsRefPtr<nsScriptLoadRequest> mRequest;
790 nsRefPtr<nsScriptLoader> mLoader;
791 void *mToken;
793 public:
794 NotifyOffThreadScriptLoadCompletedRunnable(nsScriptLoadRequest* aRequest,
795 nsScriptLoader* aLoader)
796 : mRequest(aRequest), mLoader(aLoader), mToken(nullptr)
799 void SetToken(void* aToken) {
800 MOZ_ASSERT(aToken && !mToken);
801 mToken = aToken;
804 NS_DECL_NSIRUNNABLE
807 } /* anonymous namespace */
809 nsresult
810 nsScriptLoader::ProcessOffThreadRequest(nsScriptLoadRequest* aRequest, void **aOffThreadToken)
812 nsresult rv = ProcessRequest(aRequest, aOffThreadToken);
813 mDocument->UnblockOnload(false);
814 return rv;
817 NS_IMETHODIMP
818 NotifyOffThreadScriptLoadCompletedRunnable::Run()
820 MOZ_ASSERT(NS_IsMainThread());
822 // We want these to be dropped on the main thread, once we return from this
823 // function.
824 nsRefPtr<nsScriptLoadRequest> request = mRequest.forget();
825 nsRefPtr<nsScriptLoader> loader = mLoader.forget();
827 nsresult rv = loader->ProcessOffThreadRequest(request, &mToken);
829 if (mToken) {
830 // The result of the off thread parse was not actually needed to process
831 // the request (disappearing window, some other error, ...). Finish the
832 // request to avoid leaks in the JS engine.
833 nsCOMPtr<nsIJSRuntimeService> svc = do_GetService("@mozilla.org/js/xpc/RuntimeService;1");
834 NS_ENSURE_TRUE(svc, NS_ERROR_FAILURE);
835 JSRuntime *rt;
836 svc->GetRuntime(&rt);
837 NS_ENSURE_TRUE(rt, NS_ERROR_FAILURE);
838 JS::FinishOffThreadScript(nullptr, rt, mToken);
841 return rv;
844 static void
845 OffThreadScriptLoaderCallback(void *aToken, void *aCallbackData)
847 nsRefPtr<NotifyOffThreadScriptLoadCompletedRunnable> aRunnable =
848 dont_AddRef(static_cast<NotifyOffThreadScriptLoadCompletedRunnable*>(aCallbackData));
849 aRunnable->SetToken(aToken);
850 NS_DispatchToMainThread(aRunnable);
853 nsresult
854 nsScriptLoader::AttemptAsyncScriptParse(nsScriptLoadRequest* aRequest)
856 if (!aRequest->mElement->GetScriptAsync() || aRequest->mIsInline) {
857 return NS_ERROR_FAILURE;
860 nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
861 if (!globalObject) {
862 return NS_ERROR_FAILURE;
865 AutoJSAPI jsapi;
866 if (!jsapi.InitWithLegacyErrorReporting(globalObject)) {
867 return NS_ERROR_FAILURE;
870 JSContext* cx = jsapi.cx();
871 JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
872 JS::CompileOptions options(cx);
873 FillCompileOptionsForRequest(jsapi, aRequest, global, &options);
875 if (!JS::CanCompileOffThread(cx, options, aRequest->mScriptTextLength)) {
876 return NS_ERROR_FAILURE;
879 nsRefPtr<NotifyOffThreadScriptLoadCompletedRunnable> runnable =
880 new NotifyOffThreadScriptLoadCompletedRunnable(aRequest, this);
882 if (!JS::CompileOffThread(cx, options,
883 aRequest->mScriptTextBuf, aRequest->mScriptTextLength,
884 OffThreadScriptLoaderCallback,
885 static_cast<void*>(runnable))) {
886 return NS_ERROR_OUT_OF_MEMORY;
889 mDocument->BlockOnload();
891 unused << runnable.forget();
892 return NS_OK;
895 nsresult
896 nsScriptLoader::ProcessRequest(nsScriptLoadRequest* aRequest, void **aOffThreadToken)
898 NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
899 "Processing requests when running scripts is unsafe.");
901 if (!aOffThreadToken) {
902 nsresult rv = AttemptAsyncScriptParse(aRequest);
903 if (rv != NS_ERROR_FAILURE)
904 return rv;
907 NS_ENSURE_ARG(aRequest);
908 nsAutoString textData;
909 const char16_t* scriptBuf = nullptr;
910 size_t scriptLength = 0;
911 JS::SourceBufferHolder::Ownership giveScriptOwnership =
912 JS::SourceBufferHolder::NoOwnership;
914 nsCOMPtr<nsIDocument> doc;
916 nsCOMPtr<nsINode> scriptElem = do_QueryInterface(aRequest->mElement);
918 // If there's no script text, we try to get it from the element
919 if (aRequest->mIsInline) {
920 // XXX This is inefficient - GetText makes multiple
921 // copies.
922 aRequest->mElement->GetScriptText(textData);
924 scriptBuf = textData.get();
925 scriptLength = textData.Length();
926 giveScriptOwnership = JS::SourceBufferHolder::NoOwnership;
928 else {
929 scriptBuf = aRequest->mScriptTextBuf;
930 scriptLength = aRequest->mScriptTextLength;
932 giveScriptOwnership = JS::SourceBufferHolder::GiveOwnership;
933 aRequest->mScriptTextBuf = nullptr;
934 aRequest->mScriptTextLength = 0;
936 doc = scriptElem->OwnerDoc();
939 JS::SourceBufferHolder srcBuf(scriptBuf, scriptLength, giveScriptOwnership);
941 nsCOMPtr<nsIScriptElement> oldParserInsertedScript;
942 uint32_t parserCreated = aRequest->mElement->GetParserCreated();
943 if (parserCreated) {
944 oldParserInsertedScript = mCurrentParserInsertedScript;
945 mCurrentParserInsertedScript = aRequest->mElement;
948 FireScriptAvailable(NS_OK, aRequest);
950 // The window may have gone away by this point, in which case there's no point
951 // in trying to run the script.
952 nsCOMPtr<nsIDocument> master = mDocument->MasterDocument();
953 nsPIDOMWindow *pwin = master->GetInnerWindow();
954 bool runScript = !!pwin;
955 if (runScript) {
956 nsContentUtils::DispatchTrustedEvent(scriptElem->OwnerDoc(),
957 scriptElem,
958 NS_LITERAL_STRING("beforescriptexecute"),
959 true, true, &runScript);
962 // Inner window could have gone away after firing beforescriptexecute
963 pwin = master->GetInnerWindow();
964 if (!pwin) {
965 runScript = false;
968 nsresult rv = NS_OK;
969 if (runScript) {
970 if (doc) {
971 doc->BeginEvaluatingExternalScript();
973 aRequest->mElement->BeginEvaluating();
974 rv = EvaluateScript(aRequest, srcBuf, aOffThreadToken);
975 aRequest->mElement->EndEvaluating();
976 if (doc) {
977 doc->EndEvaluatingExternalScript();
980 nsContentUtils::DispatchTrustedEvent(scriptElem->OwnerDoc(),
981 scriptElem,
982 NS_LITERAL_STRING("afterscriptexecute"),
983 true, false);
986 FireScriptEvaluated(rv, aRequest);
988 if (parserCreated) {
989 mCurrentParserInsertedScript = oldParserInsertedScript;
992 return rv;
995 void
996 nsScriptLoader::FireScriptAvailable(nsresult aResult,
997 nsScriptLoadRequest* aRequest)
999 for (int32_t i = 0; i < mObservers.Count(); i++) {
1000 nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i];
1001 obs->ScriptAvailable(aResult, aRequest->mElement,
1002 aRequest->mIsInline, aRequest->mURI,
1003 aRequest->mLineNo);
1006 aRequest->FireScriptAvailable(aResult);
1009 void
1010 nsScriptLoader::FireScriptEvaluated(nsresult aResult,
1011 nsScriptLoadRequest* aRequest)
1013 for (int32_t i = 0; i < mObservers.Count(); i++) {
1014 nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i];
1015 obs->ScriptEvaluated(aResult, aRequest->mElement,
1016 aRequest->mIsInline);
1019 aRequest->FireScriptEvaluated(aResult);
1022 already_AddRefed<nsIScriptGlobalObject>
1023 nsScriptLoader::GetScriptGlobalObject()
1025 nsCOMPtr<nsIDocument> master = mDocument->MasterDocument();
1026 nsPIDOMWindow *pwin = master->GetInnerWindow();
1027 if (!pwin) {
1028 return nullptr;
1031 nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(pwin);
1032 NS_ASSERTION(globalObject, "windows must be global objects");
1034 // and make sure we are setup for this type of script.
1035 nsresult rv = globalObject->EnsureScriptEnvironment();
1036 if (NS_FAILED(rv)) {
1037 return nullptr;
1040 return globalObject.forget();
1043 void
1044 nsScriptLoader::FillCompileOptionsForRequest(const AutoJSAPI &jsapi,
1045 nsScriptLoadRequest *aRequest,
1046 JS::Handle<JSObject *> aScopeChain,
1047 JS::CompileOptions *aOptions)
1049 // It's very important to use aRequest->mURI, not the final URI of the channel
1050 // aRequest ended up getting script data from, as the script filename.
1051 nsContentUtils::GetWrapperSafeScriptFilename(mDocument, aRequest->mURI, aRequest->mURL);
1053 aOptions->setIntroductionType("scriptElement");
1054 aOptions->setFileAndLine(aRequest->mURL.get(), aRequest->mLineNo);
1055 aOptions->setVersion(JSVersion(aRequest->mJSVersion));
1056 aOptions->setCompileAndGo(JS_IsGlobalObject(aScopeChain));
1057 // We only need the setNoScriptRval bit when compiling off-thread here, since
1058 // otherwise nsJSUtils::EvaluateString will set it up for us.
1059 aOptions->setNoScriptRval(true);
1060 if (aRequest->mHasSourceMapURL) {
1061 aOptions->setSourceMapURL(aRequest->mSourceMapURL.get());
1063 if (aRequest->mOriginPrincipal) {
1064 nsIPrincipal* scriptPrin = nsContentUtils::ObjectPrincipal(aScopeChain);
1065 bool subsumes = scriptPrin->Subsumes(aRequest->mOriginPrincipal);
1066 aOptions->setMutedErrors(!subsumes);
1069 JSContext* cx = jsapi.cx();
1070 JS::Rooted<JS::Value> elementVal(cx);
1071 MOZ_ASSERT(aRequest->mElement);
1072 if (NS_SUCCEEDED(nsContentUtils::WrapNative(cx, aRequest->mElement,
1073 &elementVal,
1074 /* aAllowWrapping = */ true))) {
1075 MOZ_ASSERT(elementVal.isObject());
1076 aOptions->setElement(&elementVal.toObject());
1080 nsresult
1081 nsScriptLoader::EvaluateScript(nsScriptLoadRequest* aRequest,
1082 JS::SourceBufferHolder& aSrcBuf,
1083 void** aOffThreadToken)
1085 // We need a document to evaluate scripts.
1086 if (!mDocument) {
1087 return NS_ERROR_FAILURE;
1090 nsCOMPtr<nsIContent> scriptContent(do_QueryInterface(aRequest->mElement));
1091 nsIDocument* ownerDoc = scriptContent->OwnerDoc();
1092 if (ownerDoc != mDocument) {
1093 // Willful violation of HTML5 as of 2010-12-01
1094 return NS_ERROR_FAILURE;
1097 // Get the script-type to be used by this element.
1098 NS_ASSERTION(scriptContent, "no content - what is default script-type?");
1100 nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
1101 if (!globalObject) {
1102 return NS_ERROR_FAILURE;
1105 // Make sure context is a strong reference since we access it after
1106 // we've executed a script, which may cause all other references to
1107 // the context to go away.
1108 nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext();
1109 if (!context) {
1110 return NS_ERROR_FAILURE;
1113 JSVersion version = JSVersion(aRequest->mJSVersion);
1114 if (version == JSVERSION_UNKNOWN) {
1115 return NS_OK;
1118 // New script entry point required, due to the "Create a script" sub-step of
1119 // http://www.whatwg.org/specs/web-apps/current-work/#execute-the-script-block
1120 AutoEntryScript entryScript(globalObject, true, context->GetNativeContext());
1121 JS::Rooted<JSObject*> global(entryScript.cx(),
1122 globalObject->GetGlobalJSObject());
1124 bool oldProcessingScriptTag = context->GetProcessingScriptTag();
1125 context->SetProcessingScriptTag(true);
1126 nsresult rv;
1128 // Update our current script.
1129 AutoCurrentScriptUpdater scriptUpdater(this, aRequest->mElement);
1130 Maybe<AutoCurrentScriptUpdater> masterScriptUpdater;
1131 nsCOMPtr<nsIDocument> master = mDocument->MasterDocument();
1132 if (master != mDocument) {
1133 // If this script belongs to an import document, it will be
1134 // executed in the context of the master document. During the
1135 // execution currentScript of the master should refer to this
1136 // script. So let's update the mCurrentScript of the ScriptLoader
1137 // of the master document too.
1138 masterScriptUpdater.emplace(master->ScriptLoader(),
1139 aRequest->mElement);
1142 JS::CompileOptions options(entryScript.cx());
1143 FillCompileOptionsForRequest(entryScript, aRequest, global, &options);
1144 rv = nsJSUtils::EvaluateString(entryScript.cx(), aSrcBuf, global, options,
1145 aOffThreadToken);
1148 context->SetProcessingScriptTag(oldProcessingScriptTag);
1149 return rv;
1152 void
1153 nsScriptLoader::ProcessPendingRequestsAsync()
1155 if (mParserBlockingRequest || !mPendingChildLoaders.IsEmpty()) {
1156 nsCOMPtr<nsIRunnable> ev = NS_NewRunnableMethod(this,
1157 &nsScriptLoader::ProcessPendingRequests);
1159 NS_DispatchToCurrentThread(ev);
1163 void
1164 nsScriptLoader::ProcessPendingRequests()
1166 nsRefPtr<nsScriptLoadRequest> request;
1167 if (mParserBlockingRequest &&
1168 !mParserBlockingRequest->mLoading &&
1169 ReadyToExecuteScripts()) {
1170 request.swap(mParserBlockingRequest);
1171 UnblockParser(request);
1172 ProcessRequest(request);
1173 ContinueParserAsync(request);
1176 while (ReadyToExecuteScripts() &&
1177 !mXSLTRequests.IsEmpty() &&
1178 !mXSLTRequests[0]->mLoading) {
1179 request.swap(mXSLTRequests[0]);
1180 mXSLTRequests.RemoveElementAt(0);
1181 ProcessRequest(request);
1184 uint32_t i = 0;
1185 while (mEnabled && i < mAsyncRequests.Length()) {
1186 if (!mAsyncRequests[i]->mLoading) {
1187 request.swap(mAsyncRequests[i]);
1188 mAsyncRequests.RemoveElementAt(i);
1189 ProcessRequest(request);
1190 continue;
1192 ++i;
1195 while (mEnabled && !mNonAsyncExternalScriptInsertedRequests.IsEmpty() &&
1196 !mNonAsyncExternalScriptInsertedRequests[0]->mLoading) {
1197 // Violate the HTML5 spec and execute these in the insertion order in
1198 // order to make LABjs and the "order" plug-in for RequireJS work with
1199 // their Gecko-sniffed code path. See
1200 // http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html
1201 request.swap(mNonAsyncExternalScriptInsertedRequests[0]);
1202 mNonAsyncExternalScriptInsertedRequests.RemoveElementAt(0);
1203 ProcessRequest(request);
1206 if (mDocumentParsingDone && mXSLTRequests.IsEmpty()) {
1207 while (!mDeferRequests.IsEmpty() && !mDeferRequests[0]->mLoading) {
1208 request.swap(mDeferRequests[0]);
1209 mDeferRequests.RemoveElementAt(0);
1210 ProcessRequest(request);
1214 while (!mPendingChildLoaders.IsEmpty() && ReadyToExecuteScripts()) {
1215 nsRefPtr<nsScriptLoader> child = mPendingChildLoaders[0];
1216 mPendingChildLoaders.RemoveElementAt(0);
1217 child->RemoveExecuteBlocker();
1220 if (mDocumentParsingDone && mDocument &&
1221 !mParserBlockingRequest && mAsyncRequests.IsEmpty() &&
1222 mNonAsyncExternalScriptInsertedRequests.IsEmpty() &&
1223 mXSLTRequests.IsEmpty() && mDeferRequests.IsEmpty()) {
1224 if (MaybeRemovedDeferRequests()) {
1225 return ProcessPendingRequests();
1227 // No more pending scripts; time to unblock onload.
1228 // OK to unblock onload synchronously here, since callers must be
1229 // prepared for the world changing anyway.
1230 mDocumentParsingDone = false;
1231 mDocument->UnblockOnload(true);
1235 bool
1236 nsScriptLoader::ReadyToExecuteScripts()
1238 // Make sure the SelfReadyToExecuteScripts check is first, so that
1239 // we don't block twice on an ancestor.
1240 if (!SelfReadyToExecuteScripts()) {
1241 return false;
1244 for (nsIDocument* doc = mDocument; doc; doc = doc->GetParentDocument()) {
1245 nsScriptLoader* ancestor = doc->ScriptLoader();
1246 if (!ancestor->SelfReadyToExecuteScripts() &&
1247 ancestor->AddPendingChildLoader(this)) {
1248 AddExecuteBlocker();
1249 return false;
1253 if (mDocument && !mDocument->IsMasterDocument()) {
1254 nsRefPtr<ImportManager> im = mDocument->ImportManager();
1255 nsRefPtr<ImportLoader> loader = im->Find(mDocument);
1256 MOZ_ASSERT(loader, "How can we have an import document without a loader?");
1258 // The referring link that counts in the execution order calculation
1259 // (in spec: flagged as branch)
1260 nsCOMPtr<nsINode> referrer = loader->GetMainReferrer();
1261 MOZ_ASSERT(referrer, "There has to be a main referring link for each imports");
1263 // Import documents are blocked by their import predecessors. We need to
1264 // wait with script execution until all the predecessors are done.
1265 // Technically it means we have to wait for the last one to finish,
1266 // which is the neares one to us in the order.
1267 nsRefPtr<ImportLoader> lastPred = im->GetNearestPredecessor(referrer);
1268 if (!lastPred) {
1269 // If there is no predecessor we can run.
1270 return true;
1273 nsCOMPtr<nsIDocument> doc = lastPred->GetDocument();
1274 if (lastPred->IsBlocking() || !doc || (doc && !doc->ScriptLoader()->SelfReadyToExecuteScripts())) {
1275 // Document has not been created yet or it was created but not ready.
1276 // Either case we are blocked by it. The ImportLoader will take care
1277 // of blocking us, and adding the pending child loader to the blocking
1278 // ScriptLoader when it's possible (at this point the blocking loader
1279 // might not have created the document/ScriptLoader)
1280 lastPred->AddBlockedScriptLoader(this);
1281 // As more imports are parsed, this can change, let's cache what we
1282 // blocked, so it can be later updated if needed (see: ImportLoader::Updater).
1283 loader->SetBlockingPredecessor(lastPred);
1284 return false;
1288 return true;
1291 // This function was copied from nsParser.cpp. It was simplified a bit.
1292 static bool
1293 DetectByteOrderMark(const unsigned char* aBytes, int32_t aLen, nsCString& oCharset)
1295 if (aLen < 2)
1296 return false;
1298 switch(aBytes[0]) {
1299 case 0xEF:
1300 if (aLen >= 3 && 0xBB == aBytes[1] && 0xBF == aBytes[2]) {
1301 // EF BB BF
1302 // Win2K UTF-8 BOM
1303 oCharset.AssignLiteral("UTF-8");
1305 break;
1306 case 0xFE:
1307 if (0xFF == aBytes[1]) {
1308 // FE FF
1309 // UTF-16, big-endian
1310 oCharset.AssignLiteral("UTF-16BE");
1312 break;
1313 case 0xFF:
1314 if (0xFE == aBytes[1]) {
1315 // FF FE
1316 // UTF-16, little-endian
1317 oCharset.AssignLiteral("UTF-16LE");
1319 break;
1321 return !oCharset.IsEmpty();
1324 /* static */ nsresult
1325 nsScriptLoader::ConvertToUTF16(nsIChannel* aChannel, const uint8_t* aData,
1326 uint32_t aLength, const nsAString& aHintCharset,
1327 nsIDocument* aDocument,
1328 char16_t*& aBufOut, size_t& aLengthOut)
1330 if (!aLength) {
1331 aBufOut = nullptr;
1332 aLengthOut = 0;
1333 return NS_OK;
1336 // The encoding info precedence is as follows from high to low:
1337 // The BOM
1338 // HTTP Content-Type (if name recognized)
1339 // charset attribute (if name recognized)
1340 // The encoding of the document
1342 nsAutoCString charset;
1344 nsCOMPtr<nsIUnicodeDecoder> unicodeDecoder;
1346 if (DetectByteOrderMark(aData, aLength, charset)) {
1347 // charset is now "UTF-8" or "UTF-16". The UTF-16 decoder will re-sniff
1348 // the BOM for endianness. Both the UTF-16 and the UTF-8 decoder will
1349 // take care of swallowing the BOM.
1350 unicodeDecoder = EncodingUtils::DecoderForEncoding(charset);
1353 if (!unicodeDecoder &&
1354 aChannel &&
1355 NS_SUCCEEDED(aChannel->GetContentCharset(charset)) &&
1356 EncodingUtils::FindEncodingForLabel(charset, charset)) {
1357 unicodeDecoder = EncodingUtils::DecoderForEncoding(charset);
1360 if (!unicodeDecoder &&
1361 EncodingUtils::FindEncodingForLabel(aHintCharset, charset)) {
1362 unicodeDecoder = EncodingUtils::DecoderForEncoding(charset);
1365 if (!unicodeDecoder && aDocument) {
1366 charset = aDocument->GetDocumentCharacterSet();
1367 unicodeDecoder = EncodingUtils::DecoderForEncoding(charset);
1370 if (!unicodeDecoder) {
1371 // Curiously, there are various callers that don't pass aDocument. The
1372 // fallback in the old code was ISO-8859-1, which behaved like
1373 // windows-1252. Saying windows-1252 for clarity and for compliance
1374 // with the Encoding Standard.
1375 unicodeDecoder = EncodingUtils::DecoderForEncoding("windows-1252");
1378 int32_t unicodeLength = 0;
1380 nsresult rv =
1381 unicodeDecoder->GetMaxLength(reinterpret_cast<const char*>(aData),
1382 aLength, &unicodeLength);
1383 NS_ENSURE_SUCCESS(rv, rv);
1385 aBufOut = static_cast<char16_t*>(js_malloc(unicodeLength * sizeof(char16_t)));
1386 if (!aBufOut) {
1387 aLengthOut = 0;
1388 return NS_ERROR_OUT_OF_MEMORY;
1390 aLengthOut = unicodeLength;
1392 rv = unicodeDecoder->Convert(reinterpret_cast<const char*>(aData),
1393 (int32_t *) &aLength, aBufOut,
1394 &unicodeLength);
1395 MOZ_ASSERT(NS_SUCCEEDED(rv));
1396 aLengthOut = unicodeLength;
1397 if (NS_FAILED(rv)) {
1398 js_free(aBufOut);
1399 aBufOut = nullptr;
1400 aLengthOut = 0;
1402 return rv;
1405 NS_IMETHODIMP
1406 nsScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader,
1407 nsISupports* aContext,
1408 nsresult aStatus,
1409 uint32_t aStringLen,
1410 const uint8_t* aString)
1412 nsScriptLoadRequest* request = static_cast<nsScriptLoadRequest*>(aContext);
1413 NS_ASSERTION(request, "null request in stream complete handler");
1414 NS_ENSURE_TRUE(request, NS_ERROR_FAILURE);
1416 nsresult rv = PrepareLoadedRequest(request, aLoader, aStatus, aStringLen,
1417 aString);
1418 if (NS_FAILED(rv)) {
1420 * Handle script not loading error because source was a tracking URL.
1421 * We make a note of this script node by including it in a dedicated
1422 * array of blocked tracking nodes under its parent document.
1424 if (rv == NS_ERROR_TRACKING_URI) {
1425 nsCOMPtr<nsIContent> cont = do_QueryInterface(request->mElement);
1426 mDocument->AddBlockedTrackingNode(cont);
1429 if (mDeferRequests.RemoveElement(request) ||
1430 mAsyncRequests.RemoveElement(request) ||
1431 mNonAsyncExternalScriptInsertedRequests.RemoveElement(request) ||
1432 mXSLTRequests.RemoveElement(request)) {
1433 FireScriptAvailable(rv, request);
1434 } else if (mParserBlockingRequest == request) {
1435 mParserBlockingRequest = nullptr;
1436 UnblockParser(request);
1437 FireScriptAvailable(rv, request);
1438 ContinueParserAsync(request);
1439 } else {
1440 mPreloads.RemoveElement(request, PreloadRequestComparator());
1442 rv = NS_OK;
1443 } else {
1444 moz_free(const_cast<uint8_t *>(aString));
1445 rv = NS_SUCCESS_ADOPTED_DATA;
1448 // Process our request and/or any pending ones
1449 ProcessPendingRequests();
1451 return rv;
1454 void
1455 nsScriptLoader::UnblockParser(nsScriptLoadRequest* aParserBlockingRequest)
1457 aParserBlockingRequest->mElement->UnblockParser();
1460 void
1461 nsScriptLoader::ContinueParserAsync(nsScriptLoadRequest* aParserBlockingRequest)
1463 aParserBlockingRequest->mElement->ContinueParserAsync();
1466 nsresult
1467 nsScriptLoader::PrepareLoadedRequest(nsScriptLoadRequest* aRequest,
1468 nsIStreamLoader* aLoader,
1469 nsresult aStatus,
1470 uint32_t aStringLen,
1471 const uint8_t* aString)
1473 if (NS_FAILED(aStatus)) {
1474 return aStatus;
1477 // If we don't have a document, then we need to abort further
1478 // evaluation.
1479 if (!mDocument) {
1480 return NS_ERROR_NOT_AVAILABLE;
1483 // If the load returned an error page, then we need to abort
1484 nsCOMPtr<nsIRequest> req;
1485 nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
1486 NS_ASSERTION(req, "StreamLoader's request went away prematurely");
1487 NS_ENSURE_SUCCESS(rv, rv);
1489 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(req);
1490 if (httpChannel) {
1491 bool requestSucceeded;
1492 rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
1493 if (NS_SUCCEEDED(rv) && !requestSucceeded) {
1494 return NS_ERROR_NOT_AVAILABLE;
1497 nsAutoCString sourceMapURL;
1498 rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("X-SourceMap"), sourceMapURL);
1499 if (NS_SUCCEEDED(rv)) {
1500 aRequest->mHasSourceMapURL = true;
1501 aRequest->mSourceMapURL = NS_ConvertUTF8toUTF16(sourceMapURL);
1505 nsCOMPtr<nsIChannel> channel = do_QueryInterface(req);
1506 // If this load was subject to a CORS check; don't flag it with a
1507 // separate origin principal, so that it will treat our document's
1508 // principal as the origin principal
1509 if (aRequest->mCORSMode == CORS_NONE) {
1510 rv = nsContentUtils::GetSecurityManager()->
1511 GetChannelResultPrincipal(channel, getter_AddRefs(aRequest->mOriginPrincipal));
1512 NS_ENSURE_SUCCESS(rv, rv);
1515 if (aStringLen) {
1516 // Check the charset attribute to determine script charset.
1517 nsAutoString hintCharset;
1518 if (!aRequest->IsPreload()) {
1519 aRequest->mElement->GetScriptCharset(hintCharset);
1520 } else {
1521 nsTArray<PreloadInfo>::index_type i =
1522 mPreloads.IndexOf(aRequest, 0, PreloadRequestComparator());
1523 NS_ASSERTION(i != mPreloads.NoIndex, "Incorrect preload bookkeeping");
1524 hintCharset = mPreloads[i].mCharset;
1526 rv = ConvertToUTF16(channel, aString, aStringLen, hintCharset, mDocument,
1527 aRequest->mScriptTextBuf, aRequest->mScriptTextLength);
1529 NS_ENSURE_SUCCESS(rv, rv);
1532 // This assertion could fire errorously if we ran out of memory when
1533 // inserting the request in the array. However it's an unlikely case
1534 // so if you see this assertion it is likely something else that is
1535 // wrong, especially if you see it more than once.
1536 NS_ASSERTION(mDeferRequests.Contains(aRequest) ||
1537 mAsyncRequests.Contains(aRequest) ||
1538 mNonAsyncExternalScriptInsertedRequests.Contains(aRequest) ||
1539 mXSLTRequests.Contains(aRequest) ||
1540 mPreloads.Contains(aRequest, PreloadRequestComparator()) ||
1541 mParserBlockingRequest,
1542 "aRequest should be pending!");
1544 // Mark this as loaded
1545 aRequest->mLoading = false;
1547 return NS_OK;
1550 void
1551 nsScriptLoader::ParsingComplete(bool aTerminated)
1553 if (mDeferEnabled) {
1554 // Have to check because we apparently get ParsingComplete
1555 // without BeginDeferringScripts in some cases
1556 mDocumentParsingDone = true;
1558 mDeferEnabled = false;
1559 if (aTerminated) {
1560 mDeferRequests.Clear();
1561 mAsyncRequests.Clear();
1562 mNonAsyncExternalScriptInsertedRequests.Clear();
1563 mXSLTRequests.Clear();
1564 mParserBlockingRequest = nullptr;
1567 // Have to call this even if aTerminated so we'll correctly unblock
1568 // onload and all.
1569 ProcessPendingRequests();
1572 void
1573 nsScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset,
1574 const nsAString &aType,
1575 const nsAString &aCrossOrigin,
1576 bool aScriptFromHead,
1577 const mozilla::net::ReferrerPolicy aReferrerPolicy)
1579 // Check to see if scripts has been turned off.
1580 if (!mEnabled || !mDocument->IsScriptEnabled()) {
1581 return;
1584 nsRefPtr<nsScriptLoadRequest> request =
1585 new nsScriptLoadRequest(nullptr, 0,
1586 Element::StringToCORSMode(aCrossOrigin));
1587 request->mURI = aURI;
1588 request->mIsInline = false;
1589 request->mLoading = true;
1590 request->mReferrerPolicy = aReferrerPolicy;
1592 nsresult rv = StartLoad(request, aType, aScriptFromHead);
1593 if (NS_FAILED(rv)) {
1594 return;
1597 PreloadInfo *pi = mPreloads.AppendElement();
1598 pi->mRequest = request;
1599 pi->mCharset = aCharset;
1602 void
1603 nsScriptLoader::AddDeferRequest(nsScriptLoadRequest* aRequest)
1605 mDeferRequests.AppendElement(aRequest);
1606 if (mDeferEnabled && mDeferRequests.Length() == 1 && mDocument &&
1607 !mBlockingDOMContentLoaded) {
1608 MOZ_ASSERT(mDocument->GetReadyStateEnum() == nsIDocument::READYSTATE_LOADING);
1609 mBlockingDOMContentLoaded = true;
1610 mDocument->BlockDOMContentLoaded();
1614 bool
1615 nsScriptLoader::MaybeRemovedDeferRequests()
1617 if (mDeferRequests.Length() == 0 && mDocument &&
1618 mBlockingDOMContentLoaded) {
1619 mBlockingDOMContentLoaded = false;
1620 mDocument->UnblockDOMContentLoaded();
1621 return true;
1623 return false;