Roll wpt tooling

This rolls wpt to latest commit at
https://github.com/web-platform-tests/wpt.
REMOTE-WPT-HEAD: d64f55daffaf7ad0e8cd041ddcfecffb8c7afaaf

Cq-Include-Trybots: luci.chromium.try:linux-wpt-identity-fyi-rel,linux-wpt-input-fyi-rel,linux-blink-rel
Change-Id: I35d26c4f6b5836a4f300c0cbad68387c1cd286d2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4891267
Commit-Queue: Jonathan Lee <jonathanjlee@google.com>
Reviewed-by: Weizhong Xia <weizhong@google.com>
Cr-Commit-Position: refs/heads/main@{#1201743}
diff --git a/third_party/blink/tools/blinkpy/presubmit/common_checks.py b/third_party/blink/tools/blinkpy/presubmit/common_checks.py
index 7024b0c5..133e167 100644
--- a/third_party/blink/tools/blinkpy/presubmit/common_checks.py
+++ b/third_party/blink/tools/blinkpy/presubmit/common_checks.py
@@ -63,6 +63,6 @@
     if proc.returncode != 0:
         return [
             output_api.PresubmitError('`blink_tool.py lint-wpt` failed:',
-                                      long_text=stdout + stderr)
+                                      long_text=(stdout + stderr).decode())
         ]
     return []
diff --git a/third_party/blink/web_tests/external/PRESUBMIT_test.py b/third_party/blink/web_tests/external/PRESUBMIT_test.py
index 1db5b16..817c3caa 100755
--- a/third_party/blink/web_tests/external/PRESUBMIT_test.py
+++ b/third_party/blink/web_tests/external/PRESUBMIT_test.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env vpython3
 # Copyright 2018 The Chromium Authors
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
diff --git a/third_party/wpt_tools/README.chromium b/third_party/wpt_tools/README.chromium
index 647f777..f494585b 100644
--- a/third_party/wpt_tools/README.chromium
+++ b/third_party/wpt_tools/README.chromium
@@ -1,7 +1,7 @@
 Name: web-platform-tests - Test Suites for Web Platform specifications
 Short Name: wpt
 URL: https://github.com/web-platform-tests/wpt/
-Version: 42bf85b98c7037d0eb011589701dd68c71312fb3
+Version: d64f55daffaf7ad0e8cd041ddcfecffb8c7afaaf
 License: LICENSES FOR W3C TEST SUITES (https://www.w3.org/Consortium/Legal/2008/03-bsd-license.html)
 Security Critical: no
 Shipped: no
diff --git a/third_party/wpt_tools/WPTIncludeList b/third_party/wpt_tools/WPTIncludeList
index 60052cf..7d634ba 100644
--- a/third_party/wpt_tools/WPTIncludeList
+++ b/third_party/wpt_tools/WPTIncludeList
@@ -52,6 +52,7 @@
 ./tools/manifest/jsonlib.py
 ./tools/manifest/log.py
 ./tools/manifest/manifest.py
+./tools/manifest/mputil.py
 ./tools/manifest/requirements.txt
 ./tools/manifest/sourcefile.py
 ./tools/manifest/testpaths.py
diff --git a/third_party/wpt_tools/wpt/resources/testdriver.js b/third_party/wpt_tools/wpt/resources/testdriver.js
index a23d6eaf..93419c6 100644
--- a/third_party/wpt_tools/wpt/resources/testdriver.js
+++ b/third_party/wpt_tools/wpt/resources/testdriver.js
@@ -262,7 +262,9 @@
          * occurs.
          *
          * If ``element`` is from a different browsing context, the
-         * command will be run in that context.
+         * command will be run in that context. The test must not depend
+         * on the ``window.name`` property being unset on the target
+         * window.
          *
          * To send special keys, send the respective key's codepoint,
          * as defined by `WebDriver
@@ -417,8 +419,9 @@
         /**
          * Sets the state of a permission
          *
-         * This function simulates a user setting a permission into a
-         * particular state.
+         * This function causes permission requests and queries for the status
+         * of a certain permission type (e.g. "push", or "background-fetch") to
+         * always return ``state``.
          *
          * Matches the `Set Permission
          * <https://w3c.github.io/permissions/#set-permission-command>`_
@@ -430,8 +433,10 @@
          *
          * @param {PermissionDescriptor} descriptor - a `PermissionDescriptor
          *                              <https://w3c.github.io/permissions/#dom-permissiondescriptor>`_
-         *                              dictionary.
-         * @param {String} state - the state of the permission
+         *                              or derived object.
+         * @param {PermissionState} state - a `PermissionState
+         *                          <https://w3c.github.io/permissions/#dom-permissionstate>`_
+         *                          value.
          * @param {WindowProxy} context - Browsing context in which
          *                                to run the call, or null for the current
          *                                browsing context.
diff --git a/third_party/wpt_tools/wpt/tools/certs/cacert.key b/third_party/wpt_tools/wpt/tools/certs/cacert.key
index 2bfd999..28424d9 100644
--- a/third_party/wpt_tools/wpt/tools/certs/cacert.key
+++ b/third_party/wpt_tools/wpt/tools/certs/cacert.key
@@ -1,30 +1,30 @@
 -----BEGIN ENCRYPTED PRIVATE KEY-----
-MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIesNOadYpVBkCAggA
-MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECH8qbBfjkDSHBIIEyIsJK4OoIxiW
-sjNIuMr4mv0JNr5C2fPCodKQjm+iBFOAoWmkQ/tXATJdTkchmwRfG1ydaRwFLg0c
-u9YH01LD09JjGhOuCOvC2EuPMDEUN1zJTimZhk/HeEO0SIlHyq4L+ApYPtEQwQeZ
-sM8qWXyvSpjsoZJV29lwVmzOiO/9xhebvHLOMZKEugjuqDTuchHfX1guTuTIIg6T
-uj9CcmuUYPm6R8teZXkw9eSh+x8ztvn7q0SC/vcpGpyLqyu61MlbW5E33pKqMwiv
-urH87GyxkOZUrO/qhJkgy8SpT6aOQDTS5rnjCSGYpXpTXzdhZFuNtN5nkO6H3qQL
-Pk98EwTGxhH+eaKWqOd3rSGUJYGnqlV+YcK2R80ua6bhj0KoMNyLq9Ga/8eYaCVE
-VQjwBFzAr3NLx/kP2YYXEN34uOQyeuRnghAI0iToLhs7cL/+CVu6g9EEkCfREZ1a
-ipgdfraU4nbswSauc1NiaUX5l5myT/X19gwpxlVqQwG/UgExdI9VGqPDc+t9qSch
-FH2a0r/Ll98IPXHOdnlaPsTvI33V6cFfZwFdUnpH5ESSINQi5tjFH5iyIK9xDr5V
-S3Rm5d3CWaTabJx+hADilGzT77pruHji77Xug0blpbiA0Lp58G++U3cBwiOI5odO
-h/G7jaNE2Owrfla6p1fpWhfZhHUiQDoE6sBHuPA3t1gP8oQTLmSEY/0Kr7UZUGZk
-AHIyVymXFIhpn6spCnBzDgSrsQlFvJuQalbOQIfuCZ/5Gp6+IrJRSawzxzZaVvG0
-PYrB3cQ4w2fMJrtmHBHThebNOlXIXsgAyOr8lpzlrdhHfSJWPwaN+KftyMno9PFR
-J3K4Oay1SHJx7GfnZ7naD53YzekxxEfLFEFYg89Z+EMo2gDVX+v+tLqeQyX0/egz
-jk+EBQHBSaS+sZiw31+kebzZenbrvog9BhMFD0hhiSpA1zGOHND+zY5itxSWXUk7
-fm6FAKOGgQ7lJG0hCHUDoMPMQpXkp/txhcf7gV5WpO4WaCi3Scoo2EFqty/cBibK
-t0HU1yHqU6rAhab1QIsI2PtVL/Qfwx9dsTNKuOACRs9gA+RGZYncDnVv6nLLlUTd
-ARps6JK6dGL4owzkyXKjK7L7Gw7SrChfNHgYlsJ6ktLvlRPVYpHPH5VeLWgyYR4n
-uQiVgHfqGygd8abZEI3kFL+qI8HNIuXaGtRs9kZMfybUZJV9s5sS2XAw7bFlujqq
-XpC1EJZtZtwkKIWDtiFS7E/Q+uWzxLWUP5qF2io3Rs5gUhRB+ep4nqiSsdNu/Ijb
-tPNhpIwnLsFCriaIAMsQ/4hGbCBCCuZmY6917FIwEtZDTAb04ro2NYOhP7LvpiiH
-PRsFNom5aRyhEx/xXOO933rBuhNi7U5EbyDwviaUi7ETij6QGTXd/iI04Er29QDw
-hFemoi/gkW3j3/YMQE7ylbUoWgcEet1BJPeAGBTJ3JpAXdXBLfU7wLnJkhWUv8iS
-ZVn9Gub8Pu2vRqp9REU0jKJqW9F/1zZKi30BfWCs0yy5o2IfNEjlUQwokYb2nHDF
-ITy5LdUd2JqvpbBrPGgc6mTLfr7SpBY3+EA7RwDUFteNXPjnjr4T2rGiYykbsSr/
-a/aqz8EPgvjZOHDxcCBRiw==
+MIIFHDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIlEW03rkHr0YCAggA
+MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECMnvBPzVShbWBIIEyBthE6OWkj8F
+nK4JOaxJeH3h0R6ocmQ3u2H4kPWu34zpGCMdXYJ3N1w2JotViAQQWrVpy0Y4ixr1
+FE0Y1GX/4YYBm+/PMmxPZTKfVIHIHvTCDryOUIhpLp/3tio/NIhvUNYLDHug5wyP
+HCgt4HDNEEV9mlB35delYcHHDl/DDTr5LdtAX1Svxets0cTEZ3XqTVmfN1Y2OBAG
+gPw3fyDqgKD/hq5y2CjonY6vxyMSmTrm3SJhKTwtelKeRLmfLDBoHXHLaoamd8nd
+hGvIGeINwV1Y6z2c+NNJ9ZwKMD2k+Qmt6CfGJKqw+T852ESFyPSqlwQfrlV8Yy69
+gA/POlxF7BHI970QMvZBuMBQgLtbNorvJTEqXBqbM7jlLZxVfZE76X7hkvWT298y
+GAHWI/Y/Qct+PpaBr0yxSN0NHZE0be13gOqSUivshqwf73r6CFnOQbmx3003OqR1
+nltppjrAd+dyJMxek0ouubMPDsG6CgBUgEKUjuAE6zhKwq7m4pHjsWasbT+3Eoly
+o895bTdFbI/t62Xx4NOPrir7KOi1bo6X8LPrjKhJKsYuFdrT8+EgZhUUy6fjdKpx
+lKmvHX8nIoKrM+rdfxNLHMOTHpEsxLiE+WcCcgc9xjPm7d0KTKpacemlKSOL77NN
+Fc0oTP0kANFDz4SFgkUhflP2t8jbyoors1uH4KrkNo2vAxD9DQCdlqgC80zv9+Tq
+ynIHHAbNKwX8gsnKSvKcyuTrw4fA/0r1+1D25kIuameQ0i6fUAVPeVBsQN+2StkC
+N4h3yGpLeIA0UAS+aIjR1h1e/NWvPWvPAhEbWsyuwlxaRSGsTKKLD5WkPw5nQrir
+W1LqfrkEQgo1g+CDl70YU1sPsI3jRBk0kGn0uDTo6SCYPOPmClWESchB/1Plk0e0
+Yr90/258niPcLsNV2lSdnHky0pqGmColPMqCWQvl0fjWpy9WSrFLAFAmo4Pqsdt7
+fZh3vNTpD/EGJQdYhQfyKmSnR0124jAMmu6wQf1jf7cZO7JV7lz2YIdS1V1zuG0X
+CS83EoJlI+OgDsbEtGFkC8sSYtyT+bzc4WLcByWuvZ0HBB0ATfLyzQIEQkRzIVTT
+C3eYgv+LyZi/l7TBLEBNOYH/2jPRghDhwA2F2xtc/81O3C078OP2F+rj8JUk7rX2
+Yaqvpg9bvHvkCu2F2xSG8IFVVzvtR1j0w0tJtjoYSc26R4bJtf1upfuIVyoRogwh
+qvCZfWpIq6wCShdgkHXFCWTlThPCEnATdPBRoCtQ/OoonZtJMJsnMoA3wEqu+Qf7
+DMwffby/ylccNdRtjN8TsUkI2uVeuJa+g4l4uT3h2GSXxNmJuy3+pKm4CllaQp+k
+rAQBigpnNj7Jt1n5OZwiKlwx+yQykhuFt3Y9SiXxlMQWwEYBDB/sziaRFQahQQti
+KveWzLr+Z6kIIvYd8Ay9TOAsdUN2GOeVQXHfaurb+1g3ZJBWxrQNlRF4irdDmsPp
+qNwKVGnMOfkis2mGSmSyBMdbwJYC64V0sTHRC9t/rn33eK0zI9cL5FY9ay1cokLX
+nkZpZKjguQ2yzzq34udLiCQGElcQnCUAMgTe7Vdb81ziPlmR5soTcshirM4+vaN4
+uaG/s+sespCfuB15hdLpAg==
 -----END ENCRYPTED PRIVATE KEY-----
diff --git a/third_party/wpt_tools/wpt/tools/certs/cacert.pem b/third_party/wpt_tools/wpt/tools/certs/cacert.pem
index f698a35..4905177a 100644
--- a/third_party/wpt_tools/wpt/tools/certs/cacert.pem
+++ b/third_party/wpt_tools/wpt/tools/certs/cacert.pem
@@ -1,53 +1,53 @@
 -----BEGIN CERTIFICATE-----
-MIIW5zCCFc+gAwIBAgIDBmgJMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMMEndl
-Yi1wbGF0Zm9ybS10ZXN0czAeFw0yMzA4MTIwMDQ1MzZaFw0yNDA4MTEwMDQ1MzZa
+MIIW5zCCFc+gAwIBAgIDCcbkMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMMEndl
+Yi1wbGF0Zm9ybS10ZXN0czAeFw0yMzA5MTIwMDQ3MTlaFw0yNDA5MTEwMDQ3MTla
 MB0xGzAZBgNVBAMMEndlYi1wbGF0Zm9ybS10ZXN0czCCASIwDQYJKoZIhvcNAQEB
-BQADggEPADCCAQoCggEBAM3mSDwW7plqJ78BOxRgptVkADl0EM2SvRTKGYDoBHLX
-grogviXHsAud4MzZdHa0WuQ7sWTyANLl7sinORE5yG+UWCU7p7DsLuAbn4X5gfDv
-AiQJ+4eZP3/GYS4czjSkX/1Gg7+nxIhKI0hRZdIzv2/etGm+1cBNxiQSf5GcyQrF
-y4iWNcQqBkBXKZalfF68n8uB77s+IlwEzJE+3Q1BXjOG6C3KcoBM6QVKS6iSQVsh
-0bFc/M6eqPn1r87LTIP35PewE8AgA/n9lDcV6ITFB/p8AypJFsY/v0BHP8t8Ua3y
-1/lg5/vXvC1ARIiDsJWirRfywRLTCX2cCIsUSIaqdM8CAwEAAaOCFC4wghQqMAwG
-A1UdEwQFMAMBAf8wHQYDVR0OBBYEFBPyid267eESVzsJ+HDXeY4gCT1wMEcGA1Ud
-IwRAMD6AFBPyid267eESVzsJ+HDXeY4gCT1woSGkHzAdMRswGQYDVQQDDBJ3ZWIt
-cGxhdGZvcm0tdGVzdHOCAwZoCTALBgNVHQ8EBAMCAgQwggoFBgNVHR4Eggn8MIIJ
+BQADggEPADCCAQoCggEBAOKsSaJFosHayLqBysXW9/V44WPagXl1guuUrD0eJJy6
+FnzpUyn3MKB6epxRyMcQuRCct4GC8HBaUpYM9D5uG2aIYdr47P6C5lJJ90503yjy
+OciIPnljxLcoMOpG6HqEiMlBBUsNZSOXx4MgrPrrG/prN9mJIoQYy6iJn4xfh4OE
+cx+O1ZmbFgWsMyrEQ+g98J/bz4bM66hHRKlEu7mJNYoAPsLvAPQVSJGwSlBfLm4p
+LE3Lg70wsXHuY/+xNZ7dbRTnvh3dOoqcbiXzMvAILJTHLSbCQB+gXxGBSyWxWrkO
+bIXxr68K5GDbSNI0GIyN/fRfUGKCOKRclaqsfBbym7kCAwEAAaOCFC4wghQqMAwG
+A1UdEwQFMAMBAf8wHQYDVR0OBBYEFC/OVhUnYgYooLOcpHVY9g2HWSDYMEcGA1Ud
+IwRAMD6AFC/OVhUnYgYooLOcpHVY9g2HWSDYoSGkHzAdMRswGQYDVQQDDBJ3ZWIt
+cGxhdGZvcm0tdGVzdHOCAwnG5DALBgNVHQ8EBAMCAgQwggoFBgNVHR4Eggn8MIIJ
 +KCCCfQwE4IRd2ViLXBsYXRmb3JtLnRlc3QwF4IVd3d3LndlYi1wbGF0Zm9ybS50
-ZXN0MBeCFW5vdC13ZWItcGxhdGZvcm0udGVzdDAYghZ3d3cyLndlYi1wbGF0Zm9y
-bS50ZXN0MBiCFnd3dzEud2ViLXBsYXRmb3JtLnRlc3QwG4IZd3d3Lnd3dy53ZWIt
-cGxhdGZvcm0udGVzdDAbghl3d3cubm90LXdlYi1wbGF0Zm9ybS50ZXN0MByCGnd3
-dzIud3d3LndlYi1wbGF0Zm9ybS50ZXN0MByCGnd3dzEud3d3LndlYi1wbGF0Zm9y
-bS50ZXN0MByCGnd3dzEubm90LXdlYi1wbGF0Zm9ybS50ZXN0MByCGnd3dy53d3cx
-LndlYi1wbGF0Zm9ybS50ZXN0MByCGnd3dy53d3cyLndlYi1wbGF0Zm9ybS50ZXN0
-MByCGnd3dzIubm90LXdlYi1wbGF0Zm9ybS50ZXN0MB2CG3d3dzIud3d3MS53ZWIt
-cGxhdGZvcm0udGVzdDAdght3d3cxLnd3dzIud2ViLXBsYXRmb3JtLnRlc3QwHYIb
-d3d3MS53d3cxLndlYi1wbGF0Zm9ybS50ZXN0MB2CG3d3dzIud3d3Mi53ZWItcGxh
+ZXN0MBeCFW5vdC13ZWItcGxhdGZvcm0udGVzdDAYghZ3d3cxLndlYi1wbGF0Zm9y
+bS50ZXN0MBiCFnd3dzIud2ViLXBsYXRmb3JtLnRlc3QwG4IZd3d3Lm5vdC13ZWIt
+cGxhdGZvcm0udGVzdDAbghl3d3cud3d3LndlYi1wbGF0Zm9ybS50ZXN0MByCGnd3
+dy53d3cyLndlYi1wbGF0Zm9ybS50ZXN0MByCGnd3dzIud3d3LndlYi1wbGF0Zm9y
+bS50ZXN0MByCGnd3dy53d3cxLndlYi1wbGF0Zm9ybS50ZXN0MByCGnd3dzIubm90
+LXdlYi1wbGF0Zm9ybS50ZXN0MByCGnd3dzEud3d3LndlYi1wbGF0Zm9ybS50ZXN0
+MByCGnd3dzEubm90LXdlYi1wbGF0Zm9ybS50ZXN0MB2CG3d3dzIud3d3Mi53ZWIt
+cGxhdGZvcm0udGVzdDAdght3d3cxLnd3dzEud2ViLXBsYXRmb3JtLnRlc3QwHYIb
+d3d3MS53d3cyLndlYi1wbGF0Zm9ybS50ZXN0MB2CG3d3dzIud3d3MS53ZWItcGxh
 dGZvcm0udGVzdDAfgh13d3cud3d3Lm5vdC13ZWItcGxhdGZvcm0udGVzdDAggh53
-d3cyLnd3dy5ub3Qtd2ViLXBsYXRmb3JtLnRlc3QwIIIeeG4tLWx2ZS02bGFkLndl
+d3cxLnd3dy5ub3Qtd2ViLXBsYXRmb3JtLnRlc3QwIIIed3d3Lnd3dzEubm90LXdl
 Yi1wbGF0Zm9ybS50ZXN0MCCCHnd3dy53d3cyLm5vdC13ZWItcGxhdGZvcm0udGVz
-dDAggh53d3cud3d3MS5ub3Qtd2ViLXBsYXRmb3JtLnRlc3QwIIIed3d3MS53d3cu
-bm90LXdlYi1wbGF0Zm9ybS50ZXN0MCGCH3d3dzEud3d3MS5ub3Qtd2ViLXBsYXRm
+dDAggh54bi0tbHZlLTZsYWQud2ViLXBsYXRmb3JtLnRlc3QwIIIed3d3Mi53d3cu
+bm90LXdlYi1wbGF0Zm9ybS50ZXN0MCGCH3d3dzEud3d3Mi5ub3Qtd2ViLXBsYXRm
 b3JtLnRlc3QwIYIfd3d3Mi53d3cyLm5vdC13ZWItcGxhdGZvcm0udGVzdDAhgh93
-d3cxLnd3dzIubm90LXdlYi1wbGF0Zm9ybS50ZXN0MCGCH3d3dzIud3d3MS5ub3Qt
-d2ViLXBsYXRmb3JtLnRlc3QwJIIieG4tLWx2ZS02bGFkLnd3dy53ZWItcGxhdGZv
-cm0udGVzdDAkgiJ4bi0tbHZlLTZsYWQubm90LXdlYi1wbGF0Zm9ybS50ZXN0MCSC
-Ind3dy54bi0tbHZlLTZsYWQud2ViLXBsYXRmb3JtLnRlc3QwJYIjeG4tLWx2ZS02
-bGFkLnd3dzIud2ViLXBsYXRmb3JtLnRlc3QwJYIjd3d3MS54bi0tbHZlLTZsYWQu
-d2ViLXBsYXRmb3JtLnRlc3QwJYIjeG4tLWx2ZS02bGFkLnd3dzEud2ViLXBsYXRm
-b3JtLnRlc3QwJYIjd3d3Mi54bi0tbHZlLTZsYWQud2ViLXBsYXRmb3JtLnRlc3Qw
+d3cyLnd3dzEubm90LXdlYi1wbGF0Zm9ybS50ZXN0MCGCH3d3dzEud3d3MS5ub3Qt
+d2ViLXBsYXRmb3JtLnRlc3QwJIIid3d3LnhuLS1sdmUtNmxhZC53ZWItcGxhdGZv
+cm0udGVzdDAkgiJ4bi0tbHZlLTZsYWQud3d3LndlYi1wbGF0Zm9ybS50ZXN0MCSC
+InhuLS1sdmUtNmxhZC5ub3Qtd2ViLXBsYXRmb3JtLnRlc3QwJYIjd3d3Mi54bi0t
+bHZlLTZsYWQud2ViLXBsYXRmb3JtLnRlc3QwJYIjeG4tLWx2ZS02bGFkLnd3dzIu
+d2ViLXBsYXRmb3JtLnRlc3QwJYIjd3d3MS54bi0tbHZlLTZsYWQud2ViLXBsYXRm
+b3JtLnRlc3QwJYIjeG4tLWx2ZS02bGFkLnd3dzEud2ViLXBsYXRmb3JtLnRlc3Qw
 KIImeG4tLWx2ZS02bGFkLnd3dy5ub3Qtd2ViLXBsYXRmb3JtLnRlc3QwKIImd3d3
-LnhuLS1sdmUtNmxhZC5ub3Qtd2ViLXBsYXRmb3JtLnRlc3QwKYIneG4tLWx2ZS02
-bGFkLnd3dzEubm90LXdlYi1wbGF0Zm9ybS50ZXN0MCmCJ3huLS1sdmUtNmxhZC53
-d3cyLm5vdC13ZWItcGxhdGZvcm0udGVzdDApgid3d3cxLnhuLS1sdmUtNmxhZC5u
-b3Qtd2ViLXBsYXRmb3JtLnRlc3QwKYInd3d3Mi54bi0tbHZlLTZsYWQubm90LXdl
+LnhuLS1sdmUtNmxhZC5ub3Qtd2ViLXBsYXRmb3JtLnRlc3QwKYInd3d3Mi54bi0t
+bHZlLTZsYWQubm90LXdlYi1wbGF0Zm9ybS50ZXN0MCmCJ3d3dzEueG4tLWx2ZS02
+bGFkLm5vdC13ZWItcGxhdGZvcm0udGVzdDApgid4bi0tbHZlLTZsYWQud3d3MS5u
+b3Qtd2ViLXBsYXRmb3JtLnRlc3QwKYIneG4tLWx2ZS02bGFkLnd3dzIubm90LXdl
 Yi1wbGF0Zm9ybS50ZXN0MCuCKXhuLS1uOGo2ZHM1M2x3d2tycWh2MjhhLndlYi1w
 bGF0Zm9ybS50ZXN0MC2CK3huLS1sdmUtNmxhZC54bi0tbHZlLTZsYWQud2ViLXBs
-YXRmb3JtLnRlc3QwL4IteG4tLW44ajZkczUzbHd3a3JxaHYyOGEubm90LXdlYi1w
-bGF0Zm9ybS50ZXN0MC+CLXd3dy54bi0tbjhqNmRzNTNsd3drcnFodjI4YS53ZWIt
-cGxhdGZvcm0udGVzdDAvgi14bi0tbjhqNmRzNTNsd3drcnFodjI4YS53d3cud2Vi
+YXRmb3JtLnRlc3QwL4IteG4tLW44ajZkczUzbHd3a3JxaHYyOGEud3d3LndlYi1w
+bGF0Zm9ybS50ZXN0MC+CLXhuLS1uOGo2ZHM1M2x3d2tycWh2MjhhLm5vdC13ZWIt
+cGxhdGZvcm0udGVzdDAvgi13d3cueG4tLW44ajZkczUzbHd3a3JxaHYyOGEud2Vi
 LXBsYXRmb3JtLnRlc3QwMIIueG4tLW44ajZkczUzbHd3a3JxaHYyOGEud3d3Mi53
 ZWItcGxhdGZvcm0udGVzdDAwgi54bi0tbjhqNmRzNTNsd3drcnFodjI4YS53d3cx
-LndlYi1wbGF0Zm9ybS50ZXN0MDCCLnd3dzEueG4tLW44ajZkczUzbHd3a3JxaHYy
-OGEud2ViLXBsYXRmb3JtLnRlc3QwMIIud3d3Mi54bi0tbjhqNmRzNTNsd3drcnFo
+LndlYi1wbGF0Zm9ybS50ZXN0MDCCLnd3dzIueG4tLW44ajZkczUzbHd3a3JxaHYy
+OGEud2ViLXBsYXRmb3JtLnRlc3QwMIIud3d3MS54bi0tbjhqNmRzNTNsd3drcnFo
 djI4YS53ZWItcGxhdGZvcm0udGVzdDAxgi94bi0tbHZlLTZsYWQueG4tLWx2ZS02
 bGFkLm5vdC13ZWItcGxhdGZvcm0udGVzdDAzgjF4bi0tbjhqNmRzNTNsd3drcnFo
 djI4YS53d3cubm90LXdlYi1wbGF0Zm9ybS50ZXN0MDOCMXd3dy54bi0tbjhqNmRz
@@ -58,48 +58,48 @@
 LnRlc3QwNIIyd3d3Mi54bi0tbjhqNmRzNTNsd3drcnFodjI4YS5ub3Qtd2ViLXBs
 YXRmb3JtLnRlc3QwOII2eG4tLW44ajZkczUzbHd3a3JxaHYyOGEueG4tLWx2ZS02
 bGFkLndlYi1wbGF0Zm9ybS50ZXN0MDiCNnhuLS1sdmUtNmxhZC54bi0tbjhqNmRz
-NTNsd3drcnFodjI4YS53ZWItcGxhdGZvcm0udGVzdDA8gjp4bi0tbjhqNmRzNTNs
-d3drcnFodjI4YS54bi0tbHZlLTZsYWQubm90LXdlYi1wbGF0Zm9ybS50ZXN0MDyC
-OnhuLS1sdmUtNmxhZC54bi0tbjhqNmRzNTNsd3drcnFodjI4YS5ub3Qtd2ViLXBs
+NTNsd3drcnFodjI4YS53ZWItcGxhdGZvcm0udGVzdDA8gjp4bi0tbHZlLTZsYWQu
+eG4tLW44ajZkczUzbHd3a3JxaHYyOGEubm90LXdlYi1wbGF0Zm9ybS50ZXN0MDyC
+OnhuLS1uOGo2ZHM1M2x3d2tycWh2MjhhLnhuLS1sdmUtNmxhZC5ub3Qtd2ViLXBs
 YXRmb3JtLnRlc3QwQ4JBeG4tLW44ajZkczUzbHd3a3JxaHYyOGEueG4tLW44ajZk
 czUzbHd3a3JxaHYyOGEud2ViLXBsYXRmb3JtLnRlc3QwR4JFeG4tLW44ajZkczUz
 bHd3a3JxaHYyOGEueG4tLW44ajZkczUzbHd3a3JxaHYyOGEubm90LXdlYi1wbGF0
 Zm9ybS50ZXN0MBMGA1UdJQQMMAoGCCsGAQUFBwMBMIIJhQYDVR0RBIIJfDCCCXiC
 EXdlYi1wbGF0Zm9ybS50ZXN0ghV3d3cud2ViLXBsYXRmb3JtLnRlc3SCFW5vdC13
-ZWItcGxhdGZvcm0udGVzdIIWd3d3Mi53ZWItcGxhdGZvcm0udGVzdIIWd3d3MS53
-ZWItcGxhdGZvcm0udGVzdIIZd3d3Lnd3dy53ZWItcGxhdGZvcm0udGVzdIIZd3d3
-Lm5vdC13ZWItcGxhdGZvcm0udGVzdIIad3d3Mi53d3cud2ViLXBsYXRmb3JtLnRl
-c3SCGnd3dzEud3d3LndlYi1wbGF0Zm9ybS50ZXN0ghp3d3cxLm5vdC13ZWItcGxh
-dGZvcm0udGVzdIIad3d3Lnd3dzEud2ViLXBsYXRmb3JtLnRlc3SCGnd3dy53d3cy
-LndlYi1wbGF0Zm9ybS50ZXN0ghp3d3cyLm5vdC13ZWItcGxhdGZvcm0udGVzdIIb
-d3d3Mi53d3cxLndlYi1wbGF0Zm9ybS50ZXN0ght3d3cxLnd3dzIud2ViLXBsYXRm
-b3JtLnRlc3SCG3d3dzEud3d3MS53ZWItcGxhdGZvcm0udGVzdIIbd3d3Mi53d3cy
+ZWItcGxhdGZvcm0udGVzdIIWd3d3MS53ZWItcGxhdGZvcm0udGVzdIIWd3d3Mi53
+ZWItcGxhdGZvcm0udGVzdIIZd3d3Lm5vdC13ZWItcGxhdGZvcm0udGVzdIIZd3d3
+Lnd3dy53ZWItcGxhdGZvcm0udGVzdIIad3d3Lnd3dzIud2ViLXBsYXRmb3JtLnRl
+c3SCGnd3dzIud3d3LndlYi1wbGF0Zm9ybS50ZXN0ghp3d3cud3d3MS53ZWItcGxh
+dGZvcm0udGVzdIIad3d3Mi5ub3Qtd2ViLXBsYXRmb3JtLnRlc3SCGnd3dzEud3d3
+LndlYi1wbGF0Zm9ybS50ZXN0ghp3d3cxLm5vdC13ZWItcGxhdGZvcm0udGVzdIIb
+d3d3Mi53d3cyLndlYi1wbGF0Zm9ybS50ZXN0ght3d3cxLnd3dzEud2ViLXBsYXRm
+b3JtLnRlc3SCG3d3dzEud3d3Mi53ZWItcGxhdGZvcm0udGVzdIIbd3d3Mi53d3cx
 LndlYi1wbGF0Zm9ybS50ZXN0gh13d3cud3d3Lm5vdC13ZWItcGxhdGZvcm0udGVz
-dIIed3d3Mi53d3cubm90LXdlYi1wbGF0Zm9ybS50ZXN0gh54bi0tbHZlLTZsYWQu
+dIIed3d3MS53d3cubm90LXdlYi1wbGF0Zm9ybS50ZXN0gh53d3cud3d3MS5ub3Qt
 d2ViLXBsYXRmb3JtLnRlc3SCHnd3dy53d3cyLm5vdC13ZWItcGxhdGZvcm0udGVz
-dIIed3d3Lnd3dzEubm90LXdlYi1wbGF0Zm9ybS50ZXN0gh53d3cxLnd3dy5ub3Qt
-d2ViLXBsYXRmb3JtLnRlc3SCH3d3dzEud3d3MS5ub3Qtd2ViLXBsYXRmb3JtLnRl
-c3SCH3d3dzIud3d3Mi5ub3Qtd2ViLXBsYXRmb3JtLnRlc3SCH3d3dzEud3d3Mi5u
-b3Qtd2ViLXBsYXRmb3JtLnRlc3SCH3d3dzIud3d3MS5ub3Qtd2ViLXBsYXRmb3Jt
-LnRlc3SCInhuLS1sdmUtNmxhZC53d3cud2ViLXBsYXRmb3JtLnRlc3SCInhuLS1s
-dmUtNmxhZC5ub3Qtd2ViLXBsYXRmb3JtLnRlc3SCInd3dy54bi0tbHZlLTZsYWQu
-d2ViLXBsYXRmb3JtLnRlc3SCI3huLS1sdmUtNmxhZC53d3cyLndlYi1wbGF0Zm9y
-bS50ZXN0giN3d3cxLnhuLS1sdmUtNmxhZC53ZWItcGxhdGZvcm0udGVzdIIjeG4t
-LWx2ZS02bGFkLnd3dzEud2ViLXBsYXRmb3JtLnRlc3SCI3d3dzIueG4tLWx2ZS02
-bGFkLndlYi1wbGF0Zm9ybS50ZXN0giZ4bi0tbHZlLTZsYWQud3d3Lm5vdC13ZWIt
+dIIeeG4tLWx2ZS02bGFkLndlYi1wbGF0Zm9ybS50ZXN0gh53d3cyLnd3dy5ub3Qt
+d2ViLXBsYXRmb3JtLnRlc3SCH3d3dzEud3d3Mi5ub3Qtd2ViLXBsYXRmb3JtLnRl
+c3SCH3d3dzIud3d3Mi5ub3Qtd2ViLXBsYXRmb3JtLnRlc3SCH3d3dzIud3d3MS5u
+b3Qtd2ViLXBsYXRmb3JtLnRlc3SCH3d3dzEud3d3MS5ub3Qtd2ViLXBsYXRmb3Jt
+LnRlc3SCInd3dy54bi0tbHZlLTZsYWQud2ViLXBsYXRmb3JtLnRlc3SCInhuLS1s
+dmUtNmxhZC53d3cud2ViLXBsYXRmb3JtLnRlc3SCInhuLS1sdmUtNmxhZC5ub3Qt
+d2ViLXBsYXRmb3JtLnRlc3SCI3d3dzIueG4tLWx2ZS02bGFkLndlYi1wbGF0Zm9y
+bS50ZXN0giN4bi0tbHZlLTZsYWQud3d3Mi53ZWItcGxhdGZvcm0udGVzdIIjd3d3
+MS54bi0tbHZlLTZsYWQud2ViLXBsYXRmb3JtLnRlc3SCI3huLS1sdmUtNmxhZC53
+d3cxLndlYi1wbGF0Zm9ybS50ZXN0giZ4bi0tbHZlLTZsYWQud3d3Lm5vdC13ZWIt
 cGxhdGZvcm0udGVzdIImd3d3LnhuLS1sdmUtNmxhZC5ub3Qtd2ViLXBsYXRmb3Jt
-LnRlc3SCJ3huLS1sdmUtNmxhZC53d3cxLm5vdC13ZWItcGxhdGZvcm0udGVzdIIn
-eG4tLWx2ZS02bGFkLnd3dzIubm90LXdlYi1wbGF0Zm9ybS50ZXN0gid3d3cxLnhu
-LS1sdmUtNmxhZC5ub3Qtd2ViLXBsYXRmb3JtLnRlc3SCJ3d3dzIueG4tLWx2ZS02
-bGFkLm5vdC13ZWItcGxhdGZvcm0udGVzdIIpeG4tLW44ajZkczUzbHd3a3JxaHYy
+LnRlc3SCJ3d3dzIueG4tLWx2ZS02bGFkLm5vdC13ZWItcGxhdGZvcm0udGVzdIIn
+d3d3MS54bi0tbHZlLTZsYWQubm90LXdlYi1wbGF0Zm9ybS50ZXN0gid4bi0tbHZl
+LTZsYWQud3d3MS5ub3Qtd2ViLXBsYXRmb3JtLnRlc3SCJ3huLS1sdmUtNmxhZC53
+d3cyLm5vdC13ZWItcGxhdGZvcm0udGVzdIIpeG4tLW44ajZkczUzbHd3a3JxaHYy
 OGEud2ViLXBsYXRmb3JtLnRlc3SCK3huLS1sdmUtNmxhZC54bi0tbHZlLTZsYWQu
-d2ViLXBsYXRmb3JtLnRlc3SCLXhuLS1uOGo2ZHM1M2x3d2tycWh2MjhhLm5vdC13
-ZWItcGxhdGZvcm0udGVzdIItd3d3LnhuLS1uOGo2ZHM1M2x3d2tycWh2MjhhLndl
-Yi1wbGF0Zm9ybS50ZXN0gi14bi0tbjhqNmRzNTNsd3drcnFodjI4YS53d3cud2Vi
+d2ViLXBsYXRmb3JtLnRlc3SCLXhuLS1uOGo2ZHM1M2x3d2tycWh2MjhhLnd3dy53
+ZWItcGxhdGZvcm0udGVzdIIteG4tLW44ajZkczUzbHd3a3JxaHYyOGEubm90LXdl
+Yi1wbGF0Zm9ybS50ZXN0gi13d3cueG4tLW44ajZkczUzbHd3a3JxaHYyOGEud2Vi
 LXBsYXRmb3JtLnRlc3SCLnhuLS1uOGo2ZHM1M2x3d2tycWh2MjhhLnd3dzIud2Vi
 LXBsYXRmb3JtLnRlc3SCLnhuLS1uOGo2ZHM1M2x3d2tycWh2MjhhLnd3dzEud2Vi
-LXBsYXRmb3JtLnRlc3SCLnd3dzEueG4tLW44ajZkczUzbHd3a3JxaHYyOGEud2Vi
 LXBsYXRmb3JtLnRlc3SCLnd3dzIueG4tLW44ajZkczUzbHd3a3JxaHYyOGEud2Vi
+LXBsYXRmb3JtLnRlc3SCLnd3dzEueG4tLW44ajZkczUzbHd3a3JxaHYyOGEud2Vi
 LXBsYXRmb3JtLnRlc3SCL3huLS1sdmUtNmxhZC54bi0tbHZlLTZsYWQubm90LXdl
 Yi1wbGF0Zm9ybS50ZXN0gjF4bi0tbjhqNmRzNTNsd3drcnFodjI4YS53d3cubm90
 LXdlYi1wbGF0Zm9ybS50ZXN0gjF3d3cueG4tLW44ajZkczUzbHd3a3JxaHYyOGEu
@@ -110,16 +110,16 @@
 ZHM1M2x3d2tycWh2MjhhLm5vdC13ZWItcGxhdGZvcm0udGVzdII2eG4tLW44ajZk
 czUzbHd3a3JxaHYyOGEueG4tLWx2ZS02bGFkLndlYi1wbGF0Zm9ybS50ZXN0gjZ4
 bi0tbHZlLTZsYWQueG4tLW44ajZkczUzbHd3a3JxaHYyOGEud2ViLXBsYXRmb3Jt
-LnRlc3SCOnhuLS1uOGo2ZHM1M2x3d2tycWh2MjhhLnhuLS1sdmUtNmxhZC5ub3Qt
-d2ViLXBsYXRmb3JtLnRlc3SCOnhuLS1sdmUtNmxhZC54bi0tbjhqNmRzNTNsd3dr
-cnFodjI4YS5ub3Qtd2ViLXBsYXRmb3JtLnRlc3SCQXhuLS1uOGo2ZHM1M2x3d2ty
+LnRlc3SCOnhuLS1sdmUtNmxhZC54bi0tbjhqNmRzNTNsd3drcnFodjI4YS5ub3Qt
+d2ViLXBsYXRmb3JtLnRlc3SCOnhuLS1uOGo2ZHM1M2x3d2tycWh2MjhhLnhuLS1s
+dmUtNmxhZC5ub3Qtd2ViLXBsYXRmb3JtLnRlc3SCQXhuLS1uOGo2ZHM1M2x3d2ty
 cWh2MjhhLnhuLS1uOGo2ZHM1M2x3d2tycWh2MjhhLndlYi1wbGF0Zm9ybS50ZXN0
 gkV4bi0tbjhqNmRzNTNsd3drcnFodjI4YS54bi0tbjhqNmRzNTNsd3drcnFodjI4
-YS5ub3Qtd2ViLXBsYXRmb3JtLnRlc3QwDQYJKoZIhvcNAQELBQADggEBADRLq9/7
-cf51ebjreSdMGetCUq2IRBsM4zVSq0TFEV0r4wSjL8snqRK98iyw/KHBwGS6XJhy
-OeIPAfkogUUliyvgj7Xy2n0CzbyLlCH8e1lqJYER9DtGVtXa6+K2dMzlWCh6kHz+
-i6axsgF4cQoPgirR3XAxM8VyJ+0GWynJ5piFhLKVG0oEOZywBofmicemAfb1R62u
-fAYLSqzizfYXBkvVSISEf4UjzK7O3P31F3IXhHhyIfZ9aZn0EyUvBauuMWeUMKQB
-G4M2A74WpWFOGBKhCCmmR5q6lZt+mAVQMdBAOXpTkOPByWilzitXruh3tLDkS3B6
-vGgUAnQOM955NiY=
+YS5ub3Qtd2ViLXBsYXRmb3JtLnRlc3QwDQYJKoZIhvcNAQELBQADggEBABgmlNYL
+FvectFPZIiLdS2t3K73eIDfyK2Ay1gILuzSggrB1GNL6X4AmWeoX962KV/lnKPTt
+2SfzV4ej3A4tJNql+vFUHtYV1GfXbDDjRLYff1b/GiF3QbJy7bZmzt9gxTE9/Rf1
+uj7M9VbnfQ9svyr7j5O5C9SQ811ejTGeGQ8oD1i3ymRnOm8hnQf0PnHcsJFfvaiV
+R5bqtARM7ONxSoCI1oJvq/iXUgEUiwDWfGZCJslBY9M0AwEX9XL9tRwGjwzUzDp3
+U3kyG0pCEcBYBz3RxUlKHft6MWBGEs8rDZiEJtLj4CUyfcWOn3YPdA7uZ9UgZ6D1
+XHPWabPan/e6Hzo=
 -----END CERTIFICATE-----
diff --git a/third_party/wpt_tools/wpt/tools/certs/web-platform.test.key b/third_party/wpt_tools/wpt/tools/certs/web-platform.test.key
index ae07ef8..16c60a9 100644
--- a/third_party/wpt_tools/wpt/tools/certs/web-platform.test.key
+++ b/third_party/wpt_tools/wpt/tools/certs/web-platform.test.key
@@ -1,28 +1,28 @@
 -----BEGIN PRIVATE KEY-----
-MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCo+KBRak1wDwph
-yImIneMMr/QDWXuBxIQlvlDVczGR3/p73qcT3UNBe+bKDNFcOujJ/S8oRdDswnPA
-Fw8zBgmq9+7md5KSawX6ehLPaubPgOrdvZ4h7Fyzg0C0X3Erjq6ZZdqsKBlDKO2s
-rxvB36+FfgsZNqeYnwhUwUKGQihPbZehdM0elcYpT9fLQSU/wXkVSuitOTevUQMF
-aX/mvEhHgYcPUywf3zzqEaJbl4swet4JmcfNZYpiaLozwWJA64BDBEzt6pjssQMx
-FAdKoU6BEfDUvIphXAwmX5DIb6ZolR8To/abPIEgmMhkmRpMhHs37+TRxOWacBbN
-1VSu43lrAgMBAAECggEBAJcckrN87/gRW2WLpIQLP2yvRvhf5DZvewcOJthdhq1A
-s5Bn1N/M90Lu/b0grXNgM6PAy5WmqmuykzXs6WjmM2s1A/AUa36KO/R/i6b3oadd
-Ua4wz+TirxzTeuBUoq8jkT4aeu+PhNgtGDFhAN/nnd8yk9ZerI+vWhICK+5J6Cbv
-FbPFF9uL/OKxNQPD/f1ScDKOdcvNwcYkZIRbTYiO/WaQqhQ+N3EG1eR6QHA8Xj29
-FZxgbH0GZysZ9D2/u3nTZ9u+n7D2rGmjPOJ6nRWsju6fQJZP7RFlfLu1C2MLlpdN
-ewEVriSRfJqYEx9tWAMUi6X5bEkz1zeu8HLMP1LvDNECgYEA0QF1fnludQckpuMY
-itX4ALyG5rE4deSWy4g544B6/N1xx4kQfL1iE7Fyfqv75YliexoY4XBiGh06bDaH
-gtaLn/NhD0GgBfBozTuXBNemFk4e6rChSBHHVenQ0WUcPusQ8J4WFSyfLbZ81Yj3
-8+kpNdHOwyoItNeYVKvbTdi6f+cCgYEAzva/h4d/G+Lnf9IvE9D/Xx0ZDp11VAK7
-N+5mzbsIaGa93ZJDFpvzgidZuuzZvQmpyVaBkZgq8wdNCizV9z1ffkPi0mZ5Ulk9
-hBelqt9U97K3jgJrWcljpI8aMOQEGmKQ/Jfh8GC3uFQ9oEXPXUOVEnVwPa04eLpc
-GFRBof6+md0CgYBZcGNkLPi8scLYe8QL3YaUYhjkbaA4qwpwrKoBFgELZoQDHD9m
-8fAfd0cEt/pCfqka/s744gvJGwreNfOFZOQ5HYBCdBXTlM50TPTSSKo1T1bhnZZW
-7ey8ciM/CCKvNg54uV2/z+mKOVZwVn2s7fEJq+/sCnfRU5VHXoepZ35frwKBgHAN
-4pcjgmt7x7FKAFkvUASUQYvLLsokjG708c4zlJLKBcRgO1iTaR0v5wIw2JbkE3xC
-DPbiN2bLridzLQmmvfwkDzuxZcBzNr48+2JvdpaBuaX2o17CjeN740dTPPF4Kl3I
-cfjutKnlWlRbV7F8NMsAsJuEHM4owFIf1EU8IsA1AoGAMN2S5kcmdMXGTrKk1EU0
-eFaFvVnXkqSFcZ+jAzeIutG9HpywpOhcIki5KveLSu6Cq4k4Y3RjnTbzfm+jlFoq
-0Q95PMTLdrHA3+EPR1GZHG0N08C/SyNpKiSuOYfSWjc1+qDZ1K6PSoRseCjkL45c
-ByqDG0VTRllPB/tjcyEinrk=
+MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQC1nEdkHngqBiPE
+jhia+mVcB77pqyB4XuSIcwRayIAnbTX8WO1AwA04wXxfyJbzkTgms1tw9Cjtg4Nx
+20QDOfLfFuQL/yrknlY/Pc93XWvC5OlbXR9b1IlWe2oQlqQtn9N6yf1mc8TlM9V9
+B7kzCjG5HBMUQHRWnBptRkJ0kn8OrfjCwo+6+2VpWg+qNuOWih/VdJjwQsV3ADwu
+R6AtIg/+84qF8CJl11GO0bmZjObcYjKhBOVRXPzMpBQ0tzUa4uR9uMYIJzeVo1tB
+8tNon5sEhbxfnxHJ4hpVfc1iNYetrlX+P4i0aIKmLVdqVwG48CKEQr/9vFLOAxX+
+tDVSNUzNAgMBAAECggEBAI9YGsFbi10gG3s47RLmMLrDIl0tRSO9QoSww+8j8oMs
+IEBdsGY7MDRsA89WdE8rgDaLutEc5nESLN3hQ32Ib7rGRplxflmU4F5qWybMBhJ+
+u/HxPp8s0noAT2dof43DaRix9eC33+FJWGinf/ZZRYXmMuj5NtplvDYkDrAUMVVT
+h2ndzlfv5u9fGZnXT/7qke03OZW3lGnI6BDw8dKgr/Lt0yF2oHUt3wbg29lHOh2N
+i/MUIxhYM/RnjZYHveKph3fapVN63ZVBi6xCZzLEPTFrZUtMZ0HpyNxNrCE6ZQrl
+dH8qH72ksvtTpUnIcoehAbjuaedBtzGEcaeXUGFoLcECgYEA2u6onDUmsiv/prZX
+sHUtHaTJf09M0RASaSIKZvVVl03wsL2uHqF8PU2QaOh1G85u+D+vNlBqA4lKAxsD
+W7Cv4qOJxGlVYeYYNhuPMXaWbnGFeSd8ijP44WUSDsCdmx99mwV1rMYXirypCgiq
+5Cg8TYQ/Ze2A5Z5hFltpuWVNzuMCgYEA1Fv0TGrWhuaJy+M9JErNmmvaLjtIknIH
+J2iaA0bAgINBDxODVN8gx4CufuJ8+QlRziIlWeYhE6Xg077GCsYYMW9GiaqyPDpG
+9q4efQU4hqgt/1Nx29hXO+sBCvgwFWNM1+4bBwwlfl2RIH9MUmFVUeb9jrE71IEi
+Vtai+MEnFI8CgYEAvwWJfHIrxz34nB9xN3el272SSlCIt8kMJ0saRGc/PPs2TR/T
+Msq3uk1Y+RAKTi66SERYvk0/ksJRH5CMR45MiLUkWYmGhBrdeShmskPEUa0fRmyb
+j4J1X0Rukwyg915sm7wRxqQ4mkauPyD86oHoy8HeBzNJSg/8qiMshiTL1P0CgYEA
+iMn+sGa1b0n7AvmKPeFtVQYaEWOgmKyYzRrE8k1V3LIvay2vkDD2JWxPem4cADKv
+ni9Cjgj9z3EvRFYDrZbyqI08C5uHmeIUKfv4qaF+Ssb0ch5nHizZ7D+xeUZreqtv
+KTw37q81S5Or0xoMqJgH5Gz9cTfbCURxQSya589LhCMCgYEAr5rWX0j32ZKMldtA
+yKWuNrEiX66Ab09hquP8Ew6atKDxdgp6vxNcnKKGFkMidg5gv8NAht08EWh/Yyuf
+VcigxrpqNkLJaqjntXCeurGUkcsLr8eZc0Cp+LJYsiOxg/TEnR0TlGraM4GftwP9
+Na3VJtUm8mO3hM+O5p7sTwNOmuI=
 -----END PRIVATE KEY-----
diff --git a/third_party/wpt_tools/wpt/tools/certs/web-platform.test.pem b/third_party/wpt_tools/wpt/tools/certs/web-platform.test.pem
index 6f84355..5140687 100644
--- a/third_party/wpt_tools/wpt/tools/certs/web-platform.test.pem
+++ b/third_party/wpt_tools/wpt/tools/certs/web-platform.test.pem
@@ -1,113 +1,113 @@
 Certificate:
     Data:
         Version: 3 (0x2)
-        Serial Number: 419850 (0x6680a)
+        Serial Number: 640741 (0x9c6e5)
         Signature Algorithm: sha256WithRSAEncryption
         Issuer: CN=web-platform-tests
         Validity
-            Not Before: Aug 12 00:45:36 2023 GMT
-            Not After : Aug 11 00:45:36 2024 GMT
+            Not Before: Sep 12 00:47:19 2023 GMT
+            Not After : Sep 11 00:47:19 2024 GMT
         Subject: CN=web-platform.test
         Subject Public Key Info:
             Public Key Algorithm: rsaEncryption
                 RSA Public-Key: (2048 bit)
                 Modulus:
-                    00:a8:f8:a0:51:6a:4d:70:0f:0a:61:c8:89:88:9d:
-                    e3:0c:af:f4:03:59:7b:81:c4:84:25:be:50:d5:73:
-                    31:91:df:fa:7b:de:a7:13:dd:43:41:7b:e6:ca:0c:
-                    d1:5c:3a:e8:c9:fd:2f:28:45:d0:ec:c2:73:c0:17:
-                    0f:33:06:09:aa:f7:ee:e6:77:92:92:6b:05:fa:7a:
-                    12:cf:6a:e6:cf:80:ea:dd:bd:9e:21:ec:5c:b3:83:
-                    40:b4:5f:71:2b:8e:ae:99:65:da:ac:28:19:43:28:
-                    ed:ac:af:1b:c1:df:af:85:7e:0b:19:36:a7:98:9f:
-                    08:54:c1:42:86:42:28:4f:6d:97:a1:74:cd:1e:95:
-                    c6:29:4f:d7:cb:41:25:3f:c1:79:15:4a:e8:ad:39:
-                    37:af:51:03:05:69:7f:e6:bc:48:47:81:87:0f:53:
-                    2c:1f:df:3c:ea:11:a2:5b:97:8b:30:7a:de:09:99:
-                    c7:cd:65:8a:62:68:ba:33:c1:62:40:eb:80:43:04:
-                    4c:ed:ea:98:ec:b1:03:31:14:07:4a:a1:4e:81:11:
-                    f0:d4:bc:8a:61:5c:0c:26:5f:90:c8:6f:a6:68:95:
-                    1f:13:a3:f6:9b:3c:81:20:98:c8:64:99:1a:4c:84:
-                    7b:37:ef:e4:d1:c4:e5:9a:70:16:cd:d5:54:ae:e3:
-                    79:6b
+                    00:b5:9c:47:64:1e:78:2a:06:23:c4:8e:18:9a:fa:
+                    65:5c:07:be:e9:ab:20:78:5e:e4:88:73:04:5a:c8:
+                    80:27:6d:35:fc:58:ed:40:c0:0d:38:c1:7c:5f:c8:
+                    96:f3:91:38:26:b3:5b:70:f4:28:ed:83:83:71:db:
+                    44:03:39:f2:df:16:e4:0b:ff:2a:e4:9e:56:3f:3d:
+                    cf:77:5d:6b:c2:e4:e9:5b:5d:1f:5b:d4:89:56:7b:
+                    6a:10:96:a4:2d:9f:d3:7a:c9:fd:66:73:c4:e5:33:
+                    d5:7d:07:b9:33:0a:31:b9:1c:13:14:40:74:56:9c:
+                    1a:6d:46:42:74:92:7f:0e:ad:f8:c2:c2:8f:ba:fb:
+                    65:69:5a:0f:aa:36:e3:96:8a:1f:d5:74:98:f0:42:
+                    c5:77:00:3c:2e:47:a0:2d:22:0f:fe:f3:8a:85:f0:
+                    22:65:d7:51:8e:d1:b9:99:8c:e6:dc:62:32:a1:04:
+                    e5:51:5c:fc:cc:a4:14:34:b7:35:1a:e2:e4:7d:b8:
+                    c6:08:27:37:95:a3:5b:41:f2:d3:68:9f:9b:04:85:
+                    bc:5f:9f:11:c9:e2:1a:55:7d:cd:62:35:87:ad:ae:
+                    55:fe:3f:88:b4:68:82:a6:2d:57:6a:57:01:b8:f0:
+                    22:84:42:bf:fd:bc:52:ce:03:15:fe:b4:35:52:35:
+                    4c:cd
                 Exponent: 65537 (0x10001)
         X509v3 extensions:
             X509v3 Basic Constraints: 
                 CA:FALSE
             X509v3 Subject Key Identifier: 
-                3F:99:A2:8E:CA:F2:40:25:E7:B8:79:FA:B2:6D:09:FF:51:B9:A5:8F
+                8C:6B:B4:12:01:78:DC:2B:78:9F:58:EC:38:00:49:12:7A:CB:FE:AA
             X509v3 Authority Key Identifier: 
-                keyid:13:F2:89:DD:BA:ED:E1:12:57:3B:09:F8:70:D7:79:8E:20:09:3D:70
+                keyid:2F:CE:56:15:27:62:06:28:A0:B3:9C:A4:75:58:F6:0D:87:59:20:D8
 
             X509v3 Key Usage: 
                 Digital Signature, Non Repudiation, Key Encipherment
             X509v3 Extended Key Usage: 
                 TLS Web Server Authentication
             X509v3 Subject Alternative Name: 
-                DNS:web-platform.test, DNS:www.web-platform.test, DNS:not-web-platform.test, DNS:www2.web-platform.test, DNS:www1.web-platform.test, DNS:www.www.web-platform.test, DNS:www.not-web-platform.test, DNS:www2.www.web-platform.test, DNS:www1.www.web-platform.test, DNS:www1.not-web-platform.test, DNS:www.www1.web-platform.test, DNS:www.www2.web-platform.test, DNS:www2.not-web-platform.test, DNS:www2.www1.web-platform.test, DNS:www1.www2.web-platform.test, DNS:www1.www1.web-platform.test, DNS:www2.www2.web-platform.test, DNS:www.www.not-web-platform.test, DNS:www2.www.not-web-platform.test, DNS:xn--lve-6lad.web-platform.test, DNS:www.www2.not-web-platform.test, DNS:www.www1.not-web-platform.test, DNS:www1.www.not-web-platform.test, DNS:www1.www1.not-web-platform.test, DNS:www2.www2.not-web-platform.test, DNS:www1.www2.not-web-platform.test, DNS:www2.www1.not-web-platform.test, DNS:xn--lve-6lad.www.web-platform.test, DNS:xn--lve-6lad.not-web-platform.test, DNS:www.xn--lve-6lad.web-platform.test, DNS:xn--lve-6lad.www2.web-platform.test, DNS:www1.xn--lve-6lad.web-platform.test, DNS:xn--lve-6lad.www1.web-platform.test, DNS:www2.xn--lve-6lad.web-platform.test, DNS:xn--lve-6lad.www.not-web-platform.test, DNS:www.xn--lve-6lad.not-web-platform.test, DNS:xn--lve-6lad.www1.not-web-platform.test, DNS:xn--lve-6lad.www2.not-web-platform.test, DNS:www1.xn--lve-6lad.not-web-platform.test, DNS:www2.xn--lve-6lad.not-web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.web-platform.test, DNS:xn--lve-6lad.xn--lve-6lad.web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.not-web-platform.test, DNS:www.xn--n8j6ds53lwwkrqhv28a.web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.www.web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.www2.web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.www1.web-platform.test, DNS:www1.xn--n8j6ds53lwwkrqhv28a.web-platform.test, DNS:www2.xn--n8j6ds53lwwkrqhv28a.web-platform.test, DNS:xn--lve-6lad.xn--lve-6lad.not-web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.www.not-web-platform.test, DNS:www.xn--n8j6ds53lwwkrqhv28a.not-web-platform.test, DNS:www1.xn--n8j6ds53lwwkrqhv28a.not-web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.www2.not-web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.www1.not-web-platform.test, DNS:www2.xn--n8j6ds53lwwkrqhv28a.not-web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.xn--lve-6lad.web-platform.test, DNS:xn--lve-6lad.xn--n8j6ds53lwwkrqhv28a.web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.xn--lve-6lad.not-web-platform.test, DNS:xn--lve-6lad.xn--n8j6ds53lwwkrqhv28a.not-web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.xn--n8j6ds53lwwkrqhv28a.web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.xn--n8j6ds53lwwkrqhv28a.not-web-platform.test
+                DNS:web-platform.test, DNS:www.web-platform.test, DNS:not-web-platform.test, DNS:www1.web-platform.test, DNS:www2.web-platform.test, DNS:www.not-web-platform.test, DNS:www.www.web-platform.test, DNS:www.www2.web-platform.test, DNS:www2.www.web-platform.test, DNS:www.www1.web-platform.test, DNS:www2.not-web-platform.test, DNS:www1.www.web-platform.test, DNS:www1.not-web-platform.test, DNS:www2.www2.web-platform.test, DNS:www1.www1.web-platform.test, DNS:www1.www2.web-platform.test, DNS:www2.www1.web-platform.test, DNS:www.www.not-web-platform.test, DNS:www1.www.not-web-platform.test, DNS:www.www1.not-web-platform.test, DNS:www.www2.not-web-platform.test, DNS:xn--lve-6lad.web-platform.test, DNS:www2.www.not-web-platform.test, DNS:www1.www2.not-web-platform.test, DNS:www2.www2.not-web-platform.test, DNS:www2.www1.not-web-platform.test, DNS:www1.www1.not-web-platform.test, DNS:www.xn--lve-6lad.web-platform.test, DNS:xn--lve-6lad.www.web-platform.test, DNS:xn--lve-6lad.not-web-platform.test, DNS:www2.xn--lve-6lad.web-platform.test, DNS:xn--lve-6lad.www2.web-platform.test, DNS:www1.xn--lve-6lad.web-platform.test, DNS:xn--lve-6lad.www1.web-platform.test, DNS:xn--lve-6lad.www.not-web-platform.test, DNS:www.xn--lve-6lad.not-web-platform.test, DNS:www2.xn--lve-6lad.not-web-platform.test, DNS:www1.xn--lve-6lad.not-web-platform.test, DNS:xn--lve-6lad.www1.not-web-platform.test, DNS:xn--lve-6lad.www2.not-web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.web-platform.test, DNS:xn--lve-6lad.xn--lve-6lad.web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.www.web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.not-web-platform.test, DNS:www.xn--n8j6ds53lwwkrqhv28a.web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.www2.web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.www1.web-platform.test, DNS:www2.xn--n8j6ds53lwwkrqhv28a.web-platform.test, DNS:www1.xn--n8j6ds53lwwkrqhv28a.web-platform.test, DNS:xn--lve-6lad.xn--lve-6lad.not-web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.www.not-web-platform.test, DNS:www.xn--n8j6ds53lwwkrqhv28a.not-web-platform.test, DNS:www1.xn--n8j6ds53lwwkrqhv28a.not-web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.www2.not-web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.www1.not-web-platform.test, DNS:www2.xn--n8j6ds53lwwkrqhv28a.not-web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.xn--lve-6lad.web-platform.test, DNS:xn--lve-6lad.xn--n8j6ds53lwwkrqhv28a.web-platform.test, DNS:xn--lve-6lad.xn--n8j6ds53lwwkrqhv28a.not-web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.xn--lve-6lad.not-web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.xn--n8j6ds53lwwkrqhv28a.web-platform.test, DNS:xn--n8j6ds53lwwkrqhv28a.xn--n8j6ds53lwwkrqhv28a.not-web-platform.test
     Signature Algorithm: sha256WithRSAEncryption
-         5f:75:75:27:16:7c:d4:f8:7d:8d:09:54:98:3d:92:8f:a4:2f:
-         e4:69:cc:ae:a8:af:88:e9:d2:53:29:35:78:f8:38:a0:f9:7a:
-         19:65:01:ad:b4:83:2f:c5:4f:5e:a3:08:5a:fb:52:f1:a7:79:
-         8d:cc:18:42:4d:df:1f:e1:8a:8e:c3:75:ec:cc:66:40:1f:c5:
-         15:be:03:79:d2:7e:93:80:ef:75:5c:7f:71:eb:c5:45:0f:08:
-         82:5f:ca:ba:7b:56:98:b4:64:ff:54:43:9c:99:cb:31:5c:21:
-         9e:88:37:2e:41:59:e4:59:1a:0f:6d:05:58:84:e4:b9:c7:ed:
-         6c:f7:b3:75:bf:18:31:00:d6:9a:cf:60:07:74:cb:81:ec:d8:
-         a3:91:96:77:36:04:aa:93:2f:4d:28:6f:20:94:43:57:0c:83:
-         b5:26:e5:68:c0:2b:b9:20:49:34:10:bf:71:6d:78:ef:35:a0:
-         bf:df:1c:d7:29:ec:af:d8:ac:9b:f0:14:80:1b:03:dc:9c:d6:
-         25:29:ff:86:31:b5:55:c0:7b:6d:a7:73:02:74:06:ea:83:64:
-         f6:7f:3b:83:9b:08:36:cc:77:58:75:e5:4c:73:e5:bf:0e:fb:
-         f1:65:83:e9:a9:2b:db:71:93:61:a5:ce:46:aa:c8:34:01:e9:
-         a8:20:1d:09
+         89:74:5b:31:67:f5:72:96:7a:32:f5:b4:9e:d3:09:39:a5:a6:
+         8b:a6:51:8c:42:83:77:5a:b3:9f:92:a3:61:80:d8:3a:3c:98:
+         96:35:eb:bf:db:74:4e:a4:35:f1:af:cd:e0:19:ee:b1:b5:ea:
+         ff:ef:96:56:7f:68:70:1a:74:24:e7:66:1a:52:f2:66:48:6a:
+         15:f4:c1:a4:5e:09:ea:41:17:fc:71:1b:a6:63:2d:f6:ee:89:
+         83:96:0c:e4:61:e0:6e:c2:cd:cb:77:c2:e5:68:5d:c3:37:d2:
+         77:18:c9:1c:ee:95:3c:41:17:1e:bd:02:d9:85:e3:d4:18:d3:
+         47:0b:98:ed:c5:2e:ad:a9:24:16:5e:b4:42:66:c1:78:ce:1f:
+         01:39:d3:75:cd:78:cc:da:a0:58:e2:df:b7:38:8a:c2:66:46:
+         1c:9b:cb:15:10:b4:0c:b6:ef:c7:c3:18:bd:84:ec:6b:ef:9a:
+         86:35:96:f1:ba:53:ce:96:bf:12:bf:e1:ac:13:49:8c:87:0b:
+         c6:b8:b0:84:f1:ea:0c:4e:04:3f:51:4a:56:88:34:7d:f9:9b:
+         e3:dc:4b:fe:57:b3:ed:3d:4e:06:bc:0f:e7:bf:bf:bd:c6:43:
+         dc:47:aa:a0:34:df:7b:66:84:c2:32:54:16:f7:56:ee:68:7c:
+         cd:44:99:d9
 -----BEGIN CERTIFICATE-----
-MIIMsjCCC5qgAwIBAgIDBmgKMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMMEndl
-Yi1wbGF0Zm9ybS10ZXN0czAeFw0yMzA4MTIwMDQ1MzZaFw0yNDA4MTEwMDQ1MzZa
+MIIMsjCCC5qgAwIBAgIDCcblMA0GCSqGSIb3DQEBCwUAMB0xGzAZBgNVBAMMEndl
+Yi1wbGF0Zm9ybS10ZXN0czAeFw0yMzA5MTIwMDQ3MTlaFw0yNDA5MTEwMDQ3MTla
 MBwxGjAYBgNVBAMMEXdlYi1wbGF0Zm9ybS50ZXN0MIIBIjANBgkqhkiG9w0BAQEF
-AAOCAQ8AMIIBCgKCAQEAqPigUWpNcA8KYciJiJ3jDK/0A1l7gcSEJb5Q1XMxkd/6
-e96nE91DQXvmygzRXDroyf0vKEXQ7MJzwBcPMwYJqvfu5neSkmsF+noSz2rmz4Dq
-3b2eIexcs4NAtF9xK46umWXarCgZQyjtrK8bwd+vhX4LGTanmJ8IVMFChkIoT22X
-oXTNHpXGKU/Xy0ElP8F5FUrorTk3r1EDBWl/5rxIR4GHD1MsH9886hGiW5eLMHre
-CZnHzWWKYmi6M8FiQOuAQwRM7eqY7LEDMRQHSqFOgRHw1LyKYVwMJl+QyG+maJUf
-E6P2mzyBIJjIZJkaTIR7N+/k0cTlmnAWzdVUruN5awIDAQABo4IJ+jCCCfYwCQYD
-VR0TBAIwADAdBgNVHQ4EFgQUP5mijsryQCXnuHn6sm0J/1G5pY8wHwYDVR0jBBgw
-FoAUE/KJ3brt4RJXOwn4cNd5jiAJPXAwCwYDVR0PBAQDAgXgMBMGA1UdJQQMMAoG
+AAOCAQ8AMIIBCgKCAQEAtZxHZB54KgYjxI4YmvplXAe+6asgeF7kiHMEWsiAJ201
+/FjtQMANOMF8X8iW85E4JrNbcPQo7YODcdtEAzny3xbkC/8q5J5WPz3Pd11rwuTp
+W10fW9SJVntqEJakLZ/Tesn9ZnPE5TPVfQe5MwoxuRwTFEB0VpwabUZCdJJ/Dq34
+wsKPuvtlaVoPqjbjloof1XSY8ELFdwA8LkegLSIP/vOKhfAiZddRjtG5mYzm3GIy
+oQTlUVz8zKQUNLc1GuLkfbjGCCc3laNbQfLTaJ+bBIW8X58RyeIaVX3NYjWHra5V
+/j+ItGiCpi1XalcBuPAihEK//bxSzgMV/rQ1UjVMzQIDAQABo4IJ+jCCCfYwCQYD
+VR0TBAIwADAdBgNVHQ4EFgQUjGu0EgF43Ct4n1jsOABJEnrL/qowHwYDVR0jBBgw
+FoAUL85WFSdiBiigs5ykdVj2DYdZINgwCwYDVR0PBAQDAgXgMBMGA1UdJQQMMAoG
 CCsGAQUFBwMBMIIJhQYDVR0RBIIJfDCCCXiCEXdlYi1wbGF0Zm9ybS50ZXN0ghV3
 d3cud2ViLXBsYXRmb3JtLnRlc3SCFW5vdC13ZWItcGxhdGZvcm0udGVzdIIWd3d3
-Mi53ZWItcGxhdGZvcm0udGVzdIIWd3d3MS53ZWItcGxhdGZvcm0udGVzdIIZd3d3
-Lnd3dy53ZWItcGxhdGZvcm0udGVzdIIZd3d3Lm5vdC13ZWItcGxhdGZvcm0udGVz
-dIIad3d3Mi53d3cud2ViLXBsYXRmb3JtLnRlc3SCGnd3dzEud3d3LndlYi1wbGF0
-Zm9ybS50ZXN0ghp3d3cxLm5vdC13ZWItcGxhdGZvcm0udGVzdIIad3d3Lnd3dzEu
-d2ViLXBsYXRmb3JtLnRlc3SCGnd3dy53d3cyLndlYi1wbGF0Zm9ybS50ZXN0ghp3
-d3cyLm5vdC13ZWItcGxhdGZvcm0udGVzdIIbd3d3Mi53d3cxLndlYi1wbGF0Zm9y
-bS50ZXN0ght3d3cxLnd3dzIud2ViLXBsYXRmb3JtLnRlc3SCG3d3dzEud3d3MS53
-ZWItcGxhdGZvcm0udGVzdIIbd3d3Mi53d3cyLndlYi1wbGF0Zm9ybS50ZXN0gh13
-d3cud3d3Lm5vdC13ZWItcGxhdGZvcm0udGVzdIIed3d3Mi53d3cubm90LXdlYi1w
-bGF0Zm9ybS50ZXN0gh54bi0tbHZlLTZsYWQud2ViLXBsYXRmb3JtLnRlc3SCHnd3
-dy53d3cyLm5vdC13ZWItcGxhdGZvcm0udGVzdIIed3d3Lnd3dzEubm90LXdlYi1w
-bGF0Zm9ybS50ZXN0gh53d3cxLnd3dy5ub3Qtd2ViLXBsYXRmb3JtLnRlc3SCH3d3
-dzEud3d3MS5ub3Qtd2ViLXBsYXRmb3JtLnRlc3SCH3d3dzIud3d3Mi5ub3Qtd2Vi
-LXBsYXRmb3JtLnRlc3SCH3d3dzEud3d3Mi5ub3Qtd2ViLXBsYXRmb3JtLnRlc3SC
-H3d3dzIud3d3MS5ub3Qtd2ViLXBsYXRmb3JtLnRlc3SCInhuLS1sdmUtNmxhZC53
-d3cud2ViLXBsYXRmb3JtLnRlc3SCInhuLS1sdmUtNmxhZC5ub3Qtd2ViLXBsYXRm
-b3JtLnRlc3SCInd3dy54bi0tbHZlLTZsYWQud2ViLXBsYXRmb3JtLnRlc3SCI3hu
-LS1sdmUtNmxhZC53d3cyLndlYi1wbGF0Zm9ybS50ZXN0giN3d3cxLnhuLS1sdmUt
-NmxhZC53ZWItcGxhdGZvcm0udGVzdIIjeG4tLWx2ZS02bGFkLnd3dzEud2ViLXBs
-YXRmb3JtLnRlc3SCI3d3dzIueG4tLWx2ZS02bGFkLndlYi1wbGF0Zm9ybS50ZXN0
+MS53ZWItcGxhdGZvcm0udGVzdIIWd3d3Mi53ZWItcGxhdGZvcm0udGVzdIIZd3d3
+Lm5vdC13ZWItcGxhdGZvcm0udGVzdIIZd3d3Lnd3dy53ZWItcGxhdGZvcm0udGVz
+dIIad3d3Lnd3dzIud2ViLXBsYXRmb3JtLnRlc3SCGnd3dzIud3d3LndlYi1wbGF0
+Zm9ybS50ZXN0ghp3d3cud3d3MS53ZWItcGxhdGZvcm0udGVzdIIad3d3Mi5ub3Qt
+d2ViLXBsYXRmb3JtLnRlc3SCGnd3dzEud3d3LndlYi1wbGF0Zm9ybS50ZXN0ghp3
+d3cxLm5vdC13ZWItcGxhdGZvcm0udGVzdIIbd3d3Mi53d3cyLndlYi1wbGF0Zm9y
+bS50ZXN0ght3d3cxLnd3dzEud2ViLXBsYXRmb3JtLnRlc3SCG3d3dzEud3d3Mi53
+ZWItcGxhdGZvcm0udGVzdIIbd3d3Mi53d3cxLndlYi1wbGF0Zm9ybS50ZXN0gh13
+d3cud3d3Lm5vdC13ZWItcGxhdGZvcm0udGVzdIIed3d3MS53d3cubm90LXdlYi1w
+bGF0Zm9ybS50ZXN0gh53d3cud3d3MS5ub3Qtd2ViLXBsYXRmb3JtLnRlc3SCHnd3
+dy53d3cyLm5vdC13ZWItcGxhdGZvcm0udGVzdIIeeG4tLWx2ZS02bGFkLndlYi1w
+bGF0Zm9ybS50ZXN0gh53d3cyLnd3dy5ub3Qtd2ViLXBsYXRmb3JtLnRlc3SCH3d3
+dzEud3d3Mi5ub3Qtd2ViLXBsYXRmb3JtLnRlc3SCH3d3dzIud3d3Mi5ub3Qtd2Vi
+LXBsYXRmb3JtLnRlc3SCH3d3dzIud3d3MS5ub3Qtd2ViLXBsYXRmb3JtLnRlc3SC
+H3d3dzEud3d3MS5ub3Qtd2ViLXBsYXRmb3JtLnRlc3SCInd3dy54bi0tbHZlLTZs
+YWQud2ViLXBsYXRmb3JtLnRlc3SCInhuLS1sdmUtNmxhZC53d3cud2ViLXBsYXRm
+b3JtLnRlc3SCInhuLS1sdmUtNmxhZC5ub3Qtd2ViLXBsYXRmb3JtLnRlc3SCI3d3
+dzIueG4tLWx2ZS02bGFkLndlYi1wbGF0Zm9ybS50ZXN0giN4bi0tbHZlLTZsYWQu
+d3d3Mi53ZWItcGxhdGZvcm0udGVzdIIjd3d3MS54bi0tbHZlLTZsYWQud2ViLXBs
+YXRmb3JtLnRlc3SCI3huLS1sdmUtNmxhZC53d3cxLndlYi1wbGF0Zm9ybS50ZXN0
 giZ4bi0tbHZlLTZsYWQud3d3Lm5vdC13ZWItcGxhdGZvcm0udGVzdIImd3d3Lnhu
-LS1sdmUtNmxhZC5ub3Qtd2ViLXBsYXRmb3JtLnRlc3SCJ3huLS1sdmUtNmxhZC53
-d3cxLm5vdC13ZWItcGxhdGZvcm0udGVzdIIneG4tLWx2ZS02bGFkLnd3dzIubm90
-LXdlYi1wbGF0Zm9ybS50ZXN0gid3d3cxLnhuLS1sdmUtNmxhZC5ub3Qtd2ViLXBs
-YXRmb3JtLnRlc3SCJ3d3dzIueG4tLWx2ZS02bGFkLm5vdC13ZWItcGxhdGZvcm0u
+LS1sdmUtNmxhZC5ub3Qtd2ViLXBsYXRmb3JtLnRlc3SCJ3d3dzIueG4tLWx2ZS02
+bGFkLm5vdC13ZWItcGxhdGZvcm0udGVzdIInd3d3MS54bi0tbHZlLTZsYWQubm90
+LXdlYi1wbGF0Zm9ybS50ZXN0gid4bi0tbHZlLTZsYWQud3d3MS5ub3Qtd2ViLXBs
+YXRmb3JtLnRlc3SCJ3huLS1sdmUtNmxhZC53d3cyLm5vdC13ZWItcGxhdGZvcm0u
 dGVzdIIpeG4tLW44ajZkczUzbHd3a3JxaHYyOGEud2ViLXBsYXRmb3JtLnRlc3SC
 K3huLS1sdmUtNmxhZC54bi0tbHZlLTZsYWQud2ViLXBsYXRmb3JtLnRlc3SCLXhu
-LS1uOGo2ZHM1M2x3d2tycWh2MjhhLm5vdC13ZWItcGxhdGZvcm0udGVzdIItd3d3
-LnhuLS1uOGo2ZHM1M2x3d2tycWh2MjhhLndlYi1wbGF0Zm9ybS50ZXN0gi14bi0t
-bjhqNmRzNTNsd3drcnFodjI4YS53d3cud2ViLXBsYXRmb3JtLnRlc3SCLnhuLS1u
+LS1uOGo2ZHM1M2x3d2tycWh2MjhhLnd3dy53ZWItcGxhdGZvcm0udGVzdIIteG4t
+LW44ajZkczUzbHd3a3JxaHYyOGEubm90LXdlYi1wbGF0Zm9ybS50ZXN0gi13d3cu
+eG4tLW44ajZkczUzbHd3a3JxaHYyOGEud2ViLXBsYXRmb3JtLnRlc3SCLnhuLS1u
 OGo2ZHM1M2x3d2tycWh2MjhhLnd3dzIud2ViLXBsYXRmb3JtLnRlc3SCLnhuLS1u
-OGo2ZHM1M2x3d2tycWh2MjhhLnd3dzEud2ViLXBsYXRmb3JtLnRlc3SCLnd3dzEu
-eG4tLW44ajZkczUzbHd3a3JxaHYyOGEud2ViLXBsYXRmb3JtLnRlc3SCLnd3dzIu
+OGo2ZHM1M2x3d2tycWh2MjhhLnd3dzEud2ViLXBsYXRmb3JtLnRlc3SCLnd3dzIu
+eG4tLW44ajZkczUzbHd3a3JxaHYyOGEud2ViLXBsYXRmb3JtLnRlc3SCLnd3dzEu
 eG4tLW44ajZkczUzbHd3a3JxaHYyOGEud2ViLXBsYXRmb3JtLnRlc3SCL3huLS1s
 dmUtNmxhZC54bi0tbHZlLTZsYWQubm90LXdlYi1wbGF0Zm9ybS50ZXN0gjF4bi0t
 bjhqNmRzNTNsd3drcnFodjI4YS53d3cubm90LXdlYi1wbGF0Zm9ybS50ZXN0gjF3
@@ -118,16 +118,16 @@
 bGF0Zm9ybS50ZXN0gjJ3d3cyLnhuLS1uOGo2ZHM1M2x3d2tycWh2MjhhLm5vdC13
 ZWItcGxhdGZvcm0udGVzdII2eG4tLW44ajZkczUzbHd3a3JxaHYyOGEueG4tLWx2
 ZS02bGFkLndlYi1wbGF0Zm9ybS50ZXN0gjZ4bi0tbHZlLTZsYWQueG4tLW44ajZk
-czUzbHd3a3JxaHYyOGEud2ViLXBsYXRmb3JtLnRlc3SCOnhuLS1uOGo2ZHM1M2x3
-d2tycWh2MjhhLnhuLS1sdmUtNmxhZC5ub3Qtd2ViLXBsYXRmb3JtLnRlc3SCOnhu
-LS1sdmUtNmxhZC54bi0tbjhqNmRzNTNsd3drcnFodjI4YS5ub3Qtd2ViLXBsYXRm
+czUzbHd3a3JxaHYyOGEud2ViLXBsYXRmb3JtLnRlc3SCOnhuLS1sdmUtNmxhZC54
+bi0tbjhqNmRzNTNsd3drcnFodjI4YS5ub3Qtd2ViLXBsYXRmb3JtLnRlc3SCOnhu
+LS1uOGo2ZHM1M2x3d2tycWh2MjhhLnhuLS1sdmUtNmxhZC5ub3Qtd2ViLXBsYXRm
 b3JtLnRlc3SCQXhuLS1uOGo2ZHM1M2x3d2tycWh2MjhhLnhuLS1uOGo2ZHM1M2x3
 d2tycWh2MjhhLndlYi1wbGF0Zm9ybS50ZXN0gkV4bi0tbjhqNmRzNTNsd3drcnFo
 djI4YS54bi0tbjhqNmRzNTNsd3drcnFodjI4YS5ub3Qtd2ViLXBsYXRmb3JtLnRl
-c3QwDQYJKoZIhvcNAQELBQADggEBAF91dScWfNT4fY0JVJg9ko+kL+RpzK6or4jp
-0lMpNXj4OKD5ehllAa20gy/FT16jCFr7UvGneY3MGEJN3x/hio7DdezMZkAfxRW+
-A3nSfpOA73Vcf3HrxUUPCIJfyrp7Vpi0ZP9UQ5yZyzFcIZ6INy5BWeRZGg9tBViE
-5LnH7Wz3s3W/GDEA1prPYAd0y4Hs2KORlnc2BKqTL00obyCUQ1cMg7Um5WjAK7kg
-STQQv3FteO81oL/fHNcp7K/YrJvwFIAbA9yc1iUp/4YxtVXAe22ncwJ0BuqDZPZ/
-O4ObCDbMd1h15Uxz5b8O+/Flg+mpK9txk2GlzkaqyDQB6aggHQk=
+c3QwDQYJKoZIhvcNAQELBQADggEBAIl0WzFn9XKWejL1tJ7TCTmlpoumUYxCg3da
+s5+So2GA2Do8mJY167/bdE6kNfGvzeAZ7rG16v/vllZ/aHAadCTnZhpS8mZIahX0
+waReCepBF/xxG6ZjLfbuiYOWDORh4G7Czct3wuVoXcM30ncYyRzulTxBFx69AtmF
+49QY00cLmO3FLq2pJBZetEJmwXjOHwE503XNeMzaoFji37c4isJmRhybyxUQtAy2
+78fDGL2E7GvvmoY1lvG6U86WvxK/4awTSYyHC8a4sITx6gxOBD9RSlaINH35m+Pc
+S/5Xs+09Tga8D+e/v73GQ9xHqqA033tmhMIyVBb3Vu5ofM1Emdk=
 -----END CERTIFICATE-----
diff --git a/third_party/wpt_tools/wpt/tools/lint/lint.py b/third_party/wpt_tools/wpt/tools/lint/lint.py
index 00d7614..67cbddc 100644
--- a/third_party/wpt_tools/wpt/tools/lint/lint.py
+++ b/third_party/wpt_tools/wpt/tools/lint/lint.py
@@ -26,6 +26,7 @@
 from ..ci.tc.github_checks_output import get_gh_checks_outputter, GitHubChecksOutputter
 from ..gitignore.gitignore import PathFilter
 from ..wpt import testfiles
+from ..manifest.mputil import max_parallelism
 from ..manifest.vcs import walk
 
 from ..manifest.sourcefile import SourceFile, js_meta_re, python_meta_re, space_chars, get_any_variants
@@ -351,7 +352,8 @@
             rules.SpecialPowersRegexp,
             rules.AssertThrowsRegexp,
             rules.PromiseRejectsRegexp,
-            rules.AssertPreconditionRegexp]]
+            rules.AssertPreconditionRegexp,
+            rules.HTMLInvalidSyntaxRegexp]]
 
 
 def check_regexp_line(repo_root: Text, path: Text, f: IO[bytes]) -> List[rules.Error]:
@@ -877,12 +879,7 @@
     last = None
 
     if jobs == 0:
-        jobs = multiprocessing.cpu_count()
-        if sys.platform == 'win32':
-            # Using too many child processes in Python 3 hits either hangs or a
-            # ValueError exception, and, has diminishing returns. Clamp to 56 to
-            # give margin for error.
-            jobs = min(jobs, 56)
+        jobs = max_parallelism()
 
     with open(os.path.join(repo_root, "lint.ignore")) as f:
         ignorelist, skipped_files = parse_ignorelist(f)
diff --git a/third_party/wpt_tools/wpt/tools/lint/rules.py b/third_party/wpt_tools/wpt/tools/lint/rules.py
index 580ef800..06c4f57 100644
--- a/third_party/wpt_tools/wpt/tools/lint/rules.py
+++ b/third_party/wpt_tools/wpt/tools/lint/rules.py
@@ -502,3 +502,15 @@
     file_extensions = [".html", ".htm", ".js", ".xht", ".xhtml", ".svg"]
     description = "Test-file line has an `assert_precondition(...)` call"
     to_fix = """Replace with `assert_implements` or `assert_implements_optional`"""
+
+
+class HTMLInvalidSyntaxRegexp(Regexp):
+    pattern = (br"<(a|abbr|article|audio|b|bdi|bdo|blockquote|body|button|canvas|caption|cite|code|colgroup|data|datalist|dd|del|details|"
+               br"dfn|dialog|div|dl|dt|em|fieldset|figcaption|figure|footer|form|h[1-6]|head|header|html|i|iframe|ins|kbd|label|legend|li|"
+               br"main|map|mark|menu|meter|nav|noscript|object|ol|optgroup|option|output|p|picture|pre|progress|q|rp|rt|ruby|s|samp|script|"
+               br"search|section|select|slot|small|span|strong|style|sub|summary|sup|table|tbody|td|template|textarea|tfoot|th|thead|time|"
+               br"title|tr|u|ul|var|video)(\s+[^>]+)?\s*/>")
+    name = "HTML INVALID SYNTAX"
+    file_extensions = [".html", ".htm"]
+    description = "Test-file line has a non-void HTML tag with /> syntax"
+    to_fix = """Replace with start tag and end tag"""
diff --git a/third_party/wpt_tools/wpt/tools/manifest/manifest.py b/third_party/wpt_tools/wpt/tools/manifest/manifest.py
index 0b00c71ac..959978f 100644
--- a/third_party/wpt_tools/wpt/tools/manifest/manifest.py
+++ b/third_party/wpt_tools/wpt/tools/manifest/manifest.py
@@ -1,9 +1,8 @@
 import os
-import sys
 from atomicwrites import atomic_write
 from copy import deepcopy
 from logging import Logger
-from multiprocessing import Pool, cpu_count
+from multiprocessing import Pool
 from typing import (Any, Callable, Container, Dict, IO, Iterator, Iterable, Optional, Set, Text, Tuple, Type,
                     Union)
 
@@ -21,11 +20,11 @@
                    VisualTest,
                    WebDriverSpecTest)
 from .log import get_logger
+from .mputil import max_parallelism
 from .sourcefile import SourceFile
 from .typedata import TypeData
 
 
-
 CURRENT_VERSION: int = 8
 
 
@@ -206,24 +205,16 @@
         # 25 items was derived experimentally (2020-01) to be approximately the
         # point at which it is quicker to create a Pool and parallelize update.
         pool = None
-        if parallel and len(to_update) > 25 and cpu_count() > 1:
-            # On Python 3 on Windows, using >= MAXIMUM_WAIT_OBJECTS processes
-            # causes a crash in the multiprocessing module. Whilst this enum
-            # can technically have any value, it is usually 64. For safety,
-            # restrict manifest regeneration to 48 processes on Windows.
-            #
-            # See https://bugs.python.org/issue26903 and https://bugs.python.org/issue40263
-            processes = cpu_count()
-            if sys.platform == "win32" and processes > 48:
-                processes = 48
+        processes = max_parallelism()
+        if parallel and len(to_update) > 25 and processes > 1:
             pool = Pool(processes)
 
             # chunksize set > 1 when more than 10000 tests, because
             # chunking is a net-gain once we get to very large numbers
             # of items (again, experimentally, 2020-01)
             chunksize = max(1, len(to_update) // 10000)
-            logger.debug("Doing a multiprocessed update. CPU count: %s, "
-                "processes: %s, chunksize: %s" % (cpu_count(), processes, chunksize))
+            logger.debug("Doing a multiprocessed update. "
+                "Processes: %s, chunksize: %s" % (processes, chunksize))
             results: Iterator[Optional[Tuple[Tuple[Text, ...],
                                     Text,
                                     Set[ManifestItem], Text]]] = pool.imap_unordered(
diff --git a/third_party/wpt_tools/wpt/tools/manifest/mputil.py b/third_party/wpt_tools/wpt/tools/manifest/mputil.py
new file mode 100644
index 0000000..fc9d5ac
--- /dev/null
+++ b/third_party/wpt_tools/wpt/tools/manifest/mputil.py
@@ -0,0 +1,14 @@
+import multiprocessing
+import sys
+
+def max_parallelism() -> int:
+    cpu_count = multiprocessing.cpu_count()
+    if sys.platform == 'win32':
+        # On Python 3 on Windows, using >= MAXIMUM_WAIT_OBJECTS processes
+        # causes a crash in the multiprocessing module. Whilst this enum
+        # can technically have any value, it is usually 64. For safety,
+        # restrict manifest regeneration to 56 processes on Windows.
+        #
+        # See https://bugs.python.org/issue26903 and https://bugs.python.org/issue40263
+        cpu_count = min(cpu_count, 56)
+    return cpu_count
diff --git a/third_party/wpt_tools/wpt/tools/wpt/browser.py b/third_party/wpt_tools/wpt/tools/wpt/browser.py
index a8c0d16..442fd9a0 100644
--- a/third_party/wpt_tools/wpt/tools/wpt/browser.py
+++ b/third_party/wpt_tools/wpt/tools/wpt/browser.py
@@ -1596,25 +1596,53 @@
 
 
 class EdgeChromium(Browser):
-    """MicrosoftEdge-specific interface."""
+    """Microsoft Edge Chromium Browser class."""
+
+    product = "edgechromium"
+    requirements = "requirements_chromium.txt"
     platform = {
         "Linux": "linux",
         "Windows": "win",
         "Darwin": "macos"
     }.get(uname[0])
-    product = "edgechromium"
-    edgedriver_name = "msedgedriver"
-    requirements = "requirements_chromium.txt"
+
+    def _get_build_version(self, version):
+        """Convert a Edge/MSEdgeDriver version into MAJOR.MINOR.BUILD format."""
+        version_parts = version.split(".")
+        if len(version_parts) < 3:
+            self.logger.info(f"Version {version} could not be formatted for build matching.")
+            return None
+        return ".".join(version_parts[0:3])
+
+    def _remove_existing_edgedriver_binary(self, path):
+        """Remove an existing MSEdgeDriver for this product if it exists
+        in the virtual environment.
+        """
+        # There may be an existing MSEdgeDriver binary from a previous install.
+        # To provide a clean install experience, remove the old binary - this
+        # avoids tricky issues like unzipping over a read-only file.
+        existing_edgedriver_path = which("MSEdgeDriver", path=path)
+        if existing_edgedriver_path:
+            self.logger.info(f"Removing existing MSEdgeDriver binary: {existing_edgedriver_path}")
+            os.chmod(existing_edgedriver_path, stat.S_IWUSR)
+            os.remove(existing_edgedriver_path)
+        existing_driver_notes_path = os.path.join(path, "Driver_notes")
+        if os.path.isdir(existing_driver_notes_path):
+            self.logger.info(f"Removing existing MSEdgeDriver binary: {existing_driver_notes_path}")
+            print(f"Delete {existing_driver_notes_path} folder")
+            rmtree(existing_driver_notes_path)
 
     def download(self, dest=None, channel=None, rename=None):
         raise NotImplementedError
 
-    def install(self, dest=None, channel=None):
-        raise NotImplementedError
+    def install_mojojs(self, dest, browser_binary):
+        # TODO: Install MojoJS web framework.
+        # MojoJS is platform agnostic, but the version number must be an
+        # exact match of the Edge version to be compatible.
+        return None
 
     def find_binary(self, venv_path=None, channel=None):
-        self.logger.info(f'Finding Edge binary for channel {channel}')
-
+        # TODO: Check for binary in virtual environment first
         if self.platform == "linux":
             name = "microsoft-edge"
             if channel == "stable":
@@ -1631,117 +1659,100 @@
                 suffix = " " + channel.capitalize()
             return f"/Applications/Microsoft Edge{suffix}.app/Contents/MacOS/Microsoft Edge{suffix}"
         if self.platform == "win":
-            binaryname = "msedge"
-            if channel == "beta":
-                winpaths = [os.path.expandvars("$SYSTEMDRIVE\\Program Files\\Microsoft\\Edge Beta\\Application"),
-                            os.path.expandvars("$SYSTEMDRIVE\\Program Files (x86)\\Microsoft\\Edge Beta\\Application")]
-                return which(binaryname, path=os.pathsep.join(winpaths))
-            elif channel == "dev":
-                winpaths = [os.path.expandvars("$SYSTEMDRIVE\\Program Files\\Microsoft\\Edge Dev\\Application"),
-                            os.path.expandvars("$SYSTEMDRIVE\\Program Files (x86)\\Microsoft\\Edge Dev\\Application")]
-                return which(binaryname, path=os.pathsep.join(winpaths))
-            elif channel == "canary":
-                winpaths = [os.path.expanduser("~\\AppData\\Local\\Microsoft\\Edge\\Application"),
-                            os.path.expanduser("~\\AppData\\Local\\Microsoft\\Edge SxS\\Application")]
-                return which(binaryname, path=os.pathsep.join(winpaths))
-            else:
-                winpaths = [os.path.expandvars("$SYSTEMDRIVE\\Program Files\\Microsoft\\Edge\\Application"),
-                            os.path.expandvars("$SYSTEMDRIVE\\Program Files (x86)\\Microsoft\\Edge\\Application")]
-                return which(binaryname, path=os.pathsep.join(winpaths))
-
+            suffix = ""
+            if channel in ("beta", "dev"):
+                suffix = " " + channel.capitalize()
+            winpaths = [os.path.expandvars(fr"%PROGRAMFILES%\Microsoft\Edge{suffix}\Application"),
+                        os.path.expandvars(fr"%programfiles(x86)%\Microsoft\Edge{suffix}\Application")]
+            path = which("msedge.exe", path=os.pathsep.join(winpaths))
+            if channel == "canary":
+                path = os.path.expandvars(r"%LOCALAPPDATA%\Microsoft\Edge SxS\Application\msedge.exe")
+            return path
         self.logger.warning("Unable to find the browser binary.")
         return None
 
     def find_webdriver(self, venv_path=None, channel=None):
         return which("msedgedriver")
 
-    def webdriver_supports_browser(self, webdriver_binary, browser_binary):
-        edgedriver_version = self.webdriver_version(webdriver_binary)
-        if not edgedriver_version:
-            self.logger.warning(
-                f"Unable to get version for EdgeDriver {webdriver_binary}, rejecting it")
-            return False
+    def install(self, dest=None, channel=None):
+        raise NotImplementedError
 
-        browser_version = self.version(browser_binary)
-        if not browser_version:
-            # If we can't get the browser version, we just have to assume the
-            # EdgeDriver is good.
-            return True
-
-        # Check that the EdgeDriver version matches the Edge version.
-        edgedriver_major = int(edgedriver_version.split('.')[0])
-        browser_major = int(browser_version.split('.')[0])
-        if edgedriver_major != browser_major:
-            self.logger.warning(
-                f"EdgeDriver {edgedriver_version} does not match Edge {browser_version}")
-            return False
-        return True
-
-    def install_webdriver_by_version(self, version, dest=None):
+    def install_webdriver(self, dest=None, channel=None, browser_binary=None):
         if dest is None:
             dest = os.pwd
 
-        if self.platform == "linux":
-            bits = "linux64"
-            edgedriver_path = os.path.join(dest, self.edgedriver_name)
-        elif self.platform == "macos":
-            bits = "mac64"
-            edgedriver_path = os.path.join(dest, self.edgedriver_name)
-        else:
-            bits = "win64" if uname[4] == "x86_64" else "win32"
-            edgedriver_path = os.path.join(dest, f"{self.edgedriver_name}.exe")
-        url = f"https://msedgedriver.azureedge.net/{version}/edgedriver_{bits}.zip"
-
-        # cleanup existing Edge driver files to avoid access_denied errors when unzipping
-        if os.path.isfile(edgedriver_path):
-            # remove read-only attribute
-            os.chmod(edgedriver_path, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO)  # 0777
-            print(f"Delete {edgedriver_path} file")
-            os.remove(edgedriver_path)
-        driver_notes_path = os.path.join(dest, "Driver_notes")
-        if os.path.isdir(driver_notes_path):
-            print(f"Delete {driver_notes_path} folder")
-            rmtree(driver_notes_path)
-
-        self.logger.info(f"Downloading MSEdgeDriver from {url}")
-        unzip(get(url).raw, dest)
-        if os.path.isfile(edgedriver_path):
-            self.logger.info(f"Successfully downloaded MSEdgeDriver to {edgedriver_path}")
-        return which(self.edgedriver_name, path=dest)
-
-    def install_webdriver(self, dest=None, channel=None, browser_binary=None):
-        self.logger.info(f"Installing MSEdgeDriver for channel {channel}")
-
+        # Detect the browser version.
+        # The MSEdgeDriver that is installed will match this version.
         if browser_binary is None:
+            # If a browser binary path was not given, detect a valid path.
             browser_binary = self.find_binary(channel=channel)
-        else:
-            self.logger.info(f"Installing matching MSEdgeDriver for Edge binary at {browser_binary}")
+            # We need a browser to version match, so if a browser binary path
+            # was not given and cannot be detected, raise an error.
+            if browser_binary is None:
+                raise FileNotFoundError("No browser binary detected. "
+                                        "Cannot install MSEdgeDriver without a browser version.")
 
         version = self.version(browser_binary)
-
-        # If an exact version can't be found, use a suitable fallback based on
-        # the browser channel, if available.
         if version is None:
-            platforms = {
-                "linux": "LINUX",
-                "macos": "MACOS",
-                "win": "WINDOWS"
-            }
-            if channel is None:
-                channel = "dev"
-            platform = platforms[self.platform]
-            suffix = f"{channel.upper()}_{platform}"
-            version_url = f"https://msedgedriver.azureedge.net/LATEST_{suffix}"
-            version = get(version_url).text.strip()
+            raise ValueError(f"Unable to detect browser version from binary at {browser_binary}. "
+                             "Cannot install MSEdgeDriver without a valid version to match.")
 
-        return self.install_webdriver_by_version(version, dest)
+        edgedriver_path = self.install_webdriver_by_version(version, dest, channel)
+
+        return edgedriver_path
+
+    def install_webdriver_by_version(self, version, dest, channel):
+        self._remove_existing_edgedriver_binary(dest)
+
+        if self.platform == "linux":
+            bits = "linux64"
+        elif self.platform == "macos":
+            bits = "mac64"
+        elif self.platform == "win":
+            bits = "win64" if uname[4] == "x86_64" else "win32"
+
+        url = f"https://msedgedriver.azureedge.net/{version}/edgedriver_{bits}.zip"
+        self.logger.info(f"Downloading MSEdgeDriver from {url}")
+        unzip(get(url).raw, dest)
+        edgedriver_path = which("msedgedriver", path=dest)
+        assert edgedriver_path is not None
+        return edgedriver_path
+
+    def webdriver_supports_browser(self, webdriver_binary, browser_binary, browser_channel):
+        """Check that the browser binary and MSEdgeDriver versions are a valid match."""
+        browser_version = self.version(browser_binary)
+        edgedriver_version = self.webdriver_version(webdriver_binary)
+
+        if not edgedriver_version:
+            self.logger.warning("Unable to get version for MSEdgeDriver "
+                                f"{webdriver_binary}, rejecting it")
+            return False
+
+        if not browser_version:
+            # If we can't get the browser version, we just have to assume the
+            # MSEdgeDriver is good.
+            self.logger.warning("Unable to get version for the browser "
+                                f"{browser_binary}, assuming MSEdgeDriver is good.")
+            return True
+
+        # Check that the EdgeDriver version matches the Edge version.
+        browser_version = self._get_build_version(browser_version)
+        edgedriver_version = self._get_build_version(edgedriver_version)
+
+        # Edge and MSEdgeDriver versions should match on the same MAJOR.MINOR.BUILD version.
+        if browser_version is not None and browser_version != edgedriver_version:
+            self.logger.warning(
+                f"MSEdgeDriver {edgedriver_version} does not match Edge {browser_version}")
+            return False
+        return True
 
     def version(self, binary=None, webdriver_binary=None):
+        """Get version string from browser binary."""
         if not binary:
             self.logger.warning("No browser binary provided.")
             return None
 
-        if self.platform == "win":
+        if uname[0] == "Windows":
             return _get_fileversion(binary, self.logger)
 
         try:
@@ -1756,11 +1767,10 @@
         return m.group(1)
 
     def webdriver_version(self, webdriver_binary):
+        """Get version string from MSEdgeDriver binary."""
         if webdriver_binary is None:
             self.logger.warning("No valid webdriver supplied to detect version.")
             return None
-        if self.platform == "win":
-            return _get_fileversion(webdriver_binary, self.logger)
 
         try:
             version_string = call(webdriver_binary, "--version").strip()
diff --git a/third_party/wpt_tools/wpt/tools/wpt/run.py b/third_party/wpt_tools/wpt/tools/wpt/run.py
index c737c05..8419bd00 100644
--- a/third_party/wpt_tools/wpt/tools/wpt/run.py
+++ b/third_party/wpt_tools/wpt/tools/wpt/run.py
@@ -110,7 +110,8 @@
 def check_environ(product):
     if product not in ("android_weblayer", "android_webview", "chrome",
                        "chrome_android", "chrome_ios", "content_shell",
-                       "firefox", "firefox_android", "servo", "wktr"):
+                       "edgechromium", "firefox", "firefox_android", "servo",
+                       "wktr"):
         config_builder = serve.build_config(os.path.join(wpt_root, "config.json"))
         # Override the ports to avoid looking for free ports
         config_builder.ssl = {"type": "none"}
@@ -542,43 +543,69 @@
 class EdgeChromium(BrowserSetup):
     name = "MicrosoftEdge"
     browser_cls = browser.EdgeChromium
+    experimental_channels: ClassVar[Tuple[str, ...]] = ("dev", "canary")
 
     def setup_kwargs(self, kwargs):
         browser_channel = kwargs["browser_channel"]
         if kwargs["binary"] is None:
-            binary = self.browser.find_binary(channel=browser_channel)
+            binary = self.browser.find_binary(venv_path=self.venv.path, channel=browser_channel)
             if binary:
-                logger.info("Using Edge binary %s" % binary)
                 kwargs["binary"] = binary
             else:
-                raise WptrunError("Unable to locate Edge binary")
+                raise WptrunError(f"Unable to locate {self.name.capitalize()} binary")
+
+        if kwargs["mojojs_path"]:
+            kwargs["enable_mojojs"] = True
+            logger.info("--mojojs-path is provided, enabling MojoJS")
+        else:
+            path = self.browser.install_mojojs(dest=self.venv.path,
+                                               browser_binary=kwargs["binary"])
+            if path:
+                kwargs["mojojs_path"] = path
+                kwargs["enable_mojojs"] = True
+                logger.info(f"MojoJS enabled automatically (mojojs_path: {path})")
+            else:
+                kwargs["enable_mojojs"] = False
+                logger.info("MojoJS is disabled for this run.")
 
         if kwargs["webdriver_binary"] is None:
             webdriver_binary = None
             if not kwargs["install_webdriver"]:
-                webdriver_binary = self.browser.find_webdriver()
-                if (webdriver_binary and not self.browser.webdriver_supports_browser(
-                    webdriver_binary, kwargs["binary"])):
+                webdriver_binary = self.browser.find_webdriver(self.venv.bin_path)
+                if webdriver_binary and not self.browser.webdriver_supports_browser(
+                        webdriver_binary, kwargs["binary"], browser_channel):
                     webdriver_binary = None
 
             if webdriver_binary is None:
                 install = self.prompt_install("msedgedriver")
 
                 if install:
-                    logger.info("Downloading msedgedriver")
                     webdriver_binary = self.browser.install_webdriver(
                         dest=self.venv.bin_path,
-                        channel=browser_channel)
+                        channel=browser_channel,
+                        browser_binary=kwargs["binary"],
+                    )
             else:
                 logger.info("Using webdriver binary %s" % webdriver_binary)
 
             if webdriver_binary:
                 kwargs["webdriver_binary"] = webdriver_binary
             else:
-                raise WptrunError("Unable to locate or install msedgedriver binary")
-        if browser_channel in ("dev", "canary") and kwargs["enable_experimental"] is None:
-            logger.info("Automatically turning on experimental features for Edge Dev/Canary")
-            kwargs["enable_experimental"] = True
+                raise WptrunError("Unable to locate or install matching msedgedriver binary")
+        if browser_channel in self.experimental_channels:
+            # HACK(Hexcles): work around https://github.com/web-platform-tests/wpt/issues/16448
+            kwargs["webdriver_args"].append("--disable-build-check")
+            if kwargs["enable_experimental"] is None:
+                logger.info(
+                    "Automatically turning on experimental features for Microsoft Edge Dev/Canary")
+                kwargs["enable_experimental"] = True
+            if kwargs["enable_webtransport_h3"] is None:
+                # To start the WebTransport over HTTP/3 test server.
+                kwargs["enable_webtransport_h3"] = True
+        if os.getenv("TASKCLUSTER_ROOT_URL"):
+            # We are on Taskcluster, where our Docker container does not have
+            # enough capabilities to run Microsoft Edge with sandboxing. (gh-20133)
+            kwargs["binary_args"].append("--no-sandbox")
 
 
 class Edge(BrowserSetup):
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/requirements.txt b/third_party/wpt_tools/wpt/tools/wptrunner/requirements.txt
index 0b7ccba4..ffb46de 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/requirements.txt
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/requirements.txt
@@ -7,4 +7,4 @@
 pillow==9.5.0
 requests==2.31.0
 six==1.16.0
-urllib3==2.0.4
+urllib3==2.0.5
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/requirements_firefox.txt b/third_party/wpt_tools/wpt/tools/wptrunner/requirements_firefox.txt
index 0bb6812..3ba47314 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/requirements_firefox.txt
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/requirements_firefox.txt
@@ -3,7 +3,7 @@
 mozdevice==4.1.1
 mozinstall==2.1.0
 mozleak==0.2
-mozprofile==2.6.0
+mozprofile==2.6.1
 mozrunner==8.3.0
 mozversion==2.4.0
 psutil==5.9.5
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/base.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/base.py
index adf665c19..db344d2 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/base.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/base.py
@@ -342,7 +342,7 @@
         except OSError as e:
             if e.errno == errno.ENOENT:
                 raise OSError(
-                    "WebDriver executable not found: %s" % self.webdriver_binary)
+                    "WebDriver executable not found: %s" % self.webdriver_binary) from e
             raise
         self._output_handler.after_process_start(self._proc.pid)
 
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/chrome_spki_certs.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/chrome_spki_certs.py
index 380f0848..0f7825c 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/chrome_spki_certs.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/chrome_spki_certs.py
@@ -2,7 +2,7 @@
 # DO NOT EDIT MANUALLY.
 
 # tools/certs/web-platform.test.pem
-WPT_FINGERPRINT = 'Be2q39hn/OSyGxIOIDwkKYzGmG+Z82YfIX9bvqg0lwY='
+WPT_FINGERPRINT = 'sCJ8962Wxqgz44IKoPQLcDT7YRRAxO2w1iYIqpMYHhg='
 
 # signed-exchange/resources/127.0.0.1.sxg.pem
 SXG_WPT_FINGERPRINT = '0Rt4mT6SJXojEMHTnKnlJ/hBKMBcI4kteBlhR1eTTdk='
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/edgechromium.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/edgechromium.py
index df7c41a..4f5bffa 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/edgechromium.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/edgechromium.py
@@ -1,24 +1,30 @@
 # mypy: allow-untyped-defs
-
-from .base import cmd_arg, require_arg
-from .base import WebDriverBrowser
+from .base import WebDriverBrowser, require_arg
 from .base import get_timeout_multiplier   # noqa: F401
+from .base import cmd_arg
+from .chrome import executor_kwargs as chrome_executor_kwargs
+from ..executors.executorwebdriver import WebDriverCrashtestExecutor  # noqa: F401
 from ..executors.base import WdspecExecutor  # noqa: F401
-from ..executors import executor_kwargs as base_executor_kwargs
-from ..executors.executorwebdriver import (WebDriverTestharnessExecutor,  # noqa: F401
-                                           WebDriverRefTestExecutor)  # noqa: F401
+from ..executors.executoredge import (  # noqa: F401
+    EdgeChromiumDriverPrintRefTestExecutor,
+    EdgeChromiumDriverRefTestExecutor,
+    EdgeChromiumDriverTestharnessExecutor,
+)
 
 
 __wptrunner__ = {"product": "edgechromium",
                  "check_args": "check_args",
                  "browser": "EdgeChromiumBrowser",
-                 "executor": {"testharness": "WebDriverTestharnessExecutor",
-                              "reftest": "WebDriverRefTestExecutor",
-                              "wdspec": "WdspecExecutor"},
+                 "executor": {"testharness": "EdgeChromiumDriverTestharnessExecutor",
+                              "reftest": "EdgeChromiumDriverRefTestExecutor",
+                              "print-reftest": "EdgeChromiumDriverPrintRefTestExecutor",
+                              "wdspec": "WdspecExecutor",
+                              "crashtest": "WebDriverCrashtestExecutor"},
                  "browser_kwargs": "browser_kwargs",
                  "executor_kwargs": "executor_kwargs",
                  "env_extras": "env_extras",
                  "env_options": "env_options",
+                 "update_properties": "update_properties",
                  "timeout_multiplier": "get_timeout_multiplier",}
 
 
@@ -34,44 +40,9 @@
 
 def executor_kwargs(logger, test_type, test_environment, run_info_data,
                     **kwargs):
-    executor_kwargs = base_executor_kwargs(test_type,
-                                           test_environment,
-                                           run_info_data,
-                                           **kwargs)
-    executor_kwargs["close_after_done"] = True
-
-    capabilities = {
-        "ms:edgeOptions": {
-            "prefs": {
-                "profile": {
-                    "default_content_setting_values": {
-                        "popups": 1
-                    }
-                }
-            },
-            "useAutomationExtension": False,
-            "excludeSwitches": ["enable-automation"],
-            "w3c": True
-        }
-    }
-
-    for (kwarg, capability) in [("binary", "binary"), ("binary_args", "args")]:
-        if kwargs[kwarg] is not None:
-            capabilities["ms:edgeOptions"][capability] = kwargs[kwarg]
-
-    if kwargs["headless"]:
-        if "args" not in capabilities["ms:edgeOptions"]:
-            capabilities["ms:edgeOptions"]["args"] = []
-        if "--headless" not in capabilities["ms:edgeOptions"]["args"]:
-            capabilities["ms:edgeOptions"]["args"].append("--headless")
-        capabilities["ms:edgeOptions"]["args"].append("--use-fake-device-for-media-stream")
-        capabilities["ms:edgeOptions"]["args"].append("--use-fake-ui-for-fedcm")
-
-    if kwargs["enable_experimental"]:
-        capabilities["ms:edgeOptions"]["args"].append("--enable-experimental-web-platform-features")
-
-    executor_kwargs["capabilities"] = capabilities
-
+    executor_kwargs = chrome_executor_kwargs(logger, test_type, test_environment, run_info_data, **kwargs)
+    capabilities = executor_kwargs["capabilities"]
+    capabilities["ms:edgeOptions"] = capabilities.pop("goog:chromeOptions")
     return executor_kwargs
 
 
@@ -80,7 +51,11 @@
 
 
 def env_options():
-    return {}
+    return {"server_host": "127.0.0.1"}
+
+
+def update_properties():
+    return (["debug", "os", "processor"], {"os": ["version"], "processor": ["bits"]})
 
 
 class EdgeChromiumBrowser(WebDriverBrowser):
@@ -91,4 +66,5 @@
     def make_command(self):
         return [self.webdriver_binary,
                 cmd_arg("port", str(self.port)),
-                cmd_arg("url-base", self.base_path)] + self.webdriver_args
+                cmd_arg("url-base", self.base_path),
+                cmd_arg("enable-edge-logs")] + self.webdriver_args
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/safari.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/safari.py
index f7dcd55..40f5f548 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/safari.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/browsers/safari.py
@@ -212,3 +212,7 @@
                         proc.wait(10)
                 except psutil.NoSuchProcess:
                     pass
+                except Exception:
+                    # Safari is a singleton, so treat failure here as a critical error.
+                    self.logger.critical("Failed to stop Safari")
+                    raise
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/environment.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/environment.py
index 24c88aa..de96f79 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/environment.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/environment.py
@@ -264,7 +264,7 @@
 
     def ensure_started(self):
         # Pause for a while to ensure that the server has a chance to start
-        total_sleep_secs = 30
+        total_sleep_secs = 60
         each_sleep_secs = 0.5
         end_time = time.time() + total_sleep_secs
         while time.time() < end_time:
@@ -274,8 +274,13 @@
             if not pending:
                 return
             time.sleep(each_sleep_secs)
-        raise OSError("Servers failed to start: %s" %
-                      ", ".join("%s:%s" % item for item in failed))
+        if failed:
+            failures = ", ".join(f"{scheme}:{port}" for scheme, port in failed)
+            msg = f"Servers failed to start: {failures}"
+        else:
+            pending = ", ".join(f"{scheme}:{port}" for scheme, port in pending)
+            msg = f"Timed out wait for servers to start: {pending}"
+        raise OSError(msg)
 
     def test_servers(self):
         failed = []
@@ -298,7 +303,7 @@
                     try:
                         s.connect((host, port))
                     except OSError:
-                        pending.append((host, port))
+                        pending.append((scheme, port))
                     finally:
                         s.close()
 
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/base.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/base.py
index fa8519b..e3e13576 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/base.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/base.py
@@ -304,7 +304,8 @@
             result = self.do_test(test)
         except Exception as e:
             exception_string = traceback.format_exc()
-            self.logger.warning(exception_string)
+            message = f"Exception in TextExecutor.run:\n{exception_string}"
+            self.logger.warning(message)
             result = self.result_from_exception(test, e, exception_string)
 
         # log result of parent test
@@ -731,8 +732,8 @@
         self.logger.debug("Got async callback: %s" % result[1])
         try:
             callback = self.callbacks[command]
-        except KeyError:
-            raise ValueError("Unknown callback type %r" % result[1])
+        except KeyError as e:
+            raise ValueError("Unknown callback type %r" % result[1]) from e
         return callback(url, payload)
 
     def process_complete(self, url, payload):
@@ -745,8 +746,8 @@
         self.logger.debug(f"Got action: {action}")
         try:
             action_handler = self.actions[action]
-        except KeyError:
-            raise ValueError(f"Unknown action {action}")
+        except KeyError as e:
+            raise ValueError(f"Unknown action {action}") from e
         try:
             with ActionContext(self.logger, self.protocol, payload.get("context")):
                 result = action_handler(payload)
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executorchrome.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executorchrome.py
index 5df9de5..c3c4bebb 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executorchrome.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executorchrome.py
@@ -79,6 +79,8 @@
         # exposed to the base WebDriver testharness executor.
         self.test_window = None
         self.reuse_window = self.parent.reuse_window
+        # Company prefix to apply to vendor-specific WebDriver extension commands.
+        self.cdp_company_prefix = "goog"
 
     def close_test_window(self):
         if self.test_window:
@@ -107,7 +109,8 @@
                     "cmd": "Page.resetNavigationHistory",
                     "params": {},
                 }
-                self.webdriver.send_session_command("POST", "goog/cdp/execute",
+                self.webdriver.send_session_command("POST",
+                                                    self.cdp_company_prefix + "/cdp/execute",
                                                     body=body)
                 self.webdriver.url = "about:blank"
                 return
@@ -135,6 +138,8 @@
     def setup(self):
         self.webdriver = self.parent.webdriver
         self.runner_handle = None
+        # Company prefix to apply to vendor-specific WebDriver extension commands.
+        self.cdp_company_prefix = "goog"
 
     def load_runner(self):
         url = urljoin(self.parent.executor.server_url("http"), "/print_pdf_runner.html")
@@ -166,7 +171,9 @@
                 "printBackground": True,
             }
         }
-        return self.webdriver.send_session_command("POST", "goog/cdp/execute", body=body)["data"]
+        return self.webdriver.send_session_command("POST",
+                                                   self.cdp_company_prefix + "/cdp/execute",
+                                                   body=body)["data"]
 
     def pdf_to_png(self, pdf_base64, ranges):
         handle = self.webdriver.window_handle
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executorcontentshell.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executorcontentshell.py
index 6928809..5c4068c 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executorcontentshell.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executorcontentshell.py
@@ -33,8 +33,8 @@
             raise CrashError()
         if raise_crash_leak and line.startswith(b"#LEAK"):
             raise LeakError()
-    except Empty:
-        raise TimeoutError()
+    except Empty as e:
+        raise TimeoutError() from e
 
     return line.decode(encoding, errors) if encoding else line
 
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executoredge.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executoredge.py
new file mode 100644
index 0000000..810fecb8
--- /dev/null
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executoredge.py
@@ -0,0 +1,101 @@
+# mypy: allow-untyped-defs
+
+import os
+
+from .executorwebdriver import (
+    WebDriverCrashtestExecutor,
+    WebDriverProtocol,
+    WebDriverRefTestExecutor,
+    WebDriverRun,
+    WebDriverTestharnessExecutor,
+)
+
+from .executorchrome import (
+    ChromeDriverPrintProtocolPart,
+    ChromeDriverTestharnessProtocolPart,
+    make_sanitizer_mixin,
+)
+
+here = os.path.dirname(__file__)
+
+_SanitizerMixin = make_sanitizer_mixin(WebDriverCrashtestExecutor)
+
+class EdgeChromiumDriverTestharnessProtocolPart(ChromeDriverTestharnessProtocolPart):
+    def setup(self):
+        super().setup()
+        self.cdp_company_prefix = "ms"
+
+
+class EdgeChromiumDriverPrintProtocolPart(ChromeDriverPrintProtocolPart):
+    def setup(self):
+        super().setup()
+        self.cdp_company_prefix = "ms"
+
+
+class EdgeChromiumDriverProtocol(WebDriverProtocol):
+    implements = [
+        EdgeChromiumDriverPrintProtocolPart,
+        EdgeChromiumDriverTestharnessProtocolPart,
+        *(part for part in WebDriverProtocol.implements
+          if part.name != EdgeChromiumDriverTestharnessProtocolPart.name)
+    ]
+    reuse_window = False
+
+
+class EdgeChromiumDriverRefTestExecutor(WebDriverRefTestExecutor, _SanitizerMixin):  # type: ignore
+    protocol_cls = EdgeChromiumDriverProtocol
+
+
+class EdgeChromiumDriverTestharnessExecutor(WebDriverTestharnessExecutor, _SanitizerMixin):  # type: ignore
+    protocol_cls = EdgeChromiumDriverProtocol
+
+    def __init__(self, *args, reuse_window=False, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.protocol.reuse_window = reuse_window
+
+
+class EdgeChromiumDriverPrintRefTestExecutor(EdgeChromiumDriverRefTestExecutor):
+    protocol_cls = EdgeChromiumDriverProtocol
+
+    def setup(self, runner):
+        super().setup(runner)
+        self.protocol.pdf_print.load_runner()
+        self.has_window = False
+        with open(os.path.join(here, "reftest.js")) as f:
+            self.script = f.read()
+
+    def screenshot(self, test, viewport_size, dpi, page_ranges):
+        # https://github.com/web-platform-tests/wpt/issues/7140
+        assert dpi is None
+
+        if not self.has_window:
+            self.protocol.base.execute_script(self.script)
+            self.protocol.base.set_window(self.protocol.webdriver.handles[-1])
+            self.has_window = True
+
+        self.viewport_size = viewport_size
+        self.page_ranges = page_ranges.get(test.url)
+        timeout = self.timeout_multiplier * test.timeout if self.debug_info is None else None
+
+        test_url = self.test_url(test)
+
+        return WebDriverRun(self.logger,
+                            self._render,
+                            self.protocol,
+                            test_url,
+                            timeout,
+                            self.extra_timeout).run()
+
+    def _render(self, protocol, url, timeout):
+        protocol.webdriver.url = url
+
+        protocol.base.execute_script(self.wait_script, asynchronous=True)
+
+        pdf = protocol.pdf_print.render_as_pdf(*self.viewport_size)
+        screenshots = protocol.pdf_print.pdf_to_png(pdf, self.page_ranges)
+        for i, screenshot in enumerate(screenshots):
+            # strip off the data:img/png, part of the url
+            if screenshot.startswith("data:image/png;base64,"):
+                screenshots[i] = screenshot.split(",", 1)[1]
+
+        return screenshots
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executormarionette.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executormarionette.py
index a3b56fe0..eaeaddf 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executormarionette.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executormarionette.py
@@ -387,7 +387,7 @@
             let principal = ssm.createContentPrincipal(uri, {});
             let qms = Components.classes["@mozilla.org/dom/quota-manager-service;1"]
                                 .getService(Components.interfaces.nsIQuotaManagerService);
-            qms.clearStoragesForPrincipal(principal, "default", null, true);
+            qms.clearStoragesForOriginPrefix(principal, "default");
             """ % url
         with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
             self.marionette.execute_script(script)
@@ -626,8 +626,8 @@
         }
         try:
             self.marionette._send_message("WebDriver:SetPermission", body)
-        except errors.UnsupportedOperationException:
-            raise NotImplementedError("set_permission not yet implemented")
+        except errors.UnsupportedOperationException as e:
+            raise NotImplementedError("set_permission not yet implemented") from e
 
 
 class MarionettePrintProtocolPart(PrintProtocolPart):
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executorwebdriver.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executorwebdriver.py
index b8e60ad3..ff174a4 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executorwebdriver.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/executorwebdriver.py
@@ -91,7 +91,9 @@
             except (socket.timeout, error.NoSuchWindowException, error.UnknownErrorException, OSError):
                 break
             except Exception:
-                self.logger.error(traceback.format_exc())
+                message = "Uncaught exception in WebDriverBaseProtocolPart.wait:\n"
+                message += traceback.format_exc()
+                self.logger.error(message)
                 break
         return False
 
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/protocol.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/protocol.py
index 170c27cd..3f999cf 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/protocol.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/executors/protocol.py
@@ -70,9 +70,10 @@
             msg = "Post-connection steps failed"
             self.after_connect()
         except Exception:
-            if msg is not None:
-                self.logger.warning(msg)
-            self.logger.warning(traceback.format_exc())
+            message = "Protocol.setup caught an exception:\n"
+            message += f"{msg}\n" if msg is not None else ""
+            message += traceback.format_exc()
+            self.logger.warning(message)
             raise
 
     @abstractmethod
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testloader.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testloader.py
index 9b55ad9..6cb21103 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testloader.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testloader.py
@@ -149,15 +149,17 @@
 
 
 def update_include_for_groups(test_groups, include):
+    new_include = []
     if include is None:
         # We're just running everything
-        return
-    new_include = []
-    for item in include:
-        if item in test_groups.tests_by_group:
-            new_include.extend(test_groups.tests_by_group[item])
-        else:
-            new_include.append(item)
+        for tests in test_groups.tests_by_group.values():
+            new_include.extend(tests)
+    else:
+        for item in include:
+            if item in test_groups.tests_by_group:
+                new_include.extend(test_groups.tests_by_group[item])
+            else:
+                new_include.append(item)
     return new_include
 
 
@@ -480,7 +482,9 @@
     test_source_kwargs = {"processes": kwargs["processes"],
                           "logger": kwargs["logger"]}
     chunker_kwargs = {}
-    if kwargs["run_by_dir"] is not False:
+    if kwargs["fully_parallel"]:
+        test_source_cls = FullyParallelGroupedSource
+    elif kwargs["run_by_dir"] is not False:
         # A value of None indicates infinite depth
         test_source_cls = PathGroupedSource
         test_source_kwargs["depth"] = kwargs["run_by_dir"]
@@ -587,11 +591,11 @@
 
     @classmethod
     def tests_by_group(cls, tests_by_type, **kwargs):
-        rv = {}
+        groups = defaultdict(list)
         for (subsuite, test_type), tests in tests_by_type.items():
             group_name = f"{subsuite}:{cls.group_metadata(None)['scope']}"
-            rv[group_name] = [t.id for t in tests]
-        return rv
+            groups[group_name].extend(test.id for test in tests)
+        return groups
 
 
 class PathGroupedSource(TestSource):
@@ -638,6 +642,17 @@
         return {"scope": "/%s" % "/".join(state["prev_group_key"][2])}
 
 
+class FullyParallelGroupedSource(PathGroupedSource):
+    # Chuck every test into a different group, so that they can run
+    # fully parallel with each other. Useful to run a lot of tests
+    # clustered in a few directories.
+    @classmethod
+    def new_group(cls, state, subsuite, test_type, test, **kwargs):
+        path = urlsplit(test.url).path.split("/")[1:-1]
+        state["prev_group_key"] = (subsuite, test_type, path)
+        return True
+
+
 class GroupFileTestSource(TestSource):
     @classmethod
     def make_groups(cls, tests_by_type, **kwargs):
@@ -658,7 +673,6 @@
 
     @classmethod
     def tests_by_group(cls, tests_by_type, **kwargs):
-        logger = kwargs["logger"]
         test_groups = kwargs["test_groups"]
 
         tests_by_group = defaultdict(list)
@@ -667,7 +681,7 @@
                 try:
                     group = test_groups.group_by_test[(subsuite, test.id)]
                 except KeyError:
-                    logger.error("%s is missing from test groups file" % test.id)
+                    print(f"{test.id} is missing from test groups file")
                     raise
                 if subsuite:
                     group_name = f"{subsuite}:{group}"
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testrunner.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testrunner.py
index fbaaf0789..13aecf2 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testrunner.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/testrunner.py
@@ -120,7 +120,9 @@
         try:
             return self.executor.run_test(test)
         except Exception:
-            self.logger.error(traceback.format_exc())
+            message = "TestRunner.run_test caught an exception:\n"
+            message += traceback.format_exc()
+            self.logger.error(message)
             raise
 
     def wait(self):
@@ -211,10 +213,9 @@
             self.browser.start(group_metadata=group_metadata, **self.browser_settings)
             self.browser_pid = self.browser.pid
         except Exception:
-            self.logger.warning("Failure during init %s" % traceback.format_exc())
+            self.logger.error(f"Failure during init:\n{traceback.format_exc()}")
             if self.init_timer is not None:
                 self.init_timer.cancel()
-            self.logger.error(traceback.format_exc())
             succeeded = False
         else:
             succeeded = True
@@ -399,7 +400,9 @@
                 self.state = new_state
                 self.logger.debug(f"new state: {self.state.__class__.__name__}")
         except Exception:
-            self.logger.error(traceback.format_exc())
+            message = "Uncaught exception in TestRunnerManager.run:\n"
+            message += traceback.format_exc()
+            self.logger.error(message)
             raise
         finally:
             self.logger.debug("TestRunnerManager main loop terminating, starting cleanup")
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/update/state.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/update/state.py
index 2c23ad6..e187d411 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/update/state.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/update/state.py
@@ -63,8 +63,8 @@
             raise AttributeError
         try:
             return self._data[self._index][key]
-        except KeyError:
-            raise AttributeError
+        except KeyError as e:
+            raise AttributeError from e
 
     def __contains__(self, key):
         return key in self._data[self._index]
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptcommandline.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptcommandline.py
index 4b644ac..29a0a9e2 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptcommandline.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptcommandline.py
@@ -11,6 +11,7 @@
 from . import products
 from . import wpttest
 from .formatters import chromium, wptreport, wptscreenshot
+from manifest import mputil  # type: ignore
 
 def abs_path(path):
     return os.path.abspath(os.path.expanduser(path))
@@ -61,6 +62,8 @@
                         help="Split run into groups by directories. With a parameter,"
                         "limit the depth of splits e.g. --run-by-dir=1 to split by top-level"
                         "directory")
+    parser.add_argument("-f", "--fully-parallel", action='store_true',
+                        help='Run every test in a separate group for fully parallelism.')
     parser.add_argument("--processes", action="store", type=int, default=None,
                         help="Number of simultaneous processes to use")
     parser.add_argument("--max-restarts", action="store", type=int, default=5,
@@ -585,13 +588,18 @@
         else:
             kwargs["chunk_type"] = "none"
 
-    if kwargs["test_groups_file"] is not None:
-        if kwargs["run_by_dir"] is not False:
-            print("Can't pass --test-groups and --run-by-dir")
-            sys.exit(1)
-        if not os.path.exists(kwargs["test_groups_file"]):
-            print("--test-groups file %s not found" % kwargs["test_groups_file"])
-            sys.exit(1)
+    if sum([
+        kwargs["test_groups_file"] is not None,
+        kwargs["run_by_dir"] is not False,
+        kwargs["fully_parallel"],
+    ]) > 1:
+        print('Must pass up to one of: --test-groups, --run-by-dir, --fully-parallel')
+        sys.exit(1)
+
+    if (kwargs["test_groups_file"] is not None and
+        not os.path.exists(kwargs["test_groups_file"])):
+        print("--test-groups file %s not found" % kwargs["test_groups_file"])
+        sys.exit(1)
 
     # When running on Android, the number of workers is decided by the number of
     # emulators. Each worker will use one emulator to run the Android browser.
@@ -606,7 +614,7 @@
             sys.exit(1)
 
     if kwargs["processes"] is None:
-        kwargs["processes"] = 1
+        kwargs["processes"] = mputil.max_parallelism() if kwargs["fully_parallel"] else 1
 
     if kwargs["debugger"] is not None:
         import mozdebug
diff --git a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptrunner.py b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptrunner.py
index 18a1fcc3..8badb13 100644
--- a/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptrunner.py
+++ b/third_party/wpt_tools/wpt/tools/wptrunner/wptrunner/wptrunner.py
@@ -66,7 +66,8 @@
 
     if kwargs["test_groups_file"] is not None:
         test_groups = testloader.TestGroups(logger,
-                                            kwargs["test_groups_file"])
+                                            kwargs["test_groups_file"],
+                                            subsuites)
     else:
         test_groups = None
 
@@ -194,7 +195,7 @@
                 tests_by_type[(subsuite_name, test_type)].extend(type_tests_active)
                 tests_by_type[(subsuite_name, test_type)].extend(type_tests_disabled)
 
-    tests_by_group = test_source.cls.tests_by_group(tests_by_type, **kwargs)
+    tests_by_group = test_source.cls.tests_by_group(tests_by_type, **test_source.kwargs)
 
     log_suite_start(tests_by_group,
                     test_loader.base_run_info,