Coverage Report

Created: 2018-09-25 14:53

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