1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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/. */
6 
7 #include "ScriptLoader.h"
8 #include "ScriptLoadHandler.h"
9 #include "ScriptLoadRequest.h"
10 #include "ScriptTrace.h"
11 #include "ModuleLoadRequest.h"
12 #include "ModuleScript.h"
13 
14 #include "prsystem.h"
15 #include "jsapi.h"
16 #include "jsfriendapi.h"
17 #include "js/Utility.h"
18 #include "xpcpublic.h"
19 #include "nsCycleCollectionParticipant.h"
20 #include "nsIContent.h"
21 #include "nsJSUtils.h"
22 #include "mozilla/dom/DocGroup.h"
23 #include "mozilla/dom/Element.h"
24 #include "mozilla/dom/ScriptSettings.h"
25 #include "mozilla/dom/SRILogHelper.h"
26 #include "nsGkAtoms.h"
27 #include "nsNetUtil.h"
28 #include "nsIScriptGlobalObject.h"
29 #include "nsIScriptContext.h"
30 #include "nsIScriptSecurityManager.h"
31 #include "nsIPrincipal.h"
32 #include "nsJSPrincipals.h"
33 #include "nsContentPolicyUtils.h"
34 #include "nsIHttpChannel.h"
35 #include "nsIHttpChannelInternal.h"
36 #include "nsIClassOfService.h"
37 #include "nsICacheInfoChannel.h"
38 #include "nsITimedChannel.h"
39 #include "nsIScriptElement.h"
40 #include "nsIDocShell.h"
41 #include "nsContentUtils.h"
42 #include "nsUnicharUtils.h"
43 #include "nsAutoPtr.h"
44 #include "nsIXPConnect.h"
45 #include "nsError.h"
46 #include "nsThreadUtils.h"
47 #include "nsDocShellCID.h"
48 #include "nsIContentSecurityPolicy.h"
49 #include "mozilla/Logging.h"
50 #include "nsCRT.h"
51 #include "nsContentCreatorFunctions.h"
52 #include "nsProxyRelease.h"
53 #include "nsSandboxFlags.h"
54 #include "nsContentTypeParser.h"
55 #include "nsINetworkPredictor.h"
56 #include "mozilla/ConsoleReportCollector.h"
57 
58 #include "mozilla/AsyncEventDispatcher.h"
59 #include "mozilla/Attributes.h"
60 #include "mozilla/Telemetry.h"
61 #include "mozilla/TimeStamp.h"
62 #include "mozilla/Unused.h"
63 #include "nsIScriptError.h"
64 #include "nsIOutputStream.h"
65 
66 using JS::SourceBufferHolder;
67 
68 using mozilla::Telemetry::LABELS_DOM_SCRIPT_PRELOAD_RESULT;
69 
70 namespace mozilla {
71 namespace dom {
72 
73 LazyLogModule ScriptLoader::gCspPRLog("CSP");
74 LazyLogModule ScriptLoader::gScriptLoaderLog("ScriptLoader");
75 
76 #undef LOG
77 #define LOG(args) \
78   MOZ_LOG(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug, args)
79 
80 #define LOG_ENABLED() \
81   MOZ_LOG_TEST(ScriptLoader::gScriptLoaderLog, mozilla::LogLevel::Debug)
82 
83 // Alternate Data MIME type used by the ScriptLoader to register that we want to
84 // store bytecode without reading it.
85 static NS_NAMED_LITERAL_CSTRING(kNullMimeType, "javascript/null");
86 
87 //////////////////////////////////////////////////////////////
88 // ScriptLoader::PreloadInfo
89 //////////////////////////////////////////////////////////////
90 
ImplCycleCollectionUnlink(ScriptLoader::PreloadInfo & aField)91 inline void ImplCycleCollectionUnlink(ScriptLoader::PreloadInfo& aField) {
92   ImplCycleCollectionUnlink(aField.mRequest);
93 }
94 
ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback & aCallback,ScriptLoader::PreloadInfo & aField,const char * aName,uint32_t aFlags=0)95 inline void ImplCycleCollectionTraverse(
96     nsCycleCollectionTraversalCallback& aCallback,
97     ScriptLoader::PreloadInfo& aField, const char* aName, uint32_t aFlags = 0) {
98   ImplCycleCollectionTraverse(aCallback, aField.mRequest, aName, aFlags);
99 }
100 
101 //////////////////////////////////////////////////////////////
102 // ScriptLoader
103 //////////////////////////////////////////////////////////////
104 
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptLoader)105 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScriptLoader)
106 NS_INTERFACE_MAP_END
107 
108 NS_IMPL_CYCLE_COLLECTION(ScriptLoader, mNonAsyncExternalScriptInsertedRequests,
109                          mLoadingAsyncRequests, mLoadedAsyncRequests,
110                          mDeferRequests, mXSLTRequests, mParserBlockingRequest,
111                          mBytecodeEncodingQueue, mPreloads,
112                          mPendingChildLoaders, mFetchedModules)
113 
114 NS_IMPL_CYCLE_COLLECTING_ADDREF(ScriptLoader)
115 NS_IMPL_CYCLE_COLLECTING_RELEASE(ScriptLoader)
116 
117 ScriptLoader::ScriptLoader(nsIDocument* aDocument)
118     : mDocument(aDocument),
119       mParserBlockingBlockerCount(0),
120       mBlockerCount(0),
121       mNumberOfProcessors(0),
122       mEnabled(true),
123       mDeferEnabled(false),
124       mDocumentParsingDone(false),
125       mBlockingDOMContentLoaded(false),
126       mLoadEventFired(false),
127       mGiveUpEncoding(false),
128       mReporter(new ConsoleReportCollector()) {
129   LOG(("ScriptLoader::ScriptLoader %p", this));
130 }
131 
~ScriptLoader()132 ScriptLoader::~ScriptLoader() {
133   LOG(("ScriptLoader::~ScriptLoader %p", this));
134 
135   mObservers.Clear();
136 
137   if (mParserBlockingRequest) {
138     mParserBlockingRequest->FireScriptAvailable(NS_ERROR_ABORT);
139   }
140 
141   for (ScriptLoadRequest* req = mXSLTRequests.getFirst(); req;
142        req = req->getNext()) {
143     req->FireScriptAvailable(NS_ERROR_ABORT);
144   }
145 
146   for (ScriptLoadRequest* req = mDeferRequests.getFirst(); req;
147        req = req->getNext()) {
148     req->FireScriptAvailable(NS_ERROR_ABORT);
149   }
150 
151   for (ScriptLoadRequest* req = mLoadingAsyncRequests.getFirst(); req;
152        req = req->getNext()) {
153     req->FireScriptAvailable(NS_ERROR_ABORT);
154   }
155 
156   for (ScriptLoadRequest* req = mLoadedAsyncRequests.getFirst(); req;
157        req = req->getNext()) {
158     req->FireScriptAvailable(NS_ERROR_ABORT);
159   }
160 
161   for (ScriptLoadRequest* req =
162            mNonAsyncExternalScriptInsertedRequests.getFirst();
163        req; req = req->getNext()) {
164     req->FireScriptAvailable(NS_ERROR_ABORT);
165   }
166 
167   // Unblock the kids, in case any of them moved to a different document
168   // subtree in the meantime and therefore aren't actually going away.
169   for (uint32_t j = 0; j < mPendingChildLoaders.Length(); ++j) {
170     mPendingChildLoaders[j]->RemoveParserBlockingScriptExecutionBlocker();
171   }
172 
173   for (size_t i = 0; i < mPreloads.Length(); i++) {
174     AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::NotUsed);
175   }
176 }
177 
178 // Collect telemtry data about the cache information, and the kind of source
179 // which are being loaded, and where it is being loaded from.
CollectScriptTelemetry(nsIIncrementalStreamLoader * aLoader,ScriptLoadRequest * aRequest)180 static void CollectScriptTelemetry(nsIIncrementalStreamLoader* aLoader,
181                                    ScriptLoadRequest* aRequest) {
182   using namespace mozilla::Telemetry;
183 
184   // Skip this function if we are not running telemetry.
185   if (!CanRecordExtended()) {
186     return;
187   }
188 
189   // Report the script kind.
190   if (aRequest->IsModuleRequest()) {
191     AccumulateCategorical(LABELS_DOM_SCRIPT_KIND::ModuleScript);
192   } else {
193     AccumulateCategorical(LABELS_DOM_SCRIPT_KIND::ClassicScript);
194   }
195 
196   // Report the type of source, as well as the size of the source.
197   if (aRequest->IsLoadingSource()) {
198     if (aRequest->mIsInline) {
199       AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::Inline);
200       nsAutoString inlineData;
201       aRequest->mElement->GetScriptText(inlineData);
202       Accumulate(DOM_SCRIPT_INLINE_SIZE, inlineData.Length());
203     } else {
204       AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::SourceFallback);
205       Accumulate(DOM_SCRIPT_SOURCE_SIZE, aRequest->mScriptText.length());
206     }
207   } else {
208     MOZ_ASSERT(aRequest->IsLoading());
209     if (aRequest->IsSource()) {
210       AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::Source);
211       Accumulate(DOM_SCRIPT_SOURCE_SIZE, aRequest->mScriptText.length());
212     } else {
213       MOZ_ASSERT(aRequest->IsBytecode());
214       AccumulateCategorical(LABELS_DOM_SCRIPT_LOADING_SOURCE::AltData);
215       Accumulate(DOM_SCRIPT_BYTECODE_SIZE, aRequest->mScriptBytecode.length());
216     }
217   }
218 
219   // Skip if we do not have any cache information for the given script.
220   if (!aLoader) {
221     return;
222   }
223   nsCOMPtr<nsIRequest> channel;
224   aLoader->GetRequest(getter_AddRefs(channel));
225   nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(channel));
226   if (!cic) {
227     return;
228   }
229 
230   int32_t fetchCount = 0;
231   if (NS_SUCCEEDED(cic->GetCacheTokenFetchCount(&fetchCount))) {
232     Accumulate(DOM_SCRIPT_FETCH_COUNT, fetchCount);
233   }
234 }
235 
236 // Helper method for checking if the script element is an event-handler
237 // This means that it has both a for-attribute and a event-attribute.
238 // Also, if the for-attribute has a value that matches "\s*window\s*",
239 // and the event-attribute matches "\s*onload([ \(].*)?" then it isn't an
240 // eventhandler. (both matches are case insensitive).
241 // This is how IE seems to filter out a window's onload handler from a
242 // <script for=... event=...> element.
243 
IsScriptEventHandler(ScriptKind kind,nsIContent * aScriptElement)244 static bool IsScriptEventHandler(ScriptKind kind, nsIContent* aScriptElement) {
245   if (kind != ScriptKind::eClassic) {
246     return false;
247   }
248 
249   if (!aScriptElement->IsHTMLElement()) {
250     return false;
251   }
252 
253   nsAutoString forAttr, eventAttr;
254   if (!aScriptElement->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::_for,
255                                             forAttr) ||
256       !aScriptElement->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::event,
257                                             eventAttr)) {
258     return false;
259   }
260 
261   const nsAString& for_str =
262       nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(forAttr);
263   if (!for_str.LowerCaseEqualsLiteral("window")) {
264     return true;
265   }
266 
267   // We found for="window", now check for event="onload".
268   const nsAString& event_str =
269       nsContentUtils::TrimWhitespace<nsCRT::IsAsciiSpace>(eventAttr, false);
270   if (!StringBeginsWith(event_str, NS_LITERAL_STRING("onload"),
271                         nsCaseInsensitiveStringComparator())) {
272     // It ain't "onload.*".
273 
274     return true;
275   }
276 
277   nsAutoString::const_iterator start, end;
278   event_str.BeginReading(start);
279   event_str.EndReading(end);
280 
281   start.advance(6);  // advance past "onload"
282 
283   if (start != end && *start != '(' && *start != ' ') {
284     // We got onload followed by something other than space or
285     // '('. Not good enough.
286 
287     return true;
288   }
289 
290   return false;
291 }
292 
CheckContentPolicy(nsIDocument * aDocument,nsISupports * aContext,nsIURI * aURI,const nsAString & aType,bool aIsPreLoad)293 nsresult ScriptLoader::CheckContentPolicy(nsIDocument* aDocument,
294                                           nsISupports* aContext, nsIURI* aURI,
295                                           const nsAString& aType,
296                                           bool aIsPreLoad) {
297   nsContentPolicyType contentPolicyType =
298       aIsPreLoad ? nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD
299                  : nsIContentPolicy::TYPE_INTERNAL_SCRIPT;
300 
301   int16_t shouldLoad = nsIContentPolicy::ACCEPT;
302   nsresult rv = NS_CheckContentLoadPolicy(
303       contentPolicyType, aURI,
304       aDocument->NodePrincipal(),  // loading principal
305       aDocument->NodePrincipal(),  // triggering principal
306       aContext, NS_LossyConvertUTF16toASCII(aType),
307       nullptr,  // extra
308       &shouldLoad, nsContentUtils::GetContentPolicy());
309   if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) {
310     if (NS_FAILED(rv) || shouldLoad != nsIContentPolicy::REJECT_TYPE) {
311       return NS_ERROR_CONTENT_BLOCKED;
312     }
313     return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT;
314   }
315 
316   return NS_OK;
317 }
318 
ModuleMapContainsURL(nsIURI * aURL) const319 bool ScriptLoader::ModuleMapContainsURL(nsIURI* aURL) const {
320   // Returns whether we have fetched, or are currently fetching, a module script
321   // for a URL.
322   return mFetchingModules.Contains(aURL) || mFetchedModules.Contains(aURL);
323 }
324 
IsFetchingModule(ModuleLoadRequest * aRequest) const325 bool ScriptLoader::IsFetchingModule(ModuleLoadRequest* aRequest) const {
326   bool fetching = mFetchingModules.Contains(aRequest->mURI);
327   MOZ_ASSERT_IF(fetching, !mFetchedModules.Contains(aRequest->mURI));
328   return fetching;
329 }
330 
SetModuleFetchStarted(ModuleLoadRequest * aRequest)331 void ScriptLoader::SetModuleFetchStarted(ModuleLoadRequest* aRequest) {
332   // Update the module map to indicate that a module is currently being fetched.
333 
334   MOZ_ASSERT(aRequest->IsLoading());
335   MOZ_ASSERT(!ModuleMapContainsURL(aRequest->mURI));
336   mFetchingModules.Put(aRequest->mURI, nullptr);
337 }
338 
SetModuleFetchFinishedAndResumeWaitingRequests(ModuleLoadRequest * aRequest,nsresult aResult)339 void ScriptLoader::SetModuleFetchFinishedAndResumeWaitingRequests(
340     ModuleLoadRequest* aRequest, nsresult aResult) {
341   // Update module map with the result of fetching a single module script.
342   //
343   // If any requests for the same URL are waiting on this one to complete, they
344   // will have ModuleLoaded or LoadFailed on them when the promise is
345   // resolved/rejected. This is set up in StartLoad.
346 
347   LOG(
348       ("ScriptLoadRequest (%p): Module fetch finished (script == %p, result == "
349        "%u)",
350        aRequest, aRequest->mModuleScript.get(), unsigned(aResult)));
351 
352   RefPtr<GenericPromise::Private> promise;
353   MOZ_ALWAYS_TRUE(
354       mFetchingModules.Remove(aRequest->mURI, getter_AddRefs(promise)));
355 
356   RefPtr<ModuleScript> moduleScript(aRequest->mModuleScript);
357   MOZ_ASSERT(NS_FAILED(aResult) == !moduleScript);
358 
359   mFetchedModules.Put(aRequest->mURI, moduleScript);
360 
361   if (promise) {
362     if (moduleScript) {
363       LOG(("ScriptLoadRequest (%p):   resolving %p", aRequest, promise.get()));
364       promise->Resolve(true, __func__);
365     } else {
366       LOG(("ScriptLoadRequest (%p):   rejecting %p", aRequest, promise.get()));
367       promise->Reject(aResult, __func__);
368     }
369   }
370 }
371 
WaitForModuleFetch(nsIURI * aURL)372 RefPtr<GenericPromise> ScriptLoader::WaitForModuleFetch(nsIURI* aURL) {
373   MOZ_ASSERT(ModuleMapContainsURL(aURL));
374 
375   if (auto entry = mFetchingModules.Lookup(aURL)) {
376     if (!entry.Data()) {
377       entry.Data() = new GenericPromise::Private(__func__);
378     }
379     return entry.Data();
380   }
381 
382   RefPtr<ModuleScript> ms;
383   MOZ_ALWAYS_TRUE(mFetchedModules.Get(aURL, getter_AddRefs(ms)));
384   if (!ms) {
385     return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
386   }
387 
388   return GenericPromise::CreateAndResolve(true, __func__);
389 }
390 
GetFetchedModule(nsIURI * aURL) const391 ModuleScript* ScriptLoader::GetFetchedModule(nsIURI* aURL) const {
392   if (LOG_ENABLED()) {
393     nsAutoCString url;
394     aURL->GetAsciiSpec(url);
395     LOG(("GetFetchedModule %s", url.get()));
396   }
397 
398   bool found;
399   ModuleScript* ms = mFetchedModules.GetWeak(aURL, &found);
400   MOZ_ASSERT(found);
401   return ms;
402 }
403 
ClearModuleMap()404 void ScriptLoader::ClearModuleMap() {
405   MOZ_ASSERT(mFetchingModules.IsEmpty());
406   mFetchedModules.Clear();
407 }
408 
ProcessFetchedModuleSource(ModuleLoadRequest * aRequest)409 nsresult ScriptLoader::ProcessFetchedModuleSource(ModuleLoadRequest* aRequest) {
410   MOZ_ASSERT(!aRequest->mModuleScript);
411 
412   nsresult rv = CreateModuleScript(aRequest);
413   MOZ_ASSERT(NS_FAILED(rv) == !aRequest->mModuleScript);
414 
415   aRequest->mScriptText.clearAndFree();
416 
417   if (NS_FAILED(rv)) {
418     aRequest->LoadFailed();
419     return rv;
420   }
421 
422   if (!aRequest->mIsInline) {
423     SetModuleFetchFinishedAndResumeWaitingRequests(aRequest, rv);
424   }
425 
426   if (!aRequest->mModuleScript->HasParseError()) {
427     StartFetchingModuleDependencies(aRequest);
428   }
429 
430   return NS_OK;
431 }
432 
433 static nsresult ResolveRequestedModules(ModuleLoadRequest* aRequest,
434                                         nsCOMArray<nsIURI>* aUrlsOut);
435 
CreateModuleScript(ModuleLoadRequest * aRequest)436 nsresult ScriptLoader::CreateModuleScript(ModuleLoadRequest* aRequest) {
437   MOZ_ASSERT(!aRequest->mModuleScript);
438   MOZ_ASSERT(aRequest->mBaseURL);
439 
440   LOG(("ScriptLoadRequest (%p): Create module script", aRequest));
441 
442   nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
443   if (!globalObject) {
444     return NS_ERROR_FAILURE;
445   }
446 
447   nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext();
448   if (!context) {
449     return NS_ERROR_FAILURE;
450   }
451 
452   nsAutoMicroTask mt;
453   AutoEntryScript aes(globalObject, "CompileModule", true);
454 
455   bool oldProcessingScriptTag = context->GetProcessingScriptTag();
456   context->SetProcessingScriptTag(true);
457 
458   nsresult rv;
459   {
460     JSContext* cx = aes.cx();
461     JS::Rooted<JSObject*> module(cx);
462 
463     if (aRequest->mWasCompiledOMT) {
464       module = JS::FinishOffThreadModule(cx, aRequest->mOffThreadToken);
465       aRequest->mOffThreadToken = nullptr;
466       rv = module ? NS_OK : NS_ERROR_FAILURE;
467     } else {
468       JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
469 
470       JS::CompileOptions options(cx);
471       rv = FillCompileOptionsForRequest(aes, aRequest, global, &options);
472 
473       if (NS_SUCCEEDED(rv)) {
474         nsAutoString inlineData;
475         SourceBufferHolder srcBuf = GetScriptSource(aRequest, inlineData);
476         rv = nsJSUtils::CompileModule(cx, srcBuf, global, options, &module);
477       }
478     }
479 
480     MOZ_ASSERT(NS_SUCCEEDED(rv) == (module != nullptr));
481 
482     RefPtr<ModuleScript> moduleScript =
483         new ModuleScript(this, aRequest->mBaseURL);
484     aRequest->mModuleScript = moduleScript;
485 
486     if (!module) {
487       LOG(("ScriptLoadRequest (%p):   compilation failed (%d)", aRequest,
488            unsigned(rv)));
489 
490       MOZ_ASSERT(aes.HasException());
491       JS::Rooted<JS::Value> error(cx);
492       if (!aes.StealException(&error)) {
493         aRequest->mModuleScript = nullptr;
494         return NS_ERROR_FAILURE;
495       }
496 
497       moduleScript->SetParseError(error);
498       aRequest->ModuleErrored();
499       return NS_OK;
500     }
501 
502     moduleScript->SetModuleRecord(module);
503 
504     // Validate requested modules and treat failure to resolve module specifiers
505     // the same as a parse error.
506     rv = ResolveRequestedModules(aRequest, nullptr);
507     if (NS_FAILED(rv)) {
508       aRequest->ModuleErrored();
509       return NS_OK;
510     }
511   }
512 
513   context->SetProcessingScriptTag(oldProcessingScriptTag);
514 
515   LOG(("ScriptLoadRequest (%p):   module script == %p", aRequest,
516        aRequest->mModuleScript.get()));
517 
518   return rv;
519 }
520 
HandleResolveFailure(JSContext * aCx,ModuleScript * aScript,const nsAString & aSpecifier,uint32_t aLineNumber,uint32_t aColumnNumber)521 static nsresult HandleResolveFailure(JSContext* aCx, ModuleScript* aScript,
522                                      const nsAString& aSpecifier,
523                                      uint32_t aLineNumber,
524                                      uint32_t aColumnNumber) {
525   nsAutoCString url;
526   aScript->BaseURL()->GetAsciiSpec(url);
527 
528   JS::Rooted<JSString*> filename(aCx);
529   filename = JS_NewStringCopyZ(aCx, url.get());
530   if (!filename) {
531     return NS_ERROR_OUT_OF_MEMORY;
532   }
533 
534   nsAutoString message(NS_LITERAL_STRING("Error resolving module specifier: "));
535   message.Append(aSpecifier);
536 
537   JS::Rooted<JSString*> string(aCx, JS_NewUCStringCopyZ(aCx, message.get()));
538   if (!string) {
539     return NS_ERROR_OUT_OF_MEMORY;
540   }
541 
542   JS::Rooted<JS::Value> error(aCx);
543   if (!JS::CreateError(aCx, JSEXN_TYPEERR, nullptr, filename, aLineNumber,
544                        aColumnNumber, nullptr, string, &error)) {
545     return NS_ERROR_OUT_OF_MEMORY;
546   }
547 
548   aScript->SetParseError(error);
549   return NS_OK;
550 }
551 
ResolveModuleSpecifier(ModuleScript * aScript,const nsAString & aSpecifier)552 static already_AddRefed<nsIURI> ResolveModuleSpecifier(
553     ModuleScript* aScript, const nsAString& aSpecifier) {
554   // The following module specifiers are allowed by the spec:
555   //  - a valid absolute URL
556   //  - a valid relative URL that starts with "/", "./" or "../"
557   //
558   // Bareword module specifiers are currently disallowed as these may be given
559   // special meanings in the future.
560 
561   nsCOMPtr<nsIURI> uri;
562   nsresult rv = NS_NewURI(getter_AddRefs(uri), aSpecifier);
563   if (NS_SUCCEEDED(rv)) {
564     return uri.forget();
565   }
566 
567   if (rv != NS_ERROR_MALFORMED_URI) {
568     return nullptr;
569   }
570 
571   if (!StringBeginsWith(aSpecifier, NS_LITERAL_STRING("/")) &&
572       !StringBeginsWith(aSpecifier, NS_LITERAL_STRING("./")) &&
573       !StringBeginsWith(aSpecifier, NS_LITERAL_STRING("../"))) {
574     return nullptr;
575   }
576 
577   rv = NS_NewURI(getter_AddRefs(uri), aSpecifier, nullptr, aScript->BaseURL());
578   if (NS_SUCCEEDED(rv)) {
579     return uri.forget();
580   }
581 
582   return nullptr;
583 }
584 
ResolveRequestedModules(ModuleLoadRequest * aRequest,nsCOMArray<nsIURI> * aUrlsOut)585 static nsresult ResolveRequestedModules(ModuleLoadRequest* aRequest,
586                                         nsCOMArray<nsIURI>* aUrlsOut) {
587   ModuleScript* ms = aRequest->mModuleScript;
588 
589   AutoJSAPI jsapi;
590   if (!jsapi.Init(ms->ModuleRecord())) {
591     return NS_ERROR_FAILURE;
592   }
593 
594   JSContext* cx = jsapi.cx();
595   JS::Rooted<JSObject*> moduleRecord(cx, ms->ModuleRecord());
596   JS::Rooted<JSObject*> requestedModules(cx);
597   requestedModules = JS::GetRequestedModules(cx, moduleRecord);
598   MOZ_ASSERT(requestedModules);
599 
600   uint32_t length;
601   if (!JS_GetArrayLength(cx, requestedModules, &length)) {
602     return NS_ERROR_FAILURE;
603   }
604 
605   JS::Rooted<JS::Value> element(cx);
606   for (uint32_t i = 0; i < length; i++) {
607     if (!JS_GetElement(cx, requestedModules, i, &element)) {
608       return NS_ERROR_FAILURE;
609     }
610 
611     JS::Rooted<JSString*> str(cx, JS::GetRequestedModuleSpecifier(cx, element));
612     MOZ_ASSERT(str);
613 
614     nsAutoJSString specifier;
615     if (!specifier.init(cx, str)) {
616       return NS_ERROR_FAILURE;
617     }
618 
619     // Let url be the result of resolving a module specifier given module script
620     // and requested.
621     nsCOMPtr<nsIURI> uri = ResolveModuleSpecifier(ms, specifier);
622     if (!uri) {
623       uint32_t lineNumber = 0;
624       uint32_t columnNumber = 0;
625       JS::GetRequestedModuleSourcePos(cx, element, &lineNumber, &columnNumber);
626 
627       nsresult rv =
628           HandleResolveFailure(cx, ms, specifier, lineNumber, columnNumber);
629       NS_ENSURE_SUCCESS(rv, rv);
630       return NS_ERROR_FAILURE;
631     }
632 
633     if (aUrlsOut) {
634       aUrlsOut->AppendElement(uri.forget());
635     }
636   }
637 
638   return NS_OK;
639 }
640 
StartFetchingModuleDependencies(ModuleLoadRequest * aRequest)641 void ScriptLoader::StartFetchingModuleDependencies(
642     ModuleLoadRequest* aRequest) {
643   LOG(("ScriptLoadRequest (%p): Start fetching module dependencies", aRequest));
644 
645   MOZ_ASSERT(aRequest->mModuleScript);
646   MOZ_ASSERT(!aRequest->mModuleScript->HasParseError());
647   MOZ_ASSERT(!aRequest->IsReadyToRun());
648 
649   auto visitedSet = aRequest->mVisitedSet;
650   MOZ_ASSERT(visitedSet->Contains(aRequest->mURI));
651 
652   aRequest->mProgress = ModuleLoadRequest::Progress::eFetchingImports;
653 
654   nsCOMArray<nsIURI> urls;
655   nsresult rv = ResolveRequestedModules(aRequest, &urls);
656   if (NS_FAILED(rv)) {
657     aRequest->ModuleErrored();
658     return;
659   }
660 
661   // Remove already visited URLs from the list. Put unvisited URLs into the
662   // visited set.
663   int32_t i = 0;
664   while (i < urls.Count()) {
665     nsIURI* url = urls[i];
666     if (visitedSet->Contains(url)) {
667       urls.RemoveObjectAt(i);
668     } else {
669       visitedSet->PutEntry(url);
670       i++;
671     }
672   }
673 
674   if (urls.Count() == 0) {
675     // There are no descendants to load so this request is ready.
676     aRequest->DependenciesLoaded();
677     return;
678   }
679 
680   // For each url in urls, fetch a module script tree given url, module script's
681   // CORS setting, and module script's settings object.
682   nsTArray<RefPtr<GenericPromise>> importsReady;
683   for (auto url : urls) {
684     RefPtr<GenericPromise> childReady =
685         StartFetchingModuleAndDependencies(aRequest, url);
686     importsReady.AppendElement(childReady);
687   }
688 
689   // Wait for all imports to become ready.
690   RefPtr<GenericPromise::AllPromiseType> allReady =
691       GenericPromise::All(GetMainThreadSerialEventTarget(), importsReady);
692   allReady->Then(GetMainThreadSerialEventTarget(), __func__, aRequest,
693                  &ModuleLoadRequest::DependenciesLoaded,
694                  &ModuleLoadRequest::ModuleErrored);
695 }
696 
StartFetchingModuleAndDependencies(ModuleLoadRequest * aParent,nsIURI * aURI)697 RefPtr<GenericPromise> ScriptLoader::StartFetchingModuleAndDependencies(
698     ModuleLoadRequest* aParent, nsIURI* aURI) {
699   MOZ_ASSERT(aURI);
700 
701   RefPtr<ModuleLoadRequest> childRequest = new ModuleLoadRequest(aURI, aParent);
702 
703   aParent->mImports.AppendElement(childRequest);
704 
705   if (LOG_ENABLED()) {
706     nsAutoCString url1;
707     aParent->mURI->GetAsciiSpec(url1);
708 
709     nsAutoCString url2;
710     aURI->GetAsciiSpec(url2);
711 
712     LOG(("ScriptLoadRequest (%p): Start fetching dependency %p", aParent,
713          childRequest.get()));
714     LOG(("StartFetchingModuleAndDependencies \"%s\" -> \"%s\"", url1.get(),
715          url2.get()));
716   }
717 
718   RefPtr<GenericPromise> ready = childRequest->mReady.Ensure(__func__);
719 
720   nsresult rv = StartLoad(childRequest);
721   if (NS_FAILED(rv)) {
722     MOZ_ASSERT(!childRequest->mModuleScript);
723     LOG(("ScriptLoadRequest (%p):   rejecting %p", aParent,
724          &childRequest->mReady));
725     childRequest->mReady.Reject(rv, __func__);
726     return ready;
727   }
728 
729   return ready;
730 }
731 
732 // 8.1.3.8.1 HostResolveImportedModule(referencingModule, specifier)
HostResolveImportedModule(JSContext * aCx,unsigned argc,JS::Value * vp)733 bool HostResolveImportedModule(JSContext* aCx, unsigned argc, JS::Value* vp) {
734   MOZ_ASSERT(argc == 2);
735   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
736   JS::Rooted<JSObject*> module(aCx, &args[0].toObject());
737   JS::Rooted<JSString*> specifier(aCx, args[1].toString());
738 
739   // Let referencing module script be referencingModule.[[HostDefined]].
740   JS::Value value = JS::GetModuleHostDefinedField(module);
741   auto script = static_cast<ModuleScript*>(value.toPrivate());
742   MOZ_ASSERT(script->ModuleRecord() == module);
743 
744   // Let url be the result of resolving a module specifier given referencing
745   // module script and specifier.
746   nsAutoJSString string;
747   if (!string.init(aCx, specifier)) {
748     return false;
749   }
750 
751   nsCOMPtr<nsIURI> uri = ResolveModuleSpecifier(script, string);
752 
753   // This cannot fail because resolving a module specifier must have been
754   // previously successful with these same two arguments.
755   MOZ_ASSERT(uri, "Failed to resolve previously-resolved module specifier");
756 
757   // Let resolved module script be moduleMap[url]. (This entry must exist for us
758   // to have gotten to this point.)
759   ModuleScript* ms = script->Loader()->GetFetchedModule(uri);
760   MOZ_ASSERT(ms, "Resolved module not found in module map");
761 
762   MOZ_ASSERT(!ms->HasParseError());
763 
764   *vp = JS::ObjectValue(*ms->ModuleRecord());
765   return true;
766 }
767 
EnsureModuleResolveHook(JSContext * aCx)768 static nsresult EnsureModuleResolveHook(JSContext* aCx) {
769   if (JS::GetModuleResolveHook(aCx)) {
770     return NS_OK;
771   }
772 
773   JS::Rooted<JSFunction*> func(aCx);
774   func = JS_NewFunction(aCx, HostResolveImportedModule, 2, 0,
775                         "HostResolveImportedModule");
776   if (!func) {
777     return NS_ERROR_FAILURE;
778   }
779 
780   JS::SetModuleResolveHook(aCx, func);
781   return NS_OK;
782 }
783 
CheckModuleDependenciesLoaded(ModuleLoadRequest * aRequest)784 void ScriptLoader::CheckModuleDependenciesLoaded(ModuleLoadRequest* aRequest) {
785   LOG(("ScriptLoadRequest (%p): Check dependencies loaded", aRequest));
786 
787   RefPtr<ModuleScript> moduleScript = aRequest->mModuleScript;
788   if (!moduleScript || moduleScript->HasParseError()) {
789     return;
790   }
791 
792   for (auto childRequest : aRequest->mImports) {
793     ModuleScript* childScript = childRequest->mModuleScript;
794     if (!childScript) {
795       aRequest->mModuleScript = nullptr;
796       LOG(("ScriptLoadRequest (%p):   %p failed (load error)", aRequest,
797            childRequest.get()));
798       return;
799     }
800   }
801 
802   LOG(("ScriptLoadRequest (%p):   all ok", aRequest));
803 }
804 
805 class ScriptRequestProcessor : public Runnable {
806  private:
807   RefPtr<ScriptLoader> mLoader;
808   RefPtr<ScriptLoadRequest> mRequest;
809 
810  public:
ScriptRequestProcessor(ScriptLoader * aLoader,ScriptLoadRequest * aRequest)811   ScriptRequestProcessor(ScriptLoader* aLoader, ScriptLoadRequest* aRequest)
812       : Runnable("dom::ScriptRequestProcessor"),
813         mLoader(aLoader),
814         mRequest(aRequest) {}
Run()815   NS_IMETHOD Run() override { return mLoader->ProcessRequest(mRequest); }
816 };
817 
ProcessLoadedModuleTree(ModuleLoadRequest * aRequest)818 void ScriptLoader::ProcessLoadedModuleTree(ModuleLoadRequest* aRequest) {
819   MOZ_ASSERT(aRequest->IsReadyToRun());
820 
821   if (aRequest->IsTopLevel()) {
822     ModuleScript* moduleScript = aRequest->mModuleScript;
823     if (moduleScript && !moduleScript->HasErrorToRethrow()) {
824       if (!InstantiateModuleTree(aRequest)) {
825         aRequest->mModuleScript = nullptr;
826       }
827     }
828 
829     if (aRequest->mIsInline &&
830         aRequest->mElement->GetParserCreated() == NOT_FROM_PARSER) {
831       MOZ_ASSERT(!aRequest->isInList());
832       nsContentUtils::AddScriptRunner(
833           new ScriptRequestProcessor(this, aRequest));
834     } else {
835       MaybeMoveToLoadedList(aRequest);
836       ProcessPendingRequests();
837     }
838   }
839 
840   if (aRequest->mWasCompiledOMT) {
841     mDocument->UnblockOnload(false);
842   }
843 }
844 
FindFirstParseError(ModuleLoadRequest * aRequest)845 JS::Value ScriptLoader::FindFirstParseError(ModuleLoadRequest* aRequest) {
846   MOZ_ASSERT(aRequest);
847 
848   ModuleScript* moduleScript = aRequest->mModuleScript;
849   MOZ_ASSERT(moduleScript);
850 
851   if (moduleScript->HasParseError()) {
852     return moduleScript->ParseError();
853   }
854 
855   for (ModuleLoadRequest* childRequest : aRequest->mImports) {
856     JS::Value error = FindFirstParseError(childRequest);
857     if (!error.isUndefined()) {
858       return error;
859     }
860   }
861 
862   return JS::UndefinedValue();
863 }
864 
InstantiateModuleTree(ModuleLoadRequest * aRequest)865 bool ScriptLoader::InstantiateModuleTree(ModuleLoadRequest* aRequest) {
866   // Instantiate a top-level module and record any error.
867 
868   MOZ_ASSERT(aRequest);
869   MOZ_ASSERT(aRequest->IsTopLevel());
870 
871   LOG(("ScriptLoadRequest (%p): Instantiate module tree", aRequest));
872 
873   ModuleScript* moduleScript = aRequest->mModuleScript;
874   MOZ_ASSERT(moduleScript);
875 
876   JS::Value parseError = FindFirstParseError(aRequest);
877   if (!parseError.isUndefined()) {
878     moduleScript->SetErrorToRethrow(parseError);
879     LOG(("ScriptLoadRequest (%p):   found parse error", aRequest));
880     return true;
881   }
882 
883   MOZ_ASSERT(moduleScript->ModuleRecord());
884 
885   nsAutoMicroTask mt;
886   AutoJSAPI jsapi;
887   if (NS_WARN_IF(!jsapi.Init(moduleScript->ModuleRecord()))) {
888     return false;
889   }
890 
891   nsresult rv = EnsureModuleResolveHook(jsapi.cx());
892   NS_ENSURE_SUCCESS(rv, false);
893 
894   JS::Rooted<JSObject*> module(jsapi.cx(), moduleScript->ModuleRecord());
895   bool ok = NS_SUCCEEDED(nsJSUtils::ModuleInstantiate(jsapi.cx(), module));
896 
897   if (!ok) {
898     LOG(("ScriptLoadRequest (%p): Instantiate failed", aRequest));
899     MOZ_ASSERT(jsapi.HasException());
900     JS::RootedValue exception(jsapi.cx());
901     if (!jsapi.StealException(&exception)) {
902       return false;
903     }
904     MOZ_ASSERT(!exception.isUndefined());
905     moduleScript->SetErrorToRethrow(exception);
906   }
907 
908   return true;
909 }
910 
RestartLoad(ScriptLoadRequest * aRequest)911 nsresult ScriptLoader::RestartLoad(ScriptLoadRequest* aRequest) {
912   MOZ_ASSERT(aRequest->IsBytecode());
913   aRequest->mScriptBytecode.clearAndFree();
914   TRACE_FOR_TEST(aRequest->mElement, "scriptloader_fallback");
915 
916   // Start a new channel from which we explicitly request to stream the source
917   // instead of the bytecode.
918   aRequest->mProgress = ScriptLoadRequest::Progress::eLoading_Source;
919   nsresult rv = StartLoad(aRequest);
920   if (NS_FAILED(rv)) {
921     return rv;
922   }
923 
924   // Close the current channel and this ScriptLoadHandler as we created a new
925   // one for the same request.
926   return NS_BINDING_RETARGETED;
927 }
928 
StartLoad(ScriptLoadRequest * aRequest)929 nsresult ScriptLoader::StartLoad(ScriptLoadRequest* aRequest) {
930   MOZ_ASSERT(aRequest->IsLoading());
931   NS_ENSURE_TRUE(mDocument, NS_ERROR_NULL_POINTER);
932   aRequest->mDataType = ScriptLoadRequest::DataType::eUnknown;
933 
934   // If this document is sandboxed without 'allow-scripts', abort.
935   if (mDocument->HasScriptsBlockedBySandbox()) {
936     return NS_OK;
937   }
938 
939   if (LOG_ENABLED()) {
940     nsAutoCString url;
941     aRequest->mURI->GetAsciiSpec(url);
942     LOG(("ScriptLoadRequest (%p): Start Load (url = %s)", aRequest, url.get()));
943   }
944 
945   if (aRequest->IsModuleRequest()) {
946     // Check whether the module has been fetched or is currently being fetched,
947     // and if so wait for it rather than starting a new fetch.
948     ModuleLoadRequest* request = aRequest->AsModuleRequest();
949     if (ModuleMapContainsURL(request->mURI)) {
950       LOG(("ScriptLoadRequest (%p): Waiting for module fetch", aRequest));
951       WaitForModuleFetch(request->mURI)
952           ->Then(GetMainThreadSerialEventTarget(), __func__, request,
953                  &ModuleLoadRequest::ModuleLoaded,
954                  &ModuleLoadRequest::LoadFailed);
955       return NS_OK;
956     }
957   }
958 
959   nsContentPolicyType contentPolicyType =
960       aRequest->IsPreload() ? nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD
961                             : nsIContentPolicy::TYPE_INTERNAL_SCRIPT;
962   nsCOMPtr<nsINode> context;
963   if (aRequest->mElement) {
964     context = do_QueryInterface(aRequest->mElement);
965   } else {
966     context = mDocument;
967   }
968 
969   nsCOMPtr<nsILoadGroup> loadGroup = mDocument->GetDocumentLoadGroup();
970   nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
971   NS_ENSURE_TRUE(window, NS_ERROR_NULL_POINTER);
972   nsIDocShell* docshell = window->GetDocShell();
973   nsCOMPtr<nsIInterfaceRequestor> prompter(do_QueryInterface(docshell));
974 
975   nsSecurityFlags securityFlags;
976   if (aRequest->IsModuleRequest()) {
977     // According to the spec, module scripts have different behaviour to classic
978     // scripts and always use CORS.
979     securityFlags = nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
980     if (aRequest->mCORSMode == CORS_NONE) {
981       securityFlags |= nsILoadInfo::SEC_COOKIES_OMIT;
982     } else if (aRequest->mCORSMode == CORS_ANONYMOUS) {
983       securityFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
984     } else {
985       MOZ_ASSERT(aRequest->mCORSMode == CORS_USE_CREDENTIALS);
986       securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
987     }
988   } else {
989     securityFlags = aRequest->mCORSMode == CORS_NONE
990                         ? nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL
991                         : nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS;
992     if (aRequest->mCORSMode == CORS_ANONYMOUS) {
993       securityFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
994     } else if (aRequest->mCORSMode == CORS_USE_CREDENTIALS) {
995       securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
996     }
997   }
998   securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
999 
1000   nsCOMPtr<nsIChannel> channel;
1001   nsresult rv = NS_NewChannelWithTriggeringPrincipal(
1002       getter_AddRefs(channel), aRequest->mURI, context,
1003       aRequest->mTriggeringPrincipal, securityFlags, contentPolicyType,
1004       nullptr,  // aPerformanceStorage
1005       loadGroup, prompter,
1006       nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI);
1007 
1008   NS_ENSURE_SUCCESS(rv, rv);
1009 
1010   // To avoid decoding issues, the build-id is part of the JSBytecodeMimeType
1011   // constant.
1012   aRequest->mCacheInfo = nullptr;
1013   nsCOMPtr<nsICacheInfoChannel> cic(do_QueryInterface(channel));
1014   if (cic && nsContentUtils::IsBytecodeCacheEnabled() &&
1015       // Bug 1436400: no bytecode cache support for modules yet.
1016       !aRequest->IsModuleRequest()) {
1017     if (!aRequest->IsLoadingSource()) {
1018       // Inform the HTTP cache that we prefer to have information coming from
1019       // the bytecode cache instead of the sources, if such entry is already
1020       // registered.
1021       LOG(("ScriptLoadRequest (%p): Maybe request bytecode", aRequest));
1022       cic->PreferAlternativeDataType(nsContentUtils::JSBytecodeMimeType());
1023     } else {
1024       // If we are explicitly loading from the sources, such as after a
1025       // restarted request, we might still want to save the bytecode after.
1026       //
1027       // The following tell the cache to look for an alternative data type which
1028       // does not exist, such that we can later save the bytecode with a
1029       // different alternative data type.
1030       LOG(("ScriptLoadRequest (%p): Request saving bytecode later", aRequest));
1031       cic->PreferAlternativeDataType(kNullMimeType);
1032     }
1033   }
1034 
1035   LOG(("ScriptLoadRequest (%p): mode=%u tracking=%d", aRequest,
1036        unsigned(aRequest->mScriptMode), aRequest->IsTracking()));
1037 
1038   nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
1039   if (cos) {
1040     if (aRequest->mScriptFromHead && aRequest->IsBlockingScript()) {
1041       // synchronous head scripts block loading of most other non js/css
1042       // content such as images, Leader implicitely disallows tailing
1043       cos->AddClassFlags(nsIClassOfService::Leader);
1044     } else if (aRequest->IsDeferredScript() &&
1045                !nsContentUtils::IsTailingEnabled()) {
1046       // Bug 1395525 and the !nsContentUtils::IsTailingEnabled() bit:
1047       // We want to make sure that turing tailing off by the pref makes
1048       // the browser behave exactly the same way as before landing
1049       // the tailing patch.
1050 
1051       // head/body deferred scripts are blocked by leaders but are not
1052       // allowed tailing because they block DOMContentLoaded
1053       cos->AddClassFlags(nsIClassOfService::TailForbidden);
1054     } else {
1055       // other scripts (=body sync or head/body async) are neither blocked
1056       // nor prioritized
1057       cos->AddClassFlags(nsIClassOfService::Unblocked);
1058 
1059       if (aRequest->IsAsyncScript()) {
1060         // async scripts are allowed tailing, since those and only those
1061         // don't block DOMContentLoaded; this flag doesn't enforce tailing,
1062         // just overweights the Unblocked flag when the channel is found
1063         // to be a thrird-party tracker and thus set the Tail flag to engage
1064         // tailing.
1065         cos->AddClassFlags(nsIClassOfService::TailAllowed);
1066       }
1067     }
1068   }
1069 
1070   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
1071   if (httpChannel) {
1072     // HTTP content negotation has little value in this context.
1073     rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
1074                                        NS_LITERAL_CSTRING("*/*"), false);
1075     MOZ_ASSERT(NS_SUCCEEDED(rv));
1076     rv = httpChannel->SetReferrerWithPolicy(aRequest->mReferrer,
1077                                             aRequest->mReferrerPolicy);
1078     MOZ_ASSERT(NS_SUCCEEDED(rv));
1079 
1080     nsCOMPtr<nsIHttpChannelInternal> internalChannel(
1081         do_QueryInterface(httpChannel));
1082     if (internalChannel) {
1083       rv = internalChannel->SetIntegrityMetadata(
1084           aRequest->mIntegrity.GetIntegrityString());
1085       MOZ_ASSERT(NS_SUCCEEDED(rv));
1086     }
1087   }
1088 
1089   mozilla::net::PredictorLearn(
1090       aRequest->mURI, mDocument->GetDocumentURI(),
1091       nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE,
1092       mDocument->NodePrincipal()->OriginAttributesRef());
1093 
1094   // Set the initiator type
1095   nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChannel));
1096   if (timedChannel) {
1097     timedChannel->SetInitiatorType(NS_LITERAL_STRING("script"));
1098   }
1099 
1100   nsAutoPtr<mozilla::dom::SRICheckDataVerifier> sriDataVerifier;
1101   if (!aRequest->mIntegrity.IsEmpty()) {
1102     nsAutoCString sourceUri;
1103     if (mDocument->GetDocumentURI()) {
1104       mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
1105     }
1106     sriDataVerifier =
1107         new SRICheckDataVerifier(aRequest->mIntegrity, sourceUri, mReporter);
1108   }
1109 
1110   RefPtr<ScriptLoadHandler> handler =
1111       new ScriptLoadHandler(this, aRequest, sriDataVerifier.forget());
1112 
1113   nsCOMPtr<nsIIncrementalStreamLoader> loader;
1114   rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), handler);
1115   NS_ENSURE_SUCCESS(rv, rv);
1116 
1117   rv = channel->AsyncOpen2(loader);
1118   NS_ENSURE_SUCCESS(rv, rv);
1119 
1120   if (aRequest->IsModuleRequest()) {
1121     // We successfully started fetching a module so put its URL in the module
1122     // map and mark it as fetching.
1123     SetModuleFetchStarted(aRequest->AsModuleRequest());
1124     LOG(("ScriptLoadRequest (%p): Start fetching module", aRequest));
1125   }
1126 
1127   return NS_OK;
1128 }
1129 
Equals(const PreloadInfo & aPi,nsIURI * const & aURI) const1130 bool ScriptLoader::PreloadURIComparator::Equals(const PreloadInfo& aPi,
1131                                                 nsIURI* const& aURI) const {
1132   bool same;
1133   return NS_SUCCEEDED(aPi.mRequest->mURI->Equals(aURI, &same)) && same;
1134 }
1135 
CSPAllowsInlineScript(nsIScriptElement * aElement,nsIDocument * aDocument)1136 static bool CSPAllowsInlineScript(nsIScriptElement* aElement,
1137                                   nsIDocument* aDocument) {
1138   nsCOMPtr<nsIContentSecurityPolicy> csp;
1139   // Note: For imports NodePrincipal and the principal of the master are
1140   // the same.
1141   nsresult rv = aDocument->NodePrincipal()->GetCsp(getter_AddRefs(csp));
1142   NS_ENSURE_SUCCESS(rv, false);
1143 
1144   if (!csp) {
1145     // no CSP --> allow
1146     return true;
1147   }
1148 
1149   // query the nonce
1150   nsCOMPtr<Element> scriptContent = do_QueryInterface(aElement);
1151   nsAutoString nonce;
1152   scriptContent->GetAttr(kNameSpaceID_None, nsGkAtoms::nonce, nonce);
1153   bool parserCreated =
1154       aElement->GetParserCreated() != mozilla::dom::NOT_FROM_PARSER;
1155 
1156   bool allowInlineScript = false;
1157   rv = csp->GetAllowsInline(nsIContentPolicy::TYPE_SCRIPT, nonce, parserCreated,
1158                             aElement, aElement->GetScriptLineNumber(),
1159                             &allowInlineScript);
1160   return allowInlineScript;
1161 }
1162 
CreateLoadRequest(ScriptKind aKind,nsIURI * aURI,nsIScriptElement * aElement,CORSMode aCORSMode,const SRIMetadata & aIntegrity,mozilla::net::ReferrerPolicy aReferrerPolicy)1163 ScriptLoadRequest* ScriptLoader::CreateLoadRequest(
1164     ScriptKind aKind, nsIURI* aURI, nsIScriptElement* aElement,
1165     CORSMode aCORSMode, const SRIMetadata& aIntegrity,
1166     mozilla::net::ReferrerPolicy aReferrerPolicy) {
1167   nsIURI* referrer = mDocument->GetDocumentURI();
1168 
1169   if (aKind == ScriptKind::eClassic) {
1170     return new ScriptLoadRequest(aKind, aURI, aElement, aCORSMode, aIntegrity,
1171                                  referrer, aReferrerPolicy);
1172   }
1173 
1174   MOZ_ASSERT(aKind == ScriptKind::eModule);
1175   return new ModuleLoadRequest(aURI, aElement, aCORSMode, aIntegrity, referrer,
1176                                aReferrerPolicy, this);
1177 }
1178 
ProcessScriptElement(nsIScriptElement * aElement)1179 bool ScriptLoader::ProcessScriptElement(nsIScriptElement* aElement) {
1180   // We need a document to evaluate scripts.
1181   NS_ENSURE_TRUE(mDocument, false);
1182 
1183   // Check to see if scripts has been turned off.
1184   if (!mEnabled || !mDocument->IsScriptEnabled()) {
1185     return false;
1186   }
1187 
1188   NS_ASSERTION(!aElement->IsMalformed(), "Executing malformed script");
1189 
1190   nsCOMPtr<nsIContent> scriptContent = do_QueryInterface(aElement);
1191 
1192   nsAutoString type;
1193   bool hasType = aElement->GetScriptType(type);
1194 
1195   ScriptKind scriptKind = aElement->GetScriptIsModule() ? ScriptKind::eModule
1196                                                         : ScriptKind::eClassic;
1197 
1198   // Step 13. Check that the script is not an eventhandler
1199   if (IsScriptEventHandler(scriptKind, scriptContent)) {
1200     return false;
1201   }
1202 
1203   // For classic scripts, check the type attribute to determine language and
1204   // version. If type exists, it trumps the deprecated 'language='
1205   if (scriptKind == ScriptKind::eClassic) {
1206     if (!type.IsEmpty()) {
1207       NS_ENSURE_TRUE(nsContentUtils::IsJavascriptMIMEType(type), false);
1208     } else if (!hasType) {
1209       // no 'type=' element
1210       // "language" is a deprecated attribute of HTML, so we check it only for
1211       // HTML script elements.
1212       if (scriptContent->IsHTMLElement()) {
1213         nsAutoString language;
1214         scriptContent->AsElement()->GetAttr(kNameSpaceID_None,
1215                                             nsGkAtoms::language, language);
1216         if (!language.IsEmpty()) {
1217           if (!nsContentUtils::IsJavaScriptLanguage(language)) {
1218             return false;
1219           }
1220         }
1221       }
1222     }
1223   }
1224 
1225   // "In modern user agents that support module scripts, the script element with
1226   // the nomodule attribute will be ignored".
1227   // "The nomodule attribute must not be specified on module scripts (and will
1228   // be ignored if it is)."
1229   if (mDocument->ModuleScriptsEnabled() && scriptKind == ScriptKind::eClassic &&
1230       scriptContent->IsHTMLElement() &&
1231       scriptContent->AsElement()->HasAttr(kNameSpaceID_None,
1232                                           nsGkAtoms::nomodule)) {
1233     return false;
1234   }
1235 
1236   // Step 15. and later in the HTML5 spec
1237   if (aElement->GetScriptExternal()) {
1238     return ProcessExternalScript(aElement, scriptKind, type, scriptContent);
1239   }
1240 
1241   return ProcessInlineScript(aElement, scriptKind);
1242 }
1243 
ProcessExternalScript(nsIScriptElement * aElement,ScriptKind aScriptKind,nsAutoString aTypeAttr,nsIContent * aScriptContent)1244 bool ScriptLoader::ProcessExternalScript(nsIScriptElement* aElement,
1245                                          ScriptKind aScriptKind,
1246                                          nsAutoString aTypeAttr,
1247                                          nsIContent* aScriptContent) {
1248   LOG(("ScriptLoader (%p): Process external script for element %p", this,
1249        aElement));
1250 
1251   nsCOMPtr<nsIURI> scriptURI = aElement->GetScriptURI();
1252   if (!scriptURI) {
1253     // Asynchronously report the failure to create a URI object
1254     NS_DispatchToCurrentThread(
1255         NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement,
1256                           &nsIScriptElement::FireErrorEvent));
1257     return false;
1258   }
1259 
1260   RefPtr<ScriptLoadRequest> request =
1261       LookupPreloadRequest(aElement, aScriptKind);
1262 
1263   if (request && NS_FAILED(CheckContentPolicy(
1264                      mDocument, aElement, request->mURI, aTypeAttr, false))) {
1265     LOG(("ScriptLoader (%p): content policy check failed for preload", this));
1266 
1267     // Probably plans have changed; even though the preload was allowed seems
1268     // like the actual load is not; let's cancel the preload request.
1269     request->Cancel();
1270     AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::RejectedByPolicy);
1271     return false;
1272   }
1273 
1274   if (request) {
1275     // Use the preload request.
1276 
1277     LOG(("ScriptLoadRequest (%p): Using preload request", request.get()));
1278 
1279     // It's possible these attributes changed since we started the preload so
1280     // update them here.
1281     request->SetScriptMode(aElement->GetScriptDeferred(),
1282                            aElement->GetScriptAsync());
1283 
1284     AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::Used);
1285   } else {
1286     // No usable preload found.
1287 
1288     SRIMetadata sriMetadata;
1289     {
1290       nsAutoString integrity;
1291       aScriptContent->AsElement()->GetAttr(kNameSpaceID_None,
1292                                            nsGkAtoms::integrity, integrity);
1293       GetSRIMetadata(integrity, &sriMetadata);
1294     }
1295 
1296     nsCOMPtr<nsIPrincipal> principal =
1297         aElement->GetScriptURITriggeringPrincipal();
1298     if (!principal) {
1299       principal = aScriptContent->NodePrincipal();
1300     }
1301 
1302     CORSMode ourCORSMode = aElement->GetCORSMode();
1303     mozilla::net::ReferrerPolicy ourRefPolicy = mDocument->GetReferrerPolicy();
1304     request = CreateLoadRequest(aScriptKind, scriptURI, aElement, ourCORSMode,
1305                                 sriMetadata, ourRefPolicy);
1306     request->mTriggeringPrincipal = Move(principal);
1307     request->mIsInline = false;
1308     request->SetScriptMode(aElement->GetScriptDeferred(),
1309                            aElement->GetScriptAsync());
1310     // keep request->mScriptFromHead to false so we don't treat non preloaded
1311     // scripts as blockers for full page load. See bug 792438.
1312 
1313     LOG(("ScriptLoadRequest (%p): Created request for external script",
1314          request.get()));
1315 
1316     nsresult rv = StartLoad(request);
1317     if (NS_FAILED(rv)) {
1318       ReportErrorToConsole(request, rv);
1319 
1320       // Asynchronously report the load failure
1321       nsCOMPtr<nsIRunnable> runnable =
1322           NewRunnableMethod("nsIScriptElement::FireErrorEvent", aElement,
1323                             &nsIScriptElement::FireErrorEvent);
1324       if (mDocument) {
1325         mDocument->Dispatch(TaskCategory::Other, runnable.forget());
1326       } else {
1327         NS_DispatchToCurrentThread(runnable);
1328       }
1329       return false;
1330     }
1331   }
1332 
1333   // We should still be in loading stage of script unless we're loading a
1334   // module.
1335   NS_ASSERTION(!request->InCompilingStage() || request->IsModuleRequest(),
1336                "Request should not yet be in compiling stage.");
1337 
1338   if (request->IsAsyncScript()) {
1339     AddAsyncRequest(request);
1340     if (request->IsReadyToRun()) {
1341       // The script is available already. Run it ASAP when the event
1342       // loop gets a chance to spin.
1343 
1344       // KVKV TODO: Instead of processing immediately, try off-thread-parsing
1345       // it and only schedule a pending ProcessRequest if that fails.
1346       ProcessPendingRequestsAsync();
1347     }
1348     return false;
1349   }
1350   if (!aElement->GetParserCreated()) {
1351     // Violate the HTML5 spec in order to make LABjs and the "order" plug-in
1352     // for RequireJS work with their Gecko-sniffed code path. See
1353     // http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html
1354     request->mIsNonAsyncScriptInserted = true;
1355     mNonAsyncExternalScriptInsertedRequests.AppendElement(request);
1356     if (request->IsReadyToRun()) {
1357       // The script is available already. Run it ASAP when the event
1358       // loop gets a chance to spin.
1359       ProcessPendingRequestsAsync();
1360     }
1361     return false;
1362   }
1363   // we now have a parser-inserted request that may or may not be still
1364   // loading
1365   if (request->IsDeferredScript()) {
1366     // We don't want to run this yet.
1367     // If we come here, the script is a parser-created script and it has
1368     // the defer attribute but not the async attribute. Since a
1369     // a parser-inserted script is being run, we came here by the parser
1370     // running the script, which means the parser is still alive and the
1371     // parse is ongoing.
1372     NS_ASSERTION(mDocument->GetCurrentContentSink() ||
1373                      aElement->GetParserCreated() == FROM_PARSER_XSLT,
1374                  "Non-XSLT Defer script on a document without an active "
1375                  "parser; bug 592366.");
1376     AddDeferRequest(request);
1377     return false;
1378   }
1379 
1380   if (aElement->GetParserCreated() == FROM_PARSER_XSLT) {
1381     // Need to maintain order for XSLT-inserted scripts
1382     NS_ASSERTION(!mParserBlockingRequest,
1383                  "Parser-blocking scripts and XSLT scripts in the same doc!");
1384     request->mIsXSLT = true;
1385     mXSLTRequests.AppendElement(request);
1386     if (request->IsReadyToRun()) {
1387       // The script is available already. Run it ASAP when the event
1388       // loop gets a chance to spin.
1389       ProcessPendingRequestsAsync();
1390     }
1391     return true;
1392   }
1393 
1394   if (request->IsReadyToRun() && ReadyToExecuteParserBlockingScripts()) {
1395     // The request has already been loaded and there are no pending style
1396     // sheets. If the script comes from the network stream, cheat for
1397     // performance reasons and avoid a trip through the event loop.
1398     if (aElement->GetParserCreated() == FROM_PARSER_NETWORK) {
1399       return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK;
1400     }
1401     // Otherwise, we've got a document.written script, make a trip through
1402     // the event loop to hide the preload effects from the scripts on the
1403     // Web page.
1404     NS_ASSERTION(!mParserBlockingRequest,
1405                  "There can be only one parser-blocking script at a time");
1406     NS_ASSERTION(mXSLTRequests.isEmpty(),
1407                  "Parser-blocking scripts and XSLT scripts in the same doc!");
1408     mParserBlockingRequest = request;
1409     ProcessPendingRequestsAsync();
1410     return true;
1411   }
1412 
1413   // The script hasn't loaded yet or there's a style sheet blocking it.
1414   // The script will be run when it loads or the style sheet loads.
1415   NS_ASSERTION(!mParserBlockingRequest,
1416                "There can be only one parser-blocking script at a time");
1417   NS_ASSERTION(mXSLTRequests.isEmpty(),
1418                "Parser-blocking scripts and XSLT scripts in the same doc!");
1419   mParserBlockingRequest = request;
1420   return true;
1421 }
1422 
ProcessInlineScript(nsIScriptElement * aElement,ScriptKind aScriptKind)1423 bool ScriptLoader::ProcessInlineScript(nsIScriptElement* aElement,
1424                                        ScriptKind aScriptKind) {
1425   // Is this document sandboxed without 'allow-scripts'?
1426   if (mDocument->HasScriptsBlockedBySandbox()) {
1427     return false;
1428   }
1429 
1430   // Does CSP allow this inline script to run?
1431   if (!CSPAllowsInlineScript(aElement, mDocument)) {
1432     return false;
1433   }
1434 
1435   // Inline classic scripts ignore their CORS mode and are always CORS_NONE.
1436   CORSMode corsMode = CORS_NONE;
1437   if (aScriptKind == ScriptKind::eModule) {
1438     corsMode = aElement->GetCORSMode();
1439   }
1440 
1441   RefPtr<ScriptLoadRequest> request = CreateLoadRequest(
1442       aScriptKind, mDocument->GetDocumentURI(), aElement, corsMode,
1443       SRIMetadata(),  // SRI doesn't apply
1444       mDocument->GetReferrerPolicy());
1445   request->mIsInline = true;
1446   request->mTriggeringPrincipal = mDocument->NodePrincipal();
1447   request->mLineNo = aElement->GetScriptLineNumber();
1448   request->mProgress = ScriptLoadRequest::Progress::eLoading_Source;
1449   request->mDataType = ScriptLoadRequest::DataType::eSource;
1450   TRACE_FOR_TEST_BOOL(request->mElement, "scriptloader_load_source");
1451   CollectScriptTelemetry(nullptr, request);
1452 
1453   // Only the 'async' attribute is heeded on an inline module script and
1454   // inline classic scripts ignore both these attributes.
1455   MOZ_ASSERT(!aElement->GetScriptDeferred());
1456   MOZ_ASSERT_IF(!request->IsModuleRequest(), !aElement->GetScriptAsync());
1457   request->SetScriptMode(false, aElement->GetScriptAsync());
1458 
1459   LOG(("ScriptLoadRequest (%p): Created request for inline script",
1460        request.get()));
1461 
1462   if (request->IsModuleRequest()) {
1463     ModuleLoadRequest* modReq = request->AsModuleRequest();
1464     modReq->mBaseURL = mDocument->GetDocBaseURI();
1465 
1466     if (aElement->GetParserCreated() != NOT_FROM_PARSER) {
1467       if (aElement->GetScriptAsync()) {
1468         AddAsyncRequest(modReq);
1469       } else {
1470         AddDeferRequest(modReq);
1471       }
1472     }
1473 
1474     nsresult rv = ProcessFetchedModuleSource(modReq);
1475     if (NS_FAILED(rv)) {
1476       ReportErrorToConsole(modReq, rv);
1477       HandleLoadError(modReq, rv);
1478     }
1479 
1480     return false;
1481   }
1482   request->mProgress = ScriptLoadRequest::Progress::eReady;
1483   if (aElement->GetParserCreated() == FROM_PARSER_XSLT &&
1484       (!ReadyToExecuteParserBlockingScripts() || !mXSLTRequests.isEmpty())) {
1485     // Need to maintain order for XSLT-inserted scripts
1486     NS_ASSERTION(!mParserBlockingRequest,
1487                  "Parser-blocking scripts and XSLT scripts in the same doc!");
1488     mXSLTRequests.AppendElement(request);
1489     return true;
1490   }
1491   if (aElement->GetParserCreated() == NOT_FROM_PARSER) {
1492     NS_ASSERTION(
1493         !nsContentUtils::IsSafeToRunScript(),
1494         "A script-inserted script is inserted without an update batch?");
1495     nsContentUtils::AddScriptRunner(new ScriptRequestProcessor(this, request));
1496     return false;
1497   }
1498   if (aElement->GetParserCreated() == FROM_PARSER_NETWORK &&
1499       !ReadyToExecuteParserBlockingScripts()) {
1500     NS_ASSERTION(!mParserBlockingRequest,
1501                  "There can be only one parser-blocking script at a time");
1502     mParserBlockingRequest = request;
1503     NS_ASSERTION(mXSLTRequests.isEmpty(),
1504                  "Parser-blocking scripts and XSLT scripts in the same doc!");
1505     return true;
1506   }
1507   // We now have a document.written inline script or we have an inline script
1508   // from the network but there is no style sheet that is blocking scripts.
1509   // Don't check for style sheets blocking scripts in the document.write
1510   // case to avoid style sheet network activity affecting when
1511   // document.write returns. It's not really necessary to do this if
1512   // there's no document.write currently on the call stack. However,
1513   // this way matches IE more closely than checking if document.write
1514   // is on the call stack.
1515   NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
1516                "Not safe to run a parser-inserted script?");
1517   return ProcessRequest(request) == NS_ERROR_HTMLPARSER_BLOCK;
1518 }
1519 
LookupPreloadRequest(nsIScriptElement * aElement,ScriptKind aScriptKind)1520 ScriptLoadRequest* ScriptLoader::LookupPreloadRequest(
1521     nsIScriptElement* aElement, ScriptKind aScriptKind) {
1522   MOZ_ASSERT(aElement);
1523 
1524   nsTArray<PreloadInfo>::index_type i =
1525       mPreloads.IndexOf(aElement->GetScriptURI(), 0, PreloadURIComparator());
1526   if (i == nsTArray<PreloadInfo>::NoIndex) {
1527     return nullptr;
1528   }
1529 
1530   // Found preloaded request. Note that a script-inserted script can steal a
1531   // preload!
1532   RefPtr<ScriptLoadRequest> request = mPreloads[i].mRequest;
1533   request->mElement = aElement;
1534   nsString preloadCharset(mPreloads[i].mCharset);
1535   mPreloads.RemoveElementAt(i);
1536 
1537   // Double-check that the charset the preload used is the same as the charset
1538   // we have now.
1539   nsAutoString elementCharset;
1540   aElement->GetScriptCharset(elementCharset);
1541   if (!elementCharset.Equals(preloadCharset) ||
1542       aElement->GetCORSMode() != request->mCORSMode ||
1543       mDocument->GetReferrerPolicy() != request->mReferrerPolicy ||
1544       aScriptKind != request->mKind) {
1545     // Drop the preload.
1546     request->Cancel();
1547     AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::RequestMismatch);
1548     return nullptr;
1549   }
1550 
1551   return request;
1552 }
1553 
GetSRIMetadata(const nsAString & aIntegrityAttr,SRIMetadata * aMetadataOut)1554 void ScriptLoader::GetSRIMetadata(const nsAString& aIntegrityAttr,
1555                                   SRIMetadata* aMetadataOut) {
1556   MOZ_ASSERT(aMetadataOut->IsEmpty());
1557 
1558   if (aIntegrityAttr.IsEmpty()) {
1559     return;
1560   }
1561 
1562   MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
1563           ("ScriptLoader::GetSRIMetadata, integrity=%s",
1564            NS_ConvertUTF16toUTF8(aIntegrityAttr).get()));
1565 
1566   nsAutoCString sourceUri;
1567   if (mDocument->GetDocumentURI()) {
1568     mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
1569   }
1570   SRICheck::IntegrityMetadata(aIntegrityAttr, sourceUri, mReporter,
1571                               aMetadataOut);
1572 }
1573 
1574 namespace {
1575 
1576 class NotifyOffThreadScriptLoadCompletedRunnable : public Runnable {
1577   RefPtr<ScriptLoadRequest> mRequest;
1578   RefPtr<ScriptLoader> mLoader;
1579   RefPtr<DocGroup> mDocGroup;
1580   void* mToken;
1581 
1582  public:
NotifyOffThreadScriptLoadCompletedRunnable(ScriptLoadRequest * aRequest,ScriptLoader * aLoader)1583   NotifyOffThreadScriptLoadCompletedRunnable(ScriptLoadRequest* aRequest,
1584                                              ScriptLoader* aLoader)
1585       : Runnable("dom::NotifyOffThreadScriptLoadCompletedRunnable"),
1586         mRequest(aRequest),
1587         mLoader(aLoader),
1588         mDocGroup(aLoader->GetDocGroup()),
1589         mToken(nullptr) {
1590     MOZ_ASSERT(NS_IsMainThread());
1591   }
1592 
1593   virtual ~NotifyOffThreadScriptLoadCompletedRunnable();
1594 
SetToken(void * aToken)1595   void SetToken(void* aToken) {
1596     MOZ_ASSERT(aToken && !mToken);
1597     mToken = aToken;
1598   }
1599 
Dispatch(already_AddRefed<NotifyOffThreadScriptLoadCompletedRunnable> && aSelf)1600   static void Dispatch(
1601       already_AddRefed<NotifyOffThreadScriptLoadCompletedRunnable>&& aSelf) {
1602     RefPtr<NotifyOffThreadScriptLoadCompletedRunnable> self = aSelf;
1603     RefPtr<DocGroup> docGroup = self->mDocGroup;
1604     docGroup->Dispatch(TaskCategory::Other, self.forget());
1605   }
1606 
1607   NS_DECL_NSIRUNNABLE
1608 };
1609 
1610 } /* anonymous namespace */
1611 
ProcessOffThreadRequest(ScriptLoadRequest * aRequest)1612 nsresult ScriptLoader::ProcessOffThreadRequest(ScriptLoadRequest* aRequest) {
1613   MOZ_ASSERT(aRequest->mProgress == ScriptLoadRequest::Progress::eCompiling);
1614   MOZ_ASSERT(!aRequest->mWasCompiledOMT);
1615 
1616   aRequest->mWasCompiledOMT = true;
1617 
1618   if (aRequest->IsModuleRequest()) {
1619     MOZ_ASSERT(aRequest->mOffThreadToken);
1620     ModuleLoadRequest* request = aRequest->AsModuleRequest();
1621     return ProcessFetchedModuleSource(request);
1622   }
1623 
1624   aRequest->SetReady();
1625 
1626   if (aRequest == mParserBlockingRequest) {
1627     if (!ReadyToExecuteParserBlockingScripts()) {
1628       // If not ready to execute scripts, schedule an async call to
1629       // ProcessPendingRequests to handle it.
1630       ProcessPendingRequestsAsync();
1631       return NS_OK;
1632     }
1633 
1634     // Same logic as in top of ProcessPendingRequests.
1635     mParserBlockingRequest = nullptr;
1636     UnblockParser(aRequest);
1637     ProcessRequest(aRequest);
1638     mDocument->UnblockOnload(false);
1639     ContinueParserAsync(aRequest);
1640     return NS_OK;
1641   }
1642 
1643   nsresult rv = ProcessRequest(aRequest);
1644   mDocument->UnblockOnload(false);
1645   return rv;
1646 }
1647 
1648 NotifyOffThreadScriptLoadCompletedRunnable::
~NotifyOffThreadScriptLoadCompletedRunnable()1649     ~NotifyOffThreadScriptLoadCompletedRunnable() {
1650   if (MOZ_UNLIKELY(mRequest || mLoader) && !NS_IsMainThread()) {
1651     NS_ReleaseOnMainThreadSystemGroup(
1652         "NotifyOffThreadScriptLoadCompletedRunnable::mRequest",
1653         mRequest.forget());
1654     NS_ReleaseOnMainThreadSystemGroup(
1655         "NotifyOffThreadScriptLoadCompletedRunnable::mLoader",
1656         mLoader.forget());
1657   }
1658 }
1659 
1660 NS_IMETHODIMP
Run()1661 NotifyOffThreadScriptLoadCompletedRunnable::Run() {
1662   MOZ_ASSERT(NS_IsMainThread());
1663 
1664   // We want these to be dropped on the main thread, once we return from this
1665   // function.
1666   RefPtr<ScriptLoadRequest> request = mRequest.forget();
1667   RefPtr<ScriptLoader> loader = mLoader.forget();
1668 
1669   request->mOffThreadToken = mToken;
1670   nsresult rv = loader->ProcessOffThreadRequest(request);
1671 
1672   return rv;
1673 }
1674 
OffThreadScriptLoaderCallback(void * aToken,void * aCallbackData)1675 static void OffThreadScriptLoaderCallback(void* aToken, void* aCallbackData) {
1676   RefPtr<NotifyOffThreadScriptLoadCompletedRunnable> aRunnable = dont_AddRef(
1677       static_cast<NotifyOffThreadScriptLoadCompletedRunnable*>(aCallbackData));
1678   aRunnable->SetToken(aToken);
1679   NotifyOffThreadScriptLoadCompletedRunnable::Dispatch(aRunnable.forget());
1680 }
1681 
AttemptAsyncScriptCompile(ScriptLoadRequest * aRequest)1682 nsresult ScriptLoader::AttemptAsyncScriptCompile(ScriptLoadRequest* aRequest) {
1683   MOZ_ASSERT_IF(!aRequest->IsModuleRequest(), aRequest->IsReadyToRun());
1684   MOZ_ASSERT(!aRequest->mWasCompiledOMT);
1685 
1686   // Don't off-thread compile inline scripts.
1687   if (aRequest->mIsInline) {
1688     return NS_ERROR_FAILURE;
1689   }
1690 
1691   nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
1692   if (!globalObject) {
1693     return NS_ERROR_FAILURE;
1694   }
1695 
1696   AutoJSAPI jsapi;
1697   if (!jsapi.Init(globalObject)) {
1698     return NS_ERROR_FAILURE;
1699   }
1700 
1701   JSContext* cx = jsapi.cx();
1702   JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
1703   JS::CompileOptions options(cx);
1704 
1705   nsresult rv = FillCompileOptionsForRequest(jsapi, aRequest, global, &options);
1706   if (NS_WARN_IF(NS_FAILED(rv))) {
1707     return rv;
1708   }
1709 
1710   if (aRequest->IsSource()) {
1711     if (!JS::CanCompileOffThread(cx, options, aRequest->mScriptText.length())) {
1712       return NS_ERROR_FAILURE;
1713     }
1714   } else {
1715     if (!JS::CanDecodeOffThread(cx, options,
1716                                 aRequest->mScriptBytecode.length())) {
1717       return NS_ERROR_FAILURE;
1718     }
1719   }
1720 
1721   RefPtr<NotifyOffThreadScriptLoadCompletedRunnable> runnable =
1722       new NotifyOffThreadScriptLoadCompletedRunnable(aRequest, this);
1723 
1724   if (aRequest->IsModuleRequest()) {
1725     MOZ_ASSERT(aRequest->IsSource());
1726     if (!JS::CompileOffThreadModule(cx, options, aRequest->mScriptText.begin(),
1727                                     aRequest->mScriptText.length(),
1728                                     OffThreadScriptLoaderCallback,
1729                                     static_cast<void*>(runnable))) {
1730       return NS_ERROR_OUT_OF_MEMORY;
1731     }
1732   } else if (aRequest->IsSource()) {
1733     if (!JS::CompileOffThread(cx, options, aRequest->mScriptText.begin(),
1734                               aRequest->mScriptText.length(),
1735                               OffThreadScriptLoaderCallback,
1736                               static_cast<void*>(runnable))) {
1737       return NS_ERROR_OUT_OF_MEMORY;
1738     }
1739   } else {
1740     MOZ_ASSERT(aRequest->IsBytecode());
1741     if (!JS::DecodeOffThreadScript(
1742             cx, options, aRequest->mScriptBytecode, aRequest->mBytecodeOffset,
1743             OffThreadScriptLoaderCallback, static_cast<void*>(runnable))) {
1744       return NS_ERROR_OUT_OF_MEMORY;
1745     }
1746   }
1747 
1748   mDocument->BlockOnload();
1749 
1750   // Once the compilation is finished, an event would be added to the event loop
1751   // to call ScriptLoader::ProcessOffThreadRequest with the same request.
1752   aRequest->mProgress = ScriptLoadRequest::Progress::eCompiling;
1753 
1754   Unused << runnable.forget();
1755   return NS_OK;
1756 }
1757 
CompileOffThreadOrProcessRequest(ScriptLoadRequest * aRequest)1758 nsresult ScriptLoader::CompileOffThreadOrProcessRequest(
1759     ScriptLoadRequest* aRequest) {
1760   NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
1761                "Processing requests when running scripts is unsafe.");
1762   NS_ASSERTION(!aRequest->mOffThreadToken,
1763                "Candidate for off-thread compile is already parsed off-thread");
1764   NS_ASSERTION(
1765       !aRequest->InCompilingStage(),
1766       "Candidate for off-thread compile is already in compiling stage.");
1767 
1768   nsresult rv = AttemptAsyncScriptCompile(aRequest);
1769   if (NS_SUCCEEDED(rv)) {
1770     return rv;
1771   }
1772 
1773   return ProcessRequest(aRequest);
1774 }
1775 
GetScriptSource(ScriptLoadRequest * aRequest,nsAutoString & inlineData)1776 SourceBufferHolder ScriptLoader::GetScriptSource(ScriptLoadRequest* aRequest,
1777                                                  nsAutoString& inlineData) {
1778   // Return a SourceBufferHolder object holding the script's source text.
1779   // |inlineData| is used to hold the text for inline objects.
1780 
1781   // If there's no script text, we try to get it from the element
1782   if (aRequest->mIsInline) {
1783     // XXX This is inefficient - GetText makes multiple
1784     // copies.
1785     aRequest->mElement->GetScriptText(inlineData);
1786     return SourceBufferHolder(inlineData.get(), inlineData.Length(),
1787                               SourceBufferHolder::NoOwnership);
1788   }
1789 
1790   return SourceBufferHolder(aRequest->mScriptText.begin(),
1791                             aRequest->mScriptText.length(),
1792                             SourceBufferHolder::NoOwnership);
1793 }
1794 
ProcessRequest(ScriptLoadRequest * aRequest)1795 nsresult ScriptLoader::ProcessRequest(ScriptLoadRequest* aRequest) {
1796   LOG(("ScriptLoadRequest (%p): Process request", aRequest));
1797 
1798   NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
1799                "Processing requests when running scripts is unsafe.");
1800   NS_ASSERTION(aRequest->IsReadyToRun(),
1801                "Processing a request that is not ready to run.");
1802 
1803   NS_ENSURE_ARG(aRequest);
1804 
1805   if (aRequest->IsModuleRequest() &&
1806       !aRequest->AsModuleRequest()->mModuleScript) {
1807     // There was an error fetching a module script.  Nothing to do here.
1808     LOG(("ScriptLoadRequest (%p):   Error loading request, firing error",
1809          aRequest));
1810     FireScriptAvailable(NS_ERROR_FAILURE, aRequest);
1811     return NS_OK;
1812   }
1813 
1814   nsCOMPtr<nsINode> scriptElem = do_QueryInterface(aRequest->mElement);
1815 
1816   nsCOMPtr<nsIDocument> doc;
1817   if (!aRequest->mIsInline) {
1818     doc = scriptElem->OwnerDoc();
1819   }
1820 
1821   nsCOMPtr<nsIScriptElement> oldParserInsertedScript;
1822   uint32_t parserCreated = aRequest->mElement->GetParserCreated();
1823   if (parserCreated) {
1824     oldParserInsertedScript = mCurrentParserInsertedScript;
1825     mCurrentParserInsertedScript = aRequest->mElement;
1826   }
1827 
1828   aRequest->mElement->BeginEvaluating();
1829 
1830   FireScriptAvailable(NS_OK, aRequest);
1831 
1832   // The window may have gone away by this point, in which case there's no point
1833   // in trying to run the script.
1834 
1835   {
1836     // Try to perform a microtask checkpoint
1837     nsAutoMicroTask mt;
1838   }
1839 
1840   nsPIDOMWindowInner* pwin = mDocument->GetInnerWindow();
1841   bool runScript = !!pwin;
1842   if (runScript) {
1843     nsContentUtils::DispatchTrustedEvent(
1844         scriptElem->OwnerDoc(), scriptElem,
1845         NS_LITERAL_STRING("beforescriptexecute"), true, true, &runScript);
1846   }
1847 
1848   // Inner window could have gone away after firing beforescriptexecute
1849   pwin = mDocument->GetInnerWindow();
1850   if (!pwin) {
1851     runScript = false;
1852   }
1853 
1854   nsresult rv = NS_OK;
1855   if (runScript) {
1856     if (doc) {
1857       doc->IncrementIgnoreDestructiveWritesCounter();
1858     }
1859     rv = EvaluateScript(aRequest);
1860     if (doc) {
1861       doc->DecrementIgnoreDestructiveWritesCounter();
1862     }
1863 
1864     nsContentUtils::DispatchTrustedEvent(
1865         scriptElem->OwnerDoc(), scriptElem,
1866         NS_LITERAL_STRING("afterscriptexecute"), true, false);
1867   }
1868 
1869   FireScriptEvaluated(rv, aRequest);
1870 
1871   aRequest->mElement->EndEvaluating();
1872 
1873   if (parserCreated) {
1874     mCurrentParserInsertedScript = oldParserInsertedScript;
1875   }
1876 
1877   if (aRequest->mOffThreadToken) {
1878     // The request was parsed off-main-thread, but the result of the off
1879     // thread parse was not actually needed to process the request
1880     // (disappearing window, some other error, ...). Finish the
1881     // request to avoid leaks in the JS engine.
1882     MOZ_ASSERT(!aRequest->IsModuleRequest());
1883     aRequest->MaybeCancelOffThreadScript();
1884   }
1885 
1886   // Free any source data, but keep the bytecode content as we might have to
1887   // save it later.
1888   aRequest->mScriptText.clearAndFree();
1889   if (aRequest->IsBytecode()) {
1890     // We received bytecode as input, thus we were decoding, and we will not be
1891     // encoding the bytecode once more. We can safely clear the content of this
1892     // buffer.
1893     aRequest->mScriptBytecode.clearAndFree();
1894   }
1895 
1896   return rv;
1897 }
1898 
FireScriptAvailable(nsresult aResult,ScriptLoadRequest * aRequest)1899 void ScriptLoader::FireScriptAvailable(nsresult aResult,
1900                                        ScriptLoadRequest* aRequest) {
1901   for (int32_t i = 0; i < mObservers.Count(); i++) {
1902     nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i];
1903     obs->ScriptAvailable(aResult, aRequest->mElement, aRequest->mIsInline,
1904                          aRequest->mURI, aRequest->mLineNo);
1905   }
1906 
1907   aRequest->FireScriptAvailable(aResult);
1908 }
1909 
FireScriptEvaluated(nsresult aResult,ScriptLoadRequest * aRequest)1910 void ScriptLoader::FireScriptEvaluated(nsresult aResult,
1911                                        ScriptLoadRequest* aRequest) {
1912   for (int32_t i = 0; i < mObservers.Count(); i++) {
1913     nsCOMPtr<nsIScriptLoaderObserver> obs = mObservers[i];
1914     obs->ScriptEvaluated(aResult, aRequest->mElement, aRequest->mIsInline);
1915   }
1916 
1917   aRequest->FireScriptEvaluated(aResult);
1918 }
1919 
GetScriptGlobalObject()1920 already_AddRefed<nsIScriptGlobalObject> ScriptLoader::GetScriptGlobalObject() {
1921   if (!mDocument) {
1922     return nullptr;
1923   }
1924 
1925   nsPIDOMWindowInner* pwin = mDocument->GetInnerWindow();
1926   if (!pwin) {
1927     return nullptr;
1928   }
1929 
1930   nsCOMPtr<nsIScriptGlobalObject> globalObject = do_QueryInterface(pwin);
1931   NS_ASSERTION(globalObject, "windows must be global objects");
1932 
1933   // and make sure we are setup for this type of script.
1934   nsresult rv = globalObject->EnsureScriptEnvironment();
1935   if (NS_FAILED(rv)) {
1936     return nullptr;
1937   }
1938 
1939   return globalObject.forget();
1940 }
1941 
FillCompileOptionsForRequest(const AutoJSAPI & jsapi,ScriptLoadRequest * aRequest,JS::Handle<JSObject * > aScopeChain,JS::CompileOptions * aOptions)1942 nsresult ScriptLoader::FillCompileOptionsForRequest(
1943     const AutoJSAPI& jsapi, ScriptLoadRequest* aRequest,
1944     JS::Handle<JSObject*> aScopeChain, JS::CompileOptions* aOptions) {
1945   // It's very important to use aRequest->mURI, not the final URI of the channel
1946   // aRequest ended up getting script data from, as the script filename.
1947   nsresult rv;
1948   nsContentUtils::GetWrapperSafeScriptFilename(mDocument, aRequest->mURI,
1949                                                aRequest->mURL, &rv);
1950   if (NS_WARN_IF(NS_FAILED(rv))) {
1951     return rv;
1952   }
1953 
1954   if (mDocument) {
1955     mDocument->NoteScriptTrackingStatus(aRequest->mURL, aRequest->IsTracking());
1956   }
1957 
1958   bool isScriptElement =
1959       !aRequest->IsModuleRequest() || aRequest->AsModuleRequest()->IsTopLevel();
1960   aOptions->setIntroductionType(isScriptElement ? "scriptElement"
1961                                                 : "importedModule");
1962   aOptions->setFileAndLine(aRequest->mURL.get(), aRequest->mLineNo);
1963   aOptions->setIsRunOnce(true);
1964   aOptions->setNoScriptRval(true);
1965   if (aRequest->mHasSourceMapURL) {
1966     aOptions->setSourceMapURL(aRequest->mSourceMapURL.get());
1967   }
1968   if (aRequest->mOriginPrincipal) {
1969     nsIPrincipal* scriptPrin = nsContentUtils::ObjectPrincipal(aScopeChain);
1970     bool subsumes = scriptPrin->Subsumes(aRequest->mOriginPrincipal);
1971     aOptions->setMutedErrors(!subsumes);
1972   }
1973 
1974   if (aRequest->IsModuleRequest()) {
1975     aOptions->hideScriptFromDebugger = true;
1976   } else {
1977     JSContext* cx = jsapi.cx();
1978     JS::Rooted<JS::Value> elementVal(cx);
1979     MOZ_ASSERT(aRequest->mElement);
1980     if (NS_SUCCEEDED(nsContentUtils::WrapNative(cx, aRequest->mElement,
1981                                                 &elementVal,
1982                                                 /* aAllowWrapping = */ true))) {
1983       MOZ_ASSERT(elementVal.isObject());
1984       aOptions->setElement(&elementVal.toObject());
1985     }
1986   }
1987 
1988   return NS_OK;
1989 }
1990 
ShouldCacheBytecode(ScriptLoadRequest * aRequest)1991 /* static */ bool ScriptLoader::ShouldCacheBytecode(
1992     ScriptLoadRequest* aRequest) {
1993   using mozilla::TimeDuration;
1994   using mozilla::TimeStamp;
1995 
1996   // We need the nsICacheInfoChannel to exist to be able to open the alternate
1997   // data output stream. This pointer would only be non-null if the bytecode was
1998   // activated at the time the channel got created in StartLoad.
1999   if (!aRequest->mCacheInfo) {
2000     LOG(("ScriptLoadRequest (%p): Cannot cache anything (cacheInfo = %p)",
2001          aRequest, aRequest->mCacheInfo.get()));
2002     return false;
2003   }
2004 
2005   // Look at the preference to know which strategy (parameters) should be used
2006   // when the bytecode cache is enabled.
2007   int32_t strategy = nsContentUtils::BytecodeCacheStrategy();
2008 
2009   // List of parameters used by the strategies.
2010   bool hasSourceLengthMin = false;
2011   bool hasFetchCountMin = false;
2012   size_t sourceLengthMin = 100;
2013   int32_t fetchCountMin = 4;
2014 
2015   LOG(("ScriptLoadRequest (%p): Bytecode-cache: strategy = %d.", aRequest,
2016        strategy));
2017   switch (strategy) {
2018     case -2: {
2019       // Reader mode, keep requesting alternate data but no longer save it.
2020       LOG(("ScriptLoadRequest (%p): Bytecode-cache: Encoding disabled.",
2021            aRequest));
2022       return false;
2023     }
2024     case -1: {
2025       // Eager mode, skip heuristics!
2026       hasSourceLengthMin = false;
2027       hasFetchCountMin = false;
2028       break;
2029     }
2030     default:
2031     case 0: {
2032       hasSourceLengthMin = true;
2033       hasFetchCountMin = true;
2034       sourceLengthMin = 1024;
2035       // If we were to optimize only for speed, without considering the impact
2036       // on memory, we should set this threshold to 2. (Bug 900784 comment 120)
2037       fetchCountMin = 4;
2038       break;
2039     }
2040   }
2041 
2042   // If the script is too small/large, do not attempt at creating a bytecode
2043   // cache for this script, as the overhead of parsing it might not be worth the
2044   // effort.
2045   if (hasSourceLengthMin && aRequest->mScriptText.length() < sourceLengthMin) {
2046     LOG(("ScriptLoadRequest (%p): Bytecode-cache: Script is too small.",
2047          aRequest));
2048     return false;
2049   }
2050 
2051   // Check that we loaded the cache entry a few times before attempting any
2052   // bytecode-cache optimization, such that we do not waste time on entry which
2053   // are going to be dropped soon.
2054   if (hasFetchCountMin) {
2055     int32_t fetchCount = 0;
2056     if (NS_FAILED(aRequest->mCacheInfo->GetCacheTokenFetchCount(&fetchCount))) {
2057       LOG(("ScriptLoadRequest (%p): Bytecode-cache: Cannot get fetchCount.",
2058            aRequest));
2059       return false;
2060     }
2061     LOG(("ScriptLoadRequest (%p): Bytecode-cache: fetchCount = %d.", aRequest,
2062          fetchCount));
2063     if (fetchCount < fetchCountMin) {
2064       return false;
2065     }
2066   }
2067 
2068   LOG(("ScriptLoadRequest (%p): Bytecode-cache: Trigger encoding.", aRequest));
2069   return true;
2070 }
2071 
EvaluateScript(ScriptLoadRequest * aRequest)2072 nsresult ScriptLoader::EvaluateScript(ScriptLoadRequest* aRequest) {
2073   using namespace mozilla::Telemetry;
2074   MOZ_ASSERT(aRequest->IsReadyToRun());
2075 
2076   // We need a document to evaluate scripts.
2077   if (!mDocument) {
2078     return NS_ERROR_FAILURE;
2079   }
2080 
2081   nsCOMPtr<nsIContent> scriptContent(do_QueryInterface(aRequest->mElement));
2082   nsIDocument* ownerDoc = scriptContent->OwnerDoc();
2083   if (ownerDoc != mDocument) {
2084     // Willful violation of HTML5 as of 2010-12-01
2085     return NS_ERROR_FAILURE;
2086   }
2087 
2088   // Report telemetry results of the number of scripts evaluated.
2089   mDocument->SetDocumentIncCounter(IncCounter::eIncCounter_ScriptTag);
2090 
2091   // Get the script-type to be used by this element.
2092   NS_ASSERTION(scriptContent, "no content - what is default script-type?");
2093 
2094   nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
2095   if (!globalObject) {
2096     return NS_ERROR_FAILURE;
2097   }
2098 
2099   // Make sure context is a strong reference since we access it after
2100   // we've executed a script, which may cause all other references to
2101   // the context to go away.
2102   nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext();
2103   if (!context) {
2104     return NS_ERROR_FAILURE;
2105   }
2106 
2107   // New script entry point required, due to the "Create a script" sub-step of
2108   // http://www.whatwg.org/specs/web-apps/current-work/#execute-the-script-block
2109   nsAutoMicroTask mt;
2110   AutoEntryScript aes(globalObject, "<script> element", true);
2111   JSContext* cx = aes.cx();
2112   JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
2113 
2114   bool oldProcessingScriptTag = context->GetProcessingScriptTag();
2115   context->SetProcessingScriptTag(true);
2116   nsresult rv;
2117   {
2118     if (aRequest->IsModuleRequest()) {
2119       // When a module is already loaded, it is not feched a second time and the
2120       // mDataType of the request might remain set to DataType::Unknown.
2121       MOZ_ASSERT(!aRequest->IsBytecode());
2122       LOG(("ScriptLoadRequest (%p): Evaluate Module", aRequest));
2123 
2124       // currentScript is set to null for modules.
2125       AutoCurrentScriptUpdater scriptUpdater(this, nullptr);
2126 
2127       rv = EnsureModuleResolveHook(cx);
2128       NS_ENSURE_SUCCESS(rv, rv);
2129 
2130       ModuleLoadRequest* request = aRequest->AsModuleRequest();
2131       MOZ_ASSERT(request->mModuleScript);
2132       MOZ_ASSERT(!request->mOffThreadToken);
2133 
2134       ModuleScript* moduleScript = request->mModuleScript;
2135       if (moduleScript->HasErrorToRethrow()) {
2136         LOG(("ScriptLoadRequest (%p):   module has error to rethrow",
2137              aRequest));
2138         JS::Rooted<JS::Value> error(cx, moduleScript->ErrorToRethrow());
2139         JS_SetPendingException(cx, error);
2140         return NS_OK;  // An error is reported by AutoEntryScript.
2141       }
2142 
2143       JS::Rooted<JSObject*> module(cx, moduleScript->ModuleRecord());
2144       MOZ_ASSERT(module);
2145 
2146       if (!moduleScript->SourceElementAssociated()) {
2147         rv = nsJSUtils::InitModuleSourceElement(cx, module, aRequest->mElement);
2148         NS_ENSURE_SUCCESS(rv, rv);
2149         moduleScript->SetSourceElementAssociated();
2150 
2151         // The script is now ready to be exposed to the debugger.
2152         JS::Rooted<JSScript*> script(cx, JS::GetModuleScript(module));
2153         JS::ExposeScriptToDebugger(cx, script);
2154       }
2155 
2156       rv = nsJSUtils::ModuleEvaluate(cx, module);
2157       MOZ_ASSERT(NS_FAILED(rv) == aes.HasException());
2158       if (NS_FAILED(rv)) {
2159         LOG(("ScriptLoadRequest (%p):   evaluation failed", aRequest));
2160         rv = NS_OK;  // An error is reported by AutoEntryScript.
2161       }
2162 
2163       aRequest->mCacheInfo = nullptr;
2164     } else {
2165       // Update our current script.
2166       AutoCurrentScriptUpdater scriptUpdater(this, aRequest->mElement);
2167 
2168       JS::CompileOptions options(cx);
2169       rv = FillCompileOptionsForRequest(aes, aRequest, global, &options);
2170 
2171       if (NS_SUCCEEDED(rv)) {
2172         if (aRequest->IsBytecode()) {
2173           TRACE_FOR_TEST(aRequest->mElement, "scriptloader_execute");
2174           nsJSUtils::ExecutionContext exec(cx, global);
2175           if (aRequest->mOffThreadToken) {
2176             LOG(("ScriptLoadRequest (%p): Decode Bytecode & Join and Execute",
2177                  aRequest));
2178             AutoTimer<DOM_SCRIPT_OFF_THREAD_DECODE_EXEC_MS> timer;
2179             rv = exec.DecodeJoinAndExec(&aRequest->mOffThreadToken);
2180           } else {
2181             LOG(("ScriptLoadRequest (%p): Decode Bytecode and Execute",
2182                  aRequest));
2183             AutoTimer<DOM_SCRIPT_MAIN_THREAD_DECODE_EXEC_MS> timer;
2184             rv = exec.DecodeAndExec(options, aRequest->mScriptBytecode,
2185                                     aRequest->mBytecodeOffset);
2186           }
2187           // We do not expect to be saving anything when we already have some
2188           // bytecode.
2189           MOZ_ASSERT(!aRequest->mCacheInfo);
2190         } else {
2191           MOZ_ASSERT(aRequest->IsSource());
2192           JS::Rooted<JSScript*> script(cx);
2193           bool encodeBytecode = ShouldCacheBytecode(aRequest);
2194 
2195           TimeStamp start;
2196           if (Telemetry::CanRecordExtended()) {
2197             // Only record telemetry for scripts which are above the threshold.
2198             if (aRequest->mCacheInfo &&
2199                 aRequest->mScriptText.length() >= 1024) {
2200               start = TimeStamp::Now();
2201             }
2202           }
2203 
2204           {
2205             nsJSUtils::ExecutionContext exec(cx, global);
2206             exec.SetEncodeBytecode(encodeBytecode);
2207             TRACE_FOR_TEST(aRequest->mElement, "scriptloader_execute");
2208             if (aRequest->mOffThreadToken) {
2209               // Off-main-thread parsing.
2210               LOG(
2211                   ("ScriptLoadRequest (%p): Join (off-thread parsing) and "
2212                    "Execute",
2213                    aRequest));
2214               rv = exec.JoinAndExec(&aRequest->mOffThreadToken, &script);
2215               if (start) {
2216                 AccumulateTimeDelta(
2217                     encodeBytecode ? DOM_SCRIPT_OFF_THREAD_PARSE_ENCODE_EXEC_MS
2218                                    : DOM_SCRIPT_OFF_THREAD_PARSE_EXEC_MS,
2219                     start);
2220               }
2221             } else {
2222               // Main thread parsing (inline and small scripts)
2223               LOG(("ScriptLoadRequest (%p): Compile And Exec", aRequest));
2224               nsAutoString inlineData;
2225               SourceBufferHolder srcBuf = GetScriptSource(aRequest, inlineData);
2226               rv = exec.CompileAndExec(options, srcBuf, &script);
2227               if (start) {
2228                 AccumulateTimeDelta(
2229                     encodeBytecode ? DOM_SCRIPT_MAIN_THREAD_PARSE_ENCODE_EXEC_MS
2230                                    : DOM_SCRIPT_MAIN_THREAD_PARSE_EXEC_MS,
2231                     start);
2232               }
2233             }
2234           }
2235 
2236           // Queue the current script load request to later save the bytecode.
2237           if (script && encodeBytecode) {
2238             aRequest->mScript = script;
2239             HoldJSObjects(aRequest);
2240             TRACE_FOR_TEST(aRequest->mElement, "scriptloader_encode");
2241             MOZ_ASSERT(aRequest->mBytecodeOffset ==
2242                        aRequest->mScriptBytecode.length());
2243             RegisterForBytecodeEncoding(aRequest);
2244           } else {
2245             LOG(
2246                 ("ScriptLoadRequest (%p): Bytecode-cache: disabled (rv = %X, "
2247                  "script = %p)",
2248                  aRequest, unsigned(rv), script.get()));
2249             TRACE_FOR_TEST_NONE(aRequest->mElement, "scriptloader_no_encode");
2250             aRequest->mCacheInfo = nullptr;
2251           }
2252         }
2253       }
2254     }
2255 
2256     // Even if we are not saving the bytecode of the current script, we have
2257     // to trigger the encoding of the bytecode, as the current script can
2258     // call functions of a script for which we are recording the bytecode.
2259     LOG(("ScriptLoadRequest (%p): ScriptLoader = %p", aRequest, this));
2260     MaybeTriggerBytecodeEncoding();
2261   }
2262 
2263   context->SetProcessingScriptTag(oldProcessingScriptTag);
2264   return rv;
2265 }
2266 
RegisterForBytecodeEncoding(ScriptLoadRequest * aRequest)2267 void ScriptLoader::RegisterForBytecodeEncoding(ScriptLoadRequest* aRequest) {
2268   MOZ_ASSERT(aRequest->mCacheInfo);
2269   MOZ_ASSERT(aRequest->mScript);
2270   mBytecodeEncodingQueue.AppendElement(aRequest);
2271 }
2272 
LoadEventFired()2273 void ScriptLoader::LoadEventFired() {
2274   mLoadEventFired = true;
2275   MaybeTriggerBytecodeEncoding();
2276 }
2277 
MaybeTriggerBytecodeEncoding()2278 void ScriptLoader::MaybeTriggerBytecodeEncoding() {
2279   // If we already gave up, ensure that we are not going to enqueue any script,
2280   // and that we finalize them properly.
2281   if (mGiveUpEncoding) {
2282     LOG(("ScriptLoader (%p): Keep giving-up bytecode encoding.", this));
2283     GiveUpBytecodeEncoding();
2284     return;
2285   }
2286 
2287   // We wait for the load event to be fired before saving the bytecode of
2288   // any script to the cache. It is quite common to have load event
2289   // listeners trigger more JavaScript execution, that we want to save as
2290   // part of this start-up bytecode cache.
2291   if (!mLoadEventFired) {
2292     LOG(("ScriptLoader (%p): Wait for the load-end event to fire.", this));
2293     return;
2294   }
2295 
2296   // No need to fire any event if there is no bytecode to be saved.
2297   if (mBytecodeEncodingQueue.isEmpty()) {
2298     LOG(("ScriptLoader (%p): No script in queue to be encoded.", this));
2299     return;
2300   }
2301 
2302   // Wait until all scripts are loaded before saving the bytecode, such that
2303   // we capture most of the intialization of the page.
2304   if (HasPendingRequests()) {
2305     LOG(("ScriptLoader (%p): Wait for other pending request to finish.", this));
2306     return;
2307   }
2308 
2309   // Create a new runnable dedicated to encoding the content of the bytecode of
2310   // all enqueued scripts when the document is idle. In case of failure, we
2311   // give-up on encoding the bytecode.
2312   nsCOMPtr<nsIRunnable> encoder = NewRunnableMethod(
2313       "ScriptLoader::EncodeBytecode", this, &ScriptLoader::EncodeBytecode);
2314   if (NS_FAILED(NS_IdleDispatchToCurrentThread(encoder.forget()))) {
2315     GiveUpBytecodeEncoding();
2316     return;
2317   }
2318 
2319   LOG(("ScriptLoader (%p): Schedule bytecode encoding.", this));
2320 }
2321 
EncodeBytecode()2322 void ScriptLoader::EncodeBytecode() {
2323   LOG(("ScriptLoader (%p): Start bytecode encoding.", this));
2324 
2325   // If any script got added in the previous loop cycle, wait until all
2326   // remaining script executions are completed, such that we capture most of
2327   // the initialization.
2328   if (HasPendingRequests()) {
2329     return;
2330   }
2331 
2332   nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
2333   if (!globalObject) {
2334     GiveUpBytecodeEncoding();
2335     return;
2336   }
2337 
2338   nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext();
2339   if (!context) {
2340     GiveUpBytecodeEncoding();
2341     return;
2342   }
2343 
2344   Telemetry::AutoTimer<Telemetry::DOM_SCRIPT_ENCODING_MS_PER_DOCUMENT> timer;
2345   AutoEntryScript aes(globalObject, "encode bytecode", true);
2346   RefPtr<ScriptLoadRequest> request;
2347   while (!mBytecodeEncodingQueue.isEmpty()) {
2348     request = mBytecodeEncodingQueue.StealFirst();
2349     EncodeRequestBytecode(aes.cx(), request);
2350     request->mScriptBytecode.clearAndFree();
2351     request->DropBytecodeCacheReferences();
2352   }
2353 }
2354 
EncodeRequestBytecode(JSContext * aCx,ScriptLoadRequest * aRequest)2355 void ScriptLoader::EncodeRequestBytecode(JSContext* aCx,
2356                                          ScriptLoadRequest* aRequest) {
2357   using namespace mozilla::Telemetry;
2358   nsresult rv = NS_OK;
2359   MOZ_ASSERT(aRequest->mCacheInfo);
2360   auto bytecodeFailed = mozilla::MakeScopeExit([&]() {
2361     TRACE_FOR_TEST_NONE(aRequest->mElement, "scriptloader_bytecode_failed");
2362   });
2363 
2364   JS::RootedScript script(aCx, aRequest->mScript);
2365   if (!JS::FinishIncrementalEncoding(aCx, script, aRequest->mScriptBytecode)) {
2366     LOG(("ScriptLoadRequest (%p): Cannot serialize bytecode", aRequest));
2367     AccumulateCategorical(LABELS_DOM_SCRIPT_ENCODING_STATUS::EncodingFailure);
2368     return;
2369   }
2370 
2371   if (aRequest->mScriptBytecode.length() >= UINT32_MAX) {
2372     LOG(
2373         ("ScriptLoadRequest (%p): Bytecode cache is too large to be decoded "
2374          "correctly.",
2375          aRequest));
2376     AccumulateCategorical(LABELS_DOM_SCRIPT_ENCODING_STATUS::BufferTooLarge);
2377     return;
2378   }
2379 
2380   // Open the output stream to the cache entry alternate data storage. This
2381   // might fail if the stream is already open by another request, in which
2382   // case, we just ignore the current one.
2383   nsCOMPtr<nsIOutputStream> output;
2384   rv = aRequest->mCacheInfo->OpenAlternativeOutputStream(
2385       nsContentUtils::JSBytecodeMimeType(), getter_AddRefs(output));
2386   if (NS_FAILED(rv)) {
2387     LOG(
2388         ("ScriptLoadRequest (%p): Cannot open bytecode cache (rv = %X, output "
2389          "= %p)",
2390          aRequest, unsigned(rv), output.get()));
2391     AccumulateCategorical(LABELS_DOM_SCRIPT_ENCODING_STATUS::OpenFailure);
2392     return;
2393   }
2394   MOZ_ASSERT(output);
2395   auto closeOutStream = mozilla::MakeScopeExit([&]() {
2396     nsresult rv = output->Close();
2397     LOG(("ScriptLoadRequest (%p): Closing (rv = %X)", aRequest, unsigned(rv)));
2398     if (NS_FAILED(rv)) {
2399       AccumulateCategorical(LABELS_DOM_SCRIPT_ENCODING_STATUS::CloseFailure);
2400     }
2401   });
2402 
2403   uint32_t n;
2404   rv = output->Write(reinterpret_cast<char*>(aRequest->mScriptBytecode.begin()),
2405                      aRequest->mScriptBytecode.length(), &n);
2406   LOG((
2407       "ScriptLoadRequest (%p): Write bytecode cache (rv = %X, length = %u, "
2408       "written = %u)",
2409       aRequest, unsigned(rv), unsigned(aRequest->mScriptBytecode.length()), n));
2410   if (NS_FAILED(rv)) {
2411     AccumulateCategorical(LABELS_DOM_SCRIPT_ENCODING_STATUS::WriteFailure);
2412     return;
2413   }
2414 
2415   bytecodeFailed.release();
2416   TRACE_FOR_TEST_NONE(aRequest->mElement, "scriptloader_bytecode_saved");
2417   AccumulateCategorical(LABELS_DOM_SCRIPT_ENCODING_STATUS::EncodingSuccess);
2418 }
2419 
GiveUpBytecodeEncoding()2420 void ScriptLoader::GiveUpBytecodeEncoding() {
2421   // If the document went away prematurely, we still want to set this, in order
2422   // to avoid queuing more scripts.
2423   mGiveUpEncoding = true;
2424 
2425   // Ideally we prefer to properly end the incremental encoder, such that we
2426   // would not keep a large buffer around.  If we cannot, we fallback on the
2427   // removal of all request from the current list and these large buffers would
2428   // be removed at the same time as the source object.
2429   nsCOMPtr<nsIScriptGlobalObject> globalObject = GetScriptGlobalObject();
2430   Maybe<AutoEntryScript> aes;
2431 
2432   if (globalObject) {
2433     nsCOMPtr<nsIScriptContext> context = globalObject->GetScriptContext();
2434     if (context) {
2435       aes.emplace(globalObject, "give-up bytecode encoding", true);
2436     }
2437   }
2438 
2439   while (!mBytecodeEncodingQueue.isEmpty()) {
2440     RefPtr<ScriptLoadRequest> request = mBytecodeEncodingQueue.StealFirst();
2441     LOG(("ScriptLoadRequest (%p): Cannot serialize bytecode", request.get()));
2442     TRACE_FOR_TEST_NONE(request->mElement, "scriptloader_bytecode_failed");
2443 
2444     if (aes.isSome()) {
2445       JS::RootedScript script(aes->cx(), request->mScript);
2446       Unused << JS::FinishIncrementalEncoding(aes->cx(), script,
2447                                               request->mScriptBytecode);
2448     }
2449 
2450     request->mScriptBytecode.clearAndFree();
2451     request->DropBytecodeCacheReferences();
2452   }
2453 }
2454 
HasPendingRequests()2455 bool ScriptLoader::HasPendingRequests() {
2456   return mParserBlockingRequest || !mXSLTRequests.isEmpty() ||
2457          !mLoadedAsyncRequests.isEmpty() ||
2458          !mNonAsyncExternalScriptInsertedRequests.isEmpty() ||
2459          !mDeferRequests.isEmpty() || !mPendingChildLoaders.IsEmpty();
2460 }
2461 
ProcessPendingRequestsAsync()2462 void ScriptLoader::ProcessPendingRequestsAsync() {
2463   if (HasPendingRequests()) {
2464     nsCOMPtr<nsIRunnable> task =
2465         NewRunnableMethod("dom::ScriptLoader::ProcessPendingRequests", this,
2466                           &ScriptLoader::ProcessPendingRequests);
2467     if (mDocument) {
2468       mDocument->Dispatch(TaskCategory::Other, task.forget());
2469     } else {
2470       NS_DispatchToCurrentThread(task.forget());
2471     }
2472   }
2473 }
2474 
ProcessPendingRequests()2475 void ScriptLoader::ProcessPendingRequests() {
2476   RefPtr<ScriptLoadRequest> request;
2477 
2478   if (mParserBlockingRequest && mParserBlockingRequest->IsReadyToRun() &&
2479       ReadyToExecuteParserBlockingScripts()) {
2480     request.swap(mParserBlockingRequest);
2481     UnblockParser(request);
2482     ProcessRequest(request);
2483     if (request->mWasCompiledOMT) {
2484       mDocument->UnblockOnload(false);
2485     }
2486     ContinueParserAsync(request);
2487   }
2488 
2489   while (ReadyToExecuteParserBlockingScripts() && !mXSLTRequests.isEmpty() &&
2490          mXSLTRequests.getFirst()->IsReadyToRun()) {
2491     request = mXSLTRequests.StealFirst();
2492     ProcessRequest(request);
2493   }
2494 
2495   while (ReadyToExecuteScripts() && !mLoadedAsyncRequests.isEmpty()) {
2496     request = mLoadedAsyncRequests.StealFirst();
2497     if (request->IsModuleRequest()) {
2498       ProcessRequest(request);
2499     } else {
2500       CompileOffThreadOrProcessRequest(request);
2501     }
2502   }
2503 
2504   while (ReadyToExecuteScripts() &&
2505          !mNonAsyncExternalScriptInsertedRequests.isEmpty() &&
2506          mNonAsyncExternalScriptInsertedRequests.getFirst()->IsReadyToRun()) {
2507     // Violate the HTML5 spec and execute these in the insertion order in
2508     // order to make LABjs and the "order" plug-in for RequireJS work with
2509     // their Gecko-sniffed code path. See
2510     // http://lists.w3.org/Archives/Public/public-html/2010Oct/0088.html
2511     request = mNonAsyncExternalScriptInsertedRequests.StealFirst();
2512     ProcessRequest(request);
2513   }
2514 
2515   if (mDocumentParsingDone && mXSLTRequests.isEmpty()) {
2516     while (ReadyToExecuteScripts() && !mDeferRequests.isEmpty() &&
2517            mDeferRequests.getFirst()->IsReadyToRun()) {
2518       request = mDeferRequests.StealFirst();
2519       ProcessRequest(request);
2520     }
2521   }
2522 
2523   while (!mPendingChildLoaders.IsEmpty() &&
2524          ReadyToExecuteParserBlockingScripts()) {
2525     RefPtr<ScriptLoader> child = mPendingChildLoaders[0];
2526     mPendingChildLoaders.RemoveElementAt(0);
2527     child->RemoveParserBlockingScriptExecutionBlocker();
2528   }
2529 
2530   if (mDocumentParsingDone && mDocument && !mParserBlockingRequest &&
2531       mNonAsyncExternalScriptInsertedRequests.isEmpty() &&
2532       mXSLTRequests.isEmpty() && mDeferRequests.isEmpty() &&
2533       MaybeRemovedDeferRequests()) {
2534     return ProcessPendingRequests();
2535   }
2536 
2537   if (mDocumentParsingDone && mDocument && !mParserBlockingRequest &&
2538       mLoadingAsyncRequests.isEmpty() && mLoadedAsyncRequests.isEmpty() &&
2539       mNonAsyncExternalScriptInsertedRequests.isEmpty() &&
2540       mXSLTRequests.isEmpty() && mDeferRequests.isEmpty()) {
2541     // No more pending scripts; time to unblock onload.
2542     // OK to unblock onload synchronously here, since callers must be
2543     // prepared for the world changing anyway.
2544     mDocumentParsingDone = false;
2545     mDocument->UnblockOnload(true);
2546   }
2547 }
2548 
ReadyToExecuteParserBlockingScripts()2549 bool ScriptLoader::ReadyToExecuteParserBlockingScripts() {
2550   // Make sure the SelfReadyToExecuteParserBlockingScripts check is first, so
2551   // that we don't block twice on an ancestor.
2552   if (!SelfReadyToExecuteParserBlockingScripts()) {
2553     return false;
2554   }
2555 
2556   for (nsIDocument* doc = mDocument; doc; doc = doc->GetParentDocument()) {
2557     ScriptLoader* ancestor = doc->ScriptLoader();
2558     if (!ancestor->SelfReadyToExecuteParserBlockingScripts() &&
2559         ancestor->AddPendingChildLoader(this)) {
2560       AddParserBlockingScriptExecutionBlocker();
2561       return false;
2562     }
2563   }
2564 
2565   return true;
2566 }
2567 
ConvertToUTF16(nsIChannel * aChannel,const uint8_t * aData,uint32_t aLength,const nsAString & aHintCharset,nsIDocument * aDocument,char16_t * & aBufOut,size_t & aLengthOut)2568 /* static */ nsresult ScriptLoader::ConvertToUTF16(
2569     nsIChannel* aChannel, const uint8_t* aData, uint32_t aLength,
2570     const nsAString& aHintCharset, nsIDocument* aDocument, char16_t*& aBufOut,
2571     size_t& aLengthOut) {
2572   if (!aLength) {
2573     aBufOut = nullptr;
2574     aLengthOut = 0;
2575     return NS_OK;
2576   }
2577 
2578   auto data = MakeSpan(aData, aLength);
2579 
2580   // The encoding info precedence is as follows from high to low:
2581   // The BOM
2582   // HTTP Content-Type (if name recognized)
2583   // charset attribute (if name recognized)
2584   // The encoding of the document
2585 
2586   UniquePtr<Decoder> unicodeDecoder;
2587 
2588   const Encoding* encoding;
2589   size_t bomLength;
2590   Tie(encoding, bomLength) = Encoding::ForBOM(data);
2591   if (encoding) {
2592     unicodeDecoder = encoding->NewDecoderWithBOMRemoval();
2593   }
2594 
2595   if (!unicodeDecoder && aChannel) {
2596     nsAutoCString label;
2597     if (NS_SUCCEEDED(aChannel->GetContentCharset(label)) &&
2598         (encoding = Encoding::ForLabel(label))) {
2599       unicodeDecoder = encoding->NewDecoderWithoutBOMHandling();
2600     }
2601   }
2602 
2603   if (!unicodeDecoder && (encoding = Encoding::ForLabel(aHintCharset))) {
2604     unicodeDecoder = encoding->NewDecoderWithoutBOMHandling();
2605   }
2606 
2607   if (!unicodeDecoder && aDocument) {
2608     unicodeDecoder =
2609         aDocument->GetDocumentCharacterSet()->NewDecoderWithoutBOMHandling();
2610   }
2611 
2612   if (!unicodeDecoder) {
2613     // Curiously, there are various callers that don't pass aDocument. The
2614     // fallback in the old code was ISO-8859-1, which behaved like
2615     // windows-1252.
2616     unicodeDecoder = WINDOWS_1252_ENCODING->NewDecoderWithoutBOMHandling();
2617   }
2618 
2619   CheckedInt<size_t> maxLength = unicodeDecoder->MaxUTF16BufferLength(aLength);
2620   if (!maxLength.isValid()) {
2621     aBufOut = nullptr;
2622     aLengthOut = 0;
2623     return NS_ERROR_OUT_OF_MEMORY;
2624   }
2625 
2626   size_t unicodeLength = maxLength.value();
2627 
2628   maxLength *= sizeof(char16_t);
2629 
2630   if (!maxLength.isValid()) {
2631     aBufOut = nullptr;
2632     aLengthOut = 0;
2633     return NS_ERROR_OUT_OF_MEMORY;
2634   }
2635 
2636   aBufOut = static_cast<char16_t*>(js_malloc(maxLength.value()));
2637   if (!aBufOut) {
2638     aLengthOut = 0;
2639     return NS_ERROR_OUT_OF_MEMORY;
2640   }
2641 
2642   uint32_t result;
2643   size_t read;
2644   size_t written;
2645   bool hadErrors;
2646   Tie(result, read, written, hadErrors) = unicodeDecoder->DecodeToUTF16(
2647       data, MakeSpan(aBufOut, unicodeLength), true);
2648   MOZ_ASSERT(result == kInputEmpty);
2649   MOZ_ASSERT(read == aLength);
2650   MOZ_ASSERT(written <= unicodeLength);
2651   Unused << hadErrors;
2652   aLengthOut = written;
2653 
2654   nsAutoCString charset;
2655   unicodeDecoder->Encoding()->Name(charset);
2656   mozilla::Telemetry::Accumulate(mozilla::Telemetry::DOM_SCRIPT_SRC_ENCODING,
2657                                  charset);
2658   return NS_OK;
2659 }
2660 
OnStreamComplete(nsIIncrementalStreamLoader * aLoader,ScriptLoadRequest * aRequest,nsresult aChannelStatus,nsresult aSRIStatus,SRICheckDataVerifier * aSRIDataVerifier)2661 nsresult ScriptLoader::OnStreamComplete(
2662     nsIIncrementalStreamLoader* aLoader, ScriptLoadRequest* aRequest,
2663     nsresult aChannelStatus, nsresult aSRIStatus,
2664     SRICheckDataVerifier* aSRIDataVerifier) {
2665   NS_ASSERTION(aRequest, "null request in stream complete handler");
2666   NS_ENSURE_TRUE(aRequest, NS_ERROR_FAILURE);
2667 
2668   nsresult rv = VerifySRI(aRequest, aLoader, aSRIStatus, aSRIDataVerifier);
2669 
2670   if (NS_SUCCEEDED(rv)) {
2671     // If we are loading from source, save the computed SRI hash or a dummy SRI
2672     // hash in case we are going to save the bytecode of this script in the
2673     // cache.
2674     if (aRequest->IsSource()) {
2675       rv = SaveSRIHash(aRequest, aSRIDataVerifier);
2676     }
2677 
2678     if (NS_SUCCEEDED(rv)) {
2679       rv = PrepareLoadedRequest(aRequest, aLoader, aChannelStatus);
2680     }
2681 
2682     if (NS_FAILED(rv)) {
2683       ReportErrorToConsole(aRequest, rv);
2684     }
2685   }
2686 
2687   if (NS_FAILED(rv)) {
2688     // When loading bytecode, we verify the SRI hash. If it does not match the
2689     // one from the document we restart the load, forcing us to load the source
2690     // instead. If this happens do not remove the current request from script
2691     // loader's data structures or fire any events.
2692     if (aChannelStatus != NS_BINDING_RETARGETED) {
2693       HandleLoadError(aRequest, rv);
2694     }
2695   }
2696 
2697   // Process our request and/or any pending ones
2698   ProcessPendingRequests();
2699 
2700   return NS_OK;
2701 }
2702 
VerifySRI(ScriptLoadRequest * aRequest,nsIIncrementalStreamLoader * aLoader,nsresult aSRIStatus,SRICheckDataVerifier * aSRIDataVerifier) const2703 nsresult ScriptLoader::VerifySRI(ScriptLoadRequest* aRequest,
2704                                  nsIIncrementalStreamLoader* aLoader,
2705                                  nsresult aSRIStatus,
2706                                  SRICheckDataVerifier* aSRIDataVerifier) const {
2707   nsCOMPtr<nsIRequest> channelRequest;
2708   aLoader->GetRequest(getter_AddRefs(channelRequest));
2709   nsCOMPtr<nsIChannel> channel;
2710   channel = do_QueryInterface(channelRequest);
2711 
2712   nsresult rv = NS_OK;
2713   if (!aRequest->mIntegrity.IsEmpty() && NS_SUCCEEDED((rv = aSRIStatus))) {
2714     MOZ_ASSERT(aSRIDataVerifier);
2715     MOZ_ASSERT(mReporter);
2716 
2717     nsAutoCString sourceUri;
2718     if (mDocument && mDocument->GetDocumentURI()) {
2719       mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri);
2720     }
2721     rv = aSRIDataVerifier->Verify(aRequest->mIntegrity, channel, sourceUri,
2722                                   mReporter);
2723     if (channelRequest) {
2724       mReporter->FlushReportsToConsole(
2725           nsContentUtils::GetInnerWindowID(channelRequest));
2726     } else {
2727       mReporter->FlushConsoleReports(mDocument);
2728     }
2729     if (NS_FAILED(rv)) {
2730       rv = NS_ERROR_SRI_CORRUPT;
2731     }
2732   } else {
2733     nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
2734 
2735     if (loadInfo && loadInfo->GetEnforceSRI()) {
2736       MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
2737               ("ScriptLoader::OnStreamComplete, required SRI not found"));
2738       nsCOMPtr<nsIContentSecurityPolicy> csp;
2739       loadInfo->LoadingPrincipal()->GetCsp(getter_AddRefs(csp));
2740       nsAutoCString violationURISpec;
2741       mDocument->GetDocumentURI()->GetAsciiSpec(violationURISpec);
2742       uint32_t lineNo =
2743           aRequest->mElement ? aRequest->mElement->GetScriptLineNumber() : 0;
2744       csp->LogViolationDetails(
2745           nsIContentSecurityPolicy::VIOLATION_TYPE_REQUIRE_SRI_FOR_SCRIPT,
2746           NS_ConvertUTF8toUTF16(violationURISpec), EmptyString(), lineNo,
2747           EmptyString(), EmptyString());
2748       rv = NS_ERROR_SRI_CORRUPT;
2749     }
2750   }
2751 
2752   return rv;
2753 }
2754 
SaveSRIHash(ScriptLoadRequest * aRequest,SRICheckDataVerifier * aSRIDataVerifier) const2755 nsresult ScriptLoader::SaveSRIHash(
2756     ScriptLoadRequest* aRequest, SRICheckDataVerifier* aSRIDataVerifier) const {
2757   MOZ_ASSERT(aRequest->IsSource());
2758   MOZ_ASSERT(aRequest->mScriptBytecode.empty());
2759 
2760   // If the integrity metadata does not correspond to a valid hash function,
2761   // IsComplete would be false.
2762   if (!aRequest->mIntegrity.IsEmpty() && aSRIDataVerifier->IsComplete()) {
2763     // Encode the SRI computed hash.
2764     uint32_t len = aSRIDataVerifier->DataSummaryLength();
2765     if (!aRequest->mScriptBytecode.growBy(len)) {
2766       return NS_ERROR_OUT_OF_MEMORY;
2767     }
2768     aRequest->mBytecodeOffset = len;
2769 
2770     DebugOnly<nsresult> res = aSRIDataVerifier->ExportDataSummary(
2771         aRequest->mScriptBytecode.length(), aRequest->mScriptBytecode.begin());
2772     MOZ_ASSERT(NS_SUCCEEDED(res));
2773   } else {
2774     // Encode a dummy SRI hash.
2775     uint32_t len = SRICheckDataVerifier::EmptyDataSummaryLength();
2776     if (!aRequest->mScriptBytecode.growBy(len)) {
2777       return NS_ERROR_OUT_OF_MEMORY;
2778     }
2779     aRequest->mBytecodeOffset = len;
2780 
2781     DebugOnly<nsresult> res = SRICheckDataVerifier::ExportEmptyDataSummary(
2782         aRequest->mScriptBytecode.length(), aRequest->mScriptBytecode.begin());
2783     MOZ_ASSERT(NS_SUCCEEDED(res));
2784   }
2785 
2786   // Verify that the exported and predicted length correspond.
2787   mozilla::DebugOnly<uint32_t> srilen;
2788   MOZ_ASSERT(NS_SUCCEEDED(SRICheckDataVerifier::DataSummaryLength(
2789       aRequest->mScriptBytecode.length(), aRequest->mScriptBytecode.begin(),
2790       &srilen)));
2791   MOZ_ASSERT(srilen == aRequest->mBytecodeOffset);
2792 
2793   return NS_OK;
2794 }
2795 
ReportErrorToConsole(ScriptLoadRequest * aRequest,nsresult aResult) const2796 void ScriptLoader::ReportErrorToConsole(ScriptLoadRequest* aRequest,
2797                                         nsresult aResult) const {
2798   MOZ_ASSERT(aRequest);
2799 
2800   if (!aRequest->mElement) {
2801     return;
2802   }
2803 
2804   bool isScript = !aRequest->IsModuleRequest();
2805   const char* message;
2806   if (aResult == NS_ERROR_MALFORMED_URI) {
2807     message = isScript ? "ScriptSourceMalformed" : "ModuleSourceMalformed";
2808   } else if (aResult == NS_ERROR_DOM_BAD_URI) {
2809     message = isScript ? "ScriptSourceNotAllowed" : "ModuleSourceNotAllowed";
2810   } else {
2811     message = isScript ? "ScriptSourceLoadFailed" : "ModuleSourceLoadFailed";
2812   }
2813 
2814   NS_ConvertUTF8toUTF16 url(aRequest->mURI->GetSpecOrDefault());
2815   const char16_t* params[] = {url.get()};
2816 
2817   uint32_t lineNo = aRequest->mElement->GetScriptLineNumber();
2818 
2819   nsContentUtils::ReportToConsole(
2820       nsIScriptError::warningFlag, NS_LITERAL_CSTRING("Script Loader"),
2821       mDocument, nsContentUtils::eDOM_PROPERTIES, message, params,
2822       ArrayLength(params), nullptr, EmptyString(), lineNo);
2823 }
2824 
HandleLoadError(ScriptLoadRequest * aRequest,nsresult aResult)2825 void ScriptLoader::HandleLoadError(ScriptLoadRequest* aRequest,
2826                                    nsresult aResult) {
2827   /*
2828    * Handle script not loading error because source was a tracking URL.
2829    * We make a note of this script node by including it in a dedicated
2830    * array of blocked tracking nodes under its parent document.
2831    */
2832   if (aResult == NS_ERROR_TRACKING_URI) {
2833     nsCOMPtr<nsIContent> cont = do_QueryInterface(aRequest->mElement);
2834     mDocument->AddBlockedTrackingNode(cont);
2835   }
2836 
2837   if (aRequest->IsModuleRequest() && !aRequest->mIsInline) {
2838     auto request = aRequest->AsModuleRequest();
2839     SetModuleFetchFinishedAndResumeWaitingRequests(request, aResult);
2840   }
2841 
2842   if (aRequest->mInDeferList) {
2843     MOZ_ASSERT_IF(aRequest->IsModuleRequest(),
2844                   aRequest->AsModuleRequest()->IsTopLevel());
2845     if (aRequest->isInList()) {
2846       RefPtr<ScriptLoadRequest> req = mDeferRequests.Steal(aRequest);
2847       FireScriptAvailable(aResult, req);
2848     }
2849   } else if (aRequest->mInAsyncList) {
2850     MOZ_ASSERT_IF(aRequest->IsModuleRequest(),
2851                   aRequest->AsModuleRequest()->IsTopLevel());
2852     if (aRequest->isInList()) {
2853       RefPtr<ScriptLoadRequest> req = mLoadingAsyncRequests.Steal(aRequest);
2854       FireScriptAvailable(aResult, req);
2855     }
2856   } else if (aRequest->mIsNonAsyncScriptInserted) {
2857     if (aRequest->isInList()) {
2858       RefPtr<ScriptLoadRequest> req =
2859           mNonAsyncExternalScriptInsertedRequests.Steal(aRequest);
2860       FireScriptAvailable(aResult, req);
2861     }
2862   } else if (aRequest->mIsXSLT) {
2863     if (aRequest->isInList()) {
2864       RefPtr<ScriptLoadRequest> req = mXSLTRequests.Steal(aRequest);
2865       FireScriptAvailable(aResult, req);
2866     }
2867   } else if (aRequest->IsModuleRequest() && !aRequest->IsPreload()) {
2868     ModuleLoadRequest* modReq = aRequest->AsModuleRequest();
2869     MOZ_ASSERT(!modReq->IsTopLevel());
2870     MOZ_ASSERT(!modReq->isInList());
2871     modReq->Cancel();
2872     // A single error is fired for the top level module.
2873   } else if (mParserBlockingRequest == aRequest) {
2874     MOZ_ASSERT(!aRequest->isInList());
2875     mParserBlockingRequest = nullptr;
2876     UnblockParser(aRequest);
2877 
2878     // Ensure that we treat aRequest->mElement as our current parser-inserted
2879     // script while firing onerror on it.
2880     MOZ_ASSERT(aRequest->mElement->GetParserCreated());
2881     nsCOMPtr<nsIScriptElement> oldParserInsertedScript =
2882         mCurrentParserInsertedScript;
2883     mCurrentParserInsertedScript = aRequest->mElement;
2884     FireScriptAvailable(aResult, aRequest);
2885     ContinueParserAsync(aRequest);
2886     mCurrentParserInsertedScript = oldParserInsertedScript;
2887   } else if (aRequest->IsPreload()) {
2888     if (aRequest->IsModuleRequest()) {
2889       aRequest->Cancel();
2890     }
2891     if (aRequest->IsTopLevel()) {
2892       MOZ_ALWAYS_TRUE(
2893           mPreloads.RemoveElement(aRequest, PreloadRequestComparator()));
2894     }
2895     MOZ_ASSERT(!aRequest->isInList());
2896     AccumulateCategorical(LABELS_DOM_SCRIPT_PRELOAD_RESULT::LoadError);
2897   } else {
2898     // This happens for blocking requests cancelled by ParsingComplete().
2899     MOZ_ASSERT(aRequest->IsCanceled());
2900     MOZ_ASSERT(!aRequest->isInList());
2901   }
2902 }
2903 
UnblockParser(ScriptLoadRequest * aParserBlockingRequest)2904 void ScriptLoader::UnblockParser(ScriptLoadRequest* aParserBlockingRequest) {
2905   aParserBlockingRequest->mElement->UnblockParser();
2906 }
2907 
ContinueParserAsync(ScriptLoadRequest * aParserBlockingRequest)2908 void ScriptLoader::ContinueParserAsync(
2909     ScriptLoadRequest* aParserBlockingRequest) {
2910   aParserBlockingRequest->mElement->ContinueParserAsync();
2911 }
2912 
NumberOfProcessors()2913 uint32_t ScriptLoader::NumberOfProcessors() {
2914   if (mNumberOfProcessors > 0) return mNumberOfProcessors;
2915 
2916   int32_t numProcs = PR_GetNumberOfProcessors();
2917   if (numProcs > 0) mNumberOfProcessors = numProcs;
2918   return mNumberOfProcessors;
2919 }
2920 
PrepareLoadedRequest(ScriptLoadRequest * aRequest,nsIIncrementalStreamLoader * aLoader,nsresult aStatus)2921 nsresult ScriptLoader::PrepareLoadedRequest(ScriptLoadRequest* aRequest,
2922                                             nsIIncrementalStreamLoader* aLoader,
2923                                             nsresult aStatus) {
2924   if (NS_FAILED(aStatus)) {
2925     return aStatus;
2926   }
2927 
2928   if (aRequest->IsCanceled()) {
2929     return NS_BINDING_ABORTED;
2930   }
2931   MOZ_ASSERT(aRequest->IsLoading());
2932   CollectScriptTelemetry(aLoader, aRequest);
2933 
2934   // If we don't have a document, then we need to abort further
2935   // evaluation.
2936   if (!mDocument) {
2937     return NS_ERROR_NOT_AVAILABLE;
2938   }
2939 
2940   // If the load returned an error page, then we need to abort
2941   nsCOMPtr<nsIRequest> req;
2942   nsresult rv = aLoader->GetRequest(getter_AddRefs(req));
2943   NS_ASSERTION(req, "StreamLoader's request went away prematurely");
2944   NS_ENSURE_SUCCESS(rv, rv);
2945 
2946   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(req);
2947   if (httpChannel) {
2948     bool requestSucceeded;
2949     rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
2950     if (NS_SUCCEEDED(rv) && !requestSucceeded) {
2951       return NS_ERROR_NOT_AVAILABLE;
2952     }
2953 
2954     nsAutoCString sourceMapURL;
2955     if (nsContentUtils::GetSourceMapURL(httpChannel, sourceMapURL)) {
2956       aRequest->mHasSourceMapURL = true;
2957       aRequest->mSourceMapURL = NS_ConvertUTF8toUTF16(sourceMapURL);
2958     }
2959 
2960     if (httpChannel->GetIsTrackingResource()) {
2961       aRequest->SetIsTracking();
2962     }
2963   }
2964 
2965   nsCOMPtr<nsIChannel> channel = do_QueryInterface(req);
2966   // If this load was subject to a CORS check, don't flag it with a separate
2967   // origin principal, so that it will treat our document's principal as the
2968   // origin principal.  Module loads always use CORS.
2969   if (!aRequest->IsModuleRequest() && aRequest->mCORSMode == CORS_NONE) {
2970     rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
2971         channel, getter_AddRefs(aRequest->mOriginPrincipal));
2972     NS_ENSURE_SUCCESS(rv, rv);
2973   }
2974 
2975   // This assertion could fire errorously if we ran out of memory when
2976   // inserting the request in the array. However it's an unlikely case
2977   // so if you see this assertion it is likely something else that is
2978   // wrong, especially if you see it more than once.
2979   NS_ASSERTION(mDeferRequests.Contains(aRequest) ||
2980                    mLoadingAsyncRequests.Contains(aRequest) ||
2981                    mNonAsyncExternalScriptInsertedRequests.Contains(aRequest) ||
2982                    mXSLTRequests.Contains(aRequest) ||
2983                    (aRequest->IsModuleRequest() &&
2984                     !aRequest->AsModuleRequest()->IsTopLevel() &&
2985                     !aRequest->isInList()) ||
2986                    mPreloads.Contains(aRequest, PreloadRequestComparator()) ||
2987                    mParserBlockingRequest,
2988                "aRequest should be pending!");
2989 
2990   if (aRequest->IsModuleRequest()) {
2991     MOZ_ASSERT(aRequest->IsSource());
2992     ModuleLoadRequest* request = aRequest->AsModuleRequest();
2993 
2994     // When loading a module, only responses with a JavaScript MIME type are
2995     // acceptable.
2996     nsAutoCString mimeType;
2997     channel->GetContentType(mimeType);
2998     NS_ConvertUTF8toUTF16 typeString(mimeType);
2999     if (!nsContentUtils::IsJavascriptMIMEType(typeString)) {
3000       return NS_ERROR_FAILURE;
3001     }
3002 
3003     nsCOMPtr<nsIURI> uri;
3004     rv = channel->GetOriginalURI(getter_AddRefs(uri));
3005     NS_ENSURE_SUCCESS(rv, rv);
3006 
3007     // Fixup moz-extension URIs, because the channel URI points to file:,
3008     // which won't be allowed to load.
3009     bool isWebExt = false;
3010     if (uri && NS_SUCCEEDED(uri->SchemeIs("moz-extension", &isWebExt)) &&
3011         isWebExt) {
3012       request->mBaseURL = uri;
3013     } else {
3014       channel->GetURI(getter_AddRefs(request->mBaseURL));
3015     }
3016 
3017     // Attempt to compile off main thread.
3018     rv = AttemptAsyncScriptCompile(request);
3019     if (NS_SUCCEEDED(rv)) {
3020       return rv;
3021     }
3022 
3023     // Otherwise compile it right away and start fetching descendents.
3024     return ProcessFetchedModuleSource(request);
3025   }
3026 
3027   // The script is now loaded and ready to run.
3028   aRequest->SetReady();
3029 
3030   // If this is currently blocking the parser, attempt to compile it
3031   // off-main-thread.
3032   if (aRequest == mParserBlockingRequest && NumberOfProcessors() > 1) {
3033     MOZ_ASSERT(!aRequest->IsModuleRequest());
3034     nsresult rv = AttemptAsyncScriptCompile(aRequest);
3035     if (rv == NS_OK) {
3036       MOZ_ASSERT(aRequest->mProgress == ScriptLoadRequest::Progress::eCompiling,
3037                  "Request should be off-thread compiling now.");
3038       return NS_OK;
3039     }
3040 
3041     // If off-thread compile errored, return the error.
3042     if (rv != NS_ERROR_FAILURE) {
3043       return rv;
3044     }
3045 
3046     // If off-thread compile was rejected, continue with regular processing.
3047   }
3048 
3049   MaybeMoveToLoadedList(aRequest);
3050 
3051   return NS_OK;
3052 }
3053 
ParsingComplete(bool aTerminated)3054 void ScriptLoader::ParsingComplete(bool aTerminated) {
3055   if (mDeferEnabled) {
3056     // Have to check because we apparently get ParsingComplete
3057     // without BeginDeferringScripts in some cases
3058     mDocumentParsingDone = true;
3059   }
3060   mDeferEnabled = false;
3061   if (aTerminated) {
3062     mDeferRequests.Clear();
3063     mLoadingAsyncRequests.Clear();
3064     mLoadedAsyncRequests.Clear();
3065     mNonAsyncExternalScriptInsertedRequests.Clear();
3066     mXSLTRequests.Clear();
3067     if (mParserBlockingRequest) {
3068       mParserBlockingRequest->Cancel();
3069       mParserBlockingRequest = nullptr;
3070     }
3071   }
3072 
3073   // Have to call this even if aTerminated so we'll correctly unblock
3074   // onload and all.
3075   ProcessPendingRequests();
3076 }
3077 
PreloadURI(nsIURI * aURI,const nsAString & aCharset,const nsAString & aType,const nsAString & aCrossOrigin,const nsAString & aIntegrity,bool aScriptFromHead,bool aAsync,bool aDefer,bool aNoModule,const mozilla::net::ReferrerPolicy aReferrerPolicy)3078 void ScriptLoader::PreloadURI(
3079     nsIURI* aURI, const nsAString& aCharset, const nsAString& aType,
3080     const nsAString& aCrossOrigin, const nsAString& aIntegrity,
3081     bool aScriptFromHead, bool aAsync, bool aDefer, bool aNoModule,
3082     const mozilla::net::ReferrerPolicy aReferrerPolicy) {
3083   NS_ENSURE_TRUE_VOID(mDocument);
3084   // Check to see if scripts has been turned off.
3085   if (!mEnabled || !mDocument->IsScriptEnabled()) {
3086     return;
3087   }
3088 
3089   ScriptKind scriptKind = ScriptKind::eClassic;
3090 
3091   if (mDocument->ModuleScriptsEnabled()) {
3092     // Don't load nomodule scripts.
3093     if (aNoModule) {
3094       return;
3095     }
3096 
3097     static const char kASCIIWhitespace[] = "\t\n\f\r ";
3098 
3099     nsAutoString type(aType);
3100     type.Trim(kASCIIWhitespace);
3101     if (type.LowerCaseEqualsASCII("module")) {
3102       scriptKind = ScriptKind::eModule;
3103     }
3104   }
3105 
3106   if (scriptKind == ScriptKind::eClassic && !aType.IsEmpty() &&
3107       !nsContentUtils::IsJavascriptMIMEType(aType)) {
3108     // Unknown type.  Don't load it.
3109     return;
3110   }
3111 
3112   SRIMetadata sriMetadata;
3113   GetSRIMetadata(aIntegrity, &sriMetadata);
3114 
3115   RefPtr<ScriptLoadRequest> request = CreateLoadRequest(
3116       scriptKind, aURI, nullptr, Element::StringToCORSMode(aCrossOrigin),
3117       sriMetadata, aReferrerPolicy);
3118   request->mTriggeringPrincipal = mDocument->NodePrincipal();
3119   request->mIsInline = false;
3120   request->mScriptFromHead = aScriptFromHead;
3121   request->SetScriptMode(aDefer, aAsync);
3122 
3123   if (LOG_ENABLED()) {
3124     nsAutoCString url;
3125     aURI->GetAsciiSpec(url);
3126     LOG(("ScriptLoadRequest (%p): Created preload request for %s",
3127          request.get(), url.get()));
3128   }
3129 
3130   nsresult rv = StartLoad(request);
3131   if (NS_FAILED(rv)) {
3132     return;
3133   }
3134 
3135   PreloadInfo* pi = mPreloads.AppendElement();
3136   pi->mRequest = request;
3137   pi->mCharset = aCharset;
3138 }
3139 
AddDeferRequest(ScriptLoadRequest * aRequest)3140 void ScriptLoader::AddDeferRequest(ScriptLoadRequest* aRequest) {
3141   MOZ_ASSERT(aRequest->IsDeferredScript());
3142   MOZ_ASSERT(!aRequest->mInDeferList && !aRequest->mInAsyncList);
3143 
3144   aRequest->mInDeferList = true;
3145   mDeferRequests.AppendElement(aRequest);
3146   if (mDeferEnabled && aRequest == mDeferRequests.getFirst() && mDocument &&
3147       !mBlockingDOMContentLoaded) {
3148     MOZ_ASSERT(mDocument->GetReadyStateEnum() ==
3149                nsIDocument::READYSTATE_LOADING);
3150     mBlockingDOMContentLoaded = true;
3151     mDocument->BlockDOMContentLoaded();
3152   }
3153 }
3154 
AddAsyncRequest(ScriptLoadRequest * aRequest)3155 void ScriptLoader::AddAsyncRequest(ScriptLoadRequest* aRequest) {
3156   MOZ_ASSERT(aRequest->IsAsyncScript());
3157   MOZ_ASSERT(!aRequest->mInDeferList && !aRequest->mInAsyncList);
3158 
3159   aRequest->mInAsyncList = true;
3160   if (aRequest->IsReadyToRun()) {
3161     mLoadedAsyncRequests.AppendElement(aRequest);
3162   } else {
3163     mLoadingAsyncRequests.AppendElement(aRequest);
3164   }
3165 }
3166 
MaybeMoveToLoadedList(ScriptLoadRequest * aRequest)3167 void ScriptLoader::MaybeMoveToLoadedList(ScriptLoadRequest* aRequest) {
3168   MOZ_ASSERT(aRequest->IsReadyToRun());
3169 
3170   // If it's async, move it to the loaded list.  aRequest->mInAsyncList really
3171   // _should_ be in a list, but the consequences if it's not are bad enough we
3172   // want to avoid trying to move it if it's not.
3173   if (aRequest->mInAsyncList) {
3174     MOZ_ASSERT(aRequest->isInList());
3175     if (aRequest->isInList()) {
3176       RefPtr<ScriptLoadRequest> req = mLoadingAsyncRequests.Steal(aRequest);
3177       mLoadedAsyncRequests.AppendElement(req);
3178     }
3179   }
3180 }
3181 
MaybeRemovedDeferRequests()3182 bool ScriptLoader::MaybeRemovedDeferRequests() {
3183   if (mDeferRequests.isEmpty() && mDocument && mBlockingDOMContentLoaded) {
3184     mBlockingDOMContentLoaded = false;
3185     mDocument->UnblockDOMContentLoaded();
3186     return true;
3187   }
3188   return false;
3189 }
3190 
3191 #undef TRACE_FOR_TEST
3192 #undef TRACE_FOR_TEST_BOOL
3193 #undef TRACE_FOR_TEST_NONE
3194 
3195 #undef LOG
3196 
3197 }  // namespace dom
3198 }  // namespace mozilla
3199