| /* |
| * 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 "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_frame.h" |
| #include "third_party/blink/renderer/core/frame/use_counter.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/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/modulator.h" |
| #include "third_party/blink/renderer/core/script/module_pending_script.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/platform/bindings/parkable_string.h" |
| #include "third_party/blink/renderer/platform/feature_policy/feature_policy.h" |
| #include "third_party/blink/renderer/platform/histogram.h" |
| #include "third_party/blink/renderer/platform/loader/fetch/access_control_status.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, |
| bool parser_inserted, |
| bool already_started) |
| : element_(element), |
| will_be_parser_executed_(false), |
| will_execute_when_document_finished_parsing_(false) { |
| // <spec |
| // href="https://html.spec.whatwg.org/multipage/scripting.html#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 (already_started) |
| already_started_ = true; |
| |
| if (parser_inserted) { |
| // <spec |
| // href="https://html.spec.whatwg.org/multipage/scripting.html#parser-inserted"> |
| // ... It is set by the HTML parser and the XML parser on script elements |
| // they insert ...</spec> |
| parser_inserted_ = true; |
| |
| // <spec |
| // href="https://html.spec.whatwg.org/multipage/scripting.html#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(blink::Visitor* visitor) { |
| visitor->Trace(element_); |
| 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. |
| } |
| |
| void ScriptLoader::HandleAsyncAttribute() { |
| // <spec |
| // href="https://html.spec.whatwg.org/multipage/scripting.html#non-blocking"> |
| // ... 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 layout 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; |
| } |
| |
| } // namespace |
| |
| // https://html.spec.whatwg.org/multipage/scripting.html#prepare-a-script |
| bool ScriptLoader::IsValidScriptTypeAndLanguage( |
| const String& type, |
| const String& language, |
| LegacyTypeSupport support_legacy_types, |
| ScriptType& out_script_type) { |
| 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. |
| out_script_type = ScriptType::kClassic; |
| return true; |
| } |
| |
| if (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> |
| out_script_type = ScriptType::kModule; |
| return true; |
| } |
| |
| // <spec step="7">... If neither of the above conditions are true, then |
| // return. No script is executed.</spec> |
| return false; |
| } |
| |
| bool ScriptLoader::BlockForNoModule(ScriptType script_type, bool nomodule) { |
| return nomodule && script_type == ScriptType::kClassic; |
| } |
| |
| // Step 16 of |
| // https://html.spec.whatwg.org/multipage/scripting.html#prepare-a-script |
| network::mojom::FetchCredentialsMode ScriptLoader::ModuleScriptCredentialsMode( |
| CrossOriginAttributeValue cross_origin) { |
| switch (cross_origin) { |
| case kCrossOriginAttributeNotSet: |
| return network::mojom::FetchCredentialsMode::kOmit; |
| case kCrossOriginAttributeAnonymous: |
| return network::mojom::FetchCredentialsMode::kSameOrigin; |
| case kCrossOriginAttributeUseCredentials: |
| return network::mojom::FetchCredentialsMode::kInclude; |
| } |
| NOTREACHED(); |
| return network::mojom::FetchCredentialsMode::kOmit; |
| } |
| |
| // https://github.com/WICG/feature-policy/issues/135 |
| bool ShouldBlockSyncScriptForFeaturePolicy(const ScriptElementBase* element, |
| ScriptType script_type, |
| bool parser_inserted) { |
| if (element->GetDocument().GetFeaturePolicy()->IsFeatureEnabled( |
| mojom::FeaturePolicyFeature::kSyncScript)) { |
| return false; |
| } |
| |
| // Module scripts never block parsing. |
| if (script_type == ScriptType::kModule || !parser_inserted) |
| return false; |
| |
| if (!element->HasSourceAttribute()) |
| return true; |
| return !element->DeferAttributeValue() && !element->AsyncAttributeValue(); |
| } |
| |
| // https://html.spec.whatwg.org/multipage/scripting.html#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> |
| const String source_text = element_->TextFromChildren(); |
| |
| // <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; |
| |
| // <spec step="7">... Determine the script's type as follows: ...</spec> |
| // |
| // |script_type_| is set here. |
| |
| if (!IsValidScriptTypeAndLanguage(element_->TypeAttributeValue(), |
| element_->LanguageAttributeValue(), |
| support_legacy_types, script_type_)) { |
| return false; |
| } |
| |
| // <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> |
| // |
| // FIXME: If script is parser inserted, verify it's still in the original |
| // document. |
| |
| // <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/multipage/webappapis.html#concept-n-noscript"> |
| // Scripting is disabled for a node if [the node's node document has no |
| // browsing context], or if scripting is disabled in that browsing context. |
| // </spec> |
| Document& element_document = element_->GetDocument(); |
| // TODO(timothygu): Investigate if we could switch from ExecutingFrame() to |
| // ExecutingWindow(). |
| if (!element_document.ExecutingFrame()) |
| return false; |
| |
| Document* context_document = element_document.ContextDocument(); |
| if (!context_document || !context_document->ExecutingFrame()) |
| return false; |
| if (!context_document->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(script_type_, element_->NomoduleAttributeValue())) |
| return false; |
| |
| // 13. |
| if (!IsScriptForEventSupported()) |
| return false; |
| |
| // This FeaturePolicy is still in the process of being added to the spec. |
| if (ShouldBlockSyncScriptForFeaturePolicy(element_.Get(), GetScriptType(), |
| parser_inserted_)) { |
| element_document.AddConsoleMessage(ConsoleMessage::Create( |
| kJSMessageSource, kErrorMessageLevel, |
| "Synchronous script execution is disabled by Feature 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::FetchCredentialsMode 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_document); |
| SubresourceIntegrity::ReportInfo report_info; |
| SubresourceIntegrity::ParseIntegrityAttribute( |
| integrity_attr, integrity_features, integrity_metadata, &report_info); |
| SubresourceIntegrityHelper::DoReport(element_document, 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(); |
| ReferrerPolicy referrer_policy = kReferrerPolicyDefault; |
| if (!referrerpolicy_attr.IsEmpty()) { |
| SecurityPolicy::ReferrerPolicyFromString( |
| referrerpolicy_attr, kDoNotSupportReferrerPolicyLegacyKeywords, |
| &referrer_policy); |
| } |
| |
| // <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() == ScriptType::kModule) |
| UseCounter::Count(*context_document, WebFeature::kPrepareModuleScript); |
| |
| DCHECK(!prepared_pending_script_); |
| |
| // 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="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); |
| |
| // <spec step="23">Let settings object be the element's node document's Window |
| // object's environment settings object.</spec> |
| // |
| // Note: We use |element_document| as "settings object" in the steps below. |
| auto* settings_object = |
| element_document.CreateFetchClientSettingsObjectSnapshot(); |
| |
| // <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> |
| if (GetScriptType() == ScriptType::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> |
| FetchClassicScript(url, element_document, options, encoding); |
| } else { |
| // - "module": |
| |
| // Step 15 is skipped because they are not used in module |
| // scripts. |
| |
| // <spec step="24.6.B">"module" |
| // |
| // Fetch a module script graph given url, settings object, "script", and |
| // options.</spec> |
| Modulator* modulator = Modulator::From( |
| ToScriptStateForMainWorld(context_document->GetFrame())); |
| FetchModuleScriptTree(url, settings_object, 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". |
| } |
| |
| // <spec step="25">If the element does not have a src content attribute, run |
| // these substeps:</spec> |
| if (!element_->HasSourceAttribute()) { |
| // <spec step="25.1">Let src be the value of the element's src |
| // attribute.</spec> |
| // |
| // This step is done later as ScriptElementBase::TextFromChildren(): |
| // - 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()) { |
| // Step 25.2.A. "classic" [spec text] |
| case ScriptType::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, 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; |
| } |
| |
| // Step 25.2.B. "module" [spec text] |
| case ScriptType::kModule: { |
| // <spec step="25.2.B.1">Let script be the result of creating a module |
| // script using source text, settings object, base URL, and |
| // options.</spec> |
| const KURL& source_url = element_document.Url(); |
| Modulator* modulator = Modulator::From( |
| ToScriptStateForMainWorld(context_document->GetFrame())); |
| ModuleScript* module_script = ModuleScript::Create( |
| ParkableString(element_->TextFromChildren().Impl()), modulator, |
| source_url, base_url, options, kSharableCrossOrigin, position); |
| |
| // <spec step="25.2.B.2">If this returns null, set the script's script |
| // to null and return; the script is ready.</spec> |
| if (!module_script) |
| return false; |
| |
| // <spec step="25.2.B.3">Fetch the descendants of and instantiate |
| // script, given the destination "script". When this asynchronously |
| // completes, set the script's script to the result. At that time, the |
| // script is ready.</spec> |
| auto* module_tree_client = ModulePendingScriptTreeClient::Create(); |
| modulator->FetchDescendantsForInlineScript( |
| module_script, settings_object, |
| WebURLRequest::kRequestContextScript, module_tree_client); |
| prepared_pending_script_ = ModulePendingScript::Create( |
| element_, module_tree_client, is_external_script_); |
| break; |
| } |
| } |
| } |
| |
| 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() == ScriptType::kClassic && |
| element_->HasSourceAttribute() && element_->DeferAttributeValue() && |
| parser_inserted_ && !element_->AsyncAttributeValue()) || |
| (GetScriptType() == ScriptType::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; |
| } |
| |
| // <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() == ScriptType::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() == ScriptType::kClassic && |
| element_->HasSourceAttribute() && !element_->AsyncAttributeValue() && |
| !non_blocking_) || |
| (GetScriptType() == ScriptType::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 |contextDocument| is used as "node document" |
| // while Step 14 uses |elementDocument| as "node document". Fix this. |
| context_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() == ScriptType::kClassic && |
| element_->HasSourceAttribute()) || |
| GetScriptType() == ScriptType::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); |
| // TODO(hiroshige): Here |contextDocument| is used as "node document" |
| // while Step 14 uses |elementDocument| as "node document". Fix this. |
| context_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(), ScriptType::kClassic); |
| DCHECK(!is_external_script_); |
| |
| // <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> |
| // |
| // The last part "... has a style sheet that is blocking scripts" |
| // 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/multipage/webappapis.html#fetch-a-classic-script |
| void ScriptLoader::FetchClassicScript(const KURL& url, |
| Document& element_document, |
| const ScriptFetchOptions& options, |
| 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, element_document, options, encoding, element_, defer); |
| prepared_pending_script_ = pending_script; |
| resource_keep_alive_ = pending_script->GetResource(); |
| } |
| |
| void ScriptLoader::FetchModuleScriptTree( |
| const KURL& url, |
| FetchClientSettingsObjectSnapshot* settings_object, |
| Modulator* modulator, |
| const ScriptFetchOptions& options) { |
| // <spec |
| // href="https://html.spec.whatwg.org/multipage/scripting.html#prepare-a-script" |
| // step="23.6.B">"module" |
| // |
| // Fetch a module script graph given url, settings object, "script", and |
| // options.</spec> |
| auto* module_tree_client = ModulePendingScriptTreeClient::Create(); |
| modulator->FetchTree(url, settings_object, |
| WebURLRequest::kRequestContextScript, options, |
| ModuleScriptCustomFetchType::kNone, module_tree_client); |
| prepared_pending_script_ = ModulePendingScript::Create( |
| element_, module_tree_client, is_external_script_); |
| } |
| |
| PendingScript* ScriptLoader::TakePendingScript( |
| ScriptSchedulingType scheduling_type) { |
| CHECK(prepared_pending_script_); |
| |
| DEFINE_STATIC_LOCAL( |
| EnumerationHistogram, scheduling_type_histogram, |
| ("Blink.Script.SchedulingType", kLastScriptSchedulingType + 1)); |
| scheduling_type_histogram.Count(static_cast<int>(scheduling_type)); |
| |
| switch (scheduling_type) { |
| case ScriptSchedulingType::kAsync: |
| case ScriptSchedulingType::kInOrder: |
| // As ClassicPendingScript keeps a reference to ScriptResource, |
| // the ScriptResource is anyway kept alive until evaluation, |
| // and can be garbage-collected after that (together with |
| // ClassicPendingScript). |
| resource_keep_alive_ = nullptr; |
| break; |
| |
| default: |
| // ScriptResource is kept alive by resource_keep_alive_ |
| // until ScriptLoader is garbage collected. |
| break; |
| } |
| |
| 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_EQ(pending_script_->GetScriptType(), GetScriptType()); |
| DCHECK(pending_script->IsControlledByScriptRunner()); |
| |
| Document* context_document = element_->GetDocument().ContextDocument(); |
| if (!context_document) { |
| DetachPendingScript(); |
| return; |
| } |
| |
| context_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(); |
| } |
| |
| // https://html.spec.whatwg.org/multipage/scripting.html#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() != ScriptType::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 (!DeprecatedEqualIgnoringCase(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 DeprecatedEqualIgnoringCase(event_attribute, "onload") || |
| DeprecatedEqualIgnoringCase(event_attribute, "onload()"); |
| } |
| |
| PendingScript* |
| ScriptLoader::GetPendingScriptIfControlledByScriptRunnerForCrossDocMove() { |
| DCHECK(!pending_script_ || pending_script_->IsControlledByScriptRunner()); |
| return pending_script_; |
| } |
| |
| } // namespace blink |