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