content/base/src/nsScriptLoader.cpp

Thu, 15 Jan 2015 21:03:48 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 21:03:48 +0100
branch
TOR_BUG_9701
changeset 11
deefc01c0e14
permissions
-rw-r--r--

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)
  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);
  1007   aRequest->FireScriptEvaluated(aResult);
  1010 already_AddRefed<nsIScriptGlobalObject>
  1011 nsScriptLoader::GetScriptGlobalObject()
  1013   nsPIDOMWindow *pwin = mDocument->GetInnerWindow();
  1014   if (!pwin) {
  1015     return nullptr;
  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;
  1027   return globalObject.forget();
  1030 void
  1031 nsScriptLoader::FillCompileOptionsForRequest(nsScriptLoadRequest *aRequest,
  1032                                              JS::Handle<JSObject *> aScopeChain,
  1033                                              JS::CompileOptions *aOptions)
  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());
  1046   if (aRequest->mOriginPrincipal) {
  1047     aOptions->setOriginPrincipals(nsJSPrincipals::get(aRequest->mOriginPrincipal));
  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());
  1067 nsresult
  1068 nsScriptLoader::EvaluateScript(nsScriptLoadRequest* aRequest,
  1069                                JS::SourceBufferHolder& aSrcBuf,
  1070                                void** aOffThreadToken)
  1072   // We need a document to evaluate scripts.
  1073   if (!mDocument) {
  1074     return NS_ERROR_FAILURE;
  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;
  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;
  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;
  1100   JSVersion version = JSVersion(aRequest->mJSVersion);
  1101   if (version == JSVERSION_UNKNOWN) {
  1102     return NS_OK;
  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;
  1130 void
  1131 nsScriptLoader::ProcessPendingRequestsAsync()
  1133   if (mParserBlockingRequest || !mPendingChildLoaders.IsEmpty()) {
  1134     nsCOMPtr<nsIRunnable> ev = NS_NewRunnableMethod(this,
  1135       &nsScriptLoader::ProcessPendingRequests);
  1137     NS_DispatchToCurrentThread(ev);
  1141 void
  1142 nsScriptLoader::ProcessPendingRequests()
  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);
  1154   while (ReadyToExecuteScripts() && 
  1155          !mXSLTRequests.IsEmpty() && 
  1156          !mXSLTRequests[0]->mLoading) {
  1157     request.swap(mXSLTRequests[0]);
  1158     mXSLTRequests.RemoveElementAt(0);
  1159     ProcessRequest(request);
  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;
  1170     ++i;
  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);
  1184   if (mDocumentParsingDone && mXSLTRequests.IsEmpty()) {
  1185     while (!mDeferRequests.IsEmpty() && !mDeferRequests[0]->mLoading) {
  1186       request.swap(mDeferRequests[0]);
  1187       mDeferRequests.RemoveElementAt(0);
  1188       ProcessRequest(request);
  1192   while (!mPendingChildLoaders.IsEmpty() && ReadyToExecuteScripts()) {
  1193     nsRefPtr<nsScriptLoader> child = mPendingChildLoaders[0];
  1194     mPendingChildLoaders.RemoveElementAt(0);
  1195     child->RemoveExecuteBlocker();
  1198   if (mDocumentParsingDone && mDocument &&
  1199       !mParserBlockingRequest && mAsyncRequests.IsEmpty() &&
  1200       mNonAsyncExternalScriptInsertedRequests.IsEmpty() &&
  1201       mXSLTRequests.IsEmpty() && mDeferRequests.IsEmpty()) {
  1202     if (MaybeRemovedDeferRequests()) {
  1203       return ProcessPendingRequests();
  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);
  1213 bool
  1214 nsScriptLoader::ReadyToExecuteScripts()
  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;
  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;
  1231   return true;
  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)
  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");
  1249     break;
  1250   case 0xFE:
  1251     if (0xFF == aBytes[1]) {
  1252       // FE FF
  1253       // UTF-16, big-endian
  1254       oCharset.Assign("UTF-16BE");
  1256     break;
  1257   case 0xFF:
  1258     if (0xFE == aBytes[1]) {
  1259       // FF FE
  1260       // UTF-16, little-endian
  1261       oCharset.Assign("UTF-16LE");
  1263     break;
  1265   return !oCharset.IsEmpty();
  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)
  1274   if (!aLength) {
  1275     aBufOut = nullptr;
  1276     aLengthOut = 0;
  1277     return NS_OK;
  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);
  1297   if (!unicodeDecoder &&
  1298       aChannel &&
  1299       NS_SUCCEEDED(aChannel->GetContentCharset(charset)) &&
  1300       EncodingUtils::FindEncodingForLabel(charset, charset)) {
  1301     unicodeDecoder = EncodingUtils::DecoderForEncoding(charset);
  1304   if (!unicodeDecoder &&
  1305       EncodingUtils::FindEncodingForLabel(aHintCharset, charset)) {
  1306     unicodeDecoder = EncodingUtils::DecoderForEncoding(charset);
  1309   if (!unicodeDecoder && aDocument) {
  1310     charset = aDocument->GetDocumentCharacterSet();
  1311     unicodeDecoder = EncodingUtils::DecoderForEncoding(charset);
  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");
  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;
  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;
  1346   return rv;
  1349 NS_IMETHODIMP
  1350 nsScriptLoader::OnStreamComplete(nsIStreamLoader* aLoader,
  1351                                  nsISupports* aContext,
  1352                                  nsresult aStatus,
  1353                                  uint32_t aStringLen,
  1354                                  const uint8_t* aString)
  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());
  1376     rv = NS_OK;
  1377   } else {
  1378     NS_Free(const_cast<uint8_t *>(aString));
  1379     rv = NS_SUCCESS_ADOPTED_DATA;
  1382   // Process our request and/or any pending ones
  1383   ProcessPendingRequests();
  1385   return rv;
  1388 void
  1389 nsScriptLoader::UnblockParser(nsScriptLoadRequest* aParserBlockingRequest)
  1391   aParserBlockingRequest->mElement->UnblockParser();
  1394 void
  1395 nsScriptLoader::ContinueParserAsync(nsScriptLoadRequest* aParserBlockingRequest)
  1397   aParserBlockingRequest->mElement->ContinueParserAsync();
  1400 nsresult
  1401 nsScriptLoader::PrepareLoadedRequest(nsScriptLoadRequest* aRequest,
  1402                                      nsIStreamLoader* aLoader,
  1403                                      nsresult aStatus,
  1404                                      uint32_t aStringLen,
  1405                                      const uint8_t* aString)
  1407   if (NS_FAILED(aStatus)) {
  1408     return aStatus;
  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;
  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;
  1431     nsAutoCString sourceMapURL;
  1432     httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("X-SourceMap"), sourceMapURL);
  1433     aRequest->mHasSourceMapURL = true;
  1434     aRequest->mSourceMapURL = NS_ConvertUTF8toUTF16(sourceMapURL);
  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);
  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;
  1458     rv = ConvertToUTF16(channel, aString, aStringLen, hintCharset, mDocument,
  1459                         aRequest->mScriptTextBuf, aRequest->mScriptTextLength);
  1461     NS_ENSURE_SUCCESS(rv, rv);
  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;
  1482 void
  1483 nsScriptLoader::ParsingComplete(bool aTerminated)
  1485   if (mDeferEnabled) {
  1486     // Have to check because we apparently get ParsingComplete
  1487     // without BeginDeferringScripts in some cases
  1488     mDocumentParsingDone = true;
  1490   mDeferEnabled = false;
  1491   if (aTerminated) {
  1492     mDeferRequests.Clear();
  1493     mAsyncRequests.Clear();
  1494     mNonAsyncExternalScriptInsertedRequests.Clear();
  1495     mXSLTRequests.Clear();
  1496     mParserBlockingRequest = nullptr;
  1499   // Have to call this even if aTerminated so we'll correctly unblock
  1500   // onload and all.
  1501   ProcessPendingRequests();
  1504 void
  1505 nsScriptLoader::PreloadURI(nsIURI *aURI, const nsAString &aCharset,
  1506                            const nsAString &aType,
  1507                            const nsAString &aCrossOrigin,
  1508                            bool aScriptFromHead)
  1510   // Check to see if scripts has been turned off.
  1511   if (!mEnabled || !mDocument->IsScriptEnabled()) {
  1512     return;
  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;
  1526   PreloadInfo *pi = mPreloads.AppendElement();
  1527   pi->mRequest = request;
  1528   pi->mCharset = aCharset;
  1531 void
  1532 nsScriptLoader::AddDeferRequest(nsScriptLoadRequest* aRequest)
  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();
  1543 bool
  1544 nsScriptLoader::MaybeRemovedDeferRequests()
  1546   if (mDeferRequests.Length() == 0 && mDocument &&
  1547       mBlockingDOMContentLoaded) {
  1548     mBlockingDOMContentLoaded = false;
  1549     mDocument->UnblockDOMContentLoaded();
  1550     return true;
  1552   return false;

mercurial