summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/core/loader/modulescript/module_tree_linker.cc
blob: 67a0ecfa41ab6ed218c63d2798e17fda5b8dc803 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "third_party/blink/renderer/core/loader/modulescript/module_tree_linker.h"

#include "third_party/blink/renderer/bindings/core/v8/script_module.h"
#include "third_party/blink/renderer/core/loader/modulescript/module_script_fetch_request.h"
#include "third_party/blink/renderer/core/loader/modulescript/module_tree_linker_registry.h"
#include "third_party/blink/renderer/core/script/layered_api.h"
#include "third_party/blink/renderer/core/script/module_script.h"
#include "third_party/blink/renderer/platform/bindings/v8_throw_exception.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_loading_log.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/weborigin/security_policy.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
#include "v8/include/v8.h"

namespace blink {

void ModuleTreeLinker::Fetch(
    const KURL& url,
    FetchClientSettingsObjectSnapshot* fetch_client_settings_object,
    WebURLRequest::RequestContext destination,
    const ScriptFetchOptions& options,
    Modulator* modulator,
    ModuleScriptCustomFetchType custom_fetch_type,
    ModuleTreeLinkerRegistry* registry,
    ModuleTreeClient* client) {
  ModuleTreeLinker* fetcher =
      new ModuleTreeLinker(fetch_client_settings_object, destination, modulator,
                           custom_fetch_type, registry, client);
  registry->AddFetcher(fetcher);
  fetcher->FetchRoot(url, options);
  DCHECK(fetcher->IsFetching());
}

void ModuleTreeLinker::FetchDescendantsForInlineScript(
    ModuleScript* module_script,
    FetchClientSettingsObjectSnapshot* fetch_client_settings_object,
    WebURLRequest::RequestContext destination,
    Modulator* modulator,
    ModuleScriptCustomFetchType custom_fetch_type,
    ModuleTreeLinkerRegistry* registry,
    ModuleTreeClient* client) {
  DCHECK(module_script);
  ModuleTreeLinker* fetcher =
      new ModuleTreeLinker(fetch_client_settings_object, destination, modulator,
                           custom_fetch_type, registry, client);
  registry->AddFetcher(fetcher);
  fetcher->FetchRootInline(module_script);
  DCHECK(fetcher->IsFetching());
}

ModuleTreeLinker::ModuleTreeLinker(
    FetchClientSettingsObjectSnapshot* fetch_client_settings_object,
    WebURLRequest::RequestContext destination,
    Modulator* modulator,
    ModuleScriptCustomFetchType custom_fetch_type,
    ModuleTreeLinkerRegistry* registry,
    ModuleTreeClient* client)
    : fetch_client_settings_object_(fetch_client_settings_object),
      destination_(destination),
      modulator_(modulator),
      custom_fetch_type_(custom_fetch_type),
      registry_(registry),
      client_(client) {
  CHECK(modulator);
  CHECK(registry);
  CHECK(client);
}

void ModuleTreeLinker::Trace(blink::Visitor* visitor) {
  visitor->Trace(fetch_client_settings_object_);
  visitor->Trace(modulator_);
  visitor->Trace(registry_);
  visitor->Trace(client_);
  visitor->Trace(result_);
  SingleModuleClient::Trace(visitor);
}

#if DCHECK_IS_ON()
const char* ModuleTreeLinker::StateToString(ModuleTreeLinker::State state) {
  switch (state) {
    case State::kInitial:
      return "Initial";
    case State::kFetchingSelf:
      return "FetchingSelf";
    case State::kFetchingDependencies:
      return "FetchingDependencies";
    case State::kInstantiating:
      return "Instantiating";
    case State::kFinished:
      return "Finished";
  }
  NOTREACHED();
  return "";
}
#endif

void ModuleTreeLinker::AdvanceState(State new_state) {
#if DCHECK_IS_ON()
  RESOURCE_LOADING_DVLOG(1)
      << *this << "::advanceState(" << StateToString(state_) << " -> "
      << StateToString(new_state) << ")";
#endif

  switch (state_) {
    case State::kInitial:
      CHECK_EQ(num_incomplete_fetches_, 0u);
      CHECK_EQ(new_state, State::kFetchingSelf);
      break;
    case State::kFetchingSelf:
      CHECK_EQ(num_incomplete_fetches_, 0u);
      CHECK(new_state == State::kFetchingDependencies ||
            new_state == State::kFinished);
      break;
    case State::kFetchingDependencies:
      CHECK(new_state == State::kInstantiating ||
            new_state == State::kFinished);
      break;
    case State::kInstantiating:
      CHECK_EQ(new_state, State::kFinished);
      break;
    case State::kFinished:
      NOTREACHED();
      break;
  }

  state_ = new_state;

  if (state_ == State::kFinished) {
#if DCHECK_IS_ON()
    if (result_) {
      RESOURCE_LOADING_DVLOG(1)
          << *this << " finished with final result " << *result_;
    } else {
      RESOURCE_LOADING_DVLOG(1) << *this << " finished with nullptr.";
    }
#endif

    registry_->ReleaseFinishedFetcher(this);

    // [IMSGF] Step 6. When the appropriate algorithm asynchronously completes
    // with final result, asynchronously complete this algorithm with final
    // result.
    client_->NotifyModuleTreeLoadFinished(result_);
  }
}

// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-module-script-tree
void ModuleTreeLinker::FetchRoot(const KURL& original_url,
                                 const ScriptFetchOptions& options) {
#if DCHECK_IS_ON()
  original_url_ = original_url;
  root_is_inline_ = false;
#endif

  AdvanceState(State::kFetchingSelf);

  KURL url = original_url;
  // <spec
  // href="https://github.com/drufball/layered-apis/blob/master/spec.md#fetch-a-module-script-graph"
  // step="1">Set url to the layered API fetching URL given url and the current
  // settings object's API base URL.</spec>
  if (RuntimeEnabledFeatures::LayeredAPIEnabled()) {
    url = blink::layered_api::ResolveFetchingURL(
        url, fetch_client_settings_object_->BaseURL());
  }

#if DCHECK_IS_ON()
  url_ = url;
#endif

  // <spec
  // href="https://github.com/drufball/layered-apis/blob/master/spec.md#fetch-a-module-script-graph"
  // step="2">If url is failure, asynchronously complete this algorithm with
  // null.</spec>
  if (!url.IsValid()) {
    result_ = nullptr;
    modulator_->TaskRunner()->PostTask(
        FROM_HERE, WTF::Bind(&ModuleTreeLinker::AdvanceState,
                             WrapPersistent(this), State::kFinished));
    return;
  }

  // Step 1. Let visited set be << url >>.
  visited_set_.insert(url);

  // Step 2. Perform the internal module script graph fetching procedure given
  // ... with the top-level module fetch flag set. ...
  ModuleScriptFetchRequest request(
      url, destination_, options,
      SecurityPolicy::GenerateReferrer(
          options.GetReferrerPolicy(), url,
          fetch_client_settings_object_->GetOutgoingReferrer()),
      TextPosition::MinimumPosition());

  InitiateInternalModuleScriptGraphFetching(
      request, ModuleGraphLevel::kTopLevelModuleFetch);
}

void ModuleTreeLinker::FetchRootInline(ModuleScript* module_script) {
  // Top-level entry point for [FDaI] for an inline module script.
  DCHECK(module_script);
#if DCHECK_IS_ON()
  original_url_ = module_script->BaseURL();
  url_ = original_url_;
  root_is_inline_ = true;
#endif

  AdvanceState(State::kFetchingSelf);

  // Store the |module_script| here which will be used as result of the
  // algorithm when success. Also, this ensures that the |module_script| is
  // traced via ModuleTreeLinker.
  result_ = module_script;
  AdvanceState(State::kFetchingDependencies);

  modulator_->TaskRunner()->PostTask(
      FROM_HERE,
      WTF::Bind(&ModuleTreeLinker::FetchDescendants, WrapPersistent(this),
                WrapPersistent(module_script)));
}

void ModuleTreeLinker::InitiateInternalModuleScriptGraphFetching(
    const ModuleScriptFetchRequest& request,
    ModuleGraphLevel level) {
  // [IMSGF] Step 1. Assert: visited set contains url.
  DCHECK(visited_set_.Contains(request.Url()));

  ++num_incomplete_fetches_;

  // [IMSGF] Step 2. Fetch a single module script given ...
  modulator_->FetchSingle(request, fetch_client_settings_object_.Get(), level,
                          custom_fetch_type_, this);

  // [IMSGF] Step 3-- are executed when NotifyModuleLoadFinished() is called.
}

void ModuleTreeLinker::NotifyModuleLoadFinished(ModuleScript* module_script) {
  // [IMSGF] Step 3. Return from this algorithm, and run the following steps
  // when fetching a single module script asynchronously completes with result:

  CHECK_GT(num_incomplete_fetches_, 0u);
  --num_incomplete_fetches_;

#if DCHECK_IS_ON()
  if (module_script) {
    RESOURCE_LOADING_DVLOG(1)
        << *this << "::NotifyModuleLoadFinished() with " << *module_script;
  } else {
    RESOURCE_LOADING_DVLOG(1)
        << *this << "::NotifyModuleLoadFinished() with nullptr.";
  }
#endif

  if (state_ == State::kFetchingSelf) {
    // Corresponds to top-level calls to
    // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-the-descendants-of-and-instantiate-a-module-script
    // i.e. [IMSGF] with the top-level module fetch flag set (external), or
    // Step 22 of "prepare a script" (inline).
    // |module_script| is the top-level module, and will be instantiated
    // and returned later.
    result_ = module_script;
    AdvanceState(State::kFetchingDependencies);
  }

  if (state_ != State::kFetchingDependencies) {
    // We may reach here if one of the descendant failed to load, and the other
    // descendants fetches were in flight.
    return;
  }

  // Note: top-level module fetch flag is implemented so that Instantiate()
  // is called once after all descendants are fetched, which corresponds to
  // the single invocation of "fetch the descendants of and instantiate".

  // [IMSGF] Step 4. If result is null, asynchronously complete this algorithm
  // with null, and abort these steps.
  if (!module_script) {
    result_ = nullptr;
    AdvanceState(State::kFinished);
    return;
  }

  // [IMSGF] Step 5. If the top-level module fetch flag is set, fetch the
  // descendants of and instantiate result given destination and visited set.
  // Otherwise, fetch the descendants of result given the same arguments.
  FetchDescendants(module_script);
}

void ModuleTreeLinker::FetchDescendants(ModuleScript* module_script) {
  DCHECK(module_script);

  // [nospec] Abort the steps if the browsing context is discarded.
  if (!modulator_->HasValidContext()) {
    result_ = nullptr;
    AdvanceState(State::kFinished);
    return;
  }

  // [FD] Step 2. Let record be module script's record.
  ScriptModule record = module_script->Record();

  // [FD] Step 1. If module script's record is null, then asynchronously
  // complete this algorithm with module script and abort these steps.
  if (record.IsNull()) {
    found_parse_error_ = true;
    // We don't early-exit here and wait until all module scripts to be
    // loaded, because we might be not sure which error to be reported.
    //
    // It is possible to determine whether the error to be reported can be
    // determined without waiting for loading module scripts, and thus to
    // early-exit here if possible. However, the complexity of such early-exit
    // implementation might be high, and optimizing error cases with the
    // implementation cost might be not worth doing.
    FinalizeFetchDescendantsForOneModuleScript();
    return;
  }

  // [FD] Step 3. If record.[[RequestedModules]] is empty, asynchronously
  // complete this algorithm with module script.
  //
  // Note: We defer this bail-out until the end of the procedure. The rest of
  // the procedure will be no-op anyway if record.[[RequestedModules]] is empty.

  // [FD] Step 4. Let urls be a new empty list.
  Vector<KURL> urls;
  Vector<TextPosition> positions;

  // [FD] Step 5. For each string requested of record.[[RequestedModules]],
  Vector<Modulator::ModuleRequest> module_requests =
      modulator_->ModuleRequestsFromScriptModule(record);
  for (const auto& module_request : module_requests) {
    // [FD] Step 5.1. Let url be the result of resolving a module specifier
    // given module script and requested.
    KURL url = module_script->ResolveModuleSpecifier(module_request.specifier);

    // [FD] Step 5.2. Assert: url is never failure, because resolving a module
    // specifier must have been previously successful with these same two
    // arguments.
    CHECK(url.IsValid()) << "ModuleScript::ResolveModuleSpecifier() impl must "
                            "return either a valid url or null.";

    // [FD] Step 5.3. If visited set does not contain url, then:
    if (!visited_set_.Contains(url)) {
      // [FD] Step 5.3.1. Append url to urls.
      urls.push_back(url);

      // [FD] Step 5.3.2. Append url to visited set.
      visited_set_.insert(url);

      positions.push_back(module_request.position);
    }
  }

  if (urls.IsEmpty()) {
    // [FD] Step 3. If record.[[RequestedModules]] is empty, asynchronously
    // complete this algorithm with module script.
    //
    // Also, if record.[[RequestedModules]] is not empty but |urls| is
    // empty here, we complete this algorithm.
    FinalizeFetchDescendantsForOneModuleScript();
    return;
  }

  // [FD] Step 6. Let options be the descendant script fetch options for module
  // script's fetch options.
  // https://html.spec.whatwg.org/multipage/webappapis.html#descendant-script-fetch-options
  // the descendant script fetch options are a new script fetch options whose
  // items all have the same values, except for the integrity metadata, which is
  // instead the empty string.
  ScriptFetchOptions options(module_script->FetchOptions().Nonce(),
                             IntegrityMetadataSet(), String(),
                             module_script->FetchOptions().ParserState(),
                             module_script->FetchOptions().CredentialsMode(),
                             module_script->FetchOptions().GetReferrerPolicy());

  // [FD] Step 7. For each url in urls, ...
  //
  // [FD] Step 7. These invocations of the internal module script graph fetching
  // procedure should be performed in parallel to each other.
  for (size_t i = 0; i < urls.size(); ++i) {
    // [FD] Step 7. ... perform the internal module script graph fetching
    // procedure given url, fetch client settings object, destination, options,
    // module script's settings object, visited set, module script's base URL,
    // and with the top-level module fetch flag unset. ...
    ModuleScriptFetchRequest request(
        urls[i], destination_, options,
        SecurityPolicy::GenerateReferrer(options.GetReferrerPolicy(), urls[i],
                                         module_script->BaseURL().GetString()),
        positions[i]);
    InitiateInternalModuleScriptGraphFetching(
        request, ModuleGraphLevel::kDependentModuleFetch);
  }

  // Asynchronously continue processing after NotifyModuleLoadFinished() is
  // called num_incomplete_fetches_ times.
  CHECK_GT(num_incomplete_fetches_, 0u);
}

void ModuleTreeLinker::FinalizeFetchDescendantsForOneModuleScript() {
  // [FD] of a single module script is completed here:
  //
  // [FD] Step 7. Otherwise, wait until all of the internal module script graph
  // fetching procedure invocations have asynchronously completed. ...

  // And, if |num_incomplete_fetches_| is 0, all the invocations of [FD]
  // (called from [FDaI] Step 2) of the root module script is completed here
  // and thus we proceed to [FDaI] Step 4 implemented by Instantiate().
  if (num_incomplete_fetches_ == 0)
    Instantiate();
}

void ModuleTreeLinker::Instantiate() {
  // [nospec] Abort the steps if the browsing context is discarded.
  if (!modulator_->HasValidContext()) {
    result_ = nullptr;
    AdvanceState(State::kFinished);
    return;
  }

  // [FDaI] Step 4. If result is null, then asynchronously complete this
  // algorithm with result.
  if (!result_) {
    AdvanceState(State::kFinished);
    return;
  }

  // [FDaI] Step 6. If parse error is null, then:
  //
  // [Optimization] If |found_parse_error_| is false (i.e. no parse errors
  // were found during fetching), we are sure that |parse error| is null and
  // thus skip FindFirstParseError() call.
  if (!found_parse_error_) {
#if DCHECK_IS_ON()
    HeapHashSet<Member<ModuleScript>> discovered_set;
    DCHECK(FindFirstParseError(result_, &discovered_set).IsEmpty());
#endif

    // [FDaI] Step 6.1. Let record be result's record.
    ScriptModule record = result_->Record();

    // [FDaI] Step 6.2. Perform record.Instantiate().
    AdvanceState(State::kInstantiating);
    ScriptValue instantiation_error = modulator_->InstantiateModule(record);

    // [FDaI] Step 6.2. If this throws an exception, set result's error to
    // rethrow to that exception.
    if (!instantiation_error.IsEmpty())
      result_->SetErrorToRethrow(instantiation_error);
  } else {
    // [FDaI] Step 7. Otherwise ...

    // [FFPE] Step 2. If discoveredSet was not given, let it be an empty set.
    HeapHashSet<Member<ModuleScript>> discovered_set;

    // [FDaI] Step 5. Let parse error be the result of finding the first parse
    // error given result.
    ScriptValue parse_error = FindFirstParseError(result_, &discovered_set);
    DCHECK(!parse_error.IsEmpty());

    // [FDaI] Step 7. ... set result's error to rethrow to parse error.
    result_->SetErrorToRethrow(parse_error);
  }

  // [FDaI] Step 8. Asynchronously complete this algorithm with result.
  AdvanceState(State::kFinished);
}

// [FFPE] https://html.spec.whatwg.org/#finding-the-first-parse-error
//
// This returns non-empty ScriptValue iff a parse error is found.
ScriptValue ModuleTreeLinker::FindFirstParseError(
    ModuleScript* module_script,
    HeapHashSet<Member<ModuleScript>>* discovered_set) const {
  // FindFirstParseError() is called only when there is no fetch errors, i.e.
  // all module scripts in the graph are non-null.
  DCHECK(module_script);

  // [FFPE] Step 1. Let moduleMap be moduleScript's settings object's module
  // map.
  //
  // This is accessed via |modulator_|.

  // [FFPE] Step 2 is done before calling this in Instantiate().

  // [FFPE] Step 3. Append moduleScript to discoveredSet.
  discovered_set->insert(module_script);

  // [FFPE] Step 4. If moduleScript's record is null, then return moduleScript's
  // parse error.
  ScriptModule record = module_script->Record();
  if (record.IsNull())
    return module_script->CreateParseError();

  // [FFPE] Step 5. Let childSpecifiers be the value of moduleScript's record's
  // [[RequestedModules]] internal slot.
  Vector<Modulator::ModuleRequest> child_specifiers =
      modulator_->ModuleRequestsFromScriptModule(record);

  for (const auto& module_request : child_specifiers) {
    // [FFPE] Step 6. Let childURLs be the list obtained by calling resolve a
    // module specifier once for each item of childSpecifiers, given
    // moduleScript and that item.
    KURL child_url =
        module_script->ResolveModuleSpecifier(module_request.specifier);

    // [FFPE] Step 6. ...  (None of these will ever fail, as otherwise
    // moduleScript would have been marked as itself having a parse error.)
    CHECK(child_url.IsValid())
        << "ModuleScript::ResolveModuleSpecifier() impl must "
           "return either a valid url or null.";

    // [FFPE] Step 7. Let childModules be the list obtained by getting each
    // value in moduleMap whose key is given by an item of childURLs.
    //
    // [FFPE] Step 8. For each childModule of childModules:
    ModuleScript* child_module = modulator_->GetFetchedModuleScript(child_url);

    // [FFPE] Step 8.1. Assert: childModule is a module script (i.e., it is not
    // "fetching" or null)
    CHECK(child_module);

    // [FFPE] Step 8.2. If discoveredSet already contains childModule, continue.
    if (discovered_set->Contains(child_module))
      continue;

    // [FFPE] Step 8.3. Let childParseError be the result of finding the first
    // parse error given childModule and discoveredSet.
    ScriptValue child_parse_error =
        FindFirstParseError(child_module, discovered_set);

    // [FFPE] Step 8.4. If childParseError is not null, return childParseError.
    if (!child_parse_error.IsEmpty())
      return child_parse_error;
  }

  // [FFPE] Step 9. Return null.
  return ScriptValue();
}

#if DCHECK_IS_ON()
std::ostream& operator<<(std::ostream& stream, const ModuleTreeLinker& linker) {
  stream << "ModuleTreeLinker[" << &linker
         << ", original_url=" << linker.original_url_.GetString()
         << ", url=" << linker.url_.GetString()
         << ", inline=" << linker.root_is_inline_ << "]";
  return stream;
}
#endif

}  // namespace blink