From 575ce0414827cf6bc76e7c954eef762393407993 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Wed, 6 Oct 2021 14:27:13 +0100 Subject: [PATCH] LibWeb: Add CSS.escape() JS function This is the `CSS` namespace defined in IDL here: https://www.w3.org/TR/cssom-1/#namespacedef-css , not to be confused with our `Web::CSS` namespace. Words are hard. `CSS.escape()` lets you escape identifiers that can then be used to create a CSS string. I've also stubbed out the `CSS.supports()` function. --- .../LibWeb/Bindings/CSSNamespace.cpp | 75 +++++++++++++++++ .../Libraries/LibWeb/Bindings/CSSNamespace.h | 28 +++++++ .../LibWeb/Bindings/WindowObject.cpp | 3 + Userland/Libraries/LibWeb/CMakeLists.txt | 2 + Userland/Libraries/LibWeb/CSS/Serialize.cpp | 82 +++++++++++++++++++ Userland/Libraries/LibWeb/CSS/Serialize.h | 19 +++++ 6 files changed, 209 insertions(+) create mode 100644 Userland/Libraries/LibWeb/Bindings/CSSNamespace.cpp create mode 100644 Userland/Libraries/LibWeb/Bindings/CSSNamespace.h create mode 100644 Userland/Libraries/LibWeb/CSS/Serialize.cpp create mode 100644 Userland/Libraries/LibWeb/CSS/Serialize.h diff --git a/Userland/Libraries/LibWeb/Bindings/CSSNamespace.cpp b/Userland/Libraries/LibWeb/Bindings/CSSNamespace.cpp new file mode 100644 index 00000000000..3392d8084e7 --- /dev/null +++ b/Userland/Libraries/LibWeb/Bindings/CSSNamespace.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2021, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include + +namespace Web::Bindings { + +CSSNamespace::CSSNamespace(JS::GlobalObject& global_object) + : JS::Object(*global_object.object_prototype()) +{ +} + +CSSNamespace::~CSSNamespace() +{ +} + +void CSSNamespace::initialize(JS::GlobalObject& global_object) +{ + Object::initialize(global_object); + u8 attr = JS::Attribute::Enumerable; + define_native_function("escape", escape, 1, attr); + define_native_function("supports", supports, 2, attr); +} + +// https://www.w3.org/TR/cssom-1/#dom-css-escape +JS_DEFINE_NATIVE_FUNCTION(CSSNamespace::escape) +{ + if (!vm.argument_count()) { + vm.throw_exception(global_object, JS::ErrorType::BadArgCountAtLeastOne, "CSS.escape"); + return {}; + } + + String result = Web::CSS::serialize_an_identifier(vm.argument(0).to_string(global_object)); + if (vm.exception()) + return {}; + + return JS::Value(JS::js_string(vm, result)); +} + +// https://www.w3.org/TR/css-conditional-3/#dom-css-supports +JS_DEFINE_NATIVE_FUNCTION(CSSNamespace::supports) +{ + if (!vm.argument_count()) { + vm.throw_exception(global_object, JS::ErrorType::BadArgCountAtLeastOne, "CSS.supports"); + return {}; + } + + if (vm.argument_count() >= 2) { + // When the supports(property, value) method is invoked with two arguments property and value: + // If property is an ASCII case-insensitive match for any defined CSS property that the UA supports, and value successfully parses according to that property’s grammar, return true. + // + // Otherwise, if property is a custom property name string, return true. + // + // Otherwise, return false. + return JS::Value(false); + } else { + // When the supports(conditionText) method is invoked with a single conditionText argument: + // + // If conditionText, parsed and evaluated as a , would return true, return true. + // + // Otherwise, If conditionText, wrapped in parentheses and then parsed and evaluated as a , would return true, return true. + // + // Otherwise, return false. + return JS::Value(false); + } +} + +} diff --git a/Userland/Libraries/LibWeb/Bindings/CSSNamespace.h b/Userland/Libraries/LibWeb/Bindings/CSSNamespace.h new file mode 100644 index 00000000000..2791c545804 --- /dev/null +++ b/Userland/Libraries/LibWeb/Bindings/CSSNamespace.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2021, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::Bindings { + +// The `CSS` namespace object in IDL. https://www.w3.org/TR/cssom-1/#namespacedef-css +class CSSNamespace final : public JS::Object { + JS_OBJECT(CSSNamespace, JS::Object) + +public: + explicit CSSNamespace(JS::GlobalObject&); + virtual void initialize(JS::GlobalObject&) override; + virtual ~CSSNamespace() override; + +private: + JS_DECLARE_NATIVE_FUNCTION(escape); + JS_DECLARE_NATIVE_FUNCTION(supports); +}; + +} diff --git a/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp b/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp index e8876a3984a..d21887e1704 100644 --- a/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp +++ b/Userland/Libraries/LibWeb/Bindings/WindowObject.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -104,6 +105,8 @@ void WindowObject::initialize_global_object() define_native_accessor("screenLeft", screen_left_getter, {}, attr); define_native_accessor("screenTop", screen_top_getter, {}, attr); + define_direct_property("CSS", heap().allocate(*this, *this), 0); + // Legacy define_native_accessor("event", event_getter, event_setter, JS::Attribute::Enumerable); diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index fb6bedc4b3a..d7cce140915 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -1,4 +1,5 @@ set(SOURCES + Bindings/CSSNamespace.cpp Bindings/CSSRuleWrapperFactory.cpp Bindings/CSSStyleDeclarationWrapperCustom.cpp Bindings/EventListenerWrapper.cpp @@ -14,6 +15,7 @@ set(SOURCES Bindings/WindowObject.cpp Bindings/Wrappable.cpp Crypto/Crypto.cpp + CSS/Serialize.cpp CSS/CSSConditionRule.cpp CSS/CSSGroupingRule.cpp CSS/CSSImportRule.cpp diff --git a/Userland/Libraries/LibWeb/CSS/Serialize.cpp b/Userland/Libraries/LibWeb/CSS/Serialize.cpp new file mode 100644 index 00000000000..3989f64787b --- /dev/null +++ b/Userland/Libraries/LibWeb/CSS/Serialize.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2021, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace Web::CSS { + +// https://www.w3.org/TR/cssom-1/#escape-a-character +String escape_a_character(u32 character) +{ + StringBuilder builder; + builder.append('\\'); + builder.append_code_point(character); + return builder.to_string(); +} + +// https://www.w3.org/TR/cssom-1/#escape-a-character-as-code-point +String escape_a_character_as_code_point(u32 character) +{ + return String::formatted("\\{:x} ", character); +} + +// https://www.w3.org/TR/cssom-1/#serialize-an-identifier +String serialize_an_identifier(StringView const& ident) +{ + StringBuilder builder; + Utf8View characters { ident }; + auto first_character = characters.is_empty() ? 0 : *characters.begin(); + + // To serialize an identifier means to create a string represented by the concatenation of, + // for each character of the identifier: + for (auto character : characters) { + // If the character is NULL (U+0000), then the REPLACEMENT CHARACTER (U+FFFD). + if (character == 0) { + builder.append_code_point(0xFFFD); + continue; + } + // If the character is in the range [\1-\1f] (U+0001 to U+001F) or is U+007F, + // then the character escaped as code point. + if ((character >= 0x0001 && character <= 0x001F) || (character == 0x007F)) { + builder.append(escape_a_character_as_code_point(character)); + continue; + } + // If the character is the first character and is in the range [0-9] (U+0030 to U+0039), + // then the character escaped as code point. + if (builder.is_empty() && character >= '0' && character <= '9') { + builder.append(escape_a_character_as_code_point(character)); + continue; + } + // If the character is the second character and is in the range [0-9] (U+0030 to U+0039) + // and the first character is a "-" (U+002D), then the character escaped as code point. + if (builder.length() == 1 && first_character == '-' && character >= '0' && character <= '9') { + builder.append(escape_a_character_as_code_point(character)); + continue; + } + // If the character is the first character and is a "-" (U+002D), and there is no second + // character, then the escaped character. + if (builder.is_empty() && character == '-' && characters.length() == 1) { + builder.append(escape_a_character(character)); + continue; + } + // If the character is not handled by one of the above rules and is greater than or equal to U+0080, is "-" (U+002D) or "_" (U+005F), or is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to U+005A), or \[a-z] (U+0061 to U+007A), then the character itself. + if ((character >= 0x0080) + || (character == '-') || (character == '_') + || (character >= '0' && character <= '9') + || (character >= 'A' && character <= 'Z') + || (character >= 'a' && character <= 'z')) { + builder.append_code_point(character); + continue; + } + // Otherwise, the escaped character. + builder.append(escape_a_character(character)); + } + return builder.to_string(); +} + +} diff --git a/Userland/Libraries/LibWeb/CSS/Serialize.h b/Userland/Libraries/LibWeb/CSS/Serialize.h new file mode 100644 index 00000000000..08264ebc819 --- /dev/null +++ b/Userland/Libraries/LibWeb/CSS/Serialize.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2021, Sam Atkins + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::CSS { + +String escape_a_character(u32 character); +String escape_a_character_as_code_point(u32 character); + +String serialize_an_identifier(StringView const& ident); + +}