| /* |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * (C) 1999 Antti Koivisto (koivisto@kde.org) |
| * (C) 2001 Dirk Mueller (mueller@kde.org) |
| * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights |
| * reserved. |
| * Copyright (C) 2008 Nikolas Zimmermann <zimmermann@kde.org> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "third_party/blink/renderer/core/script/script_loader.h" |
| |
| #include "base/feature_list.h" |
| #include "services/network/public/mojom/fetch_api.mojom-shared.h" |
| #include "third_party/blink/public/common/feature_policy/feature_policy.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/mojom/feature_policy/feature_policy_feature.mojom-blink.h" |
| #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h" |
| #include "third_party/blink/renderer/bindings/core/v8/sanitize_script_errors.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/dom/events/event.h" |
| #include "third_party/blink/renderer/core/dom/scriptable_document_parser.h" |
| #include "third_party/blink/renderer/core/dom/text.h" |
| #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/frame/local_frame.h" |
| #include "third_party/blink/renderer/core/html/html_document.h" |
| #include "third_party/blink/renderer/core/html/imports/html_import.h" |
| #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h" |
| #include "third_party/blink/renderer/core/html_names.h" |
| #include "third_party/blink/renderer/core/inspector/console_message.h" |
| #include "third_party/blink/renderer/core/loader/importance_attribute.h" |
| #include "third_party/blink/renderer/core/loader/modulescript/module_script_creation_params.h" |
| #include "third_party/blink/renderer/core/loader/modulescript/module_script_fetch_request.h" |
| #include "third_party/blink/renderer/core/loader/subresource_integrity_helper.h" |
| #include "third_party/blink/renderer/core/script/classic_pending_script.h" |
| #include "third_party/blink/renderer/core/script/classic_script.h" |
| #include "third_party/blink/renderer/core/script/import_map.h" |
| #include "third_party/blink/renderer/core/script/js_module_script.h" |
| #include "third_party/blink/renderer/core/script/modulator.h" |
| #include "third_party/blink/renderer/core/script/module_pending_script.h" |
| #include "third_party/blink/renderer/core/script/pending_import_map.h" |
| #include "third_party/blink/renderer/core/script/script.h" |
| #include "third_party/blink/renderer/core/script/script_element_base.h" |
| #include "third_party/blink/renderer/core/script/script_runner.h" |
| #include "third_party/blink/renderer/core/svg_names.h" |
| #include "third_party/blink/renderer/core/trustedtypes/trusted_types_util.h" |
| #include "third_party/blink/renderer/platform/bindings/parkable_string.h" |
| #include "third_party/blink/renderer/platform/heap/heap.h" |
| #include "third_party/blink/renderer/platform/instrumentation/histogram.h" |
| #include "third_party/blink/renderer/platform/instrumentation/use_counter.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/fetch_client_settings_object_snapshot.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/fetch_parameters.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h" |
| #include "third_party/blink/renderer/platform/loader/subresource_integrity.h" |
| #include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h" |
| #include "third_party/blink/renderer/platform/weborigin/security_origin.h" |
| #include "third_party/blink/renderer/platform/weborigin/security_policy.h" |
| #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_hash.h" |
| |
| namespace blink { |
| |
| ScriptLoader::ScriptLoader(ScriptElementBase* element, |
| const CreateElementFlags flags) |
| : element_(element), |
| will_be_parser_executed_(false), |
| will_execute_when_document_finished_parsing_(false), |
| force_deferred_(false) { |
| // <spec href="https://html.spec.whatwg.org/C/#already-started">... The |
| // cloning steps for script elements must set the "already started" flag on |
| // the copy if it is set on the element being cloned.</spec> |
| // |
| // TODO(hiroshige): Cloning is implemented together with |
| // {HTML,SVG}ScriptElement::cloneElementWithoutAttributesAndChildren(). |
| // Clean up these later. |
| if (flags.WasAlreadyStarted()) |
| already_started_ = true; |
| |
| if (flags.IsCreatedByParser()) { |
| // <spec href="https://html.spec.whatwg.org/C/#parser-inserted">script |
| // elements with non-null parser documents are known as |
| // "parser-inserted".</spec> |
| // For more information on why this is not implemented in terms of a |
| // non-null parser document, see the documentation in the header file. |
| parser_inserted_ = true; |
| |
| // <spec href="https://html.spec.whatwg.org/C/#parser-document">... It is |
| // set by the HTML parser and the XML parser on script elements they insert |
| // ...</spec> |
| parser_document_ = flags.ParserDocument(); |
| |
| // <spec href="https://html.spec.whatwg.org/C/#non-blocking">... It is unset |
| // by the HTML parser and the XML parser on script elements they insert. |
| // ...</spec> |
| non_blocking_ = false; |
| } |
| } |
| |
| ScriptLoader::~ScriptLoader() {} |
| |
| void ScriptLoader::Trace(Visitor* visitor) const { |
| visitor->Trace(element_); |
| visitor->Trace(parser_document_); |
| visitor->Trace(pending_script_); |
| visitor->Trace(prepared_pending_script_); |
| visitor->Trace(resource_keep_alive_); |
| PendingScriptClient::Trace(visitor); |
| } |
| |
| void ScriptLoader::DidNotifySubtreeInsertionsToDocument() { |
| if (!parser_inserted_) |
| PrepareScript(); // FIXME: Provide a real starting line number here. |
| } |
| |
| void ScriptLoader::ChildrenChanged() { |
| if (!parser_inserted_ && element_->IsConnected()) |
| PrepareScript(); // FIXME: Provide a real starting line number here. |
| } |
| |
| void ScriptLoader::HandleSourceAttribute(const String& source_url) { |
| if (IgnoresLoadRequest() || source_url.IsEmpty()) |
| return; |
| |
| PrepareScript(); // FIXME: Provide a real starting line number here. |
| } |
| |
| // <specdef href="https://html.spec.whatwg.org/C/#non-blocking"> |
| void ScriptLoader::HandleAsyncAttribute() { |
| // <spec>... In addition, whenever a script element whose "non-blocking" flag |
| // is set has an async content attribute added, the element's "non-blocking" |
| // flag must be unset.</spec> |
| non_blocking_ = false; |
| } |
| |
| void ScriptLoader::DetachPendingScript() { |
| if (!pending_script_) |
| return; |
| pending_script_->Dispose(); |
| pending_script_ = nullptr; |
| } |
| |
| namespace { |
| |
| bool IsValidClassicScriptTypeAndLanguage( |
| const String& type, |
| const String& language, |
| ScriptLoader::LegacyTypeSupport support_legacy_types) { |
| // FIXME: IsLegacySupportedJavaScriptLanguage() is not valid HTML5. It is used |
| // here to maintain backwards compatibility with existing web tests. The |
| // specific violations are: |
| // - Allowing type=javascript. type= should only support MIME types, such as |
| // text/javascript. |
| // - Allowing a different set of languages for language= and type=. language= |
| // supports Javascript 1.1 and 1.4-1.6, but type= does not. |
| if (type.IsEmpty()) { |
| return language.IsEmpty() || // assume text/javascript. |
| MIMETypeRegistry::IsSupportedJavaScriptMIMEType("text/" + |
| language) || |
| MIMETypeRegistry::IsLegacySupportedJavaScriptLanguage(language); |
| } else if (MIMETypeRegistry::IsSupportedJavaScriptMIMEType( |
| type.StripWhiteSpace()) || |
| (support_legacy_types == |
| ScriptLoader::kAllowLegacyTypeInTypeAttribute && |
| MIMETypeRegistry::IsLegacySupportedJavaScriptLanguage(type))) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| enum class ShouldFireErrorEvent { |
| kDoNotFire, |
| kShouldFire, |
| }; |
| |
| bool IsImportMapEnabled(LocalDOMWindow* context_window) { |
| // When window/modulator is null, `true` is returned here, because |
| // PrepareScript() should fail at "scripting is disabled" checks, not here. |
| |
| if (!context_window) |
| return true; |
| |
| Modulator* modulator = |
| Modulator::From(ToScriptStateForMainWorld(context_window->GetFrame())); |
| if (!modulator) |
| return true; |
| |
| return modulator->ImportMapsEnabled(); |
| } |
| |
| } // namespace |
| |
| // <specdef href="https://html.spec.whatwg.org/C/#prepare-a-script"> |
| ScriptLoader::ScriptTypeAtPrepare ScriptLoader::GetScriptTypeAtPrepare( |
| const String& type, |
| const String& language, |
| LegacyTypeSupport support_legacy_types) { |
| if (IsValidClassicScriptTypeAndLanguage(type, language, |
| support_legacy_types)) { |
| // <spec step="7">... If the script block's type string is a JavaScript MIME |
| // type essence match, the script's type is "classic". ...</spec> |
| // |
| // TODO(hiroshige): Annotate and/or cleanup this step. |
| return ScriptTypeAtPrepare::kClassic; |
| } |
| |
| if (EqualIgnoringASCIICase(type, "module")) { |
| // <spec step="7">... If the script block's type string is an ASCII |
| // case-insensitive match for the string "module", the script's type is |
| // "module". ...</spec> |
| return ScriptTypeAtPrepare::kModule; |
| } |
| |
| if (EqualIgnoringASCIICase(type, "importmap")) { |
| return ScriptTypeAtPrepare::kImportMap; |
| } |
| |
| // <spec step="7">... If neither of the above conditions are true, then |
| // return. No script is executed.</spec> |
| return ScriptTypeAtPrepare::kInvalid; |
| } |
| |
| bool ScriptLoader::BlockForNoModule(ScriptTypeAtPrepare script_type, |
| bool nomodule) { |
| return nomodule && script_type == ScriptTypeAtPrepare::kClassic; |
| } |
| |
| // Corresponds to |
| // https://html.spec.whatwg.org/C/#module-script-credentials-mode |
| // which is a translation of the CORS settings attribute in the context of |
| // module scripts. This is used in: |
| // - Step 17 of |
| // https://html.spec.whatwg.org/C/#prepare-a-script |
| // - Step 6 of obtaining a preloaded module script |
| // https://html.spec.whatwg.org/C/#link-type-modulepreload. |
| network::mojom::CredentialsMode ScriptLoader::ModuleScriptCredentialsMode( |
| CrossOriginAttributeValue cross_origin) { |
| switch (cross_origin) { |
| case kCrossOriginAttributeNotSet: |
| case kCrossOriginAttributeAnonymous: |
| return network::mojom::CredentialsMode::kSameOrigin; |
| case kCrossOriginAttributeUseCredentials: |
| return network::mojom::CredentialsMode::kInclude; |
| } |
| NOTREACHED(); |
| return network::mojom::CredentialsMode::kOmit; |
| } |
| |
| // https://github.com/w3c/webappsec-permissions-policy/issues/135 |
| bool ShouldBlockSyncScriptForDocumentPolicy( |
| const ScriptElementBase* element, |
| ScriptLoader::ScriptTypeAtPrepare script_type, |
| bool parser_inserted) { |
| if (element->GetExecutionContext()->IsFeatureEnabled( |
| mojom::blink::DocumentPolicyFeature::kSyncScript)) { |
| return false; |
| } |
| |
| // Module scripts and import maps never block parsing. |
| if (script_type == ScriptLoader::ScriptTypeAtPrepare::kModule || |
| script_type == ScriptLoader::ScriptTypeAtPrepare::kImportMap || |
| !parser_inserted) |
| return false; |
| |
| if (!element->HasSourceAttribute()) |
| return true; |
| return !element->DeferAttributeValue() && !element->AsyncAttributeValue(); |
| } |
| |
| // <specdef href="https://html.spec.whatwg.org/C/#prepare-a-script"> |
| bool ScriptLoader::PrepareScript(const TextPosition& script_start_position, |
| LegacyTypeSupport support_legacy_types) { |
| // <spec step="1">If the script element is marked as having "already started", |
| // then return. The script is not executed.</spec> |
| if (already_started_) |
| return false; |
| |
| // <spec step="2">If the element has its "parser-inserted" flag set, then set |
| // was-parser-inserted to true and unset the element's "parser-inserted" flag. |
| // Otherwise, set was-parser-inserted to false.</spec> |
| bool was_parser_inserted; |
| if (parser_inserted_) { |
| was_parser_inserted = true; |
| parser_inserted_ = false; |
| } else { |
| was_parser_inserted = false; |
| } |
| |
| // <spec step="3">If was-parser-inserted is true and the element does not have |
| // an async attribute, then set the element's "non-blocking" flag to |
| // true.</spec> |
| if (was_parser_inserted && !element_->AsyncAttributeValue()) |
| non_blocking_ = true; |
| |
| // <spec step="4">Let source text be the element's child text content.</spec> |
| // |
| // Trusted Types additionally requires: |
| // https://w3c.github.io/webappsec-trusted-types/dist/spec/#slot-value-verification |
| // - Step 4: Execute the Prepare the script URL and text algorithm upon the |
| // script element. If that algorithm threw an error, then return. The |
| // script is not executed. |
| // - Step 5: Let source text be the elementโs [[ScriptText]] internal slot |
| // value. |
| const String source_text = GetScriptText(); |
| |
| // <spec step="5">If the element has no src attribute, and source text is the |
| // empty string, then return. The script is not executed.</spec> |
| if (!element_->HasSourceAttribute() && source_text.IsEmpty()) |
| return false; |
| |
| // <spec step="6">If the element is not connected, then return. The script is |
| // not executed.</spec> |
| if (!element_->IsConnected()) |
| return false; |
| |
| Document& element_document = element_->GetDocument(); |
| LocalDOMWindow* context_window = element_document.ExecutingWindow(); |
| |
| // <spec step="7">... Determine the script's type as follows: ...</spec> |
| script_type_ = GetScriptTypeAtPrepare(element_->TypeAttributeValue(), |
| element_->LanguageAttributeValue(), |
| support_legacy_types); |
| |
| switch (GetScriptType()) { |
| case ScriptTypeAtPrepare::kInvalid: |
| return false; |
| |
| case ScriptTypeAtPrepare::kImportMap: |
| if (!IsImportMapEnabled(context_window)) |
| return false; |
| break; |
| |
| case ScriptTypeAtPrepare::kClassic: |
| case ScriptTypeAtPrepare::kModule: |
| break; |
| } |
| |
| // <spec step="8">If was-parser-inserted is true, then flag the element as |
| // "parser-inserted" again, and set the element's "non-blocking" flag to |
| // false.</spec> |
| if (was_parser_inserted) { |
| parser_inserted_ = true; |
| non_blocking_ = false; |
| } |
| |
| // <spec step="9">Set the element's "already started" flag.</spec> |
| already_started_ = true; |
| |
| // <spec step="10">If the element is flagged as "parser-inserted", but the |
| // element's node document is not the Document of the parser that created the |
| // element, then return.</spec> |
| if (parser_inserted_ && parser_document_ != &element_->GetDocument()) { |
| return false; |
| } |
| |
| // <spec step="11">If scripting is disabled for the script element, then |
| // return. The script is not executed.</spec> |
| // |
| // <spec href="https://html.spec.whatwg.org/C/#concept-n-noscript">Scripting |
| // is disabled for a node if there is no such browsing context, or if |
| // scripting is disabled in that browsing context.</spec> |
| if (!context_window) |
| return false; |
| if (!context_window->CanExecuteScripts(kAboutToExecuteScript)) |
| return false; |
| |
| // <spec step="12">If the script element has a nomodule content attribute and |
| // the script's type is "classic", then return. The script is not |
| // executed.</spec> |
| if (BlockForNoModule(GetScriptType(), element_->NomoduleAttributeValue())) |
| return false; |
| |
| // TODO(csharrison): This logic only works if the tokenizer/parser was not |
| // blocked waiting for scripts when the element was inserted. This usually |
| // fails for instance, on second document.write if a script writes twice |
| // in a row. To fix this, the parser might have to keep track of raw |
| // string position. |
| // |
| // Also PendingScript's contructor has the same code. |
| const bool is_in_document_write = element_document.IsInDocumentWrite(); |
| |
| // Reset line numbering for nested writes. |
| TextPosition position = |
| is_in_document_write ? TextPosition() : script_start_position; |
| |
| // <spec step="13">If the script element does not have a src content |
| // attribute, and the Should element's inline behavior be blocked by Content |
| // Security Policy? algorithm returns "Blocked" when executed upon the script |
| // element, "script", and source text, then return. The script is not |
| // executed. [CSP]</spec> |
| if (!element_->HasSourceAttribute() && |
| !element_->AllowInlineScriptForCSP(element_->GetNonceForElement(), |
| position.line_, source_text)) { |
| return false; |
| } |
| |
| // 14. |
| if (!IsScriptForEventSupported()) |
| return false; |
| |
| // This Document Policy is still in the process of being added to the spec. |
| if (ShouldBlockSyncScriptForDocumentPolicy(element_.Get(), GetScriptType(), |
| parser_inserted_)) { |
| element_document.AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| mojom::ConsoleMessageSource::kJavaScript, |
| mojom::ConsoleMessageLevel::kError, |
| "Synchronous script execution is disabled by Document Policy")); |
| return false; |
| } |
| |
| // 14. is handled below. |
| |
| // <spec step="16">Let classic script CORS setting be the current state of the |
| // element's crossorigin content attribute.</spec> |
| CrossOriginAttributeValue cross_origin = |
| GetCrossOriginAttributeValue(element_->CrossOriginAttributeValue()); |
| |
| // <spec step="17">Let module script credentials mode be the module script |
| // credentials mode for the element's crossorigin content attribute.</spec> |
| network::mojom::CredentialsMode credentials_mode = |
| ModuleScriptCredentialsMode(cross_origin); |
| |
| // <spec step="18">Let cryptographic nonce be the element's |
| // [[CryptographicNonce]] internal slot's value.</spec> |
| String nonce = element_->GetNonceForElement(); |
| |
| // <spec step="19">If the script element has an integrity attribute, then let |
| // integrity metadata be that attribute's value. Otherwise, let integrity |
| // metadata be the empty string.</spec> |
| String integrity_attr = element_->IntegrityAttributeValue(); |
| IntegrityMetadataSet integrity_metadata; |
| if (!integrity_attr.IsEmpty()) { |
| SubresourceIntegrity::IntegrityFeatures integrity_features = |
| SubresourceIntegrityHelper::GetFeatures( |
| element_->GetExecutionContext()); |
| SubresourceIntegrity::ReportInfo report_info; |
| SubresourceIntegrity::ParseIntegrityAttribute( |
| integrity_attr, integrity_features, integrity_metadata, &report_info); |
| SubresourceIntegrityHelper::DoReport(*element_->GetExecutionContext(), |
| report_info); |
| } |
| |
| // <spec step="20">Let referrer policy be the current state of the element's |
| // referrerpolicy content attribute.</spec> |
| String referrerpolicy_attr = element_->ReferrerPolicyAttributeValue(); |
| network::mojom::ReferrerPolicy referrer_policy = |
| network::mojom::ReferrerPolicy::kDefault; |
| if (!referrerpolicy_attr.IsEmpty()) { |
| SecurityPolicy::ReferrerPolicyFromString( |
| referrerpolicy_attr, kDoNotSupportReferrerPolicyLegacyKeywords, |
| &referrer_policy); |
| } |
| |
| // Priority Hints is currently a non-standard feature, but we can assume the |
| // following (see https://crbug.com/821464): |
| // <spec step="21">Let importance be the current state of the element's |
| // importance content attribute.</spec> |
| String importance_attr = element_->ImportanceAttributeValue(); |
| mojom::FetchImportanceMode importance = |
| GetFetchImportanceAttributeValue(importance_attr); |
| |
| // <spec step="21">Let parser metadata be "parser-inserted" if the script |
| // element has been flagged as "parser-inserted", and "not-parser-inserted" |
| // otherwise.</spec> |
| ParserDisposition parser_state = |
| IsParserInserted() ? kParserInserted : kNotParserInserted; |
| |
| if (GetScriptType() == ScriptLoader::ScriptTypeAtPrepare::kModule) |
| UseCounter::Count(*context_window, WebFeature::kPrepareModuleScript); |
| |
| DCHECK(!prepared_pending_script_); |
| |
| RenderBlockingBehavior render_blocking_behavior = |
| non_blocking_ ? RenderBlockingBehavior::kNonBlocking |
| : RenderBlockingBehavior::kBlocking; |
| |
| // <spec step="22">Let options be a script fetch options whose cryptographic |
| // nonce is cryptographic nonce, integrity metadata is integrity metadata, |
| // parser metadata is parser metadata, credentials mode is module script |
| // credentials mode, and referrer policy is referrer policy.</spec> |
| ScriptFetchOptions options(nonce, integrity_metadata, integrity_attr, |
| parser_state, credentials_mode, referrer_policy, |
| importance, render_blocking_behavior); |
| |
| // <spec step="23">Let settings object be the element's node document's |
| // relevant settings object.</spec> |
| // |
| // In some cases (mainly for classic scripts) |element_document| is used as |
| // the "settings object", while in other cases (mainly for module scripts) |
| // |content_document| is used. |
| // TODO(hiroshige): Use a consistent Document everywhere. |
| auto* fetch_client_settings_object_fetcher = context_window->Fetcher(); |
| |
| // https://wicg.github.io/import-maps/#integration-prepare-a-script |
| // If the scriptโs type is "importmap": [spec text] |
| if (GetScriptType() == ScriptTypeAtPrepare::kImportMap) { |
| Modulator* modulator = |
| Modulator::From(ToScriptStateForMainWorld(context_window->GetFrame())); |
| auto aquiring_state = modulator->GetAcquiringImportMapsState(); |
| switch (aquiring_state) { |
| case Modulator::AcquiringImportMapsState::kAfterModuleScriptLoad: |
| case Modulator::AcquiringImportMapsState::kMultipleImportMaps: |
| // 1. If the elementโs node document's acquiring import maps is false, |
| // then queue a task to fire an event named error at the element, and |
| // return. [spec text] |
| element_document.AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| mojom::blink::ConsoleMessageSource::kJavaScript, |
| mojom::blink::ConsoleMessageLevel::kError, |
| aquiring_state == |
| Modulator::AcquiringImportMapsState::kAfterModuleScriptLoad |
| ? "An import map is added after module script load was " |
| "triggered." |
| : "Multiple import maps are not yet supported. " |
| "https://crbug.com/927119")); |
| element_document.GetTaskRunner(TaskType::kDOMManipulation) |
| ->PostTask(FROM_HERE, |
| WTF::Bind(&ScriptElementBase::DispatchErrorEvent, |
| WrapPersistent(element_.Get()))); |
| return false; |
| |
| case Modulator::AcquiringImportMapsState::kAcquiring: |
| // 2. Set the elementโs node document's acquiring import maps to false. |
| // [spec text] |
| modulator->SetAcquiringImportMapsState( |
| Modulator::AcquiringImportMapsState::kMultipleImportMaps); |
| |
| // 3. Assert: the elementโs node document's pending import map script is |
| // null. [spec text] |
| // |
| // TODO(crbug.com/922212): Currently there are no implementation for |
| // "pending import map script" as we don't support external import maps. |
| break; |
| } |
| } |
| |
| // <spec step="24">If the element has a src content attribute, then:</spec> |
| if (element_->HasSourceAttribute()) { |
| // <spec step="24.1">Let src be the value of the element's src |
| // attribute.</spec> |
| String src = |
| StripLeadingAndTrailingHTMLSpaces(element_->SourceAttributeValue()); |
| |
| // <spec step="24.2">If src is the empty string, queue a task to fire an |
| // event named error at the element, and return.</spec> |
| if (src.IsEmpty()) { |
| element_document.GetTaskRunner(TaskType::kDOMManipulation) |
| ->PostTask(FROM_HERE, |
| WTF::Bind(&ScriptElementBase::DispatchErrorEvent, |
| WrapPersistent(element_.Get()))); |
| return false; |
| } |
| |
| // <spec step="24.3">Set the element's from an external file flag.</spec> |
| is_external_script_ = true; |
| |
| // <spec step="24.4">Parse src relative to the element's node |
| // document.</spec> |
| KURL url = element_document.CompleteURL(src); |
| |
| // <spec step="24.5">If the previous step failed, queue a task to fire an |
| // event named error at the element, and return. Otherwise, let url be the |
| // resulting URL record.</spec> |
| if (!url.IsValid()) { |
| element_document.GetTaskRunner(TaskType::kDOMManipulation) |
| ->PostTask(FROM_HERE, |
| WTF::Bind(&ScriptElementBase::DispatchErrorEvent, |
| WrapPersistent(element_.Get()))); |
| return false; |
| } |
| |
| // <spec step="24.6">Switch on the script's type:</spec> |
| switch (GetScriptType()) { |
| case ScriptTypeAtPrepare::kInvalid: |
| NOTREACHED(); |
| return false; |
| |
| case ScriptTypeAtPrepare::kImportMap: |
| // TODO(crbug.com/922212): Implement external import maps. |
| element_document.AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| mojom::ConsoleMessageSource::kJavaScript, |
| mojom::ConsoleMessageLevel::kError, |
| "External import maps are not yet supported.")); |
| element_document.GetTaskRunner(TaskType::kDOMManipulation) |
| ->PostTask(FROM_HERE, |
| WTF::Bind(&ScriptElementBase::DispatchErrorEvent, |
| WrapPersistent(element_.Get()))); |
| return false; |
| |
| case ScriptTypeAtPrepare::kClassic: { |
| // - "classic": |
| |
| // <spec step="15">If the script element has a charset attribute, then |
| // let encoding be the result of getting an encoding from the value of |
| // the charset attribute. If the script element does not have a charset |
| // attribute, or if getting an encoding failed, let encoding be the same |
| // as the encoding of the script element's node document.</spec> |
| // |
| // TODO(hiroshige): Should we handle failure in getting an encoding? |
| WTF::TextEncoding encoding; |
| if (!element_->CharsetAttributeValue().IsEmpty()) |
| encoding = WTF::TextEncoding(element_->CharsetAttributeValue()); |
| else |
| encoding = element_document.Encoding(); |
| |
| // <spec step="24.6.A">"classic" |
| // |
| // Fetch a classic script given url, settings object, options, classic |
| // script CORS setting, and encoding.</spec> |
| Document* document_for_origin = &element_document; |
| if (element_document.ImportsController()) { |
| document_for_origin = context_window->document(); |
| } |
| FetchClassicScript(url, *document_for_origin, options, cross_origin, |
| encoding); |
| break; |
| } |
| case ScriptTypeAtPrepare::kModule: { |
| // - "module": |
| |
| // Step 15 is skipped because they are not used in module |
| // scripts. |
| |
| // <spec step="24.6.B">"module" |
| // |
| // Fetch an external module script graph given url, settings object, and |
| // options.</spec> |
| Modulator* modulator = Modulator::From( |
| ToScriptStateForMainWorld(context_window->GetFrame())); |
| FetchModuleScriptTree(url, fetch_client_settings_object_fetcher, |
| modulator, options); |
| } |
| // <spec step="24.6">When the chosen algorithm asynchronously completes, |
| // set the script's script to the result. At that time, the script is |
| // ready. |
| // ...</spec> |
| // |
| // When the script is ready, |
| // PendingScriptClient::pendingScriptFinished() is used as the |
| // notification, and the action to take when the script is ready is |
| // specified later, in |
| // - ScriptLoader::PrepareScript(), or |
| // - HTMLParserScriptRunner, |
| // depending on the conditions in Step 25 of "prepare a script". |
| break; |
| } |
| } |
| |
| // <spec step="25">If the element does not have a src content attribute, run |
| // these substeps:</spec> |
| if (!element_->HasSourceAttribute()) { |
| // <spec step="24.1">Let src be the value of the element's src |
| // attribute.</spec> |
| // |
| // This step is done later as ScriptElementBase::ChildTextContent(): |
| // - in ScriptLoader::PrepareScript() (Step 26, 6th Clause), |
| // - in HTMLParserScriptRunner::ProcessScriptElementInternal() |
| // (Duplicated code of Step 26, 6th Clause), |
| // - in XMLDocumentParser::EndElementNs() (Step 26, 5th Clause), or |
| // - in PendingScript::GetSource() (Indirectly used via |
| // HTMLParserScriptRunner::ProcessScriptElementInternal(), |
| // Step 26, 5th Clause). |
| |
| // <spec step="25.1">Let base URL be the script element's node document's |
| // document base URL.</spec> |
| KURL base_url = element_document.BaseURL(); |
| |
| // <spec step="25.2">Switch on the script's type:</spec> |
| |
| switch (GetScriptType()) { |
| case ScriptTypeAtPrepare::kInvalid: |
| NOTREACHED(); |
| return false; |
| |
| case ScriptTypeAtPrepare::kImportMap: { |
| UseCounter::Count(*context_window, WebFeature::kImportMap); |
| |
| // https://wicg.github.io/import-maps/#integration-prepare-a-script |
| // 1. Let import map parse result be the result of create an import map |
| // parse result, given source text, base URL and settings object. [spec |
| // text] |
| PendingImportMap* pending_import_map = |
| PendingImportMap::CreateInline(*element_, source_text, base_url); |
| |
| // Because we currently support inline import maps only, the pending |
| // import map is ready immediately and thus we call `register an import |
| // map` synchronously here. |
| pending_import_map->RegisterImportMap(); |
| |
| return false; |
| } |
| |
| // <spec step="25.2.A">"classic"</spec> |
| case ScriptTypeAtPrepare::kClassic: { |
| // <spec step="25.2.A.1">Let script be the result of creating a classic |
| // script using source text, settings object, base URL, and |
| // options.</spec> |
| |
| ScriptSourceLocationType script_location_type = |
| ScriptSourceLocationType::kInline; |
| if (!parser_inserted_) { |
| script_location_type = |
| ScriptSourceLocationType::kInlineInsideGeneratedElement; |
| } else if (is_in_document_write) { |
| script_location_type = |
| ScriptSourceLocationType::kInlineInsideDocumentWrite; |
| } |
| |
| prepared_pending_script_ = ClassicPendingScript::CreateInline( |
| element_, position, base_url, source_text, script_location_type, |
| options); |
| |
| // <spec step="25.2.A.2">Set the script's script to script.</spec> |
| // |
| // <spec step="25.2.A.3">The script is ready.</spec> |
| // |
| // Implemented by ClassicPendingScript. |
| break; |
| } |
| |
| // <spec step="25.2.B">"module"</spec> |
| case ScriptTypeAtPrepare::kModule: { |
| // <spec step="25.2.B.1">Fetch an inline module script graph, given |
| // source text, base URL, settings object, and options. When this |
| // asynchronously completes, set the script's script to the result. At |
| // that time, the script is ready.</spec> |
| // |
| // <specdef label="fetch-an-inline-module-script-graph" |
| // href="https://html.spec.whatwg.org/C/#fetch-an-inline-module-script-graph"> |
| const KURL& source_url = element_document.Url(); |
| Modulator* modulator = Modulator::From( |
| ToScriptStateForMainWorld(context_window->GetFrame())); |
| |
| // <spec label="fetch-an-inline-module-script-graph" step="1">Let script |
| // be the result of creating a JavaScript module script using source |
| // text, settings object, base URL, and options.</spec> |
| |
| ModuleScriptCreationParams params( |
| source_url, base_url, ScriptSourceLocationType::kInline, |
| ModuleType::kJavaScript, ParkableString(source_text.Impl()), |
| nullptr); |
| ModuleScript* module_script = |
| JSModuleScript::Create(params, modulator, options, position); |
| |
| // <spec label="fetch-an-inline-module-script-graph" step="2">If script |
| // is null, asynchronously complete this algorithm with null, and abort |
| // these steps.</spec> |
| if (!module_script) |
| return false; |
| |
| // <spec label="fetch-an-inline-module-script-graph" step="4">Fetch the |
| // descendants of and instantiate script, given settings object, the |
| // destination "script", and visited set. When this asynchronously |
| // completes with final result, asynchronously complete this algorithm |
| // with final result.</spec> |
| auto* module_tree_client = |
| MakeGarbageCollected<ModulePendingScriptTreeClient>(); |
| modulator->FetchDescendantsForInlineScript( |
| module_script, fetch_client_settings_object_fetcher, |
| mojom::blink::RequestContextType::SCRIPT, |
| network::mojom::RequestDestination::kScript, module_tree_client); |
| prepared_pending_script_ = MakeGarbageCollected<ModulePendingScript>( |
| element_, module_tree_client, is_external_script_); |
| break; |
| } |
| } |
| } |
| |
| DCHECK_NE(GetScriptType(), ScriptLoader::ScriptTypeAtPrepare::kImportMap); |
| |
| DCHECK(prepared_pending_script_); |
| |
| // <spec step="26">Then, follow the first of the following options that |
| // describes the situation:</spec> |
| |
| // Three flags are used to instruct the caller of prepareScript() to execute |
| // a part of Step 25, when |m_willBeParserExecuted| is true: |
| // - |m_willBeParserExecuted| |
| // - |m_willExecuteWhenDocumentFinishedParsing| |
| // - |m_readyToBeParserExecuted| |
| // TODO(hiroshige): Clean up the dependency. |
| |
| // <spec step="26.A">If the script's type is "classic", and the element has a |
| // src attribute, and the element has a defer attribute, and the element has |
| // been flagged as "parser-inserted", and the element does not have an async |
| // attribute |
| // |
| // If the script's type is "module", and the element has been flagged as |
| // "parser-inserted", and the element does not have an async attribute |
| // ...</spec> |
| if ((GetScriptType() == ScriptTypeAtPrepare::kClassic && |
| element_->HasSourceAttribute() && element_->DeferAttributeValue() && |
| parser_inserted_ && !element_->AsyncAttributeValue()) || |
| (GetScriptType() == ScriptTypeAtPrepare::kModule && parser_inserted_ && |
| !element_->AsyncAttributeValue())) { |
| // This clause is implemented by the caller-side of prepareScript(): |
| // - HTMLParserScriptRunner::requestDeferredScript(), and |
| // - TODO(hiroshige): Investigate XMLDocumentParser::endElementNs() |
| will_execute_when_document_finished_parsing_ = true; |
| will_be_parser_executed_ = true; |
| |
| return true; |
| } |
| |
| // Check for external script that should be force deferred. |
| if (GetScriptType() == ScriptTypeAtPrepare::kClassic && |
| element_->HasSourceAttribute() && |
| context_window->GetFrame()->ShouldForceDeferScript() && |
| IsA<HTMLDocument>(context_window->document()) && parser_inserted_ && |
| !element_->AsyncAttributeValue()) { |
| // In terms of ScriptLoader flags, force deferred scripts behave like |
| // parser-blocking scripts, except that |force_deferred_| is set. |
| // The caller of PrepareScript() |
| // - Force-defers such scripts if the caller supports force-defer |
| // (i.e., HTMLParserScriptRunner); or |
| // - Ignores the |force_deferred_| flag and handles such scripts as |
| // parser-blocking scripts (e.g., XMLParserScriptRunner). |
| force_deferred_ = true; |
| will_be_parser_executed_ = true; |
| |
| return true; |
| } |
| |
| // <spec step="26.B">If the script's type is "classic", and the element has a |
| // src attribute, and the element has been flagged as "parser-inserted", and |
| // the element does not have an async attribute ...</spec> |
| if (GetScriptType() == ScriptTypeAtPrepare::kClassic && |
| element_->HasSourceAttribute() && parser_inserted_ && |
| !element_->AsyncAttributeValue()) { |
| // This clause is implemented by the caller-side of prepareScript(): |
| // - HTMLParserScriptRunner::requestParsingBlockingScript() |
| // - TODO(hiroshige): Investigate XMLDocumentParser::endElementNs() |
| will_be_parser_executed_ = true; |
| |
| return true; |
| } |
| |
| // <spec step="26.C">If the script's type is "classic", and the element has a |
| // src attribute, and the element does not have an async attribute, and the |
| // element does not have the "non-blocking" flag set |
| // |
| // If the script's type is "module", and the element does not have an async |
| // attribute, and the element does not have the "non-blocking" flag set |
| // ...</spec> |
| if ((GetScriptType() == ScriptTypeAtPrepare::kClassic && |
| element_->HasSourceAttribute() && !element_->AsyncAttributeValue() && |
| !non_blocking_) || |
| (GetScriptType() == ScriptTypeAtPrepare::kModule && |
| !element_->AsyncAttributeValue() && !non_blocking_)) { |
| // <spec step="26.C">... Add the element to the end of the list of scripts |
| // that will execute in order as soon as possible associated with the node |
| // document of the script element at the time the prepare a script algorithm |
| // started. ...</spec> |
| pending_script_ = TakePendingScript(ScriptSchedulingType::kInOrder); |
| // TODO(hiroshige): Here the context document is used as "node document" |
| // while Step 14 uses |elementDocument| as "node document". Fix this. |
| context_window->document()->GetScriptRunner()->QueueScriptForExecution( |
| pending_script_); |
| // Note that watchForLoad can immediately call pendingScriptFinished. |
| pending_script_->WatchForLoad(this); |
| // The part "When the script is ready..." is implemented in |
| // ScriptRunner::notifyScriptReady(). |
| // TODO(hiroshige): Annotate it. |
| |
| return true; |
| } |
| |
| // <spec step="26.D">If the script's type is "classic", and the element has a |
| // src attribute |
| // |
| // If the script's type is "module" ...</spec> |
| if ((GetScriptType() == ScriptTypeAtPrepare::kClassic && |
| element_->HasSourceAttribute()) || |
| GetScriptType() == ScriptTypeAtPrepare::kModule) { |
| // <spec step="26.D">... The element must be added to the set of scripts |
| // that will execute as soon as possible of the node document of the script |
| // element at the time the prepare a script algorithm started. When the |
| // script is ready, execute the script block and then remove the element |
| // from the set of scripts that will execute as soon as possible.</spec> |
| pending_script_ = TakePendingScript(ScriptSchedulingType::kAsync); |
| // This is for the UKM count of async scripts in a document. |
| context_window->document()->IncrementAsyncScriptCount(); |
| // TODO(hiroshige): Here the context document is used as "node document" |
| // while Step 14 uses |elementDocument| as "node document". Fix this. |
| context_window->document()->GetScriptRunner()->QueueScriptForExecution( |
| pending_script_); |
| // Note that watchForLoad can immediately call pendingScriptFinished. |
| pending_script_->WatchForLoad(this); |
| // The part "When the script is ready..." is implemented in |
| // ScriptRunner::notifyScriptReady(). |
| // TODO(hiroshige): Annotate it. |
| |
| return true; |
| } |
| |
| // The following clauses are executed only if the script's type is "classic" |
| // and the element doesn't have a src attribute. |
| DCHECK_EQ(GetScriptType(), ScriptTypeAtPrepare::kClassic); |
| DCHECK(!is_external_script_); |
| |
| // Check for inline script that should be force deferred. |
| if (context_window->GetFrame()->ShouldForceDeferScript() && |
| IsA<HTMLDocument>(context_window->document()) && parser_inserted_) { |
| force_deferred_ = true; |
| will_be_parser_executed_ = true; |
| return true; |
| } |
| |
| // <spec step="26.E">If the element does not have a src attribute, and the |
| // element has been flagged as "parser-inserted", and either the parser that |
| // created the script is an XML parser or it's an HTML parser whose script |
| // nesting level is not greater than one, and the Document of the HTML parser |
| // or XML parser that created the script element has a style sheet that is |
| // blocking scripts ...</spec> |
| // |
| // <spec step="26.E">... has a style sheet that is blocking scripts ...</spec> |
| // |
| // is implemented in Document::isScriptExecutionReady(). |
| // Part of the condition check is done in |
| // HTMLParserScriptRunner::processScriptElementInternal(). |
| // TODO(hiroshige): Clean up the split condition check. |
| if (!element_->HasSourceAttribute() && parser_inserted_ && |
| !element_document.IsScriptExecutionReady()) { |
| // The former part of this clause is |
| // implemented by the caller-side of prepareScript(): |
| // - HTMLParserScriptRunner::requestParsingBlockingScript() |
| // - TODO(hiroshige): Investigate XMLDocumentParser::endElementNs() |
| will_be_parser_executed_ = true; |
| // <spec step="26.E">... Set the element's "ready to be parser-executed" |
| // flag. ...</spec> |
| ready_to_be_parser_executed_ = true; |
| |
| return true; |
| } |
| |
| // <spec step="26.F">Otherwise |
| // |
| // Immediately execute the script block, even if other scripts are already |
| // executing.</spec> |
| // |
| // Note: this block is also duplicated in |
| // HTMLParserScriptRunner::processScriptElementInternal(). |
| // TODO(hiroshige): Merge the duplicated code. |
| KURL script_url = (!is_in_document_write && parser_inserted_) |
| ? element_document.Url() |
| : KURL(); |
| TakePendingScript(ScriptSchedulingType::kImmediate) |
| ->ExecuteScriptBlock(script_url); |
| return true; |
| } |
| |
| // https://html.spec.whatwg.org/C/#fetch-a-classic-script |
| void ScriptLoader::FetchClassicScript(const KURL& url, |
| Document& document, |
| const ScriptFetchOptions& options, |
| CrossOriginAttributeValue cross_origin, |
| const WTF::TextEncoding& encoding) { |
| FetchParameters::DeferOption defer = FetchParameters::kNoDefer; |
| if (!parser_inserted_ || element_->AsyncAttributeValue() || |
| element_->DeferAttributeValue()) { |
| defer = FetchParameters::kLazyLoad; |
| } |
| ClassicPendingScript* pending_script = ClassicPendingScript::Fetch( |
| url, document, options, cross_origin, encoding, element_, defer); |
| prepared_pending_script_ = pending_script; |
| resource_keep_alive_ = pending_script->GetResource(); |
| } |
| |
| // <specdef href="https://html.spec.whatwg.org/C/#prepare-a-script"> |
| void ScriptLoader::FetchModuleScriptTree( |
| const KURL& url, |
| ResourceFetcher* fetch_client_settings_object_fetcher, |
| Modulator* modulator, |
| const ScriptFetchOptions& options) { |
| // <spec step="24.6.B">"module" |
| // |
| // Fetch an external module script graph given url, settings object, and |
| // options.</spec> |
| auto* module_tree_client = |
| MakeGarbageCollected<ModulePendingScriptTreeClient>(); |
| modulator->FetchTree(url, ModuleType::kJavaScript, |
| fetch_client_settings_object_fetcher, |
| mojom::blink::RequestContextType::SCRIPT, |
| network::mojom::RequestDestination::kScript, options, |
| ModuleScriptCustomFetchType::kNone, module_tree_client); |
| prepared_pending_script_ = MakeGarbageCollected<ModulePendingScript>( |
| element_, module_tree_client, is_external_script_); |
| } |
| |
| PendingScript* ScriptLoader::TakePendingScript( |
| ScriptSchedulingType scheduling_type) { |
| CHECK(prepared_pending_script_); |
| |
| PendingScript* pending_script = prepared_pending_script_; |
| prepared_pending_script_ = nullptr; |
| pending_script->SetSchedulingType(scheduling_type); |
| return pending_script; |
| } |
| |
| void ScriptLoader::PendingScriptFinished(PendingScript* pending_script) { |
| DCHECK(!will_be_parser_executed_); |
| DCHECK_EQ(pending_script_, pending_script); |
| DCHECK(pending_script->IsControlledByScriptRunner()); |
| DCHECK(pending_script_->GetSchedulingType() == ScriptSchedulingType::kAsync || |
| pending_script_->GetSchedulingType() == |
| ScriptSchedulingType::kInOrder); |
| // Historically we clear |resource_keep_alive_| when the scheduling type is |
| // kAsync or kInOrder (crbug.com/778799). But if the script resource was |
| // served via signed exchange, the script may not be in the HTTPCache, and |
| // therefore will need to be refetched over network if it's evicted from the |
| // memory cache. So we keep |resource_keep_alive_| to keep the resource in the |
| // memory cache. |
| if (resource_keep_alive_ && |
| !resource_keep_alive_->GetResponse().IsSignedExchangeInnerResponse() && |
| !base::FeatureList::IsEnabled( |
| blink::features::kKeepScriptResourceAlive)) { |
| resource_keep_alive_ = nullptr; |
| } |
| |
| if (!element_->GetExecutionContext()) { |
| DetachPendingScript(); |
| return; |
| } |
| |
| LocalDOMWindow* context_window = |
| To<LocalDOMWindow>(element_->GetExecutionContext()); |
| context_window->document()->GetScriptRunner()->NotifyScriptReady( |
| pending_script); |
| pending_script_->StopWatchingForLoad(); |
| pending_script_ = nullptr; |
| } |
| |
| bool ScriptLoader::IgnoresLoadRequest() const { |
| return already_started_ || is_external_script_ || parser_inserted_ || |
| !element_->IsConnected(); |
| } |
| |
| // <specdef href="https://html.spec.whatwg.org/C/#prepare-a-script"> |
| bool ScriptLoader::IsScriptForEventSupported() const { |
| // <spec step="14.1">Let for be the value of the for attribute.</spec> |
| String event_attribute = element_->EventAttributeValue(); |
| // <spec step="14.2">Let event be the value of the event attribute.</spec> |
| String for_attribute = element_->ForAttributeValue(); |
| |
| // <spec step="14">If the script element has an event attribute and a for |
| // attribute, and the script's type is "classic", then:</spec> |
| if (GetScriptType() != ScriptTypeAtPrepare::kClassic || |
| event_attribute.IsNull() || for_attribute.IsNull()) |
| return true; |
| |
| // <spec step="14.3">Strip leading and trailing ASCII whitespace from event |
| // and for.</spec> |
| for_attribute = for_attribute.StripWhiteSpace(); |
| // <spec step="14.4">If for is not an ASCII case-insensitive match for the |
| // string "window", then return. The script is not executed.</spec> |
| if (!EqualIgnoringASCIICase(for_attribute, "window")) |
| return false; |
| event_attribute = event_attribute.StripWhiteSpace(); |
| // <spec step="14.5">If event is not an ASCII case-insensitive match for |
| // either the string "onload" or the string "onload()", then return. The |
| // script is not executed.</spec> |
| return EqualIgnoringASCIICase(event_attribute, "onload") || |
| EqualIgnoringASCIICase(event_attribute, "onload()"); |
| } |
| |
| PendingScript* |
| ScriptLoader::GetPendingScriptIfControlledByScriptRunnerForCrossDocMove() { |
| DCHECK(!pending_script_ || pending_script_->IsControlledByScriptRunner()); |
| return pending_script_; |
| } |
| |
| String ScriptLoader::GetScriptText() const { |
| // Step 3 of |
| // https://w3c.github.io/webappsec-trusted-types/dist/spec/#abstract-opdef-prepare-the-script-url-and-text |
| // called from ยง 4.1.3.3, step 4 of |
| // https://w3c.github.io/webappsec-trusted-types/dist/spec/#slot-value-verification |
| // This will return the [[ScriptText]] internal slot value after that step, |
| // or a null string if the the Trusted Type algorithm threw an error. |
| String child_text_content = element_->ChildTextContent(); |
| DCHECK(!child_text_content.IsNull()); |
| String script_text_internal_slot = element_->ScriptTextInternalSlot(); |
| if (child_text_content == script_text_internal_slot) |
| return child_text_content; |
| return GetStringForScriptExecution(child_text_content, |
| element_->GetScriptElementType(), |
| element_->GetExecutionContext()); |
| } |
| |
| } // namespace blink |