| /* |
| * 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 "core/dom/ScriptLoader.h" |
| |
| #include "bindings/core/v8/ScriptController.h" |
| #include "bindings/core/v8/ScriptSourceCode.h" |
| #include "bindings/core/v8/V8BindingForCore.h" |
| #include "core/HTMLNames.h" |
| #include "core/SVGNames.h" |
| #include "core/dom/ClassicPendingScript.h" |
| #include "core/dom/ClassicScript.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/DocumentParserTiming.h" |
| #include "core/dom/DocumentWriteIntervention.h" |
| #include "core/dom/IgnoreDestructiveWriteCountIncrementer.h" |
| #include "core/dom/Modulator.h" |
| #include "core/dom/ModulePendingScript.h" |
| #include "core/dom/Script.h" |
| #include "core/dom/ScriptElementBase.h" |
| #include "core/dom/ScriptRunner.h" |
| #include "core/dom/ScriptableDocumentParser.h" |
| #include "core/dom/Text.h" |
| #include "core/events/Event.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/SubresourceIntegrity.h" |
| #include "core/frame/csp/ContentSecurityPolicy.h" |
| #include "core/html/imports/HTMLImport.h" |
| #include "core/html/parser/HTMLParserIdioms.h" |
| #include "core/inspector/ConsoleMessage.h" |
| #include "core/loader/modulescript/ModuleScriptFetchRequest.h" |
| #include "core/loader/resource/ScriptResource.h" |
| #include "platform/WebFrameScheduler.h" |
| #include "platform/loader/fetch/AccessControlStatus.h" |
| #include "platform/loader/fetch/FetchParameters.h" |
| #include "platform/loader/fetch/ResourceFetcher.h" |
| #include "platform/network/mime/MIMETypeRegistry.h" |
| #include "platform/weborigin/SecurityOrigin.h" |
| #include "platform/wtf/StdLibExtras.h" |
| #include "platform/wtf/text/StringBuilder.h" |
| #include "platform/wtf/text/StringHash.h" |
| #include "public/platform/WebCachePolicy.h" |
| |
| namespace blink { |
| |
| ScriptLoader::ScriptLoader(ScriptElementBase* element, |
| bool parser_inserted, |
| bool already_started, |
| bool created_during_document_write) |
| : element_(element), |
| start_line_number_(WTF::OrdinalNumber::BeforeFirst()), |
| have_fired_load_(false), |
| will_be_parser_executed_(false), |
| will_execute_when_document_finished_parsing_(false), |
| created_during_document_write_(created_during_document_write), |
| async_exec_type_(ScriptRunner::kNone), |
| document_write_intervention_( |
| DocumentWriteIntervention::kDocumentWriteInterventionNone) { |
| // https://html.spec.whatwg.org/#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." |
| // TODO(hiroshige): Cloning is implemented together with |
| // {HTML,SVG}ScriptElement::cloneElementWithoutAttributesAndChildren(). |
| // Clean up these later. |
| if (already_started) |
| already_started_ = true; |
| |
| if (parser_inserted) { |
| // https://html.spec.whatwg.org/#parser-inserted |
| // "It is set by the HTML parser and the XML parser |
| // on script elements they insert" |
| parser_inserted_ = true; |
| |
| // https://html.spec.whatwg.org/#non-blocking |
| // "It is unset by the HTML parser and the XML parser |
| // on script elements they insert." |
| non_blocking_ = false; |
| } |
| |
| if (parser_inserted && |
| element_->GetDocument().GetScriptableDocumentParser() && |
| !element_->GetDocument().IsInDocumentWrite()) { |
| start_line_number_ = |
| element_->GetDocument().GetScriptableDocumentParser()->LineNumber(); |
| } |
| } |
| |
| ScriptLoader::~ScriptLoader() {} |
| |
| DEFINE_TRACE(ScriptLoader) { |
| visitor->Trace(element_); |
| visitor->Trace(resource_); |
| visitor->Trace(pending_script_); |
| visitor->Trace(module_tree_client_); |
| PendingScriptClient::Trace(visitor); |
| } |
| |
| void ScriptLoader::SetFetchDocWrittenScriptDeferIdle() { |
| DCHECK(!created_during_document_write_); |
| document_write_intervention_ = |
| DocumentWriteIntervention::kFetchDocWrittenScriptDeferIdle; |
| } |
| |
| 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() { |
| // https://html.spec.whatwg.org/#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." |
| non_blocking_ = false; |
| } |
| |
| void ScriptLoader::DetachPendingScript() { |
| if (!pending_script_) |
| return; |
| pending_script_->Dispose(); |
| pending_script_ = nullptr; |
| } |
| |
| void ScriptLoader::DispatchErrorEvent() { |
| element_->DispatchErrorEvent(); |
| } |
| |
| void ScriptLoader::DispatchLoadEvent() { |
| element_->DispatchLoadEvent(); |
| SetHaveFiredLoadEvent(true); |
| } |
| |
| 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 |
| |
| // Step 6 of https://html.spec.whatwg.org/#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)) { |
| // - "If the script block's type string is an ASCII case-insensitive match |
| // for any JavaScript MIME type, the script's type is "classic"." |
| // TODO(hiroshige): Annotate and/or cleanup this step. |
| out_script_type = ScriptType::kClassic; |
| return true; |
| } |
| |
| if (RuntimeEnabledFeatures::moduleScriptsEnabled() && type == "module") { |
| // - "If the script block's type string is an ASCII case-insensitive match |
| // for the string "module", the script's type is "module"." |
| out_script_type = ScriptType::kModule; |
| return true; |
| } |
| |
| // - "If neither of the above conditions are true, then abort these steps |
| // at this point. No script is executed." |
| return false; |
| } |
| |
| bool ScriptLoader::BlockForNoModule(ScriptType script_type, bool nomodule) { |
| return nomodule && script_type == ScriptType::kClassic && |
| RuntimeEnabledFeatures::moduleScriptsEnabled(); |
| } |
| |
| bool ScriptLoader::IsScriptTypeSupported(LegacyTypeSupport support_legacy_types, |
| ScriptType& out_script_type) const { |
| return IsValidScriptTypeAndLanguage(element_->TypeAttributeValue(), |
| element_->LanguageAttributeValue(), |
| support_legacy_types, out_script_type); |
| } |
| |
| // https://html.spec.whatwg.org/#prepare-a-script |
| bool ScriptLoader::PrepareScript(const TextPosition& script_start_position, |
| LegacyTypeSupport support_legacy_types) { |
| // 1. "If the script element is marked as having "already started", then |
| // abort these steps at this point. The script is not executed." |
| if (already_started_) |
| return false; |
| |
| // 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." |
| bool was_parser_inserted; |
| if (parser_inserted_) { |
| was_parser_inserted = true; |
| parser_inserted_ = false; |
| } else { |
| was_parser_inserted = false; |
| } |
| |
| // 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." |
| if (was_parser_inserted && !element_->AsyncAttributeValue()) |
| non_blocking_ = true; |
| |
| // 4. "If the element has no src attribute, and its child nodes, if any, |
| // consist only of comment nodes and empty Text nodes, |
| // then abort these steps at this point. The script is not executed." |
| // FIXME: HTML5 spec says we should check that all children are either |
| // comments or empty text nodes. |
| if (!element_->HasSourceAttribute() && !element_->HasChildren()) |
| return false; |
| |
| // 5. "If the element is not connected, then abort these steps. |
| // The script is not executed." |
| if (!element_->IsConnected()) |
| return false; |
| |
| // 6. "Determine the script's type as follows:" |
| // |script_type_| is set here. |
| if (!IsScriptTypeSupported(support_legacy_types, script_type_)) |
| return false; |
| |
| // 7. "If was-parser-inserted is true, |
| // then flag the element as "parser-inserted" again, |
| // and set the element's "non-blocking" flag to false." |
| if (was_parser_inserted) { |
| parser_inserted_ = true; |
| non_blocking_ = false; |
| } |
| |
| // 8. "Set the element's "already started" flag." |
| already_started_ = true; |
| |
| // 9. "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 abort these steps." |
| // FIXME: If script is parser inserted, verify it's still in the original |
| // document. |
| Document& element_document = element_->GetDocument(); |
| Document* context_document = element_document.ContextDocument(); |
| if (!element_document.ExecutingFrame()) |
| return false; |
| if (!context_document || !context_document->ExecutingFrame()) |
| return false; |
| |
| // 10. "If scripting is disabled for the script element, then abort these |
| // steps at this point. The script is not executed." |
| if (!context_document->CanExecuteScripts(kAboutToExecuteScript)) |
| return false; |
| |
| // 11. "If the script element has a nomodule content attribute |
| // and the script's type is "classic", then abort these steps. |
| // The script is not executed." |
| if (BlockForNoModule(script_type_, element_->NomoduleAttributeValue())) |
| return false; |
| |
| // 13. |
| if (!IsScriptForEventSupported()) |
| return false; |
| |
| // 14. is handled below. |
| |
| // 15. "Let CORS setting be the current state of the element's |
| // crossorigin content attribute." |
| CrossOriginAttributeValue cross_origin = |
| GetCrossOriginAttributeValue(element_->CrossOriginAttributeValue()); |
| |
| // 16. is handled below. |
| |
| // 17. "If the script element has a nonce attribute, |
| // then let cryptographic nonce be that attribute's value. |
| // Otherwise, let cryptographic nonce be the empty string." |
| String nonce; |
| if (element_->IsNonceableElement()) |
| nonce = element_->nonce(); |
| |
| // 18. is handled below. |
| |
| // 19. "Let parser state be "parser-inserted" |
| // if the script element has been flagged as "parser-inserted", |
| // and "not parser-inserted" otherwise." |
| ParserDisposition parser_state = |
| IsParserInserted() ? kParserInserted : kNotParserInserted; |
| |
| // 21. "If the element has a src content attribute, run these substeps:" |
| if (element_->HasSourceAttribute()) { |
| // 21.1. Let src be the value of the element's src attribute. |
| String src = |
| StripLeadingAndTrailingHTMLSpaces(element_->SourceAttributeValue()); |
| |
| // 21.2. "If src is the empty string, queue a task to |
| // fire an event named error at the element, and abort these steps." |
| if (src.IsEmpty()) { |
| // TODO(hiroshige): Make this asynchronous. Currently we fire the error |
| // event synchronously to keep the existing behavior. |
| DispatchErrorEvent(); |
| return false; |
| } |
| |
| // 21.3. "Set the element's from an external file flag." |
| is_external_script_ = true; |
| |
| // 21.4. "Parse src relative to the element's node document." |
| // TODO(hiroshige): Use CompleteURL(src) instead. |
| KURL url = element_document.CompleteURL(element_->SourceAttributeValue()); |
| |
| // 21.5. "If the previous step failed, queue a task to |
| // fire an event named error at the element, and abort these steps." |
| if (!url.IsValid()) { |
| // TODO(hiroshige): Make this asynchronous. Currently we fire the error |
| // event synchronously to keep the existing behavior. |
| DispatchErrorEvent(); |
| return false; |
| } |
| |
| DCHECK(!resource_); |
| DCHECK(!module_tree_client_); |
| |
| // 21.6. "Switch on the script's type:" |
| if (GetScriptType() == ScriptType::kClassic) { |
| // - "classic": |
| |
| // 14. "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." |
| // TODO(hiroshige): Should we handle failure in getting an encoding? |
| String encoding; |
| if (!element_->CharsetAttributeValue().IsEmpty()) |
| encoding = element_->CharsetAttributeValue(); |
| else |
| encoding = element_document.characterSet(); |
| |
| // Step 16 is skipped because "module script credentials" is not used |
| // for classic scripts. |
| |
| // 18. "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." |
| String integrity_attr = element_->IntegrityAttributeValue(); |
| IntegrityMetadataSet integrity_metadata; |
| if (!integrity_attr.IsEmpty()) { |
| SubresourceIntegrity::ParseIntegrityAttribute( |
| integrity_attr, integrity_metadata, &element_document); |
| } |
| |
| if (!FetchClassicScript(url, element_document.Fetcher(), nonce, |
| integrity_metadata, parser_state, cross_origin, |
| element_document.GetSecurityOrigin(), encoding)) { |
| // TODO(hiroshige): Make this asynchronous. Currently we fire the error |
| // event synchronously to keep the existing behavior. |
| DispatchErrorEvent(); |
| return false; |
| } |
| |
| DCHECK(resource_); |
| DCHECK(!module_tree_client_); |
| } else { |
| // - "module": |
| |
| // Steps 14 and 18 are skipped because they are not used in module |
| // scripts. |
| |
| // 16. "Let module script credentials mode be determined by switching |
| // on CORS setting:" |
| WebURLRequest::FetchCredentialsMode credentials_mode = |
| WebURLRequest::kFetchCredentialsModeOmit; |
| switch (cross_origin) { |
| case kCrossOriginAttributeNotSet: |
| credentials_mode = WebURLRequest::kFetchCredentialsModeOmit; |
| break; |
| case kCrossOriginAttributeAnonymous: |
| credentials_mode = WebURLRequest::kFetchCredentialsModeSameOrigin; |
| break; |
| case kCrossOriginAttributeUseCredentials: |
| credentials_mode = WebURLRequest::kFetchCredentialsModeInclude; |
| break; |
| } |
| |
| DCHECK(RuntimeEnabledFeatures::moduleScriptsEnabled()); |
| Modulator* modulator = Modulator::From( |
| ToScriptStateForMainWorld(element_document.GetFrame())); |
| FetchModuleScriptTree(url, modulator, nonce, parser_state, |
| credentials_mode); |
| |
| DCHECK(!resource_); |
| DCHECK(module_tree_client_); |
| } |
| |
| // "When the chosen algorithm asynchronously completes, set |
| // the script's script to the result. At that time, the script is ready." |
| // 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 23 of "prepare a script". |
| } |
| |
| // 22. "If the element does not have a src content attribute, |
| // run these substeps:" |
| if (!element_->HasSourceAttribute()) { |
| // 22.1. "Let source text be the value of the text IDL attribute." |
| // This step is done later: |
| // - in ScriptLoader::pendingScript() (Step 23, 6th Clause), |
| // as Element::textFromChildren() in ScriptLoader::scriptContent(), |
| // - in HTMLParserScriptRunner::processScriptElementInternal() |
| // (Duplicated code of Step 23, 6th Clause), |
| // as Element::textContent(), |
| // - in XMLDocumentParser::endElementNs() (Step 23, 5th Clause), |
| // as Element::textFromChildren() in ScriptLoader::scriptContent(), |
| // - PendingScript::getSource() (Indirectly used via |
| // HTMLParserScriptRunner::processScriptElementInternal(), |
| // Step 23, 5th Clause), |
| // as Element::textContent(). |
| // TODO(hiroshige): Make them merged or consistent. |
| |
| // 22.2. "Switch on the script's type:" |
| switch (GetScriptType()) { |
| // - "classic": |
| case ScriptType::kClassic: |
| // TODO(hiroshige): Clarify how Step 22.2 is implemented for "classic". |
| break; |
| |
| // - "module": |
| case ScriptType::kModule: |
| // TODO(hiroshige): Implement inline module scripts. |
| element_document.AddConsoleMessage(ConsoleMessage::Create( |
| kJSMessageSource, kErrorMessageLevel, |
| "Inline module script is not yet supported", |
| SourceLocation::Create(element_document.Url().GetString(), |
| script_start_position.line_.OneBasedInt(), |
| script_start_position.column_.OneBasedInt(), |
| nullptr))); |
| return false; |
| } |
| } |
| |
| // [Intervention] |
| // Since the asynchronous, low priority fetch for doc.written blocked |
| // script is not for execution, return early from here. Watch for its |
| // completion to be able to remove it from the memory cache. |
| if (GetScriptType() == ScriptType::kClassic && |
| document_write_intervention_ == |
| DocumentWriteIntervention::kFetchDocWrittenScriptDeferIdle) { |
| pending_script_ = CreatePendingScript(); |
| pending_script_->WatchForLoad(this); |
| return true; |
| } |
| |
| // 23. "Then, follow the first of the following options that describes the |
| // situation:" |
| |
| // Three flags are used to instruct the caller of prepareScript() to execute |
| // a part of Step 23, when |m_willBeParserExecuted| is true: |
| // - |m_willBeParserExecuted| |
| // - |m_willExecuteWhenDocumentFinishedParsing| |
| // - |m_readyToBeParserExecuted| |
| // TODO(hiroshige): Clean up the dependency. |
| |
| // 1st Clause: |
| // - "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" |
| 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; |
| } |
| |
| // 2nd Clause: |
| // - "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" |
| // TODO(hiroshige): Check the script's type. |
| 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; |
| } |
| |
| // 5th Clause: |
| // TODO(hiroshige): Reorder the clauses to match the spec. |
| // - "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" |
| // 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. |
| // We check that the type is "classic" here, because according to the spec |
| // a "module" script doesn't reach the 5th Clause because the 4th Clause |
| // catches all "module" scripts. |
| if (GetScriptType() == ScriptType::kClassic && |
| !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; |
| // "Set the element's "ready to be parser-executed" flag." |
| ready_to_be_parser_executed_ = true; |
| |
| return true; |
| } |
| |
| // 3rd Clause: |
| // - "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" |
| // TODO(hiroshige): Check the script's type and implement "module" case. |
| if ((GetScriptType() == ScriptType::kClassic && |
| element_->HasSourceAttribute() && !element_->AsyncAttributeValue() && |
| !non_blocking_) || |
| (GetScriptType() == ScriptType::kModule && |
| !element_->AsyncAttributeValue() && !non_blocking_)) { |
| // "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." |
| pending_script_ = CreatePendingScript(); |
| async_exec_type_ = ScriptRunner::kInOrder; |
| // TODO(hiroshige): Here |contextDocument| is used as "node document" |
| // while Step 14 uses |elementDocument| as "node document". Fix this. |
| context_document->GetScriptRunner()->QueueScriptForExecution( |
| this, async_exec_type_); |
| // 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; |
| } |
| |
| // 4th Clause: |
| // - "If the script's type is "classic", and the element has a src attribute" |
| // - "If the script's type is "module"" |
| if ((GetScriptType() == ScriptType::kClassic && |
| element_->HasSourceAttribute()) || |
| GetScriptType() == ScriptType::kModule) { |
| // "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." |
| pending_script_ = CreatePendingScript(); |
| async_exec_type_ = ScriptRunner::kAsync; |
| pending_script_->StartStreamingIfPossible(&element_->GetDocument(), |
| ScriptStreamer::kAsync); |
| // TODO(hiroshige): Here |contextDocument| is used as "node document" |
| // while Step 14 uses |elementDocument| as "node document". Fix this. |
| context_document->GetScriptRunner()->QueueScriptForExecution( |
| this, async_exec_type_); |
| // 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; |
| } |
| |
| // 6th Clause: |
| // - "Otherwise" |
| // "Immediately execute the script block, |
| // even if other scripts are already executing." |
| // Note: this block is also duplicated in |
| // HTMLParserScriptRunner::processScriptElementInternal(). |
| // TODO(hiroshige): Merge the duplicated code. |
| |
| // This clause is 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_); |
| |
| // Reset line numbering for nested writes. |
| TextPosition position = element_document.IsInDocumentWrite() |
| ? TextPosition() |
| : script_start_position; |
| KURL script_url = (!element_document.IsInDocumentWrite() && parser_inserted_) |
| ? element_document.Url() |
| : KURL(); |
| |
| if (!ExecuteScript(ClassicScript::Create( |
| ScriptSourceCode(ScriptContent(), script_url, position)))) { |
| DispatchErrorEvent(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool ScriptLoader::FetchClassicScript( |
| const KURL& url, |
| ResourceFetcher* fetcher, |
| const String& nonce, |
| const IntegrityMetadataSet& integrity_metadata, |
| ParserDisposition parser_state, |
| CrossOriginAttributeValue cross_origin, |
| SecurityOrigin* security_origin, |
| const String& encoding) { |
| // https://html.spec.whatwg.org/#prepare-a-script |
| // 21.6, "classic": |
| // "Fetch a classic script given url, settings, ..." |
| ResourceRequest resource_request(url); |
| |
| FetchParameters::DeferOption defer = FetchParameters::kNoDefer; |
| if (!parser_inserted_ || element_->AsyncAttributeValue() || |
| element_->DeferAttributeValue()) |
| defer = FetchParameters::kLazyLoad; |
| |
| // [Intervention] |
| if (document_write_intervention_ == |
| DocumentWriteIntervention::kFetchDocWrittenScriptDeferIdle) { |
| resource_request.SetHTTPHeaderField( |
| "Intervention", |
| "<https://www.chromestatus.com/feature/5718547946799104>"); |
| defer = FetchParameters::kIdleLoad; |
| } |
| |
| // [Intervention] |
| // For users on slow connections, we want to avoid blocking the parser in |
| // the main frame on script loads inserted via document.write, since it can |
| // add significant delays before page content is displayed on the screen. |
| if (MaybeDisallowFetchForDocWrittenScript(resource_request, defer, |
| element_->GetDocument())) { |
| document_write_intervention_ = |
| DocumentWriteIntervention::kDoNotFetchDocWrittenScript; |
| } |
| |
| FetchParameters params(resource_request, element_->InitiatorName()); |
| |
| // "... cryptographic nonce, ..." |
| params.SetContentSecurityPolicyNonce(nonce); |
| |
| // "... integrity metadata, ..." |
| params.SetIntegrityMetadata(integrity_metadata); |
| |
| // "... parser state, ..." |
| params.SetParserDisposition(parser_state); |
| |
| // "... CORS setting, ..." |
| if (cross_origin != kCrossOriginAttributeNotSet) { |
| params.SetCrossOriginAccessControl(security_origin, cross_origin); |
| } |
| |
| // "... and encoding." |
| params.SetCharset(encoding); |
| |
| // This DeferOption logic is only for classic scripts, as we always set |
| // |kLazyLoad| for module scripts in ModuleScriptLoader. |
| params.SetDefer(defer); |
| |
| resource_ = ScriptResource::Fetch(params, fetcher); |
| |
| if (!resource_) |
| return false; |
| |
| return true; |
| } |
| |
| void ScriptLoader::FetchModuleScriptTree( |
| const KURL& url, |
| Modulator* modulator, |
| const String& nonce, |
| ParserDisposition parser_state, |
| WebURLRequest::FetchCredentialsMode credentials_mode) { |
| // https://html.spec.whatwg.org/#prepare-a-script |
| // 21.6, "module": |
| // "Fetch a module script graph given url, settings, "script", |
| // cryptographic nonce, parser state, and |
| // module script credentials mode." |
| ModuleScriptFetchRequest module_request(url, nonce, parser_state, |
| credentials_mode); |
| |
| module_tree_client_ = ModulePendingScriptTreeClient::Create(); |
| |
| modulator->FetchTree(module_request, module_tree_client_); |
| } |
| |
| PendingScript* ScriptLoader::CreatePendingScript() { |
| switch (GetScriptType()) { |
| case ScriptType::kClassic: |
| CHECK(resource_); |
| return ClassicPendingScript::Create(element_, resource_); |
| case ScriptType::kModule: |
| CHECK(module_tree_client_); |
| return ModulePendingScript::Create(element_, module_tree_client_); |
| } |
| NOTREACHED(); |
| return nullptr; |
| } |
| |
| bool ScriptLoader::ExecuteScript(const Script* script) { |
| double script_exec_start_time = MonotonicallyIncreasingTime(); |
| bool result = DoExecuteScript(script); |
| |
| // NOTE: we do not check m_willBeParserExecuted here, since |
| // m_willBeParserExecuted is false for inline scripts, and we want to |
| // include inline script execution time as part of parser blocked script |
| // execution time. |
| if (async_exec_type_ == ScriptRunner::kNone) |
| DocumentParserTiming::From(element_->GetDocument()) |
| .RecordParserBlockedOnScriptExecutionDuration( |
| MonotonicallyIncreasingTime() - script_exec_start_time, |
| WasCreatedDuringDocumentWrite()); |
| return result; |
| } |
| |
| // https://html.spec.whatwg.org/#execute-the-script-block |
| // with additional support for HTML imports. |
| // Note that Steps 2 and 8 must be handled by the caller of doExecuteScript(), |
| // i.e. load/error events are dispatched by the caller. |
| // Steps 3--7 are implemented here in doExecuteScript(). |
| // TODO(hiroshige): Move event dispatching code to doExecuteScript(). |
| bool ScriptLoader::DoExecuteScript(const Script* script) { |
| DCHECK(already_started_); |
| CHECK_EQ(script->GetScriptType(), GetScriptType()); |
| |
| if (script->IsEmpty()) |
| return true; |
| |
| Document* element_document = &(element_->GetDocument()); |
| Document* context_document = element_document->ContextDocument(); |
| if (!context_document) |
| return true; |
| |
| LocalFrame* frame = context_document->GetFrame(); |
| if (!frame) |
| return true; |
| |
| if (!is_external_script_) { |
| const ContentSecurityPolicy* csp = |
| element_document->GetContentSecurityPolicy(); |
| bool should_bypass_main_world_csp = |
| (frame->GetScriptController().ShouldBypassMainWorldCSP()) || |
| csp->AllowScriptWithHash(script->InlineSourceTextForCSP(), |
| ContentSecurityPolicy::InlineType::kBlock); |
| |
| AtomicString nonce = |
| element_->IsNonceableElement() ? element_->nonce() : g_null_atom; |
| if (!should_bypass_main_world_csp && |
| !element_->AllowInlineScriptForCSP(nonce, start_line_number_, |
| script->InlineSourceTextForCSP())) { |
| return false; |
| } |
| } |
| |
| if (is_external_script_) { |
| if (!script->CheckMIMETypeBeforeRunScript( |
| context_document, element_->GetDocument().GetSecurityOrigin())) |
| return false; |
| } |
| |
| const bool is_imported_script = context_document != element_document; |
| |
| // 3. "If the script is from an external file, |
| // or the script's type is module", |
| // then increment the ignore-destructive-writes counter of the |
| // script element's node document. Let neutralized doc be that Document." |
| IgnoreDestructiveWriteCountIncrementer |
| ignore_destructive_write_count_incrementer( |
| is_external_script_ || |
| script->GetScriptType() == ScriptType::kModule || |
| is_imported_script |
| ? context_document |
| : 0); |
| |
| // 4. "Let old script element be the value to which the script element's |
| // node document's currentScript object was most recently set." |
| // This is implemented as push/popCurrentScript(). |
| |
| // 5. "Switch on the script's type:" |
| // - "classic": |
| // 1. "If the script element's root is not a shadow root, |
| // then set the script element's node document's currentScript |
| // attribute to the script element. Otherwise, set it to null." |
| // - "module": |
| // 1. "Set the script element's node document's currentScript attribute |
| // to null." |
| ScriptElementBase* current_script = nullptr; |
| if (script->GetScriptType() == ScriptType::kClassic) |
| current_script = element_; |
| context_document->PushCurrentScript(current_script); |
| |
| // - "classic": |
| // 2. "Run the classic script given by the script's script." |
| // Note: This is where the script is compiled and actually executed. |
| // - "module": |
| // 2. "Run the module script given by the script's script." |
| script->RunScript(frame, element_->GetDocument().GetSecurityOrigin()); |
| |
| // 6. "Set the script element's node document's currentScript attribute |
| // to old script element." |
| context_document->PopCurrentScript(current_script); |
| |
| return true; |
| |
| // 7. "Decrement the ignore-destructive-writes counter of neutralized doc, |
| // if it was incremented in the earlier step." |
| // Implemented as the scope out of IgnoreDestructiveWriteCountIncrementer. |
| } |
| |
| void ScriptLoader::Execute() { |
| DCHECK(!will_be_parser_executed_); |
| DCHECK(async_exec_type_ != ScriptRunner::kNone); |
| DCHECK(pending_script_->IsExternal()); |
| bool error_occurred = false; |
| Script* script = pending_script_->GetSource(KURL(), error_occurred); |
| const bool wasCanceled = pending_script_->WasCanceled(); |
| DetachPendingScript(); |
| if (error_occurred) { |
| DispatchErrorEvent(); |
| } else if (!wasCanceled) { |
| if (ExecuteScript(script)) |
| DispatchLoadEvent(); |
| else |
| DispatchErrorEvent(); |
| } |
| resource_ = nullptr; |
| module_tree_client_ = nullptr; |
| } |
| |
| void ScriptLoader::PendingScriptFinished(PendingScript* pending_script) { |
| DCHECK(!will_be_parser_executed_); |
| DCHECK_EQ(pending_script_, pending_script); |
| DCHECK_EQ(pending_script_->GetScriptType(), GetScriptType()); |
| |
| // We do not need this script in the memory cache. The primary goals of |
| // sending this fetch request are to let the third party server know |
| // about the document.write scripts intervention and populate the http |
| // cache for subsequent uses. |
| if (document_write_intervention_ == |
| DocumentWriteIntervention::kFetchDocWrittenScriptDeferIdle) { |
| DCHECK_EQ(pending_script_->GetScriptType(), ScriptType::kClassic); |
| pending_script_->RemoveFromMemoryCache(); |
| pending_script_->StopWatchingForLoad(); |
| return; |
| } |
| |
| DCHECK(async_exec_type_ != ScriptRunner::kNone); |
| |
| Document* context_document = element_->GetDocument().ContextDocument(); |
| if (!context_document) { |
| DetachPendingScript(); |
| return; |
| } |
| |
| if (ErrorOccurred()) { |
| context_document->GetScriptRunner()->NotifyScriptLoadError( |
| this, async_exec_type_); |
| DetachPendingScript(); |
| DispatchErrorEvent(); |
| return; |
| } |
| context_document->GetScriptRunner()->NotifyScriptReady(this, |
| async_exec_type_); |
| pending_script_->StopWatchingForLoad(); |
| } |
| |
| bool ScriptLoader::IgnoresLoadRequest() const { |
| return already_started_ || is_external_script_ || parser_inserted_ || |
| !element_->IsConnected(); |
| } |
| |
| // Step 13 of https://html.spec.whatwg.org/#prepare-a-script |
| bool ScriptLoader::IsScriptForEventSupported() const { |
| // 1. "Let for be the value of the for attribute." |
| String event_attribute = element_->EventAttributeValue(); |
| // 2. "Let event be the value of the event attribute." |
| String for_attribute = element_->ForAttributeValue(); |
| |
| // "If the script element has an event attribute and a for attribute, and |
| // the script's type is "classic", then run these substeps:" |
| // TODO(hiroshige): Check the script's type. |
| if (event_attribute.IsNull() || for_attribute.IsNull()) |
| return true; |
| |
| // 3. "Strip leading and trailing ASCII whitespace from event and for." |
| for_attribute = for_attribute.StripWhiteSpace(); |
| // 4. "If for is not an ASCII case-insensitive match for the string |
| // "window", |
| // then abort these steps at this point. The script is not executed." |
| if (!DeprecatedEqualIgnoringCase(for_attribute, "window")) |
| return false; |
| event_attribute = event_attribute.StripWhiteSpace(); |
| // 5. "If event is not an ASCII case-insensitive match for either the |
| // string "onload" or the string "onload()", |
| // then abort these steps at this point. The script is not executed. |
| return DeprecatedEqualIgnoringCase(event_attribute, "onload") || |
| DeprecatedEqualIgnoringCase(event_attribute, "onload()"); |
| } |
| |
| String ScriptLoader::ScriptContent() const { |
| return element_->TextFromChildren(); |
| } |
| |
| } // namespace blink |