LibWeb: Implement TrustedTypes spec for set_attribute on Element

This part of the spec is still under a PR in GitHub, but it should be
safe to implement like it is.
This commit is contained in:
Tete17 2025-08-08 00:39:16 +02:00 committed by Luke Wilde
parent 5381146e85
commit df543cf31a
Notes: github-actions[bot] 2025-10-27 16:16:43 +00:00
7 changed files with 108 additions and 29 deletions

View file

@ -89,6 +89,7 @@
#include <LibWeb/Painting/ViewportPaintable.h>
#include <LibWeb/SVG/SVGAElement.h>
#include <LibWeb/Selection/Selection.h>
#include <LibWeb/TrustedTypes/TrustedTypePolicy.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
#include <LibWeb/WebIDL/DOMException.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
@ -206,38 +207,49 @@ GC::Ptr<Attr> Element::get_attribute_node_ns(Optional<FlyString> const& namespac
return m_attributes->get_attribute_ns(namespace_, name);
}
// https://dom.spec.whatwg.org/#dom-element-setattribute
WebIDL::ExceptionOr<void> Element::set_attribute(FlyString const& name, String const& value)
// FIXME: Trusted Types integration with DOM is still under review https://github.com/whatwg/dom/pull/1268
// https://whatpr.org/dom/1268.html#dom-element-setattribute
WebIDL::ExceptionOr<void> Element::set_attribute(FlyString qualified_name, Variant<GC::Root<TrustedTypes::TrustedHTML>, GC::Root<TrustedTypes::TrustedScript>, GC::Root<TrustedTypes::TrustedScriptURL>, Utf16String> const& value)
{
// 1. If qualifiedName is not a valid attribute local name, then throw an "InvalidCharacterError" DOMException.
if (!is_valid_attribute_local_name(name))
if (!is_valid_attribute_local_name(qualified_name))
return WebIDL::InvalidCharacterError::create(realm(), "Attribute name must not be empty or contain invalid characters"_utf16);
// 2. If this is in the HTML namespace and its node document is an HTML document, then set qualifiedName to qualifiedName in ASCII lowercase.
bool insert_as_lowercase = namespace_uri() == Namespace::HTML && document().document_type() == Document::Type::HTML;
// 2. If this is in the HTML namespace and its node document is an HTML document, then set qualifiedName to
// qualifiedName in ASCII lowercase.
if (namespace_uri() == Namespace::HTML && document().document_type() == Document::Type::HTML)
qualified_name = qualified_name.to_ascii_lowercase();
// 3. Let attribute be the first attribute in thiss attribute list whose qualified name is qualifiedName, and null otherwise.
auto* attribute = attributes()->get_attribute(name);
// 3. Let verifiedValue be the result of calling get Trusted Types-compliant attribute value
// with qualifiedName, null, this, and value.
auto const verified_value = TRY(TrustedTypes::get_trusted_types_compliant_attribute_value(qualified_name, {}, *this, value));
// 4. If attribute is null, create an attribute whose local name is qualifiedName, value is value, and node document
// 4. Let attribute be the first attribute in thiss attribute list whose qualified name is qualifiedName, and null otherwise.
auto* attribute = attributes()->get_attribute(qualified_name);
// 5. If attribute is null, create an attribute whose local name is qualifiedName, value is verifiedValue, and node document
// is thiss node document, then append this attribute to this, and then return.
if (!attribute) {
auto new_attribute = Attr::create(document(), insert_as_lowercase ? name.to_ascii_lowercase() : name, value);
auto new_attribute = Attr::create(document(), qualified_name, verified_value.to_utf8_but_should_be_ported_to_utf16());
m_attributes->append_attribute(new_attribute);
return {};
}
// 5. Change attribute to value.
attribute->change_attribute(value);
// 6. Change attribute to verifiedValue.
attribute->change_attribute(verified_value.to_utf8_but_should_be_ported_to_utf16());
return {};
}
// https://dom.spec.whatwg.org/#dom-element-setattribute
WebIDL::ExceptionOr<void> Element::set_attribute(FlyString const& name, Utf16String const& value)
// FIXME: Trusted Types integration with DOM is still under review https://github.com/whatwg/dom/pull/1268
// https://whatpr.org/dom/1268.html#dom-element-setattribute
WebIDL::ExceptionOr<void> Element::set_attribute(FlyString qualified_name, Variant<GC::Root<TrustedTypes::TrustedHTML>, GC::Root<TrustedTypes::TrustedScript>, GC::Root<TrustedTypes::TrustedScriptURL>, String> const& value)
{
return set_attribute(name, value.to_utf8_but_should_be_ported_to_utf16());
return set_attribute(move(qualified_name),
value.visit(
[](auto const& trusted_type) -> Variant<GC::Root<TrustedTypes::TrustedHTML>, GC::Root<TrustedTypes::TrustedScript>, GC::Root<TrustedTypes::TrustedScriptURL>, Utf16String> { return trusted_type; },
[](String const& string) -> Variant<GC::Root<TrustedTypes::TrustedHTML>, GC::Root<TrustedTypes::TrustedScript>, GC::Root<TrustedTypes::TrustedScriptURL>, Utf16String> { return Utf16String::from_utf8(string); }));
}
// https://dom.spec.whatwg.org/#valid-namespace-prefix

View file

@ -32,6 +32,9 @@
#include <LibWeb/HTML/ScrollOptions.h>
#include <LibWeb/HTML/TagNames.h>
#include <LibWeb/IntersectionObserver/IntersectionObserver.h>
#include <LibWeb/TrustedTypes/TrustedHTML.h>
#include <LibWeb/TrustedTypes/TrustedScript.h>
#include <LibWeb/TrustedTypes/TrustedScriptURL.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
#include <LibWeb/WebIDL/Types.h>
@ -146,8 +149,8 @@ public:
Optional<String> lang() const;
WebIDL::ExceptionOr<void> set_attribute(FlyString const& name, String const& value);
WebIDL::ExceptionOr<void> set_attribute(FlyString const& name, Utf16String const& value);
WebIDL::ExceptionOr<void> set_attribute(FlyString qualified_name, Variant<GC::Root<TrustedTypes::TrustedHTML>, GC::Root<TrustedTypes::TrustedScript>, GC::Root<TrustedTypes::TrustedScriptURL>, String> const& value);
WebIDL::ExceptionOr<void> set_attribute(FlyString qualified_name, Variant<GC::Root<TrustedTypes::TrustedHTML>, GC::Root<TrustedTypes::TrustedScript>, GC::Root<TrustedTypes::TrustedScriptURL>, Utf16String> const& value);
WebIDL::ExceptionOr<void> set_attribute_ns(Optional<FlyString> const& namespace_, FlyString const& qualified_name, String const& value);
void set_attribute_value(FlyString const& local_name, String const& value, Optional<FlyString> const& prefix = {}, Optional<FlyString> const& namespace_ = {});

View file

@ -14,6 +14,7 @@
#import <Geometry/DOMRectList.idl>
#import <HTML/HTMLSlotElement.idl>
#import <HTML/Window.idl>
#import <TrustedTypes/TrustedTypePolicy.idl>
enum ScrollLogicalPosition { "start", "center", "end", "nearest" };
// https://drafts.csswg.org/cssom-view-1/#dictdef-scrollintoviewoptions
@ -52,7 +53,7 @@ interface Element : Node {
sequence<DOMString> getAttributeNames();
DOMString? getAttribute(DOMString qualifiedName);
DOMString? getAttributeNS([FlyString] DOMString? namespace, [FlyString] DOMString localName);
[CEReactions] undefined setAttribute(DOMString qualifiedName, DOMString value);
[CEReactions] undefined setAttribute(DOMString qualifiedName, (TrustedType or Utf16DOMString) value);
[CEReactions] undefined setAttributeNS([FlyString] DOMString? namespace , [FlyString] DOMString qualifiedName , DOMString value);
[CEReactions] undefined removeAttribute([FlyString] DOMString qualifiedName);
[CEReactions] undefined removeAttributeNS([FlyString] DOMString? namespace, [FlyString] DOMString localName);

View file

@ -10,7 +10,10 @@
#include <LibJS/Runtime/Realm.h>
#include <LibJS/Runtime/Value.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/HTML/TagNames.h>
#include <LibWeb/HTML/WindowOrWorkerGlobalScope.h>
#include <LibWeb/Namespace.h>
#include <LibWeb/SVG/TagNames.h>
#include <LibWeb/TrustedTypes/RequireTrustedTypesForDirective.h>
#include <LibWeb/TrustedTypes/TrustedHTML.h>
#include <LibWeb/TrustedTypes/TrustedScript.h>
@ -340,4 +343,68 @@ WebIDL::ExceptionOr<Utf16String> get_trusted_type_compliant_string(TrustedTypeNa
});
}
// https://w3c.github.io/trusted-types/dist/spec/#validate-attribute-mutation
WebIDL::ExceptionOr<Utf16String> get_trusted_types_compliant_attribute_value(FlyString const& attribute_name, Optional<Utf16String> attribute_ns, const DOM::Element& element, Variant<GC::Root<TrustedHTML>, GC::Root<TrustedScript>, GC::Root<TrustedScriptURL>, Utf16String> const& new_value)
{
// 1. If attributeNs is the empty string, set attributeNs to null.
if (attribute_ns.has_value() && attribute_ns.value().is_empty())
attribute_ns.clear();
// 2. Set attributeData to the result of Get Trusted Type data for attribute algorithm, with the following arguments:
// element
// attributeName
// attributeNs
auto const attribute_data = get_trusted_type_data_for_attribute(
element_interface_name(Utf16String::from_utf8(element.local_name()), attribute_ns.has_value() ? attribute_ns.value() : Utf16String::from_utf8(Namespace::HTML)),
Utf16String::from_utf8(attribute_name),
attribute_ns);
// 3. If attributeData is null, then:
if (!attribute_data.has_value()) {
// 1. If newValue is a string, return newValue.
if (new_value.has<Utf16String>())
return new_value.get<Utf16String>();
// 2. Assert: newValue is TrustedHTML or TrustedScript or TrustedScriptURL.
VERIFY(new_value.has<GC::Root<TrustedHTML>>() || new_value.has<GC::Root<TrustedScript>>() || new_value.has<GC::Root<TrustedScriptURL>>());
// 3. Return values associated data.
// FIXME: This is badly worded in the spec it should say "Return stringified newvalues's"
return new_value.downcast<TrustedType>().visit([](auto& value) { return value->to_string(); });
}
// 4. Let expectedType be the value of the fourth member of attributeData.
auto const expected_type = attribute_data->trusted_type;
// 5. Let sink be the value of the fifth member of attributeData.
auto const sink = attribute_data->sink;
// 6. Return the result of executing Get Trusted Type compliant string with the following arguments:
// expectedType
// newValue as input
// elements node documents relevant global object as global
// sink
// 'script' as sinkGroup
return get_trusted_type_compliant_string(
expected_type,
HTML::relevant_global_object(element.document()),
new_value,
sink,
Script.to_string());
}
Utf16String element_interface_name(Utf16String const& local_name, Utf16String const& element_ns)
{
// FIXME: We don't have a method in ElementFactory that can give us the interface name but these are all the cases
// we care about in the table in get_trusted_type_data_for_attribute function
if (local_name == HTML::TagNames::iframe && element_ns == Namespace::HTML)
return "HTMLIFrameElement"_utf16;
if (local_name == HTML::TagNames::script && element_ns == Namespace::HTML)
return "HTMLScriptElement"_utf16;
if (local_name == SVG::TagNames::script && element_ns == Namespace::SVG)
return "SVGScriptElement"_utf16;
return "Element"_utf16;
}
}

View file

@ -69,4 +69,8 @@ WebIDL::ExceptionOr<Optional<TrustedType>> process_value_with_a_default_policy(T
WebIDL::ExceptionOr<Utf16String> get_trusted_type_compliant_string(TrustedTypeName, JS::Object&, Variant<GC::Root<TrustedHTML>, GC::Root<TrustedScript>, GC::Root<TrustedScriptURL>, Utf16String> input, InjectionSink sink, String sink_group);
WebIDL::ExceptionOr<Utf16String> get_trusted_types_compliant_attribute_value(FlyString const& attribute_name, Optional<Utf16String> attribute_ns, const DOM::Element& element, Variant<GC::Root<TrustedHTML>, GC::Root<TrustedScript>, GC::Root<TrustedScriptURL>, Utf16String> const& new_value);
Utf16String element_interface_name(Utf16String const& local_name, Utf16String const& element_ns);
}

View file

@ -2,6 +2,9 @@
#import <TrustedTypes/TrustedScript.idl>
#import <TrustedTypes/TrustedScriptURL.idl>
// https://www.w3.org/TR/trusted-types/#typedefdef-trustedtype
typedef (TrustedHTML or TrustedScript or TrustedScriptURL) TrustedType;
// https://w3c.github.io/trusted-types/dist/spec/#trusted-type-policy
[Exposed=(Window,Worker)]
interface TrustedTypePolicy {

View file

@ -48,19 +48,8 @@ Optional<Utf16String> TrustedTypePolicyFactory::get_attribute_type(Utf16String c
if (attr_ns.has_value() && attr_ns.value().is_empty())
attr_ns.clear();
// FIXME: We don't have a method in ElementFactory that can give us the interface name but these are all the cases
// we care about in the table in get_trusted_type_data_for_attribute function
// 5. Let interface be the element interface for localName and elementNs.
Utf16String interface;
if (local_name == HTML::TagNames::iframe && element_ns == Namespace::HTML) {
interface = "HTMLIFrameElement"_utf16;
} else if (local_name == HTML::TagNames::script && element_ns == Namespace::HTML) {
interface = "HTMLScriptElement"_utf16;
} else if (local_name == SVG::TagNames::script && element_ns == Namespace::SVG) {
interface = "SVGScriptElement"_utf16;
} else {
interface = "Element"_utf16;
}
Utf16String const interface = element_interface_name(local_name, element_ns.value());
// 6. Let expectedType be null.
Optional<Utf16String> expected_type {};