mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-12-07 21:59:54 +00:00
LibWeb: Implement slot validation for HTMLScriptElement
This should be the last section missing in the TrustedType spec.
This commit is contained in:
parent
1d1182cad8
commit
901afee50b
Notes:
github-actions[bot]
2025-12-01 08:55:48 +00:00
Author: https://github.com/tete17
Commit: 901afee50b
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/6630
Reviewed-by: https://github.com/gmta ✅
6 changed files with 567 additions and 33 deletions
|
|
@ -82,6 +82,31 @@ void HTMLScriptElement::attribute_changed(FlyString const& name, Optional<String
|
|||
}
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/trusted-types/#prepare-script-text
|
||||
WebIDL::ExceptionOr<void> HTMLScriptElement::prepare_script_text()
|
||||
{
|
||||
// 1. Let sink be "HTMLScriptElement text" if script is an HTMLScriptElement; otherwise "SVGScriptElement text".
|
||||
constexpr auto sink = TrustedTypes::InjectionSink::HTMLScriptElement_text;
|
||||
|
||||
// 2. If script’s script text value is not equal to its child text content,
|
||||
// set script’s script text to the result of executing get Trusted Type compliant string, with the following arguments:
|
||||
// TrustedScriptURL as expectedType,
|
||||
// script’s Document’s relevant global object as global,
|
||||
// script’s child text content attribute value as input,
|
||||
// sink,
|
||||
// 'script' as sinkGroup.
|
||||
if (m_script_text != child_text_content()) {
|
||||
m_script_text = TRY(TrustedTypes::get_trusted_type_compliant_string(
|
||||
TrustedTypes::TrustedTypeName::TrustedScriptURL,
|
||||
HTML::relevant_global_object(document()),
|
||||
child_text_content(),
|
||||
sink,
|
||||
TrustedTypes::Script.to_string()));
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void HTMLScriptElement::begin_delaying_document_load_event(DOM::Document& document)
|
||||
{
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#concept-script-script
|
||||
|
|
@ -170,6 +195,7 @@ void HTMLScriptElement::execute_script()
|
|||
dispatch_event(DOM::Event::create(realm(), HTML::EventNames::load));
|
||||
}
|
||||
|
||||
// https://w3c.github.io/trusted-types/dist/spec/#slot-value-verification
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#prepare-a-script
|
||||
// https://whatpr.org/html/9893/scripting.html#prepare-a-script
|
||||
void HTMLScriptElement::prepare_script()
|
||||
|
|
@ -191,22 +217,26 @@ void HTMLScriptElement::prepare_script()
|
|||
m_force_async = true;
|
||||
}
|
||||
|
||||
// 5. Let source text be el's child text content.
|
||||
auto source_text = child_text_content();
|
||||
// 5. Execute the Prepare the script text algorithm on el. If that algorithm threw an error, then return.
|
||||
if (prepare_script_text().is_exception())
|
||||
return;
|
||||
|
||||
// 6. Let source text be el’s script text value.
|
||||
auto source_text = m_script_text;
|
||||
auto source_text_utf8 = source_text.to_utf8_but_should_be_ported_to_utf16();
|
||||
|
||||
// 6. If el has no src attribute, and source text is the empty string, then return.
|
||||
// 7. If el has no src attribute, and source text is the empty string, then return.
|
||||
if (!has_attribute(HTML::AttributeNames::src) && source_text.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 7. If el is not connected, then return.
|
||||
// 8. If el is not connected, then return.
|
||||
if (!is_connected()) {
|
||||
dbgln("HTMLScriptElement: Refusing to run script because the element is not connected.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 8. If any of the following are true:
|
||||
// 9. 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
|
||||
|
|
@ -230,61 +260,61 @@ void HTMLScriptElement::prepare_script()
|
|||
script_block_type = MUST(String::formatted("text/{}", maybe_language_attribute.value()));
|
||||
}
|
||||
|
||||
// 9. If the script block's type string is a JavaScript MIME type essence match,
|
||||
// 10. If the script block's type string is a JavaScript MIME type essence match,
|
||||
if (MimeSniff::is_javascript_mime_type_essence_match(script_block_type)) {
|
||||
// then set el's type to "classic".
|
||||
m_script_type = ScriptType::Classic;
|
||||
}
|
||||
// 10. Otherwise, if the script block's type string is an ASCII case-insensitive match for the string "module",
|
||||
// 11. Otherwise, if the script block's type string is an ASCII case-insensitive match for the string "module",
|
||||
else if (script_block_type.equals_ignoring_ascii_case("module"sv)) {
|
||||
// then set el's type to "module".
|
||||
m_script_type = ScriptType::Module;
|
||||
}
|
||||
// 11. Otherwise, if the script block's type string is an ASCII case-insensitive match for the string "importmap",
|
||||
// 12. Otherwise, if the script block's type string is an ASCII case-insensitive match for the string "importmap",
|
||||
else if (script_block_type.equals_ignoring_ascii_case("importmap"sv)) {
|
||||
// then set el's type to "importmap".
|
||||
m_script_type = ScriptType::ImportMap;
|
||||
}
|
||||
// FIXME: 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".
|
||||
// 13. Otherwise, return. (No script is executed, and el's type is left as null.)
|
||||
// FIXME: 13. 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".
|
||||
// 14. Otherwise, return. (No script is executed, and el's type is left as null.)
|
||||
else {
|
||||
VERIFY(m_script_type == ScriptType::Null);
|
||||
return;
|
||||
}
|
||||
|
||||
// 14. If parser document is non-null, then set el's parser document back to parser document and set el's force async to false.
|
||||
// 15. If parser document is non-null, then set el's parser document back to parser document and set el's force async to false.
|
||||
if (parser_document) {
|
||||
m_parser_document = parser_document;
|
||||
m_force_async = false;
|
||||
}
|
||||
|
||||
// 15. Set el's already started to true.
|
||||
// 16. Set el's already started to true.
|
||||
m_already_started = true;
|
||||
|
||||
// 16. Set el's preparation-time document to its node document.
|
||||
// 17. Set el's preparation-time document to its node document.
|
||||
m_preparation_time_document = &document();
|
||||
|
||||
// 17. If parser document is non-null, and parser document is not equal to el's preparation-time document, then return.
|
||||
// 18. If parser document is non-null, and parser document is not equal to el's preparation-time document, then return.
|
||||
if (parser_document != nullptr && parser_document != m_preparation_time_document) {
|
||||
dbgln("HTMLScriptElement: Refusing to run script because the parser document is not the same as the preparation time document.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 18. If scripting is disabled for el, then return.
|
||||
// 19. If scripting is disabled for el, then return.
|
||||
if (is_scripting_disabled()) {
|
||||
dbgln("HTMLScriptElement: Refusing to run script because scripting is disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 19. If el has a nomodule content attribute and its type is "classic", then return.
|
||||
// 20. If el has a nomodule content attribute and its type is "classic", then return.
|
||||
if (m_script_type == ScriptType::Classic && has_attribute(HTML::AttributeNames::nomodule)) {
|
||||
dbgln("HTMLScriptElement: Refusing to run classic script because it has the nomodule attribute.");
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: 20. Let cspType be "script speculationrules" if el's type is "speculationrules"; otherwise, "script".
|
||||
// FIXME: 21. Let cspType be "script speculationrules" if el's type is "speculationrules"; otherwise, "script".
|
||||
|
||||
// 21. If el does not have a src content attribute, and the Should element's inline behavior be blocked by Content
|
||||
// 22. If el does not have a src content attribute, and the Should element's inline behavior be blocked by Content
|
||||
// Security Policy? algorithm returns "Blocked" when given el, cspType, and source text, then return [CSP]
|
||||
if (!has_attribute(AttributeNames::src)
|
||||
&& ContentSecurityPolicy::should_elements_inline_type_behavior_be_blocked_by_content_security_policy(realm(), *this, ContentSecurityPolicy::Directives::Directive::InlineType::Script, source_text_utf8) == ContentSecurityPolicy::Directives::Directive::Result::Blocked) {
|
||||
|
|
@ -292,7 +322,7 @@ void HTMLScriptElement::prepare_script()
|
|||
return;
|
||||
}
|
||||
|
||||
// 22. If el has an event attribute and a for attribute, and el's type is "classic", then:
|
||||
// 23. If el has an event attribute and a for attribute, and el's type is "classic", then:
|
||||
if (m_script_type == ScriptType::Classic && has_attribute(HTML::AttributeNames::event) && has_attribute(HTML::AttributeNames::for_)) {
|
||||
// 1. Let for be the value of el's' for attribute.
|
||||
auto for_ = get_attribute_value(HTML::AttributeNames::for_);
|
||||
|
|
@ -318,7 +348,7 @@ void HTMLScriptElement::prepare_script()
|
|||
}
|
||||
}
|
||||
|
||||
// 23. If el has a charset attribute, then let encoding be the result of getting an encoding from the value of the charset attribute.
|
||||
// 24. If el has a charset attribute, then let encoding be the result of getting an encoding from the value of the charset attribute.
|
||||
// If el does not have a charset attribute, or if getting an encoding failed, then let encoding be el's node document's the encoding.
|
||||
Optional<String> encoding;
|
||||
|
||||
|
|
@ -334,34 +364,34 @@ void HTMLScriptElement::prepare_script()
|
|||
|
||||
VERIFY(encoding.has_value());
|
||||
|
||||
// 24. Let classic script CORS setting be the current state of el's crossorigin content attribute.
|
||||
// 25. Let classic script CORS setting be the current state of el's crossorigin content attribute.
|
||||
auto classic_script_cors_setting = m_crossorigin;
|
||||
|
||||
// 25. Let module script credentials mode be the CORS settings attribute credentials mode for el's crossorigin content attribute.
|
||||
// 26. Let module script credentials mode be the CORS settings attribute credentials mode for el's crossorigin content attribute.
|
||||
auto module_script_credential_mode = cors_settings_attribute_credentials_mode(m_crossorigin);
|
||||
|
||||
// 26. Let cryptographic nonce be el's [[CryptographicNonce]] internal slot's value.
|
||||
// 27. Let cryptographic nonce be el's [[CryptographicNonce]] internal slot's value.
|
||||
auto cryptographic_nonce = m_cryptographic_nonce;
|
||||
|
||||
// 27. If el has an integrity attribute, then let integrity metadata be that attribute's value.
|
||||
// 28. If el has an integrity attribute, then let integrity metadata be that attribute's value.
|
||||
// Otherwise, let integrity metadata be the empty string.
|
||||
String integrity_metadata;
|
||||
if (auto maybe_integrity = attribute(HTML::AttributeNames::integrity); maybe_integrity.has_value()) {
|
||||
integrity_metadata = *maybe_integrity;
|
||||
}
|
||||
|
||||
// 28. Let referrer policy be the current state of el's referrerpolicy content attribute.
|
||||
// 29. Let referrer policy be the current state of el's referrerpolicy content attribute.
|
||||
auto referrer_policy = m_referrer_policy;
|
||||
|
||||
// 29. Let fetch priority be the current state of el's fetchpriority content attribute.
|
||||
// 30. Let fetch priority be the current state of el's fetchpriority content attribute.
|
||||
auto fetch_priority = Fetch::Infrastructure::request_priority_from_string(get_attribute_value(HTML::AttributeNames::fetchpriority)).value_or(Fetch::Infrastructure::Request::Priority::Auto);
|
||||
|
||||
// 30. Let parser metadata be "parser-inserted" if el is parser-inserted, and "not-parser-inserted" otherwise.
|
||||
// 31. Let parser metadata be "parser-inserted" if el is parser-inserted, and "not-parser-inserted" otherwise.
|
||||
auto parser_metadata = is_parser_inserted()
|
||||
? Fetch::Infrastructure::Request::ParserMetadata::ParserInserted
|
||||
: Fetch::Infrastructure::Request::ParserMetadata::NotParserInserted;
|
||||
|
||||
// 31. Let options be a script fetch options whose cryptographic nonce is cryptographic nonce,
|
||||
// 32. Let options be a script fetch options whose cryptographic nonce is cryptographic nonce,
|
||||
// integrity metadata is integrity metadata, parser metadata is parser metadata,
|
||||
// credentials mode is module script credentials mode, referrer policy is referrer policy,
|
||||
// and fetch priority is fetch priority.
|
||||
|
|
@ -374,10 +404,10 @@ void HTMLScriptElement::prepare_script()
|
|||
.fetch_priority = move(fetch_priority),
|
||||
};
|
||||
|
||||
// 32. Let settings object be el's node document's relevant settings object.
|
||||
// 33. Let settings object be el's node document's relevant settings object.
|
||||
auto& settings_object = document().relevant_settings_object();
|
||||
|
||||
// 33. If el has a src content attribute, then:
|
||||
// 34. If el has a src content attribute, then:
|
||||
if (has_attribute(HTML::AttributeNames::src)) {
|
||||
// 1. If el's type is "importmap" or "speculationrules", then:
|
||||
// FIXME: Add "speculationrules" support.
|
||||
|
|
@ -452,7 +482,7 @@ void HTMLScriptElement::prepare_script()
|
|||
}
|
||||
}
|
||||
|
||||
// 34. If el does not have a src content attribute:
|
||||
// 35. If el does not have a src content attribute:
|
||||
if (!has_attribute(HTML::AttributeNames::src)) {
|
||||
// 1. Let base URL be el's node document's document base URL.
|
||||
auto base_url = document().base_url();
|
||||
|
|
@ -498,7 +528,7 @@ void HTMLScriptElement::prepare_script()
|
|||
// FIXME: -> "speculationrules"
|
||||
}
|
||||
|
||||
// 35. If el's type is "classic" and el has a src attribute, or el's type is "module":
|
||||
// 36. If el's type is "classic" and el has a src attribute, or el's type is "module":
|
||||
if ((m_script_type == ScriptType::Classic && has_attribute(HTML::AttributeNames::src)) || m_script_type == ScriptType::Module) {
|
||||
// 1. Assert: el's result is "uninitialized".
|
||||
// FIXME: I believe this step to be a spec bug, and it should be removed: https://github.com/whatwg/html/issues/8534
|
||||
|
|
@ -572,7 +602,7 @@ void HTMLScriptElement::prepare_script()
|
|||
}
|
||||
}
|
||||
|
||||
// 36. Otherwise:
|
||||
// 37. Otherwise:
|
||||
else {
|
||||
// 1. Assert: el's result is not "uninitialized".
|
||||
VERIFY(!m_result.has<ResultState::Uninitialized>());
|
||||
|
|
|
|||
|
|
@ -92,6 +92,9 @@ private:
|
|||
|
||||
virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) override;
|
||||
|
||||
// https://www.w3.org/TR/trusted-types/#prepare-script-text
|
||||
WebIDL::ExceptionOr<void> prepare_script_text();
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element
|
||||
void prepare_script();
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 34 tests
|
||||
|
||||
34 Pass
|
||||
Pass The HTMLScriptElement is initially trusted.
|
||||
Pass Script source set via TrustedScript sink HTMLScriptElement.innerText keeps trustworthiness.
|
||||
Pass Script source set via HTMLElement.innerText drops trustworthiness.
|
||||
Pass Script source set via TrustedScript sink HTMLScriptElement.textContent keeps trustworthiness.
|
||||
Pass Script source set Node.textContent drops trustworthiness.
|
||||
Pass Script source set via TrustedScript sink HTMLScriptElement.text keeps trustworthiness.
|
||||
Pass Script source set via TrustedHTML sink Element.innerHTML drops trustworthiness.
|
||||
Pass Script source set via TrustedHTML sink Element.setHTMLUnsafe() drops trustworthiness.
|
||||
Pass Splitting script source via Text.splitText() keeps trustworthiness.
|
||||
Pass Normalizing script source via Element.normalize() keeps trustworthiness.
|
||||
Pass Script source set via Node.nodeValue drops trustworthiness.
|
||||
Pass Setting script source via CharacterData.data drops trustworthiness.
|
||||
Pass Setting script source via CharacterData.appendData() drops trustworthiness.
|
||||
Pass Setting script source via CharacterData.insertData() drops trustworthiness.
|
||||
Pass Setting script source via CharacterData.replaceData() drops trustworthiness.
|
||||
Pass Setting script source via CharacterData.deleteData() drops trustworthiness.
|
||||
Pass Setting script source via CharacterData.before() drops trustworthiness.
|
||||
Pass Setting script source via CharacterData.after() drops trustworthiness.
|
||||
Pass Setting script source via CharacterData.remove() drops trustworthiness.
|
||||
Pass Setting script source via CharacterData.replaceWith() drops trustworthiness.
|
||||
Pass Setting script source via Node.appendChild() drops trustworthiness.
|
||||
Pass Setting script source via Node.insertBefore() drops trustworthiness.
|
||||
Pass Setting script source via Node.replaceChild() drops trustworthiness.
|
||||
Pass Setting script source via Node.removeChild() drops trustworthiness.
|
||||
Pass Setting script source via Element.prepend() drops trustworthiness.
|
||||
Pass Setting script source via Element.append() drops trustworthiness.
|
||||
Pass Setting script source via Element.replaceChildren() drops trustworthiness.
|
||||
Pass Setting script source via Element.moveBefore() drops trustworthiness.
|
||||
Pass Setting script source via TrustedHTML sink Node.insertAdjacentHTML() drops trustworthiness.
|
||||
Pass Setting script source via Node.insertAdjacentText() drops trustworthiness.
|
||||
Pass Setting script source via Range.insertNode() drops trustworthiness.
|
||||
Pass Setting script source via Range.deleteContents() drops trustworthiness.
|
||||
Pass Cloning a script via Node.cloneNode() drops trustworthiness.
|
||||
Pass Cloning a script via Range.cloneContents() drops trustworthiness.
|
||||
|
|
@ -0,0 +1,351 @@
|
|||
<!DOCTYPE html>
|
||||
<script src="../resources/testharness.js"></script>
|
||||
<script src="../resources/testharnessreport.js"></script>
|
||||
<script src="support/namespaces.js"></script>
|
||||
<script src="support/passthroughpolicy.js"></script>
|
||||
<script src="support/script-messages.js"></script>
|
||||
<link rel="help" href="https://w3c.github.io/trusted-types/dist/spec/#enforcement-in-scripts">
|
||||
<meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script';">
|
||||
<!-- This test modifies the source of a new (initially disconnected)
|
||||
HTMLScriptElement by various DOM APIs and verifies whether it will
|
||||
remain trustworthy. This can be done by checking whether the script
|
||||
is actually executed after insertion, because this page enforces
|
||||
Trusted Types without defining any default policy. -->
|
||||
<div id="container"></div>
|
||||
<script>
|
||||
promise_test(async t => {
|
||||
let message = await script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(LOG_RUN_MESSAGE);
|
||||
container.appendChild(script);
|
||||
});
|
||||
assert_equals(message, "RUN");
|
||||
}, "The HTMLScriptElement is initially trusted.");
|
||||
|
||||
promise_test(async t => {
|
||||
await promise_rejects_js(t, TypeError, script_messages_for(_ => {
|
||||
document.createElement("script").innerText = LOG_RUN_MESSAGE;
|
||||
}), "TrustedScript required.");
|
||||
let message = await script_message_for(_ => {
|
||||
let script = document.createElement("script");
|
||||
script.innerText = passthroughpolicy.createScript(LOG_RUN_MESSAGE);
|
||||
container.appendChild(script);
|
||||
});
|
||||
assert_equals(message, "RUN");
|
||||
}, "Script source set via TrustedScript sink HTMLScriptElement.innerText keeps trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
let script = document.createElement("script");
|
||||
Object.getOwnPropertyDescriptor(HTMLElement.prototype, "innerText").set.call(script, LOG_RUN_MESSAGE);
|
||||
assert_equals(script.innerText, LOG_RUN_MESSAGE, "TrustedScript not required.");
|
||||
await no_script_message_for(_ => {
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Script source set via HTMLElement.innerText drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await promise_rejects_js(t, TypeError, script_messages_for(_ => {
|
||||
document.createElement("script").textContent = LOG_RUN_MESSAGE;
|
||||
}), "TrustedScript required.");
|
||||
let message = await script_message_for(_ => {
|
||||
let script = document.createElement("script");
|
||||
script.textContent = passthroughpolicy.createScript(LOG_RUN_MESSAGE);
|
||||
container.appendChild(script);
|
||||
});
|
||||
assert_equals(message, "RUN");
|
||||
}, "Script source set via TrustedScript sink HTMLScriptElement.textContent keeps trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
let script = document.createElement("script");
|
||||
Object.getOwnPropertyDescriptor(Node.prototype, "textContent").set.call(script, LOG_RUN_MESSAGE);
|
||||
assert_equals(script.textContent, LOG_RUN_MESSAGE, "TrustedScript not required.");
|
||||
await no_script_message_for(_ => {
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Script source set Node.textContent drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await promise_rejects_js(t, TypeError, script_messages_for(_ => {
|
||||
document.createElement("script").text = LOG_RUN_MESSAGE;
|
||||
}), "TrustedScript required.");
|
||||
let message = await script_message_for(_ => {
|
||||
let script = document.createElement("script");
|
||||
script.text = passthroughpolicy.createScript(LOG_RUN_MESSAGE);
|
||||
container.appendChild(script);
|
||||
});
|
||||
assert_equals(message, "RUN");
|
||||
}, "Script source set via TrustedScript sink HTMLScriptElement.text keeps trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await promise_rejects_js(t, TypeError, script_messages_for(_ => {
|
||||
document.createElement("script").innerHTML = LOG_RUN_MESSAGE;
|
||||
}), "TrustedHTML required.");
|
||||
await no_script_message_for(_ => {
|
||||
let script = document.createElement("script");
|
||||
script.innerHTML = passthroughpolicy.createHTML(LOG_RUN_MESSAGE);
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Script source set via TrustedHTML sink Element.innerHTML drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await promise_rejects_js(t, TypeError, script_messages_for(_ => {
|
||||
document.createElement("script").setHTMLUnsafe(LOG_RUN_MESSAGE);
|
||||
}), "TrustedHTML required.");
|
||||
await no_script_message_for(_ => {
|
||||
let script = document.createElement("script");
|
||||
script.setHTMLUnsafe(passthroughpolicy.createHTML(LOG_RUN_MESSAGE));
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Script source set via TrustedHTML sink Element.setHTMLUnsafe() drops trustworthiness.");
|
||||
|
||||
if (HTMLScriptElement.prototype.setHTML) {
|
||||
promise_test(async t => {
|
||||
// https://wicg.github.io/sanitizer-api/#set-and-filter-html
|
||||
let script = document.createElement("script");
|
||||
script.setHTML(LOG_RUN_MESSAGE);
|
||||
assert_equals(script.text, "");
|
||||
}, "Script source cannot be set via Element.setHTML().");
|
||||
}
|
||||
|
||||
promise_test(async t => {
|
||||
let message = await script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(`${LOG_RUN_MESSAGE};;;`);
|
||||
script.firstChild.splitText(3);
|
||||
container.appendChild(script);
|
||||
});
|
||||
assert_equals(message, "RUN");
|
||||
}, "Splitting script source via Text.splitText() keeps trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
let message = await script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(`${LOG_RUN_MESSAGE};;;`);
|
||||
script.firstChild.splitText(3);
|
||||
script.normalize();
|
||||
container.appendChild(script);
|
||||
});
|
||||
assert_equals(message, "RUN");
|
||||
}, "Normalizing script source via Element.normalize() keeps trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(";");
|
||||
script.firstChild.nodeValue = LOG_RUN_MESSAGE;
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Script source set via Node.nodeValue drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(";");
|
||||
script.firstChild.data = LOG_RUN_MESSAGE;
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Setting script source via CharacterData.data drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(";");
|
||||
script.firstChild.appendData(LOG_RUN_MESSAGE);
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Setting script source via CharacterData.appendData() drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(";");
|
||||
script.firstChild.insertData(0, LOG_RUN_MESSAGE);
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Setting script source via CharacterData.insertData() drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(";");
|
||||
script.firstChild.replaceData(0, 0, LOG_RUN_MESSAGE);
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Setting script source via CharacterData.replaceData() drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(`//${LOG_RUN_MESSAGE}`);
|
||||
script.firstChild.deleteData(0, 2);
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Setting script source via CharacterData.deleteData() drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(";");
|
||||
script.firstChild.before(LOG_RUN_MESSAGE);
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Setting script source via CharacterData.before() drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(";");
|
||||
script.firstChild.after(LOG_RUN_MESSAGE);
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Setting script source via CharacterData.after() drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(`;;;${LOG_RUN_MESSAGE}`);
|
||||
script.firstChild.splitText(3);
|
||||
script.firstChild.remove();
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Setting script source via CharacterData.remove() drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(";");
|
||||
script.firstChild.replaceWith(document.createTextNode(LOG_RUN_MESSAGE));
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Setting script source via CharacterData.replaceWith() drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(";");
|
||||
script.appendChild(document.createTextNode(LOG_RUN_MESSAGE));
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Setting script source via Node.appendChild() drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(";");
|
||||
script.insertBefore(document.createTextNode(LOG_RUN_MESSAGE), script.firstChild);
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Setting script source via Node.insertBefore() drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(";");
|
||||
script.replaceChild(document.createTextNode(LOG_RUN_MESSAGE), script.firstChild);
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Setting script source via Node.replaceChild() drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(`;;;${LOG_RUN_MESSAGE}`);
|
||||
script.firstChild.splitText(3);
|
||||
script.removeChild(script.firstChild);
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Setting script source via Node.removeChild() drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(";");
|
||||
script.prepend(document.createTextNode(LOG_RUN_MESSAGE));
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Setting script source via Element.prepend() drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(";");
|
||||
script.append(document.createTextNode(LOG_RUN_MESSAGE));
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Setting script source via Element.append() drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(";");
|
||||
script.replaceChildren(document.createTextNode(LOG_RUN_MESSAGE));
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Setting script source via Element.replaceChildren() drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(";");
|
||||
let text = document.createTextNode(LOG_RUN_MESSAGE);
|
||||
// Per https://dom.spec.whatwg.org/#move, step 1, moveBefore requires
|
||||
// the two nodes to have the same shadow-including root.
|
||||
let root = document.createElement("div");
|
||||
root.appendChild(script);
|
||||
root.appendChild(text);
|
||||
script.moveBefore(text, script.firstChild);
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Setting script source via Element.moveBefore() drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await promise_rejects_js(t, TypeError, script_messages_for(_ => {
|
||||
document.createElement("script").insertAdjacentHTML("afterbegin", LOG_RUN_MESSAGE);
|
||||
}), "TrustedHTML required.");
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(";");
|
||||
script.insertAdjacentHTML("afterbegin", passthroughpolicy.createHTML(LOG_RUN_MESSAGE));
|
||||
container.appendChild(script);
|
||||
});
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(";");
|
||||
script.insertAdjacentHTML("beforeend", passthroughpolicy.createHTML(LOG_RUN_MESSAGE));
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Setting script source via TrustedHTML sink Node.insertAdjacentHTML() drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(";");
|
||||
script.insertAdjacentText("afterbegin", LOG_RUN_MESSAGE);
|
||||
container.appendChild(script);
|
||||
});
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(";");
|
||||
script.insertAdjacentText("beforeend", LOG_RUN_MESSAGE);
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Setting script source via Node.insertAdjacentText() drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(`;`);
|
||||
let range = new Range();
|
||||
range.selectNode(script.firstChild);
|
||||
range.insertNode(document.createTextNode(LOG_RUN_MESSAGE));
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Setting script source via Range.insertNode() drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(`//;;;${LOG_RUN_MESSAGE}`);
|
||||
script.firstChild.splitText(2);
|
||||
let range = new Range();
|
||||
range.setStart(script.firstChild, 0);
|
||||
range.setEnd(script.lastChild, 3);
|
||||
range.deleteContents();
|
||||
container.appendChild(script);
|
||||
});
|
||||
}, "Setting script source via Range.deleteContents() drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(`${LOG_RUN_MESSAGE}`);
|
||||
let clone = script.cloneNode(true);
|
||||
container.appendChild(clone);
|
||||
});
|
||||
}, "Cloning a script via Node.cloneNode() drops trustworthiness.");
|
||||
|
||||
promise_test(async t => {
|
||||
await no_script_message_for(_ => {
|
||||
let div = document.createElement("div");
|
||||
let script = create_html_script_with_trusted_source_text(`${LOG_RUN_MESSAGE}`);
|
||||
div.appendChild(script);
|
||||
let range = new Range();
|
||||
range.selectNode(script);
|
||||
let documentFragment = range.cloneContents();
|
||||
container.appendChild(documentFragment.firstElementChild);
|
||||
});
|
||||
}, "Cloning a script via Range.cloneContents() drops trustworthiness.");
|
||||
</script>
|
||||
</body>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
'use strict'
|
||||
|
||||
const passthroughpolicy = trustedTypes.createPolicy("passthroughpolicy", {
|
||||
createHTML: s => s,
|
||||
createScript: s => s,
|
||||
createScriptURL: s => s,
|
||||
});
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
'use strict'
|
||||
|
||||
const LOG_RUN_MESSAGE = `window.log_message("RUN")`;
|
||||
|
||||
function create_html_script_with_trusted_source_text(source_text) {
|
||||
let script = document.createElement("script");
|
||||
script.text = passthroughpolicy.createScript(source_text);
|
||||
return script;
|
||||
}
|
||||
|
||||
function create_html_script_with_untrusted_source_text(source_text) {
|
||||
let script = document.createElement("script");
|
||||
// Setting script source via Node.appendChild() drops trustworthiness.
|
||||
script.appendChild(document.createTextNode(source_text));
|
||||
return script;
|
||||
}
|
||||
|
||||
function create_svg_script_with_trusted_source_text(source_text) {
|
||||
// SVGScriptElement has no API to set its source while preserving its
|
||||
// trustworthiness. For now, we just expect a <script type="unknown"> tag
|
||||
// with the desired source to already be present in the page, so we can just
|
||||
// reuse it. See https://github.com/w3c/trusted-types/issues/512
|
||||
let script =
|
||||
Array.from(document.querySelectorAll("svg script[type='unknown']")).
|
||||
find(script => script.textContent === source_text);
|
||||
assert_true(!!script, `<script type="unknown">${source_text}</script> not found!`);
|
||||
script.remove();
|
||||
script.removeAttribute("type");
|
||||
return script;
|
||||
}
|
||||
|
||||
function create_svg_script_with_untrusted_source_text(source_text) {
|
||||
let script = document.createElementNS(NSURI_SVG, "script")
|
||||
// Setting script source via Node.appendChild() drops trustworthiness.
|
||||
script.appendChild(document.createTextNode(source_text));
|
||||
return script;
|
||||
}
|
||||
|
||||
// A generic helper that runs function fn and returns a promise resolving with
|
||||
// an array of received messages. A script forcing a "done" message is appended
|
||||
// after calling fn, to make sure that all the messages reported by fn have been
|
||||
// delivered.
|
||||
function script_messages_for(fn) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// Listen for messages.
|
||||
let messages = [];
|
||||
let exception = null;
|
||||
window.log_message = message => {
|
||||
if (message === "DONE") {
|
||||
window.log_message = null;
|
||||
if (exception) {
|
||||
reject(exception);
|
||||
} else {
|
||||
resolve(messages);
|
||||
}
|
||||
} else {
|
||||
messages.push(message);
|
||||
}
|
||||
};
|
||||
|
||||
// Execute the function.
|
||||
try {
|
||||
await fn();
|
||||
} catch(e) {
|
||||
exception = e;
|
||||
}
|
||||
|
||||
// Indicate the last message.
|
||||
// This is done by appending an inline script to make sure it is executed
|
||||
// after processing any previously inserted inline script. Additionally, we
|
||||
// delay by a double requestAnimationFrame to work around incompatible
|
||||
// interop bugs:
|
||||
// - WebKit/Chromium seems to give lower priority to module, so it looks
|
||||
// like the appended script should have type="module" here to work with
|
||||
// tests for inline modules.
|
||||
// - but Firefox does not allow type="importmaps" after a type="module" so
|
||||
// making the appended script a module would make importmap tests fail...
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1916277#c4
|
||||
requestAnimationFrame(_ => requestAnimationFrame(_ => {
|
||||
let script = create_html_script_with_trusted_source_text(`window.log_message("DONE")`);
|
||||
script.setAttribute("nonce", "script-messages");
|
||||
document.body.appendChild(script);
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
async function script_message_for(fn) {
|
||||
let messages = await script_messages_for(fn);
|
||||
assert_equals(messages.length, 1, `Number of messages (${messages})`);
|
||||
return messages[0];
|
||||
}
|
||||
|
||||
async function no_script_message_for(fn) {
|
||||
let messages = await script_messages_for(fn);
|
||||
assert_equals(messages.length, 0, `Number of messages (${messages})`);
|
||||
}
|
||||
|
||||
async function base64_hash_for_inline_script(source_text, algorithm) {
|
||||
const encoder = new TextEncoder();
|
||||
const data = encoder.encode(source_text);
|
||||
const hashBuffer = await window.crypto.subtle.digest(algorithm, data);
|
||||
const base64Array = (new Uint8Array(hashBuffer)).toBase64();
|
||||
return base64Array.toString();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue