blob: bc799e4a33cd6ec9f015367f66920607c0f986f2 [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/V8Binding.h"
#include "core/HTMLNames.h"
#include "core/SVGNames.h"
#include "core/dom/Document.h"
#include "core/dom/DocumentParserTiming.h"
#include "core/dom/IgnoreDestructiveWriteCountIncrementer.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/UseCounter.h"
#include "core/frame/csp/ContentSecurityPolicy.h"
#include "core/html/CrossOriginAttribute.h"
#include "core/html/imports/HTMLImport.h"
#include "core/html/parser/HTMLParserIdioms.h"
#include "core/inspector/ConsoleMessage.h"
#include "platform/WebFrameScheduler.h"
#include "platform/loader/fetch/AccessControlStatus.h"
#include "platform/loader/fetch/FetchRequest.h"
#include "platform/loader/fetch/MemoryCache.h"
#include "platform/loader/fetch/ResourceFetcher.h"
#include "platform/network/mime/MIMETypeRegistry.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "public/platform/WebCachePolicy.h"
#include "wtf/StdLibExtras.h"
#include "wtf/text/StringBuilder.h"
#include "wtf/text/StringHash.h"
namespace blink {
ScriptLoader::ScriptLoader(ScriptElementBase* element,
bool parserInserted,
bool alreadyStarted,
bool createdDuringDocumentWrite)
: m_element(element),
m_startLineNumber(WTF::OrdinalNumber::beforeFirst()),
m_haveFiredLoad(false),
m_willBeParserExecuted(false),
m_willExecuteWhenDocumentFinishedParsing(false),
m_createdDuringDocumentWrite(createdDuringDocumentWrite),
m_asyncExecType(ScriptRunner::None),
m_documentWriteIntervention(
DocumentWriteIntervention::DocumentWriteInterventionNone) {
// 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 (alreadyStarted)
m_alreadyStarted = true;
if (parserInserted) {
// https://html.spec.whatwg.org/#parser-inserted
// "It is set by the HTML parser and the XML parser
// on script elements they insert"
m_parserInserted = true;
// https://html.spec.whatwg.org/#non-blocking
// "It is unset by the HTML parser and the XML parser
// on script elements they insert."
m_nonBlocking = false;
}
if (parserInserted && m_element->document().scriptableDocumentParser() &&
!m_element->document().isInDocumentWrite()) {
m_startLineNumber =
m_element->document().scriptableDocumentParser()->lineNumber();
}
}
ScriptLoader::~ScriptLoader() {}
DEFINE_TRACE(ScriptLoader) {
visitor->trace(m_element);
visitor->trace(m_resource);
visitor->trace(m_pendingScript);
PendingScriptClient::trace(visitor);
}
void ScriptLoader::setFetchDocWrittenScriptDeferIdle() {
DCHECK(!m_createdDuringDocumentWrite);
m_documentWriteIntervention =
DocumentWriteIntervention::FetchDocWrittenScriptDeferIdle;
}
void ScriptLoader::didNotifySubtreeInsertionsToDocument() {
if (!m_parserInserted)
prepareScript(); // FIXME: Provide a real starting line number here.
}
void ScriptLoader::childrenChanged() {
if (!m_parserInserted && m_element->isConnected())
prepareScript(); // FIXME: Provide a real starting line number here.
}
void ScriptLoader::handleSourceAttribute(const String& sourceUrl) {
if (ignoresLoadRequest() || sourceUrl.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."
m_nonBlocking = false;
}
void ScriptLoader::detachPendingScript() {
if (!m_pendingScript)
return;
m_pendingScript->dispose();
m_pendingScript = nullptr;
}
void ScriptLoader::dispatchErrorEvent() {
m_element->dispatchErrorEvent();
}
void ScriptLoader::dispatchLoadEvent() {
m_element->dispatchLoadEvent();
setHaveFiredLoadEvent(true);
}
bool ScriptLoader::isValidScriptTypeAndLanguage(
const String& type,
const String& language,
LegacyTypeSupport supportLegacyTypes) {
// 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 (RuntimeEnabledFeatures::moduleScriptsEnabled() &&
type == "module") {
return true;
} else if (MIMETypeRegistry::isSupportedJavaScriptMIMEType(
type.stripWhiteSpace()) ||
(supportLegacyTypes == AllowLegacyTypeInTypeAttribute &&
MIMETypeRegistry::isLegacySupportedJavaScriptLanguage(type))) {
return true;
}
return false;
}
bool ScriptLoader::isScriptTypeSupported(
LegacyTypeSupport supportLegacyTypes) const {
return isValidScriptTypeAndLanguage(m_element->typeAttributeValue(),
m_element->languageAttributeValue(),
supportLegacyTypes);
}
// https://html.spec.whatwg.org/#prepare-a-script
bool ScriptLoader::prepareScript(const TextPosition& scriptStartPosition,
LegacyTypeSupport supportLegacyTypes) {
// 1. "If the script element is marked as having "already started", then
// abort these steps at this point. The script is not executed."
if (m_alreadyStarted)
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 wasParserInserted;
if (m_parserInserted) {
wasParserInserted = true;
m_parserInserted = false;
} else {
wasParserInserted = 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 (wasParserInserted && !m_element->asyncAttributeValue())
m_nonBlocking = 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 (!m_element->hasSourceAttribute() && !m_element->hasChildren())
return false;
// 5. "If the element is not connected, then abort these steps.
// The script is not executed."
if (!m_element->isConnected())
return false;
// 6.
// TODO(hiroshige): Annotate and/or cleanup this step.
if (!isScriptTypeSupported(supportLegacyTypes))
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 (wasParserInserted) {
m_parserInserted = true;
m_nonBlocking = false;
}
// 8. "Set the element's "already started" flag."
m_alreadyStarted = 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& elementDocument = m_element->document();
Document* contextDocument = elementDocument.contextDocument();
if (!elementDocument.executingFrame())
return false;
if (!contextDocument || !contextDocument->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 (!contextDocument->canExecuteScripts(AboutToExecuteScript))
return false;
// 13.
if (!isScriptForEventSupported())
return false;
// 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 (!m_element->charsetAttributeValue().isEmpty())
encoding = m_element->charsetAttributeValue();
else
encoding = elementDocument.characterSet();
// Steps 15--20 are handled in fetchScript().
// 21. "If the element has a src content attribute, run these substeps:"
if (m_element->hasSourceAttribute()) {
FetchRequest::DeferOption defer = FetchRequest::NoDefer;
if (!m_parserInserted || m_element->asyncAttributeValue() ||
m_element->deferAttributeValue())
defer = FetchRequest::LazyLoad;
if (m_documentWriteIntervention ==
DocumentWriteIntervention::FetchDocWrittenScriptDeferIdle)
defer = FetchRequest::IdleLoad;
if (!fetchScript(m_element->sourceAttributeValue(), encoding, defer))
return false;
}
// 22. "If the element does not have a src content attribute,
// run these substeps:"
// 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:"
// TODO(hiroshige): Clarify how Step 22.2 is implemented for "classic".
// TODO(hiroshige): Implement Step 22.2 for "module".
// [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 (m_documentWriteIntervention ==
DocumentWriteIntervention::FetchDocWrittenScriptDeferIdle) {
m_pendingScript = PendingScript::create(m_element.get(), m_resource.get());
m_pendingScript->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"
// TODO(hiroshige): Check the script's type and implement "module" case.
if (m_element->hasSourceAttribute() && m_element->deferAttributeValue() &&
m_parserInserted && !m_element->asyncAttributeValue()) {
// This clause is implemented by the caller-side of prepareScript():
// - HTMLParserScriptRunner::requestDeferredScript(), and
// - TODO(hiroshige): Investigate XMLDocumentParser::endElementNs()
m_willExecuteWhenDocumentFinishedParsing = true;
m_willBeParserExecuted = 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 (m_element->hasSourceAttribute() && m_parserInserted &&
!m_element->asyncAttributeValue()) {
// This clause is implemented by the caller-side of prepareScript():
// - HTMLParserScriptRunner::requestParsingBlockingScript()
// - TODO(hiroshige): Investigate XMLDocumentParser::endElementNs()
m_willBeParserExecuted = 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.
if (!m_element->hasSourceAttribute() && m_parserInserted &&
!elementDocument.isScriptExecutionReady()) {
// The former part of this clause is
// implemented by the caller-side of prepareScript():
// - HTMLParserScriptRunner::requestParsingBlockingScript()
// - TODO(hiroshige): Investigate XMLDocumentParser::endElementNs()
m_willBeParserExecuted = true;
// "Set the element's "ready to be parser-executed" flag."
m_readyToBeParserExecuted = 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"
// TODO(hiroshige): Check the script's type and implement "module" case.
if (m_element->hasSourceAttribute() && !m_element->asyncAttributeValue() &&
!m_nonBlocking) {
// "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."
m_pendingScript = PendingScript::create(m_element.get(), m_resource.get());
m_asyncExecType = ScriptRunner::InOrder;
// TODO(hiroshige): Here |contextDocument| is used as "node document"
// while Step 14 uses |elementDocument| as "node document". Fix this.
contextDocument->scriptRunner()->queueScriptForExecution(this,
m_asyncExecType);
// Note that watchForLoad can immediately call pendingScriptFinished.
m_pendingScript->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"
// TODO(hiroshige): Check the script's type and implement "module" case.
if (m_element->hasSourceAttribute()) {
// "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."
m_pendingScript = PendingScript::create(m_element.get(), m_resource.get());
m_asyncExecType = ScriptRunner::Async;
m_pendingScript->startStreamingIfPossible(&m_element->document(),
ScriptStreamer::Async);
// TODO(hiroshige): Here |contextDocument| is used as "node document"
// while Step 14 uses |elementDocument| as "node document". Fix this.
contextDocument->scriptRunner()->queueScriptForExecution(this,
m_asyncExecType);
// Note that watchForLoad can immediately call pendingScriptFinished.
m_pendingScript->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.
// Reset line numbering for nested writes.
TextPosition position = elementDocument.isInDocumentWrite()
? TextPosition()
: scriptStartPosition;
KURL scriptURL = (!elementDocument.isInDocumentWrite() && m_parserInserted)
? elementDocument.url()
: KURL();
if (!executeScript(ScriptSourceCode(scriptContent(), scriptURL, position))) {
dispatchErrorEvent();
return false;
}
return true;
}
// Steps 15--21 of https://html.spec.whatwg.org/#prepare-a-script
bool ScriptLoader::fetchScript(const String& sourceUrl,
const String& encoding,
FetchRequest::DeferOption defer) {
Document* elementDocument = &(m_element->document());
if (!m_element->isConnected() || m_element->document() != elementDocument)
return false;
DCHECK(!m_resource);
// 21. "If the element has a src content attribute, run these substeps:"
if (!stripLeadingAndTrailingHTMLSpaces(sourceUrl).isEmpty()) {
// 21.4. "Parse src relative to the element's node document."
ResourceRequest resourceRequest(elementDocument->completeURL(sourceUrl));
// [Intervention]
if (m_documentWriteIntervention ==
DocumentWriteIntervention::FetchDocWrittenScriptDeferIdle) {
resourceRequest.setHTTPHeaderField(
"Intervention",
"<https://www.chromestatus.com/feature/5718547946799104>");
}
FetchRequest request(resourceRequest, m_element->initiatorName());
// 15. "Let CORS setting be the current state of the element's
// crossorigin content attribute."
CrossOriginAttributeValue crossOrigin =
crossOriginAttributeValue(m_element->crossOriginAttributeValue());
// 16. "Let module script credentials mode be determined by switching
// on CORS setting:"
// TODO(hiroshige): Implement this step for "module".
// 21.6, "classic": "Fetch a classic script given ... CORS setting
// ... and encoding."
if (crossOrigin != CrossOriginAttributeNotSet)
request.setCrossOriginAccessControl(elementDocument->getSecurityOrigin(),
crossOrigin);
request.setCharset(encoding);
// 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."
if (m_element->isNonceableElement())
request.setContentSecurityPolicyNonce(m_element->nonce());
// 19. "Let parser state be "parser-inserted"
// if the script element has been flagged as "parser-inserted",
// and "not parser-inserted" otherwise."
request.setParserDisposition(isParserInserted() ? ParserInserted
: NotParserInserted);
request.setDefer(defer);
// 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 integrityAttr = m_element->integrityAttributeValue();
if (!integrityAttr.isEmpty()) {
IntegrityMetadataSet metadataSet;
SubresourceIntegrity::parseIntegrityAttribute(integrityAttr, metadataSet,
elementDocument);
request.setIntegrityMetadata(metadataSet);
}
// 21.6. "Switch on the script's type:"
// - "classic":
// "Fetch a classic script given url, settings, cryptographic nonce,
// integrity metadata, parser state, CORS setting, and encoding."
m_resource = ScriptResource::fetch(request, elementDocument->fetcher());
// - "module":
// "Fetch a module script graph given url, settings, "script",
// cryptographic nonce, parser state, and
// module script credentials mode."
// TODO(kouhei, hiroshige): Implement this.
// "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".
// 21.3. "Set the element's from an external file flag."
m_isExternalScript = true;
}
if (!m_resource) {
// 21.2. "If src is the empty string, queue a task to
// fire an event named error at the element, and abort these steps."
// 21.5. "If the previous step failed, queue a task to
// fire an event named error at the element, and abort these steps."
// TODO(hiroshige): Make this asynchronous.
dispatchErrorEvent();
return false;
}
// [Intervention]
if (m_createdDuringDocumentWrite &&
m_resource->resourceRequest().getCachePolicy() ==
WebCachePolicy::ReturnCacheDataDontLoad) {
m_documentWriteIntervention =
DocumentWriteIntervention::DoNotFetchDocWrittenScript;
}
return true;
}
void ScriptLoader::logScriptMIMEType(LocalFrame* frame,
ScriptResource* resource,
const String& mimeType) {
if (MIMETypeRegistry::isSupportedJavaScriptMIMEType(mimeType))
return;
bool isText = mimeType.startsWith("text/", TextCaseASCIIInsensitive);
if (isText && MIMETypeRegistry::isLegacySupportedJavaScriptLanguage(
mimeType.substring(5)))
return;
bool isSameOrigin =
m_element->document().getSecurityOrigin()->canRequest(resource->url());
bool isApplication =
!isText && mimeType.startsWith("application/", TextCaseASCIIInsensitive);
UseCounter::Feature feature =
isSameOrigin
? (isText ? UseCounter::SameOriginTextScript
: isApplication ? UseCounter::SameOriginApplicationScript
: UseCounter::SameOriginOtherScript)
: (isText ? UseCounter::CrossOriginTextScript
: isApplication ? UseCounter::CrossOriginApplicationScript
: UseCounter::CrossOriginOtherScript);
UseCounter::count(frame, feature);
}
bool ScriptLoader::executeScript(const ScriptSourceCode& sourceCode) {
double scriptExecStartTime = monotonicallyIncreasingTime();
bool result = doExecuteScript(sourceCode);
// 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 (m_asyncExecType == ScriptRunner::None)
DocumentParserTiming::from(m_element->document())
.recordParserBlockedOnScriptExecutionDuration(
monotonicallyIncreasingTime() - scriptExecStartTime,
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 ScriptSourceCode& sourceCode) {
DCHECK(m_alreadyStarted);
if (sourceCode.isEmpty())
return true;
Document* elementDocument = &(m_element->document());
Document* contextDocument = elementDocument->contextDocument();
if (!contextDocument)
return true;
LocalFrame* frame = contextDocument->frame();
if (!frame)
return true;
const ContentSecurityPolicy* csp = elementDocument->contentSecurityPolicy();
bool shouldBypassMainWorldCSP =
(frame->script().shouldBypassMainWorldCSP()) ||
csp->allowScriptWithHash(sourceCode.source(),
ContentSecurityPolicy::InlineType::Block);
AtomicString nonce =
m_element->isNonceableElement() ? m_element->nonce() : nullAtom;
if (!m_isExternalScript && !shouldBypassMainWorldCSP &&
!m_element->allowInlineScriptForCSP(nonce, m_startLineNumber,
sourceCode.source())) {
return false;
}
if (m_isExternalScript) {
ScriptResource* resource = sourceCode.resource();
CHECK_EQ(resource, m_resource);
CHECK(resource);
if (!ScriptResource::mimeTypeAllowedByNosniff(resource->response())) {
contextDocument->addConsoleMessage(ConsoleMessage::create(
SecurityMessageSource, ErrorMessageLevel,
"Refused to execute script from '" + resource->url().elidedString() +
"' because its MIME type ('" + resource->httpContentType() +
"') is not executable, and "
"strict MIME type checking is "
"enabled."));
return false;
}
String mimeType = resource->httpContentType();
if (mimeType.startsWith("image/") || mimeType == "text/csv" ||
mimeType.startsWith("audio/") || mimeType.startsWith("video/")) {
contextDocument->addConsoleMessage(ConsoleMessage::create(
SecurityMessageSource, ErrorMessageLevel,
"Refused to execute script from '" + resource->url().elidedString() +
"' because its MIME type ('" + mimeType +
"') is not executable."));
if (mimeType.startsWith("image/"))
UseCounter::count(frame, UseCounter::BlockedSniffingImageToScript);
else if (mimeType.startsWith("audio/"))
UseCounter::count(frame, UseCounter::BlockedSniffingAudioToScript);
else if (mimeType.startsWith("video/"))
UseCounter::count(frame, UseCounter::BlockedSniffingVideoToScript);
else if (mimeType == "text/csv")
UseCounter::count(frame, UseCounter::BlockedSniffingCSVToScript);
return false;
}
logScriptMIMEType(frame, resource, mimeType);
}
AccessControlStatus accessControlStatus = NotSharableCrossOrigin;
if (!m_isExternalScript) {
accessControlStatus = SharableCrossOrigin;
} else {
CHECK(sourceCode.resource());
accessControlStatus = sourceCode.resource()->calculateAccessControlStatus(
m_element->document().getSecurityOrigin());
}
const bool isImportedScript = contextDocument != elementDocument;
// 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."
// TODO(hiroshige): Implement "module" case.
IgnoreDestructiveWriteCountIncrementer ignoreDestructiveWriteCountIncrementer(
m_isExternalScript || isImportedScript ? contextDocument : 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."
contextDocument->pushCurrentScript(m_element.get());
// 2. "Run the classic script given by the script's script."
// Note: This is where the script is compiled and actually executed.
frame->script().executeScriptInMainWorld(sourceCode, accessControlStatus);
// - "module":
// TODO(hiroshige): Implement this.
// 6. "Set the script element's node document's currentScript attribute
// to old script element."
contextDocument->popCurrentScript(m_element.get());
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(!m_willBeParserExecuted);
DCHECK(m_asyncExecType != ScriptRunner::None);
DCHECK(m_pendingScript->resource());
bool errorOccurred = false;
ScriptSourceCode source = m_pendingScript->getSource(KURL(), errorOccurred);
detachPendingScript();
if (errorOccurred) {
dispatchErrorEvent();
} else if (!m_resource->wasCanceled()) {
if (executeScript(source))
dispatchLoadEvent();
else
dispatchErrorEvent();
}
m_resource = nullptr;
}
void ScriptLoader::pendingScriptFinished(PendingScript* pendingScript) {
DCHECK(!m_willBeParserExecuted);
DCHECK_EQ(m_pendingScript, pendingScript);
DCHECK_EQ(pendingScript->resource(), m_resource);
// 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 (m_documentWriteIntervention ==
DocumentWriteIntervention::FetchDocWrittenScriptDeferIdle) {
memoryCache()->remove(m_pendingScript->resource());
m_pendingScript->stopWatchingForLoad();
return;
}
DCHECK(m_asyncExecType != ScriptRunner::None);
Document* contextDocument = m_element->document().contextDocument();
if (!contextDocument) {
detachPendingScript();
return;
}
if (errorOccurred()) {
contextDocument->scriptRunner()->notifyScriptLoadError(this,
m_asyncExecType);
detachPendingScript();
dispatchErrorEvent();
return;
}
contextDocument->scriptRunner()->notifyScriptReady(this, m_asyncExecType);
m_pendingScript->stopWatchingForLoad();
}
bool ScriptLoader::ignoresLoadRequest() const {
return m_alreadyStarted || m_isExternalScript || m_parserInserted ||
!m_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 eventAttribute = m_element->eventAttributeValue();
// 2. "Let event be the value of the event attribute."
String forAttribute = m_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 (eventAttribute.isNull() || forAttribute.isNull())
return true;
// 3. "Strip leading and trailing ASCII whitespace from event and for."
forAttribute = forAttribute.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 (!equalIgnoringCase(forAttribute, "window"))
return false;
eventAttribute = eventAttribute.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 equalIgnoringCase(eventAttribute, "onload") ||
equalIgnoringCase(eventAttribute, "onload()");
}
String ScriptLoader::scriptContent() const {
return m_element->textFromChildren();
}
} // namespace blink