ladybird/Libraries/LibWeb/CSS/StylePropertyMapReadOnly.cpp
Sam Atkins 089f70a918 LibWeb/CSS: Reify StyleValues in StylePropertyMap get()/getAll()
The limitations right now are:
- We don't know if a property is a list or not.
- We always reify as a CSSStyleValue directly.

So, we pass some tests but only ones that expect a CSSStyleValue.
2025-08-18 10:12:53 +01:00

199 lines
9.4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "StylePropertyMapReadOnly.h"
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/StylePropertyMapReadOnlyPrototype.h>
#include <LibWeb/CSS/CSSStyleDeclaration.h>
#include <LibWeb/CSS/PropertyName.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::CSS {
GC_DEFINE_ALLOCATOR(StylePropertyMapReadOnly);
GC::Ref<StylePropertyMapReadOnly> StylePropertyMapReadOnly::create_computed_style(JS::Realm& realm, DOM::AbstractElement element)
{
return realm.create<StylePropertyMapReadOnly>(realm, element);
}
StylePropertyMapReadOnly::StylePropertyMapReadOnly(JS::Realm& realm, Source source)
: Bindings::PlatformObject(realm)
, m_declarations(move(source))
{
}
StylePropertyMapReadOnly::~StylePropertyMapReadOnly() = default;
void StylePropertyMapReadOnly::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(StylePropertyMapReadOnly);
Base::initialize(realm);
}
void StylePropertyMapReadOnly::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
m_declarations.visit(
[&visitor](DOM::AbstractElement& element) { element.visit(visitor); },
[&visitor](GC::Ref<CSSStyleDeclaration>& declaration) { visitor.visit(declaration); });
}
// https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymapreadonly-get
WebIDL::ExceptionOr<Variant<GC::Ref<CSSStyleValue>, Empty>> StylePropertyMapReadOnly::get(String property)
{
// The get(property) method, when called on a StylePropertyMapReadOnly this, must perform the following steps:
// 1. If property is not a custom property name string, set property to property ASCII lowercased.
if (!is_a_custom_property_name_string(property))
property = property.to_ascii_lowercase();
// 2. If property is not a valid CSS property, throw a TypeError.
if (!is_a_valid_css_property(property))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("'{}' is not a valid CSS property", property)) };
// 3. Let props be the value of thiss [[declarations]] internal slot.
auto& props = m_declarations;
// 4. If props[property] exists, subdivide into iterations props[property], then reify the first item of the result and return it.
if (auto property_value = get_style_value(props, property)) {
// FIXME: Subdivide into iterations, and only reify/return the first.
return property_value->reify(realm(), property);
}
// 5. Otherwise, return undefined.
return Empty {};
}
// https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymapreadonly-getall
WebIDL::ExceptionOr<Vector<GC::Ref<CSSStyleValue>>> StylePropertyMapReadOnly::get_all(String property)
{
// The getAll(property) method, when called on a StylePropertyMap this, must perform the following steps:
// 1. If property is not a custom property name string, set property to property ASCII lowercased.
if (!is_a_custom_property_name_string(property))
property = property.to_ascii_lowercase();
// 2. If property is not a valid CSS property, throw a TypeError.
if (!is_a_valid_css_property(property))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("'{}' is not a valid CSS property", property)) };
// 3. Let props be the value of thiss [[declarations]] internal slot.
auto& props = m_declarations;
// 4. If props[property] exists, subdivide into iterations props[property], then reify each item of the result, and return the list.
if (auto property_value = get_style_value(props, property)) {
// FIXME: Subdivide into iterations.
return Vector { property_value->reify(realm(), property) };
}
// 5. Otherwise, return an empty list.
return Vector<GC::Ref<CSSStyleValue>> {};
}
// https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymapreadonly-has
WebIDL::ExceptionOr<bool> StylePropertyMapReadOnly::has(String property)
{
// The has(property) method, when called on a StylePropertyMapReadOnly this, must perform the following steps:
// 1. If property is not a custom property name string, set property to property ASCII lowercased.
if (!is_a_custom_property_name_string(property))
property = property.to_ascii_lowercase();
// 2. If property is not a valid CSS property, throw a TypeError.
if (!is_a_valid_css_property(property))
return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, MUST(String::formatted("'{}' is not a valid CSS property", property)) };
// 3. Let props be the value of thiss [[declarations]] internal slot.
auto& props = m_declarations;
// 4. If props[property] exists, return true. Otherwise, return false.
return props.visit(
[&property](DOM::AbstractElement& element) {
// From https://drafts.css-houdini.org/css-typed-om-1/#dom-element-computedstylemap we need to include:
// "the name and computed value of every longhand CSS property supported by the User Agent, every
// registered custom property, and every non-registered custom property which is not set to its initial
// value on this"
auto property_id = property_id_from_string(property);
if (!property_id.has_value())
return false;
// Ensure style is computed on the element before we try to read it, so we can check custom properties.
element.document().update_style();
if (property_id == PropertyID::Custom) {
if (element.get_custom_property(property))
return true;
return element.document().registered_custom_properties().contains(property);
}
return true;
},
[&property](GC::Ref<CSSStyleDeclaration>& declaration) {
return declaration->has_property(property);
});
}
// https://drafts.css-houdini.org/css-typed-om-1/#dom-stylepropertymapreadonly-size
WebIDL::UnsignedLong StylePropertyMapReadOnly::size() const
{
// The size attribute, on getting from a StylePropertyMapReadOnly this, must perform the following steps:
// 1. Return the size of the value of thiss [[declarations]] internal slot.
return m_declarations.visit(
[](DOM::AbstractElement const& element) {
// From https://drafts.css-houdini.org/css-typed-om-1/#dom-element-computedstylemap we need to include:
// "the name and computed value of every longhand CSS property supported by the User Agent, every
// registered custom property, and every non-registered custom property which is not set to its initial
// value on this"
// Ensure style is computed on the element before we try to read it.
element.document().update_style();
auto longhands_count = to_underlying(last_longhand_property_id) - to_underlying(first_longhand_property_id) + 1;
// Some custom properties set on the element might also be in the registered custom properties set, so we
// want the size of the union of the two sets.
HashTable<FlyString> custom_properties;
for (auto const& key : element.custom_properties().keys())
custom_properties.set(key);
for (auto const& [key, _] : element.document().registered_custom_properties())
custom_properties.set(key);
return longhands_count + custom_properties.size();
},
[](GC::Ref<CSSStyleDeclaration> const& declaration) { return declaration->length(); });
}
RefPtr<StyleValue const> StylePropertyMapReadOnly::get_style_value(Source& source, String property)
{
return source.visit(
[&property](DOM::AbstractElement& element) -> RefPtr<StyleValue const> {
// From https://drafts.css-houdini.org/css-typed-om-1/#dom-element-computedstylemap we need to include:
// "the name and computed value of every longhand CSS property supported by the User Agent, every
// registered custom property, and every non-registered custom property which is not set to its initial
// value on this"
auto property_id = property_id_from_string(property);
if (!property_id.has_value())
return nullptr;
// Ensure style is computed on the element before we try to read it.
element.document().update_style();
if (property_id == PropertyID::Custom) {
if (auto custom_property = element.get_custom_property(property))
return custom_property;
if (auto registered_custom_property = element.document().registered_custom_properties().get(property); registered_custom_property.has_value())
return registered_custom_property.value()->initial_style_value();
return nullptr;
}
// FIXME: This will only ever be null for pseudo-elements. What should we do in that case?
// The property's value is also sometimes null. Is that correct?
if (auto computed_properties = element.computed_properties())
return computed_properties->maybe_null_property(property_id.value());
return nullptr;
},
[&property](GC::Ref<CSSStyleDeclaration>& declaration) {
return declaration->get_property_style_value(property);
});
}
}