2025-08-11 14:28:57 +01:00
|
|
|
|
/*
|
|
|
|
|
* 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>
|
2025-08-13 13:52:59 +01:00
|
|
|
|
#include <LibWeb/DOM/Document.h>
|
2025-08-11 14:28:57 +01:00
|
|
|
|
#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);
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-13 13:52:59 +01:00
|
|
|
|
StylePropertyMapReadOnly::StylePropertyMapReadOnly(JS::Realm& realm, Source source)
|
2025-08-11 14:28:57 +01:00
|
|
|
|
: Bindings::PlatformObject(realm)
|
2025-08-13 13:52:59 +01:00
|
|
|
|
, m_declarations(move(source))
|
2025-08-11 14:28:57 +01:00
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2025-08-13 13:52:59 +01:00
|
|
|
|
m_declarations.visit(
|
|
|
|
|
[&visitor](DOM::AbstractElement& element) { element.visit(visitor); },
|
|
|
|
|
[&visitor](GC::Ref<CSSStyleDeclaration>& declaration) { visitor.visit(declaration); });
|
2025-08-11 14:28:57 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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 this’s [[declarations]] internal slot.
|
|
|
|
|
auto& props = m_declarations;
|
|
|
|
|
|
2025-08-13 15:26:54 +01:00
|
|
|
|
// 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);
|
|
|
|
|
}
|
2025-08-11 14:28:57 +01:00
|
|
|
|
|
|
|
|
|
// 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 this’s [[declarations]] internal slot.
|
|
|
|
|
auto& props = m_declarations;
|
|
|
|
|
|
2025-08-13 15:26:54 +01:00
|
|
|
|
// 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) };
|
|
|
|
|
}
|
2025-08-11 14:28:57 +01:00
|
|
|
|
|
|
|
|
|
// 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 this’s [[declarations]] internal slot.
|
|
|
|
|
auto& props = m_declarations;
|
|
|
|
|
|
|
|
|
|
// 4. If props[property] exists, return true. Otherwise, return false.
|
2025-08-13 13:52:59 +01:00
|
|
|
|
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;
|
2025-08-13 15:26:54 +01:00
|
|
|
|
// Ensure style is computed on the element before we try to read it, so we can check custom properties.
|
|
|
|
|
element.document().update_style();
|
2025-08-13 13:52:59 +01:00
|
|
|
|
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);
|
|
|
|
|
});
|
2025-08-11 14:28:57 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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 this’s [[declarations]] internal slot.
|
2025-08-13 13:52:59 +01:00
|
|
|
|
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"
|
2025-08-13 15:26:54 +01:00
|
|
|
|
// Ensure style is computed on the element before we try to read it.
|
|
|
|
|
element.document().update_style();
|
2025-08-13 13:52:59 +01:00
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
2025-08-24 14:26:17 +12:00
|
|
|
|
return number_of_longhand_properties + custom_properties.size();
|
2025-08-13 13:52:59 +01:00
|
|
|
|
},
|
|
|
|
|
[](GC::Ref<CSSStyleDeclaration> const& declaration) { return declaration->length(); });
|
2025-08-11 14:28:57 +01:00
|
|
|
|
}
|
|
|
|
|
|
2025-08-13 15:26:54 +01:00
|
|
|
|
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;
|
|
|
|
|
}
|
2025-08-25 15:56:21 +12:00
|
|
|
|
|
|
|
|
|
if (*property_id >= first_longhand_property_id && *property_id <= last_longhand_property_id) {
|
|
|
|
|
// FIXME: This will only ever be null for pseudo-elements. What should we do in that case?
|
|
|
|
|
if (auto computed_properties = element.computed_properties())
|
|
|
|
|
return computed_properties->property(property_id.value());
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-13 15:26:54 +01:00
|
|
|
|
return nullptr;
|
|
|
|
|
},
|
|
|
|
|
[&property](GC::Ref<CSSStyleDeclaration>& declaration) {
|
|
|
|
|
return declaration->get_property_style_value(property);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-11 14:28:57 +01:00
|
|
|
|
}
|