blob: 77bd54d0574752b82b1845e25b61899120ebb282 [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-2025 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 "config.h"
#include "ScriptElement.h"
#include "CachedResourceLoader.h"
#include "CachedResourceRequest.h"
#include "CachedScript.h"
#include "CommonVM.h"
#include "ContentSecurityPolicy.h"
#include "CrossOriginAccessControl.h"
#include "CurrentScriptIncrementer.h"
#include "DOMWrapperWorld.h"
#include "DocumentEventLoop.h"
#include "DocumentInlines.h"
#include "DocumentPage.h"
#include "DocumentPrefetcher.h"
#include "ElementInlines.h"
#include "Event.h"
#include "EventLoop.h"
#include "EventNames.h"
#include "FrameDestructionObserverInlines.h"
#include "FrameLoader.h"
#include "HTMLAnchorElement.h"
#include "HTMLCollection.h"
#include "HTMLNames.h"
#include "HTMLScriptElement.h"
#include "IgnoreDestructiveWriteCountIncrementer.h"
#include "InlineClassicScript.h"
#include "LoadableClassicScript.h"
#include "LoadableModuleScript.h"
#include "LoadableScriptError.h"
#include "LocalFrame.h"
#include "MIMETypeRegistry.h"
#include "ModuleFetchParameters.h"
#include "PendingScript.h"
#include "SVGElementTypeHelpers.h"
#include "SVGScriptElement.h"
#include "ScriptController.h"
#include "ScriptDisallowedScope.h"
#include "ScriptRunner.h"
#include "ScriptSourceCode.h"
#include "ScriptableDocumentParser.h"
#include "Settings.h"
#include "SpeculationRules.h"
#include "TextNodeTraversal.h"
#include "TrustedType.h"
#include <JavaScriptCore/Error.h>
#include <JavaScriptCore/Exception.h>
#include <JavaScriptCore/ImportMap.h>
#include <JavaScriptCore/JSCJSValue.h>
#include <JavaScriptCore/JSGlobalObject.h>
#include <JavaScriptCore/JSLock.h>
#include <wtf/RuntimeApplicationChecks.h>
#include <wtf/Scope.h>
#include <wtf/SystemTracing.h>
#include <wtf/text/MakeString.h>
namespace WebCore {
static const auto maxUserGesturePropagationTime = 1_s;
ScriptElement::ScriptElement(Element& element, bool parserInserted, bool alreadyStarted)
: m_element(element)
, m_parserInserted(parserInserted ? ParserInserted::Yes : ParserInserted::No)
, m_alreadyStarted(alreadyStarted)
, m_forceAsync(!parserInserted)
, m_creationTime(MonotonicTime::now())
, m_userGestureToken(UserGestureIndicator::currentUserGesture())
{
Ref vm = commonVM();
m_taintedOrigin = computeNewSourceTaintedOriginFromStack(vm, vm->topCallFrame);
if (parserInserted) {
Ref document = element.document();
if (RefPtr parser = document->scriptableDocumentParser(); parser && !document->isInDocumentWrite())
m_startLineNumber = parser->textPosition().m_line;
}
}
void ScriptElement::didFinishInsertingNode()
{
if (m_parserInserted == ParserInserted::No)
prepareScript(); // FIXME: Provide a real starting line number here.
}
void ScriptElement::childrenChanged(const ContainerNode::ChildChange& childChange)
{
if (m_parserInserted == ParserInserted::No && childChange.isInsertion() && element().isConnected())
prepareScript(); // FIXME: Provide a real starting line number here.
if (childChange.source == ContainerNode::ChildChange::Source::API)
m_childrenChangedByAPI = true;
}
void ScriptElement::finishParsingChildren()
{
if (!m_childrenChangedByAPI)
m_trustedScriptText = scriptContent();
}
void ScriptElement::handleSourceAttribute(const String& sourceURL)
{
if (ignoresLoadRequest() || sourceURL.isEmpty())
return;
prepareScript(); // FIXME: Provide a real starting line number here.
}
void ScriptElement::handleAsyncAttribute()
{
m_forceAsync = false;
}
void ScriptElement::dispatchErrorEvent()
{
protectedElement()->dispatchEvent(Event::create(eventNames().errorEvent, Event::CanBubble::No, Event::IsCancelable::No));
}
static void reportSpeculationRulesError(LocalFrame& frame, const String& errorMessage)
{
// https://html.spec.whatwg.org/C#report-an-exception
auto& world = mainThreadNormalWorldSingleton();
JSC::VM& vm = world.vm();
JSC::JSLockHolder lock(vm);
if (auto* jsGlobalObject = frame.checkedScript()->globalObject(world)) {
auto* error = JSC::createTypeError(jsGlobalObject, errorMessage);
LoadableScript::Error scriptError {
LoadableScriptErrorType::Script,
std::nullopt,
JSC::Strong<JSC::Unknown>(vm, error)
};
frame.checkedScript()->reportExceptionFromScriptError(scriptError, false);
}
}
// https://html.spec.whatwg.org/C#prepare-the-script-element (Steps 8-12)
std::optional<ScriptType> ScriptElement::determineScriptType(const String& type, const String& language, bool isHTMLDocument, bool speculationRulesPrefetchEnabled)
{
// Step 8. If any of the following are true:
// - el has a type attribute whose value is the empty string;
// - el has no type attribute but it has a language attribute and that attribute's value is the empty string; or
// - el has neither a type attribute nor a language attribute,
// then let the script block's type string for this script element be "text/javascript".
if (type.isNull()) {
if (language.isEmpty())
return ScriptType::Classic;
if (MIMETypeRegistry::isSupportedJavaScriptMIMEType(makeString("text/"_s, language)))
return ScriptType::Classic;
return std::nullopt;
}
if (type.isEmpty())
return ScriptType::Classic; // Assume text/javascript.
// Step 9. If the script block's type string is a JavaScript MIME type essence match, then set el's type to "classic".
if (MIMETypeRegistry::isSupportedJavaScriptMIMEType(type.trim(isASCIIWhitespace)))
return ScriptType::Classic;
// FIXME: XHTML spec defines "defer" attribute. But WebKit does not implement it for a long time.
// And module tag also uses defer attribute semantics. We disable script type="module" for non HTML document.
// Once "defer" is implemented, we can reconsider enabling modules in XHTML.
// https://bugs.webkit.org/show_bug.cgi?id=123387
if (!isHTMLDocument)
return std::nullopt;
// Step 10. Otherwise, if the script block's type string is an ASCII case-insensitive match for the string "module", then set el's type to "module".
if (equalLettersIgnoringASCIICase(type, "module"_s))
return ScriptType::Module;
// Step 11. Otherwise, if the script block's type string is an ASCII case-insensitive match for the string "importmap", then set el's type to "importmap".
if (equalLettersIgnoringASCIICase(type, "importmap"_s))
return ScriptType::ImportMap;
// Step 12. Otherwise, if the script block's type string is an ASCII case-insensitive match for the string "speculationrules", then set el's type to "speculationrules".
if (speculationRulesPrefetchEnabled && equalLettersIgnoringASCIICase(type, "speculationrules"_s))
return ScriptType::SpeculationRules;
return std::nullopt;
}
std::optional<ScriptType> ScriptElement::determineScriptType() const
{
return determineScriptType(typeAttributeValue(), languageAttributeValue(), element().document().isHTMLDocument(), element().document().settings().speculationRulesPrefetchEnabled());
}
// https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element
bool ScriptElement::prepareScript(const TextPosition& scriptStartPosition)
{
if (m_alreadyStarted)
return false;
bool wasParserInserted;
if (m_parserInserted == ParserInserted::Yes) {
wasParserInserted = true;
m_parserInserted = ParserInserted::No;
} else
wasParserInserted = false;
if (wasParserInserted && !hasAsyncAttribute())
m_forceAsync = true;
String sourceText = scriptContent();
Ref element = this->element();
Ref context = *element->scriptExecutionContext();
if (context->settingsValues().trustedTypesEnabled && sourceText != m_trustedScriptText) {
auto trustedText = trustedTypeCompliantString(TrustedType::TrustedScript, context, sourceText, is<HTMLScriptElement>(element) ? "HTMLScriptElement text"_s : "SVGScriptElement text"_s);
if (trustedText.hasException())
return false;
sourceText = trustedText.releaseReturnValue();
}
if (!hasSourceAttribute() && sourceText.isEmpty())
return false;
if (!element->isConnected())
return false;
ScriptType scriptType = ScriptType::Classic;
if (std::optional<ScriptType> result = determineScriptType())
scriptType = result.value();
else
return false;
m_scriptType = scriptType;
if (wasParserInserted) {
m_parserInserted = ParserInserted::Yes;
m_forceAsync = false;
}
m_alreadyStarted = true;
// FIXME: If script is parser inserted, verify it's still in the original document.
Ref document = element->document();
// FIXME: Eventually we'd like to evaluate scripts which are inserted into a
// viewless document but this'll do for now.
// See http://bugs.webkit.org/show_bug.cgi?id=5727
if (!document->frame())
return false;
if (scriptType == ScriptType::Classic && hasNoModuleAttribute())
return false;
m_preparationTimeDocumentIdentifier = document->identifier();
if (!document->frame()->script().canExecuteScripts(ReasonForCallingCanExecuteScripts::AboutToExecuteScript))
return false;
if (scriptType == ScriptType::Classic && isScriptPreventedByAttributes())
return false;
// According to the spec, the module tag ignores the "charset" attribute as the same to the worker's
// importScript. But WebKit supports the "charset" for importScript intentionally. So to be consistent,
// even for the module tags, we handle the "charset" attribute.
if (auto attributeValue = charsetAttributeValue(); !attributeValue.isEmpty())
m_characterEncoding = WTF::move(attributeValue);
else
m_characterEncoding = document->charset();
switch (scriptType) {
case ScriptType::Classic: {
if (hasSourceAttribute()) {
if (!requestClassicScript(sourceAttributeValue()))
return false;
potentiallyBlockRendering();
}
break;
}
case ScriptType::Module: {
if (!requestModuleScript(sourceText, scriptStartPosition))
return false;
potentiallyBlockRendering();
break;
}
case ScriptType::ImportMap:
case ScriptType::SpeculationRules: {
// If the element has a source attribute, queue a task to fire an event named error at the element, and return.
if (hasSourceAttribute()) {
element->protectedDocument()->checkedEventLoop()->queueTask(TaskSource::DOMManipulation, [protectedThis = Ref { *this }] {
protectedThis->dispatchErrorEvent();
});
return false;
}
break;
}
}
updateTaintedOriginFromSourceURL();
// All the inlined module script is handled by requestModuleScript. It produces LoadableModuleScript and inlined module script
// is handled as the same to the external module script.
bool isClassicExternalScript = scriptType == ScriptType::Classic && hasSourceAttribute();
bool isParserInsertedDeferredScript = ((isClassicExternalScript && hasDeferAttribute()) || scriptType == ScriptType::Module)
&& m_parserInserted == ParserInserted::Yes && !hasAsyncAttribute();
if (isParserInsertedDeferredScript) {
m_willExecuteWhenDocumentFinishedParsing = true;
m_willBeParserExecuted = true;
} else if (isClassicExternalScript && m_parserInserted == ParserInserted::Yes && !hasAsyncAttribute()) {
ASSERT(scriptType == ScriptType::Classic);
m_willBeParserExecuted = true;
} else if ((isClassicExternalScript || scriptType == ScriptType::Module) && !hasAsyncAttribute() && !m_forceAsync) {
m_willExecuteInOrder = true;
ASSERT(m_loadableScript);
document->protectedScriptRunner()->queueScriptForExecution(*this, *protectedLoadableScript(), ScriptRunner::IN_ORDER_EXECUTION);
} else if (hasSourceAttribute() || scriptType == ScriptType::Module) {
ASSERT(m_loadableScript);
ASSERT(hasAsyncAttribute() || m_forceAsync);
document->protectedScriptRunner()->queueScriptForExecution(*this, *protectedLoadableScript(), ScriptRunner::ASYNC_EXECUTION);
} else if (!hasSourceAttribute() && m_parserInserted == ParserInserted::Yes && !document->haveStylesheetsLoaded()) {
ASSERT(scriptType == ScriptType::Classic || scriptType == ScriptType::ImportMap || scriptType == ScriptType::SpeculationRules);
m_willBeParserExecuted = true;
m_readyToBeParserExecuted = true;
} else {
ASSERT(scriptType == ScriptType::Classic || scriptType == ScriptType::ImportMap || scriptType == ScriptType::SpeculationRules);
TextPosition position = document->isInDocumentWrite() ? TextPosition() : scriptStartPosition;
if (scriptType == ScriptType::Classic)
executeClassicScript(ScriptSourceCode(sourceText, m_taintedOrigin, URL(document->url()), position, JSC::SourceProviderSourceType::Program, InlineClassicScript::create(*this)));
else if (scriptType == ScriptType::ImportMap)
registerImportMap(ScriptSourceCode(sourceText, m_taintedOrigin, URL(document->url()), position, JSC::SourceProviderSourceType::ImportMap));
else
registerSpeculationRules(ScriptSourceCode(sourceText, m_taintedOrigin, URL(document->url()), position, JSC::SourceProviderSourceType::Program));
}
return true;
}
void ScriptElement::updateTaintedOriginFromSourceURL()
{
if (m_taintedOrigin == JSC::SourceTaintedOrigin::KnownTainted)
return;
Ref document = element().document();
RefPtr page = document->page();
if (!page)
return;
if (!page->requiresScriptTrackingPrivacyProtections(hasSourceAttribute() ? document->completeURL(sourceAttributeValue()) : document->url()))
return;
m_taintedOrigin = JSC::SourceTaintedOrigin::KnownTainted;
}
bool ScriptElement::requestClassicScript(const String& sourceURL)
{
Ref element = this->element();
ASSERT(element->isConnected());
ASSERT(!m_loadableScript);
Ref document = element->document();
if (!StringView(sourceURL).containsOnly<isASCIIWhitespace<char16_t>>()) {
auto script = LoadableClassicScript::create(element->nonce(), element->attributeWithoutSynchronization(HTMLNames::integrityAttr), referrerPolicy(), fetchPriority(),
element->attributeWithoutSynchronization(HTMLNames::crossoriginAttr), scriptCharset(), element->localName(), element->isInUserAgentShadowTree(), hasAsyncAttribute());
auto scriptURL = document->completeURL(sourceURL);
document->willLoadScriptElement(scriptURL);
if (!document->checkedContentSecurityPolicy()->allowNonParserInsertedScripts(scriptURL, URL(), m_startLineNumber, element->nonce(), script->integrity(), String(), m_parserInserted))
return false;
if (script->load(document, scriptURL)) {
m_loadableScript = WTF::move(script);
m_isExternalScript = true;
}
}
if (m_loadableScript)
return true;
document->eventLoop().queueTask(TaskSource::DOMManipulation, [protectedThis = Ref { *this }] {
protectedThis->dispatchErrorEvent();
});
return false;
}
bool ScriptElement::requestModuleScript(const String& sourceText, const TextPosition& scriptStartPosition)
{
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#cors-settings-attributes
// Module is always CORS request. If attribute is not given, it should be same-origin credential.
Ref element = this->element();
Ref document = element->document();
auto nonce = element->nonce();
auto crossOriginMode = element->attributeWithoutSynchronization(HTMLNames::crossoriginAttr);
if (crossOriginMode.isNull())
crossOriginMode = ScriptElementCachedScriptFetcher::defaultCrossOriginModeForModule;
if (hasSourceAttribute()) {
ASSERT(element->isConnected());
String sourceURL = sourceAttributeValue();
if (StringView(sourceURL).containsOnly<isASCIIWhitespace<char16_t>>()) {
dispatchErrorEvent();
return false;
}
auto moduleScriptRootURL = document->completeURL(sourceURL);
if (!moduleScriptRootURL.isValid()) {
dispatchErrorEvent();
return false;
}
m_isExternalScript = true;
AtomString integrity = element->attributeWithoutSynchronization(HTMLNames::integrityAttr);
if (integrity.isNull())
integrity = AtomString { document->globalObject()->importMap().integrityForURL(moduleScriptRootURL) };
Ref script = LoadableModuleScript::create(LoadableModuleScript::IsInline::No, nonce, integrity, referrerPolicy(), fetchPriority(), crossOriginMode,
scriptCharset(), element->localName(), element->isInUserAgentShadowTree());
m_loadableScript = script.copyRef();
if (RefPtr frame = element->document().frame())
frame->checkedScript()->loadModuleScript(script, moduleScriptRootURL, script->parameters());
return true;
}
Ref script = LoadableModuleScript::create(LoadableModuleScript::IsInline::Yes, nonce, emptyAtom(), referrerPolicy(), fetchPriority(), crossOriginMode, scriptCharset(), element->localName(), element->isInUserAgentShadowTree());
TextPosition position = document->isInDocumentWrite() ? TextPosition() : scriptStartPosition;
ScriptSourceCode sourceCode(sourceText, m_taintedOrigin, URL(document->url()), position, JSC::SourceProviderSourceType::Module, script.copyRef());
ASSERT(document->contentSecurityPolicy());
{
CheckedRef contentSecurityPolicy = *document->contentSecurityPolicy();
if (!contentSecurityPolicy->allowNonParserInsertedScripts(URL(), document->url(), m_startLineNumber, element->nonce(), script->parameters().integrity(), sourceCode.source(), m_parserInserted))
return false;
if (!contentSecurityPolicy->allowInlineScript(document->url().string(), m_startLineNumber, sourceCode.source(), element, nonce, element->isInUserAgentShadowTree()))
return false;
}
m_loadableScript = script.copyRef();
if (RefPtr frame = document->frame())
frame->checkedScript()->loadModuleScript(script, sourceCode);
return true;
}
void ScriptElement::executeClassicScript(const ScriptSourceCode& sourceCode)
{
RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(ScriptDisallowedScope::InMainThread::isScriptAllowed());
ASSERT(m_alreadyStarted);
if (sourceCode.isEmpty())
return;
Ref element = this->element();
Ref document = element->document();
if (!m_isExternalScript) {
ASSERT(document->contentSecurityPolicy());
CheckedRef contentSecurityPolicy = *document->contentSecurityPolicy();
if (!contentSecurityPolicy->allowNonParserInsertedScripts(URL(), document->url(), m_startLineNumber, element->nonce(), emptyString(), sourceCode.source(), m_parserInserted))
return;
if (!contentSecurityPolicy->allowInlineScript(document->url().string(), m_startLineNumber, sourceCode.source(), element, element->nonce(), element->isInUserAgentShadowTree()))
return;
}
RefPtr frame = document->frame();
if (!frame)
return;
IgnoreDestructiveWriteCountIncrementer ignoreDestructiveWriteCountIncrementer(m_isExternalScript ? document.ptr() : nullptr);
CurrentScriptIncrementer currentScriptIncrementer(document, *this);
WTFBeginSignpost(this, ExecuteScriptElement, "executing classic script from URL: %" PRIVATE_LOG_STRING " async: %d defer: %d", m_isExternalScript ? sourceCode.url().string().utf8().data() : "inline", hasAsyncAttribute(), hasDeferAttribute());
frame->checkedScript()->evaluateIgnoringException(sourceCode);
WTFEndSignpost(this, ExecuteScriptElement);
}
void ScriptElement::registerImportMap(const ScriptSourceCode& sourceCode)
{
// https://html.spec.whatwg.org/#register-an-import-map
ASSERT(m_alreadyStarted);
ASSERT(scriptType() == ScriptType::ImportMap);
Ref element = this->element();
Ref document = element->document();
RefPtr frame = document->frame();
if (sourceCode.isEmpty()) {
dispatchErrorEvent();
return;
}
if (!m_isExternalScript) {
ASSERT(document->contentSecurityPolicy());
CheckedRef contentSecurityPolicy = *document->contentSecurityPolicy();
if (!contentSecurityPolicy->allowNonParserInsertedScripts(URL(), document->url(), m_startLineNumber, element->nonce(), emptyString(), sourceCode.source(), m_parserInserted))
return;
if (!contentSecurityPolicy->allowInlineScript(document->url().string(), m_startLineNumber, sourceCode.source(), element, element->nonce(), element->isInUserAgentShadowTree()))
return;
}
if (!frame)
return;
WTFBeginSignpost(this, RegisterImportMap, "registering import-map from URL: %" PRIVATE_LOG_STRING " async: %d defer: %d", m_isExternalScript ? sourceCode.url().string().utf8().data() : "inline", hasAsyncAttribute(), hasDeferAttribute());
frame->checkedScript()->registerImportMap(sourceCode, document->baseURL());
WTFEndSignpost(this, RegisterImportMap);
}
void ScriptElement::executeModuleScript(LoadableModuleScript& loadableModuleScript)
{
// https://html.spec.whatwg.org/multipage/scripting.html#execute-the-script-block
ASSERT(!loadableModuleScript.hasError());
Ref document = element().document();
RefPtr frame = document->frame();
if (!frame)
return;
IgnoreDestructiveWriteCountIncrementer ignoreDestructiveWriteCountIncrementer(document.ptr());
CurrentScriptIncrementer currentScriptIncrementer(document, *this);
WTFBeginSignpost(this, ExecuteScriptElement, "executing module script");
frame->script().linkAndEvaluateModuleScript(loadableModuleScript);
WTFEndSignpost(this, ExecuteScriptElement, "executing module script");
}
void ScriptElement::dispatchLoadEventRespectingUserGestureIndicator()
{
if (MonotonicTime::now() - m_creationTime > maxUserGesturePropagationTime) {
dispatchLoadEvent();
return;
}
UserGestureIndicator indicator(m_userGestureToken);
dispatchLoadEvent();
}
void ScriptElement::executeScriptAndDispatchEvent(LoadableScript& loadableScript)
{
if (auto error = loadableScript.takeError()) {
// There are three types of errors in script loading, fetch error, parse error, and script error.
// 1. Fetch error dispatches an error event on <script> tag, but not on window.
// 2. Parse error dispatches an error event on window, but not on <script>. And
// it still dispatches a load event on <script>.
// 3. Script error dispatches an error event on window.
switch (error->type) {
// Fetch error
case LoadableScript::ErrorType::Fetch:
case LoadableScript::ErrorType::CrossOriginLoad:
case LoadableScript::ErrorType::MIMEType:
case LoadableScript::ErrorType::Nosniff:
case LoadableScript::ErrorType::FailedIntegrityCheck: {
// https://html.spec.whatwg.org/multipage/scripting.html#execute-the-script-block
// When the script is "null" due to a fetch error, an error event
// should be dispatched for the script element.
if (std::optional<LoadableScript::ConsoleMessage> message = error->consoleMessage)
element().protectedDocument()->addConsoleMessage(message->source, message->level, message->message);
dispatchErrorEvent();
break;
}
// Parse error
case LoadableScript::ErrorType::Resolve: {
if (RefPtr frame = element().document().frame())
frame->checkedScript()->reportExceptionFromScriptError(error.value(), loadableScript.isModuleScript());
if (!loadableScript.isInlineModule())
dispatchLoadEventRespectingUserGestureIndicator();
break;
}
// Script error
case LoadableScript::ErrorType::Script: {
// https://html.spec.whatwg.org/multipage/webappapis.html#report-the-exception
// An error value is present when there is a load failure that was
// not triggered during fetching. In this case, we need to report
// the exception to the global object.
if (RefPtr frame = element().document().frame())
frame->checkedScript()->reportExceptionFromScriptError(error.value(), loadableScript.isModuleScript());
break;
}
}
} else if (!loadableScript.wasCanceled()) {
loadableScript.execute(*this);
if (!loadableScript.isInlineModule())
dispatchLoadEventRespectingUserGestureIndicator();
}
}
void ScriptElement::executePendingScript(PendingScript& pendingScript)
{
unblockRendering();
RefPtr loadableScript = pendingScript.loadableScript();
Ref document = element().document();
if (document->identifier() != m_preparationTimeDocumentIdentifier) {
document->addConsoleMessage(MessageSource::Security, MessageLevel::Error, "Not executing script because it moved between documents during fetching"_s);
} else {
if (loadableScript)
executeScriptAndDispatchEvent(*loadableScript);
else {
ASSERT(!pendingScript.hasError());
ASSERT_WITH_MESSAGE(scriptType() == ScriptType::Classic || scriptType() == ScriptType::ImportMap || (scriptType() == ScriptType::SpeculationRules && document->settings().speculationRulesPrefetchEnabled()), "Module script always have a loadableScript pointer.");
if (scriptType() == ScriptType::Classic)
executeClassicScript(ScriptSourceCode(scriptContent(), m_taintedOrigin, URL(document->url()), pendingScript.startingPosition(), JSC::SourceProviderSourceType::Program, InlineClassicScript::create(*this)));
else if (scriptType() == ScriptType::ImportMap)
registerImportMap(ScriptSourceCode(scriptContent(), m_taintedOrigin, URL(document->url()), pendingScript.startingPosition(), JSC::SourceProviderSourceType::ImportMap));
else
registerSpeculationRules(ScriptSourceCode(scriptContent(), m_taintedOrigin, URL(document->url()), pendingScript.startingPosition(), JSC::SourceProviderSourceType::Program));
dispatchLoadEventRespectingUserGestureIndicator();
}
}
}
bool ScriptElement::ignoresLoadRequest() const
{
return m_alreadyStarted || m_isExternalScript || m_parserInserted == ParserInserted::Yes || !element().isConnected();
}
String ScriptElement::scriptContent() const
{
return TextNodeTraversal::childTextContent(protectedElement());
}
void ScriptElement::setTrustedScriptText(const String& text)
{
m_trustedScriptText = text;
}
void ScriptElement::ref() const
{
element().ref();
}
void ScriptElement::deref() const
{
element().deref();
}
bool isScriptElement(Node& node)
{
return is<HTMLScriptElement>(node) || is<SVGScriptElement>(node);
}
ScriptElement* dynamicDowncastScriptElement(Element& element)
{
if (auto* htmlElement = dynamicDowncast<HTMLScriptElement>(element))
return htmlElement;
return dynamicDowncast<SVGScriptElement>(element);
}
// https://html.spec.whatwg.org/C#register-speculation-rules
void ScriptElement::registerSpeculationRules(const ScriptSourceCode& sourceCode)
{
ASSERT(m_alreadyStarted);
ASSERT(scriptType() == ScriptType::SpeculationRules);
Ref element = this->element();
Ref document = element->document();
if (!document->settings().speculationRulesPrefetchEnabled())
return;
RefPtr frame = document->frame();
if (sourceCode.isEmpty()) {
dispatchErrorEvent();
if (frame)
reportSpeculationRulesError(*frame, "Speculation rules script has empty content"_s);
return;
}
if (!m_isExternalScript) {
CheckedPtr contentSecurityPolicy = document->contentSecurityPolicy();
if (!contentSecurityPolicy)
return;
if (!contentSecurityPolicy->allowNonParserInsertedScripts(URL(), document->url(), m_startLineNumber, element->nonce(), emptyString(), sourceCode.source(), m_parserInserted))
return;
if (!contentSecurityPolicy->allowInlineScript(document->url().string(), m_startLineNumber, sourceCode.source(), element, element->nonce(), element->isInUserAgentShadowTree()))
return;
}
if (!frame)
return;
if (frame->checkedScript()->registerSpeculationRules(element.get(), sourceCode, document->baseURL()))
document->considerSpeculationRules();
else {
dispatchErrorEvent();
reportSpeculationRulesError(*frame, "Failed to register speculation rules"_s);
}
}
// https://html.spec.whatwg.org/multipage/webappapis.html#unregister-speculation-rules
void ScriptElement::unregisterSpeculationRules()
{
if (scriptType() != ScriptType::SpeculationRules)
return;
Ref element = this->element();
Ref document = element->document();
if (!document->settings().speculationRulesPrefetchEnabled())
return;
auto removedURLs = document->speculationRules()->unregisterSpeculationRules(element);
// Remove prefetched resources that were initiated by the removed rules.
if (RefPtr frame = document->frame()) {
Ref prefetcher = frame->loader().documentPrefetcher();
for (const auto& url : removedURLs)
prefetcher->removePrefetch(url);
}
document->considerSpeculationRules();
}
}