Prechádzať zdrojové kódy

LibWeb: Implement <input type=checkbox switch> experimentally

In conformance with the requirements of the spec PR at
https://github.com/whatwg/html/pull/9546, this change adds support for
the “switch” attribute for type=checkbox “input” elements — which is
shipping in Safari (since Safari 17.4). This change also implements
support for exposing it to AT users with role=switch.
sideshowbarker 7 mesiacov pred
rodič
commit
583ca6af89
16 zmenil súbory, kde vykonal 268 pridanie a 3 odobranie
  1. 35 0
      Libraries/LibWeb/CSS/Default.css
  2. 3 1
      Libraries/LibWeb/CSS/SelectorEngine.cpp
  3. 2 0
      Libraries/LibWeb/HTML/AttributeNames.cpp
  4. 1 0
      Libraries/LibWeb/HTML/AttributeNames.h
  5. 4 1
      Libraries/LibWeb/HTML/HTMLInputElement.cpp
  6. 2 0
      Libraries/LibWeb/HTML/HTMLInputElement.idl
  7. 1 1
      Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp
  8. 11 0
      Tests/LibWeb/Text/expected/wpt-import/html-aam/roles-dynamic-switch.tentative.window.txt
  9. 7 0
      Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.txt
  10. 11 0
      Tests/LibWeb/Text/expected/wpt-import/html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.txt
  11. 10 0
      Tests/LibWeb/Text/input/wpt-import/html-aam/roles-dynamic-switch.tentative.window.html
  12. 71 0
      Tests/LibWeb/Text/input/wpt-import/html-aam/roles-dynamic-switch.tentative.window.js
  13. 8 0
      Tests/LibWeb/Text/input/wpt-import/html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.html
  14. 19 0
      Tests/LibWeb/Text/input/wpt-import/html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.js
  15. 8 0
      Tests/LibWeb/Text/input/wpt-import/html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.html
  16. 75 0
      Tests/LibWeb/Text/input/wpt-import/html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.js

+ 35 - 0
Libraries/LibWeb/CSS/Default.css

@@ -857,3 +857,38 @@ progress {
         filter: invert(100%);
     }
 }
+
+/* https://github.com/whatwg/html/pull/9546
+ */
+input[type=checkbox][switch] {
+	appearance: none;
+	height: 1em;
+	width: 1.8em;
+	vertical-align: middle;
+	border-radius: 1em;
+	position: relative;
+	overflow: hidden;
+	border-color: transparent;
+	background-color: ButtonFace;
+}
+
+input[type=checkbox][switch]::before {
+	content: '';
+	position: absolute;
+	height: 0;
+	width: 0;
+	border: .46em solid Field;
+	border-radius: 100%;
+	top: 0;
+	bottom: 0;
+	left: 0;
+	margin: auto;
+}
+
+input[type=checkbox][switch]:checked::before {
+	left: calc(100% - .87em);
+}
+
+input[type=checkbox][switch]:checked {
+	background-color: AccentColor;
+}

+ 3 - 1
Libraries/LibWeb/CSS/SelectorEngine.cpp

@@ -201,7 +201,9 @@ static inline bool matches_indeterminate_pseudo_class(DOM::Element const& elemen
         auto const& input_element = static_cast<HTML::HTMLInputElement const&>(element);
         switch (input_element.type_state()) {
         case HTML::HTMLInputElement::TypeAttributeState::Checkbox:
-            return input_element.indeterminate();
+            // https://whatpr.org/html-attr-input-switch/9546/semantics-other.html#selector-indeterminate
+            // input elements whose type attribute is in the Checkbox state, whose switch attribute is not set
+            return input_element.indeterminate() && !element.has_attribute(HTML::AttributeNames::switch_);
         default:
             return false;
         }

+ 2 - 0
Libraries/LibWeb/HTML/AttributeNames.cpp

@@ -28,6 +28,7 @@ void initialize_strings()
     for_ = "for"_fly_string;
     default_ = "default"_fly_string;
     char_ = "char"_fly_string;
+    switch_ = "switch"_fly_string;
 
     // NOTE: Special cases for attributes with dashes in them.
     accept_charset = "accept-charset"_fly_string;
@@ -81,6 +82,7 @@ bool is_boolean_attribute(FlyString const& attribute)
         || attribute.equals_ignoring_ascii_case(AttributeNames::reversed)
         || attribute.equals_ignoring_ascii_case(AttributeNames::seeking)
         || attribute.equals_ignoring_ascii_case(AttributeNames::selected)
+        || attribute.equals_ignoring_ascii_case(AttributeNames::switch_)
         || attribute.equals_ignoring_ascii_case(AttributeNames::truespeed)
         || attribute.equals_ignoring_ascii_case(AttributeNames::willvalidate);
 }

+ 1 - 0
Libraries/LibWeb/HTML/AttributeNames.h

@@ -278,6 +278,7 @@ namespace AttributeNames {
     __ENUMERATE_HTML_ATTRIBUTE(step)                       \
     __ENUMERATE_HTML_ATTRIBUTE(style)                      \
     __ENUMERATE_HTML_ATTRIBUTE(summary)                    \
+    __ENUMERATE_HTML_ATTRIBUTE(switch_)                    \
     __ENUMERATE_HTML_ATTRIBUTE(tabindex)                   \
     __ENUMERATE_HTML_ATTRIBUTE(target)                     \
     __ENUMERATE_HTML_ATTRIBUTE(text)                       \

+ 4 - 1
Libraries/LibWeb/HTML/HTMLInputElement.cpp

@@ -2343,13 +2343,16 @@ void HTMLInputElement::set_custom_validity(String const& error)
 
 Optional<ARIA::Role> HTMLInputElement::default_role() const
 {
+    // http://wpt.live/html-aam/roles-dynamic-switch.tentative.window.html "Disconnected <input type=checkbox switch>"
+    if (!is_connected())
+        return {};
     // https://www.w3.org/TR/html-aria/#el-input-button
     if (type_state() == TypeAttributeState::Button)
         return ARIA::Role::button;
     // https://www.w3.org/TR/html-aria/#el-input-checkbox
     if (type_state() == TypeAttributeState::Checkbox) {
         // https://github.com/w3c/html-aam/issues/496
-        if (has_attribute("switch"_string))
+        if (has_attribute(HTML::AttributeNames::switch_))
             return ARIA::Role::switch_;
         return ARIA::Role::checkbox;
     }

+ 2 - 0
Libraries/LibWeb/HTML/HTMLInputElement.idl

@@ -38,6 +38,8 @@ interface HTMLInputElement : HTMLElement {
     [CEReactions] attribute unsigned long size;
     [CEReactions, Reflect, URL] attribute USVString src;
     [CEReactions, Reflect] attribute DOMString step;
+    // https://whatpr.org/html-attr-input-switch/9546/input.html#the-input-element:dom-input-switch
+    [CEReactions, Reflect] attribute boolean switch;
     [CEReactions] attribute DOMString type;
     [CEReactions, Reflect=value] attribute DOMString defaultValue;
     [CEReactions, LegacyNullToEmptyString] attribute DOMString value;

+ 1 - 1
Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp

@@ -288,7 +288,7 @@ CppType idl_type_name_to_cpp_type(Type const& type, Interface const& interface)
 
 static ByteString make_input_acceptable_cpp(ByteString const& input)
 {
-    if (input.is_one_of("class", "template", "for", "default", "char", "namespace", "delete", "inline", "register")) {
+    if (input.is_one_of("class", "template", "for", "default", "char", "namespace", "delete", "inline", "register", "switch")) {
         StringBuilder builder;
         builder.append(input);
         builder.append('_');

+ 11 - 0
Tests/LibWeb/Text/expected/wpt-import/html-aam/roles-dynamic-switch.tentative.window.txt

@@ -0,0 +1,11 @@
+Harness status: OK
+
+Found 6 tests
+
+6 Pass
+Pass	Disconnected <input type=checkbox switch>
+Pass	Connected <input type=checkbox switch>
+Pass	Connected <input type=checkbox switch>: adding switch attribute
+Pass	Connected <input type=checkbox switch>: removing switch attribute
+Pass	Connected <input type=checkbox switch>: removing type attribute
+Pass	Connected <input type=checkbox switch>: adding type attribute

+ 7 - 0
Tests/LibWeb/Text/expected/wpt-import/html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.txt

@@ -0,0 +1,7 @@
+Harness status: OK
+
+Found 2 tests
+
+2 Pass
+Pass	switch IDL attribute, setter
+Pass	switch IDL attribute, getter

+ 11 - 0
Tests/LibWeb/Text/expected/wpt-import/html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.txt

@@ -0,0 +1,11 @@
+Harness status: OK
+
+Found 6 tests
+
+6 Pass
+Pass	Switch control does not match :indeterminate
+Pass	Checkbox that is no longer a switch control does match :indeterminate
+Pass	Checkbox that becomes a switch control does not match :indeterminate
+Pass	Parent of a checkbox that becomes a switch control does not match :has(:indeterminate)
+Pass	Parent of a switch control that becomes a checkbox continues to match :has(:checked)
+Pass	A switch control that becomes a checkbox in a roundabout way

+ 10 - 0
Tests/LibWeb/Text/input/wpt-import/html-aam/roles-dynamic-switch.tentative.window.html

@@ -0,0 +1,10 @@
+<!doctype html>
+<meta charset=utf-8>
+
+<script src="../resources/testharness.js"></script>
+<script src="../resources/testharnessreport.js"></script>
+<script src="../resources/testdriver.js"></script>
+<script src="../resources/testdriver-vendor.js"></script>
+<script src="../resources/testdriver-actions.js"></script>
+<div id=log></div>
+<script src="../html-aam/roles-dynamic-switch.tentative.window.js"></script>

+ 71 - 0
Tests/LibWeb/Text/input/wpt-import/html-aam/roles-dynamic-switch.tentative.window.js

@@ -0,0 +1,71 @@
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+// META: script=/resources/testdriver-actions.js
+
+promise_test(async () => {
+  const control = document.createElement("input");
+  control.type = "checkbox";
+  control.switch = true;
+  const role = await test_driver.get_computed_role(control);
+  assert_equals(role, "");
+}, `Disconnected <input type=checkbox switch>`);
+
+promise_test(async t => {
+  const control = document.createElement("input");
+  t.add_cleanup(() => control.remove());
+  control.type = "checkbox";
+  control.switch = true;
+  document.body.append(control);
+  const role = await test_driver.get_computed_role(control);
+  assert_equals(role, "switch");
+}, `Connected <input type=checkbox switch>`);
+
+promise_test(async t => {
+  const control = document.createElement("input");
+  t.add_cleanup(() => control.remove());
+  control.type = "checkbox";
+  document.body.append(control);
+  let role = await test_driver.get_computed_role(control);
+  assert_equals(role, "checkbox");
+  control.switch = true;
+  role = await test_driver.get_computed_role(control);
+  assert_equals(role, "switch");
+}, `Connected <input type=checkbox switch>: adding switch attribute`);
+
+promise_test(async t => {
+  const control = document.createElement("input");
+  t.add_cleanup(() => control.remove());
+  control.type = "checkbox";
+  control.switch = true;
+  document.body.append(control);
+  let role = await test_driver.get_computed_role(control);
+  assert_equals(role, "switch");
+  control.switch = false;
+  role = await test_driver.get_computed_role(control);
+  assert_equals(role, "checkbox");
+}, `Connected <input type=checkbox switch>: removing switch attribute`);
+
+promise_test(async t => {
+  const control = document.createElement("input");
+  t.add_cleanup(() => control.remove());
+  control.type = "checkbox";
+  document.body.append(control);
+  control.switch = true;
+  let role = await test_driver.get_computed_role(control);
+  assert_equals(role, "switch");
+  control.removeAttribute("type");
+  role = await test_driver.get_computed_role(control);
+  assert_equals(role, "textbox");
+}, `Connected <input type=checkbox switch>: removing type attribute`);
+
+promise_test(async t => {
+  const control = document.createElement("input");
+  t.add_cleanup(() => control.remove());
+  control.switch = true;
+  document.body.append(control);
+  let role = await test_driver.get_computed_role(control);
+  assert_equals(role, "textbox");
+  control.type = "checkbox";
+  role = await test_driver.get_computed_role(control);
+  assert_equals(role, "switch");
+}, `Connected <input type=checkbox switch>: adding type attribute`);

+ 8 - 0
Tests/LibWeb/Text/input/wpt-import/html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.html

@@ -0,0 +1,8 @@
+<!doctype html>
+<meta charset=utf-8>
+
+<script src="../../../../resources/testharness.js"></script>
+<script src="../../../../resources/testharnessreport.js"></script>
+
+<div id=log></div>
+<script src="../../../../html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.js"></script>

+ 19 - 0
Tests/LibWeb/Text/input/wpt-import/html/semantics/forms/the-input-element/input-type-checkbox-switch.tentative.window.js

@@ -0,0 +1,19 @@
+test(t => {
+  const input = document.createElement("input");
+  input.switch = true;
+
+  assert_true(input.hasAttribute("switch"));
+  assert_equals(input.getAttribute("switch"), "");
+  assert_equals(input.type, "text");
+}, "switch IDL attribute, setter");
+
+test(t => {
+  const container = document.createElement("div");
+  container.innerHTML = "<input type=checkbox switch>";
+  const input = container.firstChild;
+
+  assert_true(input.hasAttribute("switch"));
+  assert_equals(input.getAttribute("switch"), "");
+  assert_equals(input.type, "checkbox");
+  assert_true(input.switch);
+}, "switch IDL attribute, getter");

+ 8 - 0
Tests/LibWeb/Text/input/wpt-import/html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.html

@@ -0,0 +1,8 @@
+<!doctype html>
+<meta charset=utf-8>
+
+<script src="../../../../resources/testharness.js"></script>
+<script src="../../../../resources/testharnessreport.js"></script>
+
+<div id=log></div>
+<script src="../../../../html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.js"></script>

+ 75 - 0
Tests/LibWeb/Text/input/wpt-import/html/semantics/selectors/pseudo-classes/input-checkbox-switch.tentative.window.js

@@ -0,0 +1,75 @@
+test(t => {
+  const input = document.body.appendChild(document.createElement("input"));
+  t.add_cleanup(() => input.remove());
+  input.type = "checkbox";
+  input.switch = true;
+  input.indeterminate = true;
+
+  assert_false(input.matches(":indeterminate"));
+}, "Switch control does not match :indeterminate");
+
+test(t => {
+  const input = document.body.appendChild(document.createElement("input"));
+  t.add_cleanup(() => input.remove());
+  input.type = "checkbox";
+  input.switch = true;
+  input.indeterminate = true;
+
+  assert_false(input.matches(":indeterminate"));
+
+  input.switch = false;
+  assert_true(input.matches(":indeterminate"));
+}, "Checkbox that is no longer a switch control does match :indeterminate");
+
+test(t => {
+  const input = document.body.appendChild(document.createElement("input"));
+  t.add_cleanup(() => input.remove());
+  input.type = "checkbox";
+  input.indeterminate = true;
+
+  assert_true(input.matches(":indeterminate"));
+
+  input.setAttribute("switch", "blah");
+  assert_false(input.matches(":indeterminate"));
+}, "Checkbox that becomes a switch control does not match :indeterminate");
+
+test(t => {
+  const input = document.body.appendChild(document.createElement("input"));
+  t.add_cleanup(() => input.remove());
+  input.type = "checkbox";
+  input.indeterminate = true;
+
+  assert_true(document.body.matches(":has(:indeterminate)"));
+
+  input.switch = true;
+  assert_false(document.body.matches(":has(:indeterminate)"));
+}, "Parent of a checkbox that becomes a switch control does not match :has(:indeterminate)");
+
+test(t => {
+  const input = document.body.appendChild(document.createElement("input"));
+  t.add_cleanup(() => input.remove());
+  input.type = "checkbox";
+  input.switch = true
+  input.checked = true;
+
+  assert_true(document.body.matches(":has(:checked)"));
+
+  input.switch = false;
+  assert_true(document.body.matches(":has(:checked)"));
+
+  input.checked = false;
+  assert_false(document.body.matches(":has(:checked)"));
+}, "Parent of a switch control that becomes a checkbox continues to match :has(:checked)");
+
+test(t => {
+  const input = document.body.appendChild(document.createElement("input"));
+  t.add_cleanup(() => input.remove());
+  input.type = "checkbox";
+  input.switch = true;
+  input.indeterminate = true;
+  assert_false(input.matches(":indeterminate"));
+  input.type = "text";
+  input.removeAttribute("switch");
+  input.type = "checkbox";
+  assert_true(input.matches(":indeterminate"));
+}, "A switch control that becomes a checkbox in a roundabout way");