LibWeb: Generate IDL attributes for all supported CSS properties

The CSSOM spec tells us to potentially add up to three different IDL
attributes to CSSStyleDeclaration for every CSS property we support:
- A camelCased attribute, where a dash indicates the next character
  should be uppercase
- A camelCased attribute for every -webkit- prefixed property, with the
  first letter always being lowercase
- A dashed-attribute for every property with a dash in it.

Additionally, every attribute must have the CEReactions and
LegacyNullToEmptyString extended attributes specified on it.

Since we specify every property we support with Properties.json, we can
use that file to generate the IDL file and it's implementation.

We import it from the Build directory with the help of multiple import
base paths. Then, we add it to CSSStyleDeclaration via the mixin
functionality and inheriting the generated class in
CSSStyleDeclaration.
This commit is contained in:
Luke Wilde 2024-11-14 16:57:14 +00:00 committed by Andreas Kling
parent d95ae629ee
commit aacf9b08ed
Notes: github-actions[bot] 2024-11-14 18:51:20 +00:00
12 changed files with 909 additions and 60 deletions

View file

@ -11,7 +11,7 @@ They are run automatically as part of the build, and most of the time you can ig
## Properties.json ## Properties.json
Each CSS property has an entry here, which describes what values it accepts, whether it's inherited, and similar data. Each CSS property has an entry here, which describes what values it accepts, whether it's inherited, and similar data.
This generates `PropertyID.h` and `PropertyID.cpp`. This generates `PropertyID.h`, `PropertyID.cpp`, `GeneratedCSSStyleProperties.h`, `GeneratedCSSStyleProperties.cpp` and `GeneratedCSSStyleProperties.idl`.
Most of this data is found in the information box for that property in the relevant CSS spec. Most of this data is found in the information box for that property in the relevant CSS spec.
The file is organized as a single JSON object, with keys being property names, and the values being the data for that property. The file is organized as a single JSON object, with keys being property names, and the values being the data for that property.

View file

@ -832,6 +832,7 @@ set(GENERATED_SOURCES
ARIA/AriaRoles.cpp ARIA/AriaRoles.cpp
CSS/DefaultStyleSheetSource.cpp CSS/DefaultStyleSheetSource.cpp
CSS/Enums.cpp CSS/Enums.cpp
CSS/GeneratedCSSStyleProperties.cpp
CSS/Keyword.cpp CSS/Keyword.cpp
CSS/MathFunctions.cpp CSS/MathFunctions.cpp
CSS/MediaFeatureID.cpp CSS/MediaFeatureID.cpp

View file

@ -314,6 +314,20 @@ String CSSStyleDeclaration::css_text() const
return serialized(); return serialized();
} }
// https://drafts.csswg.org/cssom/#dom-cssstyleproperties-cssfloat
String CSSStyleDeclaration::css_float() const
{
// The cssFloat attribute, on getting, must return the result of invoking getPropertyValue() with float as argument.
return get_property_value("float"sv);
}
WebIDL::ExceptionOr<void> CSSStyleDeclaration::set_css_float(StringView value)
{
// On setting, the attribute must invoke setProperty() with float as first argument, as second argument the given value,
// and no third argument. Any exceptions thrown must be re-thrown.
return set_property("float"sv, value, ""sv);
}
// https://www.w3.org/TR/cssom/#serialize-a-css-declaration // https://www.w3.org/TR/cssom/#serialize-a-css-declaration
static String serialize_a_css_declaration(CSS::PropertyID property, StringView value, Important important) static String serialize_a_css_declaration(CSS::PropertyID property, StringView value, Important important)
{ {
@ -436,55 +450,6 @@ String PropertyOwningCSSStyleDeclaration::serialized() const
return MUST(builder.to_string()); return MUST(builder.to_string());
} }
static CSS::PropertyID property_id_from_name(StringView name)
{
// FIXME: Perhaps this should go in the code generator.
if (name == "cssFloat"sv)
return CSS::PropertyID::Float;
if (auto property_id = CSS::property_id_from_camel_case_string(name); property_id.has_value())
return property_id.value();
if (auto property_id = CSS::property_id_from_string(name); property_id.has_value())
return property_id.value();
return CSS::PropertyID::Invalid;
}
JS::ThrowCompletionOr<bool> CSSStyleDeclaration::internal_has_property(JS::PropertyKey const& name) const
{
if (!name.is_string())
return Base::internal_has_property(name);
return property_id_from_name(name.to_string()) != CSS::PropertyID::Invalid;
}
JS::ThrowCompletionOr<JS::Value> CSSStyleDeclaration::internal_get(JS::PropertyKey const& name, JS::Value receiver, JS::CacheablePropertyMetadata* cacheable_metadata, PropertyLookupPhase phase) const
{
if (name.is_number())
return { JS::PrimitiveString::create(vm(), item(name.as_number())) };
if (!name.is_string())
return Base::internal_get(name, receiver, cacheable_metadata, phase);
auto property_id = property_id_from_name(name.to_string());
if (property_id == CSS::PropertyID::Invalid)
return Base::internal_get(name, receiver, cacheable_metadata, phase);
return JS::PrimitiveString::create(vm(), get_property_value(string_from_property_id(property_id)));
}
JS::ThrowCompletionOr<bool> CSSStyleDeclaration::internal_set(JS::PropertyKey const& name, JS::Value value, JS::Value receiver, JS::CacheablePropertyMetadata* cacheable_metadata)
{
auto& vm = this->vm();
if (!name.is_string())
return Base::internal_set(name, value, receiver, cacheable_metadata);
auto property_id = property_id_from_name(name.to_string());
if (property_id == CSS::PropertyID::Invalid)
return Base::internal_set(name, value, receiver, cacheable_metadata);
auto css_text = value.is_null() ? String {} : TRY(value.to_string(vm));
TRY(Bindings::throw_dom_exception_if_needed(vm, [&] { return set_property(property_id, css_text); }));
return true;
}
WebIDL::ExceptionOr<void> PropertyOwningCSSStyleDeclaration::set_css_text(StringView css_text) WebIDL::ExceptionOr<void> PropertyOwningCSSStyleDeclaration::set_css_text(StringView css_text)
{ {
dbgln("(STUBBED) PropertyOwningCSSStyleDeclaration::set_css_text(css_text='{}')", css_text); dbgln("(STUBBED) PropertyOwningCSSStyleDeclaration::set_css_text(css_text='{}')", css_text);

View file

@ -10,11 +10,14 @@
#include <AK/Vector.h> #include <AK/Vector.h>
#include <LibWeb/Bindings/PlatformObject.h> #include <LibWeb/Bindings/PlatformObject.h>
#include <LibWeb/CSS/CSSStyleValue.h> #include <LibWeb/CSS/CSSStyleValue.h>
#include <LibWeb/CSS/GeneratedCSSStyleProperties.h>
#include <LibWeb/CSS/StyleProperty.h> #include <LibWeb/CSS/StyleProperty.h>
namespace Web::CSS { namespace Web::CSS {
class CSSStyleDeclaration : public Bindings::PlatformObject { class CSSStyleDeclaration
: public Bindings::PlatformObject
, public Bindings::GeneratedCSSStyleProperties {
WEB_PLATFORM_OBJECT(CSSStyleDeclaration, Bindings::PlatformObject); WEB_PLATFORM_OBJECT(CSSStyleDeclaration, Bindings::PlatformObject);
JS_DECLARE_ALLOCATOR(CSSStyleDeclaration); JS_DECLARE_ALLOCATOR(CSSStyleDeclaration);
@ -39,16 +42,21 @@ public:
String css_text() const; String css_text() const;
virtual WebIDL::ExceptionOr<void> set_css_text(StringView) = 0; virtual WebIDL::ExceptionOr<void> set_css_text(StringView) = 0;
virtual String serialized() const = 0; String css_float() const;
WebIDL::ExceptionOr<void> set_css_float(StringView);
virtual JS::ThrowCompletionOr<bool> internal_has_property(JS::PropertyKey const& name) const override; virtual String serialized() const = 0;
virtual JS::ThrowCompletionOr<JS::Value> internal_get(JS::PropertyKey const&, JS::Value receiver, JS::CacheablePropertyMetadata*, PropertyLookupPhase) const override;
virtual JS::ThrowCompletionOr<bool> internal_set(JS::PropertyKey const&, JS::Value value, JS::Value receiver, JS::CacheablePropertyMetadata*) override;
virtual JS::GCPtr<CSSRule> parent_rule() const; virtual JS::GCPtr<CSSRule> parent_rule() const;
protected: protected:
explicit CSSStyleDeclaration(JS::Realm&); explicit CSSStyleDeclaration(JS::Realm&);
virtual CSSStyleDeclaration& generated_style_properties_to_css_style_declaration() override { return *this; }
private:
// ^PlatformObject
virtual Optional<JS::Value> item_value(size_t index) const override;
}; };
class PropertyOwningCSSStyleDeclaration : public CSSStyleDeclaration { class PropertyOwningCSSStyleDeclaration : public CSSStyleDeclaration {

View file

@ -1,3 +1,5 @@
#import <CSS/GeneratedCSSStyleProperties.idl>
// https://drafts.csswg.org/cssom/#cssstyledeclaration // https://drafts.csswg.org/cssom/#cssstyledeclaration
[Exposed=Window] [Exposed=Window]
interface CSSStyleDeclaration { interface CSSStyleDeclaration {
@ -15,8 +17,8 @@ interface CSSStyleDeclaration {
readonly attribute CSSRule? parentRule; readonly attribute CSSRule? parentRule;
// NOTE: cssFloat is implemented manually in the bindings code, along with the other property accessors. [CEReactions, LegacyNullToEmptyString] attribute CSSOMString cssFloat;
// Hence, this does not need to be implemented.
// [CEReactions, LegacyNullToEmptyString] attribute CSSOMString cssFloat;
}; };
CSSStyleDeclaration includes GeneratedCSSStyleProperties;

View file

@ -64,6 +64,16 @@ function (generate_css_implementation)
arguments -j "${LIBWEB_INPUT_FOLDER}/CSS/Keywords.json" arguments -j "${LIBWEB_INPUT_FOLDER}/CSS/Keywords.json"
) )
invoke_idl_generator(
"GeneratedCSSStyleProperties.cpp"
Lagom::GenerateCSSStyleProperties
"${LIBWEB_INPUT_FOLDER}/CSS/Properties.json"
"CSS/GeneratedCSSStyleProperties.h"
"CSS/GeneratedCSSStyleProperties.cpp"
"CSS/GeneratedCSSStyleProperties.idl"
arguments -j "${LIBWEB_INPUT_FOLDER}/CSS/Properties.json"
)
embed_as_string( embed_as_string(
"DefaultStyleSheetSource.cpp" "DefaultStyleSheetSource.cpp"
"${LIBWEB_INPUT_FOLDER}/CSS/Default.css" "${LIBWEB_INPUT_FOLDER}/CSS/Default.css"
@ -98,6 +108,7 @@ function (generate_css_implementation)
set(CSS_GENERATED_HEADERS set(CSS_GENERATED_HEADERS
"CSS/Enums.h" "CSS/Enums.h"
"CSS/GeneratedCSSStyleProperties.h"
"CSS/Keyword.h" "CSS/Keyword.h"
"CSS/MathFunctions.h" "CSS/MathFunctions.h"
"CSS/MediaFeatureID.h" "CSS/MediaFeatureID.h"
@ -164,7 +175,7 @@ function (generate_js_bindings target)
add_custom_command( add_custom_command(
OUTPUT ${BINDINGS_SOURCES} OUTPUT ${BINDINGS_SOURCES}
COMMAND "$<TARGET_FILE:Lagom::BindingsGenerator>" -o "Bindings" --depfile "Bindings/${basename}.d" COMMAND "$<TARGET_FILE:Lagom::BindingsGenerator>" -o "Bindings" --depfile "Bindings/${basename}.d"
${depfile_prefix_arg} "${LIBWEB_INPUT_FOLDER}/${class}.idl" "${LIBWEB_INPUT_FOLDER}" ${depfile_prefix_arg} "${LIBWEB_INPUT_FOLDER}/${class}.idl" "${LIBWEB_INPUT_FOLDER}" "${CMAKE_CURRENT_BINARY_DIR}"
VERBATIM VERBATIM
COMMENT "Generating Bindings for ${class}" COMMENT "Generating Bindings for ${class}"
DEPENDS Lagom::BindingsGenerator DEPENDS Lagom::BindingsGenerator
@ -201,7 +212,7 @@ function (generate_js_bindings target)
add_custom_command( add_custom_command(
OUTPUT ${exposed_interface_sources} OUTPUT ${exposed_interface_sources}
COMMAND "${CMAKE_COMMAND}" -E make_directory "tmp" COMMAND "${CMAKE_COMMAND}" -E make_directory "tmp"
COMMAND $<TARGET_FILE:Lagom::GenerateWindowOrWorkerInterfaces> -o "${CMAKE_CURRENT_BINARY_DIR}/tmp" -b "${LIBWEB_INPUT_FOLDER}" ${LIBWEB_ALL_IDL_FILES} COMMAND $<TARGET_FILE:Lagom::GenerateWindowOrWorkerInterfaces> -o "${CMAKE_CURRENT_BINARY_DIR}/tmp" -b "${LIBWEB_INPUT_FOLDER}" -b "${CMAKE_CURRENT_BINARY_DIR}" ${LIBWEB_ALL_IDL_FILES}
COMMAND "${CMAKE_COMMAND}" -E copy_if_different tmp/IntrinsicDefinitions.cpp "Bindings/IntrinsicDefinitions.cpp" COMMAND "${CMAKE_COMMAND}" -E copy_if_different tmp/IntrinsicDefinitions.cpp "Bindings/IntrinsicDefinitions.cpp"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different tmp/DedicatedWorkerExposedInterfaces.h "Bindings/DedicatedWorkerExposedInterfaces.h" COMMAND "${CMAKE_COMMAND}" -E copy_if_different tmp/DedicatedWorkerExposedInterfaces.h "Bindings/DedicatedWorkerExposedInterfaces.h"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different tmp/DedicatedWorkerExposedInterfaces.cpp "Bindings/DedicatedWorkerExposedInterfaces.cpp" COMMAND "${CMAKE_COMMAND}" -E copy_if_different tmp/DedicatedWorkerExposedInterfaces.cpp "Bindings/DedicatedWorkerExposedInterfaces.cpp"

View file

@ -184,6 +184,26 @@ function(invoke_generator name generator primary_source header implementation)
set(CURRENT_LIB_GENERATED ${CURRENT_LIB_GENERATED} PARENT_SCOPE) set(CURRENT_LIB_GENERATED ${CURRENT_LIB_GENERATED} PARENT_SCOPE)
endfunction() endfunction()
function(invoke_idl_generator name generator primary_source header implementation idl)
cmake_parse_arguments(invoke_idl_generator "" "" "arguments;dependencies" ${ARGN})
add_custom_command(
OUTPUT "${header}" "${implementation}" "${idl}"
COMMAND $<TARGET_FILE:${generator}> -h "${header}.tmp" -c "${implementation}.tmp" -i "${idl}.tmp" ${invoke_idl_generator_arguments}
COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${header}.tmp" "${header}"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${implementation}.tmp" "${implementation}"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${idl}.tmp" "${idl}"
COMMAND "${CMAKE_COMMAND}" -E remove "${header}.tmp" "${implementation}.tmp" "${idl}.tmp"
VERBATIM
DEPENDS ${generator} ${invoke_idl_generator_dependencies} "${primary_source}"
)
add_custom_target("generate_${name}" DEPENDS "${header}" "${implementation}" "${idl}")
add_dependencies(all_generated "generate_${name}")
list(APPEND CURRENT_LIB_GENERATED "${name}")
set(CURRENT_LIB_GENERATED ${CURRENT_LIB_GENERATED} PARENT_SCOPE)
endfunction()
function(download_file_multisource urls path) function(download_file_multisource urls path)
cmake_parse_arguments(DOWNLOAD "" "SHA256" "" ${ARGN}) cmake_parse_arguments(DOWNLOAD "" "SHA256" "" ${ARGN})

View file

@ -6,6 +6,7 @@ lagom_tool(GenerateCSSMathFunctions SOURCES GenerateCSSMathFunctions.cpp
lagom_tool(GenerateCSSMediaFeatureID SOURCES GenerateCSSMediaFeatureID.cpp LIBS LibMain) lagom_tool(GenerateCSSMediaFeatureID SOURCES GenerateCSSMediaFeatureID.cpp LIBS LibMain)
lagom_tool(GenerateCSSPropertyID SOURCES GenerateCSSPropertyID.cpp LIBS LibMain) lagom_tool(GenerateCSSPropertyID SOURCES GenerateCSSPropertyID.cpp LIBS LibMain)
lagom_tool(GenerateCSSPseudoClass SOURCES GenerateCSSPseudoClass.cpp LIBS LibMain) lagom_tool(GenerateCSSPseudoClass SOURCES GenerateCSSPseudoClass.cpp LIBS LibMain)
lagom_tool(GenerateCSSStyleProperties SOURCES GenerateCSSStyleProperties.cpp LIBS LibMain)
lagom_tool(GenerateCSSTransformFunctions SOURCES GenerateCSSTransformFunctions.cpp LIBS LibMain) lagom_tool(GenerateCSSTransformFunctions SOURCES GenerateCSSTransformFunctions.cpp LIBS LibMain)
lagom_tool(GenerateWindowOrWorkerInterfaces SOURCES GenerateWindowOrWorkerInterfaces.cpp LIBS LibMain LibIDL) lagom_tool(GenerateWindowOrWorkerInterfaces SOURCES GenerateWindowOrWorkerInterfaces.cpp LIBS LibMain LibIDL)
lagom_tool(GenerateAriaRoles SOURCES GenerateAriaRoles.cpp LIBS LibMain) lagom_tool(GenerateAriaRoles SOURCES GenerateAriaRoles.cpp LIBS LibMain)

View file

@ -0,0 +1,211 @@
/*
* Copyright (c) 2024, Luke Wilde <luke@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "GeneratorUtil.h"
#include <AK/SourceGenerator.h>
#include <AK/StringBuilder.h>
#include <LibCore/ArgsParser.h>
#include <LibMain/Main.h>
ErrorOr<void> generate_header_file(JsonObject& properties, Core::File& file);
ErrorOr<void> generate_implementation_file(JsonObject& properties, Core::File& file);
ErrorOr<void> generate_idl_file(JsonObject& properties, Core::File& file);
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
StringView generated_header_path;
StringView generated_implementation_path;
StringView generated_idl_path;
StringView properties_json_path;
Core::ArgsParser args_parser;
args_parser.add_option(generated_header_path, "Path to the CSSStyleProperties header file to generate", "generated-header-path", 'h', "generated-header-path");
args_parser.add_option(generated_implementation_path, "Path to the CSSStyleProperties implementation file to generate", "generated-implementation-path", 'c', "generated-implementation-path");
args_parser.add_option(generated_idl_path, "Path to the CSSStyleProperties IDL file to generate", "generated-idl-path", 'i', "generated-idl-path");
args_parser.add_option(properties_json_path, "Path to the JSON file to read from", "json-path", 'j', "json-path");
args_parser.parse(arguments);
auto json = TRY(read_entire_file_as_json(properties_json_path));
VERIFY(json.is_object());
auto properties = json.as_object();
auto generated_header_file = TRY(Core::File::open(generated_header_path, Core::File::OpenMode::Write));
auto generated_implementation_file = TRY(Core::File::open(generated_implementation_path, Core::File::OpenMode::Write));
auto generated_idl_file = TRY(Core::File::open(generated_idl_path, Core::File::OpenMode::Write));
TRY(generate_header_file(properties, *generated_header_file));
TRY(generate_implementation_file(properties, *generated_implementation_file));
TRY(generate_idl_file(properties, *generated_idl_file));
return 0;
}
static String get_snake_case_function_name_for_css_property_name(ByteString const& name)
{
auto snake_case_name = snake_casify(name);
if (snake_case_name.starts_with('_'))
return MUST(snake_case_name.substring_from_byte_offset(1));
return snake_case_name;
}
static String make_name_acceptable_cpp(String const& name)
{
if (name.is_one_of("float")) {
StringBuilder builder;
builder.append(name);
builder.append('_');
return MUST(builder.to_string());
}
return name;
}
ErrorOr<void> generate_header_file(JsonObject& properties, Core::File& file)
{
StringBuilder builder;
SourceGenerator generator { builder };
generator.append(R"~~~(
#pragma once
#include <AK/String.h>
#include <LibWeb/Forward.h>
namespace Web::Bindings {
class GeneratedCSSStyleProperties {
public:
)~~~");
properties.for_each_member([&](auto& name, auto&) {
auto declaration_generator = generator.fork();
auto snake_case_name = get_snake_case_function_name_for_css_property_name(name);
declaration_generator.set("name:acceptable_cpp", make_name_acceptable_cpp(snake_case_name));
declaration_generator.append(R"~~~(
WebIDL::ExceptionOr<void> set_@name:acceptable_cpp@(StringView value);
String @name:acceptable_cpp@() const;
)~~~");
});
generator.append(R"~~~(
protected:
GeneratedCSSStyleProperties() = default;
virtual ~GeneratedCSSStyleProperties() = default;
virtual CSS::CSSStyleDeclaration& generated_style_properties_to_css_style_declaration() = 0;
CSS::CSSStyleDeclaration const& generated_style_properties_to_css_style_declaration() const { return const_cast<GeneratedCSSStyleProperties&>(*this).generated_style_properties_to_css_style_declaration(); }
}; // class GeneratedCSSStyleProperties
} // namespace Web::Bindings
)~~~");
TRY(file.write_until_depleted(generator.as_string_view().bytes()));
return {};
}
ErrorOr<void> generate_implementation_file(JsonObject& properties, Core::File& file)
{
StringBuilder builder;
SourceGenerator generator { builder };
generator.append(R"~~~(
#include <LibWeb/CSS/CSSStyleDeclaration.h>
#include <LibWeb/CSS/GeneratedCSSStyleProperties.h>
#include <LibWeb/WebIDL/ExceptionOr.h>
namespace Web::Bindings {
)~~~");
properties.for_each_member([&](auto& name, auto&) {
auto definition_generator = generator.fork();
definition_generator.set("name", name);
auto snake_case_name = get_snake_case_function_name_for_css_property_name(name);
definition_generator.set("name:acceptable_cpp", make_name_acceptable_cpp(snake_case_name));
definition_generator.append(R"~~~(
WebIDL::ExceptionOr<void> GeneratedCSSStyleProperties::set_@name:acceptable_cpp@(StringView value)
{
return generated_style_properties_to_css_style_declaration().set_property("@name@"sv, value, ""sv);
}
String GeneratedCSSStyleProperties::@name:acceptable_cpp@() const
{
return generated_style_properties_to_css_style_declaration().get_property_value("@name@"sv);
}
)~~~");
});
generator.append(R"~~~(
} // namespace Web::Bindings
)~~~");
TRY(file.write_until_depleted(generator.as_string_view().bytes()));
return {};
}
ErrorOr<void> generate_idl_file(JsonObject& properties, Core::File& file)
{
StringBuilder builder;
SourceGenerator generator { builder };
generator.append(R"~~~(
interface mixin GeneratedCSSStyleProperties {
)~~~");
properties.for_each_member([&](auto& name, auto&) {
auto member_generator = generator.fork();
member_generator.set("name", name);
auto snake_case_name = get_snake_case_function_name_for_css_property_name(name);
member_generator.set("name:snakecase", snake_case_name);
member_generator.set("name:acceptable_cpp", make_name_acceptable_cpp(snake_case_name));
// https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-camel-cased-attribute
// For each CSS property property that is a supported CSS property, the following partial interface applies
// where camel-cased attribute is obtained by running the CSS property to IDL attribute algorithm for property.
// partial interface CSSStyleProperties {
// [CEReactions] attribute [LegacyNullToEmptyString] CSSOMString _camel_cased_attribute;
// };
member_generator.set("name:camelcase", css_property_to_idl_attribute(name));
member_generator.append(R"~~~(
[CEReactions, LegacyNullToEmptyString, AttributeCallbackName=@name:snakecase@_regular, ImplementedAs=@name:acceptable_cpp@] attribute CSSOMString @name:camelcase@;
)~~~");
// For each CSS property property that is a supported CSS property and that begins with the string -webkit-,
// the following partial interface applies where webkit-cased attribute is obtained by running the CSS property
// to IDL attribute algorithm for property, with the lowercase first flag set.
if (name.starts_with("-webkit-"sv)) {
member_generator.set("name:webkit", css_property_to_idl_attribute(name, /* lowercase_first= */ true));
member_generator.append(R"~~~(
[CEReactions, LegacyNullToEmptyString, AttributeCallbackName=@name:snakecase@_webkit, ImplementedAs=@name:acceptable_cpp@] attribute CSSOMString @name:webkit@;
)~~~");
}
// For each CSS property property that is a supported CSS property, except for properties that have no
// "-" (U+002D) in the property name, the following partial interface applies where dashed attribute is
// property.
// partial interface CSSStyleProperties {
// [CEReactions] attribute [LegacyNullToEmptyString] CSSOMString _dashed_attribute;
// };
if (name.contains('-')) {
member_generator.append(R"~~~(
[CEReactions, LegacyNullToEmptyString, AttributeCallbackName=@name:snakecase@_dashed, ImplementedAs=@name:acceptable_cpp@] attribute CSSOMString @name@;
)~~~");
}
});
generator.append(R"~~~(
};
)~~~");
TRY(file.write_until_depleted(generator.as_string_view().bytes()));
return {};
}

View file

@ -64,3 +64,42 @@ inline ErrorOr<JsonValue> read_entire_file_as_json(StringView filename)
TRY(file->read_until_filled(json_data.bytes())); TRY(file->read_until_filled(json_data.bytes()));
return JsonValue::from_string(json_data); return JsonValue::from_string(json_data);
} }
// https://drafts.csswg.org/cssom/#css-property-to-idl-attribute
inline String css_property_to_idl_attribute(StringView property_name, bool lowercase_first = false)
{
// The CSS property to IDL attribute algorithm for property, optionally with a lowercase first flag set, is as follows:
// 1. Let output be the empty string.
StringBuilder output;
// 2. Let uppercase next be unset.
bool uppercase_next = false;
// 3. If the lowercase first flag is set, remove the first character from property.
StringView actual_property_name;
if (lowercase_first) {
actual_property_name = property_name.substring_view(1);
} else {
actual_property_name = property_name;
}
// 4. For each character c in property:
for (auto c : actual_property_name) {
// 1. If c is "-" (U+002D), let uppercase next be set.
if (c == '-') {
uppercase_next = true;
}
// 2. Otherwise, if uppercase next is set, let uppercase next be unset and append c converted to ASCII uppercase to output.
else if (uppercase_next) {
uppercase_next = false;
output.append(to_ascii_uppercase(c));
}
// 3. Otherwise, append c to output.
else {
output.append(c);
}
}
// 5. Return output.
return MUST(output.to_string());
}

View file

@ -0,0 +1,573 @@
All supported properties and their default values exposed from CSSStyleDeclaration from getComputedStyle:
'cssText': ''
'length': '201'
'parentRule': 'null'
'cssFloat': 'none'
'WebkitAlignContent': 'normal'
'webkitAlignContent': 'normal'
'-webkit-align-content': 'normal'
'WebkitAlignItems': 'normal'
'webkitAlignItems': 'normal'
'-webkit-align-items': 'normal'
'WebkitAlignSelf': 'auto'
'webkitAlignSelf': 'auto'
'-webkit-align-self': 'auto'
'WebkitAnimation': 'none auto ease 1 normal running 0s none'
'webkitAnimation': 'none auto ease 1 normal running 0s none'
'-webkit-animation': 'none auto ease 1 normal running 0s none'
'WebkitAnimationDelay': '0s'
'webkitAnimationDelay': '0s'
'-webkit-animation-delay': '0s'
'WebkitAnimationDirection': 'normal'
'webkitAnimationDirection': 'normal'
'-webkit-animation-direction': 'normal'
'WebkitAnimationDuration': 'auto'
'webkitAnimationDuration': 'auto'
'-webkit-animation-duration': 'auto'
'WebkitAnimationFillMode': 'none'
'webkitAnimationFillMode': 'none'
'-webkit-animation-fill-mode': 'none'
'WebkitAnimationIterationCount': '1'
'webkitAnimationIterationCount': '1'
'-webkit-animation-iteration-count': '1'
'WebkitAnimationName': 'none'
'webkitAnimationName': 'none'
'-webkit-animation-name': 'none'
'WebkitAnimationPlayState': 'running'
'webkitAnimationPlayState': 'running'
'-webkit-animation-play-state': 'running'
'WebkitAnimationTimingFunction': 'ease'
'webkitAnimationTimingFunction': 'ease'
'-webkit-animation-timing-function': 'ease'
'WebkitAppearance': 'auto'
'webkitAppearance': 'auto'
'-webkit-appearance': 'auto'
'WebkitBackgroundClip': 'border-box'
'webkitBackgroundClip': 'border-box'
'-webkit-background-clip': 'border-box'
'WebkitBackgroundOrigin': 'padding-box'
'webkitBackgroundOrigin': 'padding-box'
'-webkit-background-origin': 'padding-box'
'WebkitBackgroundSize': 'auto auto'
'webkitBackgroundSize': 'auto auto'
'-webkit-background-size': 'auto auto'
'WebkitBorderBottomLeftRadius': '0px'
'webkitBorderBottomLeftRadius': '0px'
'-webkit-border-bottom-left-radius': '0px'
'WebkitBorderBottomRightRadius': '0px'
'webkitBorderBottomRightRadius': '0px'
'-webkit-border-bottom-right-radius': '0px'
'WebkitBorderRadius': '0px 0px 0px 0px'
'webkitBorderRadius': '0px 0px 0px 0px'
'-webkit-border-radius': '0px 0px 0px 0px'
'WebkitBorderTopLeftRadius': '0px'
'webkitBorderTopLeftRadius': '0px'
'-webkit-border-top-left-radius': '0px'
'WebkitBorderTopRightRadius': '0px'
'webkitBorderTopRightRadius': '0px'
'-webkit-border-top-right-radius': '0px'
'WebkitBoxShadow': 'none'
'webkitBoxShadow': 'none'
'-webkit-box-shadow': 'none'
'WebkitBoxSizing': 'content-box'
'webkitBoxSizing': 'content-box'
'-webkit-box-sizing': 'content-box'
'WebkitFlex': '0 1 auto'
'webkitFlex': '0 1 auto'
'-webkit-flex': '0 1 auto'
'WebkitFlexBasis': 'auto'
'webkitFlexBasis': 'auto'
'-webkit-flex-basis': 'auto'
'WebkitFlexDirection': 'row'
'webkitFlexDirection': 'row'
'-webkit-flex-direction': 'row'
'WebkitFlexFlow': 'row nowrap'
'webkitFlexFlow': 'row nowrap'
'-webkit-flex-flow': 'row nowrap'
'WebkitFlexGrow': '0'
'webkitFlexGrow': '0'
'-webkit-flex-grow': '0'
'WebkitFlexShrink': '1'
'webkitFlexShrink': '1'
'-webkit-flex-shrink': '1'
'WebkitFlexWrap': 'nowrap'
'webkitFlexWrap': 'nowrap'
'-webkit-flex-wrap': 'nowrap'
'WebkitJustifyContent': 'normal'
'webkitJustifyContent': 'normal'
'-webkit-justify-content': 'normal'
'WebkitMask': 'none'
'webkitMask': 'none'
'-webkit-mask': 'none'
'WebkitOrder': '0'
'webkitOrder': '0'
'-webkit-order': '0'
'WebkitTextFillColor': 'rgb(0, 0, 0)'
'webkitTextFillColor': 'rgb(0, 0, 0)'
'-webkit-text-fill-color': 'rgb(0, 0, 0)'
'WebkitTransform': 'none'
'webkitTransform': 'none'
'-webkit-transform': 'none'
'WebkitTransformOrigin': '50% 50%'
'webkitTransformOrigin': '50% 50%'
'-webkit-transform-origin': '50% 50%'
'WebkitTransition': 'all 0s ease 0s'
'webkitTransition': 'all 0s ease 0s'
'-webkit-transition': 'all 0s ease 0s'
'WebkitTransitionDelay': '0s'
'webkitTransitionDelay': '0s'
'-webkit-transition-delay': '0s'
'WebkitTransitionDuration': '0s'
'webkitTransitionDuration': '0s'
'-webkit-transition-duration': '0s'
'WebkitTransitionProperty': 'all'
'webkitTransitionProperty': 'all'
'-webkit-transition-property': 'all'
'WebkitTransitionTimingFunction': 'ease'
'webkitTransitionTimingFunction': 'ease'
'-webkit-transition-timing-function': 'ease'
'accentColor': 'auto'
'accent-color': 'auto'
'alignContent': 'normal'
'align-content': 'normal'
'alignItems': 'normal'
'align-items': 'normal'
'alignSelf': 'auto'
'align-self': 'auto'
'animation': 'none auto ease 1 normal running 0s none'
'animationDelay': '0s'
'animation-delay': '0s'
'animationDirection': 'normal'
'animation-direction': 'normal'
'animationDuration': 'auto'
'animation-duration': 'auto'
'animationFillMode': 'none'
'animation-fill-mode': 'none'
'animationIterationCount': '1'
'animation-iteration-count': '1'
'animationName': 'none'
'animation-name': 'none'
'animationPlayState': 'running'
'animation-play-state': 'running'
'animationTimingFunction': 'ease'
'animation-timing-function': 'ease'
'appearance': 'auto'
'aspectRatio': 'auto'
'aspect-ratio': 'auto'
'backdropFilter': 'none'
'backdrop-filter': 'none'
'background': 'scroll border-box rgba(0, 0, 0, 0) none padding-box left 0% top 0% repeat repeat auto auto'
'backgroundAttachment': 'scroll'
'background-attachment': 'scroll'
'backgroundClip': 'border-box'
'background-clip': 'border-box'
'backgroundColor': 'rgba(0, 0, 0, 0)'
'background-color': 'rgba(0, 0, 0, 0)'
'backgroundImage': 'none'
'background-image': 'none'
'backgroundOrigin': 'padding-box'
'background-origin': 'padding-box'
'backgroundPosition': 'left 0% top 0%'
'background-position': 'left 0% top 0%'
'backgroundPositionX': 'left 0%'
'background-position-x': 'left 0%'
'backgroundPositionY': 'top 0%'
'background-position-y': 'top 0%'
'backgroundRepeat': 'repeat repeat'
'background-repeat': 'repeat repeat'
'backgroundSize': 'auto auto'
'background-size': 'auto auto'
'border': 'medium none rgb(0, 0, 0)'
'borderBottom': 'medium none rgb(0, 0, 0)'
'border-bottom': 'medium none rgb(0, 0, 0)'
'borderBottomColor': 'rgb(0, 0, 0)'
'border-bottom-color': 'rgb(0, 0, 0)'
'borderBottomLeftRadius': '0px'
'border-bottom-left-radius': '0px'
'borderBottomRightRadius': '0px'
'border-bottom-right-radius': '0px'
'borderBottomStyle': 'none'
'border-bottom-style': 'none'
'borderBottomWidth': 'medium'
'border-bottom-width': 'medium'
'borderCollapse': 'separate'
'border-collapse': 'separate'
'borderColor': 'rgb(0, 0, 0) rgb(0, 0, 0) rgb(0, 0, 0) rgb(0, 0, 0)'
'border-color': 'rgb(0, 0, 0) rgb(0, 0, 0) rgb(0, 0, 0) rgb(0, 0, 0)'
'borderLeft': 'medium none rgb(0, 0, 0)'
'border-left': 'medium none rgb(0, 0, 0)'
'borderLeftColor': 'rgb(0, 0, 0)'
'border-left-color': 'rgb(0, 0, 0)'
'borderLeftStyle': 'none'
'border-left-style': 'none'
'borderLeftWidth': 'medium'
'border-left-width': 'medium'
'borderRadius': '0px 0px 0px 0px'
'border-radius': '0px 0px 0px 0px'
'borderRight': 'medium none rgb(0, 0, 0)'
'border-right': 'medium none rgb(0, 0, 0)'
'borderRightColor': 'rgb(0, 0, 0)'
'border-right-color': 'rgb(0, 0, 0)'
'borderRightStyle': 'none'
'border-right-style': 'none'
'borderRightWidth': 'medium'
'border-right-width': 'medium'
'borderSpacing': '0px'
'border-spacing': '0px'
'borderStyle': 'none none none none'
'border-style': 'none none none none'
'borderTop': 'medium none rgb(0, 0, 0)'
'border-top': 'medium none rgb(0, 0, 0)'
'borderTopColor': 'rgb(0, 0, 0)'
'border-top-color': 'rgb(0, 0, 0)'
'borderTopLeftRadius': '0px'
'border-top-left-radius': '0px'
'borderTopRightRadius': '0px'
'border-top-right-radius': '0px'
'borderTopStyle': 'none'
'border-top-style': 'none'
'borderTopWidth': 'medium'
'border-top-width': 'medium'
'borderWidth': 'medium medium medium medium'
'border-width': 'medium medium medium medium'
'bottom': 'auto'
'boxShadow': 'none'
'box-shadow': 'none'
'boxSizing': 'content-box'
'box-sizing': 'content-box'
'captionSide': 'top'
'caption-side': 'top'
'clear': 'none'
'clip': 'auto'
'clipPath': 'none'
'clip-path': 'none'
'clipRule': 'nonzero'
'clip-rule': 'nonzero'
'color': 'rgb(0, 0, 0)'
'columnCount': 'auto'
'column-count': 'auto'
'columnGap': 'normal'
'column-gap': 'normal'
'columnSpan': 'none'
'column-span': 'none'
'columnWidth': 'auto'
'column-width': 'auto'
'columns': 'auto auto'
'content': 'normal'
'contentVisibility': 'visible'
'content-visibility': 'visible'
'counterIncrement': 'none'
'counter-increment': 'none'
'counterReset': 'none'
'counter-reset': 'none'
'counterSet': 'none'
'counter-set': 'none'
'cursor': 'auto'
'cx': '0px'
'cy': '0px'
'direction': 'ltr'
'display': 'block'
'fill': 'rgb(0, 0, 0)'
'fillOpacity': '1'
'fill-opacity': '1'
'fillRule': 'nonzero'
'fill-rule': 'nonzero'
'filter': 'none'
'flex': '0 1 auto'
'flexBasis': 'auto'
'flex-basis': 'auto'
'flexDirection': 'row'
'flex-direction': 'row'
'flexFlow': 'row nowrap'
'flex-flow': 'row nowrap'
'flexGrow': '0'
'flex-grow': '0'
'flexShrink': '1'
'flex-shrink': '1'
'flexWrap': 'nowrap'
'flex-wrap': 'nowrap'
'float': 'none'
'font': 'serif 16px normal normal normal 400 normal'
'fontFamily': 'serif'
'font-family': 'serif'
'fontFeatureSettings': 'normal'
'font-feature-settings': 'normal'
'fontLanguageOverride': 'normal'
'font-language-override': 'normal'
'fontSize': '16px'
'font-size': '16px'
'fontStretch': 'normal'
'font-stretch': 'normal'
'fontStyle': 'normal'
'font-style': 'normal'
'fontVariant': 'normal'
'font-variant': 'normal'
'fontVariationSettings': 'normal'
'font-variation-settings': 'normal'
'fontWeight': '400'
'font-weight': '400'
'fontWidth': 'normal'
'font-width': 'normal'
'gap': 'normal normal'
'grid': 'none auto auto'
'gridArea': 'auto auto auto auto'
'grid-area': 'auto auto auto auto'
'gridAutoColumns': 'auto'
'grid-auto-columns': 'auto'
'gridAutoFlow': 'row'
'grid-auto-flow': 'row'
'gridAutoRows': 'auto'
'grid-auto-rows': 'auto'
'gridColumn': 'auto auto'
'grid-column': 'auto auto'
'gridColumnEnd': 'auto'
'grid-column-end': 'auto'
'gridColumnGap': 'normal'
'grid-column-gap': 'normal'
'gridColumnStart': 'auto'
'grid-column-start': 'auto'
'gridGap': 'normal normal'
'grid-gap': 'normal normal'
'gridRow': 'auto auto'
'grid-row': 'auto auto'
'gridRowEnd': 'auto'
'grid-row-end': 'auto'
'gridRowGap': 'normal'
'grid-row-gap': 'normal'
'gridRowStart': 'auto'
'grid-row-start': 'auto'
'gridTemplate': 'none auto auto'
'grid-template': 'none auto auto'
'gridTemplateAreas': 'none'
'grid-template-areas': 'none'
'gridTemplateColumns': 'auto'
'grid-template-columns': 'auto'
'gridTemplateRows': 'auto'
'grid-template-rows': 'auto'
'height': '0px'
'imageRendering': 'auto'
'image-rendering': 'auto'
'inlineSize': 'auto'
'inline-size': 'auto'
'inset': 'auto auto auto auto'
'insetBlock': 'auto auto auto auto'
'inset-block': 'auto auto auto auto'
'insetBlockEnd': 'auto'
'inset-block-end': 'auto'
'insetBlockStart': 'auto'
'inset-block-start': 'auto'
'insetInline': 'auto auto auto auto'
'inset-inline': 'auto auto auto auto'
'insetInlineEnd': 'auto'
'inset-inline-end': 'auto'
'insetInlineStart': 'auto'
'inset-inline-start': 'auto'
'justifyContent': 'normal'
'justify-content': 'normal'
'justifyItems': 'legacy'
'justify-items': 'legacy'
'justifySelf': 'auto'
'justify-self': 'auto'
'left': 'auto'
'letterSpacing': 'normal'
'letter-spacing': 'normal'
'lineHeight': 'normal'
'line-height': 'normal'
'listStyle': 'disc outside none'
'list-style': 'disc outside none'
'listStyleImage': 'none'
'list-style-image': 'none'
'listStylePosition': 'outside'
'list-style-position': 'outside'
'listStyleType': 'disc'
'list-style-type': 'disc'
'margin': '8px 8px 8px 8px'
'marginBlock': '8px 8px 8px 8px'
'margin-block': '8px 8px 8px 8px'
'marginBlockEnd': '8px'
'margin-block-end': '8px'
'marginBlockStart': '8px'
'margin-block-start': '8px'
'marginBottom': '8px'
'margin-bottom': '8px'
'marginInline': '8px 8px 8px 8px'
'margin-inline': '8px 8px 8px 8px'
'marginInlineEnd': '8px'
'margin-inline-end': '8px'
'marginInlineStart': '8px'
'margin-inline-start': '8px'
'marginLeft': '8px'
'margin-left': '8px'
'marginRight': '8px'
'margin-right': '8px'
'marginTop': '8px'
'margin-top': '8px'
'mask': 'none'
'maskType': 'luminance'
'mask-type': 'luminance'
'mathDepth': '0'
'math-depth': '0'
'mathShift': 'normal'
'math-shift': 'normal'
'mathStyle': 'normal'
'math-style': 'normal'
'maxHeight': 'none'
'max-height': 'none'
'maxInlineSize': 'none'
'max-inline-size': 'none'
'maxWidth': 'none'
'max-width': 'none'
'minHeight': 'auto'
'min-height': 'auto'
'minInlineSize': '0px'
'min-inline-size': '0px'
'minWidth': 'auto'
'min-width': 'auto'
'objectFit': 'fill'
'object-fit': 'fill'
'objectPosition': 'left 50% top 50%'
'object-position': 'left 50% top 50%'
'opacity': '1'
'order': '0'
'outline': 'rgb(0, 0, 0) none medium'
'outlineColor': 'rgb(0, 0, 0)'
'outline-color': 'rgb(0, 0, 0)'
'outlineOffset': '0px'
'outline-offset': '0px'
'outlineStyle': 'none'
'outline-style': 'none'
'outlineWidth': 'medium'
'outline-width': 'medium'
'overflow': 'visible visible'
'overflowX': 'visible'
'overflow-x': 'visible'
'overflowY': 'visible'
'overflow-y': 'visible'
'padding': '0px 0px 0px 0px'
'paddingBlock': '0px 0px 0px 0px'
'padding-block': '0px 0px 0px 0px'
'paddingBlockEnd': '0px'
'padding-block-end': '0px'
'paddingBlockStart': '0px'
'padding-block-start': '0px'
'paddingBottom': '0px'
'padding-bottom': '0px'
'paddingInline': '0px 0px 0px 0px'
'padding-inline': '0px 0px 0px 0px'
'paddingInlineEnd': '0px'
'padding-inline-end': '0px'
'paddingInlineStart': '0px'
'padding-inline-start': '0px'
'paddingLeft': '0px'
'padding-left': '0px'
'paddingRight': '0px'
'padding-right': '0px'
'paddingTop': '0px'
'padding-top': '0px'
'placeContent': 'normal normal'
'place-content': 'normal normal'
'placeItems': 'normal legacy'
'place-items': 'normal legacy'
'placeSelf': 'auto auto'
'place-self': 'auto auto'
'pointerEvents': 'auto'
'pointer-events': 'auto'
'position': 'static'
'quotes': 'auto'
'r': '0px'
'right': 'auto'
'rotate': 'none'
'rowGap': 'normal'
'row-gap': 'normal'
'rx': 'auto'
'ry': 'auto'
'scrollbarGutter': 'auto'
'scrollbar-gutter': 'auto'
'scrollbarWidth': 'auto'
'scrollbar-width': 'auto'
'stopColor': 'rgb(0, 0, 0)'
'stop-color': 'rgb(0, 0, 0)'
'stopOpacity': '1'
'stop-opacity': '1'
'stroke': 'none'
'strokeLinecap': 'butt'
'stroke-linecap': 'butt'
'strokeLinejoin': 'miter'
'stroke-linejoin': 'miter'
'strokeMiterlimit': '4'
'stroke-miterlimit': '4'
'strokeOpacity': '1'
'stroke-opacity': '1'
'strokeWidth': '1px'
'stroke-width': '1px'
'tabSize': '8'
'tab-size': '8'
'tableLayout': 'auto'
'table-layout': 'auto'
'textAlign': 'start'
'text-align': 'start'
'textAnchor': 'start'
'text-anchor': 'start'
'textDecoration': 'rgb(0, 0, 0) none solid auto'
'text-decoration': 'rgb(0, 0, 0) none solid auto'
'textDecorationColor': 'rgb(0, 0, 0)'
'text-decoration-color': 'rgb(0, 0, 0)'
'textDecorationLine': 'none'
'text-decoration-line': 'none'
'textDecorationStyle': 'solid'
'text-decoration-style': 'solid'
'textDecorationThickness': 'auto'
'text-decoration-thickness': 'auto'
'textIndent': '0px'
'text-indent': '0px'
'textJustify': 'auto'
'text-justify': 'auto'
'textOverflow': 'clip'
'text-overflow': 'clip'
'textShadow': 'none'
'text-shadow': 'none'
'textTransform': 'none'
'text-transform': 'none'
'top': 'auto'
'transform': 'none'
'transformBox': 'view-box'
'transform-box': 'view-box'
'transformOrigin': '50% 50%'
'transform-origin': '50% 50%'
'transition': 'all 0s ease 0s'
'transitionDelay': '0s'
'transition-delay': '0s'
'transitionDuration': '0s'
'transition-duration': '0s'
'transitionProperty': 'all'
'transition-property': 'all'
'transitionTimingFunction': 'ease'
'transition-timing-function': 'ease'
'unicodeBidi': 'normal'
'unicode-bidi': 'normal'
'userSelect': 'auto'
'user-select': 'auto'
'verticalAlign': 'baseline'
'vertical-align': 'baseline'
'visibility': 'visible'
'whiteSpace': 'normal'
'white-space': 'normal'
'width': '284px'
'wordBreak': 'normal'
'word-break': 'normal'
'wordSpacing': 'normal'
'word-spacing': 'normal'
'wordWrap': 'normal'
'word-wrap': 'normal'
'writingMode': 'horizontal-tb'
'writing-mode': 'horizontal-tb'
'x': '0px'
'y': '0px'
'zIndex': 'auto'
'z-index': 'auto'
'getPropertyPriority': 'function getPropertyPriority() { [native code] }'
'getPropertyValue': 'function getPropertyValue() { [native code] }'
'removeProperty': 'function removeProperty() { [native code] }'
'item': 'function item() { [native code] }'
'setProperty': 'function setProperty() { [native code] }'
'constructor': 'function CSSStyleDeclaration() { [native code] }'

View file

@ -0,0 +1,18 @@
<!DOCTYPE html>
<script src="../include.js"></script>
<script>
test(() => {
const defaultStyleIframe = document.createElement("iframe");
document.body.appendChild(defaultStyleIframe);
const defaultStyle = getComputedStyle(defaultStyleIframe.contentDocument.body);
const stylePrototype = Object.getPrototypeOf(defaultStyle);
const supportedProperties = Object.getOwnPropertyNames(stylePrototype);
println("All supported properties and their default values exposed from CSSStyleDeclaration from getComputedStyle:");
for (const supportedProperty of supportedProperties) {
println(`'${supportedProperty}': '${defaultStyle[supportedProperty]}'`);
}
});
</script>