From 6cbb5d278532931cdcdece133efe87ff682eb695 Mon Sep 17 00:00:00 2001 From: Mark Langen Date: Sun, 16 Mar 2025 18:44:49 -0700 Subject: [PATCH] LibWeb: Parse and propagate touch-action CSS property Co-authored-by: Sam Atkins --- Libraries/LibWeb/CSS/ComputedProperties.cpp | 48 +++++++++++++ Libraries/LibWeb/CSS/ComputedProperties.h | 1 + Libraries/LibWeb/CSS/ComputedValues.h | 26 +++++++ Libraries/LibWeb/CSS/Enums.json | 11 +++ Libraries/LibWeb/CSS/Keywords.json | 7 ++ Libraries/LibWeb/CSS/Parser/Parser.h | 1 + .../LibWeb/CSS/Parser/PropertyParsing.cpp | 65 ++++++++++++++++++ Libraries/LibWeb/CSS/Properties.json | 9 +++ Libraries/LibWeb/Layout/Node.cpp | 2 + ...eclaration-has-indexed-property-getter.txt | 35 +++++----- ...upported-properties-and-default-values.txt | 2 + .../css/getComputedStyle-print-all.txt | 1 + .../css/css-cascade/all-prop-revert-layer.txt | 5 +- .../parsing/touch-action-computed.txt | 11 +++ .../parsing/touch-action-invalid.txt | 8 +++ .../parsing/touch-action-valid.txt | 11 +++ .../pointerevent_touch-action-illegal.txt | 8 +++ .../parsing/touch-action-computed.html | 24 +++++++ .../parsing/touch-action-invalid.html | 18 +++++ .../parsing/touch-action-valid.html | 23 +++++++ .../pointerevent_touch-action-illegal.html | 67 +++++++++++++++++++ 21 files changed, 364 insertions(+), 19 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/pointerevents/parsing/touch-action-computed.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/pointerevents/parsing/touch-action-invalid.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/pointerevents/parsing/touch-action-valid.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/pointerevents/pointerevent_touch-action-illegal.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/pointerevents/parsing/touch-action-computed.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/pointerevents/parsing/touch-action-invalid.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/pointerevents/parsing/touch-action-valid.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/pointerevents/pointerevent_touch-action-illegal.html diff --git a/Libraries/LibWeb/CSS/ComputedProperties.cpp b/Libraries/LibWeb/CSS/ComputedProperties.cpp index 06a74741450..c5f1acea4e0 100644 --- a/Libraries/LibWeb/CSS/ComputedProperties.cpp +++ b/Libraries/LibWeb/CSS/ComputedProperties.cpp @@ -1580,6 +1580,54 @@ Isolation ComputedProperties::isolation() const return keyword_to_isolation(value.to_keyword()).release_value(); } +TouchActionData ComputedProperties::touch_action() const +{ + auto const& touch_action = property(PropertyID::TouchAction); + if (touch_action.is_keyword()) { + switch (touch_action.to_keyword()) { + case Keyword::Auto: + return TouchActionData {}; + case Keyword::None: + return TouchActionData::none(); + case Keyword::Manipulation: + return TouchActionData { .allow_other = false }; + default: + VERIFY_NOT_REACHED(); + } + } + if (touch_action.is_value_list()) { + TouchActionData touch_action_data = TouchActionData::none(); + for (auto const& value : touch_action.as_value_list().values()) { + switch (value->as_keyword().keyword()) { + case Keyword::PanX: + touch_action_data.allow_right = true; + touch_action_data.allow_left = true; + break; + case Keyword::PanLeft: + touch_action_data.allow_left = true; + break; + case Keyword::PanRight: + touch_action_data.allow_right = true; + break; + case Keyword::PanY: + touch_action_data.allow_up = true; + touch_action_data.allow_down = true; + break; + case Keyword::PanUp: + touch_action_data.allow_up = true; + break; + case Keyword::PanDown: + touch_action_data.allow_down = true; + break; + default: + VERIFY_NOT_REACHED(); + } + } + return touch_action_data; + } + return TouchActionData {}; +} + Containment ComputedProperties::contain() const { Containment containment = {}; diff --git a/Libraries/LibWeb/CSS/ComputedProperties.h b/Libraries/LibWeb/CSS/ComputedProperties.h index a232b39d36a..cbc65e842ef 100644 --- a/Libraries/LibWeb/CSS/ComputedProperties.h +++ b/Libraries/LibWeb/CSS/ComputedProperties.h @@ -166,6 +166,7 @@ public: WritingMode writing_mode() const; UserSelect user_select() const; Isolation isolation() const; + TouchActionData touch_action() const; Containment contain() const; MixBlendMode mix_blend_mode() const; Optional view_transition_name() const; diff --git a/Libraries/LibWeb/CSS/ComputedValues.h b/Libraries/LibWeb/CSS/ComputedValues.h index d006458f700..49913acba2e 100644 --- a/Libraries/LibWeb/CSS/ComputedValues.h +++ b/Libraries/LibWeb/CSS/ComputedValues.h @@ -309,6 +309,29 @@ public: bool operator==(BorderData const&) const = default; }; +struct TouchActionData { + bool allow_left : 1 { true }; + bool allow_right : 1 { true }; + bool allow_up : 1 { true }; + bool allow_down : 1 { true }; + bool allow_pinch_zoom : 1 { true }; + + // Other touch interactions which aren't pan or pinch to zoom. E.g.: Double tap to zoom. + bool allow_other : 1 { true }; + + static TouchActionData none() + { + return TouchActionData { + .allow_left = false, + .allow_right = false, + .allow_up = false, + .allow_down = false, + .allow_pinch_zoom = false, + .allow_other = false, + }; + } +}; + struct TransformOrigin { CSS::LengthPercentage x { Percentage(50) }; CSS::LengthPercentage y { Percentage(50) }; @@ -456,6 +479,7 @@ public: CSS::Containment const& contain() const { return m_noninherited.contain; } CSS::MixBlendMode mix_blend_mode() const { return m_noninherited.mix_blend_mode; } Optional view_transition_name() const { return m_noninherited.view_transition_name; } + TouchActionData touch_action() const { return m_noninherited.touch_action; } CSS::LengthBox const& inset() const { return m_noninherited.inset; } const CSS::LengthBox& margin() const { return m_noninherited.margin; } @@ -713,6 +737,7 @@ protected: CSS::Containment contain { InitialValues::contain() }; CSS::MixBlendMode mix_blend_mode { InitialValues::mix_blend_mode() }; Optional view_transition_name; + TouchActionData touch_action; Optional rotate; Optional translate; @@ -891,6 +916,7 @@ public: void set_contain(CSS::Containment value) { m_noninherited.contain = move(value); } void set_mix_blend_mode(CSS::MixBlendMode value) { m_noninherited.mix_blend_mode = value; } void set_view_transition_name(Optional value) { m_noninherited.view_transition_name = value; } + void set_touch_action(TouchActionData value) { m_noninherited.touch_action = value; } void set_fill(SVGPaint value) { m_inherited.fill = move(value); } void set_stroke(SVGPaint value) { m_inherited.stroke = move(value); } diff --git a/Libraries/LibWeb/CSS/Enums.json b/Libraries/LibWeb/CSS/Enums.json index 9c74fc26c3c..49c8c8f7192 100644 --- a/Libraries/LibWeb/CSS/Enums.json +++ b/Libraries/LibWeb/CSS/Enums.json @@ -605,6 +605,17 @@ "none", "uppercase" ], + "touch-action": [ + "auto", + "manipulation", + "none", + "pan-down", + "pan-left", + "pan-right", + "pan-up", + "pan-x", + "pan-y" + ], "transform-box": [ "content-box", "border-box", diff --git a/Libraries/LibWeb/CSS/Keywords.json b/Libraries/LibWeb/CSS/Keywords.json index 4be4ed52c6d..ab755575894 100644 --- a/Libraries/LibWeb/CSS/Keywords.json +++ b/Libraries/LibWeb/CSS/Keywords.json @@ -279,6 +279,7 @@ "ltr", "luminance", "luminosity", + "manipulation", "mark", "marktext", "match-parent", @@ -344,6 +345,12 @@ "padding-box", "paged", "paint", + "pan-down", + "pan-left", + "pan-right", + "pan-up", + "pan-x", + "pan-y", "paused", "petite-caps", "pi", diff --git a/Libraries/LibWeb/CSS/Parser/Parser.h b/Libraries/LibWeb/CSS/Parser/Parser.h index 34b5b0a5600..03710565d3a 100644 --- a/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Libraries/LibWeb/CSS/Parser/Parser.h @@ -434,6 +434,7 @@ private: RefPtr parse_grid_template_areas_value(TokenStream&); RefPtr parse_grid_area_shorthand_value(TokenStream&); RefPtr parse_grid_shorthand_value(TokenStream&); + RefPtr parse_touch_action_value(TokenStream&); RefPtr parse_list_of_time_values(PropertyID, TokenStream&); diff --git a/Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp b/Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp index 2d8ed9cc640..062c17fd66a 100644 --- a/Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp +++ b/Libraries/LibWeb/CSS/Parser/PropertyParsing.cpp @@ -664,6 +664,10 @@ Parser::ParseErrorOr> Parser::parse_css_value if (auto parsed_value = parse_shadow_value(tokens, AllowInsetKeyword::No); parsed_value && !tokens.has_next_token()) return parsed_value.release_nonnull(); return ParseError::SyntaxError; + case PropertyID::TouchAction: + if (auto parsed_value = parse_touch_action_value(tokens); parsed_value && !tokens.has_next_token()) + return parsed_value.release_nonnull(); + return ParseError::SyntaxError; case PropertyID::Transform: if (auto parsed_value = parse_transform_value(tokens); parsed_value && !tokens.has_next_token()) return parsed_value.release_nonnull(); @@ -3528,6 +3532,67 @@ RefPtr Parser::parse_text_decoration_line_value(TokenStream return StyleValueList::create(move(style_values), StyleValueList::Separator::Space); } +// https://www.w3.org/TR/pointerevents/#the-touch-action-css-property +RefPtr Parser::parse_touch_action_value(TokenStream& tokens) +{ + // auto | none | [ [ pan-x | pan-left | pan-right ] || [ pan-y | pan-up | pan-down ] ] | manipulation + + if (auto value = parse_all_as_single_keyword_value(tokens, Keyword::Auto)) + return value; + if (auto value = parse_all_as_single_keyword_value(tokens, Keyword::None)) + return value; + if (auto value = parse_all_as_single_keyword_value(tokens, Keyword::Manipulation)) + return value; + + StyleValueVector parsed_values; + auto transaction = tokens.begin_transaction(); + + // We will verify that we have up to one vertical and one horizontal value + bool has_horizontal = false; + bool has_vertical = false; + + // Were the values specified in y/x order? (we need to store them in canonical x/y order) + bool swap_order = false; + + while (auto parsed_value = parse_css_value_for_property(PropertyID::TouchAction, tokens)) { + switch (parsed_value->as_keyword().keyword()) { + case Keyword::PanX: + case Keyword::PanLeft: + case Keyword::PanRight: + if (has_horizontal) + return {}; + if (has_vertical) + swap_order = true; + has_horizontal = true; + break; + case Keyword::PanY: + case Keyword::PanUp: + case Keyword::PanDown: + if (has_vertical) + return {}; + has_vertical = true; + break; + case Keyword::Auto: + case Keyword::None: + case Keyword::Manipulation: + // Not valid as part of a list + return {}; + default: + VERIFY_NOT_REACHED(); + } + + parsed_values.append(parsed_value.release_nonnull()); + if (!tokens.has_next_token()) + break; + } + + if (swap_order) + swap(parsed_values[0], parsed_values[1]); + + transaction.commit(); + return StyleValueList::create(move(parsed_values), StyleValueList::Separator::Space); +} + // https://www.w3.org/TR/css-transforms-1/#transform-property RefPtr Parser::parse_transform_value(TokenStream& tokens) { diff --git a/Libraries/LibWeb/CSS/Properties.json b/Libraries/LibWeb/CSS/Properties.json index d16b6e369fd..0ecc2403b02 100644 --- a/Libraries/LibWeb/CSS/Properties.json +++ b/Libraries/LibWeb/CSS/Properties.json @@ -2954,6 +2954,15 @@ "unitless-length" ] }, + "touch-action": { + "animation-type": "discrete", + "inherited": false, + "initial": "auto", + "max-values": 2, + "valid-types": [ + "touch-action" + ] + }, "transform": { "animation-type": "custom", "inherited": false, diff --git a/Libraries/LibWeb/Layout/Node.cpp b/Libraries/LibWeb/Layout/Node.cpp index d0c7ddd4b0b..f6853598cb1 100644 --- a/Libraries/LibWeb/Layout/Node.cpp +++ b/Libraries/LibWeb/Layout/Node.cpp @@ -933,6 +933,8 @@ void NodeWithStyle::apply_style(CSS::ComputedProperties const& computed_style) computed_values.set_aspect_ratio({ false, aspect_ratio.as_ratio().ratio() }); } + computed_values.set_touch_action(computed_style.touch_action()); + auto const& math_shift_value = computed_style.property(CSS::PropertyID::MathShift); if (auto math_shift = keyword_to_math_shift(math_shift_value.to_keyword()); math_shift.has_value()) computed_values.set_math_shift(math_shift.value()); diff --git a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-has-indexed-property-getter.txt b/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-has-indexed-property-getter.txt index e0bb738f82d..46d13531da9 100644 --- a/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-has-indexed-property-getter.txt +++ b/Tests/LibWeb/Text/expected/css/CSSStyleDeclaration-has-indexed-property-getter.txt @@ -217,23 +217,24 @@ All properties associated with getComputedStyle(document.body): "214": "text-decoration-thickness", "215": "text-overflow", "216": "top", - "217": "transform", - "218": "transform-box", - "219": "transform-origin", - "220": "transition-behavior", - "221": "transition-delay", - "222": "transition-duration", - "223": "transition-property", - "224": "transition-timing-function", - "225": "translate", - "226": "unicode-bidi", - "227": "user-select", - "228": "vertical-align", - "229": "view-transition-name", - "230": "width", - "231": "x", - "232": "y", - "233": "z-index" + "217": "touch-action", + "218": "transform", + "219": "transform-box", + "220": "transform-origin", + "221": "transition-behavior", + "222": "transition-delay", + "223": "transition-duration", + "224": "transition-property", + "225": "transition-timing-function", + "226": "translate", + "227": "unicode-bidi", + "228": "user-select", + "229": "vertical-align", + "230": "view-transition-name", + "231": "width", + "232": "x", + "233": "y", + "234": "z-index" } All properties associated with document.body.style by default: {} diff --git a/Tests/LibWeb/Text/expected/css/CSSStyleProperties-all-supported-properties-and-default-values.txt b/Tests/LibWeb/Text/expected/css/CSSStyleProperties-all-supported-properties-and-default-values.txt index edf19737e8c..2c946199619 100644 --- a/Tests/LibWeb/Text/expected/css/CSSStyleProperties-all-supported-properties-and-default-values.txt +++ b/Tests/LibWeb/Text/expected/css/CSSStyleProperties-all-supported-properties-and-default-values.txt @@ -616,6 +616,8 @@ All supported properties and their default values exposed from CSSStylePropertie 'textTransform': 'none' 'text-transform': 'none' 'top': 'auto' +'touchAction': 'auto' +'touch-action': 'auto' 'transform': 'none' 'transformBox': 'view-box' 'transform-box': 'view-box' diff --git a/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt b/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt index e22f652e47d..0c34bcceb67 100644 --- a/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt +++ b/Tests/LibWeb/Text/expected/css/getComputedStyle-print-all.txt @@ -215,6 +215,7 @@ text-decoration-style: solid text-decoration-thickness: auto text-overflow: clip top: auto +touch-action: auto transform: none transform-box: view-box transform-origin: 50% 50% diff --git a/Tests/LibWeb/Text/expected/wpt-import/css/css-cascade/all-prop-revert-layer.txt b/Tests/LibWeb/Text/expected/wpt-import/css/css-cascade/all-prop-revert-layer.txt index 8c358e7e3cc..27f5d7dfea7 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/css/css-cascade/all-prop-revert-layer.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/css/css-cascade/all-prop-revert-layer.txt @@ -1,8 +1,8 @@ Harness status: OK -Found 198 tests +Found 199 tests -188 Pass +189 Pass 10 Fail Pass accent-color Pass border-collapse @@ -186,6 +186,7 @@ Pass text-decoration-style Pass text-decoration-thickness Pass text-overflow Pass top +Pass touch-action Fail transform Pass transform-box Pass transition-behavior diff --git a/Tests/LibWeb/Text/expected/wpt-import/pointerevents/parsing/touch-action-computed.txt b/Tests/LibWeb/Text/expected/wpt-import/pointerevents/parsing/touch-action-computed.txt new file mode 100644 index 00000000000..2d325176d1b --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/pointerevents/parsing/touch-action-computed.txt @@ -0,0 +1,11 @@ +Harness status: OK + +Found 6 tests + +6 Pass +Pass Property touch-action value 'auto' +Pass Property touch-action value 'none' +Pass Property touch-action value 'manipulation' +Pass Property touch-action value 'pan-x' +Pass Property touch-action value 'pan-y' +Pass Property touch-action value 'pan-x pan-y' \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/pointerevents/parsing/touch-action-invalid.txt b/Tests/LibWeb/Text/expected/wpt-import/pointerevents/parsing/touch-action-invalid.txt new file mode 100644 index 00000000000..1f58f634bae --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/pointerevents/parsing/touch-action-invalid.txt @@ -0,0 +1,8 @@ +Harness status: OK + +Found 3 tests + +3 Pass +Pass e.style['touch-action'] = "auto none" should not set the property value +Pass e.style['touch-action'] = "manipulation pan-x" should not set the property value +Pass e.style['touch-action'] = "pan-y pan-x pan-y" should not set the property value \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/pointerevents/parsing/touch-action-valid.txt b/Tests/LibWeb/Text/expected/wpt-import/pointerevents/parsing/touch-action-valid.txt new file mode 100644 index 00000000000..76c25023f51 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/pointerevents/parsing/touch-action-valid.txt @@ -0,0 +1,11 @@ +Harness status: OK + +Found 6 tests + +6 Pass +Pass e.style['touch-action'] = "auto" should set the property value +Pass e.style['touch-action'] = "none" should set the property value +Pass e.style['touch-action'] = "manipulation" should set the property value +Pass e.style['touch-action'] = "pan-x" should set the property value +Pass e.style['touch-action'] = "pan-y" should set the property value +Pass e.style['touch-action'] = "pan-y pan-x" should set the property value \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/pointerevents/pointerevent_touch-action-illegal.txt b/Tests/LibWeb/Text/expected/wpt-import/pointerevents/pointerevent_touch-action-illegal.txt new file mode 100644 index 00000000000..169a818aedc --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/pointerevents/pointerevent_touch-action-illegal.txt @@ -0,0 +1,8 @@ +Harness status: OK + +Found 3 tests + +3 Pass +Pass 'pan-x none' is corrected properly +Pass 'pan-y none' is corrected properly +Pass 'auto none' is corrected properly \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/pointerevents/parsing/touch-action-computed.html b/Tests/LibWeb/Text/input/wpt-import/pointerevents/parsing/touch-action-computed.html new file mode 100644 index 00000000000..849ca8aa5ed --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/pointerevents/parsing/touch-action-computed.html @@ -0,0 +1,24 @@ + + + + +Pointer Events: getComputedStyle().touchAction + + + + + + + +
+ + + diff --git a/Tests/LibWeb/Text/input/wpt-import/pointerevents/parsing/touch-action-invalid.html b/Tests/LibWeb/Text/input/wpt-import/pointerevents/parsing/touch-action-invalid.html new file mode 100644 index 00000000000..f84efbacda3 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/pointerevents/parsing/touch-action-invalid.html @@ -0,0 +1,18 @@ + + + +Pointer Events: parsing touch-action with invalid values + + + + + + + + + + diff --git a/Tests/LibWeb/Text/input/wpt-import/pointerevents/parsing/touch-action-valid.html b/Tests/LibWeb/Text/input/wpt-import/pointerevents/parsing/touch-action-valid.html new file mode 100644 index 00000000000..3f8507911d5 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/pointerevents/parsing/touch-action-valid.html @@ -0,0 +1,23 @@ + + + +Pointer Events: parsing touch-action with valid values + + + + + + + + + + diff --git a/Tests/LibWeb/Text/input/wpt-import/pointerevents/pointerevent_touch-action-illegal.html b/Tests/LibWeb/Text/input/wpt-import/pointerevents/pointerevent_touch-action-illegal.html new file mode 100644 index 00000000000..280539116c7 --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/pointerevents/pointerevent_touch-action-illegal.html @@ -0,0 +1,67 @@ + + + + touch-action: illegal + + + + + + + + +

Pointer Events touch-action attribute support

+

Test Description: Test will automatically check behaviour of following combinations: 'pan-x none', 'pan-y none', 'auto none'

+
+
+
+ +

touch-action: none

+
+

The following pointer types were detected: .

+
+
+ + \ No newline at end of file