blob: 31c1d938deca8f883f86d70c94fe50df1e4631cb [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/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