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