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