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