blob: 9fe007336daf6f3b11cdce4481a51a9786d5f45d [file] [log] [blame]
/*
* 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/dom/ClassicPendingScript.h"
#include "core/dom/ClassicScript.h"
#include "core/dom/Document.h"
#include "core/dom/DocumentParserTiming.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/dom/events/Event.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/UseCounter.h"
#include "core/frame/csp/ContentSecurityPolicy.h"
#include "core/html/imports/HTMLImport.h"
#include "core/html/parser/HTMLParserIdioms.h"
#include "core/html_names.h"
#include "core/inspector/ConsoleMessage.h"
#include "core/loader/SubresourceIntegrityHelper.h"
#include "core/loader/modulescript/ModuleScriptFetchRequest.h"
#include "core/svg_names.h"
#include "platform/WebFrameScheduler.h"
#include "platform/loader/SubresourceIntegrity.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"
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) {
// 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() {}
void ScriptLoader::Trace(blink::Visitor* visitor) {
visitor->Trace(element_);
visitor->Trace(pending_script_);
visitor->Trace(prepared_pending_script_);
visitor->Trace(original_document_);
visitor->Trace(resource_keep_alive_);
PendingScriptClient::Trace(visitor);
}
void ScriptLoader::TraceWrappers(const ScriptWrappableVisitor* visitor) const {
visitor->TraceWrappers(pending_script_);
visitor->TraceWrappers(prepared_pending_script_);
}
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 (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;
}
// Step 16 of https://html.spec.whatwg.org/#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;
}
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();
original_document_ = context_document;
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. "Let module script credentials mode be determined by switching
// on CORS setting:"
network::mojom::FetchCredentialsMode credentials_mode =
ModuleScriptCredentialsMode(cross_origin);
// 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 = element_->GetNonceForElement();
// 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::ReportInfo report_info;
SubresourceIntegrity::ParseIntegrityAttribute(
integrity_attr, integrity_metadata, &report_info);
SubresourceIntegrityHelper::DoReport(element_document, report_info);
}
// 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;
if (GetScriptType() == ScriptType::kModule)
UseCounter::Count(*context_document, WebFeature::kPrepareModuleScript);
DCHECK(!prepared_pending_script_);
// Reset line numbering for nested writes.
TextPosition position = element_document.IsInDocumentWrite()
? TextPosition()
: script_start_position;
// 20. "Let options be a script fetch options whose cryptographic nonce is
// cryptographic nonce, integrity metadata is integrity metadata,
// parser metadata is parser metadata, and credentials mode is module
// script credentials mode." [spec text]
ScriptFetchOptions options(nonce, integrity_metadata, integrity_attr,
parser_state, credentials_mode);
// 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."
KURL url = element_document.CompleteURL(src);
// 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;
}
// 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?
WTF::TextEncoding encoding;
if (!element_->CharsetAttributeValue().IsEmpty())
encoding = WTF::TextEncoding(element_->CharsetAttributeValue());
else
encoding = element_document.Encoding();
// "Fetch a classic script given url, settings object, options, CORS
// setting, and encoding." [spec text]
if (!FetchClassicScript(url, element_document, options, encoding)) {
// TODO(hiroshige): Make this asynchronous. Currently we fire the error
// event synchronously to keep the existing behavior.
DispatchErrorEvent();
return false;
}
} else {
// - "module":
// Steps 14 and 18 are skipped because they are not used in module
// scripts.
Modulator* modulator = Modulator::From(
ToScriptStateForMainWorld(context_document->GetFrame()));
FetchModuleScriptTree(url, modulator, options);
}
// "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 as ScriptElementBase::TextFromChildren():
// - in ScriptLoader::PrepareScript() (Step 23, 6th Clause),
// - in HTMLParserScriptRunner::ProcessScriptElementInternal()
// (Duplicated code of Step 23, 6th Clause),
// - in XMLDocumentParser::EndElementNs() (Step 23, 5th Clause), or
// - in PendingScript::GetSource() (Indirectly used via
// HTMLParserScriptRunner::ProcessScriptElementInternal(),
// Step 23, 5th Clause).
// 22.2. "Switch on the script's type:"
switch (GetScriptType()) {
// - "classic":
case ScriptType::kClassic:
// 1. Let script be the result of creating a classic script using source
// text, settings object, base URL, and options.
//
// TODO(hiroshige): Implement base URL and options.
prepared_pending_script_ =
ClassicPendingScript::CreateInline(element_, position, options);
// 2. Set the script's script to script.
// 3. The script is ready.
// Implemented by ClassicPendingScript.
break;
// - "module":
case ScriptType::kModule: {
// 1. "Let base URL be the script element's node document's document
// base URL."
KURL base_url = element_document.BaseURL();
// 2. "Let script be the result of creating a module script using
// source text, settings, base URL, cryptographic nonce,
// parser state, and module script credentials mode."
// TODO(kouhei,hiroshige): Update spec refs to use ScriptFetchOptions.
Modulator* modulator = Modulator::From(
ToScriptStateForMainWorld(context_document->GetFrame()));
ModuleScript* module_script = ModuleScript::Create(
element_->TextFromChildren(), modulator, base_url, options,
kSharableCrossOrigin, position);
// 3. "If this returns null, set the script's script to null and abort
// these substeps; the script is ready."
if (!module_script)
return false;
// 4. "Fetch the descendants of script (using an empty ancestor list).
// When this asynchronously completes, set the script's script to
// the result. At that time, the script is ready."
auto* module_tree_client = ModulePendingScriptTreeClient::Create();
modulator->FetchDescendantsForInlineScript(module_script,
module_tree_client);
prepared_pending_script_ = ModulePendingScript::Create(
element_, module_tree_client, is_external_script_);
break;
}
}
}
DCHECK(prepared_pending_script_);
// 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;
}
// 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"
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_ = TakePendingScript();
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_ = TakePendingScript();
async_exec_type_ = ScriptRunner::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;
}
// 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_);
// 5th Clause:
// - "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.
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;
// "Set the element's "ready to be parser-executed" flag."
ready_to_be_parser_executed_ = true;
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.
KURL script_url = (!element_document.IsInDocumentWrite() && parser_inserted_)
? element_document.Url()
: KURL();
ExecuteScriptBlock(TakePendingScript(), script_url);
return true;
}
// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-classic-script
bool 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);
if (!pending_script)
return false;
prepared_pending_script_ = pending_script;
resource_keep_alive_ = pending_script->GetResource();
return true;
}
void ScriptLoader::FetchModuleScriptTree(const KURL& url,
Modulator* modulator,
const ScriptFetchOptions& options) {
// https://html.spec.whatwg.org/#prepare-a-script
// 22.6, "module":
// "Fetch a module script graph given url, settings object, "script", and
// options."
auto* module_tree_client = ModulePendingScriptTreeClient::Create();
modulator->FetchTree(
ModuleScriptFetchRequest(url, modulator->GetReferrerPolicy(), options),
module_tree_client);
prepared_pending_script_ = ModulePendingScript::Create(
element_, module_tree_client, is_external_script_);
}
PendingScript* ScriptLoader::TakePendingScript() {
CHECK(prepared_pending_script_);
PendingScript* pending_script = prepared_pending_script_;
prepared_pending_script_ = nullptr;
return pending_script;
}
// Steps 3--7 of https://html.spec.whatwg.org/#execute-the-script-block
// with additional support for HTML imports.
// Steps 2 and 8 are handled in ExecuteScriptBlock().
ScriptLoader::ExecuteScriptResult ScriptLoader::DoExecuteScript(
const Script* script) {
DCHECK(already_started_);
CHECK_EQ(script->GetScriptType(), GetScriptType());
Document* element_document = &(element_->GetDocument());
Document* context_document = element_document->ContextDocument();
if (!context_document)
return ExecuteScriptResult::kShouldFireNone;
LocalFrame* frame = context_document->GetFrame();
if (!frame)
return ExecuteScriptResult::kShouldFireNone;
if (!is_external_script_) {
bool should_bypass_main_world_csp =
(frame->GetScriptController().ShouldBypassMainWorldCSP());
AtomicString nonce = element_->GetNonceForElement();
if (!should_bypass_main_world_csp &&
!element_->AllowInlineScriptForCSP(
nonce, start_line_number_, script->InlineSourceTextForCSP(),
ContentSecurityPolicy::InlineType::kBlock)) {
return ExecuteScriptResult::kShouldFireErrorEvent;
}
}
if (is_external_script_) {
if (!script->CheckMIMETypeBeforeRunScript(
context_document, element_->GetDocument().GetSecurityOrigin()))
return ExecuteScriptResult::kShouldFireErrorEvent;
}
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
: nullptr);
// 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 ExecuteScriptResult::kShouldFireLoadEvent;
// 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_->IsExternalOrModule());
PendingScript* pending_script = pending_script_;
pending_script_ = nullptr;
ExecuteScriptBlock(pending_script, NullURL());
resource_keep_alive_ = nullptr;
}
// https://html.spec.whatwg.org/#execute-the-script-block
void ScriptLoader::ExecuteScriptBlock(PendingScript* pending_script,
const KURL& document_url) {
DCHECK(pending_script);
DCHECK_EQ(pending_script->IsExternal(), is_external_script_);
bool error_occurred = false;
Script* script = pending_script->GetSource(document_url, error_occurred);
const bool was_canceled = pending_script->WasCanceled();
const bool is_external = pending_script->IsExternal();
const double parser_blocking_load_start_time =
pending_script->ParserBlockingLoadStartTime();
pending_script->Dispose();
// Do not execute module scripts if they are moved between documents.
// TODO(hiroshige): Also do not execute classic scripts. crbug.com/721914
Document* element_document = &(element_->GetDocument());
Document* context_document = element_document->ContextDocument();
if (original_document_ != context_document &&
GetScriptType() == ScriptType::kModule)
return;
// 2. "If the script's script is null, fire an event named error at the
// element, and abort these steps."
if (error_occurred) {
DispatchErrorEvent();
return;
}
if (parser_blocking_load_start_time > 0.0) {
DocumentParserTiming::From(element_->GetDocument())
.RecordParserBlockedOnScriptLoadDuration(
MonotonicallyIncreasingTime() - parser_blocking_load_start_time,
WasCreatedDuringDocumentWrite());
}
if (was_canceled)
return;
double script_exec_start_time = MonotonicallyIncreasingTime();
// Steps 3--7 are in DoExecuteScript().
ExecuteScriptResult 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());
}
switch (result) {
case ExecuteScriptResult::kShouldFireLoadEvent:
// 8. "If the script is from an external file, then fire an event named
// load at the script element."
if (is_external)
DispatchLoadEvent();
break;
case ExecuteScriptResult::kShouldFireErrorEvent:
// Consider as if "the script's script is null" retrospectively,
// due to CSP check failures etc., which are considered as load failure.
DispatchErrorEvent();
break;
case ExecuteScriptResult::kShouldFireNone:
break;
}
}
void ScriptLoader::PendingScriptFinished(PendingScript* pending_script) {
DCHECK(!will_be_parser_executed_);
DCHECK_EQ(pending_script_, pending_script);
DCHECK_EQ(pending_script_->GetScriptType(), GetScriptType());
DCHECK(async_exec_type_ != ScriptRunner::kNone);
Document* context_document = element_->GetDocument().ContextDocument();
if (!context_document) {
DetachPendingScript();
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:"
if (GetScriptType() != ScriptType::kClassic || 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()");
}
PendingScript* ScriptLoader::GetPendingScriptIfScriptIsAsync() {
if (pending_script_ && async_exec_type_ == ScriptRunner::kAsync)
return pending_script_;
return nullptr;
}
} // namespace blink