Bug 1386530. Fix handling of type changes that affect validity state to properly notify state changes. r=jessica
authorBoris Zbarsky <bzbarsky@mit.edu>
Fri, 22 Sep 2017 15:47:16 -0400
changeset 382578 db561b3fd2655dffd04aaa0995f3cb06c3f36d09
parent 382577 3870c3b2cb5418cdffa1fd4cb612f9b36265c8cd
child 382579 ddff620ec86abbb80d2e3e46d105e30938729481
push id32562
push userarchaeopteryx@coole-files.de
push dateSat, 23 Sep 2017 09:38:29 +0000
treeherdermozilla-central@8db0c4ecd94c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjessica
bugs1386530
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1386530. Fix handling of type changes that affect validity state to properly notify state changes. r=jessica MozReview-Commit-ID: Khhzi1HyCpt
dom/html/HTMLInputElement.cpp
dom/html/HTMLInputElement.h
layout/reftests/forms/input/reftest.list
layout/reftests/forms/input/selector-read-write-type-change-001-ref.html
layout/reftests/forms/input/selector-read-write-type-change-001.html
layout/reftests/forms/input/selector-read-write-type-change-002-ref.html
layout/reftests/forms/input/selector-read-write-type-change-002.html
testing/web-platform/meta/MANIFEST.json
testing/web-platform/meta/css/selectors4/selector-placeholder-shown-type-change-001.html.ini
testing/web-platform/meta/css/selectors4/selector-read-write-type-change-002.html.ini
testing/web-platform/tests/css/selectors4/selector-placeholder-shown-type-change-001-ref.html
testing/web-platform/tests/css/selectors4/selector-placeholder-shown-type-change-001.html
testing/web-platform/tests/css/selectors4/selector-placeholder-shown-type-change-002-ref.html
testing/web-platform/tests/css/selectors4/selector-placeholder-shown-type-change-002.html
testing/web-platform/tests/css/selectors4/selector-placeholder-shown-type-change-003-ref.html
testing/web-platform/tests/css/selectors4/selector-placeholder-shown-type-change-003.html
testing/web-platform/tests/css/selectors4/selector-read-write-type-change-001-ref.html
testing/web-platform/tests/css/selectors4/selector-read-write-type-change-001.html
testing/web-platform/tests/css/selectors4/selector-read-write-type-change-002-ref.html
testing/web-platform/tests/css/selectors4/selector-read-write-type-change-002.html
testing/web-platform/tests/css/selectors4/selector-required-type-change-001-ref.html
testing/web-platform/tests/css/selectors4/selector-required-type-change-001.html
testing/web-platform/tests/css/selectors4/selector-required-type-change-002-ref.html
testing/web-platform/tests/css/selectors4/selector-required-type-change-002.html
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -3345,17 +3345,19 @@ HTMLInputElement::SetCheckedInternal(boo
   // Notify the frame
   if (mType == NS_FORM_INPUT_CHECKBOX || mType == NS_FORM_INPUT_RADIO) {
     nsIFrame* frame = GetPrimaryFrame();
     if (frame) {
       frame->InvalidateFrameSubtree();
     }
   }
 
-  UpdateAllValidityStates(aNotify);
+  // No need to update element state, since we're about to call
+  // UpdateState anyway.
+  UpdateAllValidityStatesButNotElementState();
 
   // Notify the document that the CSS :checked pseudoclass for this element
   // has changed state.
   UpdateState(aNotify);
 
   // Notify all radios in the group that value has changed, this is to let
   // radios to have the chance to update its states, e.g., :indeterminate.
   if (mType == NS_FORM_INPUT_RADIO) {
@@ -5034,18 +5036,19 @@ HTMLInputElement::HandleTypeChange(uint8
   } else if (aNotify) {
     RemoveStates(REQUIRED_STATES);
   } else {
     RemoveStatesSilently(REQUIRED_STATES);
   }
 
   UpdateHasRange();
 
-  // Do not notify, it will be done after if needed.
-  UpdateAllValidityStates(false);
+  // Update validity states, but not element state.  We'll update
+  // element state later, as part of this attribute change.
+  UpdateAllValidityStatesButNotElementState();
 
   UpdateApzAwareFlag();
 
   UpdateBarredFromConstraintValidation();
 
   if (oldType == NS_FORM_INPUT_IMAGE) {
     // We're no longer an image input.  Cancel our image requests, if we have
     // any.
@@ -7352,29 +7355,35 @@ HTMLInputElement::UpdateBadInputValidity
 {
   SetValidityState(VALIDITY_STATE_BAD_INPUT, HasBadInput());
 }
 
 void
 HTMLInputElement::UpdateAllValidityStates(bool aNotify)
 {
   bool validBefore = IsValid();
+  UpdateAllValidityStatesButNotElementState();
+
+  if (validBefore != IsValid()) {
+    UpdateState(aNotify);
+  }
+}
+
+void
+HTMLInputElement::UpdateAllValidityStatesButNotElementState()
+{
   UpdateTooLongValidityState();
   UpdateTooShortValidityState();
   UpdateValueMissingValidityState();
   UpdateTypeMismatchValidityState();
   UpdatePatternMismatchValidityState();
   UpdateRangeOverflowValidityState();
   UpdateRangeUnderflowValidityState();
   UpdateStepMismatchValidityState();
   UpdateBadInputValidityState();
-
-  if (validBefore != IsValid()) {
-    UpdateState(aNotify);
-  }
 }
 
 void
 HTMLInputElement::UpdateBarredFromConstraintValidation()
 {
   SetBarredFromConstraintValidation(mType == NS_FORM_INPUT_HIDDEN ||
                                     mType == NS_FORM_INPUT_BUTTON ||
                                     mType == NS_FORM_INPUT_RESET ||
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -363,17 +363,24 @@ public:
   void     UpdateTooShortValidityState();
   void     UpdateValueMissingValidityState();
   void     UpdateTypeMismatchValidityState();
   void     UpdatePatternMismatchValidityState();
   void     UpdateRangeOverflowValidityState();
   void     UpdateRangeUnderflowValidityState();
   void     UpdateStepMismatchValidityState();
   void     UpdateBadInputValidityState();
+  // Update all our validity states and then update our element state
+  // as needed.  aNotify controls whether the element state update
+  // needs to notify.
   void     UpdateAllValidityStates(bool aNotify);
+  // Update all our validity states without updating element state.
+  // This should be called instead of UpdateAllValidityStates any time
+  // we're guaranteed that element state will be updated anyway.
+  void     UpdateAllValidityStatesButNotElementState();
   void     UpdateBarredFromConstraintValidation();
   nsresult GetValidationMessage(nsAString& aValidationMessage,
                                 ValidityStateType aType) override;
 
   // Override SetCustomValidity so we update our state properly when it's called
   // via bindings.
   void SetCustomValidity(const nsAString& aError);
 
--- a/layout/reftests/forms/input/reftest.list
+++ b/layout/reftests/forms/input/reftest.list
@@ -7,8 +7,11 @@ include number/reftest.list
 include file/reftest.list
 include radio/reftest.list
 include range/reftest.list
 include text/reftest.list
 include percentage/reftest.list
 include hidden/reftest.list
 include color/reftest.list
 include datetime/reftest.list
+
+== selector-read-write-type-change-001.html selector-read-write-type-change-001-ref.html
+== selector-read-write-type-change-002.html selector-read-write-type-change-002-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/selector-read-write-type-change-001-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      span { color: green; }
+    </style>
+  </head>
+  <body>
+    <input type="button"><span>This should be green</span>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/selector-read-write-type-change-001.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Check for correctly updating :read-write matching on type change</title>
+    <link rel="match" href="selector-read-write-type-change-001-ref.html">
+    <style>
+      span { color: green; }
+      :-moz-read-write + span { color: red }
+    </style>
+    <script>
+      onload = function() {
+        document.querySelector("input").type = "button";
+      }
+    </script>
+  </head>
+  <body>
+    <input required><span>This should be green</span>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/selector-read-write-type-change-002-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      span { color: green; }
+    </style>
+  </head>
+  <body>
+    <input required><span>This should be green</span>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/selector-read-write-type-change-002.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <head>
+    <title>Check for correctly updating :read-write matching on type change</title>
+    <link rel="match" href="selector-read-write-type-change-002-ref.html">
+    <style>
+      span { color: red; }
+      :-moz-read-write + span { color: green }
+    </style>
+    <script>
+      onload = function() {
+        // setTimeout because in some browsers apparently a toplevel restyle
+        // happens right after the load event fires?
+        setTimeout(function() {
+          document.querySelector("input").type = "";
+          document.documentElement.className = "";
+        }, 10);
+      }
+    </script>
+  </head>
+  <body>
+    <input type="hidden" required><span>This should be green</span>
+  </body>
+</html>
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -153602,28 +153602,112 @@
       [
        "/css/selectors4/of-type-selectors-ref.xhtml",
        "=="
       ]
      ],
      {}
     ]
    ],
+   "css/selectors4/selector-placeholder-shown-type-change-001.html": [
+    [
+     "/css/selectors4/selector-placeholder-shown-type-change-001.html",
+     [
+      [
+       "/css/selectors4/selector-placeholder-shown-type-change-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/selectors4/selector-placeholder-shown-type-change-002.html": [
+    [
+     "/css/selectors4/selector-placeholder-shown-type-change-002.html",
+     [
+      [
+       "/css/selectors4/selector-placeholder-shown-type-change-002-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/selectors4/selector-placeholder-shown-type-change-003.html": [
+    [
+     "/css/selectors4/selector-placeholder-shown-type-change-003.html",
+     [
+      [
+       "/css/selectors4/selector-placeholder-shown-type-change-003-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/selectors4/selector-read-write-type-change-001.html": [
+    [
+     "/css/selectors4/selector-read-write-type-change-001.html",
+     [
+      [
+       "/css/selectors4/selector-read-write-type-change-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/selectors4/selector-read-write-type-change-002.html": [
+    [
+     "/css/selectors4/selector-read-write-type-change-002.html",
+     [
+      [
+       "/css/selectors4/selector-read-write-type-change-002-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/selectors4/selector-required.html": [
     [
      "/css/selectors4/selector-required.html",
      [
       [
        "/css/selectors4/selector-required-ref.html",
        "=="
       ]
      ],
      {}
     ]
    ],
+   "css/selectors4/selector-required-type-change-001.html": [
+    [
+     "/css/selectors4/selector-required-type-change-001.html",
+     [
+      [
+       "/css/selectors4/selector-required-type-change-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/selectors4/selector-required-type-change-002.html": [
+    [
+     "/css/selectors4/selector-required-type-change-002.html",
+     [
+      [
+       "/css/selectors4/selector-required-type-change-002-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/selectors4/selectors-dir-selector-ltr-001.html": [
     [
      "/css/selectors4/selectors-dir-selector-ltr-001.html",
      [
       [
        "/css/reference/ref-filled-green-100px-square.xht",
        "=="
       ]
@@ -253229,21 +253313,56 @@
      {}
     ]
    ],
    "css/selectors4/of-type-selectors-ref.xhtml": [
     [
      {}
     ]
    ],
+   "css/selectors4/selector-placeholder-shown-type-change-001-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/selectors4/selector-placeholder-shown-type-change-002-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/selectors4/selector-placeholder-shown-type-change-003-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/selectors4/selector-read-write-type-change-001-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/selectors4/selector-read-write-type-change-002-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/selectors4/selector-required-ref.html": [
     [
      {}
     ]
    ],
+   "css/selectors4/selector-required-type-change-001-ref.html": [
+    [
+     {}
+    ]
+   ],
+   "css/selectors4/selector-required-type-change-002-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/support/1x1-green.png": [
     [
      {}
     ]
    ],
    "css/support/1x1-lime.png": [
     [
      {}
@@ -545749,20 +545868,76 @@
   "css/selectors4/of-type-selectors-ref.xhtml": [
    "59f848418882c75898c422a9600c14ffab64c3d9",
    "support"
   ],
   "css/selectors4/of-type-selectors.xhtml": [
    "607553f41a33ce3630752cdf027c9f904833a19d",
    "reftest"
   ],
+  "css/selectors4/selector-placeholder-shown-type-change-001-ref.html": [
+    "92303d06943581738f58ff5d342ef1336539f66a",
+    "support"
+  ],
+  "css/selectors4/selector-placeholder-shown-type-change-002-ref.html": [
+    "ac3a88a758fd0ccc67077993d59bfb34eadf3931",
+    "support"
+  ],
+  "css/selectors4/selector-placeholder-shown-type-change-003-ref.html": [
+    "ac3a88a758fd0ccc67077993d59bfb34eadf3931",
+    "support"
+  ],
+  "css/selectors4/selector-placeholder-shown-type-change-001.html": [
+    "afb7a260d6f1c7da337a1f20a62778d1d6e302c4",
+    "reftest"
+  ],
+  "css/selectors4/selector-placeholder-shown-type-change-002.html": [
+    "df0cbd1b178d72de9f70d0e603c30858508c2edd",
+    "reftest"
+  ],
+  "css/selectors4/selector-placeholder-shown-type-change-003.html": [
+    "36046107e0f3763e219a2316ab6232785a325257",
+    "reftest"
+  ],
+  "css/selectors4/selector-read-write-type-change-001-ref.html": [
+    "812f07e03f5bbf86feb2d8eefe17aeaa7bd69970",
+    "support"
+  ],
+  "css/selectors4/selector-read-write-type-change-001.html": [
+    "95da0fbd6aafb8dcecb6f39e5cc8572f536e7eb5",
+    "reftest"
+  ],
+  "css/selectors4/selector-read-write-type-change-002-ref.html": [
+    "3579aa13253d99a430ce17ba1acd629a22261097",
+    "support"
+  ],
+  "css/selectors4/selector-read-write-type-change-002.html": [
+    "0bb1111fd76582a433204bce32852fc0a5dc5458",
+    "reftest"
+  ],
   "css/selectors4/selector-required-ref.html": [
    "815bc765614b4c2e3d8f8f6303e6bb2ee0989c23",
    "support"
   ],
+  "css/selectors4/selector-required-type-change-001-ref.html": [
+    "812f07e03f5bbf86feb2d8eefe17aeaa7bd69970",
+    "support"
+  ],
+  "css/selectors4/selector-required-type-change-001.html": [
+    "211b7b71cf1073dd15491b78b0152f9b7a5e9aec",
+    "reftest"
+  ],
+  "css/selectors4/selector-required-type-change-002-ref.html": [
+    "3579aa13253d99a430ce17ba1acd629a22261097",
+    "support"
+  ],
+  "css/selectors4/selector-required-type-change-002.html": [
+    "f27dbc7bd1e2aa0753bcae73fdf2d83f7248118a",
+    "reftest"
+  ],
   "css/selectors4/selector-required.html": [
    "601b8b8426c64717f82831e6258f8fe4188c797c",
    "reftest"
   ],
   "css/selectors4/selectors-dir-selector-ltr-001.html": [
    "3682f8a499ad2a1348f620b33b83944c0dc90788",
    "reftest"
   ],
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/selectors4/selector-placeholder-shown-type-change-001.html.ini
@@ -0,0 +1,4 @@
+[selector-placeholder-shown-type-change-001.html]
+  type: reftest
+  expected: FAIL
+  bug: 1401657
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/css/selectors4/selector-read-write-type-change-002.html.ini
@@ -0,0 +1,4 @@
+[selector-read-write-type-change-002.html]
+  type: reftest
+  expected: FAIL
+  bug: 312971
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/selectors4/selector-placeholder-shown-type-change-001-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      span { color: green; }
+    </style>
+  </head>
+  <body>
+    <input placeholder="text"><span>This should be green</span>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/selectors4/selector-placeholder-shown-type-change-001.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <head>
+    <title>Check for correctly updating :placeholder-shown matching on type change</title>
+    <link rel="match" href="selector-placeholder-shown-type-change-001-ref.html">
+    <style>
+      span { color: red; }
+      :placeholder-shown + span { color: green }
+    </style>
+    <script>
+      onload = function() {
+        // setTimeout because in some browsers apparently a toplevel restyle
+        // happens right after the load event fires?
+        setTimeout(function() {
+          document.querySelector("input").type = "";
+          document.documentElement.className = "";
+        }, 10);
+      }
+    </script>
+  </head>
+  <body>
+    <input type="hidden" placeholder="text"><span>This should be green</span>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/selectors4/selector-placeholder-shown-type-change-002-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      span { color: green; }
+    </style>
+  </head>
+  <body>
+    <span>This should be green</span>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/selectors4/selector-placeholder-shown-type-change-002.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <head>
+    <title>Check for correctly updating :placeholder-shown matching on type change</title>
+    <link rel="match" href="selector-placeholder-shown-type-change-002-ref.html">
+    <style>
+      span { color: green; }
+      :placeholder-shown + span { color: red }
+    </style>
+    <script>
+      onload = function() {
+        // setTimeout because in some browsers apparently a toplevel restyle
+        // happens right after the load event fires?
+        setTimeout(function() {
+          document.querySelector("input").type = "hidden";
+          document.documentElement.className = "";
+        }, 10);
+      }
+    </script>
+  </head>
+  <body>
+    <input placeholder="text"><span>This should be green</span>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/selectors4/selector-placeholder-shown-type-change-003-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      span { color: green; }
+    </style>
+  </head>
+  <body>
+    <span>This should be green</span>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/selectors4/selector-placeholder-shown-type-change-003.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <head>
+    <title>Check for correctly updating :placeholder-shown matching on type change</title>
+    <link rel="match" href="selector-placeholder-shown-type-change-003-ref.html">
+    <style>
+      span { color: green; }
+      :placeholder-shown + span { color: red }
+    </style>
+    <script>
+      onload = function() {
+        // setTimeout because in some browsers apparently a toplevel restyle
+        // happens right after the load event fires?
+        setTimeout(function() {
+          document.querySelector("input").type = "hidden";
+          document.documentElement.className = "";
+        }, 10);
+      }
+    </script>
+  </head>
+  <body>
+    <input required placeholder="text"><span>This should be green</span>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/selectors4/selector-read-write-type-change-001-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      span { color: green; }
+    </style>
+  </head>
+  <body>
+    <input type="button"><span>This should be green</span>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/selectors4/selector-read-write-type-change-001.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Check for correctly updating :read-write matching on type change</title>
+    <link rel="match" href="selector-read-write-type-change-001-ref.html">
+    <style>
+      span { color: green; }
+      :read-write + span { color: red }
+    </style>
+    <script>
+      onload = function() {
+        document.querySelector("input").type = "button";
+      }
+    </script>
+  </head>
+  <body>
+    <input required><span>This should be green</span>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/selectors4/selector-read-write-type-change-002-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      span { color: green; }
+    </style>
+  </head>
+  <body>
+    <input required><span>This should be green</span>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/selectors4/selector-read-write-type-change-002.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <head>
+    <title>Check for correctly updating :read-write matching on type change</title>
+    <link rel="match" href="selector-read-write-type-change-002-ref.html">
+    <style>
+      span { color: red; }
+      :read-write + span { color: green }
+    </style>
+    <script>
+      onload = function() {
+        // setTimeout because in some browsers apparently a toplevel restyle
+        // happens right after the load event fires?
+        setTimeout(function() {
+          document.querySelector("input").type = "";
+          document.documentElement.className = "";
+        }, 10);
+      }
+    </script>
+  </head>
+  <body>
+    <input type="hidden" required><span>This should be green</span>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/selectors4/selector-required-type-change-001-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      span { color: green; }
+    </style>
+  </head>
+  <body>
+    <input type="button"><span>This should be green</span>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/selectors4/selector-required-type-change-001.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Check for correctly updating :required matching on type change</title>
+    <link rel="match" href="selector-required-type-change-001-ref.html">
+    <style>
+      span { color: green; }
+      :required + span { color: red }
+    </style>
+    <script>
+      onload = function() {
+        document.querySelector("input").type = "button";
+      }
+    </script>
+  </head>
+  <body>
+    <input required><span>This should be green</span>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/selectors4/selector-required-type-change-002-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+      span { color: green; }
+    </style>
+  </head>
+  <body>
+    <input required><span>This should be green</span>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/selectors4/selector-required-type-change-002.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <head>
+    <title>Check for correctly updating :required matching on type change</title>
+    <link rel="match" href="selector-required-type-change-002-ref.html">
+    <style>
+      span { color: red; }
+      :required + span { color: green }
+    </style>
+    <script>
+      onload = function() {
+        // setTimeout because in some browsers apparently a toplevel restyle
+        // happens right after the load event fires?
+        setTimeout(function() {
+          document.querySelector("input").type = "";
+          document.documentElement.className = "";
+        }, 10);
+      }
+    </script>
+  </head>
+  <body>
+    <input type="hidden" required><span>This should be green</span>
+  </body>
+</html>