1 /*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * (C) 2001 Dirk Mueller (mueller@kde.org)
5 * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights
6 * reserved.
7 * Copyright (C) 2008 Nikolas Zimmermann <zimmermann@kde.org>
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Library General Public License for more details.
18 *
19 * You should have received a copy of the GNU Library General Public License
20 * along with this library; see the file COPYING.LIB. If not, write to
21 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
23 */
24
25 #include "third_party/blink/renderer/core/script/script_loader.h"
26
27 #include "base/feature_list.h"
28 #include "third_party/blink/public/common/feature_policy/feature_policy.h"
29 #include "third_party/blink/public/common/features.h"
30 #include "third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom-blink.h"
31 #include "third_party/blink/renderer/bindings/core/v8/sanitize_script_errors.h"
32 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
33 #include "third_party/blink/renderer/core/dom/document.h"
34 #include "third_party/blink/renderer/core/dom/events/event.h"
35 #include "third_party/blink/renderer/core/dom/scriptable_document_parser.h"
36 #include "third_party/blink/renderer/core/dom/text.h"
37 #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
38 #include "third_party/blink/renderer/core/frame/local_frame.h"
39 #include "third_party/blink/renderer/core/html/html_document.h"
40 #include "third_party/blink/renderer/core/html/imports/html_import.h"
41 #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
42 #include "third_party/blink/renderer/core/html_names.h"
43 #include "third_party/blink/renderer/core/inspector/console_message.h"
44 #include "third_party/blink/renderer/core/loader/importance_attribute.h"
45 #include "third_party/blink/renderer/core/loader/modulescript/module_script_fetch_request.h"
46 #include "third_party/blink/renderer/core/loader/subresource_integrity_helper.h"
47 #include "third_party/blink/renderer/core/script/classic_pending_script.h"
48 #include "third_party/blink/renderer/core/script/classic_script.h"
49 #include "third_party/blink/renderer/core/script/import_map.h"
50 #include "third_party/blink/renderer/core/script/js_module_script.h"
51 #include "third_party/blink/renderer/core/script/modulator.h"
52 #include "third_party/blink/renderer/core/script/module_pending_script.h"
53 #include "third_party/blink/renderer/core/script/pending_import_map.h"
54 #include "third_party/blink/renderer/core/script/script.h"
55 #include "third_party/blink/renderer/core/script/script_element_base.h"
56 #include "third_party/blink/renderer/core/script/script_runner.h"
57 #include "third_party/blink/renderer/core/svg_names.h"
58 #include "third_party/blink/renderer/core/trustedtypes/trusted_types_util.h"
59 #include "third_party/blink/renderer/platform/bindings/parkable_string.h"
60 #include "third_party/blink/renderer/platform/heap/heap.h"
61 #include "third_party/blink/renderer/platform/instrumentation/histogram.h"
62 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
63 #include "third_party/blink/renderer/platform/loader/fetch/fetch_client_settings_object_snapshot.h"
64 #include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h"
65 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
66 #include "third_party/blink/renderer/platform/loader/subresource_integrity.h"
67 #include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h"
68 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
69 #include "third_party/blink/renderer/platform/weborigin/security_policy.h"
70 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
71 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
72 #include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
73
74 namespace blink {
75
ScriptLoader(ScriptElementBase * element,bool parser_inserted,bool already_started)76 ScriptLoader::ScriptLoader(ScriptElementBase* element,
77 bool parser_inserted,
78 bool already_started)
79 : element_(element),
80 will_be_parser_executed_(false),
81 will_execute_when_document_finished_parsing_(false),
82 force_deferred_(false) {
83 // <spec href="https://html.spec.whatwg.org/C/#already-started">... The
84 // cloning steps for script elements must set the "already started" flag on
85 // the copy if it is set on the element being cloned.</spec>
86 //
87 // TODO(hiroshige): Cloning is implemented together with
88 // {HTML,SVG}ScriptElement::cloneElementWithoutAttributesAndChildren().
89 // Clean up these later.
90 if (already_started)
91 already_started_ = true;
92
93 if (parser_inserted) {
94 // <spec href="https://html.spec.whatwg.org/C/#parser-inserted">... It is
95 // set by the HTML parser and the XML parser on script elements they insert
96 // ...</spec>
97 parser_inserted_ = true;
98
99 // <spec href="https://html.spec.whatwg.org/C/#non-blocking">... It is unset
100 // by the HTML parser and the XML parser on script elements they insert.
101 // ...</spec>
102 non_blocking_ = false;
103 }
104 }
105
~ScriptLoader()106 ScriptLoader::~ScriptLoader() {}
107
Trace(Visitor * visitor)108 void ScriptLoader::Trace(Visitor* visitor) {
109 visitor->Trace(element_);
110 visitor->Trace(pending_script_);
111 visitor->Trace(prepared_pending_script_);
112 visitor->Trace(resource_keep_alive_);
113 PendingScriptClient::Trace(visitor);
114 }
115
DidNotifySubtreeInsertionsToDocument()116 void ScriptLoader::DidNotifySubtreeInsertionsToDocument() {
117 if (!parser_inserted_)
118 PrepareScript(); // FIXME: Provide a real starting line number here.
119 }
120
ChildrenChanged()121 void ScriptLoader::ChildrenChanged() {
122 if (!parser_inserted_ && element_->IsConnected())
123 PrepareScript(); // FIXME: Provide a real starting line number here.
124 }
125
HandleSourceAttribute(const String & source_url)126 void ScriptLoader::HandleSourceAttribute(const String& source_url) {
127 if (IgnoresLoadRequest() || source_url.IsEmpty())
128 return;
129
130 PrepareScript(); // FIXME: Provide a real starting line number here.
131 }
132
133 // <specdef href="https://html.spec.whatwg.org/C/#non-blocking">
HandleAsyncAttribute()134 void ScriptLoader::HandleAsyncAttribute() {
135 // <spec>... In addition, whenever a script element whose "non-blocking" flag
136 // is set has an async content attribute added, the element's "non-blocking"
137 // flag must be unset.</spec>
138 non_blocking_ = false;
139 }
140
DetachPendingScript()141 void ScriptLoader::DetachPendingScript() {
142 if (!pending_script_)
143 return;
144 pending_script_->Dispose();
145 pending_script_ = nullptr;
146 }
147
148 namespace {
149
IsValidClassicScriptTypeAndLanguage(const String & type,const String & language,ScriptLoader::LegacyTypeSupport support_legacy_types)150 bool IsValidClassicScriptTypeAndLanguage(
151 const String& type,
152 const String& language,
153 ScriptLoader::LegacyTypeSupport support_legacy_types) {
154 // FIXME: IsLegacySupportedJavaScriptLanguage() is not valid HTML5. It is used
155 // here to maintain backwards compatibility with existing web tests. The
156 // specific violations are:
157 // - Allowing type=javascript. type= should only support MIME types, such as
158 // text/javascript.
159 // - Allowing a different set of languages for language= and type=. language=
160 // supports Javascript 1.1 and 1.4-1.6, but type= does not.
161 if (type.IsEmpty()) {
162 return language.IsEmpty() || // assume text/javascript.
163 MIMETypeRegistry::IsSupportedJavaScriptMIMEType("text/" +
164 language) ||
165 MIMETypeRegistry::IsLegacySupportedJavaScriptLanguage(language);
166 } else if (MIMETypeRegistry::IsSupportedJavaScriptMIMEType(
167 type.StripWhiteSpace()) ||
168 (support_legacy_types ==
169 ScriptLoader::kAllowLegacyTypeInTypeAttribute &&
170 MIMETypeRegistry::IsLegacySupportedJavaScriptLanguage(type))) {
171 return true;
172 }
173
174 return false;
175 }
176
177 enum class ShouldFireErrorEvent {
178 kDoNotFire,
179 kShouldFire,
180 };
181
182 } // namespace
183
184 // <specdef href="https://html.spec.whatwg.org/C/#prepare-a-script">
IsValidScriptTypeAndLanguage(const String & type,const String & language,LegacyTypeSupport support_legacy_types,mojom::ScriptType * out_script_type,bool * out_is_import_map)185 bool ScriptLoader::IsValidScriptTypeAndLanguage(
186 const String& type,
187 const String& language,
188 LegacyTypeSupport support_legacy_types,
189 mojom::ScriptType* out_script_type,
190 bool* out_is_import_map) {
191 if (IsValidClassicScriptTypeAndLanguage(type, language,
192 support_legacy_types)) {
193 // <spec step="7">... If the script block's type string is a JavaScript MIME
194 // type essence match, the script's type is "classic". ...</spec>
195 //
196 // TODO(hiroshige): Annotate and/or cleanup this step.
197 if (out_script_type)
198 *out_script_type = mojom::ScriptType::kClassic;
199 if (out_is_import_map)
200 *out_is_import_map = false;
201 return true;
202 }
203
204 if (type == "module") {
205 // <spec step="7">... If the script block's type string is an ASCII
206 // case-insensitive match for the string "module", the script's type is
207 // "module". ...</spec>
208 if (out_script_type)
209 *out_script_type = mojom::ScriptType::kModule;
210 if (out_is_import_map)
211 *out_is_import_map = false;
212 return true;
213 }
214
215 if (type == "importmap") {
216 if (out_is_import_map)
217 *out_is_import_map = true;
218 return true;
219 }
220
221 // <spec step="7">... If neither of the above conditions are true, then
222 // return. No script is executed.</spec>
223 return false;
224 }
225
BlockForNoModule(mojom::ScriptType script_type,bool nomodule)226 bool ScriptLoader::BlockForNoModule(mojom::ScriptType script_type,
227 bool nomodule) {
228 return nomodule && script_type == mojom::ScriptType::kClassic;
229 }
230
231 // Corresponds to
232 // https://html.spec.whatwg.org/C/#module-script-credentials-mode
233 // which is a translation of the CORS settings attribute in the context of
234 // module scripts. This is used in:
235 // - Step 17 of
236 // https://html.spec.whatwg.org/C/#prepare-a-script
237 // - Step 6 of obtaining a preloaded module script
238 // https://html.spec.whatwg.org/C/#link-type-modulepreload.
ModuleScriptCredentialsMode(CrossOriginAttributeValue cross_origin)239 network::mojom::CredentialsMode ScriptLoader::ModuleScriptCredentialsMode(
240 CrossOriginAttributeValue cross_origin) {
241 switch (cross_origin) {
242 case kCrossOriginAttributeNotSet:
243 case kCrossOriginAttributeAnonymous:
244 return network::mojom::CredentialsMode::kSameOrigin;
245 case kCrossOriginAttributeUseCredentials:
246 return network::mojom::CredentialsMode::kInclude;
247 }
248 NOTREACHED();
249 return network::mojom::CredentialsMode::kOmit;
250 }
251
252 // https://github.com/WICG/feature-policy/issues/135
ShouldBlockSyncScriptForFeaturePolicy(const ScriptElementBase * element,mojom::ScriptType script_type,bool parser_inserted)253 bool ShouldBlockSyncScriptForFeaturePolicy(const ScriptElementBase* element,
254 mojom::ScriptType script_type,
255 bool parser_inserted) {
256 if (element->GetDocument().IsFeatureEnabled(
257 mojom::blink::FeaturePolicyFeature::kSyncScript)) {
258 return false;
259 }
260
261 // Module scripts never block parsing.
262 if (script_type == mojom::ScriptType::kModule || !parser_inserted)
263 return false;
264
265 if (!element->HasSourceAttribute())
266 return true;
267 return !element->DeferAttributeValue() && !element->AsyncAttributeValue();
268 }
269
270 // <specdef href="https://html.spec.whatwg.org/C/#prepare-a-script">
PrepareScript(const TextPosition & script_start_position,LegacyTypeSupport support_legacy_types)271 bool ScriptLoader::PrepareScript(const TextPosition& script_start_position,
272 LegacyTypeSupport support_legacy_types) {
273 // <spec step="1">If the script element is marked as having "already started",
274 // then return. The script is not executed.</spec>
275 if (already_started_)
276 return false;
277
278 // <spec step="2">If the element has its "parser-inserted" flag set, then set
279 // was-parser-inserted to true and unset the element's "parser-inserted" flag.
280 // Otherwise, set was-parser-inserted to false.</spec>
281 bool was_parser_inserted;
282 if (parser_inserted_) {
283 was_parser_inserted = true;
284 parser_inserted_ = false;
285 } else {
286 was_parser_inserted = false;
287 }
288
289 // <spec step="3">If was-parser-inserted is true and the element does not have
290 // an async attribute, then set the element's "non-blocking" flag to
291 // true.</spec>
292 if (was_parser_inserted && !element_->AsyncAttributeValue())
293 non_blocking_ = true;
294
295 // <spec step="4">Let source text be the element's child text content.</spec>
296 //
297 // Trusted Types additionally requires:
298 // https://w3c.github.io/webappsec-trusted-types/dist/spec/#slot-value-verification
299 // - Step 4: Execute the Prepare the script URL and text algorithm upon the
300 // script element. If that algorithm threw an error, then return. The
301 // script is not executed.
302 // - Step 5: Let source text be the element’s [[ScriptText]] internal slot
303 // value.
304 const String source_text = GetScriptText();
305
306 // <spec step="5">If the element has no src attribute, and source text is the
307 // empty string, then return. The script is not executed.</spec>
308 if (!element_->HasSourceAttribute() && source_text.IsEmpty())
309 return false;
310
311 // <spec step="6">If the element is not connected, then return. The script is
312 // not executed.</spec>
313 if (!element_->IsConnected())
314 return false;
315
316 bool is_import_map = false;
317
318 // <spec step="7">... Determine the script's type as follows: ...</spec>
319 //
320 // |script_type_| is set here.
321 if (!IsValidScriptTypeAndLanguage(
322 element_->TypeAttributeValue(), element_->LanguageAttributeValue(),
323 support_legacy_types, &script_type_, &is_import_map)) {
324 return false;
325 }
326
327 // <spec step="8">If was-parser-inserted is true, then flag the element as
328 // "parser-inserted" again, and set the element's "non-blocking" flag to
329 // false.</spec>
330 if (was_parser_inserted) {
331 parser_inserted_ = true;
332 non_blocking_ = false;
333 }
334
335 // <spec step="9">Set the element's "already started" flag.</spec>
336 already_started_ = true;
337
338 // <spec step="10">If the element is flagged as "parser-inserted", but the
339 // element's node document is not the Document of the parser that created the
340 // element, then return.</spec>
341 //
342 // FIXME: If script is parser inserted, verify it's still in the original
343 // document.
344
345 // <spec step="11">If scripting is disabled for the script element, then
346 // return. The script is not executed.</spec>
347 //
348 // <spec href="https://html.spec.whatwg.org/C/#concept-n-noscript">Scripting
349 // is disabled for a node if there is no such browsing context, or if
350 // scripting is disabled in that browsing context.</spec>
351 Document& element_document = element_->GetDocument();
352 // TODO(timothygu): Investigate if we could switch from ExecutingFrame() to
353 // ExecutingWindow().
354 if (!element_document.ExecutingFrame())
355 return false;
356
357 Document* context_document = element_document.ContextDocument();
358 if (!context_document || !context_document->ExecutingFrame())
359 return false;
360 if (!context_document->CanExecuteScripts(kAboutToExecuteScript))
361 return false;
362
363 // Accept import maps only if ImportMapsEnabled().
364 if (is_import_map) {
365 Modulator* modulator = Modulator::From(
366 ToScriptStateForMainWorld(context_document->GetFrame()));
367 if (!modulator->ImportMapsEnabled()) {
368 // Import maps should have been rejected in spec Step 7 above.
369 // TODO(hiroshige): Returning here (i.e. after spec Step 11) is not spec
370 // conformant. Fix this.
371 return false;
372 }
373 }
374
375 // <spec step="12">If the script element has a nomodule content attribute and
376 // the script's type is "classic", then return. The script is not
377 // executed.</spec>
378 if (BlockForNoModule(script_type_, element_->NomoduleAttributeValue()))
379 return false;
380
381 // TODO(csharrison): This logic only works if the tokenizer/parser was not
382 // blocked waiting for scripts when the element was inserted. This usually
383 // fails for instance, on second document.write if a script writes twice
384 // in a row. To fix this, the parser might have to keep track of raw
385 // string position.
386 //
387 // Also PendingScript's contructor has the same code.
388 const bool is_in_document_write = element_document.IsInDocumentWrite();
389
390 // Reset line numbering for nested writes.
391 TextPosition position =
392 is_in_document_write ? TextPosition() : script_start_position;
393
394 // <spec step="13">If the script element does not have a src content
395 // attribute, and the Should element's inline behavior be blocked by Content
396 // Security Policy? algorithm returns "Blocked" when executed upon the script
397 // element, "script", and source text, then return. The script is not
398 // executed. [CSP]</spec>
399 if (!element_->HasSourceAttribute() &&
400 !element_->AllowInlineScriptForCSP(element_->GetNonceForElement(),
401 position.line_, source_text)) {
402 return false;
403 }
404
405 // 14.
406 if (!IsScriptForEventSupported())
407 return false;
408
409 // This FeaturePolicy is still in the process of being added to the spec.
410 if (ShouldBlockSyncScriptForFeaturePolicy(element_.Get(), GetScriptType(),
411 parser_inserted_)) {
412 element_document.AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
413 mojom::ConsoleMessageSource::kJavaScript,
414 mojom::ConsoleMessageLevel::kError,
415 "Synchronous script execution is disabled by Feature Policy"));
416 return false;
417 }
418
419 // 14. is handled below.
420
421 // <spec step="16">Let classic script CORS setting be the current state of the
422 // element's crossorigin content attribute.</spec>
423 CrossOriginAttributeValue cross_origin =
424 GetCrossOriginAttributeValue(element_->CrossOriginAttributeValue());
425
426 // <spec step="17">Let module script credentials mode be the module script
427 // credentials mode for the element's crossorigin content attribute.</spec>
428 network::mojom::CredentialsMode credentials_mode =
429 ModuleScriptCredentialsMode(cross_origin);
430
431 // <spec step="18">Let cryptographic nonce be the element's
432 // [[CryptographicNonce]] internal slot's value.</spec>
433 String nonce = element_->GetNonceForElement();
434
435 // <spec step="19">If the script element has an integrity attribute, then let
436 // integrity metadata be that attribute's value. Otherwise, let integrity
437 // metadata be the empty string.</spec>
438 String integrity_attr = element_->IntegrityAttributeValue();
439 IntegrityMetadataSet integrity_metadata;
440 if (!integrity_attr.IsEmpty()) {
441 SubresourceIntegrity::IntegrityFeatures integrity_features =
442 SubresourceIntegrityHelper::GetFeatures(
443 element_document.ToExecutionContext());
444 SubresourceIntegrity::ReportInfo report_info;
445 SubresourceIntegrity::ParseIntegrityAttribute(
446 integrity_attr, integrity_features, integrity_metadata, &report_info);
447 SubresourceIntegrityHelper::DoReport(*element_document.ToExecutionContext(),
448 report_info);
449 }
450
451 // <spec step="20">Let referrer policy be the current state of the element's
452 // referrerpolicy content attribute.</spec>
453 String referrerpolicy_attr = element_->ReferrerPolicyAttributeValue();
454 network::mojom::ReferrerPolicy referrer_policy =
455 network::mojom::ReferrerPolicy::kDefault;
456 if (!referrerpolicy_attr.IsEmpty()) {
457 SecurityPolicy::ReferrerPolicyFromString(
458 referrerpolicy_attr, kDoNotSupportReferrerPolicyLegacyKeywords,
459 &referrer_policy);
460 }
461
462 // Priority Hints is currently a non-standard feature, but we can assume the
463 // following (see https://crbug.com/821464):
464 // <spec step="21">Let importance be the current state of the element's
465 // importance content attribute.</spec>
466 String importance_attr = element_->ImportanceAttributeValue();
467 mojom::FetchImportanceMode importance =
468 GetFetchImportanceAttributeValue(importance_attr);
469
470 // <spec step="21">Let parser metadata be "parser-inserted" if the script
471 // element has been flagged as "parser-inserted", and "not-parser-inserted"
472 // otherwise.</spec>
473 ParserDisposition parser_state =
474 IsParserInserted() ? kParserInserted : kNotParserInserted;
475
476 if (GetScriptType() == mojom::ScriptType::kModule)
477 UseCounter::Count(*context_document, WebFeature::kPrepareModuleScript);
478
479 DCHECK(!prepared_pending_script_);
480
481 // <spec step="22">Let options be a script fetch options whose cryptographic
482 // nonce is cryptographic nonce, integrity metadata is integrity metadata,
483 // parser metadata is parser metadata, credentials mode is module script
484 // credentials mode, and referrer policy is referrer policy.</spec>
485 ScriptFetchOptions options(nonce, integrity_metadata, integrity_attr,
486 parser_state, credentials_mode, referrer_policy,
487 importance);
488
489 // <spec step="23">Let settings object be the element's node document's
490 // relevant settings object.</spec>
491 //
492 // In some cases (mainly for classic scripts) |element_document| is used as
493 // the "settings object", while in other cases (mainly for module scripts)
494 // |content_document| is used.
495 // TODO(hiroshige): Use a consistent Document everywhere.
496 auto* fetch_client_settings_object_fetcher = context_document->Fetcher();
497
498 // https://wicg.github.io/import-maps/#integration-prepare-a-script
499 // If the script’s type is "importmap" and the element’s node document’s
500 // acquiring import maps is false, then queue a task to fire an event named
501 // error at the element, and return. [spec text]
502 if (is_import_map) {
503 Modulator* modulator = Modulator::From(
504 ToScriptStateForMainWorld(context_document->GetFrame()));
505 if (!modulator->IsAcquiringImportMaps()) {
506 element_document.AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
507 mojom::ConsoleMessageSource::kJavaScript,
508 mojom::ConsoleMessageLevel::kError,
509 "An import map is added after module script load was triggered."));
510 element_document.GetTaskRunner(TaskType::kDOMManipulation)
511 ->PostTask(FROM_HERE,
512 WTF::Bind(&ScriptElementBase::DispatchErrorEvent,
513 WrapPersistent(element_.Get())));
514 return false;
515 }
516 }
517
518 // <spec step="24">If the element has a src content attribute, then:</spec>
519 if (element_->HasSourceAttribute()) {
520 // <spec step="24.1">Let src be the value of the element's src
521 // attribute.</spec>
522 String src =
523 StripLeadingAndTrailingHTMLSpaces(element_->SourceAttributeValue());
524
525 // <spec step="24.2">If src is the empty string, queue a task to fire an
526 // event named error at the element, and return.</spec>
527 if (src.IsEmpty()) {
528 element_document.GetTaskRunner(TaskType::kDOMManipulation)
529 ->PostTask(FROM_HERE,
530 WTF::Bind(&ScriptElementBase::DispatchErrorEvent,
531 WrapPersistent(element_.Get())));
532 return false;
533 }
534
535 // <spec step="24.3">Set the element's from an external file flag.</spec>
536 is_external_script_ = true;
537
538 // <spec step="24.4">Parse src relative to the element's node
539 // document.</spec>
540 KURL url = element_document.CompleteURL(src);
541
542 // <spec step="24.5">If the previous step failed, queue a task to fire an
543 // event named error at the element, and return. Otherwise, let url be the
544 // resulting URL record.</spec>
545 if (!url.IsValid()) {
546 element_document.GetTaskRunner(TaskType::kDOMManipulation)
547 ->PostTask(FROM_HERE,
548 WTF::Bind(&ScriptElementBase::DispatchErrorEvent,
549 WrapPersistent(element_.Get())));
550 return false;
551 }
552
553 // <spec step="24.6">Switch on the script's type:</spec>
554 if (is_import_map) {
555 // TODO(crbug.com/922212): Implement external import maps.
556 element_document.AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>(
557 mojom::ConsoleMessageSource::kJavaScript,
558 mojom::ConsoleMessageLevel::kError,
559 "External import maps are not yet supported."));
560 element_document.GetTaskRunner(TaskType::kDOMManipulation)
561 ->PostTask(FROM_HERE,
562 WTF::Bind(&ScriptElementBase::DispatchErrorEvent,
563 WrapPersistent(element_.Get())));
564 return false;
565 }
566
567 if (GetScriptType() == mojom::ScriptType::kClassic) {
568 // - "classic":
569
570 // <spec step="15">If the script element has a charset attribute, then let
571 // encoding be the result of getting an encoding from the value of the
572 // charset attribute. If the script element does not have a charset
573 // attribute, or if getting an encoding failed, let encoding be the same
574 // as the encoding of the script element's node document.</spec>
575 //
576 // TODO(hiroshige): Should we handle failure in getting an encoding?
577 WTF::TextEncoding encoding;
578 if (!element_->CharsetAttributeValue().IsEmpty())
579 encoding = WTF::TextEncoding(element_->CharsetAttributeValue());
580 else
581 encoding = element_document.Encoding();
582
583 // <spec step="24.6.A">"classic"
584 //
585 // Fetch a classic script given url, settings object, options, classic
586 // script CORS setting, and encoding.</spec>
587 Document* document_for_origin = &element_document;
588 if (element_document.ImportsController()) {
589 document_for_origin = context_document;
590 }
591 FetchClassicScript(url, *document_for_origin, options, cross_origin,
592 encoding);
593 } else {
594 // - "module":
595
596 // Step 15 is skipped because they are not used in module
597 // scripts.
598
599 // <spec step="24.6.B">"module"
600 //
601 // Fetch an external module script graph given url, settings object, and
602 // options.</spec>
603 Modulator* modulator = Modulator::From(
604 ToScriptStateForMainWorld(context_document->GetFrame()));
605 FetchModuleScriptTree(url, fetch_client_settings_object_fetcher,
606 modulator, options);
607 }
608 // <spec step="24.6">When the chosen algorithm asynchronously completes, set
609 // the script's script to the result. At that time, the script is ready.
610 // ...</spec>
611 //
612 // When the script is ready, PendingScriptClient::pendingScriptFinished()
613 // is used as the notification, and the action to take when
614 // the script is ready is specified later, in
615 // - ScriptLoader::PrepareScript(), or
616 // - HTMLParserScriptRunner,
617 // depending on the conditions in Step 25 of "prepare a script".
618 }
619
620 // <spec step="25">If the element does not have a src content attribute, run
621 // these substeps:</spec>
622 if (!element_->HasSourceAttribute()) {
623 // <spec step="24.1">Let src be the value of the element's src
624 // attribute.</spec>
625 //
626 // This step is done later as ScriptElementBase::ChildTextContent():
627 // - in ScriptLoader::PrepareScript() (Step 26, 6th Clause),
628 // - in HTMLParserScriptRunner::ProcessScriptElementInternal()
629 // (Duplicated code of Step 26, 6th Clause),
630 // - in XMLDocumentParser::EndElementNs() (Step 26, 5th Clause), or
631 // - in PendingScript::GetSource() (Indirectly used via
632 // HTMLParserScriptRunner::ProcessScriptElementInternal(),
633 // Step 26, 5th Clause).
634
635 // <spec step="25.1">Let base URL be the script element's node document's
636 // document base URL.</spec>
637 KURL base_url = element_document.BaseURL();
638
639 // <spec step="25.2">Switch on the script's type:</spec>
640
641 if (is_import_map) {
642 UseCounter::Count(*context_document, WebFeature::kImportMap);
643
644 // https://wicg.github.io/import-maps/#integration-prepare-a-script
645 // 1. Let import map parse result be the result of create an import map
646 // parse result, given source text, base URL and settings object. [spec
647 // text]
648 PendingImportMap* pending_import_map =
649 PendingImportMap::CreateInline(*element_, source_text, base_url);
650
651 // Because we currently support inline import maps only, the pending
652 // import map is ready immediately and thus we call `register an import
653 // map` synchronously here.
654 pending_import_map->RegisterImportMap();
655
656 return false;
657 }
658
659 switch (GetScriptType()) {
660 // <spec step="25.2.A">"classic"</spec>
661 case mojom::ScriptType::kClassic: {
662 // <spec step="25.2.A.1">Let script be the result of creating a classic
663 // script using source text, settings object, base URL, and
664 // options.</spec>
665
666 ScriptSourceLocationType script_location_type =
667 ScriptSourceLocationType::kInline;
668 if (!parser_inserted_) {
669 script_location_type =
670 ScriptSourceLocationType::kInlineInsideGeneratedElement;
671 } else if (is_in_document_write) {
672 script_location_type =
673 ScriptSourceLocationType::kInlineInsideDocumentWrite;
674 }
675
676 prepared_pending_script_ = ClassicPendingScript::CreateInline(
677 element_, position, base_url, source_text, script_location_type,
678 options);
679
680 // <spec step="25.2.A.2">Set the script's script to script.</spec>
681 //
682 // <spec step="25.2.A.3">The script is ready.</spec>
683 //
684 // Implemented by ClassicPendingScript.
685 break;
686 }
687
688 // <spec step="25.2.B">"module"</spec>
689 case mojom::ScriptType::kModule: {
690 // <spec step="25.2.B.1">Fetch an inline module script graph, given
691 // source text, base URL, settings object, and options. When this
692 // asynchronously completes, set the script's script to the result. At
693 // that time, the script is ready.</spec>
694 //
695 // <specdef label="fetch-an-inline-module-script-graph"
696 // href="https://html.spec.whatwg.org/C/#fetch-an-inline-module-script-graph">
697 const KURL& source_url = element_document.Url();
698 Modulator* modulator = Modulator::From(
699 ToScriptStateForMainWorld(context_document->GetFrame()));
700
701 // <spec label="fetch-an-inline-module-script-graph" step="1">Let script
702 // be the result of creating a JavaScript module script using source
703 // text, settings object, base URL, and options.</spec>
704 ModuleScript* module_script =
705 JSModuleScript::Create(ParkableString(source_text.Impl()), nullptr,
706 ScriptSourceLocationType::kInline, modulator,
707 source_url, base_url, options, position);
708
709 // <spec label="fetch-an-inline-module-script-graph" step="2">If script
710 // is null, asynchronously complete this algorithm with null, and abort
711 // these steps.</spec>
712 if (!module_script)
713 return false;
714
715 // <spec label="fetch-an-inline-module-script-graph" step="4">Fetch the
716 // descendants of and instantiate script, given settings object, the
717 // destination "script", and visited set. When this asynchronously
718 // completes with final result, asynchronously complete this algorithm
719 // with final result.</spec>
720 auto* module_tree_client =
721 MakeGarbageCollected<ModulePendingScriptTreeClient>();
722 modulator->FetchDescendantsForInlineScript(
723 module_script, fetch_client_settings_object_fetcher,
724 mojom::RequestContextType::SCRIPT,
725 network::mojom::RequestDestination::kScript, module_tree_client);
726 prepared_pending_script_ = MakeGarbageCollected<ModulePendingScript>(
727 element_, module_tree_client, is_external_script_);
728 break;
729 }
730 }
731 }
732
733 DCHECK(prepared_pending_script_);
734
735 // <spec step="26">Then, follow the first of the following options that
736 // describes the situation:</spec>
737
738 // Three flags are used to instruct the caller of prepareScript() to execute
739 // a part of Step 25, when |m_willBeParserExecuted| is true:
740 // - |m_willBeParserExecuted|
741 // - |m_willExecuteWhenDocumentFinishedParsing|
742 // - |m_readyToBeParserExecuted|
743 // TODO(hiroshige): Clean up the dependency.
744
745 // <spec step="26.A">If the script's type is "classic", and the element has a
746 // src attribute, and the element has a defer attribute, and the element has
747 // been flagged as "parser-inserted", and the element does not have an async
748 // attribute
749 //
750 // If the script's type is "module", and the element has been flagged as
751 // "parser-inserted", and the element does not have an async attribute
752 // ...</spec>
753 if ((GetScriptType() == mojom::ScriptType::kClassic &&
754 element_->HasSourceAttribute() && element_->DeferAttributeValue() &&
755 parser_inserted_ && !element_->AsyncAttributeValue()) ||
756 (GetScriptType() == mojom::ScriptType::kModule && parser_inserted_ &&
757 !element_->AsyncAttributeValue())) {
758 // This clause is implemented by the caller-side of prepareScript():
759 // - HTMLParserScriptRunner::requestDeferredScript(), and
760 // - TODO(hiroshige): Investigate XMLDocumentParser::endElementNs()
761 will_execute_when_document_finished_parsing_ = true;
762 will_be_parser_executed_ = true;
763
764 return true;
765 }
766
767 // Check for external script that should be force deferred.
768 if (GetScriptType() == mojom::ScriptType::kClassic &&
769 element_->HasSourceAttribute() &&
770 context_document->GetFrame()->ShouldForceDeferScript() &&
771 IsA<HTMLDocument>(context_document) && parser_inserted_ &&
772 !element_->AsyncAttributeValue()) {
773 // In terms of ScriptLoader flags, force deferred scripts behave like
774 // parser-blocking scripts, except that |force_deferred_| is set.
775 // The caller of PrepareScript()
776 // - Force-defers such scripts if the caller supports force-defer
777 // (i.e., HTMLParserScriptRunner); or
778 // - Ignores the |force_deferred_| flag and handles such scripts as
779 // parser-blocking scripts (e.g., XMLParserScriptRunner).
780 force_deferred_ = true;
781 will_be_parser_executed_ = true;
782
783 return true;
784 }
785
786 // <spec step="26.B">If the script's type is "classic", and the element has a
787 // src attribute, and the element has been flagged as "parser-inserted", and
788 // the element does not have an async attribute ...</spec>
789 if (GetScriptType() == mojom::ScriptType::kClassic &&
790 element_->HasSourceAttribute() && parser_inserted_ &&
791 !element_->AsyncAttributeValue()) {
792 // This clause is implemented by the caller-side of prepareScript():
793 // - HTMLParserScriptRunner::requestParsingBlockingScript()
794 // - TODO(hiroshige): Investigate XMLDocumentParser::endElementNs()
795 will_be_parser_executed_ = true;
796
797 return true;
798 }
799
800 // <spec step="26.C">If the script's type is "classic", and the element has a
801 // src attribute, and the element does not have an async attribute, and the
802 // element does not have the "non-blocking" flag set
803 //
804 // If the script's type is "module", and the element does not have an async
805 // attribute, and the element does not have the "non-blocking" flag set
806 // ...</spec>
807 if ((GetScriptType() == mojom::ScriptType::kClassic &&
808 element_->HasSourceAttribute() && !element_->AsyncAttributeValue() &&
809 !non_blocking_) ||
810 (GetScriptType() == mojom::ScriptType::kModule &&
811 !element_->AsyncAttributeValue() && !non_blocking_)) {
812 // <spec step="26.C">... Add the element to the end of the list of scripts
813 // that will execute in order as soon as possible associated with the node
814 // document of the script element at the time the prepare a script algorithm
815 // started. ...</spec>
816 pending_script_ = TakePendingScript(ScriptSchedulingType::kInOrder);
817 // TODO(hiroshige): Here |contextDocument| is used as "node document"
818 // while Step 14 uses |elementDocument| as "node document". Fix this.
819 context_document->GetScriptRunner()->QueueScriptForExecution(
820 pending_script_);
821 // Note that watchForLoad can immediately call pendingScriptFinished.
822 pending_script_->WatchForLoad(this);
823 // The part "When the script is ready..." is implemented in
824 // ScriptRunner::notifyScriptReady().
825 // TODO(hiroshige): Annotate it.
826
827 return true;
828 }
829
830 // <spec step="26.D">If the script's type is "classic", and the element has a
831 // src attribute
832 //
833 // If the script's type is "module" ...</spec>
834 if ((GetScriptType() == mojom::ScriptType::kClassic &&
835 element_->HasSourceAttribute()) ||
836 GetScriptType() == mojom::ScriptType::kModule) {
837 // <spec step="26.D">... The element must be added to the set of scripts
838 // that will execute as soon as possible of the node document of the script
839 // element at the time the prepare a script algorithm started. When the
840 // script is ready, execute the script block and then remove the element
841 // from the set of scripts that will execute as soon as possible.</spec>
842 pending_script_ = TakePendingScript(ScriptSchedulingType::kAsync);
843 // TODO(hiroshige): Here |contextDocument| is used as "node document"
844 // while Step 14 uses |elementDocument| as "node document". Fix this.
845 context_document->GetScriptRunner()->QueueScriptForExecution(
846 pending_script_);
847 // Note that watchForLoad can immediately call pendingScriptFinished.
848 pending_script_->WatchForLoad(this);
849 // The part "When the script is ready..." is implemented in
850 // ScriptRunner::notifyScriptReady().
851 // TODO(hiroshige): Annotate it.
852
853 return true;
854 }
855
856 // The following clauses are executed only if the script's type is "classic"
857 // and the element doesn't have a src attribute.
858 DCHECK_EQ(GetScriptType(), mojom::ScriptType::kClassic);
859 DCHECK(!is_external_script_);
860
861 // Check for inline script that should be force deferred.
862 if (context_document->GetFrame()->ShouldForceDeferScript() &&
863 IsA<HTMLDocument>(context_document) && parser_inserted_) {
864 force_deferred_ = true;
865 will_be_parser_executed_ = true;
866 return true;
867 }
868
869 // <spec step="26.E">If the element does not have a src attribute, and the
870 // element has been flagged as "parser-inserted", and either the parser that
871 // created the script is an XML parser or it's an HTML parser whose script
872 // nesting level is not greater than one, and the Document of the HTML parser
873 // or XML parser that created the script element has a style sheet that is
874 // blocking scripts ...</spec>
875 //
876 // <spec step="26.E">... has a style sheet that is blocking scripts ...</spec>
877 //
878 // is implemented in Document::isScriptExecutionReady().
879 // Part of the condition check is done in
880 // HTMLParserScriptRunner::processScriptElementInternal().
881 // TODO(hiroshige): Clean up the split condition check.
882 if (!element_->HasSourceAttribute() && parser_inserted_ &&
883 !element_document.IsScriptExecutionReady()) {
884 // The former part of this clause is
885 // implemented by the caller-side of prepareScript():
886 // - HTMLParserScriptRunner::requestParsingBlockingScript()
887 // - TODO(hiroshige): Investigate XMLDocumentParser::endElementNs()
888 will_be_parser_executed_ = true;
889 // <spec step="26.E">... Set the element's "ready to be parser-executed"
890 // flag. ...</spec>
891 ready_to_be_parser_executed_ = true;
892
893 return true;
894 }
895
896 // <spec step="26.F">Otherwise
897 //
898 // Immediately execute the script block, even if other scripts are already
899 // executing.</spec>
900 //
901 // Note: this block is also duplicated in
902 // HTMLParserScriptRunner::processScriptElementInternal().
903 // TODO(hiroshige): Merge the duplicated code.
904 KURL script_url = (!is_in_document_write && parser_inserted_)
905 ? element_document.Url()
906 : KURL();
907 TakePendingScript(ScriptSchedulingType::kImmediate)
908 ->ExecuteScriptBlock(script_url);
909 return true;
910 }
911
912 // https://html.spec.whatwg.org/C/#fetch-a-classic-script
FetchClassicScript(const KURL & url,Document & document,const ScriptFetchOptions & options,CrossOriginAttributeValue cross_origin,const WTF::TextEncoding & encoding)913 void ScriptLoader::FetchClassicScript(const KURL& url,
914 Document& document,
915 const ScriptFetchOptions& options,
916 CrossOriginAttributeValue cross_origin,
917 const WTF::TextEncoding& encoding) {
918 FetchParameters::DeferOption defer = FetchParameters::kNoDefer;
919 if (!parser_inserted_ || element_->AsyncAttributeValue() ||
920 element_->DeferAttributeValue())
921 defer = FetchParameters::kLazyLoad;
922
923 ClassicPendingScript* pending_script = ClassicPendingScript::Fetch(
924 url, document, options, cross_origin, encoding, element_, defer);
925 prepared_pending_script_ = pending_script;
926 resource_keep_alive_ = pending_script->GetResource();
927 }
928
929 // <specdef href="https://html.spec.whatwg.org/C/#prepare-a-script">
FetchModuleScriptTree(const KURL & url,ResourceFetcher * fetch_client_settings_object_fetcher,Modulator * modulator,const ScriptFetchOptions & options)930 void ScriptLoader::FetchModuleScriptTree(
931 const KURL& url,
932 ResourceFetcher* fetch_client_settings_object_fetcher,
933 Modulator* modulator,
934 const ScriptFetchOptions& options) {
935 // <spec step="24.6.B">"module"
936 //
937 // Fetch an external module script graph given url, settings object, and
938 // options.</spec>
939 auto* module_tree_client =
940 MakeGarbageCollected<ModulePendingScriptTreeClient>();
941 modulator->FetchTree(url, fetch_client_settings_object_fetcher,
942 mojom::RequestContextType::SCRIPT,
943 network::mojom::RequestDestination::kScript, options,
944 ModuleScriptCustomFetchType::kNone, module_tree_client);
945 prepared_pending_script_ = MakeGarbageCollected<ModulePendingScript>(
946 element_, module_tree_client, is_external_script_);
947 }
948
TakePendingScript(ScriptSchedulingType scheduling_type)949 PendingScript* ScriptLoader::TakePendingScript(
950 ScriptSchedulingType scheduling_type) {
951 CHECK(prepared_pending_script_);
952
953 DEFINE_STATIC_LOCAL(
954 EnumerationHistogram, scheduling_type_histogram,
955 ("Blink.Script.SchedulingType", kLastScriptSchedulingType + 1));
956 scheduling_type_histogram.Count(static_cast<int>(scheduling_type));
957 PendingScript* pending_script = prepared_pending_script_;
958 prepared_pending_script_ = nullptr;
959 pending_script->SetSchedulingType(scheduling_type);
960 return pending_script;
961 }
962
PendingScriptFinished(PendingScript * pending_script)963 void ScriptLoader::PendingScriptFinished(PendingScript* pending_script) {
964 DCHECK(!will_be_parser_executed_);
965 DCHECK_EQ(pending_script_, pending_script);
966 DCHECK_EQ(pending_script_->GetScriptType(), GetScriptType());
967 DCHECK(pending_script->IsControlledByScriptRunner());
968 DCHECK(pending_script_->GetSchedulingType() == ScriptSchedulingType::kAsync ||
969 pending_script_->GetSchedulingType() ==
970 ScriptSchedulingType::kInOrder);
971 // Historically we clear |resource_keep_alive_| when the scheduling type is
972 // kAsync or kInOrder (crbug.com/778799). But if the script resource was
973 // served via signed exchange, the script may not be in the HTTPCache,
974 // therefore will need to be refetched over network if it's evicted from the
975 // memory cache not be in the HTTPCache. So we keep |resource_keep_alive_| to
976 // keep the resource in the memory cache.
977 if (resource_keep_alive_ &&
978 !resource_keep_alive_->GetResponse().IsSignedExchangeInnerResponse() &&
979 !base::FeatureList::IsEnabled(
980 blink::features::kKeepScriptResourceAlive)) {
981 resource_keep_alive_ = nullptr;
982 }
983
984 Document* context_document = element_->GetDocument().ContextDocument();
985 if (!context_document) {
986 DetachPendingScript();
987 return;
988 }
989
990 context_document->GetScriptRunner()->NotifyScriptReady(pending_script);
991 pending_script_->StopWatchingForLoad();
992 pending_script_ = nullptr;
993 }
994
IgnoresLoadRequest() const995 bool ScriptLoader::IgnoresLoadRequest() const {
996 return already_started_ || is_external_script_ || parser_inserted_ ||
997 !element_->IsConnected();
998 }
999
1000 // <specdef href="https://html.spec.whatwg.org/C/#prepare-a-script">
IsScriptForEventSupported() const1001 bool ScriptLoader::IsScriptForEventSupported() const {
1002 // <spec step="14.1">Let for be the value of the for attribute.</spec>
1003 String event_attribute = element_->EventAttributeValue();
1004 // <spec step="14.2">Let event be the value of the event attribute.</spec>
1005 String for_attribute = element_->ForAttributeValue();
1006
1007 // <spec step="14">If the script element has an event attribute and a for
1008 // attribute, and the script's type is "classic", then:</spec>
1009 if (GetScriptType() != mojom::ScriptType::kClassic ||
1010 event_attribute.IsNull() || for_attribute.IsNull())
1011 return true;
1012
1013 // <spec step="14.3">Strip leading and trailing ASCII whitespace from event
1014 // and for.</spec>
1015 for_attribute = for_attribute.StripWhiteSpace();
1016 // <spec step="14.4">If for is not an ASCII case-insensitive match for the
1017 // string "window", then return. The script is not executed.</spec>
1018 if (!EqualIgnoringASCIICase(for_attribute, "window"))
1019 return false;
1020 event_attribute = event_attribute.StripWhiteSpace();
1021 // <spec step="14.5">If event is not an ASCII case-insensitive match for
1022 // either the string "onload" or the string "onload()", then return. The
1023 // script is not executed.</spec>
1024 return EqualIgnoringASCIICase(event_attribute, "onload") ||
1025 EqualIgnoringASCIICase(event_attribute, "onload()");
1026 }
1027
1028 PendingScript*
GetPendingScriptIfControlledByScriptRunnerForCrossDocMove()1029 ScriptLoader::GetPendingScriptIfControlledByScriptRunnerForCrossDocMove() {
1030 DCHECK(!pending_script_ || pending_script_->IsControlledByScriptRunner());
1031 return pending_script_;
1032 }
1033
GetScriptText() const1034 String ScriptLoader::GetScriptText() const {
1035 // Step 3 of
1036 // https://w3c.github.io/webappsec-trusted-types/dist/spec/#abstract-opdef-prepare-the-script-url-and-text
1037 // called from § 4.1.3.3, step 4 of
1038 // https://w3c.github.io/webappsec-trusted-types/dist/spec/#slot-value-verification
1039 // This will return the [[ScriptText]] internal slot value after that step,
1040 // or a null string if the the Trusted Type algorithm threw an error.
1041 String child_text_content = element_->ChildTextContent();
1042 DCHECK(!child_text_content.IsNull());
1043 String script_text_internal_slot = element_->ScriptTextInternalSlot();
1044 if (child_text_content == script_text_internal_slot)
1045 return child_text_content;
1046 return GetStringForScriptExecution(child_text_content,
1047 element_->GetScriptElementType(),
1048 element_->GetDocument().ContextDocument());
1049 }
1050
1051 } // namespace blink
1052