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