mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-27 20:58:16 +00:00
LibWeb/CSS: Add CSS.registerProperty
JS method
This adds an *almost* complete implementation of `CSS.registerProperty` method enabling further progress on the `@property` feature.
This commit is contained in:
parent
038d8ade50
commit
90c0decd95
Notes:
github-actions[bot]
2025-07-22 09:59:17 +00:00
Author: https://github.com/Norbiros
Commit: 90c0decd95
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/5507
Reviewed-by: https://github.com/AtkinsSJ ✅
7 changed files with 154 additions and 5 deletions
|
@ -8,9 +8,12 @@
|
||||||
#include <LibJS/Runtime/VM.h>
|
#include <LibJS/Runtime/VM.h>
|
||||||
#include <LibWeb/CSS/CSS.h>
|
#include <LibWeb/CSS/CSS.h>
|
||||||
#include <LibWeb/CSS/Parser/Parser.h>
|
#include <LibWeb/CSS/Parser/Parser.h>
|
||||||
|
#include <LibWeb/CSS/Parser/Syntax.h>
|
||||||
|
#include <LibWeb/CSS/Parser/SyntaxParsing.h>
|
||||||
#include <LibWeb/CSS/PropertyID.h>
|
#include <LibWeb/CSS/PropertyID.h>
|
||||||
#include <LibWeb/CSS/PropertyName.h>
|
#include <LibWeb/CSS/PropertyName.h>
|
||||||
#include <LibWeb/CSS/Serialize.h>
|
#include <LibWeb/CSS/Serialize.h>
|
||||||
|
#include <LibWeb/HTML/Window.h>
|
||||||
|
|
||||||
namespace Web::CSS {
|
namespace Web::CSS {
|
||||||
|
|
||||||
|
@ -59,4 +62,85 @@ WebIDL::ExceptionOr<bool> supports(JS::VM& vm, StringView condition_text)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://www.w3.org/TR/css-properties-values-api-1/#the-registerproperty-function
|
||||||
|
WebIDL::ExceptionOr<void> register_property(JS::VM& vm, PropertyDefinition definition)
|
||||||
|
{
|
||||||
|
// 1. Let property set be the value of the current global object’s associated Document’s [[registeredPropertySet]] slot.
|
||||||
|
auto& realm = *vm.current_realm();
|
||||||
|
auto& window = static_cast<Web::HTML::Window&>(realm.global_object());
|
||||||
|
auto& document = window.associated_document();
|
||||||
|
|
||||||
|
// 2. If name is not a custom property name string, throw a SyntaxError and exit this algorithm.
|
||||||
|
if (!is_a_custom_property_name_string(definition.name))
|
||||||
|
return WebIDL::SyntaxError::create(realm, "Invalid property name"_string);
|
||||||
|
|
||||||
|
// If property set already contains an entry with name as its property name (compared codepoint-wise),
|
||||||
|
// throw an InvalidModificationError and exit this algorithm.
|
||||||
|
if (document.registered_custom_properties().contains(definition.name))
|
||||||
|
return WebIDL::InvalidModificationError::create(realm, "Property already registered"_string);
|
||||||
|
|
||||||
|
auto parsing_params = CSS::Parser::ParsingParams { document };
|
||||||
|
|
||||||
|
// 3. Attempt to consume a syntax definition from syntax. If it returns failure, throw a SyntaxError.
|
||||||
|
// Otherwise, let syntax definition be the returned syntax definition.
|
||||||
|
auto syntax_component_values = parse_component_values_list(parsing_params, definition.syntax);
|
||||||
|
auto maybe_syntax = parse_as_syntax(syntax_component_values);
|
||||||
|
if (!maybe_syntax) {
|
||||||
|
return WebIDL::SyntaxError::create(realm, "Invalid syntax definition"_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
RefPtr<CSSStyleValue const> initial_value_maybe;
|
||||||
|
|
||||||
|
// 4. If syntax definition is the universal syntax definition, and initialValue is not present,
|
||||||
|
if (maybe_syntax->type() == Parser::SyntaxNode::NodeType::Universal) {
|
||||||
|
if (!definition.initial_value.has_value()) {
|
||||||
|
// let parsed initial value be empty.
|
||||||
|
// This must be treated identically to the "default" initial value of custom properties, as defined in [css-variables].
|
||||||
|
initial_value_maybe = nullptr;
|
||||||
|
} else {
|
||||||
|
// Otherwise, if syntax definition is the universal syntax definition,
|
||||||
|
// parse initialValue as a <declaration-value>
|
||||||
|
initial_value_maybe = parse_css_value(parsing_params, definition.initial_value.value(), PropertyID::Custom);
|
||||||
|
// If this fails, throw a SyntaxError and exit this algorithm.
|
||||||
|
// Otherwise, let parsed initial value be the parsed result.
|
||||||
|
if (!initial_value_maybe) {
|
||||||
|
return WebIDL::SyntaxError::create(realm, "Invalid initial value"_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!definition.initial_value.has_value()) {
|
||||||
|
// Otherwise, if initialValue is not present, throw a SyntaxError and exit this algorithm.
|
||||||
|
return WebIDL::SyntaxError::create(realm, "Initial value must be provided for non-universal syntax"_string);
|
||||||
|
} else {
|
||||||
|
// Otherwise, parse initialValue according to syntax definition.
|
||||||
|
auto initial_value_component_values = parse_component_values_list(CSS::Parser::ParsingParams {}, definition.initial_value.value());
|
||||||
|
|
||||||
|
initial_value_maybe = Parser::parse_with_a_syntax(
|
||||||
|
Parser::ParsingParams { realm },
|
||||||
|
initial_value_component_values,
|
||||||
|
*maybe_syntax);
|
||||||
|
|
||||||
|
// If this fails, throw a SyntaxError and exit this algorithm.
|
||||||
|
if (!initial_value_maybe || initial_value_maybe->is_guaranteed_invalid()) {
|
||||||
|
return WebIDL::SyntaxError::create(realm, "Invalid initial value"_string);
|
||||||
|
}
|
||||||
|
// Otherwise, let parsed initial value be the parsed result.
|
||||||
|
// NB: Already done
|
||||||
|
|
||||||
|
// FIXME: If parsed initial value is not computationally independent, throw a SyntaxError and exit this algorithm.
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Set inherit flag to the value of inherits.
|
||||||
|
// NB: Combined with 6.
|
||||||
|
|
||||||
|
// 6. Let registered property be a struct with a property name of name, a syntax of syntax definition,
|
||||||
|
// an initial value of parsed initial value, and an inherit flag of inherit flag.
|
||||||
|
auto registered_property = CSSPropertyRule::create(realm, definition.name, definition.syntax, definition.inherits, initial_value_maybe);
|
||||||
|
// Append registered property to property set.
|
||||||
|
document.registered_custom_properties().set(
|
||||||
|
registered_property->name(),
|
||||||
|
registered_property);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,18 @@
|
||||||
// https://www.w3.org/TR/cssom-1/#namespacedef-css
|
// https://www.w3.org/TR/cssom-1/#namespacedef-css
|
||||||
namespace Web::CSS {
|
namespace Web::CSS {
|
||||||
|
|
||||||
|
struct PropertyDefinition {
|
||||||
|
String name;
|
||||||
|
String syntax;
|
||||||
|
bool inherits;
|
||||||
|
Optional<String> initial_value;
|
||||||
|
};
|
||||||
|
|
||||||
WebIDL::ExceptionOr<String> escape(JS::VM&, StringView identifier);
|
WebIDL::ExceptionOr<String> escape(JS::VM&, StringView identifier);
|
||||||
|
|
||||||
bool supports(JS::VM&, StringView property, StringView value);
|
bool supports(JS::VM&, StringView property, StringView value);
|
||||||
WebIDL::ExceptionOr<bool> supports(JS::VM&, StringView condition_text);
|
WebIDL::ExceptionOr<bool> supports(JS::VM&, StringView condition_text);
|
||||||
|
|
||||||
|
WebIDL::ExceptionOr<void> register_property(JS::VM&, PropertyDefinition definition);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
dictionary PropertyDefinition {
|
||||||
|
required CSSOMString name;
|
||||||
|
CSSOMString syntax = "*";
|
||||||
|
required boolean inherits;
|
||||||
|
CSSOMString initialValue;
|
||||||
|
};
|
||||||
|
|
||||||
// https://www.w3.org/TR/cssom-1/#namespacedef-css
|
// https://www.w3.org/TR/cssom-1/#namespacedef-css
|
||||||
[Exposed=Window]
|
[Exposed=Window]
|
||||||
namespace CSS {
|
namespace CSS {
|
||||||
|
@ -5,4 +12,7 @@ namespace CSS {
|
||||||
|
|
||||||
boolean supports(CSSOMString property, CSSOMString value);
|
boolean supports(CSSOMString property, CSSOMString value);
|
||||||
boolean supports(CSSOMString conditionText);
|
boolean supports(CSSOMString conditionText);
|
||||||
|
|
||||||
|
// https://www.w3.org/TR/css-properties-values-api-1/#dom-css-registerproperty
|
||||||
|
undefined registerProperty(PropertyDefinition definition);
|
||||||
};
|
};
|
||||||
|
|
|
@ -133,4 +133,9 @@ RefPtr<CSS::Supports> parse_css_supports(CSS::Parser::ParsingParams const& conte
|
||||||
return CSS::Parser::Parser::create(context, string).parse_as_supports();
|
return CSS::Parser::Parser::create(context, string).parse_as_supports();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector<CSS::Parser::ComponentValue> parse_component_values_list(CSS::Parser::ParsingParams const& parsing_params, StringView string)
|
||||||
|
{
|
||||||
|
return CSS::Parser::Parser::create(parsing_params, string).parse_as_list_of_component_values();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -577,6 +577,7 @@ CSS::CSSRule* parse_css_rule(CSS::Parser::ParsingParams const&, StringView);
|
||||||
RefPtr<CSS::MediaQuery> parse_media_query(CSS::Parser::ParsingParams const&, StringView);
|
RefPtr<CSS::MediaQuery> parse_media_query(CSS::Parser::ParsingParams const&, StringView);
|
||||||
Vector<NonnullRefPtr<CSS::MediaQuery>> parse_media_query_list(CSS::Parser::ParsingParams const&, StringView);
|
Vector<NonnullRefPtr<CSS::MediaQuery>> parse_media_query_list(CSS::Parser::ParsingParams const&, StringView);
|
||||||
RefPtr<CSS::Supports> parse_css_supports(CSS::Parser::ParsingParams const&, StringView);
|
RefPtr<CSS::Supports> parse_css_supports(CSS::Parser::ParsingParams const&, StringView);
|
||||||
|
Vector<CSS::Parser::ComponentValue> parse_component_values_list(CSS::Parser::ParsingParams const&, StringView);
|
||||||
GC::Ref<JS::Realm> internal_css_realm();
|
GC::Ref<JS::Realm> internal_css_realm();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
== CSS property descriptors
|
== CSS property descriptors
|
||||||
|
registerProperty writable: true
|
||||||
|
registerProperty configurable: true
|
||||||
|
registerProperty enumerable: true
|
||||||
|
registerProperty value before: function registerProperty() { [native code] }
|
||||||
|
registerProperty value after: replaced
|
||||||
supports writable: true
|
supports writable: true
|
||||||
supports configurable: true
|
supports configurable: true
|
||||||
supports enumerable: true
|
supports enumerable: true
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
Harness status: Error
|
Harness status: OK
|
||||||
|
|
||||||
Found 22 tests
|
Found 43 tests
|
||||||
|
|
||||||
15 Pass
|
28 Pass
|
||||||
7 Fail
|
15 Fail
|
||||||
Pass '--x: image-set(attr(data-foo))' with data-foo="https://does-not-exist.test/404.png"
|
Pass '--x: image-set(attr(data-foo))' with data-foo="https://does-not-exist.test/404.png"
|
||||||
Pass 'background-image: image-set(attr(data-foo))' with data-foo="https://does-not-exist.test/404.png"
|
Pass 'background-image: image-set(attr(data-foo))' with data-foo="https://does-not-exist.test/404.png"
|
||||||
Fail 'background-image: image-set("https://does-not-exist.test/404.png")' with data-foo="https://does-not-exist.test/404.png"
|
Fail 'background-image: image-set("https://does-not-exist.test/404.png")' with data-foo="https://does-not-exist.test/404.png"
|
||||||
|
@ -25,4 +25,39 @@ Pass 'background-image: image-set(var(--y, attr(data-foo)))' with data-foo="http
|
||||||
Pass '--x: image-set(var(--some-string))' with data-foo="https://does-not-exist.test/404.png"
|
Pass '--x: image-set(var(--some-string))' with data-foo="https://does-not-exist.test/404.png"
|
||||||
Pass 'background-image: image-set(var(--some-string))' with data-foo="https://does-not-exist.test/404.png"
|
Pass 'background-image: image-set(var(--some-string))' with data-foo="https://does-not-exist.test/404.png"
|
||||||
Pass '--x: image-set(var(--some-string-list))' with data-foo="https://does-not-exist.test/404.png"
|
Pass '--x: image-set(var(--some-string-list))' with data-foo="https://does-not-exist.test/404.png"
|
||||||
Pass 'background-image: image-set(var(--some-string-list))' with data-foo="https://does-not-exist.test/404.png"
|
Pass 'background-image: image-set(var(--some-string-list))' with data-foo="https://does-not-exist.test/404.png"
|
||||||
|
Fail '--registered-url: attr(data-foo type(<url>))' with data-foo="https://does-not-exist.test/404.png"
|
||||||
|
Fail '--registered-color: attr(data-foo type(<color>))' with data-foo="blue"
|
||||||
|
Pass '--x: image-set(var(--some-other-url))' with data-foo="https://does-not-exist.test/404.png"
|
||||||
|
Pass 'background-image: image-set(var(--some-other-url))' with data-foo="https://does-not-exist.test/404.png"
|
||||||
|
Fail 'background-image: attr(data-foo type(*))' with data-foo="url(https://does-not-exist.test/404.png), linear-gradient(black, white)"
|
||||||
|
Pass 'background-image: image-set(var(--image-set-valid))' with data-foo="image/jpeg"
|
||||||
|
Pass 'background-image: image-set(var(--image-set-invalid))' with data-foo="https://does-not-exist.test/404.png"
|
||||||
|
Fail '--x: image-set(if(style(--true): attr(data-foo);))' with data-foo="https://does-not-exist.test/404.png"
|
||||||
|
Pass 'background-image: image-set(if(style(--true): attr(data-foo);))' with data-foo="https://does-not-exist.test/404.png"
|
||||||
|
Fail 'background-image: image-set(
|
||||||
|
if(style(--true): url(https://does-not-exist-2.test/404.png);
|
||||||
|
else: attr(data-foo);))' with data-foo="https://does-not-exist-2.test/404.png"
|
||||||
|
Pass 'background-image: image-set(
|
||||||
|
if(style(--some-string): url(https://does-not-exist.test/404.png);))' with data-foo="https://does-not-exist.test/404.png"
|
||||||
|
Pass 'background-image: image-set(
|
||||||
|
if(style(--condition-val: attr(data-foo type(*))): url(https://does-not-exist.test/404.png);))' with data-foo="3"
|
||||||
|
Pass 'background-image: image-set(
|
||||||
|
if(style(--condition-val: attr(data-foo type(*))): url(https://does-not-exist.test/404.png);
|
||||||
|
style(--true): url(https://does-not-exist.test/404.png);
|
||||||
|
else: url(https://does-not-exist.test/404.png);))' with data-foo="1"
|
||||||
|
Fail 'background-image: image-set(if(style(--true): url(https://does-not-exist.test/404.png);
|
||||||
|
style(--condition-val): url(https://does-not-exist.test/404.png);
|
||||||
|
else: url(https://does-not-exist.test/404.png);))' with data-foo="attr(data-foo type(*))"
|
||||||
|
Pass 'background-image: image-set(
|
||||||
|
if(style(--condition-val: if(style(--true): attr(data-foo type(*));)): url(https://does-not-exist.test/404.png);))' with data-foo="3"
|
||||||
|
Fail '--x: image-set(if(style(--condition-val: if(style(--true): attr(data-foo type(*));)): url(https://does-not-exist.test/404.png);))' with data-foo="3"
|
||||||
|
Fail '--x: image-set(if(style(--condition-val >= attr(data-foo type(*))): url(https://does-not-exist.test/404.png);))' with data-foo="3"
|
||||||
|
Pass 'background-image: image-set(
|
||||||
|
if(style(--condition-val >= attr(data-foo type(*))): url(https://does-not-exist.test/404.png);))' with data-foo="3"
|
||||||
|
Pass 'background-image: image-set(
|
||||||
|
if(style(--condition-val < attr(data-foo type(*))): url(https://does-not-exist.test/404.png);))' with data-foo="3"
|
||||||
|
Pass 'background-image: image-set(
|
||||||
|
if(style(--str < attr(data-foo type(*))): url(https://does-not-exist.test/404.png);))' with data-foo="3"
|
||||||
|
Pass 'background-image: image-set(
|
||||||
|
if(style(--condition-val < attr(data-foo type(*))): url(https://does-not-exist.test/404.png);))' with data-foo="text"
|
Loading…
Add table
Add a link
Reference in a new issue