Skip to content

Commit 901afee

Browse files
tete17gmta
authored andcommitted
LibWeb: Implement slot validation for HTMLScriptElement
This should be the last section missing in the TrustedType spec.
1 parent 1d1182c commit 901afee

File tree

6 files changed

+567
-33
lines changed

6 files changed

+567
-33
lines changed

Libraries/LibWeb/HTML/HTMLScriptElement.cpp

Lines changed: 63 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,31 @@ void HTMLScriptElement::attribute_changed(FlyString const& name, Optional<String
8282
}
8383
}
8484

85+
// https://www.w3.org/TR/trusted-types/#prepare-script-text
86+
WebIDL::ExceptionOr<void> HTMLScriptElement::prepare_script_text()
87+
{
88+
// 1. Let sink be "HTMLScriptElement text" if script is an HTMLScriptElement; otherwise "SVGScriptElement text".
89+
constexpr auto sink = TrustedTypes::InjectionSink::HTMLScriptElement_text;
90+
91+
// 2. If script’s script text value is not equal to its child text content,
92+
// set script’s script text to the result of executing get Trusted Type compliant string, with the following arguments:
93+
// TrustedScriptURL as expectedType,
94+
// script’s Document’s relevant global object as global,
95+
// script’s child text content attribute value as input,
96+
// sink,
97+
// 'script' as sinkGroup.
98+
if (m_script_text != child_text_content()) {
99+
m_script_text = TRY(TrustedTypes::get_trusted_type_compliant_string(
100+
TrustedTypes::TrustedTypeName::TrustedScriptURL,
101+
HTML::relevant_global_object(document()),
102+
child_text_content(),
103+
sink,
104+
TrustedTypes::Script.to_string()));
105+
}
106+
107+
return {};
108+
}
109+
85110
void HTMLScriptElement::begin_delaying_document_load_event(DOM::Document& document)
86111
{
87112
// https://html.spec.whatwg.org/multipage/scripting.html#concept-script-script
@@ -170,6 +195,7 @@ void HTMLScriptElement::execute_script()
170195
dispatch_event(DOM::Event::create(realm(), HTML::EventNames::load));
171196
}
172197

198+
// https://w3c.github.io/trusted-types/dist/spec/#slot-value-verification
173199
// https://html.spec.whatwg.org/multipage/scripting.html#prepare-a-script
174200
// https://whatpr.org/html/9893/scripting.html#prepare-a-script
175201
void HTMLScriptElement::prepare_script()
@@ -191,22 +217,26 @@ void HTMLScriptElement::prepare_script()
191217
m_force_async = true;
192218
}
193219

194-
// 5. Let source text be el's child text content.
195-
auto source_text = child_text_content();
220+
// 5. Execute the Prepare the script text algorithm on el. If that algorithm threw an error, then return.
221+
if (prepare_script_text().is_exception())
222+
return;
223+
224+
// 6. Let source text be el’s script text value.
225+
auto source_text = m_script_text;
196226
auto source_text_utf8 = source_text.to_utf8_but_should_be_ported_to_utf16();
197227

198-
// 6. If el has no src attribute, and source text is the empty string, then return.
228+
// 7. If el has no src attribute, and source text is the empty string, then return.
199229
if (!has_attribute(HTML::AttributeNames::src) && source_text.is_empty()) {
200230
return;
201231
}
202232

203-
// 7. If el is not connected, then return.
233+
// 8. If el is not connected, then return.
204234
if (!is_connected()) {
205235
dbgln("HTMLScriptElement: Refusing to run script because the element is not connected.");
206236
return;
207237
}
208238

209-
// 8. If any of the following are true:
239+
// 9. If any of the following are true:
210240
// - el has a type attribute whose value is the empty string;
211241
// - el has no type attribute but it has a language attribute and that attribute's value is the empty string; or
212242
// - el has neither a type attribute nor a language attribute
@@ -230,69 +260,69 @@ void HTMLScriptElement::prepare_script()
230260
script_block_type = MUST(String::formatted("text/{}", maybe_language_attribute.value()));
231261
}
232262

233-
// 9. If the script block's type string is a JavaScript MIME type essence match,
263+
// 10. If the script block's type string is a JavaScript MIME type essence match,
234264
if (MimeSniff::is_javascript_mime_type_essence_match(script_block_type)) {
235265
// then set el's type to "classic".
236266
m_script_type = ScriptType::Classic;
237267
}
238-
// 10. Otherwise, if the script block's type string is an ASCII case-insensitive match for the string "module",
268+
// 11. Otherwise, if the script block's type string is an ASCII case-insensitive match for the string "module",
239269
else if (script_block_type.equals_ignoring_ascii_case("module"sv)) {
240270
// then set el's type to "module".
241271
m_script_type = ScriptType::Module;
242272
}
243-
// 11. Otherwise, if the script block's type string is an ASCII case-insensitive match for the string "importmap",
273+
// 12. Otherwise, if the script block's type string is an ASCII case-insensitive match for the string "importmap",
244274
else if (script_block_type.equals_ignoring_ascii_case("importmap"sv)) {
245275
// then set el's type to "importmap".
246276
m_script_type = ScriptType::ImportMap;
247277
}
248-
// 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".
249-
// 13. Otherwise, return. (No script is executed, and el's type is left as null.)
278+
// 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".
279+
// 14. Otherwise, return. (No script is executed, and el's type is left as null.)
250280
else {
251281
VERIFY(m_script_type == ScriptType::Null);
252282
return;
253283
}
254284

255-
// 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.
285+
// 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.
256286
if (parser_document) {
257287
m_parser_document = parser_document;
258288
m_force_async = false;
259289
}
260290

261-
// 15. Set el's already started to true.
291+
// 16. Set el's already started to true.
262292
m_already_started = true;
263293

264-
// 16. Set el's preparation-time document to its node document.
294+
// 17. Set el's preparation-time document to its node document.
265295
m_preparation_time_document = &document();
266296

267-
// 17. If parser document is non-null, and parser document is not equal to el's preparation-time document, then return.
297+
// 18. If parser document is non-null, and parser document is not equal to el's preparation-time document, then return.
268298
if (parser_document != nullptr && parser_document != m_preparation_time_document) {
269299
dbgln("HTMLScriptElement: Refusing to run script because the parser document is not the same as the preparation time document.");
270300
return;
271301
}
272302

273-
// 18. If scripting is disabled for el, then return.
303+
// 19. If scripting is disabled for el, then return.
274304
if (is_scripting_disabled()) {
275305
dbgln("HTMLScriptElement: Refusing to run script because scripting is disabled.");
276306
return;
277307
}
278308

279-
// 19. If el has a nomodule content attribute and its type is "classic", then return.
309+
// 20. If el has a nomodule content attribute and its type is "classic", then return.
280310
if (m_script_type == ScriptType::Classic && has_attribute(HTML::AttributeNames::nomodule)) {
281311
dbgln("HTMLScriptElement: Refusing to run classic script because it has the nomodule attribute.");
282312
return;
283313
}
284314

285-
// FIXME: 20. Let cspType be "script speculationrules" if el's type is "speculationrules"; otherwise, "script".
315+
// FIXME: 21. Let cspType be "script speculationrules" if el's type is "speculationrules"; otherwise, "script".
286316

287-
// 21. If el does not have a src content attribute, and the Should element's inline behavior be blocked by Content
317+
// 22. If el does not have a src content attribute, and the Should element's inline behavior be blocked by Content
288318
// Security Policy? algorithm returns "Blocked" when given el, cspType, and source text, then return [CSP]
289319
if (!has_attribute(AttributeNames::src)
290320
&& 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) {
291321
dbgln("HTMLScriptElement: Refusing to run inline script because it violates the Content Security Policy.");
292322
return;
293323
}
294324

295-
// 22. If el has an event attribute and a for attribute, and el's type is "classic", then:
325+
// 23. If el has an event attribute and a for attribute, and el's type is "classic", then:
296326
if (m_script_type == ScriptType::Classic && has_attribute(HTML::AttributeNames::event) && has_attribute(HTML::AttributeNames::for_)) {
297327
// 1. Let for be the value of el's' for attribute.
298328
auto for_ = get_attribute_value(HTML::AttributeNames::for_);
@@ -318,7 +348,7 @@ void HTMLScriptElement::prepare_script()
318348
}
319349
}
320350

321-
// 23. If el has a charset attribute, then let encoding be the result of getting an encoding from the value of the charset attribute.
351+
// 24. If el has a charset attribute, then let encoding be the result of getting an encoding from the value of the charset attribute.
322352
// 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.
323353
Optional<String> encoding;
324354

@@ -334,34 +364,34 @@ void HTMLScriptElement::prepare_script()
334364

335365
VERIFY(encoding.has_value());
336366

337-
// 24. Let classic script CORS setting be the current state of el's crossorigin content attribute.
367+
// 25. Let classic script CORS setting be the current state of el's crossorigin content attribute.
338368
auto classic_script_cors_setting = m_crossorigin;
339369

340-
// 25. Let module script credentials mode be the CORS settings attribute credentials mode for el's crossorigin content attribute.
370+
// 26. Let module script credentials mode be the CORS settings attribute credentials mode for el's crossorigin content attribute.
341371
auto module_script_credential_mode = cors_settings_attribute_credentials_mode(m_crossorigin);
342372

343-
// 26. Let cryptographic nonce be el's [[CryptographicNonce]] internal slot's value.
373+
// 27. Let cryptographic nonce be el's [[CryptographicNonce]] internal slot's value.
344374
auto cryptographic_nonce = m_cryptographic_nonce;
345375

346-
// 27. If el has an integrity attribute, then let integrity metadata be that attribute's value.
376+
// 28. If el has an integrity attribute, then let integrity metadata be that attribute's value.
347377
// Otherwise, let integrity metadata be the empty string.
348378
String integrity_metadata;
349379
if (auto maybe_integrity = attribute(HTML::AttributeNames::integrity); maybe_integrity.has_value()) {
350380
integrity_metadata = *maybe_integrity;
351381
}
352382

353-
// 28. Let referrer policy be the current state of el's referrerpolicy content attribute.
383+
// 29. Let referrer policy be the current state of el's referrerpolicy content attribute.
354384
auto referrer_policy = m_referrer_policy;
355385

356-
// 29. Let fetch priority be the current state of el's fetchpriority content attribute.
386+
// 30. Let fetch priority be the current state of el's fetchpriority content attribute.
357387
auto fetch_priority = Fetch::Infrastructure::request_priority_from_string(get_attribute_value(HTML::AttributeNames::fetchpriority)).value_or(Fetch::Infrastructure::Request::Priority::Auto);
358388

359-
// 30. Let parser metadata be "parser-inserted" if el is parser-inserted, and "not-parser-inserted" otherwise.
389+
// 31. Let parser metadata be "parser-inserted" if el is parser-inserted, and "not-parser-inserted" otherwise.
360390
auto parser_metadata = is_parser_inserted()
361391
? Fetch::Infrastructure::Request::ParserMetadata::ParserInserted
362392
: Fetch::Infrastructure::Request::ParserMetadata::NotParserInserted;
363393

364-
// 31. Let options be a script fetch options whose cryptographic nonce is cryptographic nonce,
394+
// 32. Let options be a script fetch options whose cryptographic nonce is cryptographic nonce,
365395
// integrity metadata is integrity metadata, parser metadata is parser metadata,
366396
// credentials mode is module script credentials mode, referrer policy is referrer policy,
367397
// and fetch priority is fetch priority.
@@ -374,10 +404,10 @@ void HTMLScriptElement::prepare_script()
374404
.fetch_priority = move(fetch_priority),
375405
};
376406

377-
// 32. Let settings object be el's node document's relevant settings object.
407+
// 33. Let settings object be el's node document's relevant settings object.
378408
auto& settings_object = document().relevant_settings_object();
379409

380-
// 33. If el has a src content attribute, then:
410+
// 34. If el has a src content attribute, then:
381411
if (has_attribute(HTML::AttributeNames::src)) {
382412
// 1. If el's type is "importmap" or "speculationrules", then:
383413
// FIXME: Add "speculationrules" support.
@@ -452,7 +482,7 @@ void HTMLScriptElement::prepare_script()
452482
}
453483
}
454484

455-
// 34. If el does not have a src content attribute:
485+
// 35. If el does not have a src content attribute:
456486
if (!has_attribute(HTML::AttributeNames::src)) {
457487
// 1. Let base URL be el's node document's document base URL.
458488
auto base_url = document().base_url();
@@ -498,7 +528,7 @@ void HTMLScriptElement::prepare_script()
498528
// FIXME: -> "speculationrules"
499529
}
500530

501-
// 35. If el's type is "classic" and el has a src attribute, or el's type is "module":
531+
// 36. If el's type is "classic" and el has a src attribute, or el's type is "module":
502532
if ((m_script_type == ScriptType::Classic && has_attribute(HTML::AttributeNames::src)) || m_script_type == ScriptType::Module) {
503533
// 1. Assert: el's result is "uninitialized".
504534
// 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()
572602
}
573603
}
574604

575-
// 36. Otherwise:
605+
// 37. Otherwise:
576606
else {
577607
// 1. Assert: el's result is not "uninitialized".
578608
VERIFY(!m_result.has<ResultState::Uninitialized>());

Libraries/LibWeb/HTML/HTMLScriptElement.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ class HTMLScriptElement final : public HTMLElement {
9292

9393
virtual void attribute_changed(FlyString const& name, Optional<String> const& old_value, Optional<String> const& value, Optional<FlyString> const& namespace_) override;
9494

95+
// https://www.w3.org/TR/trusted-types/#prepare-script-text
96+
WebIDL::ExceptionOr<void> prepare_script_text();
97+
9598
// https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element
9699
void prepare_script();
97100

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
Harness status: OK
2+
3+
Found 34 tests
4+
5+
34 Pass
6+
Pass The HTMLScriptElement is initially trusted.
7+
Pass Script source set via TrustedScript sink HTMLScriptElement.innerText keeps trustworthiness.
8+
Pass Script source set via HTMLElement.innerText drops trustworthiness.
9+
Pass Script source set via TrustedScript sink HTMLScriptElement.textContent keeps trustworthiness.
10+
Pass Script source set Node.textContent drops trustworthiness.
11+
Pass Script source set via TrustedScript sink HTMLScriptElement.text keeps trustworthiness.
12+
Pass Script source set via TrustedHTML sink Element.innerHTML drops trustworthiness.
13+
Pass Script source set via TrustedHTML sink Element.setHTMLUnsafe() drops trustworthiness.
14+
Pass Splitting script source via Text.splitText() keeps trustworthiness.
15+
Pass Normalizing script source via Element.normalize() keeps trustworthiness.
16+
Pass Script source set via Node.nodeValue drops trustworthiness.
17+
Pass Setting script source via CharacterData.data drops trustworthiness.
18+
Pass Setting script source via CharacterData.appendData() drops trustworthiness.
19+
Pass Setting script source via CharacterData.insertData() drops trustworthiness.
20+
Pass Setting script source via CharacterData.replaceData() drops trustworthiness.
21+
Pass Setting script source via CharacterData.deleteData() drops trustworthiness.
22+
Pass Setting script source via CharacterData.before() drops trustworthiness.
23+
Pass Setting script source via CharacterData.after() drops trustworthiness.
24+
Pass Setting script source via CharacterData.remove() drops trustworthiness.
25+
Pass Setting script source via CharacterData.replaceWith() drops trustworthiness.
26+
Pass Setting script source via Node.appendChild() drops trustworthiness.
27+
Pass Setting script source via Node.insertBefore() drops trustworthiness.
28+
Pass Setting script source via Node.replaceChild() drops trustworthiness.
29+
Pass Setting script source via Node.removeChild() drops trustworthiness.
30+
Pass Setting script source via Element.prepend() drops trustworthiness.
31+
Pass Setting script source via Element.append() drops trustworthiness.
32+
Pass Setting script source via Element.replaceChildren() drops trustworthiness.
33+
Pass Setting script source via Element.moveBefore() drops trustworthiness.
34+
Pass Setting script source via TrustedHTML sink Node.insertAdjacentHTML() drops trustworthiness.
35+
Pass Setting script source via Node.insertAdjacentText() drops trustworthiness.
36+
Pass Setting script source via Range.insertNode() drops trustworthiness.
37+
Pass Setting script source via Range.deleteContents() drops trustworthiness.
38+
Pass Cloning a script via Node.cloneNode() drops trustworthiness.
39+
Pass Cloning a script via Range.cloneContents() drops trustworthiness.

0 commit comments

Comments
 (0)