summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/cookies
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/cookies
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/cookies')
-rw-r--r--testing/web-platform/tests/cookies/META.yml2
-rw-r--r--testing/web-platform/tests/cookies/README.md2
-rw-r--r--testing/web-platform/tests/cookies/__init__.py0
-rw-r--r--testing/web-platform/tests/cookies/attributes/attributes-ctl.sub.html99
-rw-r--r--testing/web-platform/tests/cookies/attributes/domain.sub.html24
-rw-r--r--testing/web-platform/tests/cookies/attributes/expires.html56
-rw-r--r--testing/web-platform/tests/cookies/attributes/invalid.html171
-rw-r--r--testing/web-platform/tests/cookies/attributes/max-age.html78
-rw-r--r--testing/web-platform/tests/cookies/attributes/path-redirect.html128
-rw-r--r--testing/web-platform/tests/cookies/attributes/path.html144
-rw-r--r--testing/web-platform/tests/cookies/attributes/resources/domain-child.sub.html401
-rw-r--r--testing/web-platform/tests/cookies/attributes/resources/path-redirect-shared.js11
-rw-r--r--testing/web-platform/tests/cookies/attributes/resources/path.html14
-rw-r--r--testing/web-platform/tests/cookies/attributes/resources/path.html.headers1
-rw-r--r--testing/web-platform/tests/cookies/attributes/resources/path/one.html14
-rw-r--r--testing/web-platform/tests/cookies/attributes/resources/path/three.html14
-rw-r--r--testing/web-platform/tests/cookies/attributes/resources/path/two.html14
-rw-r--r--testing/web-platform/tests/cookies/attributes/resources/pathfakeout.html14
-rw-r--r--testing/web-platform/tests/cookies/attributes/resources/pathfakeout/one.html14
-rw-r--r--testing/web-platform/tests/cookies/attributes/resources/secure-non-secure-child.html85
-rw-r--r--testing/web-platform/tests/cookies/attributes/secure-non-secure.html23
-rw-r--r--testing/web-platform/tests/cookies/attributes/secure.https.html65
-rw-r--r--testing/web-platform/tests/cookies/cookie-enabled-noncookie-frame.html30
-rw-r--r--testing/web-platform/tests/cookies/domain/domain-attribute-host-with-and-without-leading-period.sub.https.html40
-rw-r--r--testing/web-platform/tests/cookies/domain/domain-attribute-host-with-and-without-leading-period.sub.https.html.sub.headers2
-rw-r--r--testing/web-platform/tests/cookies/domain/domain-attribute-host-with-leading-period.sub.https.html39
-rw-r--r--testing/web-platform/tests/cookies/domain/domain-attribute-host-with-leading-period.sub.https.html.sub.headers1
-rw-r--r--testing/web-platform/tests/cookies/domain/domain-attribute-idn-host.sub.https.html18
-rw-r--r--testing/web-platform/tests/cookies/domain/domain-attribute-matches-host.sub.https.html39
-rw-r--r--testing/web-platform/tests/cookies/domain/domain-attribute-matches-host.sub.https.html.sub.headers1
-rw-r--r--testing/web-platform/tests/cookies/domain/domain-attribute-missing.sub.html39
-rw-r--r--testing/web-platform/tests/cookies/domain/domain-attribute-missing.sub.html.headers1
-rw-r--r--testing/web-platform/tests/cookies/domain/support/idn-child.sub.https.html72
-rw-r--r--testing/web-platform/tests/cookies/domain/support/idn.py61
-rw-r--r--testing/web-platform/tests/cookies/encoding/charset.html55
-rw-r--r--testing/web-platform/tests/cookies/meta-blocked.html13
-rw-r--r--testing/web-platform/tests/cookies/name/name-ctl.html63
-rw-r--r--testing/web-platform/tests/cookies/name/name.html169
-rw-r--r--testing/web-platform/tests/cookies/navigated-away.html39
-rw-r--r--testing/web-platform/tests/cookies/ordering/ordering.sub.html25
-rw-r--r--testing/web-platform/tests/cookies/ordering/resources/ordering-child.sub.html76
-rw-r--r--testing/web-platform/tests/cookies/partitioned-cookies/partitioned-cookies.tentative.https.html72
-rw-r--r--testing/web-platform/tests/cookies/partitioned-cookies/resources/partitioned-cookies-cross-site-embed.html26
-rw-r--r--testing/web-platform/tests/cookies/partitioned-cookies/resources/partitioned-cookies-cross-site-window.html43
-rw-r--r--testing/web-platform/tests/cookies/partitioned-cookies/resources/test-helpers.js64
-rw-r--r--testing/web-platform/tests/cookies/path/default.html51
-rw-r--r--testing/web-platform/tests/cookies/path/match.html113
-rw-r--r--testing/web-platform/tests/cookies/prefix/__host.document-cookie.html57
-rw-r--r--testing/web-platform/tests/cookies/prefix/__host.document-cookie.https.html74
-rw-r--r--testing/web-platform/tests/cookies/prefix/__host.header.html83
-rw-r--r--testing/web-platform/tests/cookies/prefix/__host.header.https.html83
-rw-r--r--testing/web-platform/tests/cookies/prefix/__secure.document-cookie.html41
-rw-r--r--testing/web-platform/tests/cookies/prefix/__secure.document-cookie.https.html41
-rw-r--r--testing/web-platform/tests/cookies/prefix/__secure.header.html41
-rw-r--r--testing/web-platform/tests/cookies/prefix/__secure.header.https.html79
-rw-r--r--testing/web-platform/tests/cookies/prefix/document-cookie.non-secure.html42
-rw-r--r--testing/web-platform/tests/cookies/resources/__init__.py0
-rw-r--r--testing/web-platform/tests/cookies/resources/cookie-helper.sub.js284
-rw-r--r--testing/web-platform/tests/cookies/resources/cookie-test.js186
-rw-r--r--testing/web-platform/tests/cookies/resources/cookie.py42
-rw-r--r--testing/web-platform/tests/cookies/resources/drop.py14
-rw-r--r--testing/web-platform/tests/cookies/resources/dropSameSite.py13
-rw-r--r--testing/web-platform/tests/cookies/resources/dropSameSiteMultiAttribute.py17
-rw-r--r--testing/web-platform/tests/cookies/resources/dropSameSiteNone.py11
-rw-r--r--testing/web-platform/tests/cookies/resources/dropSecure.py11
-rw-r--r--testing/web-platform/tests/cookies/resources/echo-cookie.html31
-rw-r--r--testing/web-platform/tests/cookies/resources/echo-json.py15
-rw-r--r--testing/web-platform/tests/cookies/resources/helpers.py59
-rw-r--r--testing/web-platform/tests/cookies/resources/imgIfMatch.py16
-rw-r--r--testing/web-platform/tests/cookies/resources/list-cookies-for-script.py12
-rw-r--r--testing/web-platform/tests/cookies/resources/list.py10
-rw-r--r--testing/web-platform/tests/cookies/resources/navigate.html8
-rw-r--r--testing/web-platform/tests/cookies/resources/postToParent.py39
-rw-r--r--testing/web-platform/tests/cookies/resources/redirectWithCORSHeaders.py22
-rw-r--r--testing/web-platform/tests/cookies/resources/set-cookie.py45
-rw-r--r--testing/web-platform/tests/cookies/resources/set.py15
-rw-r--r--testing/web-platform/tests/cookies/resources/setSameSite.py32
-rw-r--r--testing/web-platform/tests/cookies/resources/setSameSiteDomain.py36
-rw-r--r--testing/web-platform/tests/cookies/resources/setSameSiteMultiAttribute.py60
-rw-r--r--testing/web-platform/tests/cookies/resources/setSameSiteNone.py16
-rw-r--r--testing/web-platform/tests/cookies/resources/setSecure.py14
-rw-r--r--testing/web-platform/tests/cookies/resources/testharness-helpers.js49
-rw-r--r--testing/web-platform/tests/cookies/samesite-none-secure/cookies-without-samesite-must-be-secure.https.html20
-rw-r--r--testing/web-platform/tests/cookies/samesite/about-blank-nested.https.html29
-rw-r--r--testing/web-platform/tests/cookies/samesite/about-blank-subresource.https.html31
-rw-r--r--testing/web-platform/tests/cookies/samesite/about-blank-toplevel.https.html30
-rw-r--r--testing/web-platform/tests/cookies/samesite/fetch.https.html40
-rw-r--r--testing/web-platform/tests/cookies/samesite/form-get-blank-reload.https.html66
-rw-r--r--testing/web-platform/tests/cookies/samesite/form-get-blank.https.html68
-rw-r--r--testing/web-platform/tests/cookies/samesite/form-post-blank-reload.https.html56
-rw-r--r--testing/web-platform/tests/cookies/samesite/form-post-blank.https.html57
-rw-r--r--testing/web-platform/tests/cookies/samesite/get_all_cookies-default-samesite.html20
-rw-r--r--testing/web-platform/tests/cookies/samesite/get_named_cookie-default-samesite.html18
-rw-r--r--testing/web-platform/tests/cookies/samesite/iframe-reload.https.html55
-rw-r--r--testing/web-platform/tests/cookies/samesite/iframe.document.https.html57
-rw-r--r--testing/web-platform/tests/cookies/samesite/iframe.https.html79
-rw-r--r--testing/web-platform/tests/cookies/samesite/img.https.html75
-rw-r--r--testing/web-platform/tests/cookies/samesite/multiple-samesite-attributes.https.html92
-rw-r--r--testing/web-platform/tests/cookies/samesite/resources/echo-cookies.html8
-rw-r--r--testing/web-platform/tests/cookies/samesite/resources/iframe-navigate-report.html3
-rw-r--r--testing/web-platform/tests/cookies/samesite/resources/iframe-subresource-report.html14
-rw-r--r--testing/web-platform/tests/cookies/samesite/resources/iframe.document.html8
-rw-r--r--testing/web-platform/tests/cookies/samesite/resources/navigate-iframe.html26
-rw-r--r--testing/web-platform/tests/cookies/samesite/resources/navigate.html25
-rw-r--r--testing/web-platform/tests/cookies/samesite/resources/puppet.html32
-rw-r--r--testing/web-platform/tests/cookies/samesite/sandbox-iframe-nested.https.html28
-rw-r--r--testing/web-platform/tests/cookies/samesite/sandbox-iframe-subresource.https.html28
-rw-r--r--testing/web-platform/tests/cookies/samesite/setcookie-lax.https.html32
-rw-r--r--testing/web-platform/tests/cookies/samesite/setcookie-navigation.https.html81
-rw-r--r--testing/web-platform/tests/cookies/samesite/window-open-reload.https.html52
-rw-r--r--testing/web-platform/tests/cookies/samesite/window-open.https.html54
-rw-r--r--testing/web-platform/tests/cookies/schemeful-same-site/resources/navigateToInsecurePostToParent.html6
-rw-r--r--testing/web-platform/tests/cookies/schemeful-same-site/schemeful-iframe-subresource.tentative.html28
-rw-r--r--testing/web-platform/tests/cookies/schemeful-same-site/schemeful-navigation.tentative.html41
-rw-r--r--testing/web-platform/tests/cookies/schemeful-same-site/schemeful-subresource.tentative.html49
-rw-r--r--testing/web-platform/tests/cookies/schemeful-same-site/schemeful-websockets.sub.tentative.html57
-rw-r--r--testing/web-platform/tests/cookies/secure/set-from-dom.https.sub.html47
-rw-r--r--testing/web-platform/tests/cookies/secure/set-from-dom.sub.html47
-rw-r--r--testing/web-platform/tests/cookies/secure/set-from-http.https.sub.html36
-rw-r--r--testing/web-platform/tests/cookies/secure/set-from-http.https.sub.html.headers5
-rw-r--r--testing/web-platform/tests/cookies/secure/set-from-http.sub.html36
-rw-r--r--testing/web-platform/tests/cookies/secure/set-from-http.sub.html.headers5
-rw-r--r--testing/web-platform/tests/cookies/secure/set-from-ws.sub.html45
-rw-r--r--testing/web-platform/tests/cookies/secure/set-from-wss.https.sub.html44
-rw-r--r--testing/web-platform/tests/cookies/size/attributes.www.sub.html121
-rw-r--r--testing/web-platform/tests/cookies/size/name-and-value.html83
-rw-r--r--testing/web-platform/tests/cookies/third-party-cookies/resources/test-helpers.js63
-rw-r--r--testing/web-platform/tests/cookies/third-party-cookies/resources/third-party-cookies-cross-site-embed.html57
-rw-r--r--testing/web-platform/tests/cookies/third-party-cookies/resources/third-party-cookies-cross-site-window.html62
-rw-r--r--testing/web-platform/tests/cookies/third-party-cookies/third-party-cookies.tentative.https.html72
-rw-r--r--testing/web-platform/tests/cookies/value/value-ctl.html64
-rw-r--r--testing/web-platform/tests/cookies/value/value.html170
132 files changed, 6446 insertions, 0 deletions
diff --git a/testing/web-platform/tests/cookies/META.yml b/testing/web-platform/tests/cookies/META.yml
new file mode 100644
index 0000000000..d0743949b6
--- /dev/null
+++ b/testing/web-platform/tests/cookies/META.yml
@@ -0,0 +1,2 @@
+suggested_reviewers:
+ - mikewest
diff --git a/testing/web-platform/tests/cookies/README.md b/testing/web-platform/tests/cookies/README.md
new file mode 100644
index 0000000000..ed86aebf18
--- /dev/null
+++ b/testing/web-platform/tests/cookies/README.md
@@ -0,0 +1,2 @@
+This directory contains tests for
+[Leave Secure Cookies Alone](https://tools.ietf.org/html/draft-ietf-httpbis-cookie-alone-01).
diff --git a/testing/web-platform/tests/cookies/__init__.py b/testing/web-platform/tests/cookies/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/tests/cookies/__init__.py
diff --git a/testing/web-platform/tests/cookies/attributes/attributes-ctl.sub.html b/testing/web-platform/tests/cookies/attributes/attributes-ctl.sub.html
new file mode 100644
index 0000000000..e741dfd9c2
--- /dev/null
+++ b/testing/web-platform/tests/cookies/attributes/attributes-ctl.sub.html
@@ -0,0 +1,99 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Test cookie attribute parsing with control characters</title>
+ <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ const host = "{{host}}";
+ const path = "/cookies/attributes";
+
+ // Tests for control characters (CTLs) in a cookie's attribute values.
+ // CTLs are defined by RFC 5234 to be %x00-1F / %x7F.
+ const CTLS = getCtlCharacters();
+
+ // All CTLs, with the exception of %x09 (the tab character), should
+ // cause the cookie to be rejected.
+ // In these tests we rely on subsequent attributes with the same name
+ // overriding the earlier one. In the cases where the control character
+ // should cause the entire cookie line to be rejected, if the control
+ // character were not present the cookie line should be one that
+ // would not be rejected. That way, if the attribute value is ignored
+ // instead of the cookie line being rejected, the test will catch it.
+ for (const ctl of CTLS) {
+ const controlCharacterAttributeTests = [
+ {
+ cookie: `test${ctl.code}domain=t; Domain=test${ctl.chr}.co; Domain=${host};`,
+ name: `Cookie with %x${ctl.code.toString(16)} in Domain attribute value is handled correctly.`,
+ },
+ {
+ cookie: `test${ctl.code}domain2=t; Domain=${host}${ctl.chr};`,
+ name: `Cookie with %x${ctl.code.toString(16)} after Domain attribute value is handled correctly.`,
+ },
+ {
+ cookie: `test${ctl.code}path=t; Path=/te${ctl.chr}st; Path=${path}`,
+ name: `Cookie with %x${ctl.code.toString(16)} in Path attribute value is handled correctly.`,
+ },
+ {
+ cookie: `test${ctl.code}path2=t; Path=${path}${ctl.chr};`,
+ name: `Cookie with %x${ctl.code.toString(16)} after Path attribute value is handled correctly.`,
+ },
+ {
+ cookie: `test${ctl.code}maxage=t; Max-Age=10${ctl.chr}00; Max-Age=1000;`,
+ name: `Cookie with %x${ctl.code.toString(16)} in Max-Age attribute value is handled correctly.`,
+ },
+ {
+ cookie: `test${ctl.code}maxage2=t; Max-Age=1000${ctl.chr};`,
+ name: `Cookie with %x${ctl.code.toString(16)} after Max-Age attribute value is handled correctly.`,
+ },
+ {
+ cookie: `test${ctl.code}expires=t; Expires=Fri, 01 Jan 20${ctl.chr}38 00:00:00 GMT; ` +
+ 'Expires=Fri, 01 Jan 2038 00:00:00 GMT;',
+ name: `Cookie with %x${ctl.code.toString(16)} in Expires attribute value is handled correctly.`,
+ },
+ {
+ cookie: `test${ctl.code}expires2=t; Expires=Fri, 01 Jan 2038 00:00:00 GMT${ctl.chr};`,
+ name: `Cookie with %x${ctl.code.toString(16)} after Expires attribute value is handled correctly.`,
+ },
+ {
+ cookie: `test${ctl.code}secure=t; Sec${ctl.chr}ure;`,
+ name: `Cookie with %x${ctl.code.toString(16)} in Secure attribute is handled correctly.`,
+ },
+ {
+ cookie: `test${ctl.code}secure2=t; Secure${ctl.chr};`,
+ name: `Cookie with %x${ctl.code.toString(16)} after Secure attribute is handled correctly.`,
+ },
+ {
+ cookie: `test${ctl.code}httponly=t; Http${ctl.chr}Only;`,
+ name: `Cookie with %x${ctl.code.toString(16)} in HttpOnly attribute is handled correctly.`,
+ },
+ {
+ cookie: `test${ctl.code}samesite=t; SameSite=La${ctl.chr}x; SameSite=Lax;`,
+ name: `Cookie with %x${ctl.code.toString(16)} in SameSite attribute value is handled correctly.`,
+ },
+ {
+ cookie: `test${ctl.code}samesite2=t; SameSite=Lax${ctl.chr};`,
+ name: `Cookie with %x${ctl.code.toString(16)} after SameSite attribute value is handled correctly.`,
+ },
+ ];
+
+ for (const test of controlCharacterAttributeTests) {
+ if (ctl.code === 0x09) {
+ domCookieTest(test.cookie, test.cookie.split(";")[0], test.name);
+ } else {
+ domCookieTest(test.cookie, "", test.name);
+ }
+ }
+ }
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/cookies/attributes/domain.sub.html b/testing/web-platform/tests/cookies/attributes/domain.sub.html
new file mode 100644
index 0000000000..17bc3267c2
--- /dev/null
+++ b/testing/web-platform/tests/cookies/attributes/domain.sub.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Test cookie domain attribute parsing</title>
+ <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2.3">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ </head>
+ <body>
+ <script>
+ const port = "{{ports[http][0]}}";
+ const wwwHost = "{{domains[www]}}";
+
+ test(t => {
+ const win = window.open(`http://${wwwHost}:${port}/cookies/attributes/resources/domain-child.sub.html`);
+ fetch_tests_from_window(win);
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/cookies/attributes/expires.html b/testing/web-platform/tests/cookies/attributes/expires.html
new file mode 100644
index 0000000000..a6bacfd74e
--- /dev/null
+++ b/testing/web-platform/tests/cookies/attributes/expires.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Test expires attribute parsing</title>
+ <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.1.1">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ const expiresTests = [
+ {
+ cookie: "test=1; Expires=Fri, 01 Jan 2038 00:00:00 GMT",
+ expected: "test=1",
+ name: "Set cookie with expires value containing a comma",
+ },
+ {
+ cookie: "test=2; Expires=Fri 01 Jan 2038 00:00:00 GMT, baz=qux",
+ expected: "test=2",
+ name: "Set cookie with expires value followed by comma",
+ },
+ {
+ cookie: "test=3; Expires=Fri, 01 Jan 2038 00:00:00 GMT",
+ expected: "test=3",
+ name: "Set cookie with future expiration",
+ },
+ {
+ cookie: ["test=expired; Expires=Fri, 07 Aug 2007 08:04:19 GMT", "test=4; Expires=Fri, 07 Aug 2027 08:04:19 GMT"],
+ expected: "test=4",
+ name: "Set expired cookie along with valid cookie",
+ },
+ {
+ cookie: "test=5; expires=Thu, 10 Apr 1980 16:33:12 GMT",
+ expected: "",
+ name: "Don't set cookie with expires set to the past",
+ },
+ ];
+
+ // These tests evaluate setting cookies with expiration via HTTP headers.
+ for (const test of expiresTests) {
+ httpCookieTest(test.cookie, test.expected, test.name + " via HTTP headers");
+ }
+
+ // These tests evaluate setting cookies with expiration via document.cookie.
+ for (const test of expiresTests) {
+ domCookieTest(test.cookie, test.expected, test.name + " via document.cookie");
+ }
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/cookies/attributes/invalid.html b/testing/web-platform/tests/cookies/attributes/invalid.html
new file mode 100644
index 0000000000..6d4a53916d
--- /dev/null
+++ b/testing/web-platform/tests/cookies/attributes/invalid.html
@@ -0,0 +1,171 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Test invalid attribute parsing</title>
+ <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ // These tests ensure that invalid attributes don't affect
+ // cookie parsing. `Path` isn't important to the tests where it appears,
+ // but it's used to be able to place the invalid attribute in different
+ // locations.
+ const invalidAttributeTests = [
+ {
+ cookie: "test=1; lol; Path=/",
+ expected: "test=1",
+ name: "Set cookie with invalid attribute",
+ defaultPath: false
+ },
+ {
+ cookie: "test=2; Path=/; lol",
+ expected: "test=2",
+ name: "Set cookie ending with invalid attribute.",
+ defaultPath: false
+ },
+ {
+ cookie: "test=3; Path=/; 'lol'",
+ expected: "test=3",
+ name: "Set cookie ending with quoted invalid attribute.",
+ defaultPath: false
+ },
+ {
+ cookie: 'test=4; Path=/; "lol"',
+ expected: "test=4",
+ name: "Set cookie ending with double-quoted invalid attribute.",
+ defaultPath: false
+ },
+ {
+ cookie: "test=5; Path=/; lol=",
+ expected: "test=5",
+ name: "Set cookie ending with invalid attribute equals.",
+ defaultPath: false
+ },
+ {
+ cookie: 'test=6; lol="aaa;bbb"; Path=/',
+ expected: "test=6",
+ name: "Set cookie with two invalid attributes (lol=\"aaa and bbb).",
+ defaultPath: false
+ },
+ {
+ cookie: 'test=7; Path=/; lol="aaa;bbb"',
+ expected: "test=7",
+ name: "Set cookie ending with two invalid attributes (lol=\"aaa and bbb).",
+ defaultPath: false
+ },
+ {
+ cookie: 'test=8; "Secure"',
+ expected: "test=8",
+ // This gets parsed as an unrecognized \"Secure\" attribute, not a valid
+ // Secure attribute. That's why it gets set on an non-secure origin.
+ name: "Set cookie for quoted Secure attribute",
+ },
+ {
+ cookie: "test=9; Secure qux",
+ expected: "test=9",
+ // This should be parsed as an unrecognized "Secure qux" attribute
+ // and ignored. That is, the cookie will not be Secure.
+ name: "Set cookie for Secure qux",
+ },
+ {
+ cookie: "test=10; b,az=qux",
+ expected: "test=10",
+ name: "Ignore invalid attribute name with comma",
+ },
+ {
+ cookie: "test=11; baz=q,ux",
+ expected: "test=11",
+ name: "Ignore invalid attribute value with comma",
+ },
+ {
+ cookie: " test = 12 ;foo;;; bar",
+ expected: "test=12",
+ name: "Set cookie ignoring multiple invalid attributes, whitespace, and semicolons",
+ },
+ {
+ cookie: " test=== 13 ;foo;;; bar",
+ expected: "test=== 13",
+ name: "Set cookie with multiple '='s in its value, ignoring multiple invalid attributes, whitespace, and semicolons",
+ },
+ {
+ cookie: "test=14; version=1;",
+ expected: "test=14",
+ name: "Set cookie with (invalid) version=1 attribute",
+ },
+ {
+ cookie: "test=15; version=1000;",
+ expected: "test=15",
+ name: "Set cookie with (invalid) version=1000 attribute",
+ },
+ {
+ cookie: "test=16; customvalue='1000 or more';",
+ expected: "test=16",
+ name: "Set cookie ignoring anything after ; (which looks like an invalid attribute)",
+ },
+ {
+ cookie: "test=17; customvalue='1000 or more'",
+ expected: "test=17",
+ name: "Set cookie ignoring anything after ; (which looks like an invalid attribute, with no trailing semicolon)",
+ },
+ {
+ cookie: "test=18; foo=bar, a=b",
+ expected: "test=18",
+ name: "Ignore keys after semicolon",
+ },
+ {
+ cookie: "test=19;max-age=3600, c=d;path=/",
+ expected: "test=19",
+ name: "Ignore attributes after semicolon",
+ defaultPath: false,
+ },
+ {
+ cookie: ["testA=20", "=", "testb=20"],
+ expected: "testA=20; testb=20",
+ name: "Ignore `Set-Cookie: =`",
+ },
+ {
+ cookie: ["test=21", ""],
+ expected: "test=21",
+ name: "Ignore empty cookie string",
+ },
+ {
+ cookie: ["test22", "="],
+ expected: "test22",
+ name: "Ignore `Set-Cookie: =` with other `Set-Cookie` headers",
+ },
+ {
+ cookie: ["testA23", "; testB23"],
+ expected: "testA23",
+ name: "Ignore name- and value-less `Set-Cookie: ; bar`",
+ },
+ {
+ cookie: ["test24", " "],
+ expected: "test24",
+ name: "Ignore name- and value-less `Set-Cookie: `",
+ },
+ {
+ cookie: ["test25", "\t"],
+ expected: "test25",
+ name: "Ignore name- and value-less `Set-Cookie: \\t`",
+ },
+ {
+ cookie: "test=26; domain=.parser.test; ;; ;=; ,,, ===,abc,=; abracadabra! max-age=20;=;;",
+ expected: "",
+ name: "Ignore cookie with domain that won't domain match (along with other invalid noise)",
+ },
+ ];
+
+ for (const test of invalidAttributeTests) {
+ httpCookieTest(test.cookie, test.expected, test.name, test.defaultPath);
+ }
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/cookies/attributes/max-age.html b/testing/web-platform/tests/cookies/attributes/max-age.html
new file mode 100644
index 0000000000..7b7ff6ed4c
--- /dev/null
+++ b/testing/web-platform/tests/cookies/attributes/max-age.html
@@ -0,0 +1,78 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Test max-age attribute parsing</title>
+ <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.3.2">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ // TODO: there is more to test here, these tests capture the old
+ // ported http-state tests. Feel free to delete this comment when more
+ // are added.
+ const maxAgeTests = [
+ {
+ cookie: "test=1; Max-Age=50,399",
+ expected: "test=1",
+ name: "Ignore max-age attribute with invalid non-zero-digit (containing a comma)",
+ },
+ {
+ cookie: "test=2; max-age=10000",
+ expected: "test=2",
+ name: "Set cookie with age",
+ },
+ {
+ cookie: "test=3; max-age=0",
+ expected: "",
+ name: "Set no cookie with max-age=0",
+ },
+ {
+ cookie: "test=4; max-age=-1",
+ expected: "",
+ name: "Set no cookie with max-age=-1",
+ },
+ {
+ cookie: "test=5; max-age=-20",
+ expected: "",
+ name: "Set no cookie with max-age=-20",
+ },
+ {
+ cookie: ["testA=6; max-age=60", "testB=6; max-age=60"],
+ expected: "testA=6; testB=6",
+ name: "Set multiple cookies with max-age attribute",
+ },
+ {
+ cookie: ["testA=7; max-age=60", "testB=7; max-age=60", "testA=differentvalue; max-age=0"],
+ expected: "testB=7",
+ name: "Expire later cookie with same name and max-age=0",
+ },
+ {
+ cookie: ["testA=8; max-age=60", "testB=8; max-age=60", "testA=differentvalue; max-age=0", "testC=8; max-age=0"],
+ expected: "testB=8",
+ name: "Expire later cookie with same name and max-age=0, and don't set cookie with max-age=0",
+ },
+ {
+ cookie: ['test="9! = foo;bar\";" parser; max-age=6', "test9; max-age=2.63,"],
+ expected: 'test="9! = foo; test9',
+ name: "Set mulitiple cookies with valid max-age values",
+ },
+ {
+ cookie: ["test=10; max-age=0", "test10; max-age=0"],
+ expected: "",
+ name: "Don't set multiple cookies with max-age=0",
+ },
+ ];
+
+ for (const test of maxAgeTests) {
+ httpCookieTest(test.cookie, test.expected, test.name);
+ }
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/cookies/attributes/path-redirect.html b/testing/web-platform/tests/cookies/attributes/path-redirect.html
new file mode 100644
index 0000000000..574879971f
--- /dev/null
+++ b/testing/web-platform/tests/cookies/attributes/path-redirect.html
@@ -0,0 +1,128 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Test cookie path attribute parsing</title>
+ <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2.4">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+ </head>
+ <body>
+ <script>
+ const pathRedirectTests = [
+ {
+ cookie: "test=1; path=/cookies/attributes/resources/path.html",
+ expected: "test=1",
+ name: "Cookie sent for exact redirected path match",
+ location: "/cookies/attributes/resources/path.html",
+ },
+ {
+ cookie: "test=2; path=/cookies/attributes/resources/path/one.html",
+ expected: "test=2",
+ name: "Cookie sent for exact redirected path match, one level deeper",
+ location: "/cookies/attributes/resources/path/one.html",
+ },
+ {
+ cookie: "test=3; path=/cookies/attributes/resources/path/",
+ expected: "test=3",
+ name: "Cookie sent for redirected path with trailing '/' and the redirected URL is one level deeper",
+ location: "/cookies/attributes/resources/path/one.html",
+ },
+ {
+ cookie: "test=4; path=/cookies/attributes/resources/path/",
+ expected: "test=4",
+ name: "Cookie sent for redirected path with trailing '/' and a double '/' in the redirected URL",
+ location: "/cookies/attributes/resources/path//one.html",
+ },
+ {
+ cookie: "test=5; path=/cookies/attributes/resources/path/one.html;",
+ expected: "test=5",
+ name: "Cookie sent for redirected path match with a trailing ';' after an unquoted Path",
+ location: "/cookies/attributes/resources/path/one.html",
+ },
+ {
+ cookie: 'test=6; path="/cookies/attributes/resources/path/one.html;"',
+ expected: "",
+ name: "No cookie sent for redirected path match with a trailing ';' inside a quoted Path",
+ location: "/cookies/attributes/resources/path/one.html",
+ },
+ {
+ cookie: "test=7a; path=/cookies/attributes/resources/p%61th/three.html",
+ expected: "",
+ name: "No cookie sent for redirected path match with partially URL encoded path",
+ location: "/cookies/attributes/resources/path/three.html",
+ },
+ {
+ cookie: ["test=8a; path=/cookies/attributes/resources",
+ "test=8b; path=/cookies/attributes/resources/"],
+ expected: "test=8b; test=8a",
+ name: "Multiple cookies sent for multiple redirected path matches, sorted by length",
+ location: "/cookies/attributes/resources/path.html",
+ },
+ {
+ cookie: "test=9; path=/cookies/attributes/resources/path.html",
+ expected: "",
+ name: "No cookie sent for redirected path mismatch where path and redirected URL begin with same string",
+ location: "/cookies/attributes/resources/pathfakeout.html",
+ },
+ {
+ cookie: "test=10; path=/cookies/attributes/resources/path/one.html",
+ expected: "",
+ name: "No cookie sent for redirected path mismatch where final path directory component and redirected URL resource begin with same string",
+ location: "/cookies/attributes/resources/path.html",
+ },
+ {
+ cookie: "test=11; path=/cookies/attributes/resources/path/one.html",
+ expected: "",
+ name: "No cookie sent for redirected path mismatch where final path directory component begins with same string as redirected URL final directory component",
+ location: "/cookies/attributes/resources/pathfakeout/one.html",
+ },
+ {
+ cookie: "test=12; path=/cookies/attributes/resources/path/one.html",
+ expected: "",
+ name: "No cookie sent for redirected path mismatch for different resources inside the same final directory component",
+ location: "/cookies/attributes/resources/path/two.html",
+ },
+ {
+ cookie: "test=13; path=/cookies/attributes/resources/path/one.html/",
+ expected: "",
+ name: "No cookie sent for redirected path mismatch where final path directory component ends in '/' and does not match redirected URL",
+ location: "/cookies/attributes/resources/path/two.html",
+ },
+ {
+ cookie: "test=14; path=/cookies/attributes/resources/path/",
+ expected: "",
+ name: "No cookie sent for redirected path mismatch with a similar start to the redirected URL",
+ location: "/cookies/attributes/resources/pathfakeout.html",
+ },
+ {
+ cookie: "test=15; path=/cookies/attributes/resources/path/one.html?",
+ expected: "",
+ name: "No cookie sent for redirected path mismatch with trailing '?' after unquoted Path",
+ location: "/cookies/attributes/resources/path/one.html",
+ },
+ {
+ cookie: "test=16; path=/cookies/attributes/resources/path/one.html#",
+ expected: "",
+ name: "No cookie sent for redirected path mismatch with trailing '#' after unquoted Path",
+ location: "/cookies/attributes/resources/path/one.html",
+ },
+ {
+ cookie: "test=17; path=/cookies/attributes/resources/path/one.html/",
+ expected: "",
+ name: "No cookie sent for redirected path mismatch with trailing '/' after unquoted Path",
+ location: "/cookies/attributes/resources/path/one.html",
+ },
+ ];
+
+ for (const test of pathRedirectTests) {
+ httpRedirectCookieTest(test.cookie, test.expected, test.name,
+ test.location);
+ }
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/cookies/attributes/path.html b/testing/web-platform/tests/cookies/attributes/path.html
new file mode 100644
index 0000000000..81adc08a19
--- /dev/null
+++ b/testing/web-platform/tests/cookies/attributes/path.html
@@ -0,0 +1,144 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Test cookie path attribute parsing</title>
+ <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2.4">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+ </head>
+ <body>
+ <script>
+ const pathTests = [
+ {
+ cookie: "test=1; Path",
+ expected: "test=1",
+ name: "Set cookie for bare Path",
+ },
+ {
+ cookie: "test=2; Path=",
+ expected: "test=2",
+ name: "Set cookie for Path=",
+ },
+ {
+ cookie: "test=3; Path=/",
+ expected: "test=3",
+ name: "Set cookie for Path=/",
+ defaultPath: false,
+ },
+ {
+ cookie: "test=4; Path=/qux",
+ expected: "",
+ name: "No cookie returned for mismatched path",
+ defaultPath: false,
+ },
+ {
+ cookie: "test=5; Path =/qux",
+ expected: "",
+ name: "No cookie returned for path space equals mismatched path",
+ defaultPath: false,
+ },
+ {
+ cookie: "test=6; Path= /qux",
+ expected: "",
+ name: "No cookie returned for path equals space mismatched path",
+ defaultPath: false,
+ },
+ {
+ cookie: "test=7; Path=/qux ; taz",
+ expected: "",
+ name: "No cookie returned for mismatched path and attribute",
+ defaultPath: false,
+ },
+ {
+ cookie: "test=8; Path=/qux; Path=/",
+ expected: "test=8",
+ name: "Set cookie for mismatched and root path",
+ },
+ {
+ cookie: "test=9; Path=/; Path=/qux",
+ expected: "",
+ name: "No cookie returned for root and mismatched path",
+ defaultPath: false,
+ },
+ {
+ cookie: "test=10; Path=/lol; Path=/qux",
+ expected: "",
+ name: "No cookie returned for multiple mismatched paths",
+ defaultPath: false,
+ },
+ {
+ cookie: ["testA=11; path=/", "testB=11; path=/cookies/attributes"],
+ expected: "testB=11; testA=11",
+ name: "Return 2 cookies sorted by matching path length (earlier name with shorter path set first)",
+ defaultPath: false,
+ },
+ {
+ cookie: ["testB=12; path=/", "testA=12; path=/cookies/attributes"],
+ expected: "testA=12; testB=12",
+ name: "Return 2 cookies sorted by matching path length (later name with shorter path set first)",
+ defaultPath: false,
+ },
+ {
+ cookie: ["testA=13; path=/cookies/attributes", "testB=13; path=/"],
+ expected: "testA=13; testB=13",
+ name: "Return 2 cookies sorted by matching path length (earlier name with longer path set first)",
+ defaultPath: false,
+ },
+ {
+ cookie: ["testB=14; path=/cookies/attributes", "testA=14; path=/"],
+ expected: "testB=14; testA=14",
+ name: "Return 2 cookies sorted by matching path length (later name with longer path set first)",
+ defaultPath: false,
+ },
+ {
+ cookie: ["test=15; path=/cookies/attributes/foo"],
+ expected: "",
+ name: "No cookie returned for partial path match",
+ defaultPath: false,
+ },
+ {
+ cookie: ["test=16", "test=0; path=/cookies/attributes/foo"],
+ expected: "test=16",
+ name: "No cookie returned for partial path match, return cookie for default path",
+ },
+ {
+ cookie: ["test=17; path= /"],
+ expected: "test=17",
+ name: "Return cookie for path= / (whitespace after equals)",
+ },
+ {
+ cookie: ["test=18; path=/cookies/ATTRIBUTES"],
+ expected: "",
+ name: "No cookie returned for case mismatched path",
+ defaultPath: false,
+ },
+ {
+ cookie: ["testA=19; path = /cookies/attributes", "testB=19; path = /book"],
+ expected: "testA=19",
+ name: "Return cookie A on path match, no cookie returned for path mismatch (plus whitespace)",
+ defaultPath: false,
+ },
+ {
+ cookie: ["test=20; path=; path=/dog"],
+ expected: "",
+ name: "No cookie returned for mismatched path (after bare path=)",
+ defaultPath: false,
+ },
+ {
+ cookie: ["test=21; path=/dog; path="],
+ expected: "test=21",
+ name: "Return cookie for bare path= (after mismatched path)",
+ },
+ ];
+
+ for (const test of pathTests) {
+ httpCookieTest(test.cookie, test.expected, test.name, test.defaultPath);
+ }
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/cookies/attributes/resources/domain-child.sub.html b/testing/web-platform/tests/cookies/attributes/resources/domain-child.sub.html
new file mode 100644
index 0000000000..515079b783
--- /dev/null
+++ b/testing/web-platform/tests/cookies/attributes/resources/domain-child.sub.html
@@ -0,0 +1,401 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Test cookie domain attribute parsing</title>
+ <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2.3">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+ </head>
+ <body>
+ <script>
+ const path = "path=/cookies/attributes"
+ const port = "{{ports[http][0]}}";
+ const host = "{{host}}"; // example.org
+ const wwwHost = "{{domains[www]}}"; // home.example.org
+ const www1Host = "{{domains[www1]}}"; // sibling.example.org
+ const www2wwwHost = "{{domains[www2.www]}}"; // subdomain.home.example.org
+
+ // naive helper method to return the TLD for a given domain
+ const getTLD = domain => {
+ let match = /\.[a-z]+$/.exec(domain);
+ if (match) {
+ return match[0];
+ } else {
+ throw 'Domain is malformed!';
+ }
+ }
+
+ // helper to take a domain like "www.example.org"
+ // and return a string like "www.eXaMpLe.org"
+ const makeBizarre = domain => {
+ let bizarre = "";
+ let domainArray = domain.split(".");
+ let secondLevel = domainArray[domainArray.length - 2];
+ for (let i in secondLevel) {
+ if (i % 2 == 1) {
+ bizarre += secondLevel[i].toUpperCase();
+ } else {
+ bizarre += secondLevel[i];
+ }
+ }
+ domainArray[domainArray.length - 2] = bizarre;
+ return domainArray.join(".");
+ }
+
+ // helper to change the current TLD to a TLD that doesn't exist, and is
+ // unlikely to exist in the future. (the main point is that the TLD
+ // *changes*, so there is no domain match, but we cant' predict how WPT
+ // servers may be set up in the wild so picking any valid TLD has the risk
+ // of future (unintentional) domain matching.
+ const changeTLD = domain => {
+ let domainArray = domain.split(".");
+ domainArray[domainArray.length - 1] += "zzz";
+ return domainArray.join(".");
+ }
+
+ const domainTests = [
+ {
+ cookie: `test=1; domain=${wwwHost}`,
+ expected: "test=1",
+ name: "Return cookie for a domain match",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=2; domain=${wwwHost}`,
+ expected: "",
+ name: "No cookie returned for domain mismatch (subdomains differ post-redirect)",
+ location: `http://${www1Host}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=3; domain=.${wwwHost}`,
+ expected: "test=3",
+ name: "Return cookie for a domain match with leading '.'",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=4; domain=${wwwHost}`,
+ expected: "test=4",
+ name: "Return cookie for domain match (domain attribute is suffix of the host name and first level subdomain)",
+ location: `http://${www2wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=5; domain=.${wwwHost}`,
+ expected: "test=5",
+ name: "Return cookie for domain match (domain attribute is suffix of the host name and first level subdomain, with leading '.')",
+ location: `http://${www2wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=6; domain=.${wwwHost}`,
+ expected: "",
+ name: "No cookie returned for domain mismatch (subdomains differ, with leading '.')",
+ location: `http://${www1Host}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=7; domain=${www1Host}`,
+ expected: "",
+ name: "No cookie returned for domain mismatch when cookie was created (which would match after the redirect, with one subdomain level)",
+ location: `http://${www1Host}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=8; domain=.${host}`,
+ expected: "test=8",
+ name: "Return cookie for domain match (domain attribute is suffix of the host name, with leading '.')",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=9; domain=${host}`,
+ expected: "test=9",
+ name: "Return cookie for domain match (domain attribute is suffix of the host name)",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=10; domain=..${wwwHost}`,
+ expected: "",
+ name: "No cookie returned for domain attribute with double leading '.'",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=11; domain=www..${host}`,
+ expected: "",
+ name: "No cookie returned for domain attribute with subdomain followed by ..",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=12; domain= .${wwwHost}`,
+ expected: "test=12",
+ name: "Return cookie for a domain match with leading whitespace and '.'",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=13; domain= . ${wwwHost}`,
+ expected: "",
+ name: "No cookie returned for domain attribute with whitespace that surrounds a leading '.'",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=14; domain=${wwwHost}.`,
+ expected: "",
+ name: "No cookie returned for domain attribute with trailing '.'",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=15; domain=${wwwHost}..`,
+ expected: "",
+ name: "No cookie returned for domain attribute with trailing '..'",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=16; domain=${wwwHost} .`,
+ expected: "",
+ name: "No cookie returned for domain attribute with trailing whitespace and '.'",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=17; domain=${getTLD(host)}`,
+ expected: "",
+ name: "No cookie returned for domain attribute with TLD as value",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=18; domain=.${getTLD(host)}`,
+ expected: "",
+ name: "No cookie returned for domain attribute with TLD as value, with leading '.'",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=18b; domain=.${getTLD(host)}.`,
+ expected: "",
+ name: "No cookie returned for domain attribute with TLD as value, with leading and trailing '.'",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: [`testA=19; domain=${wwwHost}`, `testB=19; domain=.${wwwHost}`],
+ expected: "testA=19; testB=19",
+ name: "Return multiple cookies that match on domain (without and with leading '.')",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: [`testB=20; domain=.${wwwHost}`, `testA=20; domain=${wwwHost}`],
+ expected: "testB=20; testA=20",
+ name: "Return multiple cookies that match on domain (with and without leading '.')",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=21; domain="${wwwHost}"`,
+ expected: "",
+ name: "No cookie returned for domain attribute value between quotes",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: [`testA=22; domain=${wwwHost}`, `testB=22; domain=.${host}`],
+ expected: "testA=22; testB=22",
+ name: "Return multiple cookies that match on subdomain and domain (without and with leading '.')",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: [`testB=23; domain=.${host}`, `testA=23; domain=${wwwHost}`],
+ expected: "testB=23; testA=23",
+ name: "Return multiple cookies that match on domain and subdomain (with and without leading '.')",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=24; domain=.${host}; domain=${wwwHost}`,
+ expected: "",
+ name: "No cookie returned when domain attribute does not domain-match (and first does)",
+ location: `http://${www1Host}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=25; domain=${wwwHost}; domain=.${host}`,
+ expected: "test=25",
+ name: "Return cookie for domain attribute match (first does not, but second does)",
+ location: `http://${www1Host}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=26; domain=${makeBizarre(wwwHost)}`,
+ expected: "test=26",
+ name: "Return cookie for domain match (with bizarre capitalization for domain attribute value)",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=27; domain="${wwwHost}:${port}"`,
+ expected: "",
+ name: "No cookie returned for domain attribute value with port",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=28; domain=${www2wwwHost}`,
+ expected: "",
+ name: "No cookie returned for domain mismatch when cookie was created (which would match after the redirect, with two subdomain levels)",
+ location: `http://${www2wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=29`,
+ expected: "",
+ name: "No cookie returned for cookie set on different domain (with no domain attribute)",
+ location: `http://${www2wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: "test=30; domain=",
+ expected: "test=30",
+ name: "Return cookie set with bare domain= attribute",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=31; domain=${wwwHost}`,
+ expected: "test=31",
+ name: "Return cookie that domain-matches with bizarre-cased URL",
+ location: `http://${makeBizarre(wwwHost)}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=32; domain=${wwwHost}; domain=${changeTLD(wwwHost)}`,
+ expected: "",
+ name: "No cookie returned for domain attribute mismatch (first attribute matches, but second does not)",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=33; domain=${changeTLD(wwwHost)}; domain=${wwwHost}`,
+ expected: "test=33",
+ name: "Return cookie for domain match (first attribute doesn't, but second does)",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=34; domain=${wwwHost}; domain=${changeTLD(wwwHost)}; domain=${wwwHost}`,
+ expected: "test=34",
+ name: "Return cookie for domain match (first attribute matches, second doesn't, third does)",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=35; domain=${changeTLD(wwwHost)}; domain=${wwwHost}; domain=${changeTLD(wwwHost)}`,
+ expected: "",
+ name: "No cookie returned for domain attribute mismatch (first attribute doesn't, second does, third doesn't)",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=36; domain=${wwwHost}; domain=${wwwHost}`,
+ expected: "test=36",
+ name: "Return cookie for domain match (with two identical domain attributes)",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=37; domain=${wwwHost}; domain=${host}`,
+ expected: "test=37",
+ name: "Return cookie for domain match (with first domain attribute a match for host name and second as suffix of host name)",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=38; domain=${host}; domain=${wwwHost}`,
+ expected: "test=38",
+ name: "Return cookie for domain match (with first domain attribute as suffix of host name and second a match for host name)",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=39; domain=.${www1Host}`,
+ expected: "",
+ name: "No cookie set on domain mismatch before a (domain matching) redirect",
+ location: `http://${www1Host}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=40; domain=.${www2wwwHost}`,
+ expected: "",
+ name: "No cookie set on domain mismatch before a (domain matching) redirect (for second level subdomain)",
+ location: `http://${www2wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=41; domain=${host}; domain=`,
+ expected: "test=41",
+ name: "Return cookie for domain match (with first domain attribute as suffix of host name and second a bare attribute)",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=42; domain=${www1Host}; domain=`,
+ expected: "test=42",
+ name: "Cookie returned for bare domain attribute following mismatched domain attribute (after redirect to same-origin page).",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=43; domain=${www1Host}; domain=`,
+ expected: "",
+ name: "No cookie returned for domain mismatch (first attribute is a different subdomain and second is bare)",
+ location: `http://${www2wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: [`test=not44; domain=${wwwHost}`, `test=44; domain=.${wwwHost}`],
+ expected: "test=44",
+ name: "Cookies with same name, path, and domain (differing only in leading '.') overwrite each other ('.' second)",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: [`test=not45; domain=.${wwwHost}`, `test=45; domain=${wwwHost}`],
+ expected: "test=45",
+ name: "Cookies with same name, path, and domain (differing only in leading '.') overwrite each other ('.' first)",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=46; domain=.`,
+ expected: "",
+ name: "No cookie returned for domain with single dot ('.') value.",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: `test=46b; domain=.; domain=${host}`,
+ expected: "test=46b",
+ name: "Return cookie with valid domain after domain with single dot ('.') value.",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: ["test=47", `test=47b; domain=${host}`,`test=47b; domain=${www1Host}; domain=`],
+ expected: "test=47b; test=47b",
+ name: "Empty domain treated as host cookie 1",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: ["test=48", `test=48b; domain=${host}`,`test=48b; domain=${host}; domain=`],
+ expected: "test=48b; test=48b",
+ name: "Empty domain treated as host cookie 2",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: ["test=49", `test=49b; domain=${host}`,`test=49b; domain=`],
+ expected: "test=49b; test=49b",
+ name: "Empty domain treated as host cookie 3",
+ location: `http://${wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: ["test=50", `test=50b; domain=${host}`,`test=50b; domain=${www1Host}; domain=`],
+ expected: "test=50b",
+ name: "No host cookies returned for host cookies after non-host redirect 1",
+ location: `http://${www2wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: ["test=51", `test=51b; domain=${host}`,`test=51b; domain=${host}; domain=`],
+ expected: "test=51b",
+ name: "No host cookies returned for host cookies after non-host redirect 2",
+ location: `http://${www2wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ {
+ cookie: ["test=52", `test=52b; domain=${host}`,`test=52b; domain=`],
+ expected: "test=52b",
+ name: "No host cookies returned for host cookies after non-host redirect 3",
+ location: `http://${www2wwwHost}:${port}/cookies/attributes/resources/path.html`,
+ },
+ ];
+
+ for (const test of domainTests) {
+ if (Array.isArray(test.cookie)) {
+ for (let i in test.cookie) {
+ test.cookie[i] += `; ${path}`;
+ }
+ } else {
+ test.cookie += `; ${path}`;
+ }
+
+ httpRedirectCookieTest(test.cookie, test.expected, test.name,
+ test.location);
+ }
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/cookies/attributes/resources/path-redirect-shared.js b/testing/web-platform/tests/cookies/attributes/resources/path-redirect-shared.js
new file mode 100644
index 0000000000..777d0abd33
--- /dev/null
+++ b/testing/web-platform/tests/cookies/attributes/resources/path-redirect-shared.js
@@ -0,0 +1,11 @@
+// Note: this function has a dependency on testdriver.js. Any test files calling
+// it should include testdriver.js and testdriver-vendor.js
+window.addEventListener("message", (e) => {
+ setTestContextUsingRootWindow();
+ if (e.data == "getAndExpireCookiesForRedirectTest") {
+ const cookies = document.cookie;
+ test_driver.delete_all_cookies().then(() => {
+ e.source.postMessage({"cookies": cookies}, '*');
+ });
+ }
+}); \ No newline at end of file
diff --git a/testing/web-platform/tests/cookies/attributes/resources/path.html b/testing/web-platform/tests/cookies/attributes/resources/path.html
new file mode 100644
index 0000000000..5ff90b9f15
--- /dev/null
+++ b/testing/web-platform/tests/cookies/attributes/resources/path.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>helper iframe for matching cookie path redirect tests</title>
+ <meta name=help href="http://tools.ietf.org/html/rfc6265#section-5.1.4">
+</head>
+<body>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+ <script src="/cookies/attributes/resources/path-redirect-shared.js"></script>
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/cookies/attributes/resources/path.html.headers b/testing/web-platform/tests/cookies/attributes/resources/path.html.headers
new file mode 100644
index 0000000000..23de552c1a
--- /dev/null
+++ b/testing/web-platform/tests/cookies/attributes/resources/path.html.headers
@@ -0,0 +1 @@
+Access-Control-Allow-Origin: * \ No newline at end of file
diff --git a/testing/web-platform/tests/cookies/attributes/resources/path/one.html b/testing/web-platform/tests/cookies/attributes/resources/path/one.html
new file mode 100644
index 0000000000..5ff90b9f15
--- /dev/null
+++ b/testing/web-platform/tests/cookies/attributes/resources/path/one.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>helper iframe for matching cookie path redirect tests</title>
+ <meta name=help href="http://tools.ietf.org/html/rfc6265#section-5.1.4">
+</head>
+<body>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+ <script src="/cookies/attributes/resources/path-redirect-shared.js"></script>
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/cookies/attributes/resources/path/three.html b/testing/web-platform/tests/cookies/attributes/resources/path/three.html
new file mode 100644
index 0000000000..5ff90b9f15
--- /dev/null
+++ b/testing/web-platform/tests/cookies/attributes/resources/path/three.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>helper iframe for matching cookie path redirect tests</title>
+ <meta name=help href="http://tools.ietf.org/html/rfc6265#section-5.1.4">
+</head>
+<body>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+ <script src="/cookies/attributes/resources/path-redirect-shared.js"></script>
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/cookies/attributes/resources/path/two.html b/testing/web-platform/tests/cookies/attributes/resources/path/two.html
new file mode 100644
index 0000000000..5ff90b9f15
--- /dev/null
+++ b/testing/web-platform/tests/cookies/attributes/resources/path/two.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>helper iframe for matching cookie path redirect tests</title>
+ <meta name=help href="http://tools.ietf.org/html/rfc6265#section-5.1.4">
+</head>
+<body>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+ <script src="/cookies/attributes/resources/path-redirect-shared.js"></script>
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/cookies/attributes/resources/pathfakeout.html b/testing/web-platform/tests/cookies/attributes/resources/pathfakeout.html
new file mode 100644
index 0000000000..5ff90b9f15
--- /dev/null
+++ b/testing/web-platform/tests/cookies/attributes/resources/pathfakeout.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>helper iframe for matching cookie path redirect tests</title>
+ <meta name=help href="http://tools.ietf.org/html/rfc6265#section-5.1.4">
+</head>
+<body>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+ <script src="/cookies/attributes/resources/path-redirect-shared.js"></script>
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/cookies/attributes/resources/pathfakeout/one.html b/testing/web-platform/tests/cookies/attributes/resources/pathfakeout/one.html
new file mode 100644
index 0000000000..5ff90b9f15
--- /dev/null
+++ b/testing/web-platform/tests/cookies/attributes/resources/pathfakeout/one.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>helper iframe for matching cookie path redirect tests</title>
+ <meta name=help href="http://tools.ietf.org/html/rfc6265#section-5.1.4">
+</head>
+<body>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+ <script src="/cookies/attributes/resources/path-redirect-shared.js"></script>
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/cookies/attributes/resources/secure-non-secure-child.html b/testing/web-platform/tests/cookies/attributes/resources/secure-non-secure-child.html
new file mode 100644
index 0000000000..e5d68b8d07
--- /dev/null
+++ b/testing/web-platform/tests/cookies/attributes/resources/secure-non-secure-child.html
@@ -0,0 +1,85 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Test cookie secure attribute parsing (on non-secure page)</title>
+ <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2.5">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+ </head>
+ <body>
+ <script>
+ setTestContextUsingRootWindow();
+ // These tests are the non-secure analog to secure.https.html.
+ // They're not in the /cookies/attributes folder because they shouldn't
+ // be run by themselves. Instead, /cookies/attributes/secure.https.html
+ // opens this in a non-secure window.
+ const secureNonSecureTests = [
+ {
+ cookie: "test=1; Secure",
+ expected: "",
+ name: "(non-secure) Ignore cookie for Secure attribute",
+ },
+ {
+ cookie: "test=2; seCURe",
+ expected: "",
+ name: "(non-secure) Ignore cookie for seCURe attribute",
+ },
+ {
+ cookie: "test=3; Secure=",
+ expected: "",
+ name: "(non-secure) Ignore cookie for for Secure= attribute",
+ },
+ {
+ cookie: "test=4; Secure=aaaa",
+ expected: "",
+ name: "(non-secure) Ignore cookie for Secure=aaaa",
+ },
+ {
+ cookie: "test=5; Secure =aaaaa",
+ expected: "",
+ name: "(non-secure) Ignore cookie for Secure space equals",
+ },
+ {
+ cookie: "test=6; Secure= aaaaa",
+ expected: "",
+ name: "(non-secure) Ignore cookie for Secure equals space",
+ },
+ {
+ cookie: "test=7; Secure",
+ expected: "",
+ name: "(non-secure) Ignore cookie for spaced Secure",
+ },
+ {
+ cookie: "test=8; Secure ;",
+ expected: "",
+ name: "(non-secure) Ignore cookie for space Secure with ;",
+ },
+ {
+ cookie: "__Secure-test=9; Secure",
+ expected: "",
+ name: "(non-secure) Ignore cookie with __Secure- prefix and Secure",
+ },
+ {
+ cookie: "__Secure-test=10",
+ expected: "",
+ name: "(non-secure) Ignore cookie with __Secure- prefix and without Secure",
+ },
+ // This is really a test that the cookie name isn't URL-decoded, but this
+ // is here to be next to the other __Secure- prefix tests.
+ {
+ cookie: "__%53ecure-test=11",
+ expected: "__%53ecure-test=11",
+ name: "(non-secure) Cookie returned with __%53ecure- prefix and without Secure",
+ },
+ ];
+
+ for (const test of secureNonSecureTests) {
+ httpCookieTest(test.cookie, test.expected, test.name, test.defaultPath);
+ }
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/cookies/attributes/secure-non-secure.html b/testing/web-platform/tests/cookies/attributes/secure-non-secure.html
new file mode 100644
index 0000000000..578cdbc1f7
--- /dev/null
+++ b/testing/web-platform/tests/cookies/attributes/secure-non-secure.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Test cookie secure attribute parsing (non-secure origin)</title>
+ <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2.5">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-helper.sub.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ test(t => {
+ const win = window.open(`${INSECURE_ORIGIN}/cookies/attributes/resources/secure-non-secure-child.html`);
+ fetch_tests_from_window(win);
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/cookies/attributes/secure.https.html b/testing/web-platform/tests/cookies/attributes/secure.https.html
new file mode 100644
index 0000000000..9308899694
--- /dev/null
+++ b/testing/web-platform/tests/cookies/attributes/secure.https.html
@@ -0,0 +1,65 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Test cookie secure attribute parsing</title>
+ <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2.5">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ const secureTests = [
+ {
+ cookie: "test=1; Secure",
+ expected: "test=1",
+ name: "Set cookie for Secure attribute",
+ },
+ {
+ cookie: "test=2; seCURe",
+ expected: "test=2",
+ name: "Set cookie for seCURe attribute",
+ },
+ {
+ cookie: "test=3; Secure=",
+ expected: "test=3",
+ name: "Set cookie for for Secure= attribute",
+ },
+ {
+ cookie: "test=4; Secure=aaaa",
+ expected: "test=4",
+ name: "Set cookie for Secure=aaaa",
+ },
+ {
+ cookie: "test=5; Secure =aaaaa",
+ expected: "test=5",
+ name: "Set cookie for Secure space equals",
+ },
+ {
+ cookie: "test=6; Secure= aaaaa",
+ expected: "test=6",
+ name: "Set cookie for Secure equals space",
+ },
+ {
+ cookie: "test=7; Secure",
+ expected: "test=7",
+ name: "Set cookie for spaced Secure",
+ },
+ {
+ cookie: "test=8; Secure ;",
+ expected: "test=8",
+ name: "Set cookie for space Secure with ;",
+ }
+ ];
+
+ for (const test of secureTests) {
+ httpCookieTest(test.cookie, test.expected, test.name, test.defaultPath);
+ }
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/cookies/cookie-enabled-noncookie-frame.html b/testing/web-platform/tests/cookies/cookie-enabled-noncookie-frame.html
new file mode 100644
index 0000000000..2dbbc0bbdc
--- /dev/null
+++ b/testing/web-platform/tests/cookies/cookie-enabled-noncookie-frame.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script>
+ var t = async_test("navigator.cookieEnabled behavior on frames without cookie access");
+ window.onmessage = t.step_func_done(ev => {
+ // Surprisingly, the legacy behavior here is to return true; this actually
+ // does match the spec definition since false is supposed to be returned
+ // when a document.cookie write is ignored --- and here it would throw
+ // a security exception, not be ignored.
+ assert_true(ev.data);
+ });
+
+ t.step(() => {
+ var iframe = document.createElement("iframe");
+ iframe.sandbox = "allow-scripts";
+ iframe.srcdoc = "<scr" + "ipt>" +
+ "window.onmessage = function() {" +
+ " parent.postMessage(navigator.cookieEnabled, '*'); " +
+ "}</scr" + "ipt>";
+ iframe.onload = function() {
+ iframe.contentWindow.postMessage({}, "*");
+ }
+ document.body.appendChild(iframe);
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-and-without-leading-period.sub.https.html b/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-and-without-leading-period.sub.https.html
new file mode 100644
index 0000000000..b5f770b848
--- /dev/null
+++ b/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-and-without-leading-period.sub.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/cookies/resources/cookie-helper.sub.js"></script>
+</head>
+<body>
+ <script>
+ //
+ // Set-Cookie: domain-attribute-host-with-and-without-leading-period=b; Path=/; Domain=.{{host}}
+ // Set-Cookie: domain-attribute-host-with-and-without-leading-period=c; Path=/; Domain={{host}}
+ //
+ const cookieName = "domain-attribute-host-with-and-without-leading-period";
+ // Clean up cookie at the end to avoid interfering with subsequent tests.
+ add_completion_callback(tests => document.cookie =
+ `${cookieName}=0; Path=/; Domain={{host}}; expires=01-jan-1970 00:00:00 GMT`);
+
+ test(t => {
+ assert_dom_cookie(cookieName, "c", true);
+ }, "Domain=.{{host}} => Second value available via `document.cookie`");
+
+ async_test(t => {
+ fetch("/cookies/resources/list.py", { credentials: "include" })
+ .then(t.step_func(r => r.json()))
+ .then(t.step_func_done(r => {
+ assert_equals(r[cookieName], "c");
+ }))
+ .catch(_ => assert_unreached);
+ }, "Domain=.{{host}} => Second value sent with same-origin requests.");
+
+ async_test(t => {
+ fetch(`${SECURE_SUBDOMAIN_ORIGIN}/cookies/resources/list.py`, { credentials: "include" })
+ .then(t.step_func(r => r.json()))
+ .then(t.step_func_done(r => {
+ assert_equals(r[cookieName], "c");
+ }))
+ .catch(_ => assert_unreached);
+ }, "Domain=.{{host}} => Second value sent with subdomain requests.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-and-without-leading-period.sub.https.html.sub.headers b/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-and-without-leading-period.sub.https.html.sub.headers
new file mode 100644
index 0000000000..77d3d8c0c4
--- /dev/null
+++ b/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-and-without-leading-period.sub.https.html.sub.headers
@@ -0,0 +1,2 @@
+Set-Cookie: domain-attribute-host-with-and-without-leading-period=b; Path=/; Domain=.{{host}}
+Set-Cookie: domain-attribute-host-with-and-without-leading-period=c; Path=/; Domain={{host}}
diff --git a/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-leading-period.sub.https.html b/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-leading-period.sub.https.html
new file mode 100644
index 0000000000..3ec52fd40b
--- /dev/null
+++ b/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-leading-period.sub.https.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/cookies/resources/cookie-helper.sub.js"></script>
+</head>
+<body>
+ <script>
+ //
+ // Set-Cookie: domain-attribute-host-with-leading-period=b; Path=/; Domain=.{{host}}
+ //
+ const cookieName = "domain-attribute-host-with-leading-period";
+ // Clean up cookie at the end to avoid interfering with subsequent tests.
+ add_completion_callback(tests => document.cookie =
+ `${cookieName}=0; Path=/; Domain=.{{host}}; expires=01-jan-1970 00:00:00 GMT`);
+
+ test(t => {
+ assert_dom_cookie(cookieName, "b", true);
+ }, "Domain=.{{host}} => available via `document.cookie`");
+
+ async_test(t => {
+ fetch("/cookies/resources/list.py", { credentials: "include" })
+ .then(t.step_func(r => r.json()))
+ .then(t.step_func_done(r => {
+ assert_equals(r[cookieName], "b");
+ }))
+ .catch(_ => assert_unreached);
+ }, "Domain=.{{host}} => sent with same-origin requests.");
+
+ async_test(t => {
+ fetch(`${SECURE_SUBDOMAIN_ORIGIN}/cookies/resources/list.py`, { credentials: "include" })
+ .then(t.step_func(r => r.json()))
+ .then(t.step_func_done(r => {
+ assert_equals(r[cookieName], "b");
+ }))
+ .catch(_ => assert_unreached);
+ }, "Domain=.{{host}} => sent with subdomain requests.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-leading-period.sub.https.html.sub.headers b/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-leading-period.sub.https.html.sub.headers
new file mode 100644
index 0000000000..7de4ae2e6a
--- /dev/null
+++ b/testing/web-platform/tests/cookies/domain/domain-attribute-host-with-leading-period.sub.https.html.sub.headers
@@ -0,0 +1 @@
+Set-Cookie: domain-attribute-host-with-leading-period=b; Path=/; Domain=.{{host}}
diff --git a/testing/web-platform/tests/cookies/domain/domain-attribute-idn-host.sub.https.html b/testing/web-platform/tests/cookies/domain/domain-attribute-idn-host.sub.https.html
new file mode 100644
index 0000000000..ae4bf3cbb3
--- /dev/null
+++ b/testing/web-platform/tests/cookies/domain/domain-attribute-idn-host.sub.https.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ </head>
+ <body>
+ <script>
+ let url = new URL(document.location);
+ url.host = "{{hosts[][élève]}}";
+ let url2 = new URL("support/idn-child.sub.https.html", url);
+ let child_window = window.open(url2.href);
+ fetch_tests_from_window(child_window);
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/cookies/domain/domain-attribute-matches-host.sub.https.html b/testing/web-platform/tests/cookies/domain/domain-attribute-matches-host.sub.https.html
new file mode 100644
index 0000000000..ac786dd882
--- /dev/null
+++ b/testing/web-platform/tests/cookies/domain/domain-attribute-matches-host.sub.https.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/cookies/resources/cookie-helper.sub.js"></script>
+</head>
+<body>
+ <script>
+ //
+ // Set-Cookie: domain-attribute-matches-host=b; Path=/; Domain={{host}}
+ //
+ const cookieName = "domain-attribute-matches-host";
+ // Clean up cookie at the end to avoid interfering with subsequent tests.
+ add_completion_callback(tests => document.cookie =
+ `${cookieName}=0; Path=/; Domain={{host}}; expires=01-jan-1970 00:00:00 GMT`);
+
+ test(t => {
+ assert_dom_cookie(cookieName, "b", true);
+ }, "Domain={{host}} => available via `document.cookie`");
+
+ async_test(t => {
+ fetch("/cookies/resources/list.py", { credentials: "include" })
+ .then(t.step_func(r => r.json()))
+ .then(t.step_func_done(r => {
+ assert_equals(r[cookieName], "b");
+ }))
+ .catch(_ => assert_unreached);
+ }, "Domain={{host}} => sent with same-origin requests.");
+
+ async_test(t => {
+ fetch(`${SECURE_SUBDOMAIN_ORIGIN}/cookies/resources/list.py`, { credentials: "include" })
+ .then(t.step_func(r => r.json()))
+ .then(t.step_func_done(r => {
+ assert_equals(r[cookieName], "b");
+ }))
+ .catch(_ => assert_unreached);
+ }, "Domain={{host}} => sent with subdomain requests.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/cookies/domain/domain-attribute-matches-host.sub.https.html.sub.headers b/testing/web-platform/tests/cookies/domain/domain-attribute-matches-host.sub.https.html.sub.headers
new file mode 100644
index 0000000000..8a2329e8c2
--- /dev/null
+++ b/testing/web-platform/tests/cookies/domain/domain-attribute-matches-host.sub.https.html.sub.headers
@@ -0,0 +1 @@
+Set-Cookie: domain-attribute-matches-host=b; Path=/; Domain={{host}}
diff --git a/testing/web-platform/tests/cookies/domain/domain-attribute-missing.sub.html b/testing/web-platform/tests/cookies/domain/domain-attribute-missing.sub.html
new file mode 100644
index 0000000000..44776ca629
--- /dev/null
+++ b/testing/web-platform/tests/cookies/domain/domain-attribute-missing.sub.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/cookies/resources/cookie-helper.sub.js"></script>
+</head>
+<body>
+ <script>
+ //
+ // Set-Cookie: domain-attribute-missing=b; Path=/
+ //
+ const cookieName = "domain-attribute-missing";
+ // Clean up cookie at the end to avoid interfering with subsequent tests.
+ add_completion_callback(tests => document.cookie =
+ `${cookieName}=0; Path=/; expires=01-jan-1970 00:00:00 GMT`);
+
+ test(t => {
+ assert_dom_cookie(cookieName, "b", true);
+ }, "No domain attribute => available via `document.cookie`");
+
+ async_test(t => {
+ fetch("/cookies/resources/list.py", { credentials: "include" })
+ .then(t.step_func(r => r.json()))
+ .then(t.step_func_done(r => {
+ assert_equals(r[cookieName], "b");
+ }))
+ .catch(_ => assert_unreached);
+ }, "No domain attribute => sent with same-origin requests.");
+
+ async_test(t => {
+ fetch(`${SECURE_SUBDOMAIN_ORIGIN}/cookies/resources/list.py`, { credentials: "include" })
+ .then(t.step_func(r => r.json()))
+ .then(t.step_func_done(r => {
+ assert_equals(r[cookieName], undefined);
+ }))
+ .catch(_ => assert_unreached);
+ }, "No domain attribute => not sent with subdomain requests.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/cookies/domain/domain-attribute-missing.sub.html.headers b/testing/web-platform/tests/cookies/domain/domain-attribute-missing.sub.html.headers
new file mode 100644
index 0000000000..3ee2833a45
--- /dev/null
+++ b/testing/web-platform/tests/cookies/domain/domain-attribute-missing.sub.html.headers
@@ -0,0 +1 @@
+Set-Cookie: domain-attribute-missing=b; Path=/
diff --git a/testing/web-platform/tests/cookies/domain/support/idn-child.sub.https.html b/testing/web-platform/tests/cookies/domain/support/idn-child.sub.https.html
new file mode 100644
index 0000000000..d3510959fb
--- /dev/null
+++ b/testing/web-platform/tests/cookies/domain/support/idn-child.sub.https.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+</head>
+<body>
+ <script>
+ async function assert_cookie(response, cookie) {
+ const get = await response.text();
+ assert_equals(get, cookie);
+ assert_equals(document.cookie, cookie);
+ }
+
+ async function assert_no_cookie(response) {
+ const get = await response.text();
+ assert_equals(get, "no cookies");
+ assert_equals(document.cookie, "");
+ }
+
+ promise_test(async t => {
+ t.add_cleanup(async () => { await fetch("idn.py?delete-utf8&host={{host}}") });
+ let response = await fetch("idn.py?set-utf8&host={{host}}");
+ assert_equals(await response.text(), "set");
+ response = await fetch("idn.py?get&host={{host}}");
+ await assert_no_cookie(response);
+ }, "UTF8-encoded IDN in domain attribute");
+
+ promise_test(async t => {
+ t.add_cleanup(async () => { await fetch("idn.py?delete-utf8-dot&host={{host}}") });
+ let response = await fetch("idn.py?set-utf8-dot&host={{host}}");
+ assert_equals(await response.text(), "set");
+ response = await fetch("idn.py?get&host={{host}}");
+ await assert_no_cookie(response);
+ }, "UTF8-encoded IDN with non-ASCII dot in domain attribute");
+
+ promise_test(async t => {
+ t.add_cleanup(async () => { await fetch("idn.py?delete-wrong-utf8&host={{host}}") });
+ let response = await fetch("idn.py?set-wrong-utf8&host={{host}}");
+ assert_equals(await response.text(), "set");
+ response = await fetch("idn.py?get&host={{host}}");
+ await assert_no_cookie(response);
+ }, "wrong UTF8-encoded IDN in domain attribute");
+
+ promise_test(async t => {
+ t.add_cleanup(async () => { await fetch("idn.py?delete-punycode&host={{host}}") });
+ let response = await fetch("idn.py?set-punycode&host={{host}}");
+ assert_equals(await response.text(), "set");
+ response = await fetch("idn.py?get&host={{host}}");
+ await assert_cookie(response, "punycode=set");
+ }, "punycode IDN in domain attribute");
+
+ promise_test(async t => {
+ t.add_cleanup(async () => { await fetch("idn.py?delete-wrong-punycode&host={{host}}") });
+ let response = await fetch("idn.py?set-wrong-punycode&host={{host}}");
+ assert_equals(await response.text(), "set");
+ response = await fetch("idn.py?get&host={{host}}");
+ await assert_no_cookie(response);
+ }, "wrong punycode IDN in domain attribute");
+
+ promise_test(async t => {
+ t.add_cleanup(async () => { await fetch("idn.py?delete-invalid-byte&host={{host}}") });
+ let response = await fetch("idn.py?set-invalid-byte&host={{host}}");
+ assert_equals(await response.text(), "set");
+ response = await fetch("idn.py?get&host={{host}}");
+ await assert_no_cookie(response);
+ }, "IDN with invalid UTF-8 bytes in domain attribute");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/cookies/domain/support/idn.py b/testing/web-platform/tests/cookies/domain/support/idn.py
new file mode 100644
index 0000000000..d75ed056f5
--- /dev/null
+++ b/testing/web-platform/tests/cookies/domain/support/idn.py
@@ -0,0 +1,61 @@
+# élève.
+utf8_subdomain = b"Domain=\xC3\xA9\x6C\xC3\xA8\x76\x65."
+# élève。
+utf8_dot_subdomain = b"Domain=\xC3\xA9\x6C\xC3\xA8\x76\x65\xE3\x80\x82"
+# élève.
+punycode_subdomain = b"Domain=xn--lve-6lad."
+# ÿlève.
+wrong_utf8_subdomain = b"Domain=\xC3\xBF\x6C\xC3\xA8\x76\x65."
+# ÿlève.
+wrong_punycode_subdomain = b"Domain=xn--lve-6la7i."
+# élève with invalid FF byte at the end
+invalid_byte_subdomain = b"Domain=\xC3\xA9\x6C\xC3\xA8\x76\x65\xFF."
+
+def main(request, response):
+ host = request.GET.get(b"host")
+
+ if b"set-utf8" in request.GET:
+ response.headers.append(b"Set-Cookie", b"utf8=set;" + utf8_subdomain + host)
+ response.content = "set"
+ if b"set-utf8-dot" in request.GET:
+ response.headers.append(b"Set-Cookie", b"utf8-dot=set;" + utf8_dot_subdomain + host)
+ response.content = "set"
+ elif b"set-wrong-utf8" in request.GET:
+ response.headers.append(b"Set-Cookie", b"wrong-utf8=set;" + wrong_utf8_subdomain + host)
+ response.content = "set"
+ elif b"set-punycode" in request.GET:
+ response.headers.append(b"Set-Cookie", b"punycode=set;" + punycode_subdomain + host)
+ response.content = "set"
+ elif b"set-wrong-punycode" in request.GET:
+ response.headers.append(b"Set-Cookie", b"wrong-punycode=set;" + wrong_punycode_subdomain + host)
+ response.content = "set"
+ elif b"set-invalid-byte" in request.GET:
+ response.headers.append(b"Set-Cookie", b"invalid-byte=set;" + invalid_byte_subdomain + host)
+ response.content = "set"
+
+ elif b"get" in request.GET:
+ if b"Cookie" in request.headers:
+ response.content = request.headers[b"Cookie"]
+ else:
+ response.content = "no cookies"
+
+ elif b"delete-utf8" in request.GET:
+ response.headers.append(b"Set-Cookie", b"utf8=unset;Max-Age=0;" + utf8_subdomain + host)
+ response.content = "delete"
+ elif b"delete-utf8-dot" in request.GET:
+ response.headers.append(b"Set-Cookie", b"utf8-dot=unset;Max-Age=0;" + utf8_dot_subdomain + host)
+ response.content = "delete"
+ elif b"delete-wrong-utf8" in request.GET:
+ response.headers.append(b"Set-Cookie", b"wrong-utf8=unset;Max-Age=0;" + wrong_utf8_subdomain + host)
+ response.content = "delete"
+ elif b"delete-punycode" in request.GET:
+ response.headers.append(b"Set-Cookie", b"punycode=unset;Max-Age=0;" + punycode_subdomain + host)
+ response.content = "delete"
+ elif b"delete-wrong-punycode" in request.GET:
+ response.headers.append(b"Set-Cookie", b"wrong-punycode=unset;Max-Age=0;" + wrong_punycode_subdomain + host)
+ response.content = "delete"
+ elif b"delete-invalid-byte" in request.GET:
+ response.headers.append(b"Set-Cookie", b"invalid-byte=unset;Max-Age=0;" + invalid_byte_subdomain + host)
+ response.content = "delete"
+
+ response.headers.append(b"Content-Type", b"text/plain")
diff --git a/testing/web-platform/tests/cookies/encoding/charset.html b/testing/web-platform/tests/cookies/encoding/charset.html
new file mode 100644
index 0000000000..55fcc58aea
--- /dev/null
+++ b/testing/web-platform/tests/cookies/encoding/charset.html
@@ -0,0 +1,55 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Test utf-8 and ASCII cookie parsing</title>
+ <meta name=help href="https://tools.ietf.org/html/rfc6265#section-4.1.1">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ const charsetTests = [
+ {
+ cookie: "test=1春节回家路·春运完全手册",
+ expected: "test=1春节回家路·春运完全手册",
+ name: "ASCII name and utf-8 value",
+ },
+ {
+ cookie: "тест=2",
+ expected: "тест=2",
+ name: "utf-8 name and ASCII value",
+ },
+ {
+ cookie: 'test="3春节回家路·春运完全手册"',
+ expected: 'test="3春节回家路·春运完全手册"',
+ name: "ASCII name and quoted utf-8 value",
+ },
+ {
+ cookie: "春节回=4家路·春运完全手册",
+ expected: "春节回=4家路·春运完全手册",
+ name: "utf-8 name and value",
+ },
+ {
+ cookie: '"春节回=5家路·春运完全手册"',
+ expected: '"春节回=5家路·春运完全手册"',
+ name: "quoted utf-8 name and value",
+ },
+ {
+ cookie: "春节回=6家路·春运; 完全手册",
+ expected: "春节回=6家路·春运",
+ name: "utf-8 name and value, with (invalid) utf-8 attribute",
+ },
+ ];
+
+ for (const test of charsetTests) {
+ httpCookieTest(test.cookie, test.expected, test.name);
+ }
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/cookies/meta-blocked.html b/testing/web-platform/tests/cookies/meta-blocked.html
new file mode 100644
index 0000000000..1b86e65c87
--- /dev/null
+++ b/testing/web-platform/tests/cookies/meta-blocked.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<head>
+ <meta http-equiv="set-cookie" content="meta-set-cookie=1">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <script>
+ test(t => {
+ assert_equals(document.cookie.indexOf('meta-set-cookie'), -1);
+ }, "Cookie is not set from `<meta>`.");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/cookies/name/name-ctl.html b/testing/web-platform/tests/cookies/name/name-ctl.html
new file mode 100644
index 0000000000..6ff2305b3a
--- /dev/null
+++ b/testing/web-platform/tests/cookies/name/name-ctl.html
@@ -0,0 +1,63 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Test cookie name parsing with control characters</title>
+ <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ // Tests for control characters (CTLs) in a cookie's name.
+ // CTLs are defined by RFC 5234 to be %x00-1F / %x7F.
+ const CTLS = getCtlCharacters();
+
+ // All CTLs, with the exception of %x09 (the tab character), should
+ // cause the cookie to be rejected.
+ for (const ctl of CTLS) {
+ if (ctl.code === 0x09) {
+ domCookieTest(
+ `test${ctl.code}${ctl.chr}name=${ctl.code}`,
+ `test${ctl.code}${ctl.chr}name=${ctl.code}`,
+ `Cookie with %x${ctl.code.toString(16)} in name is accepted (DOM).`);
+ } else {
+ domCookieTest(
+ `test${ctl.code}${ctl.chr}name=${ctl.code}`,
+ '',
+ `Cookie with %x${ctl.code.toString(16)} in name is rejected (DOM).`);
+ }
+ }
+
+ // Note that per RFC 9110, %x00, %x0A, and %x0D characters in the HTTP
+ // header MUST either cause the HTTP message to be rejected or be
+ // replaced with %x20 (space) characters. Both cases will result in a
+ // passing test here. For more info, see:
+ // https://www.rfc-editor.org/rfc/rfc9110.html#section-5.5
+ for (const ctl of CTLS) {
+ if (ctl.code === 0x09) {
+ httpCookieTest(
+ `test${ctl.code}${ctl.chr}name=${ctl.code}`,
+ `test${ctl.code}${ctl.chr}name=${ctl.code}`,
+ `Cookie with %x${ctl.code.toString(16)} in name is accepted (HTTP).`);
+ } else if (ctl.code === 0x00 || ctl.code === 0x0A || ctl.code === 0x0D) {
+ httpCookieTest(
+ `test${ctl.code}${ctl.chr}name=${ctl.code}`,
+ `test${ctl.code} name=${ctl.code}`,
+ `Cookie with %x${ctl.code.toString(16)} in name is rejected or modified (HTTP).`,
+ /* defaultPath */ true, /* allowFetchFailure */ true);
+ } else {
+ httpCookieTest(
+ `test${ctl.code}${ctl.chr}name=${ctl.code}`,
+ '',
+ `Cookie with %x${ctl.code.toString(16)} in name is rejected (HTTP).`);
+ }
+ }
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/cookies/name/name.html b/testing/web-platform/tests/cookies/name/name.html
new file mode 100644
index 0000000000..d7fe05560e
--- /dev/null
+++ b/testing/web-platform/tests/cookies/name/name.html
@@ -0,0 +1,169 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Test cookie name parsing</title>
+ <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ const nameTests = [
+ {
+ cookie: "test1=; path = /",
+ expected: "test1=",
+ name: "Set valueless cookie to its name with empty value",
+ defaultPath: false,
+ },
+ {
+ cookie: "=test=2",
+ expected: "test=2",
+ name: "Set a nameless cookie (that has an = in its value)",
+ },
+ {
+ cookie: "===test=2b",
+ expected: "==test=2b",
+ name: "Set a nameless cookie (that has multiple ='s in its value)",
+ },
+ {
+ cookie: "=test2c",
+ expected: "test2c",
+ name: "Set a nameless cookie",
+ },
+ {
+ cookie: "test =3",
+ expected: "test=3",
+ name: "Remove trailing WSP characters from the name string",
+ },
+ {
+ cookie: " test=4",
+ expected: "test=4",
+ name: "Remove leading WSP characters from the name string",
+ },
+ {
+ cookie: ['"test=5"=test', '"test=5'],
+ expected: '"test=5',
+ name: "Only return the new cookie (with the same name)",
+ },
+ {
+ cookie: "test6;cool=dude",
+ expected: "test6",
+ name: "Ignore invalid attributes after nameless cookie",
+ },
+ {
+ cookie: "$Version=1; test=7",
+ expected: "$Version=1",
+ name: "Ignore invalid attributes after valid name (that looks like Cookie2 Version attribute)",
+ },
+ {
+ cookie: "test test=8",
+ expected: "test test=8",
+ name: "Set a cookie that has whitespace in its name",
+ },
+ {
+ cookie: '"test9;test"=9',
+ expected: '"test9',
+ name: "Set a nameless cookie ignoring characters after first ;",
+ },
+ {
+ cookie: '"test\"10;baz"=qux',
+ expected: '"test\"10',
+ name: "Set a nameless cookie ignoring characters after first ; (2)",
+ },
+ {
+ cookie: ["=test=11", "test11"],
+ expected: "test11",
+ name: "Return the most recent nameless cookie",
+ },
+ {
+ cookie: ["test11", "test11a"],
+ expected: "test11a",
+ name: "Return the most recent nameless cookie, without leading =",
+ },
+ {
+ cookie: ["test11", "test11a", "=test11b"],
+ expected: "test11b",
+ name: "Return the most recent nameless cookie, even if preceded by =",
+ },
+ {
+ cookie: ["test11", "test11a", "=test11b", "test=11c"],
+ expected: "test11b; test=11c",
+ name: "Return the most recent nameless cookie, even if preceded by =, in addition to other valid cookie",
+ },
+ {
+ cookie: ["test12=11", "test12=12"],
+ expected: "test12=12",
+ name: "Use last value for cookies with identical names",
+ },
+ {
+ cookie: ["testA=13", "testB=13"],
+ expected: "testA=13; testB=13",
+ name: "Keep first-in, first-out name order",
+ },
+ {
+ cookie: ["a=test14", "z=test14"],
+ expected: "a=test14; z=test14",
+ name: "Keep first-in, first-out single-char name order",
+ },
+ {
+ cookie: ["z=test15", "a=test15"],
+ expected: "z=test15; a=test15",
+ name: "Keep non-alphabetic first-in, first-out name order",
+ },
+ {
+ cookie: "z=test16, a=test16",
+ expected: "z=test16, a=test16",
+ name: "Keep first-in, first-out order if comma-separated",
+ },
+ {
+ cookie: ["testA=16", "=test16", "testB=16"],
+ expected: "testA=16; test16; testB=16",
+ name: "Set nameless cookie, given `Set-Cookie: =test16`",
+ },
+ {
+ cookie: ["test17a", "test17b"],
+ expected: "test17b",
+ name: "Overwrite nameless cookie",
+ },
+ {
+ cookie: ["=__Secure-abc=123", "=__Host-abc=123", "=__SeCuRe-abc=123", "=__HoSt-abc=123", "__Secure-abc", "__Host-abc", "__SeCuRe-abc", "__HoSt-abc"],
+ expected: "",
+ name: "Ignore nameless cookies that impersonate cookie prefixes",
+ },
+ {
+ cookie: "=",
+ expected: "",
+ name: "Ignore cookie with empty name and empty value",
+ },
+ {
+ cookie: "",
+ expected: "",
+ name: "Ignore cookie with no name or value",
+ },
+ {
+ cookie: "%74%65%73%74=20",
+ expected: "%74%65%73%74=20",
+ name: "URL-encoded cookie name is not decoded",
+ },
+ ];
+
+ for (const test of nameTests) {
+ httpCookieTest(test.cookie, test.expected, test.name);
+ }
+
+ for (const name of ["a", "1", "$", "!a", "@a", "#a", "$a", "%a",
+ "^a", "&a", "*a", "(a", ")a", "-a", "_a", "+",
+ '"a', '"a=b"'
+ ]) {
+ const cookie = `${name}=test`;
+ httpCookieTest(cookie, cookie, `Name is set as expected for ${name}=test`);
+ }
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/cookies/navigated-away.html b/testing/web-platform/tests/cookies/navigated-away.html
new file mode 100644
index 0000000000..bd89142d32
--- /dev/null
+++ b/testing/web-platform/tests/cookies/navigated-away.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+ <iframe id="if" src="about:blank"></iframe>
+ <script>
+ var t = async_test("document.cookie behavior on documents without browser context");
+ t.add_cleanup(function() {
+ document.cookie = "nav_away_test=yes;max-age=0";
+ });
+
+ function step2() {
+ t.step(function() {
+ // Get from saved doc should fail.
+ assert_equals(window.iframeDoc.cookie, "");
+
+ // Try set from saved doc, should do nothing.
+ window.iframeDoc.cookie = "nav_away_test=second";
+ assert_equals(window.iframeDoc.cookie, "");
+ assert_not_equals(document.cookie.indexOf("nav_away_test=yes"), -1);
+ });
+ t.done();
+ }
+
+ t.step(function() {
+ document.cookie = "nav_away_test=yes";
+ var iframe = document.getElementById("if");
+ // Save original document.
+ window.iframeDoc = iframe.contentDocument;
+ assert_not_equals(window.iframeDoc.cookie.indexOf("nav_away_test=yes"), -1);
+
+ // Navigate away.
+ iframe.onload = step2;
+ iframe.contentWindow.location = "/common/blank.html";
+ })
+ </script>
+</body>
diff --git a/testing/web-platform/tests/cookies/ordering/ordering.sub.html b/testing/web-platform/tests/cookies/ordering/ordering.sub.html
new file mode 100644
index 0000000000..2c4db4e20f
--- /dev/null
+++ b/testing/web-platform/tests/cookies/ordering/ordering.sub.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Test cookie ordering</title>
+ <meta name=help href="https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-5.5">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ </head>
+ </head>
+ <body>
+ <script>
+ const port = "{{ports[http][0]}}";
+ const wwwHost = "{{domains[www]}}";
+
+ test(t => {
+ const win = window.open(`http://${wwwHost}:${port}/cookies/ordering/resources/ordering-child.sub.html`);
+ fetch_tests_from_window(win);
+ });
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/cookies/ordering/resources/ordering-child.sub.html b/testing/web-platform/tests/cookies/ordering/resources/ordering-child.sub.html
new file mode 100644
index 0000000000..b40ec64814
--- /dev/null
+++ b/testing/web-platform/tests/cookies/ordering/resources/ordering-child.sub.html
@@ -0,0 +1,76 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Test cookie ordering</title>
+ <meta name=help href="https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-5.5">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ const host = "{{host}}";
+ const wwwHost = "{{domains[www]}}";
+
+ const orderingTests = [
+ {
+ cookie: [
+ "testA=1",
+ "testB=1; path=/cookies",
+ "testC=1; path=/",
+ "testD=1; path=/cooking",
+ `testE=1; domain=.${host}; path=/`,
+ `testF=1; domain=.${host}; path=/cookies/attributes`,
+ ],
+ expected: "testF=1; testB=1; testC=1; testE=1",
+ name: "Cookies with longer path attribute values are ordered before shorter ones",
+ location: "/cookies/attributes/resources/path/one.html",
+ },
+ {
+ cookie: [
+ "testA=2",
+ "testB=2; path=/cookies/attributes/resources",
+ "testC=2; path=/",
+ "testD=2; path=/cooking",
+ `testE=2; domain=.${host}`,
+ `testF=2; domain=.${host}; path=/cookies/attributes`,
+ `testG=2; domain=.${host}; path=/cookies/attributes/resources/path`,
+ "testH=2; path=/cookies",
+ ],
+ expected: "testG=2; testB=2; testF=2; testH=2; testC=2",
+ name: "Cookies with longer path attribute values are ordered before shorter ones (2)",
+ location: "/cookies/attributes/resources/path/one.html",
+ },
+ {
+ cookie: [
+ "testA=3; path=/cookies/attributes/resources/path",
+ "testB=3; path=/cookies/attributes/resources/path/one.html",
+ "testC=3; path=/cookies/attributes",
+ ],
+ expected: "testB=3; testA=3; testC=3",
+ name: "Cookies with longer paths are listed before cookies with shorter paths",
+ location: "/cookies/attributes/resources/path/one.html",
+ },
+ {
+ cookie: [
+ "testZ=4; path=/cookies",
+ "testB=4; path=/cookies/attributes/resources/path",
+ "testA=4; path=/cookies",
+ ],
+ expected: "testB=4; testZ=4; testA=4",
+ name: "For equal length paths, list the cookie with an earlier creation time first",
+ location: "/cookies/attributes/resources/path/one.html",
+ },
+ ];
+
+ for (const test of orderingTests) {
+ httpRedirectCookieTest(test.cookie, test.expected, test.name,
+ test.location);
+ }
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/cookies/partitioned-cookies/partitioned-cookies.tentative.https.html b/testing/web-platform/tests/cookies/partitioned-cookies/partitioned-cookies.tentative.https.html
new file mode 100644
index 0000000000..deab669101
--- /dev/null
+++ b/testing/web-platform/tests/cookies/partitioned-cookies/partitioned-cookies.tentative.https.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<meta name="timeout" content="long">
+<meta name="help" href="https://github.com/WICG/CHIPS#chips-cookies-having-independent-partitioned-state">
+<title>Test partitioned cookies</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script src="/cookies/partitioned-cookies/resources/test-helpers.js"></script>
+
+<body>
+<script>
+
+document.body.onload = async () => {
+ // First, the test sets a SameSite=None;Partitioned; cookie.
+ const attributes = "Secure;Path=/;SameSite=None;Partitioned";
+ const httpCookieName = "__Host-pchttp";
+ await credFetch(
+ `${self.origin}/cookies/resources/set.py?${httpCookieName}=foobar;${
+ attributes}`);
+
+ // Set another partitioned cookie using document.cookie.
+ const domCookieName = "__Host-pcdom";
+ document.cookie = `${domCookieName}=foobar;${attributes}`;
+
+ // Set another partitioned cookie using the CookieStore API, if supported.
+ if (window.cookieStore) {
+ const cookieStoreCookieName = "__Host-pccookiestore";
+ await cookieStore.set({
+ name: cookieStoreCookieName,
+ value: "foobar",
+ path: "/",
+ sameSite: "none",
+ partitioned: true,
+ });
+ }
+
+ const cookieNames = getCookieNames();
+
+ // Verify that the cookies are sent in requests from this top-level site.
+ testHttpPartitionedCookies({
+ origin: self.origin,
+ cookieNames,
+ expectsCookie: true,
+ });
+
+ // Verify that the cookies are exposed to the DOM on this top-level site.
+ testDomPartitionedCookies({
+ cookieNames,
+ expectsCookie: true,
+ });
+ testCookieStorePartitionedCookies({
+ cookieNames,
+ expectsCookie: true,
+ });
+
+ // Open a cross-site window which will make a request to this window's origin.
+ // If partitioned cookies are disabled, then the cookies set above will still
+ // be accessible.
+ // If partitioned cookies are enabled, then the cookies should not be
+ // accessible to their origin in a window with a different top-level site.
+ const crossSiteUrl = new URL(
+ `./resources/partitioned-cookies-cross-site-window.html?origin=${
+ encodeURIComponent(self.origin)}`,
+ get_host_info().HTTPS_NOTSAMESITE_ORIGIN + self.location.pathname);
+ const popup = window.open(crossSiteUrl);
+ fetch_tests_from_window(popup);
+};
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/cookies/partitioned-cookies/resources/partitioned-cookies-cross-site-embed.html b/testing/web-platform/tests/cookies/partitioned-cookies/resources/partitioned-cookies-cross-site-embed.html
new file mode 100644
index 0000000000..05a99626dc
--- /dev/null
+++ b/testing/web-platform/tests/cookies/partitioned-cookies/resources/partitioned-cookies-cross-site-embed.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<meta name="timeout" content="long">
+<title>Test site embedded in a cross-site context</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script src="/cookies/partitioned-cookies/resources/test-helpers.js"></script>
+<body>
+<script>
+
+const cookieNames = getCookieNames();
+
+testDomPartitionedCookies({
+ cookieNames,
+ expectsCookie: false,
+});
+
+testCookieStorePartitionedCookies({
+ cookieNames,
+ expectsCookie: false,
+});
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/cookies/partitioned-cookies/resources/partitioned-cookies-cross-site-window.html b/testing/web-platform/tests/cookies/partitioned-cookies/resources/partitioned-cookies-cross-site-window.html
new file mode 100644
index 0000000000..ca1a27c8a0
--- /dev/null
+++ b/testing/web-platform/tests/cookies/partitioned-cookies/resources/partitioned-cookies-cross-site-window.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<meta name="timeout" content="long">
+<title>Cross-site window</title>
+<script src="/resources/testharness.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script src="/cookies/partitioned-cookies/resources/test-helpers.js"></script>
+
+<body>
+<script>
+
+let origin;
+
+// Test that parent window passed its origin in the URL parameters correctly.
+test(() => {
+ assert_true(window.location.search.startsWith("?origin="));
+ origin = decodeURIComponent(window.location.search.slice(
+ window.location.search.indexOf("?origin=") + 8));
+}, "Cross-site window opened correctly");
+
+// Test that the request to the parent window's origin does not contain the
+// partitioned cookie.
+testHttpPartitionedCookies({
+ origin,
+ cookieNames: getCookieNames(),
+ expectsCookie: false,
+});
+
+// Create a cross-site <iframe> which embeds the cookies' origin into this
+// page.
+const iframe = document.createElement("iframe");
+const url = new URL(
+ "/cookies/partitioned-cookies/resources/" +
+ "partitioned-cookies-cross-site-embed.html",
+ origin);
+iframe.src = String(url);
+document.body.appendChild(iframe);
+
+fetch_tests_from_window(iframe.contentWindow);
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/cookies/partitioned-cookies/resources/test-helpers.js b/testing/web-platform/tests/cookies/partitioned-cookies/resources/test-helpers.js
new file mode 100644
index 0000000000..0ecaa63c39
--- /dev/null
+++ b/testing/web-platform/tests/cookies/partitioned-cookies/resources/test-helpers.js
@@ -0,0 +1,64 @@
+// Test that a partitioned cookie set by |origin| with name |cookieName| is
+// or is not sent in a request to |origin|.
+//
+// If |expectsCookie| is true, then the test cookie should be present in the
+// request.
+function testHttpPartitionedCookies({origin, cookieNames, expectsCookie}) {
+ promise_test(async () => {
+ const resp = await credFetch(`${origin}/cookies/resources/list.py`);
+ const cookies = await resp.json();
+ for (const cookieName of cookieNames) {
+ assert_equals(
+ cookies.hasOwnProperty(cookieName), expectsCookie,
+ getPartitionedCookieAssertDesc(expectsCookie, cookieName));
+ }
+ }, getPartitionedCookieTestName(expectsCookie, "HTTP"));
+}
+
+function getPartitionedCookieTestName(expectsCookie, cookieType) {
+ if (expectsCookie) {
+ return "Partitioned cookies accessible on the top-level site they are " +
+ `created in via ${cookieType}`;
+ }
+ return "Partitioned cookies are not accessible on a different top-level " +
+ `site via ${cookieType}`;
+}
+
+function getPartitionedCookieAssertDesc(expectsCookie, cookieName) {
+ if (expectsCookie) {
+ return `Expected ${cookieName} to be available on the top-level site it ` +
+ "was created in";
+ }
+ return `Expected ${cookieName} to not be available on a different ` +
+ "top-level site";
+}
+
+function testDomPartitionedCookies({cookieNames, expectsCookie}) {
+ test(() => {
+ for (const cookieName of cookieNames) {
+ assert_equals(
+ document.cookie.includes(cookieName), expectsCookie,
+ getPartitionedCookieAssertDesc(expectsCookie, cookieName));
+ }
+ }, getPartitionedCookieTestName(expectsCookie, "DOM"));
+}
+
+function testCookieStorePartitionedCookies({cookieNames, expectsCookie}) {
+ if (!window.cookieStore) return;
+ promise_test(async () => {
+ const cookies = await cookieStore.getAll({partitioned: true});
+ for (const cookieName of cookieNames) {
+ assert_equals(
+ !!cookies.find(c => c.name === cookieName), expectsCookie,
+ getPartitionedCookieAssertDesc(expectsCookie, cookieName));
+ }
+ }, getPartitionedCookieTestName(expectsCookie, "CookieStore"));
+}
+
+function getCookieNames() {
+ const cookieNames = ["__Host-pchttp", "__Host-pcdom"];
+ if (window.cookieStore) {
+ cookieNames.push("__Host-pccookiestore");
+ }
+ return cookieNames;
+}
diff --git a/testing/web-platform/tests/cookies/path/default.html b/testing/web-platform/tests/cookies/path/default.html
new file mode 100644
index 0000000000..dbe99a7ee5
--- /dev/null
+++ b/testing/web-platform/tests/cookies/path/default.html
@@ -0,0 +1,51 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Test for default cookie path</title>
+ <meta name=help href="http://tools.ietf.org/html/rfc6265#section-5.1.4">
+
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+</head>
+<body>
+<div id=log></div>
+
+<script>
+var body = document.getElementsByTagName('body')[0];
+var createIframe = function (src, done) {
+ var iframe = document.createElement('iframe');
+ iframe.src = src;
+ body.appendChild(iframe);
+ iframe.onload = function () {
+ done(iframe);
+ };
+};
+
+async_test(function (t) {
+ var iframe;
+ var verify = t.step_func(function () {
+ assert_true(
+ !!iframe.contentWindow.isCookieSet('cookies-path-default'),
+ 'cookie can be retrieved from expected path'
+ );
+ iframe.contentWindow.expireCookies().then(t.step_func(function () {
+ assert_false(
+ !!iframe.contentWindow.isCookieSet('cookies-path-default'),
+ 'cookie can be referenced using the expected path'
+ );
+ t.done();
+ }));
+ });
+
+ createIframe('/cookies/resources/echo-cookie.html', t.step_func(function (_iframe) {
+ iframe = _iframe;
+
+ createIframe('/cookies/resources/set.py?cookies-path-default=1', verify);
+ }));
+});
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/cookies/path/match.html b/testing/web-platform/tests/cookies/path/match.html
new file mode 100644
index 0000000000..d517836e4d
--- /dev/null
+++ b/testing/web-platform/tests/cookies/path/match.html
@@ -0,0 +1,113 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>tests for matching cookie paths</title>
+ <meta name="timeout" content="long">
+ <meta name=help href="http://tools.ietf.org/html/rfc6265#section-5.1.4">
+
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/testharness-helpers.js"></script>
+</head>
+<body>
+<div id=log></div>
+
+<script>
+var body = document.getElementsByTagName('body')[0];
+var createIframeThen = function (callback) {
+ var iframe = document.createElement('iframe');
+ iframe.src = "/cookies/resources/echo-cookie.html";
+ body.appendChild(iframe);
+ iframe.onload = callback;
+ return iframe;
+};
+var testCookiePathFromDOM = function (testCase, test) {
+ var iframe = createIframeThen(test.step_func(function () {
+ iframe.contentWindow.setCookie('dom-' + testCase.name, testCase.path);
+ var cookieSet = iframe.contentWindow.isCookieSet('dom-' + testCase.name, testCase.path);
+ if (testCase.match === false) {
+ assert_equals(cookieSet, null);
+ } else {
+ assert_not_equals(cookieSet, null, "Cookie path from DOM should not be `null`");
+ }
+ iframe.contentWindow.expireCookies().then(() => test.done());
+ }));
+};
+var testCookiePathFromHeader = function (testCase, test) {
+ var iframe = createIframeThen(test.step_func(function () {
+ iframe.contentWindow.fetchCookieThen('header-' + testCase.name, testCase.path).then(test.step_func(function (response) {
+ assert_true(response.ok);
+ var cookieSet = iframe.contentWindow.isCookieSet('header-' + testCase.name, testCase.path);
+ iframe.contentWindow.expireCookies().then(test.step_func(function () {
+ if (testCase.match === false) {
+ assert_equals(cookieSet, null);
+ } else {
+ assert_not_equals(cookieSet, null, "Cookie path from header should not be `null`");
+ }
+ test.done();
+ }));
+ })).catch(test.unreached_func());
+ }));
+};
+
+var tests = [{
+ "name": "match-slash",
+ "path": "/",
+}, {
+ "name": "match-page",
+ "path": "match.html",
+}, {
+ "name": "match-prefix",
+ "path": "cookies",
+}, {
+ "name": "match-slash-prefix",
+ "path": "/cookies",
+}, {
+ "name": "match-slash-prefix-slash",
+ "path": "/cookies/",
+}, {
+ "name": "match-exact-page",
+ "path": "/cookies/resources/echo-cookie.html",
+}, {
+ "name": "no-match",
+ "path": "/cook",
+ "match": false
+}, {
+ "name": "no-match-subpath",
+ "path": "/w/",
+ "match": false
+}];
+
+var domTests = tests.map(function (testCase) {
+ var testName = "`document.cookie` on /cookies/resources/echo-cookie.html sets cookie with path: " + testCase.path;
+ if (testCase.match === false) {
+ testName = "`document.cookie` on /cookies/resources/echo-cookie.html DOES NOT set cookie for path: " + testCase.path;
+ }
+ return [
+ testName,
+ function () {
+ testCookiePathFromDOM(testCase, this);
+ }
+ ];
+});
+
+var headerTests = tests.map(function (testCase) {
+ var testName = "`Set-Cookie` on /cookies/resources/echo-cookie.html sets cookie with path: " + testCase.path;
+ if (testCase.match === false) {
+ testName = "`Set-Cookie` on /cookies/resources/echo-cookie.html DOES NOT set cookie for path: " + testCase.path;
+ }
+ return [
+ testName,
+ function () {
+ testCookiePathFromHeader(testCase, this);
+ }
+ ];
+});
+
+executeTestsSerially(domTests.concat(headerTests));
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/cookies/prefix/__host.document-cookie.html b/testing/web-platform/tests/cookies/prefix/__host.document-cookie.html
new file mode 100644
index 0000000000..2aeb0901f6
--- /dev/null
+++ b/testing/web-platform/tests/cookies/prefix/__host.document-cookie.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ ["", "domain="+document.location.hostname, "MaxAge=10", "HttpOnly"].forEach(extraParams => {
+ // Without 'secure'
+ set_prefixed_cookie_via_dom_test({
+ prefix: "__Host-",
+ params: "Path=/;" + extraParams,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__Host: Non-secure origin: 'Path=/;" + extraParams + "'"
+ });
+
+ set_prefixed_cookie_via_dom_test({
+ prefix: "__HoSt-",
+ params: "Path=/;" + extraParams,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__HoSt: Non-secure origin: 'Path=/;" + extraParams + "'"
+ });
+
+ // With 'secure'
+ set_prefixed_cookie_via_dom_test({
+ prefix: "__Host-",
+ params: "Secure; Path=/;" + extraParams,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__Host: Non-secure origin: 'Secure; Path=/;" + extraParams + "'"
+ });
+
+ set_prefixed_cookie_via_dom_test({
+ prefix: "__HoSt-",
+ params: "Secure; Path=/;" + extraParams,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__HoSt: Non-secure origin: 'Secure; Path=/;" + extraParams + "'"
+ });
+ });
+
+ set_prefixed_cookie_via_dom_test({
+ prefix: "__Host-",
+ params: "Secure; Path=/cookies/resources/list.py",
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__Host: Non-secure origin: 'Secure; Path=/cookies/resources/list.py'"
+ });
+
+ set_prefixed_cookie_via_dom_test({
+ prefix: "__HoSt-",
+ params: "Secure; Path=/cookies/resources/list.py",
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__HoSt: Non-secure origin: 'Secure; Path=/cookies/resources/list.py'"
+ });
+</script>
diff --git a/testing/web-platform/tests/cookies/prefix/__host.document-cookie.https.html b/testing/web-platform/tests/cookies/prefix/__host.document-cookie.https.html
new file mode 100644
index 0000000000..cc0cb34ecb
--- /dev/null
+++ b/testing/web-platform/tests/cookies/prefix/__host.document-cookie.https.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ ["", "MaxAge=10"].forEach(extraParams => {
+ // Without 'secure'
+ set_prefixed_cookie_via_dom_test({
+ prefix: "__Host-",
+ params: "Path=/;" + extraParams,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__Host: Secure origin: Does not set 'Path=/;" + extraParams + "'"
+ });
+
+ set_prefixed_cookie_via_dom_test({
+ prefix: "__HoSt-",
+ params: "Path=/;" + extraParams,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__HoSt: Secure origin: Does not set 'Path=/;" + extraParams + "'"
+ });
+
+ // With 'secure'
+ set_prefixed_cookie_via_dom_test({
+ prefix: "__Host-",
+ params: "Secure; Path=/;" + extraParams,
+ shouldExistInDOM: true,
+ shouldExistViaHTTP: true,
+ title: "__Host: Secure origin: Does set 'Secure; Path=/;" + extraParams + "'"
+ });
+
+ set_prefixed_cookie_via_dom_test({
+ prefix: "__HoSt-",
+ params: "Secure; Path=/;" + extraParams,
+ shouldExistInDOM: true,
+ shouldExistViaHTTP: true,
+ title: "__HoSt: Secure origin: Does set 'Secure; Path=/;" + extraParams + "'"
+ });
+
+ // With 'domain'
+ set_prefixed_cookie_via_dom_test({
+ prefix: "__Host-",
+ params: "Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__Host: Secure origin: Does not set 'Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams + "'"
+ });
+
+ set_prefixed_cookie_via_dom_test({
+ prefix: "__HoSt-",
+ params: "Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__HoSt: Secure origin: Does not set 'Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams + "'"
+ });
+ });
+
+ set_prefixed_cookie_via_dom_test({
+ prefix: "__Host-",
+ params: "Secure; Path=/cookies/resources/list.py",
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__Host: Secure origin: Does not set 'Secure; Path=/cookies/resources/list.py'"
+ });
+
+ set_prefixed_cookie_via_dom_test({
+ prefix: "__HoSt-",
+ params: "Secure; Path=/cookies/resources/list.py",
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__HoSt: Secure origin: Does not set 'Secure; Path=/cookies/resources/list.py'"
+ });
+</script>
diff --git a/testing/web-platform/tests/cookies/prefix/__host.header.html b/testing/web-platform/tests/cookies/prefix/__host.header.html
new file mode 100644
index 0000000000..7ad6782584
--- /dev/null
+++ b/testing/web-platform/tests/cookies/prefix/__host.header.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ ["", "domain="+document.location.hostname, "MaxAge=10", "HttpOnly"].forEach(extraParams => {
+ // Without 'secure'
+ set_prefixed_cookie_via_http_test({
+ prefix: "__Host-",
+ params: "Path=/;" + extraParams,
+ origin: self.origin,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__Host: Non-secure origin: Does not set 'Path=/;" + extraParams + "'"
+ });
+
+ set_prefixed_cookie_via_http_test({
+ prefix: "__HoSt-",
+ params: "Path=/;" + extraParams,
+ origin: self.origin,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__HoSt: Non-secure origin: Does not set 'Path=/;" + extraParams + "'"
+ });
+
+ // With 'secure'
+ set_prefixed_cookie_via_http_test({
+ prefix: "__Host-",
+ params: "Secure; Path=/;" + extraParams,
+ origin: self.origin,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__Host: Non-secure origin: Does not set 'Secure; Path=/;" + extraParams + "'"
+ });
+
+ set_prefixed_cookie_via_http_test({
+ prefix: "__HoSt-",
+ params: "Secure; Path=/;" + extraParams,
+ origin: self.origin,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__HoSt: Non-secure origin: Does not set 'Secure; Path=/;" + extraParams + "'"
+ });
+
+ // With 'domain'
+ set_prefixed_cookie_via_http_test({
+ prefix: "__Host-",
+ params: "Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams,
+ origin: self.origin,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__Host: Secure origin: Does not set 'Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams + "'"
+ });
+
+ set_prefixed_cookie_via_http_test({
+ prefix: "__HoSt-",
+ params: "Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams,
+ origin: self.origin,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__HoSt: Secure origin: Does not set 'Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams + "'"
+ });
+ });
+
+ set_prefixed_cookie_via_http_test({
+ prefix: "__Host-",
+ params: "Secure; Path=/cookies/resources/list.py",
+ origin: self.origin,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__Host: Non-secure origin: Does not set 'Secure; Path=/cookies/resources/list.py'"
+ });
+
+ set_prefixed_cookie_via_http_test({
+ prefix: "__HoSt-",
+ params: "Secure; Path=/cookies/resources/list.py",
+ origin: self.origin,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__HoSt: Non-secure origin: Does not set 'Secure; Path=/cookies/resources/list.py'"
+ });
+</script>
+
diff --git a/testing/web-platform/tests/cookies/prefix/__host.header.https.html b/testing/web-platform/tests/cookies/prefix/__host.header.https.html
new file mode 100644
index 0000000000..b7f1365276
--- /dev/null
+++ b/testing/web-platform/tests/cookies/prefix/__host.header.https.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ ["", "MaxAge=10", "HttpOnly"].forEach(extraParams => {
+ // Without 'secure'
+ set_prefixed_cookie_via_http_test({
+ prefix: "__Host-",
+ params: "Path=/;" + extraParams,
+ origin: self.origin,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__Host: Secure origin: Does not set 'Path=/;" + extraParams + "'"
+ });
+
+ set_prefixed_cookie_via_http_test({
+ prefix: "__HoSt-",
+ params: "Path=/;" + extraParams,
+ origin: self.origin,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__HoSt: Secure origin: Does not set 'Path=/;" + extraParams + "'"
+ });
+
+ // With 'secure'
+ set_prefixed_cookie_via_http_test({
+ prefix: "__Host-",
+ params: "Secure; Path=/;" + extraParams,
+ origin: self.origin,
+ shouldExistInDOM: true,
+ shouldExistViaHTTP: true,
+ title: "__Host: Secure origin: Does set 'Secure; Path=/;" + extraParams + "'"
+ });
+
+ set_prefixed_cookie_via_http_test({
+ prefix: "__HoSt-",
+ params: "Secure; Path=/;" + extraParams,
+ origin: self.origin,
+ shouldExistInDOM: true,
+ shouldExistViaHTTP: true,
+ title: "__HoSt: Secure origin: Does set 'Secure; Path=/;" + extraParams + "'"
+ });
+
+ // With 'domain'
+ set_prefixed_cookie_via_http_test({
+ prefix: "__Host-",
+ params: "Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams,
+ origin: self.origin,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__Host: Secure origin: Does not set 'Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams + "'"
+ });
+
+ set_prefixed_cookie_via_http_test({
+ prefix: "__HoSt-",
+ params: "Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams,
+ origin: self.origin,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__HoSt: Secure origin: Does not set 'Secure; Path=/; Domain=" + document.location.hostname + "; " + extraParams + "'"
+ });
+ });
+
+ set_prefixed_cookie_via_http_test({
+ prefix: "__Host-",
+ params: "Secure; Path=/cookies/resources/list.py",
+ origin: self.origin,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__Host: Secure origin: Does not set 'Secure; Path=/cookies/resources/list.py'"
+ });
+
+ set_prefixed_cookie_via_http_test({
+ prefix: "__HoSt-",
+ params: "Secure; Path=/cookies/resources/list.py",
+ origin: self.origin,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__HoSt: Secure origin: Does not set 'Secure; Path=/cookies/resources/list.py'"
+ });
+</script>
+
diff --git a/testing/web-platform/tests/cookies/prefix/__secure.document-cookie.html b/testing/web-platform/tests/cookies/prefix/__secure.document-cookie.html
new file mode 100644
index 0000000000..2246ffff4c
--- /dev/null
+++ b/testing/web-platform/tests/cookies/prefix/__secure.document-cookie.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ ["", "MaxAge=10", "domain="+document.location.hostname].forEach(extraParams => {
+ // Without 'secure'
+ set_prefixed_cookie_via_dom_test({
+ prefix: "__Secure-",
+ params: "Path=/;" + extraParams,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__Secure: Non-secure origin: Should not set 'Path=/;" + extraParams + "'"
+ });
+
+ set_prefixed_cookie_via_dom_test({
+ prefix: "__SeCuRe-",
+ params: "Path=/;" + extraParams,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__SeCuRe: Non-secure origin: Should not set 'Path=/;" + extraParams + "'"
+ });
+
+ // With 'secure'
+ set_prefixed_cookie_via_dom_test({
+ prefix: "__Secure-",
+ params: "Secure; Path=/;" + extraParams,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__Secure: Non-secure origin: Should not set 'Secure; Path=/;" + extraParams + "'"
+ });
+
+ set_prefixed_cookie_via_dom_test({
+ prefix: "__SeCuRe-",
+ params: "Secure; Path=/;" + extraParams,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__SeCuRe: Non-secure origin: Should not set 'Secure; Path=/;" + extraParams + "'"
+ });
+ });
+</script>
diff --git a/testing/web-platform/tests/cookies/prefix/__secure.document-cookie.https.html b/testing/web-platform/tests/cookies/prefix/__secure.document-cookie.https.html
new file mode 100644
index 0000000000..4b600032a8
--- /dev/null
+++ b/testing/web-platform/tests/cookies/prefix/__secure.document-cookie.https.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ ["", "MaxAge=10", "domain="+document.location.hostname].forEach(extraParams => {
+ // Without 'secure'
+ set_prefixed_cookie_via_dom_test({
+ prefix: "__Secure-",
+ params: "Path=/;" + extraParams,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__Secure: Secure origin: Should not set 'Path=/;" + extraParams + "'"
+ });
+
+ set_prefixed_cookie_via_dom_test({
+ prefix: "__SeCuRe-",
+ params: "Path=/;" + extraParams,
+ shouldExistInDOM: false,
+ shouldExistViaHTTP: false,
+ title: "__SeCuRe: Secure origin: Should not set 'Path=/;" + extraParams + "'"
+ });
+
+ // With 'secure'
+ set_prefixed_cookie_via_dom_test({
+ prefix: "__Secure-",
+ params: "Secure; Path=/;" + extraParams,
+ shouldExistInDOM: true,
+ shouldExistViaHTTP: true,
+ title: "__Secure: Secure origin: Should set 'Secure; Path=/;" + extraParams + "'"
+ });
+
+ set_prefixed_cookie_via_dom_test({
+ prefix: "__SeCuRe-",
+ params: "Secure; Path=/;" + extraParams,
+ shouldExistInDOM: true,
+ shouldExistViaHTTP: true,
+ title: "__SeCuRe: Secure origin: Should set 'Secure; Path=/;" + extraParams + "'"
+ });
+ });
+</script>
diff --git a/testing/web-platform/tests/cookies/prefix/__secure.header.html b/testing/web-platform/tests/cookies/prefix/__secure.header.html
new file mode 100644
index 0000000000..85665afefd
--- /dev/null
+++ b/testing/web-platform/tests/cookies/prefix/__secure.header.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ ["", "domain="+document.location.hostname, "MaxAge=10", "HttpOnly"].forEach(extraParams => {
+ // Without 'secure'
+ set_prefixed_cookie_via_http_test({
+ prefix: "__Secure-",
+ params: "Path=/;" + extraParams,
+ origin: self.origin,
+ shouldExistViaHTTP: false,
+ title: "__Secure: Non-secure origin: Should not set 'Path=/;" + extraParams + "'"
+ });
+
+ set_prefixed_cookie_via_http_test({
+ prefix: "__SeCuRe-",
+ params: "Path=/;" + extraParams,
+ origin: self.origin,
+ shouldExistViaHTTP: false,
+ title: "__SeCuRe: Non-secure origin: Should not set 'Path=/;" + extraParams + "'"
+ });
+
+ // With 'secure'
+ set_prefixed_cookie_via_http_test({
+ prefix: "__Secure-",
+ params: "Secure; Path=/;" + extraParams,
+ origin: self.origin,
+ shouldExistViaHTTP: false,
+ title: "__Secure: Non-secure origin: Should not set 'Secure; Path=/;" + extraParams + "'"
+ });
+
+ set_prefixed_cookie_via_http_test({
+ prefix: "__SeCuRe-",
+ params: "Secure; Path=/;" + extraParams,
+ origin: self.origin,
+ shouldExistViaHTTP: false,
+ title: "__SeCuRe: Non-secure origin: Should not set 'Secure; Path=/;" + extraParams + "'"
+ });
+ });
+</script>
diff --git a/testing/web-platform/tests/cookies/prefix/__secure.header.https.html b/testing/web-platform/tests/cookies/prefix/__secure.header.https.html
new file mode 100644
index 0000000000..a6fee03b0c
--- /dev/null
+++ b/testing/web-platform/tests/cookies/prefix/__secure.header.https.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ ["", "MaxAge=10", "HttpOnly"].forEach(extraParams => {
+ // Without 'secure'
+ set_prefixed_cookie_via_http_test({
+ prefix: "__Secure-",
+ params: "Path=/;" + extraParams,
+ origin: self.origin,
+ shouldExistViaHTTP: false,
+ title: "__Secure: secure origin: Should not set 'Path=/;" + extraParams + "'"
+ });
+
+ set_prefixed_cookie_via_http_test({
+ prefix: "__SeCuRe-",
+ params: "Path=/;" + extraParams,
+ origin: self.origin,
+ shouldExistViaHTTP: false,
+ title: "__SeCuRe: secure origin: Should not set 'Path=/;" + extraParams + "'"
+ });
+
+ // With 'secure'
+ set_prefixed_cookie_via_http_test({
+ prefix: "__Secure-",
+ params: "Secure;Path=/;" + extraParams,
+ origin: self.origin,
+ shouldExistViaHTTP: true,
+ title: "__Secure: secure origin: Should set 'Secure;Path=/;" + extraParams + "'"
+ });
+
+ set_prefixed_cookie_via_http_test({
+ prefix: "__SeCuRe-",
+ params: "Secure;Path=/;" + extraParams,
+ origin: self.origin,
+ shouldExistViaHTTP: true,
+ title: "__SeCuRe: secure origin: Should set 'Secure;Path=/;" + extraParams + "'"
+ });
+ });
+
+ // Without 'secure'
+ set_prefixed_cookie_via_http_test({
+ prefix: "__Secure-",
+ // SameSite=None is necessary because cross-site origins cannot set SameSite cookies via fetch.
+ params: "Path=/;SameSite=None;domain=" + CROSS_SITE_HOST,
+ origin: SECURE_CROSS_SITE_ORIGIN,
+ shouldExistViaHTTP: false,
+ title: "__Secure: secure origin: Should not set 'Path=/;domain=" + CROSS_SITE_HOST + "'"
+ });
+
+ set_prefixed_cookie_via_http_test({
+ prefix: "__SeCuRe-",
+ // SameSite=None is necessary because cross-site origins cannot set SameSite cookies via fetch.
+ params: "Path=/;SameSite=None;domain=" + CROSS_SITE_HOST,
+ origin: SECURE_CROSS_SITE_ORIGIN,
+ shouldExistViaHTTP: false,
+ title: "__SeCuRe: secure origin: Should not set 'Path=/;domain=" + CROSS_SITE_HOST + "'"
+ });
+
+ // With 'secure'
+ set_prefixed_cookie_via_http_test({
+ prefix: "__Secure-",
+ // SameSite=None is necessary because cross-site origins cannot set SameSite cookies via fetch.
+ params: "Secure;SameSite=None;Path=/;domain=" + CROSS_SITE_HOST,
+ origin: SECURE_CROSS_SITE_ORIGIN,
+ shouldExistViaHTTP: true,
+ title: "__Secure: secure origin: Should set 'Secure;Path=/;domain=" + CROSS_SITE_HOST + "'"
+ });
+
+ set_prefixed_cookie_via_http_test({
+ prefix: "__SeCuRe-",
+ // SameSite=None is necessary because cross-site origins cannot set SameSite cookies via fetch.
+ params: "Secure;SameSite=None;Path=/;domain=" + CROSS_SITE_HOST,
+ origin: SECURE_CROSS_SITE_ORIGIN,
+ shouldExistViaHTTP: true,
+ title: "__SeCuRe: secure origin: Should set 'Secure;Path=/;domain=" + CROSS_SITE_HOST + "'"
+ });
+</script>
diff --git a/testing/web-platform/tests/cookies/prefix/document-cookie.non-secure.html b/testing/web-platform/tests/cookies/prefix/document-cookie.non-secure.html
new file mode 100644
index 0000000000..efa16a8c5e
--- /dev/null
+++ b/testing/web-platform/tests/cookies/prefix/document-cookie.non-secure.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ function create_test(prefix, params, shouldExistInDOM, shouldExistViaHTTP, title) {
+ promise_test(t => {
+ var name = prefix + "prefixtestcookie";
+ erase_cookie_from_js(name, params);
+ t.add_cleanup(() => erase_cookie_from_js(name, params));
+ var value = "" + Math.random();
+ document.cookie = name + "=" + value + ";" + params;
+
+ assert_dom_cookie(name, value, shouldExistInDOM);
+
+ return credFetch("/cookies/resources/list.py")
+ .then(r => r.json())
+ .then(cookies => assert_equals(cookies[name], shouldExistViaHTTP ? value : undefined));
+ }, title);
+ }
+
+ // No prefix
+ create_test("", "path=/", true, true, "No prefix, root path, no special behavior");
+ create_test("", "path=/;domain=" + document.location.hostname, true, true, "No prefix, domain, no special behavior");
+
+ // `__Secure-` Prefix
+ ["", "domain="+document.location.hostname, "MaxAge=10", "HttpOnly"].forEach(params => {
+ create_test("__Secure-", "Path=/;" + params, false, false, "__Secure: Non-secure origin: 'Path=/;" + params + "'");
+ create_test("__SeCuRe-", "Path=/;" + params, false, false, "__SeCuRe: Non-secure origin: 'Path=/;" + params + "'");
+ create_test("__Secure-", "Secure; Path=/;" + params, false, false, "__Secure: Non-secure origin: 'Secure; Path=/;" + params + "'");
+ create_test("__SeCuRe-", "Secure; Path=/;" + params, false, false, "__SeCuRe: Non-secure origin: 'Secure; Path=/;" + params + "'");
+ });
+
+ // `__Host-` Prefix
+ ["", "domain="+document.location.hostname, "MaxAge=10", "HttpOnly"].forEach(params => {
+ create_test("__Host-", "Path=/;" + params, false, false, "__Host: Non-secure origin: 'Path=/; " + params + "'");
+ create_test("__HoSt-", "Path=/;" + params, false, false, "__HoSt: Non-secure origin: 'Path=/; " + params + "'");
+ create_test("__Host-", "Secure; Path=/;" + params, false, false, "__Host: Non-secure origin: 'Secure; Path=/; " + params + "'");
+ create_test("__HoSt-", "Secure; Path=/;" + params, false, false, "__HoSt: Non-secure origin: 'Secure; Path=/; " + params + "'");
+ });
+ create_test("__Secure-", "Path=/cookies/resources/list.py;Secure", false, false, "__Host: Non-secure origin: 'Path=/cookies/resources/list.py;Secure'");
+</script>
diff --git a/testing/web-platform/tests/cookies/resources/__init__.py b/testing/web-platform/tests/cookies/resources/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/__init__.py
diff --git a/testing/web-platform/tests/cookies/resources/cookie-helper.sub.js b/testing/web-platform/tests/cookies/resources/cookie-helper.sub.js
new file mode 100644
index 0000000000..3338cf0e80
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/cookie-helper.sub.js
@@ -0,0 +1,284 @@
+// Set up exciting global variables for cookie tests.
+(_ => {
+ var HOST = "{{host}}";
+ var INSECURE_PORT = ":{{ports[http][0]}}";
+ var SECURE_PORT = ":{{ports[https][0]}}";
+ var CROSS_ORIGIN_HOST = "{{hosts[alt][]}}";
+
+ window.INSECURE_ORIGIN = "http://" + HOST + INSECURE_PORT;
+
+ //For secure cookie verification
+ window.SECURE_ORIGIN = "https://" + HOST + SECURE_PORT;
+
+ //standard references
+ window.SECURE_SUBDOMAIN_ORIGIN = "https://{{domains[www1]}}" + SECURE_PORT;
+ window.SECURE_CROSS_SITE_ORIGIN = "https://" + CROSS_ORIGIN_HOST + SECURE_PORT;
+ window.CROSS_SITE_HOST = CROSS_ORIGIN_HOST;
+
+ // Set the global cookie name.
+ window.HTTP_COOKIE = "cookie_via_http";
+})();
+
+// A tiny helper which returns the result of fetching |url| with credentials.
+function credFetch(url) {
+ return fetch(url, {"credentials": "include"})
+ .then(response => {
+ if (response.status !== 200) {
+ throw new Error(response.statusText);
+ }
+ return response;
+ });
+}
+
+// Returns a URL on |origin| which redirects to a given absolute URL.
+function redirectTo(origin, url) {
+ return origin + "/cookies/resources/redirectWithCORSHeaders.py?status=307&location=" + encodeURIComponent(url);
+}
+
+// Returns a URL on |origin| which navigates the window to the given URL (by
+// setting window.location).
+function navigateTo(origin, url) {
+ return origin + "/cookies/resources/navigate.html?location=" + encodeURIComponent(url);
+}
+
+// Returns whether a cookie with name `name` with value `value` is in the cookie
+// string (presumably obtained via document.cookie).
+function cookieStringHasCookie(name, value, cookieString) {
+ return new RegExp(`(?:^|; )${name}=${value}(?:$|;)`).test(cookieString);
+}
+
+// Asserts that `document.cookie` contains or does not contain (according to
+// the value of |present|) a cookie named |name| with a value of |value|.
+function assert_dom_cookie(name, value, present) {
+ assert_equals(cookieStringHasCookie(name, value, document.cookie), present, "`" + name + "=" + value + "` in `document.cookie`");
+}
+
+function assert_cookie(origin, obj, name, value, present) {
+ assert_equals(obj[name], present ? value : undefined, "`" + name + "=" + value + "` in request to `" + origin + "`.");
+}
+
+// Remove the cookie named |name| from |origin|, then set it on |origin| anew.
+// If |origin| matches `self.origin`, also assert (via `document.cookie`) that
+// the cookie was correctly removed and reset.
+async function create_cookie(origin, name, value, extras) {
+ alert("Create_cookie: " + origin + "/cookies/resources/drop.py?name=" + name);
+ await credFetch(origin + "/cookies/resources/drop.py?name=" + name);
+ if (origin == self.origin)
+ assert_dom_cookie(name, value, false);
+ await credFetch(origin + "/cookies/resources/set.py?" + name + "=" + value + ";path=/;" + extras);
+ if (origin == self.origin)
+ assert_dom_cookie(name, value, true);
+}
+
+//
+// Prefix-specific test helpers
+//
+function set_prefixed_cookie_via_dom_test(options) {
+ promise_test(t => {
+ var name = options.prefix + "prefixtestcookie";
+ erase_cookie_from_js(name, options.params);
+ t.add_cleanup(() => erase_cookie_from_js(name, options.params));
+ var value = "" + Math.random();
+ document.cookie = name + "=" + value + ";" + options.params;
+
+ assert_dom_cookie(name, value, options.shouldExistInDOM);
+
+ return credFetch("/cookies/resources/list.py")
+ .then(r => r.json())
+ .then(cookies => assert_equals(cookies[name], options.shouldExistViaHTTP ? value : undefined));
+ }, options.title);
+}
+
+function set_prefixed_cookie_via_http_test(options) {
+ promise_test(t => {
+ var name = options.prefix + "prefixtestcookie";
+ var value = "" + Math.random();
+
+ t.add_cleanup(() => {
+ var cookie = name + "=0;expires=" + new Date(0).toUTCString() + ";" +
+ options.params;
+
+ return credFetch(options.origin + "/cookies/resources/set.py?" + cookie);
+ });
+
+ return credFetch(options.origin + "/cookies/resources/set.py?" + name + "=" + value + ";" + options.params)
+ .then(_ => credFetch(options.origin + "/cookies/resources/list.py"))
+ .then(r => r.json())
+ .then(cookies => assert_equals(cookies[name], options.shouldExistViaHTTP ? value : undefined));
+ }, options.title);
+}
+
+//
+// SameSite-specific test helpers:
+//
+
+// status for "network" cookies.
+window.SameSiteStatus = {
+ CROSS_SITE: "cross-site",
+ LAX: "lax",
+ STRICT: "strict"
+};
+// status for "document.cookie".
+window.DomSameSiteStatus = {
+ CROSS_SITE: "cross-site",
+ SAME_SITE: "same-site",
+};
+
+const wait_for_message = (type, origin) => {
+ return new Promise((resolve, reject) => {
+ window.addEventListener('message', e => {
+ if (origin && e.origin != origin) {
+ reject("Message from unexpected origin in wait_for_message:" + e.origin);
+ return;
+ }
+
+ if (e.data.type && e.data.type === type)
+ resolve(e);
+ }, { once: true });
+ });
+};
+
+// Reset SameSite test cookies on |origin|. If |origin| matches `self.origin`, assert
+// (via `document.cookie`) that they were properly removed and reset.
+async function resetSameSiteCookies(origin, value) {
+ let w = window.open(origin + "/cookies/samesite/resources/puppet.html");
+ try {
+ await wait_for_message("READY", origin);
+ w.postMessage({type: "drop", useOwnOrigin: true}, "*");
+ await wait_for_message("drop-complete", origin);
+ if (origin == self.origin) {
+ assert_dom_cookie("samesite_strict", value, false);
+ assert_dom_cookie("samesite_lax", value, false);
+ assert_dom_cookie("samesite_none", value, false);
+ assert_dom_cookie("samesite_unspecified", value, false);
+ }
+
+ w.postMessage({type: "set", value: value, useOwnOrigin: true}, "*");
+ await wait_for_message("set-complete", origin);
+ if (origin == self.origin) {
+ assert_dom_cookie("samesite_strict", value, true);
+ assert_dom_cookie("samesite_lax", value, true);
+ assert_dom_cookie("samesite_none", value, true);
+ assert_dom_cookie("samesite_unspecified", value, true);
+ }
+ } finally {
+ w.close();
+ }
+}
+
+// Given an |expectedStatus| and |expectedValue|, assert the |cookies| contains
+// the proper set of cookie names and values. Expects SameSite-Lax-by-default.
+function verifySameSiteCookieState(expectedStatus, expectedValue, cookies, domCookieStatus) {
+ assert_equals(cookies["samesite_none"], expectedValue, "SameSite=None cookies are always sent.");
+ if (expectedStatus == SameSiteStatus.CROSS_SITE) {
+ assert_not_equals(cookies["samesite_strict"], expectedValue, "SameSite=Strict cookies are not sent with cross-site requests.");
+ assert_not_equals(cookies["samesite_lax"], expectedValue, "SameSite=Lax cookies are not sent with cross-site requests.");
+ assert_not_equals(cookies["samesite_unspecified"], expectedValue, "Unspecified-SameSite cookies are not sent with cross-site requests.");
+ } else if (expectedStatus == SameSiteStatus.LAX) {
+ assert_not_equals(cookies["samesite_strict"], expectedValue, "SameSite=Strict cookies are not sent with lax requests.");
+ assert_equals(cookies["samesite_lax"], expectedValue, "SameSite=Lax cookies are sent with lax requests.");
+ assert_equals(cookies["samesite_unspecified"], expectedValue, "Unspecified-SameSite cookies are are sent with lax requests.")
+ } else if (expectedStatus == SameSiteStatus.STRICT) {
+ assert_equals(cookies["samesite_strict"], expectedValue, "SameSite=Strict cookies are sent with strict requests.");
+ assert_equals(cookies["samesite_lax"], expectedValue, "SameSite=Lax cookies are sent with strict requests.");
+ assert_equals(cookies["samesite_unspecified"], expectedValue, "Unspecified-SameSite cookies are are sent with strict requests.")
+ }
+
+ if (cookies["domcookies"]) {
+ verifyDocumentCookieSameSite(domCookieStatus, expectedValue, cookies['domcookies']);
+ }
+}
+
+function verifyDocumentCookieSameSite(expectedStatus, expectedValue, domcookies) {
+ const cookies = domcookies.split(";")
+ .map(cookie => cookie.trim().split("="))
+ .reduce((obj, cookie) => {
+ obj[cookie[0]] = cookie[1];
+ return obj;
+ }, {});
+
+ if (expectedStatus == DomSameSiteStatus.SAME_SITE) {
+ assert_equals(cookies["samesite_none"], expectedValue, "SameSite=None cookies are always included in document.cookie.");
+ assert_equals(cookies["samesite_unspecified"], expectedValue, "Unspecified-SameSite cookies are always included in document.cookie.");
+ assert_equals(cookies["samesite_strict"], expectedValue, "SameSite=Strict cookies are always included in document.cookie.");
+ assert_equals(cookies["samesite_lax"], expectedValue, "SameSite=Lax cookies are always included in document.cookie.");
+ } else if (expectedStatus == DomSameSiteStatus.CROSS_SITE) {
+ assert_equals(cookies["samesite_none"], expectedValue, "SameSite=None cookies are always included in document.cookie.");
+ assert_not_equals(cookies["samesite_unspecified"], expectedValue, "Unspecified-SameSite cookies are not included in document.cookie when cross-site.");
+ assert_not_equals(cookies["samesite_strict"], expectedValue, "SameSite=Strict cookies are not included in document.cookie when cross-site.");
+ assert_not_equals(cookies["samesite_lax"], expectedValue, "SameSite=Lax cookies are not included in document.cookie when cross-site.");
+ }
+}
+
+//
+// LeaveSecureCookiesAlone-specific test helpers:
+//
+
+window.SecureStatus = {
+ INSECURE_COOKIE_ONLY: "1",
+ BOTH_COOKIES: "2",
+};
+
+//Reset SameSite test cookies on |origin|. If |origin| matches `self.origin`, assert
+//(via `document.cookie`) that they were properly removed and reset.
+function resetSecureCookies(origin, value) {
+return credFetch(origin + "/cookies/resources/dropSecure.py")
+ .then(_ => {
+ if (origin == self.origin) {
+ assert_dom_cookie("alone_secure", value, false);
+ assert_dom_cookie("alone_insecure", value, false);
+ }
+ })
+ .then(_ => {
+ return credFetch(origin + "/cookie/resources/setSecure.py?" + value)
+ })
+}
+
+// Reset SameSite=None test cookies on |origin|. If |origin| matches
+// `self.origin`, assert (via `document.cookie`) that they were properly
+// removed.
+function resetSameSiteNoneCookies(origin, value) {
+ return credFetch(origin + "/cookies/resources/dropSameSiteNone.py")
+ .then(_ => {
+ if (origin == self.origin) {
+ assert_dom_cookie("samesite_none_insecure", value, false);
+ assert_dom_cookie("samesite_none_secure", value, false);
+ }
+ })
+ .then(_ => {
+ return credFetch(origin + "/cookies/resources/setSameSiteNone.py?" + value);
+ })
+}
+
+// Reset test cookies with multiple SameSite attributes on |origin|.
+// If |origin| matches `self.origin`, assert (via `document.cookie`)
+// that they were properly removed.
+function resetSameSiteMultiAttributeCookies(origin, value) {
+ return credFetch(origin + "/cookies/resources/dropSameSiteMultiAttribute.py")
+ .then(_ => {
+ if (origin == self.origin) {
+ assert_dom_cookie("samesite_unsupported", value, false);
+ assert_dom_cookie("samesite_unsupported_none", value, false);
+ assert_dom_cookie("samesite_unsupported_lax", value, false);
+ assert_dom_cookie("samesite_unsupported_strict", value, false);
+ assert_dom_cookie("samesite_none_unsupported", value, false);
+ assert_dom_cookie("samesite_lax_unsupported", value, false);
+ assert_dom_cookie("samesite_strict_unsupported", value, false);
+ assert_dom_cookie("samesite_lax_none", value, false);
+ }
+ })
+ .then(_ => {
+ return credFetch(origin + "/cookies/resources/setSameSiteMultiAttribute.py?" + value);
+ })
+}
+
+//
+// DOM based cookie manipulation APIs
+//
+
+// erase cookie value and set for expiration
+function erase_cookie_from_js(name, params) {
+ document.cookie = `${name}=0; expires=${new Date(0).toUTCString()}; ${params};`;
+ var re = new RegExp("(?:^|; )" + name);
+ assert_equals(re.test(document.cookie), false, "Sanity check: " + name + " has been deleted.");
+}
diff --git a/testing/web-platform/tests/cookies/resources/cookie-test.js b/testing/web-platform/tests/cookies/resources/cookie-test.js
new file mode 100644
index 0000000000..a909e4d72f
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/cookie-test.js
@@ -0,0 +1,186 @@
+// getAndExpireCookiesForDefaultPathTest is a helper method to get and delete
+// cookies using echo-cookie.html.
+async function getAndExpireCookiesForDefaultPathTest() {
+ return new Promise((resolve, reject) => {
+ try {
+ const iframe = document.createElement('iframe');
+ iframe.style = 'display: none';
+ iframe.src = '/cookies/resources/echo-cookie.html';
+ iframe.addEventListener('load', (e) => {
+ const win = e.target.contentWindow;
+ const iframeCookies = win.getCookies();
+ win.expireCookies().then(() => {
+ document.documentElement.removeChild(iframe);
+ resolve(iframeCookies);
+ });
+ }, {once: true});
+ document.documentElement.appendChild(iframe);
+ } catch (e) {
+ reject(e);
+ }
+ });
+}
+
+// getAndExpireCookiesForRedirectTest is a helper method to get and delete
+// cookies that were set from a Location header redirect.
+async function getAndExpireCookiesForRedirectTest(location) {
+ return new Promise((resolve, reject) => {
+ try {
+ const iframe = document.createElement('iframe');
+ iframe.style = 'display: none';
+ iframe.src = location;
+ const listener = (e) => {
+ if (typeof e.data == 'object' && 'cookies' in e.data) {
+ window.removeEventListener('message', listener);
+ document.documentElement.removeChild(iframe);
+ resolve(e.data.cookies);
+ }
+ };
+ window.addEventListener('message', listener);
+ iframe.addEventListener('load', (e) => {
+ e.target.contentWindow.postMessage('getAndExpireCookiesForRedirectTest', '*');
+ }, {once: true});
+ document.documentElement.appendChild(iframe);
+ } catch (e) {
+ reject(e);
+ }
+ });
+}
+
+// httpCookieTest sets a `cookie` (via HTTP), then asserts it was or was not set
+// via `expectedValue` (via the DOM). Then cleans it up (via test driver). Most
+// tests do not set a Path attribute, so `defaultPath` defaults to true. If the
+// cookie values are expected to cause the HTTP request or response to fail, the
+// test can be made to pass when this happens via `allowFetchFailure`, which
+// defaults to false.
+//
+// `cookie` may be a single cookie string, or an array of cookie strings, where
+// the order of the array items represents the order of the Set-Cookie headers
+// sent by the server.
+//
+// Note: this function has a dependency on testdriver.js. Any test files calling
+// it should include testdriver.js and testdriver-vendor.js
+function httpCookieTest(cookie, expectedValue, name, defaultPath = true,
+ allowFetchFailure = false) {
+ return promise_test((t) => {
+ var skipAssertions = false;
+ return new Promise(async (resolve, reject) => {
+ // The result is ignored as we're expiring cookies for cleaning here.
+ await getAndExpireCookiesForDefaultPathTest();
+ await test_driver.delete_all_cookies();
+ t.add_cleanup(test_driver.delete_all_cookies);
+
+ let encodedCookie = encodeURIComponent(JSON.stringify(cookie));
+ try {
+ await fetch(`/cookies/resources/cookie.py?set=${encodedCookie}`);
+ } catch {
+ if (allowFetchFailure) {
+ skipAssertions = true;
+ resolve();
+ } else {
+ reject('Failed to fetch /cookies/resources/cookie.py');
+ }
+ }
+ let cookies = document.cookie;
+ if (defaultPath) {
+ // for the tests where a Path is set from the request-uri
+ // path, we need to go look for cookies in an iframe at that
+ // default path.
+ cookies = await getAndExpireCookiesForDefaultPathTest();
+ }
+ resolve(cookies);
+ }).then((cookies) => {
+ if (skipAssertions) {
+ return;
+ }
+ if (Boolean(expectedValue)) {
+ assert_equals(cookies, expectedValue, 'The cookie was set as expected.');
+ } else {
+ assert_equals(cookies, expectedValue, 'The cookie was rejected.');
+ }
+ });
+ }, name);
+}
+
+// This is a variation on httpCookieTest, where a redirect happens via
+// the Location header and we check to see if cookies are sent via
+// getRedirectedCookies
+//
+// Note: the locations targeted by this function have a dependency on
+// path-redirect-shared.js and should be sure to include it.
+function httpRedirectCookieTest(cookie, expectedValue, name, location) {
+ return promise_test(async (t) => {
+ // The result is ignored as we're expiring cookies for cleaning here.
+ await getAndExpireCookiesForRedirectTest(location);
+
+ const encodedCookie = encodeURIComponent(JSON.stringify(cookie));
+ const encodedLocation = encodeURIComponent(location);
+ const setParams = `?set=${encodedCookie}&location=${encodedLocation}`;
+ await fetch(`/cookies/resources/cookie.py${setParams}`);
+ // for the tests where a redirect happens, we need to head
+ // to that URI to get the cookies (and then delete them there)
+ const cookies = await getAndExpireCookiesForRedirectTest(location);
+ if (Boolean(expectedValue)) {
+ assert_equals(cookies, expectedValue, 'The cookie was set as expected.');
+ } else {
+ assert_equals(cookies, expectedValue, 'The cookie was rejected.');
+ }
+ }, name);
+}
+
+// Sets a `cookie` via the DOM, checks it against `expectedValue` via the DOM,
+// then cleans it up via the DOM. This is needed in cases where going through
+// HTTP headers may modify the cookie line (e.g. by stripping control
+// characters).
+//
+// Note: this function has a dependency on testdriver.js. Any test files calling
+// it should include testdriver.js and testdriver-vendor.js
+function domCookieTest(cookie, expectedValue, name) {
+ return promise_test(async (t) => {
+ await test_driver.delete_all_cookies();
+ t.add_cleanup(test_driver.delete_all_cookies);
+
+ if (typeof cookie === "string") {
+ document.cookie = cookie;
+ } else if (Array.isArray(cookie)) {
+ for (const singlecookie of cookie) {
+ document.cookie = singlecookie;
+ }
+ } else {
+ throw new Error('Unexpected type passed into domCookieTest as cookie: ' + typeof cookie);
+ }
+ let cookies = document.cookie;
+ assert_equals(cookies, expectedValue, Boolean(expectedValue) ?
+ 'The cookie was set as expected.' :
+ 'The cookie was rejected.');
+ }, name);
+}
+
+// Returns an array of control characters along with their ASCII codes. Control
+// characters are defined by RFC 5234 to be %x00-1F / %x7F.
+function getCtlCharacters() {
+ const ctlCodes = [...Array(0x20).keys()]
+ .concat([0x7F]);
+ return ctlCodes.map(i => ({ code: i, chr: String.fromCharCode(i) }))
+}
+
+// Returns a cookie string with name set to "t" * nameLength and value
+// set to "1" * valueLength. Passing in 0 for either allows for creating
+// a name- or value-less cookie.
+//
+// Note: Cookie length checking should ignore the "=".
+function cookieStringWithNameAndValueLengths(nameLength, valueLength) {
+ return `${"t".repeat(nameLength)}=${"1".repeat(valueLength)}`;
+}
+
+// Finds the root window.top.opener and directs test_driver commands to it.
+//
+// If you see a message like: "Error: Tried to run in a non-testharness window
+// without a call to set_test_context." then you probably need to call this.
+function setTestContextUsingRootWindow() {
+ let test_window = window.top;
+ while (test_window.opener && !test_window.opener.closed) {
+ test_window = test_window.opener.top;
+ }
+ test_driver.set_test_context(test_window);
+}
diff --git a/testing/web-platform/tests/cookies/resources/cookie.py b/testing/web-platform/tests/cookies/resources/cookie.py
new file mode 100644
index 0000000000..936679bc9f
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/cookie.py
@@ -0,0 +1,42 @@
+import json
+
+from cookies.resources.helpers import setNoCacheAndCORSHeaders
+from wptserve.utils import isomorphic_decode
+from wptserve.utils import isomorphic_encode
+
+def set_cookie(headers, cookie_string):
+ """Helper method to add a Set-Cookie header"""
+ headers.append((b'Set-Cookie', isomorphic_encode(cookie_string)))
+
+def main(request, response):
+ """Set a cookie via GET params.
+
+ Usage: `/cookie.py?set={cookie}`
+
+ The passed-in cookie string should be stringified via JSON.stringify() (in
+ the case of multiple cookie headers sent in an array) and encoded via
+ encodeURIComponent, otherwise `parse_qsl` will split on any semicolons
+ (used by the Request.GET property getter). Note that values returned by
+ Request.GET will decode any percent-encoded sequences sent in a GET param
+ (which may or may not be surprising depending on what you're doing).
+
+ Note: here we don't use Response.delete_cookie() or similar other methods
+ in this resources directory because there are edge cases that are impossible
+ to express via those APIs, namely a bare (`Path`) or empty Path (`Path=`)
+ attribute. Instead, we pipe through the entire cookie and append `max-age=0`
+ to it.
+ """
+ headers = setNoCacheAndCORSHeaders(request, response)
+
+ if b'set' in request.GET:
+ cookie = isomorphic_decode(request.GET[b'set'])
+ cookie = json.loads(cookie)
+ cookies = cookie if isinstance(cookie, list) else [cookie]
+ for c in cookies:
+ set_cookie(headers, c)
+
+ if b'location' in request.GET:
+ headers.append((b'Location', request.GET[b'location']))
+ return 302, headers, b'{"redirect": true}'
+
+ return headers, b'{"success": true}'
diff --git a/testing/web-platform/tests/cookies/resources/drop.py b/testing/web-platform/tests/cookies/resources/drop.py
new file mode 100644
index 0000000000..612add2169
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/drop.py
@@ -0,0 +1,14 @@
+from cookies.resources.helpers import makeDropCookie, readParameter, setNoCacheAndCORSHeaders
+
+def main(request, response):
+ """Respond to `/cookie/drop?name={name}` by expiring the cookie named `{name}`."""
+ headers = setNoCacheAndCORSHeaders(request, response)
+ try:
+ # Expire the named cookie, and return a JSON-encoded success code.
+ name = readParameter(request, paramName=u"name", requireValue=True)
+ scheme = request.url_parts.scheme
+ headers.append(makeDropCookie(name, u"https" == scheme))
+ return headers, b'{"success": true}'
+ except:
+ return 500, headers, b'{"error" : "Empty or missing name parameter."}'
+
diff --git a/testing/web-platform/tests/cookies/resources/dropSameSite.py b/testing/web-platform/tests/cookies/resources/dropSameSite.py
new file mode 100644
index 0000000000..a0aa83558d
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/dropSameSite.py
@@ -0,0 +1,13 @@
+from cookies.resources.helpers import makeDropCookie, setNoCacheAndCORSHeaders
+
+def main(request, response):
+ """Respond to `/cookie/same-site/resources/dropSameSite.py by dropping the
+ four cookies set by setSameSiteCookies.py"""
+ headers = setNoCacheAndCORSHeaders(request, response)
+
+ # Expire the cookies, and return a JSON-encoded success code.
+ headers.append(makeDropCookie(b"samesite_strict", False))
+ headers.append(makeDropCookie(b"samesite_lax", False))
+ headers.append(makeDropCookie(b"samesite_none", False))
+ headers.append(makeDropCookie(b"samesite_unspecified", False))
+ return headers, b'{"success": true}'
diff --git a/testing/web-platform/tests/cookies/resources/dropSameSiteMultiAttribute.py b/testing/web-platform/tests/cookies/resources/dropSameSiteMultiAttribute.py
new file mode 100644
index 0000000000..af4fbeeca4
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/dropSameSiteMultiAttribute.py
@@ -0,0 +1,17 @@
+from cookies.resources.helpers import makeDropCookie, setNoCacheAndCORSHeaders
+
+def main(request, response):
+ """Respond to `/cookies/resources/dropSameSiteMultiAttribute.py by dropping
+ the cookies set by setSameSiteMultiAttribute.py"""
+ headers = setNoCacheAndCORSHeaders(request, response)
+
+ # Expire the cookies, and return a JSON-encoded success code.
+ headers.append(makeDropCookie(b"samesite_unsupported", True))
+ headers.append(makeDropCookie(b"samesite_unsupported_none", True))
+ headers.append(makeDropCookie(b"samesite_unsupported_lax", False))
+ headers.append(makeDropCookie(b"samesite_unsupported_strict", False))
+ headers.append(makeDropCookie(b"samesite_none_unsupported", True))
+ headers.append(makeDropCookie(b"samesite_lax_unsupported", True))
+ headers.append(makeDropCookie(b"samesite_strict_unsupported", True))
+ headers.append(makeDropCookie(b"samesite_lax_none", True))
+ return headers, b'{"success": true}'
diff --git a/testing/web-platform/tests/cookies/resources/dropSameSiteNone.py b/testing/web-platform/tests/cookies/resources/dropSameSiteNone.py
new file mode 100644
index 0000000000..2d0a837b5d
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/dropSameSiteNone.py
@@ -0,0 +1,11 @@
+from cookies.resources.helpers import makeDropCookie, setNoCacheAndCORSHeaders
+
+def main(request, response):
+ """Respond to `/cookies/resources/dropSameSiteNone.py by dropping the
+ two cookies set by setSameSiteNone.py"""
+ headers = setNoCacheAndCORSHeaders(request, response)
+
+ # Expire the cookies, and return a JSON-encoded success code.
+ headers.append(makeDropCookie(b"samesite_none_insecure", False))
+ headers.append(makeDropCookie(b"samesite_none_secure", True))
+ return headers, b'{"success": true}'
diff --git a/testing/web-platform/tests/cookies/resources/dropSecure.py b/testing/web-platform/tests/cookies/resources/dropSecure.py
new file mode 100644
index 0000000000..af71148cd6
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/dropSecure.py
@@ -0,0 +1,11 @@
+from cookies.resources.helpers import makeDropCookie, setNoCacheAndCORSHeaders
+
+def main(request, response):
+ """Respond to `/cookie/drop/secure` by dropping the two cookie set by
+ `setSecureTestCookies()`"""
+ headers = setNoCacheAndCORSHeaders(request, response)
+
+ # Expire the cookies, and return a JSON-encoded success code.
+ headers.append(makeDropCookie(b"alone_secure", False))
+ headers.append(makeDropCookie(b"alone_insecure", False))
+ return headers, b'{"success": true}'
diff --git a/testing/web-platform/tests/cookies/resources/echo-cookie.html b/testing/web-platform/tests/cookies/resources/echo-cookie.html
new file mode 100644
index 0000000000..ab78af8325
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/echo-cookie.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>helper iframe for matching cookie path tests</title>
+ <meta name=help href="http://tools.ietf.org/html/rfc6265#section-5.1.4">
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+</head>
+<body>
+<script>
+window.setCookie = function (name, path) {
+ document.cookie = name + '=1; Path=' + path + ';';
+};
+window.fetchCookieThen = function (name, path) {
+ return fetch("/cookies/resources/set-cookie.py?name=" + encodeURIComponent(name) + "&path=" + encodeURIComponent(path), {'credentials': 'include'});
+};
+window.isCookieSet = function (name, path) {
+ return document.cookie.match(name + '=1');
+};
+// Note: this function has a dependency on testdriver.js. Any test files calling
+// it should include testdriver.js and testdriver-vendor.js
+window.expireCookies = async () => {
+ setTestContextUsingRootWindow();
+ await test_driver.delete_all_cookies();
+};
+window.getCookies = () => document.cookie;
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/cookies/resources/echo-json.py b/testing/web-platform/tests/cookies/resources/echo-json.py
new file mode 100644
index 0000000000..9f1568e816
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/echo-json.py
@@ -0,0 +1,15 @@
+from wptserve.utils import isomorphic_decode
+
+def main(request, response):
+ headers = [(b"Content-Type", b"application/json"),
+ (b"Access-Control-Allow-Credentials", b"true")]
+
+ if b"origin" in request.headers:
+ headers.append((b"Access-Control-Allow-Origin", request.headers[b"origin"]))
+
+ values = []
+ for key in request.cookies:
+ for value in request.cookies.get_list(key):
+ values.append(u"\"%s\": \"%s\"" % (isomorphic_decode(key), value))
+ body = u"{ %s }" % u",".join(values)
+ return headers, body
diff --git a/testing/web-platform/tests/cookies/resources/helpers.py b/testing/web-platform/tests/cookies/resources/helpers.py
new file mode 100644
index 0000000000..5fee5a9a91
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/helpers.py
@@ -0,0 +1,59 @@
+from urllib.parse import parse_qs
+
+from wptserve.utils import isomorphic_encode
+
+def setNoCacheAndCORSHeaders(request, response):
+ """Set Cache-Control, CORS and Content-Type headers appropriate for the cookie tests."""
+ headers = [(b"Content-Type", b"application/json"),
+ (b"Access-Control-Allow-Credentials", b"true")]
+
+ origin = b"*"
+ if b"origin" in request.headers:
+ origin = request.headers[b"origin"]
+
+ headers.append((b"Access-Control-Allow-Origin", origin))
+ #headers.append(("Access-Control-Allow-Credentials", "true"))
+ headers.append((b"Cache-Control", b"no-cache"))
+ headers.append((b"Expires", b"Fri, 01 Jan 1990 00:00:00 GMT"))
+
+ return headers
+
+def makeCookieHeader(name, value, otherAttrs):
+ """Make a Set-Cookie header for a cookie with the name, value and attributes provided."""
+ def makeAV(a, v):
+ if None == v or b"" == v:
+ return a
+ if isinstance(v, int):
+ return b"%s=%i" % (a, v)
+ else:
+ return b"%s=%s" % (a, v)
+
+ # ensure cookie name is always first
+ attrs = [b"%s=%s" % (name, value)]
+ attrs.extend(makeAV(a, v) for (a, v) in otherAttrs.items())
+ return (b"Set-Cookie", b"; ".join((attrs)))
+
+def makeDropCookie(name, secure):
+ attrs = {b"max-age": 0, b"path": b"/"}
+ if secure:
+ attrs[b"secure"] = b""
+ return makeCookieHeader(name, b"", attrs)
+
+def readParameter(request, paramName, requireValue):
+ """Read a parameter from the request. Raise if requireValue is set and the
+ parameter has an empty value or is not present."""
+ params = parse_qs(request.url_parts.query)
+ param = params[paramName][0].strip()
+ if len(param) == 0:
+ raise Exception(u"Empty or missing name parameter.")
+ return isomorphic_encode(param)
+
+def readCookies(request):
+ """Read the cookies from the client present in the request."""
+ cookies = {}
+ for key in request.cookies:
+ for cookie in request.cookies.get_list(key):
+ # do we care we'll clobber cookies here? If so, do we
+ # need to modify the test to take cookie names and value lists?
+ cookies[key] = cookie.value
+ return cookies
diff --git a/testing/web-platform/tests/cookies/resources/imgIfMatch.py b/testing/web-platform/tests/cookies/resources/imgIfMatch.py
new file mode 100644
index 0000000000..72fa50e66e
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/imgIfMatch.py
@@ -0,0 +1,16 @@
+from cookies.resources import helpers
+
+def main(request, response):
+ """Respond to `/cookie/imgIfMatch?name={name}&value={value}` with a 404 if
+ the cookie isn't present, and a transparent GIF otherwise."""
+ headers = helpers.setNoCacheAndCORSHeaders(request, response)
+ name = helpers.readParameter(request, paramName=u"name", requireValue=True)
+ value = helpers.readParameter(request, paramName=u"value", requireValue=True)
+ cookiesWithMatchingNames = request.cookies.get_list(name)
+ for cookie in cookiesWithMatchingNames:
+ if cookie.value == value:
+ # From https://github.com/mathiasbynens/small/blob/master/gif-transparent.gif
+ headers.append((b"Content-Type", b"image/gif"))
+ gif = b"\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00\x00\xFF\xFF\xFF\x00\x00\x00\x21\xF9\x04\x01\x00\x00\x00\x00\x2C\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3B"
+ return headers, gif
+ return 500, headers, b'{"error": {"message": "The cookie\'s value did not match the given value."}}'
diff --git a/testing/web-platform/tests/cookies/resources/list-cookies-for-script.py b/testing/web-platform/tests/cookies/resources/list-cookies-for-script.py
new file mode 100644
index 0000000000..b325d1f745
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/list-cookies-for-script.py
@@ -0,0 +1,12 @@
+import json
+from cookies.resources import helpers
+
+from wptserve.utils import isomorphic_decode
+
+def main(request, response):
+ headers = helpers.setNoCacheAndCORSHeaders(request, response)
+ headers[0] = (b"Content-Type", b"text/javascript")
+ cookies = helpers.readCookies(request)
+ decoded_cookies = {isomorphic_decode(key): isomorphic_decode(val) for key, val in cookies.items()}
+ return headers, 'self._cookies = [{}];\n'.format(
+ ', '.join(['"{}"'.format(name) for name in decoded_cookies.keys()]))
diff --git a/testing/web-platform/tests/cookies/resources/list.py b/testing/web-platform/tests/cookies/resources/list.py
new file mode 100644
index 0000000000..4cb6639659
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/list.py
@@ -0,0 +1,10 @@
+import json
+from cookies.resources import helpers
+
+from wptserve.utils import isomorphic_decode
+
+def main(request, response):
+ headers = helpers.setNoCacheAndCORSHeaders(request, response)
+ cookies = helpers.readCookies(request)
+ decoded_cookies = {isomorphic_decode(key): isomorphic_decode(val) for key, val in cookies.items()}
+ return headers, json.dumps(decoded_cookies)
diff --git a/testing/web-platform/tests/cookies/resources/navigate.html b/testing/web-platform/tests/cookies/resources/navigate.html
new file mode 100644
index 0000000000..077efba569
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/navigate.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script>
+ // Navigates the window to a location specified via URL query param.
+ const params = new URLSearchParams(window.location.search);
+ const loc = params.get('location');
+ window.location = loc;
+</script>
diff --git a/testing/web-platform/tests/cookies/resources/postToParent.py b/testing/web-platform/tests/cookies/resources/postToParent.py
new file mode 100644
index 0000000000..43f7d679fb
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/postToParent.py
@@ -0,0 +1,39 @@
+import json
+from cookies.resources import helpers
+
+from wptserve.utils import isomorphic_decode
+
+def main(request, response):
+ headers = helpers.setNoCacheAndCORSHeaders(request, response)
+ cookies = helpers.readCookies(request)
+ headers.append((b"Content-Type", b"text/html; charset=utf-8"))
+
+ tmpl = u"""
+<!DOCTYPE html>
+<script>
+ var data = %s;
+ data.type = "COOKIES";
+
+ try {
+ data.domcookies = document.cookie;
+ } catch (e) {}
+
+ if (window.parent != window) {
+ window.parent.postMessage(data, "*");
+ if (window.top != window.parent)
+ window.top.postMessage(data, "*");
+ }
+
+
+ if (window.opener)
+ window.opener.postMessage(data, "*");
+
+ window.addEventListener("message", e => {
+ console.log(e);
+ if (e.data == "reload")
+ window.location.reload();
+ });
+</script>
+"""
+ decoded_cookies = {isomorphic_decode(key): isomorphic_decode(val) for key, val in cookies.items()}
+ return headers, tmpl % json.dumps(decoded_cookies)
diff --git a/testing/web-platform/tests/cookies/resources/redirectWithCORSHeaders.py b/testing/web-platform/tests/cookies/resources/redirectWithCORSHeaders.py
new file mode 100644
index 0000000000..0af14da3e9
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/redirectWithCORSHeaders.py
@@ -0,0 +1,22 @@
+from cookies.resources.helpers import setNoCacheAndCORSHeaders
+
+def main(request, response):
+ """Simple handler that causes redirection.
+
+ The request should typically have two query parameters:
+ status - The status to use for the redirection. Defaults to 302.
+ location - The resource to redirect to.
+ """
+ status = 302
+ if b"status" in request.GET:
+ try:
+ status = int(request.GET.first(b"status"))
+ except ValueError:
+ pass
+ headers = setNoCacheAndCORSHeaders(request, response)
+
+ location = request.GET.first(b"location")
+
+ headers.append((b"Location", location))
+
+ return status, headers, b""
diff --git a/testing/web-platform/tests/cookies/resources/set-cookie.py b/testing/web-platform/tests/cookies/resources/set-cookie.py
new file mode 100644
index 0000000000..59b5b8006a
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/set-cookie.py
@@ -0,0 +1,45 @@
+from datetime import date
+
+def main(request, response):
+ """
+ Returns cookie name and path from query params in a Set-Cookie header.
+
+ e.g.
+
+ > GET /cookies/resources/set-cookie.py?name=match-slash&path=%2F HTTP/1.1
+ > Host: localhost:8000
+ > User-Agent: curl/7.43.0
+ > Accept: */*
+ >
+ < HTTP/1.1 200 OK
+ < Content-Type: application/json
+ < Set-Cookie: match-slash=1; Path=/; Expires=09 Jun 2021 10:18:14 GMT
+ < Server: BaseHTTP/0.3 Python/2.7.12
+ < Date: Tue, 04 Oct 2016 18:16:06 GMT
+ < Content-Length: 80
+ """
+
+ name = request.GET[b'name']
+ path = request.GET[b'path']
+ samesite = request.GET.get(b'samesite')
+ secure = b'secure' in request.GET
+ expiry_year = date.today().year + 1
+ cookie = b"%s=1; Path=%s; Expires=09 Jun %d 10:18:14 GMT" % (name, path, expiry_year)
+ if samesite:
+ cookie += b";SameSite=%s" % samesite
+ if secure:
+ cookie += b";Secure"
+
+ headers = [
+ (b"Content-Type", b"application/json"),
+ (b"Set-Cookie", cookie)
+ ]
+
+ # Set the cors enabled headers.
+ origin = request.headers.get(b"Origin")
+ if origin is not None and origin != b"null":
+ headers.append((b"Access-Control-Allow-Origin", origin))
+ headers.append((b"Access-Control-Allow-Credentials", 'true'))
+
+ body = b"var dummy='value';"
+ return headers, body
diff --git a/testing/web-platform/tests/cookies/resources/set.py b/testing/web-platform/tests/cookies/resources/set.py
new file mode 100644
index 0000000000..eda9338c92
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/set.py
@@ -0,0 +1,15 @@
+from cookies.resources import helpers
+from urllib.parse import unquote
+
+from wptserve.utils import isomorphic_encode
+
+def main(request, response):
+ """Respond to `/cookie/set?{cookie}` by echoing `{cookie}` as a `Set-Cookie` header."""
+ headers = helpers.setNoCacheAndCORSHeaders(request, response)
+
+ # Cookies may require whitespace (e.g. in the `Expires` attribute), so the
+ # query string should be decoded.
+ cookie = unquote(request.url_parts.query)
+ headers.append((b"Set-Cookie", isomorphic_encode(cookie)))
+
+ return headers, b'{"success": true}'
diff --git a/testing/web-platform/tests/cookies/resources/setSameSite.py b/testing/web-platform/tests/cookies/resources/setSameSite.py
new file mode 100644
index 0000000000..05f0967088
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/setSameSite.py
@@ -0,0 +1,32 @@
+from cookies.resources.helpers import makeCookieHeader, setNoCacheAndCORSHeaders
+
+from wptserve.utils import isomorphic_encode
+
+def main(request, response):
+ """Respond to `/cookie/set/samesite?{value}` by setting four cookies:
+ 1. `samesite_strict={value};SameSite=Strict;path=/`
+ 2. `samesite_lax={value};SameSite=Lax;path=/`
+ 3. `samesite_none={value};SameSite=None;path=/`
+ 4. `samesite_unspecified={value};path=/`
+ Then navigate to a page that will post a message back to the opener with the set cookies"""
+ headers = setNoCacheAndCORSHeaders(request, response)
+ value = isomorphic_encode(request.url_parts.query)
+
+ headers.append((b"Content-Type", b"text/html; charset=utf-8"))
+ headers.append(makeCookieHeader(b"samesite_strict", value, {b"SameSite":b"Strict", b"path":b"/"}))
+ headers.append(makeCookieHeader(b"samesite_lax", value, {b"SameSite":b"Lax", b"path":b"/"}))
+ # SameSite=None cookies must be Secure.
+ headers.append(makeCookieHeader(b"samesite_none", value, {b"SameSite":b"None", b"path":b"/", b"Secure": b""}))
+ headers.append(makeCookieHeader(b"samesite_unspecified", value, {b"path":b"/"}))
+
+ document = b"""
+<!DOCTYPE html>
+<script>
+ // A same-site navigation, which should attach all cookies including SameSite ones.
+ // This is necessary because this page may have been reached via a cross-site navigation, so
+ // we might not have access to some SameSite cookies from here.
+ window.location = "../samesite/resources/echo-cookies.html";
+</script>
+"""
+
+ return headers, document
diff --git a/testing/web-platform/tests/cookies/resources/setSameSiteDomain.py b/testing/web-platform/tests/cookies/resources/setSameSiteDomain.py
new file mode 100644
index 0000000000..c8b7a71981
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/setSameSiteDomain.py
@@ -0,0 +1,36 @@
+from cookies.resources.helpers import makeCookieHeader, setNoCacheAndCORSHeaders
+
+from wptserve.utils import isomorphic_encode
+
+def main(request, response):
+ """Respond to `/cookie/set/samesite?{value}` by setting four cookies:
+ 1. `samesite_strict={value};SameSite=Strict;path=/;domain={host}`
+ 2. `samesite_lax={value};SameSite=Lax;path=/;domain={host}`
+ 3. `samesite_none={value};SameSite=None;path=/;Secure;domain={host}`
+ 4. `samesite_unspecified={value};path=/;domain={host}`
+ Where {host} is the hostname from which this page is served. (Requesting this resource
+ without a Host header will result in a 500 server error.)
+ Then navigate to a page that will post a message back to the opener with the set cookies"""
+ headers = setNoCacheAndCORSHeaders(request, response)
+ value = isomorphic_encode(request.url_parts.query)
+ host_header = request.headers['host']
+ hostname = host_header.split(b":")[0]
+ host = isomorphic_encode(hostname)
+ headers.append((b"Content-Type", b"text/html; charset=utf-8"))
+ headers.append(makeCookieHeader(b"samesite_strict", value, {b"SameSite":b"Strict", b"path":b"/", b"domain":host}))
+ headers.append(makeCookieHeader(b"samesite_lax", value, {b"SameSite":b"Lax", b"path":b"/", b"domain":host}))
+ # SameSite=None cookies must be Secure.
+ headers.append(makeCookieHeader(b"samesite_none", value, {b"SameSite":b"None", b"path":b"/", b"Secure": b"", b"domain":host}))
+ headers.append(makeCookieHeader(b"samesite_unspecified", value, {b"path":b"/", b"domain":host}))
+
+ document = b"""
+<!DOCTYPE html>
+<script>
+ // A same-site navigation, which should attach all cookies including SameSite ones.
+ // This is necessary because this page may have been reached via a cross-site navigation, so
+ // we might not have access to some SameSite cookies from here.
+ window.location = "../samesite/resources/echo-cookies.html";
+</script>
+"""
+
+ return headers, document
diff --git a/testing/web-platform/tests/cookies/resources/setSameSiteMultiAttribute.py b/testing/web-platform/tests/cookies/resources/setSameSiteMultiAttribute.py
new file mode 100644
index 0000000000..988f67f0b0
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/setSameSiteMultiAttribute.py
@@ -0,0 +1,60 @@
+from cookies.resources.helpers import makeCookieHeader, setNoCacheAndCORSHeaders
+
+from wptserve.utils import isomorphic_encode
+
+def main(request, response):
+ """Respond to `/cookie/set/samesite?{value}` by setting the following combination of cookies:
+ 1. `samesite_unsupported={value};SameSite=Unsupported;path=/;Secure`
+ 2. `samesite_unsupported_none={value};SameSite=Unsupported;SameSite=None;path=/;Secure`
+ 3. `samesite_unsupported_lax={value};SameSite=Unsupported;SameSite=Lax;path=/`
+ 4. `samesite_unsupported_strict={value};SameSite=Unsupported;SameSite=Strict;path=/`
+ 5. `samesite_none_unsupported={value};SameSite=None;SameSite=Unsupported;path=/;Secure`
+ 6. `samesite_lax_unsupported={value};SameSite=Lax;SameSite=Unsupported;path=/;Secure`
+ 7. `samesite_strict_unsupported={value};SameSite=Strict;SameSite=Unsupported;path=/;Secure`
+ 8. `samesite_lax_none={value};SameSite=Lax;SameSite=None;path=/;Secure`
+ 9. `samesite_lax_strict={value};SameSite=Lax;SameSite=Strict;path=/`
+ 10. `samesite_strict_lax={value};SameSite=Strict;SameSite=Lax;path=/`
+ Then navigate to a page that will post a message back to the opener with the set cookies"""
+ headers = setNoCacheAndCORSHeaders(request, response)
+ value = isomorphic_encode(request.url_parts.query)
+
+ headers.append((b"Content-Type", b"text/html; charset=utf-8"))
+ # Unknown value; single attribute
+ headers.append(makeCookieHeader(
+ b"samesite_unsupported", value, {b"SameSite":b"Unsupported", b"path":b"/", b"Secure":b""}))
+
+ # Multiple attributes; first attribute unknown
+ headers.append(makeCookieHeader(
+ b"samesite_unsupported_none", value, {b"SameSite":b"Unsupported", b"SameSite":b"None", b"path":b"/", b"Secure":b""}))
+ headers.append(makeCookieHeader(
+ b"samesite_unsupported_lax", value, {b"SameSite":b"Unsupported", b"SameSite":b"Lax", b"path":b"/"}))
+ headers.append(makeCookieHeader(
+ b"samesite_unsupported_strict", value, {b"SameSite":b"Unsupported", b"SameSite":b"Strict", b"path":b"/"}))
+
+ # Multiple attributes; second attribute unknown
+ headers.append(makeCookieHeader(
+ b"samesite_none_unsupported", value, {b"SameSite":b"None", b"SameSite":b"Unsupported", b"path":b"/", b"Secure":b""}))
+ headers.append(makeCookieHeader(
+ b"samesite_lax_unsupported", value, {b"SameSite":b"Lax", b"SameSite":b"Unsupported", b"path":b"/", b"Secure":b""}))
+ headers.append(makeCookieHeader(
+ b"samesite_strict_unsupported", value, {b"SameSite":b"Strict", b"SameSite":b"Unsupported", b"path":b"/", b"Secure":b""}))
+
+ # Multiple attributes; both known
+ headers.append(makeCookieHeader(
+ b"samesite_lax_none", value, {b"SameSite":b"Lax", b"SameSite":b"None", b"path":b"/", b"Secure":b""}))
+ headers.append(makeCookieHeader(
+ b"samesite_lax_strict", value, {b"SameSite":b"Lax", b"SameSite":b"Strict", b"path":b"/"}))
+ headers.append(makeCookieHeader(
+ b"samesite_strict_lax", value, {b"SameSite":b"Strict", b"SameSite":b"Lax", b"path":b"/"}))
+
+ document = b"""
+<!DOCTYPE html>
+<script>
+ // A same-site navigation, which should attach all cookies including SameSite ones.
+ // This is necessary because this page may have been reached via a cross-site navigation, so
+ // we might not have access to some SameSite cookies from here.
+ window.location = "../samesite/resources/echo-cookies.html";
+</script>
+"""
+
+ return headers, document
diff --git a/testing/web-platform/tests/cookies/resources/setSameSiteNone.py b/testing/web-platform/tests/cookies/resources/setSameSiteNone.py
new file mode 100644
index 0000000000..446c75eb44
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/setSameSiteNone.py
@@ -0,0 +1,16 @@
+from cookies.resources.helpers import makeCookieHeader, setNoCacheAndCORSHeaders
+
+from wptserve.utils import isomorphic_encode
+
+def main(request, response):
+ """Respond to `/cookies/resources/setSameSiteNone.py?{value}` by setting two cookies:
+ 1. `samesite_none_insecure={value};SameSite=None;path=/`
+ 2. `samesite_none_secure={value};SameSite=None;Secure;path=/`
+ """
+ headers = setNoCacheAndCORSHeaders(request, response)
+ value = isomorphic_encode(request.url_parts.query)
+
+ headers.append(makeCookieHeader(b"samesite_none_insecure", value, {b"SameSite":b"None", b"path":b"/"}))
+ headers.append(makeCookieHeader(b"samesite_none_secure", value, {b"SameSite":b"None", b"Secure":b"", b"path":b"/"}))
+
+ return headers, b'{"success": true}'
diff --git a/testing/web-platform/tests/cookies/resources/setSecure.py b/testing/web-platform/tests/cookies/resources/setSecure.py
new file mode 100644
index 0000000000..dd0dd1622b
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/setSecure.py
@@ -0,0 +1,14 @@
+from cookies.resources.helpers import makeCookieHeader, readParameter, setNoCacheAndCORSHeaders
+
+from wptserve.utils import isomorphic_encode
+
+def main(request, response):
+ """Respond to `/cookie/set/secure?{value}` by setting two cookies:
+ alone_secure={value};secure;path=/`
+ alone_insecure={value};path=/"""
+ headers = setNoCacheAndCORSHeaders(request, response)
+ value = isomorphic_encode(request.url_parts.query)
+
+ headers.append(makeCookieHeader(b"alone_secure", value, {b"secure": b"", b"path": b"/"}))
+ headers.append(makeCookieHeader(b"alone_insecure", value, {b"path": b"/"}))
+ return headers, b'{"success": true}'
diff --git a/testing/web-platform/tests/cookies/resources/testharness-helpers.js b/testing/web-platform/tests/cookies/resources/testharness-helpers.js
new file mode 100644
index 0000000000..84368d6d99
--- /dev/null
+++ b/testing/web-platform/tests/cookies/resources/testharness-helpers.js
@@ -0,0 +1,49 @@
+// Given an array of potentially asynchronous tests, this function will execute
+// each in serial, ensuring that one and only one test is executing at a time.
+//
+// The test array should look like this:
+//
+//
+// var tests = [
+// [
+// "Test description goes here.",
+// function () {
+// // Test code goes here. `this` is bound to the test object.
+// }
+// ],
+// ...
+// ];
+//
+// The |setup| and |teardown| arguments are functions which are executed before
+// and after each test, respectively.
+function executeTestsSerially(testList, setup, teardown) {
+ var tests = testList.map(function (t) {
+ return {
+ test: async_test(t[0]),
+ code: t[1]
+ };
+ });
+
+ var executeNextTest = function () {
+ var current = tests.shift();
+ if (current === undefined) {
+ return;
+ }
+
+ // Setup the test fixtures.
+ if (setup) {
+ setup();
+ }
+
+ // Bind a callback to tear down the test fixtures.
+ if (teardown) {
+ current.test.add_cleanup(teardown);
+ }
+
+ // Execute the test.
+ current.test.step(current.code);
+ };
+
+ add_result_callback(function () { setTimeout(executeNextTest, 0) });
+ executeNextTest();
+}
diff --git a/testing/web-platform/tests/cookies/samesite-none-secure/cookies-without-samesite-must-be-secure.https.html b/testing/web-platform/tests/cookies/samesite-none-secure/cookies-without-samesite-must-be-secure.https.html
new file mode 100644
index 0000000000..18cf0516e6
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite-none-secure/cookies-without-samesite-must-be-secure.https.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+promise_test(t => {
+ var value = "" + Math.random();
+ return resetSameSiteNoneCookies(SECURE_ORIGIN, value)
+ .then(_ => {
+ return credFetch(SECURE_ORIGIN + "/cookies/resources/list.py")
+ .then(r => r.json())
+ .then(cookies => {
+ assert_not_equals(cookies["samesite_none_insecure"], value, "Non-Secure SameSite=None cookie is rejected.");
+ assert_equals(cookies["samesite_none_secure"], value, "Secure SameSite=None cookie is set.");
+ })
+ });
+}, "SameSite=None cookies are rejected unless the Secure attribute is set.");
+</script>
diff --git a/testing/web-platform/tests/cookies/samesite/about-blank-nested.https.html b/testing/web-platform/tests/cookies/samesite/about-blank-nested.https.html
new file mode 100644
index 0000000000..d5c9b8ada3
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/about-blank-nested.https.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/cookies/resources/cookie-helper.sub.js"></script>
+</head>
+<body onload="doTests()">
+ <iframe src="about:blank" id="if">
+ </iframe>
+ <script>
+ function doTests() {
+ promise_test(async function(t) {
+ var child = document.getElementById("if");
+ var grandKid = child.contentDocument.createElement("iframe");
+ child.contentDocument.body.appendChild(grandKid);
+ var value = "" + Math.random();
+ await resetSameSiteCookies(SECURE_ORIGIN, value);
+
+ // Using postToParent.py here to see cookies used when navigating the page.
+ grandKid.src = SECURE_ORIGIN + "/cookies/resources/postToParent.py"
+ var e = await wait_for_message("COOKIES", SECURE_ORIGIN);
+ assert_cookie(SECURE_ORIGIN, e.data, "samesite_unspecified", value, true);
+ assert_cookie(SECURE_ORIGIN, e.data, "samesite_lax", value, true);
+ assert_cookie(SECURE_ORIGIN, e.data, "samesite_strict", value, true);
+ assert_cookie(SECURE_ORIGIN, e.data, "samesite_none", value, true);
+ }, "SameSite cookies with intervening about:blank iframes and navigation");
+ }
+ </script>
+</body>
diff --git a/testing/web-platform/tests/cookies/samesite/about-blank-subresource.https.html b/testing/web-platform/tests/cookies/samesite/about-blank-subresource.https.html
new file mode 100644
index 0000000000..0a19f30d34
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/about-blank-subresource.https.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/cookies/resources/cookie-helper.sub.js"></script>
+</head>
+<body onload="doTests()">
+ <script>
+ function doTests() {
+ promise_test(async function(t) {
+ var child = window.open("");
+ child.onmessage = (ev) => {
+ child.opener.postMessage(ev.data, '*');
+ };
+ var grandKid = child.document.createElement("iframe");
+ child.document.body.appendChild(grandKid);
+ var value = "" + Math.random();
+ await resetSameSiteCookies(SECURE_ORIGIN, value);
+
+ // Load at what cookies a subresource below an about:blank iframe
+ // inheritting this origin gets.
+ grandKid.src = SECURE_ORIGIN + "/cookies/samesite/resources/iframe-subresource-report.html"
+ var e = await wait_for_message("COOKIES", SECURE_ORIGIN);
+ assert_cookie(SECURE_ORIGIN, e.data, "samesite_unspecified", value, true);
+ assert_cookie(SECURE_ORIGIN, e.data, "samesite_lax", value, true);
+ assert_cookie(SECURE_ORIGIN, e.data, "samesite_strict", value, true);
+ assert_cookie(SECURE_ORIGIN, e.data, "samesite_none", value, true);
+ }, "SameSite cookies on subresource of top-level about:blank window");
+ }
+ </script>
+</body>
diff --git a/testing/web-platform/tests/cookies/samesite/about-blank-toplevel.https.html b/testing/web-platform/tests/cookies/samesite/about-blank-toplevel.https.html
new file mode 100644
index 0000000000..3a4dde7673
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/about-blank-toplevel.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/cookies/resources/cookie-helper.sub.js"></script>
+</head>
+<body onload="doTests()">
+ <script>
+ function doTests() {
+ promise_test(async function(t) {
+ var child = window.open("");
+ child.onmessage = (ev) => {
+ child.opener.postMessage(ev.data, '*');
+ };
+ var grandKid = child.document.createElement("iframe");
+ child.document.body.appendChild(grandKid);
+ var value = "" + Math.random();
+ await resetSameSiteCookies(SECURE_ORIGIN, value);
+
+ // Using postToParent.py here to see cookies used when navigating the page.
+ grandKid.src = SECURE_ORIGIN + "/cookies/resources/postToParent.py"
+ var e = await wait_for_message("COOKIES", SECURE_ORIGIN);
+ assert_cookie(SECURE_ORIGIN, e.data, "samesite_unspecified", value, true);
+ assert_cookie(SECURE_ORIGIN, e.data, "samesite_lax", value, true);
+ assert_cookie(SECURE_ORIGIN, e.data, "samesite_strict", value, true);
+ assert_cookie(SECURE_ORIGIN, e.data, "samesite_none", value, true);
+ }, "SameSite cookies with top-level about:blank window");
+ }
+ </script>
+</body>
diff --git a/testing/web-platform/tests/cookies/samesite/fetch.https.html b/testing/web-platform/tests/cookies/samesite/fetch.https.html
new file mode 100644
index 0000000000..79e49009fb
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/fetch.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ function create_test(origin, target, expectedStatus, title) {
+ promise_test(t => {
+ var value = "" + Math.random();
+ return resetSameSiteCookies(origin, value)
+ .then(_ => {
+ return credFetch(target + "/cookies/resources/list.py")
+
+ .then(r => r.json())
+ .then(cookies => verifySameSiteCookieState(expectedStatus, value, cookies, DomSameSiteStatus.SAME_SITE));
+ });
+ }, title);
+ }
+
+ // No redirect:
+ create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, "Same-host fetches are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Subdomain fetches are strictly same-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.CROSS_SITE, "Cross-site fetches are cross-site");
+
+ // Redirect from {same-host,subdomain,cross-site} to same-host:
+ create_test(SECURE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to same-host fetches are strictly same-site");
+ create_test(SECURE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to same-host fetches are strictly same-site");
+ create_test(SECURE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to same-host fetches are cross-site");
+
+ // Redirect from {same-host,subdomain,cross-site} to same-host:
+ create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to subdomain fetches are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to subdomain fetches are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to subdomain fetches are cross-site");
+
+ // Redirect from {same-host,subdomain,cross-site} to cross-site:
+ create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Same-host redirecting to cross-site fetches are cross-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Subdomain redirecting to cross-site fetches are cross-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to cross-site fetches are cross-site");
+</script>
diff --git a/testing/web-platform/tests/cookies/samesite/form-get-blank-reload.https.html b/testing/web-platform/tests/cookies/samesite/form-get-blank-reload.https.html
new file mode 100644
index 0000000000..b5ab8ade91
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/form-get-blank-reload.https.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ // This test creates a form whose submission GETs the page postToParent.py
+ // (on the specified origin) in a popup window. The popup sends a postMessage
+ // event back to its opener (i.e., here) with the cookies it received, which
+ // we verify against expectedStatus. Then, the test sends a message to the
+ // popup, telling it to reload itself via window.location.reload(). Again,
+ // the popup posts a message back here with the cookies it received. These
+ // cookies are verified against expectedStatusReload.
+ function create_test(origin, target, expectedStatus, expectedStatusReload, title) {
+ promise_test(t => {
+ var value = "" + Math.random();
+ return resetSameSiteCookies(origin, value)
+ .then(_ => {
+ return new Promise((resolve, reject) => {
+ var f = document.createElement('form');
+ f.action = target + "/cookies/resources/postToParent.py";
+ f.target = "_blank";
+ f.method = "GET";
+ f.rel = "opener";
+
+ // If |target| contains a `redir` parameter, extract it, and add it
+ // to the form so it doesn't get dropped in the submission.
+ var url = new URL(f.action);
+ if (url.pathname = "/cookies/rfc6265/resources/redirectWithCORSHeaders.py") {
+ var i = document.createElement("input");
+ i.name = "location";
+ i.value = url.searchParams.get("location");
+ i.type = "hidden";
+ f.appendChild(i);
+ }
+ var reloaded = false;
+ var msgHandler = e => {
+ try {
+ verifySameSiteCookieState(reloaded ? expectedStatusReload : expectedStatus, value, e.data, DomSameSiteStatus.SAME_SITE);
+ } catch (e) {
+ reject(e);
+ }
+
+ if (reloaded) {
+ window.removeEventListener("message", msgHandler);
+ e.source.close();
+ resolve("Popup received the cookie.");
+ } else {
+ reloaded = true;
+ e.source.postMessage("reload", "*");
+ }
+ };
+ window.addEventListener("message", msgHandler);
+ document.body.appendChild(f);
+
+ f.submit();
+ });
+ });
+ }, title);
+ }
+
+ // The reload status is always strictly same-site because this is a site-initiated reload, as opposed to a reload triggered by a user interface element.
+ create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, SameSiteStatus.STRICT, "Reloaded same-host top-level form GETs are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, SameSiteStatus.STRICT, "Reloaded subdomain top-level form GETs are strictly same-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.LAX, SameSiteStatus.STRICT, "Reloaded cross-site top-level form GETs are strictly same-site");
+</script>
diff --git a/testing/web-platform/tests/cookies/samesite/form-get-blank.https.html b/testing/web-platform/tests/cookies/samesite/form-get-blank.https.html
new file mode 100644
index 0000000000..2fe81b2b56
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/form-get-blank.https.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ function create_test(origin, target, expectedStatus, title) {
+ promise_test(t => {
+ var value = "" + Math.random();
+ return resetSameSiteCookies(origin, value)
+ .then(_ => {
+ return new Promise((resolve, reject) => {
+ var f = document.createElement('form');
+ f.action = target + "/cookies/resources/postToParent.py";
+ f.target = "_blank";
+ f.method = "GET";
+ f.rel = "opener";
+
+ // If |target| contains a `redir` parameter, extract it, and add it
+ // to the form so it doesn't get dropped in the submission.
+ var url = new URL(f.action);
+ if (url.pathname == "/cookies/resources/redirectWithCORSHeaders.py") {
+ var i = document.createElement("input");
+ i.name = "location";
+ i.type="hidden";
+ i.value = url.searchParams.get("location");
+ f.appendChild(i);
+ }
+
+ var msgHandler = e => {
+ window.removeEventListener("message", msgHandler);
+ e.source.close();
+ try {
+ verifySameSiteCookieState(expectedStatus, value, e.data, DomSameSiteStatus.SAME_SITE);
+ resolve("Popup received the cookie.");
+ } catch (e) {
+ reject(e);
+ }
+ };
+ window.addEventListener("message", msgHandler);
+ document.body.appendChild(f);
+ f.submit();
+ });
+ });
+ }, title);
+ }
+
+ // No redirect:
+ create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, "Same-host top-level form GETs are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Subdomain top-level form GETs are strictly same-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.LAX, "Cross-site top-level form GETs are laxly same-site");
+
+ // Redirect from {same-host,subdomain,cross-site} to same-host:
+ create_test(SECURE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to same-host top-level form GETs are strictly same-site");
+ create_test(SECURE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to same-host top-level form GETs are strictly same-site");
+ create_test(SECURE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.LAX, "Cross-site redirecting to same-host top-level form GETs are laxly same-site");
+
+ // Redirect from {same-host,subdomain,cross-site} to same-host:
+ create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to subdomain top-level form GETs are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to subdomain top-level form GETs are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.LAX, "Cross-site redirecting to subdomain top-level form GETs are laxly same-site");
+
+ // Redirect from {same-host,subdomain,cross-site} to cross-site:
+ create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.LAX, "Same-host redirecting to cross-site top-level form GETs are laxly same-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.LAX, "Subdomain redirecting to cross-site top-level form GETs are laxly same-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.LAX, "Cross-site redirecting to cross-site top-level form GETs are laxly same-site");
+</script>
diff --git a/testing/web-platform/tests/cookies/samesite/form-post-blank-reload.https.html b/testing/web-platform/tests/cookies/samesite/form-post-blank-reload.https.html
new file mode 100644
index 0000000000..cdbb89ace5
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/form-post-blank-reload.https.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ // This test creates a form whose submission POSTs to the page postToParent.py
+ // (on the specified origin) in a popup window. The popup sends a postMessage
+ // event back to its opener (i.e., here) with the cookies it received, which
+ // we verify against expectedStatus. Then, the test sends a message to the
+ // popup, telling it to reload itself via window.location.reload(). Again,
+ // the popup posts a message back here with the cookies it received. These
+ // cookies are verified against expectedStatusReload.
+ function create_test(origin, target, expectedStatus, expectedStatusReload, title) {
+ promise_test(t => {
+ var value = "" + Math.random();
+ return resetSameSiteCookies(origin, value)
+ .then(_ => {
+ return new Promise((resolve, reject) => {
+ var f = document.createElement('form');
+ f.action = target + "/cookies/resources/postToParent.py";
+ f.target = "_blank";
+ f.method = "POST";
+ f.rel = "opener";
+
+ var reloaded = false;
+ var msgHandler = e => {
+ try {
+ verifySameSiteCookieState(reloaded ? expectedStatusReload : expectedStatus, value, e.data, DomSameSiteStatus.SAME_SITE);
+ } catch (e) {
+ reject(e);
+ }
+
+ if (reloaded) {
+ window.removeEventListener("message", msgHandler);
+ e.source.close();
+ resolve("Popup received the cookie.");
+ } else {
+ reloaded = true;
+ e.source.postMessage("reload", "*");
+ }
+ };
+ window.addEventListener("message", msgHandler);
+
+ document.body.appendChild(f);
+ f.submit();
+ });
+ });
+ }, title);
+ }
+
+ // The reload status is always strictly same-site because this is a site-initiated reload, as opposed to a reload triggered by a user interface element.
+ create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, SameSiteStatus.STRICT, "Reloaded same-host top-level form POSTs are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, SameSiteStatus.STRICT, "Reloaded subdomain top-level form POSTs are strictly same-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.CROSS_SITE, SameSiteStatus.STRICT, "Reloaded cross-site top-level form POSTs are strictly same-site");
+</script>
diff --git a/testing/web-platform/tests/cookies/samesite/form-post-blank.https.html b/testing/web-platform/tests/cookies/samesite/form-post-blank.https.html
new file mode 100644
index 0000000000..947853893d
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/form-post-blank.https.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ function create_test(origin, target, expectedStatus, title) {
+ promise_test(t => {
+ var value = "" + Math.random();
+ return resetSameSiteCookies(origin, value)
+ .then(_ => {
+ return new Promise((resolve, reject) => {
+ var f = document.createElement('form');
+ f.action = target + "/cookies/resources/postToParent.py";
+ f.target = "_blank";
+ f.method = "POST";
+ f.rel = "opener";
+
+ var msgHandler = e => {
+ window.removeEventListener("message", msgHandler);
+ e.source.close();
+ try {
+ verifySameSiteCookieState(expectedStatus, value, e.data, DomSameSiteStatus.SAME_SITE);
+ resolve("Popup received the cookie.");
+ } catch (e) {
+ reject(e);
+ }
+ };
+ window.addEventListener("message", msgHandler);
+ document.body.appendChild(f);
+ f.submit();
+ });
+ });
+ }, title);
+ }
+
+ // No redirect:
+ create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, "Same-host top-level form POSTs are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Subdomain top-level form POSTs are strictly same-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.CROSS_SITE, "Cross-site top-level form POSTs are cross-site");
+
+ // Redirect from {same-host,subdomain,cross-site} to same-host:
+ create_test(SECURE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to same-host top-level form POSTs are strictly same-site");
+ create_test(SECURE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to same-host top-level form POSTs are strictly same-site");
+ create_test(SECURE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to same-host top-level form POSTs are cross-site");
+
+ // Redirect from {same-host,subdomain,cross-site} to same-host:
+ create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to subdomain top-level form POSTs are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to subdomain top-level form POSTs are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to subdomain top-level form POSTs are cross-site");
+
+ // Redirect from {same-host,subdomain,cross-site} to cross-site:
+ create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Same-host redirecting to cross-site top-level form POSTs are cross-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Subdomain redirecting to cross-site top-level form POSTs are cross-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to cross-site top-level form POSTs are cross-site");
+</script>
diff --git a/testing/web-platform/tests/cookies/samesite/get_all_cookies-default-samesite.html b/testing/web-platform/tests/cookies/samesite/get_all_cookies-default-samesite.html
new file mode 100644
index 0000000000..3b1606b7f8
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/get_all_cookies-default-samesite.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>TestDriver get_all_cookies method w/ default SameSite</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script>
+promise_test(async t => {
+ await test_driver.delete_all_cookies();
+ t.add_cleanup(test_driver.delete_all_cookies);
+ document.cookie = "test0=0";
+ const cookies = await test_driver.get_all_cookies();
+ assert_equals(cookies.length, 1);
+ const cookie = cookies[0];
+ assert_equals(cookie["name"], "test0");
+ assert_equals(cookie["value"], "0");
+ assert_equals(cookie["sameSite"], "Lax");
+}, "Get all cookies w/ default SameSite");
+</script>
diff --git a/testing/web-platform/tests/cookies/samesite/get_named_cookie-default-samesite.html b/testing/web-platform/tests/cookies/samesite/get_named_cookie-default-samesite.html
new file mode 100644
index 0000000000..2ee5d0f29c
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/get_named_cookie-default-samesite.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>TestDriver get_named_cookie method w/ default SameSite</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script>
+promise_test(async t => {
+ await test_driver.delete_all_cookies();
+ t.add_cleanup(test_driver.delete_all_cookies);
+ document.cookie = "test0=0";
+ const cookie = await test_driver.get_named_cookie("test0");
+ assert_equals(cookie["name"], "test0");
+ assert_equals(cookie["value"], "0");
+ assert_equals(cookie["sameSite"], "Lax");
+}, "Get Named cookie w/ default SameSite");
+</script>
diff --git a/testing/web-platform/tests/cookies/samesite/iframe-reload.https.html b/testing/web-platform/tests/cookies/samesite/iframe-reload.https.html
new file mode 100644
index 0000000000..d1916a805c
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/iframe-reload.https.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<!-- We're appending an <iframe> to the document's body, so execute tests after we have a body -->
+<body>
+<script>
+ // This test creates an iframe with postToParent.py on the specified origin,
+ // which sends a postMessage event with the cookies it received back to the
+ // parent (i.e., here). Upon receiving the message, the test verifies that the
+ // correct cookies were sent to the iframe, and posts a message back to the
+ // iframe telling it to reload itself. Upon reload, the iframe sends a
+ // postMessage event back to the test with the cookies it received, which are
+ // again verified.
+ function create_test(origin, target, expectedStatus, expectedDomStatus, title) {
+ promise_test(t => {
+ var value = "" + Math.random();
+ return resetSameSiteCookies(origin, value)
+ .then(_ => {
+ return new Promise((resolve, reject) => {
+ var iframe = document.createElement("iframe");
+ iframe.onerror = _ => reject("IFrame could not be loaded.");
+
+ var reloaded = false;
+ var msgHandler = e => {
+ try {
+ verifySameSiteCookieState(expectedStatus, value, e.data, expectedDomStatus);
+ } catch (e) {
+ reject(e);
+ }
+
+ if (reloaded) {
+ window.removeEventListener("message", msgHandler);
+ document.body.removeChild(iframe);
+ resolve("IFrame received the cookie.");
+ } else {
+ reloaded = true;
+ e.source.postMessage("reload", "*");
+ }
+ };
+ window.addEventListener("message", msgHandler);
+
+ iframe.src = target + "/cookies/resources/postToParent.py";
+ document.body.appendChild(iframe);
+ });
+ });
+ }, title);
+ }
+
+ create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Reloaded same-host fetches are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Reloaded subdomain fetches are strictly same-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.CROSS_SITE, DomSameSiteStatus.CROSS_SITE, "Reloaded cross-site fetches are cross-site");
+</script>
diff --git a/testing/web-platform/tests/cookies/samesite/iframe.document.https.html b/testing/web-platform/tests/cookies/samesite/iframe.document.https.html
new file mode 100644
index 0000000000..2d276dbcaa
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/iframe.document.https.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<!-- We're appending an <iframe> to the document's body, so execute tests after we have a body -->
+<body>
+<script>
+ function create_test(target, expectedDomStatus, title) {
+ promise_test(async t => {
+ let cookieValue = await new Promise((resolve, reject) => {
+ var iframe = document.createElement("iframe");
+
+ window.onmessage = t.step_func(e => {
+ if (e.source == iframe.contentWindow) {
+ document.body.removeChild(iframe);
+ resolve(e.data.value);
+ }
+ });
+
+ iframe.src = target + "/cookies/samesite/resources/iframe.document.html";
+ document.body.appendChild(iframe);
+ });
+
+ await new Promise((resolve, reject) => {
+ var iframe = document.createElement("iframe");
+
+ window.onmessage = t.step_func(e => {
+ if (e.source == iframe.contentWindow) {
+ // Cleanup, then verify cookie state:
+ document.body.removeChild(iframe);
+
+ const cookies = e.data;
+ assert_equals(cookies["dc_samesite_none"], cookieValue, "SameSite=none cookies can be set via document.cookies even by cross-origin documents");
+
+ if (expectedDomStatus === DomSameSiteStatus.SAME_SITE) {
+ assert_equals(cookies["dc_samesite_lax"], cookieValue, "SameSite=lax cookies can be set via document.cookies by same-site documents");
+ assert_equals(cookies["dc_samesite_strict"], cookieValue, "SameSite=strict cookies can be set via document.cookies by same-site documents");
+ } else if (expectedDomStatus === DomSameSiteStatus.CROSS_SITE) {
+ assert_not_equals(cookies["dc_samesite_lax"], cookieValue, "SameSite=lax cookies can be set via document.cookies by same-site documents");
+ assert_not_equals(cookies["dc_samesite_strict"], cookieValue, "SameSite=strict cookies can be set via document.cookies by same-site documents");
+ }
+
+ resolve();
+ }
+ });
+
+ iframe.src = target + "/cookies/resources/postToParent.py";
+ document.body.appendChild(iframe);
+ });
+ }, title);
+ }
+
+ create_test(SECURE_ORIGIN, DomSameSiteStatus.SAME_SITE, "Same-site iframes can set lax/strict cookies via document.cookie");
+ create_test(SECURE_CROSS_SITE_ORIGIN, DomSameSiteStatus.CROSS_SITE, "Cross-site iframe cannot set lax/strict cookies via document.cookie");
+</script>
diff --git a/testing/web-platform/tests/cookies/samesite/iframe.https.html b/testing/web-platform/tests/cookies/samesite/iframe.https.html
new file mode 100644
index 0000000000..31b34ccf55
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/iframe.https.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<!-- We're appending an <iframe> to the document's body, so execute tests after we have a body -->
+<body>
+<script>
+ // This test creates an iframe with postToParent.py on the specified origin,
+ // which sends a postMessage event with the cookies it received back to the
+ // parent (i.e., here). Upon receiving the message, the test verifies that the
+ // correct cookies were sent to the iframe.
+ function create_test(origin, target, expectedStatus, expectedDomStatus, title) {
+ promise_test(t => {
+ var value = "" + Math.random();
+ return resetSameSiteCookies(origin, value)
+ .then(_ => {
+ return new Promise((resolve, reject) => {
+ var iframe = document.createElement("iframe");
+ iframe.onerror = _ => reject("IFrame could not be loaded.");
+
+ var msgHandler = e => {
+ if (e.source == iframe.contentWindow) {
+ // Cleanup, then verify cookie state:
+ document.body.removeChild(iframe);
+ window.removeEventListener("message", msgHandler);
+ try {
+ verifySameSiteCookieState(expectedStatus, value, e.data, expectedDomStatus);
+ resolve();
+ } catch(e) {
+ reject(e);
+ }
+ }
+ };
+ window.addEventListener("message", msgHandler);
+
+ iframe.src = target + "/cookies/resources/postToParent.py";
+ document.body.appendChild(iframe);
+ });
+ });
+ }, title);
+ }
+
+ // No redirect:
+ create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Same-host fetches are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Subdomain fetches are strictly same-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.CROSS_SITE, DomSameSiteStatus.CROSS_SITE, "Cross-site fetches are cross-site");
+
+ // Redirect from {same-host,subdomain,cross-site} to same-host:
+ create_test(SECURE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Same-host redirecting to same-host fetches are strictly same-site");
+ create_test(SECURE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Subdomain redirecting to same-host fetches are strictly same-site");
+ create_test(SECURE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.SAME_SITE, "Cross-site redirecting to same-host fetches are cross-site");
+
+ // Redirect from {same-host,subdomain,cross-site} to subdomain:
+ create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Same-host redirecting to subdomain fetches are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Subdomain redirecting to subdomain fetches are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.SAME_SITE, "Cross-site redirecting to subdomain fetches are cross-site");
+
+ // Redirect from {same-host,subdomain,cross-site} to cross-site:
+ create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.CROSS_SITE, "Same-host redirecting to cross-site fetches are cross-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.CROSS_SITE, "Subdomain redirecting to cross-site fetches are cross-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.CROSS_SITE, "Cross-site redirecting to cross-site fetches are cross-site");
+
+ // Navigate from {same-host,subdomain,cross-site} to same-host:
+ create_test(SECURE_ORIGIN, navigateTo(SECURE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Same-host navigating to same-host fetches are strictly same-site");
+ create_test(SECURE_ORIGIN, navigateTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Subdomain navigating to same-host fetches are strictly same-site");
+ create_test(SECURE_ORIGIN, navigateTo(SECURE_CROSS_SITE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.SAME_SITE, "Cross-site navigating to same-host fetches are cross-site");
+
+ // Navigate from {same-host,subdomain,cross-site} to subdomain:
+ create_test(SECURE_SUBDOMAIN_ORIGIN, navigateTo(SECURE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Same-host navigating to subdomain fetches are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, navigateTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, DomSameSiteStatus.SAME_SITE, "Subdomain navigating to subdomain fetches are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, navigateTo(SECURE_CROSS_SITE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.SAME_SITE, "Cross-site navigating to subdomain fetches are cross-site-site");
+
+ // Navigate from {same-host,subdomain,cross-site} to cross-site:
+ create_test(SECURE_CROSS_SITE_ORIGIN, navigateTo(SECURE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.CROSS_SITE, "Same-host navigating to cross-site fetches are cross-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, navigateTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.CROSS_SITE, "Subdomain navigating to cross-site fetches are cross-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, navigateTo(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, DomSameSiteStatus.CROSS_SITE, "Cross-site navigating to cross-site fetches are cross-site");
+</script>
diff --git a/testing/web-platform/tests/cookies/samesite/img.https.html b/testing/web-platform/tests/cookies/samesite/img.https.html
new file mode 100644
index 0000000000..1ddc8d99ad
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/img.https.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ function assert_cookie_present(origin, name, value) {
+ return new Promise((resolve, reject) => {
+ var img = document.createElement("img");
+ img.onload = _ => resolve("'" + name + "=" + value + "' present on " + origin);
+ img.onerror = _ => reject("'" + name + "=" + value + "' not present on " + origin);
+
+ // We need to URL encode the destination path/query if we're redirecting:
+ if (origin.match(/\/redir/))
+ img.src = origin + encodeURIComponent("/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value);
+ else
+ img.src = origin + "/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value;
+ });
+ }
+
+ function assert_cookie_absent(origin, name, value) {
+ return new Promise((resolve, reject) => {
+ var img = document.createElement("img");
+ img.onload = _ => reject("'" + name + "=" + value + "' present on " + origin);
+ img.onerror = _ => resolve("'" + name + "=" + value + "' not present on " + origin);
+
+ // We need to URL encode the destination path/query if we're redirecting:
+ if (origin.match(/\/redir/))
+ img.src = origin + encodeURIComponent("/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value);
+ else
+ img.src = origin + "/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value;
+ });
+ }
+
+ function create_test(origin, target, expectedStatus, title) {
+ promise_test(t => {
+ var value = "" + Math.random();
+ return resetSameSiteCookies(origin, value)
+ .then(_ => {
+ var asserts = [assert_cookie_present(target, "samesite_none", value),
+ expectedStatus == SameSiteStatus.STRICT ?
+ assert_cookie_present(target, "samesite_strict", value) :
+ assert_cookie_absent(target, "samesite_strict", value),
+ expectedStatus == SameSiteStatus.CROSS_SITE ?
+ assert_cookie_absent(target, "samesite_lax", value) :
+ assert_cookie_present(target, "samesite_lax", value),
+ expectedStatus == SameSiteStatus.CROSS_SITE ?
+ assert_cookie_absent(target, "samesite_unspecified", value) :
+ assert_cookie_present(target, "samesite_unspecified", value)];
+ return Promise.all(asserts);
+ });
+ }, title);
+ }
+
+ // No redirect:
+ create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, "Same-host images are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Subdomain images are strictly same-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.CROSS_SITE, "Cross-site images are cross-site");
+
+ // Redirect from {same-host,subdomain,cross-site} to same-host:
+ create_test(SECURE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to same-host images are strictly same-site");
+ create_test(SECURE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to same-host images are strictly same-site");
+ create_test(SECURE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to same-host images are cross-site");
+
+ // Redirect from {same-host,subdomain,cross-site} to same-host:
+ create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to subdomain images are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to subdomain images are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to subdomain images are cross-site");
+
+ // Redirect from {same-host,subdomain,cross-site} to cross-site:
+ create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Same-host redirecting to cross-site images are cross-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Subdomain redirecting to cross-site images are cross-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to cross-site images are cross-site");
+</script>
diff --git a/testing/web-platform/tests/cookies/samesite/multiple-samesite-attributes.https.html b/testing/web-platform/tests/cookies/samesite/multiple-samesite-attributes.https.html
new file mode 100644
index 0000000000..d32a39639d
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/multiple-samesite-attributes.https.html
@@ -0,0 +1,92 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ function assert_cookie_present(origin, name, value) {
+ return new Promise((resolve, reject) => {
+ var img = document.createElement("img");
+ img.onload = _ => resolve("'" + name + "=" + value + "' present on " + origin);
+ img.onerror = _ => reject("'" + name + "=" + value + "' not present on " + origin);
+
+ // We need to URL encode the destination path/query if we're redirecting:
+ if (origin.match(/\/redir/))
+ img.src = origin + encodeURIComponent("/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value);
+ else
+ img.src = origin + "/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value;
+ });
+ }
+
+ function assert_cookie_absent(origin, name, value) {
+ return new Promise((resolve, reject) => {
+ var img = document.createElement("img");
+ img.onload = _ => reject("'" + name + "=" + value + "' present on " + origin);
+ img.onerror = _ => resolve("'" + name + "=" + value + "' not present on " + origin);
+
+ // We need to URL encode the destination path/query if we're redirecting:
+ if (origin.match(/\/redir/))
+ img.src = origin + encodeURIComponent("/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value);
+ else
+ img.src = origin + "/cookies/resources/imgIfMatch.py?name=" + name + "&value=" + value;
+ });
+ }
+
+ function create_test(origin, target, expectedStatus, title) {
+ promise_test(t => {
+ var value = "" + Math.random();
+ return resetSameSiteMultiAttributeCookies(origin, value)
+ .then(_ => {
+ var asserts = [
+ assert_cookie_present(target, "samesite_unsupported_none", value),
+ assert_cookie_present(target, "samesite_lax_none", value),
+ expectedStatus == SameSiteStatus.STRICT ?
+ assert_cookie_present(target, "samesite_unsupported_strict", value) :
+ assert_cookie_absent(target, "samesite_unsupported_strict", value),
+ expectedStatus == SameSiteStatus.STRICT ?
+ assert_cookie_present(target, "samesite_lax_strict", value) :
+ assert_cookie_absent(target, "samesite_lax_strict", value),
+ expectedStatus == SameSiteStatus.CROSS_SITE ?
+ assert_cookie_absent(target, "samesite_unsupported_lax", value) :
+ assert_cookie_present(target, "samesite_unsupported_lax", value),
+ expectedStatus == SameSiteStatus.CROSS_SITE ?
+ assert_cookie_absent(target, "samesite_strict_lax", value) :
+ assert_cookie_present(target, "samesite_strict_lax", value),
+ expectedStatus == SameSiteStatus.CROSS_SITE ?
+ assert_cookie_absent(target, "samesite_none_unsupported", value) :
+ assert_cookie_present(target, "samesite_none_unsupported", value),
+ expectedStatus == SameSiteStatus.CROSS_SITE ?
+ assert_cookie_absent(target, "samesite_lax_unsupported", value) :
+ assert_cookie_present(target, "samesite_lax_unsupported", value),
+ expectedStatus == SameSiteStatus.CROSS_SITE ?
+ assert_cookie_absent(target, "samesite_strict_unsupported", value) :
+ assert_cookie_present(target, "samesite_strict_unsupported", value),
+ expectedStatus == SameSiteStatus.CROSS_SITE ?
+ assert_cookie_absent(target, "samesite_unsupported", value) :
+ assert_cookie_present(target, "samesite_unsupported", value)];
+ return Promise.all(asserts);
+ });
+ }, title);
+ }
+
+ // No redirect:
+ create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, "Same-host images are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Subdomain images are strictly same-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.CROSS_SITE, "Cross-site images are cross-site");
+
+ // Redirect from {same-host,subdomain,cross-site} to same-host:
+ create_test(SECURE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to same-host images are strictly same-site");
+ create_test(SECURE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to same-host images are strictly same-site");
+ create_test(SECURE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to same-host images are cross-site");
+
+ // Redirect from {same-host,subdomain,cross-site} to same-host:
+ create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to subdomain images are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to subdomain images are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to subdomain images are cross-site");
+
+ // Redirect from {same-host,subdomain,cross-site} to cross-site:
+ create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Same-host redirecting to cross-site images are cross-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Subdomain redirecting to cross-site images are cross-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.CROSS_SITE, "Cross-site redirecting to cross-site images are cross-site");
+</script>
diff --git a/testing/web-platform/tests/cookies/samesite/resources/echo-cookies.html b/testing/web-platform/tests/cookies/samesite/resources/echo-cookies.html
new file mode 100644
index 0000000000..a1b29b9b03
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/resources/echo-cookies.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script>
+ if (window.opener)
+ window.opener.postMessage({ type: 'COOKIES_SET', cookies: document.cookie }, '*');
+ if (window.parent !== window)
+ window.parent.postMessage({ type: 'FRAME_COOKIES_SET', cookies: document.cookie }, '*');
+</script>
diff --git a/testing/web-platform/tests/cookies/samesite/resources/iframe-navigate-report.html b/testing/web-platform/tests/cookies/samesite/resources/iframe-navigate-report.html
new file mode 100644
index 0000000000..98ea469fda
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/resources/iframe-navigate-report.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<iframe src="/cookies/resources/postToParent.py">
+</iframe>
diff --git a/testing/web-platform/tests/cookies/samesite/resources/iframe-subresource-report.html b/testing/web-platform/tests/cookies/samesite/resources/iframe-subresource-report.html
new file mode 100644
index 0000000000..1dceb4e436
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/resources/iframe-subresource-report.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<head>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+function reportSubresourceCookies() {
+ credFetch(SECURE_ORIGIN + "/cookies/resources/list.py")
+ .then(r => r.json())
+ .then(cookies => { cookies.type = "COOKIES";
+ target = window.opener ? window.opener : window.parent;
+ target.postMessage(cookies, "*");});
+}
+</script>
+</head>
+<body onload="reportSubresourceCookies()">
diff --git a/testing/web-platform/tests/cookies/samesite/resources/iframe.document.html b/testing/web-platform/tests/cookies/samesite/resources/iframe.document.html
new file mode 100644
index 0000000000..7026beb7e8
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/resources/iframe.document.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<script>
+ var value = "" + Math.random();
+ document.cookie = `dc_samesite_strict=${value}; secure; sameSite=strict; path=/`;
+ document.cookie = `dc_samesite_lax=${value}; secure; sameSite=lax; path=/`;
+ document.cookie = `dc_samesite_none=${value}; secure; sameSite=none; path=/`;
+ parent.postMessage({value}, "*");
+</script>
diff --git a/testing/web-platform/tests/cookies/samesite/resources/navigate-iframe.html b/testing/web-platform/tests/cookies/samesite/resources/navigate-iframe.html
new file mode 100644
index 0000000000..98ad6264fa
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/resources/navigate-iframe.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ window.addEventListener('load', function() {
+ window.opener.postMessage({ type: 'LOADED' }, '*');
+ });
+
+ window.addEventListener('message', function(e) {
+ if (SECURE_ORIGIN !== window.location.origin)
+ return;
+
+ if (e.data.type === "initialize-iframe")
+ window.frames[0].location = e.data.url;
+ if (e.data.type === "navigate-iframe")
+ window.frames[0].postMessage({ type: 'navigate', url: e.data.url }, '*');
+
+ // Relay messages sent by the subframe to the opener.
+ if (e.data.type === 'FRAME_READY')
+ window.opener.postMessage({ type: 'FRAME_READY' }, '*');
+
+ if (e.data.type === 'FRAME_COOKIES_SET')
+ window.opener.postMessage({ type: 'FRAME_COOKIES_SET', cookies: e.data.cookies }, '*');
+ });
+</script>
+<iframe></iframe>
diff --git a/testing/web-platform/tests/cookies/samesite/resources/navigate.html b/testing/web-platform/tests/cookies/samesite/resources/navigate.html
new file mode 100644
index 0000000000..88de6dff92
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/resources/navigate.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ window.addEventListener('load', function() {
+ if (window.opener)
+ window.opener.postMessage({ type: 'READY' }, '*');
+ if (window.parent !== window)
+ window.parent.postMessage({ type: 'FRAME_READY' }, '*');
+ });
+
+ window.addEventListener('message', function(e) {
+ if (e.data.type === "navigate") {
+ window.location = e.data.url;
+ }
+
+ if (e.data.type === "post-form") {
+ var f = document.createElement('form');
+ f.action = e.data.url;
+ f.method = "POST";
+ document.body.appendChild(f);
+ f.submit();
+ }
+ });
+</script>
diff --git a/testing/web-platform/tests/cookies/samesite/resources/puppet.html b/testing/web-platform/tests/cookies/samesite/resources/puppet.html
new file mode 100644
index 0000000000..6d36132d3a
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/resources/puppet.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ // Helper to either set or clear some cookies on its own origin, or
+ // (potentially) cross-site on SECURE_ORIGIN.
+ window.onmessage = e => {
+ var originToUse = SECURE_ORIGIN;
+ if (e.data.useOwnOrigin)
+ originToUse = self.origin;
+
+ if (e.data.type === "set") {
+ credFetch(originToUse + "/cookies/resources/setSameSite.py?" + e.data.value)
+ .then(_ => {
+ e.source.postMessage({
+ type: "set-complete",
+ value: e.data.value
+ }, "*");
+ });
+ }
+
+ if (e.data.type === "drop") {
+ credFetch(originToUse + "/cookies/resources/dropSameSite.py")
+ .then(_ => {
+ e.source.postMessage({type: "drop-complete"}, "*");
+ });
+ }
+ };
+
+ window.opener.postMessage({
+ type: "READY"
+ }, "*");
+</script>
diff --git a/testing/web-platform/tests/cookies/samesite/sandbox-iframe-nested.https.html b/testing/web-platform/tests/cookies/samesite/sandbox-iframe-nested.https.html
new file mode 100644
index 0000000000..310f86a446
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/sandbox-iframe-nested.https.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/cookies/resources/cookie-helper.sub.js"></script>
+</head>
+<body onload="doTests()">
+ <iframe id="if" sandbox="allow-scripts">
+ </iframe>
+ <script>
+ function doTests() {
+ promise_test(async function(t) {
+ var value = "" + Math.random();
+ await resetSameSiteCookies(SECURE_ORIGIN, value);
+ var child = document.getElementById("if");
+ child.src = SECURE_ORIGIN + "/cookies/samesite/resources/iframe-navigate-report.html";
+
+ // the iframe nested inside if should post COOKIES to here.
+ var e = await wait_for_message("COOKIES");
+ // Not testing unspecified here as to not depend on the presence or
+ // absence of upcoming change of behavior.
+ assert_cookie(SECURE_ORIGIN, e.data, "samesite_lax", value, false);
+ assert_cookie(SECURE_ORIGIN, e.data, "samesite_strict", value, false);
+ assert_cookie(SECURE_ORIGIN, e.data, "samesite_none", value, true);
+ }, "SameSite cookies with intervening sandboxed iframe and navigation");
+ }
+ </script>
+</body>
diff --git a/testing/web-platform/tests/cookies/samesite/sandbox-iframe-subresource.https.html b/testing/web-platform/tests/cookies/samesite/sandbox-iframe-subresource.https.html
new file mode 100644
index 0000000000..417089ef57
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/sandbox-iframe-subresource.https.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/cookies/resources/cookie-helper.sub.js"></script>
+</head>
+<body onload="doTests()">
+ <iframe id="if" sandbox="allow-scripts">
+ </iframe>
+ <script>
+ function doTests() {
+ promise_test(async function(t) {
+ var value = "" + Math.random();
+ await resetSameSiteCookies(SECURE_ORIGIN, value);
+ var child = document.getElementById("if");
+ child.src = SECURE_ORIGIN + "/cookies/samesite/resources/iframe-subresource-report.html";
+
+ // the iframe nested inside if should post COOKIES to here.
+ var e = await wait_for_message("COOKIES");
+ // Not testing unspecified here as to not depend on the presence or
+ // absence of upcoming change of behavior.
+ assert_cookie(SECURE_ORIGIN, e.data, "samesite_lax", value, false);
+ assert_cookie(SECURE_ORIGIN, e.data, "samesite_strict", value, false);
+ assert_cookie(SECURE_ORIGIN, e.data, "samesite_none", value, true);
+ }, "SameSite cookies with intervening sandboxed iframe and subresources");
+ }
+ </script>
+</body>
diff --git a/testing/web-platform/tests/cookies/samesite/setcookie-lax.https.html b/testing/web-platform/tests/cookies/samesite/setcookie-lax.https.html
new file mode 100644
index 0000000000..f2094af693
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/setcookie-lax.https.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ promise_test(async function(t) {
+ let w = window.open(SECURE_ORIGIN + "/cookies/samesite/resources/puppet.html");
+ await wait_for_message("READY", SECURE_ORIGIN);
+ let random = "" + Math.random();
+ w.postMessage({type: "set", value: random}, "*");
+ let e = await wait_for_message("set-complete", SECURE_ORIGIN)
+ assert_dom_cookie("samesite_strict", e.data.value, true);
+ assert_dom_cookie("samesite_lax", e.data.value, true);
+ assert_dom_cookie("samesite_none", e.data.value, true);
+ assert_dom_cookie("samesite_unspecified", e.data.value, true);
+ w.close();
+ }, "Same-site window should be able to set `SameSite=Lax` or `SameSite=Strict` cookies.");
+
+ promise_test(async function(t) {
+ let w = window.open(SECURE_CROSS_SITE_ORIGIN + "/cookies/samesite/resources/puppet.html");
+ await wait_for_message("READY", SECURE_CROSS_SITE_ORIGIN);
+ let random = "" + Math.random();
+ w.postMessage({type: "set", value: random}, "*");
+ let e = await wait_for_message("set-complete", SECURE_CROSS_SITE_ORIGIN);
+ assert_dom_cookie("samesite_strict", e.data.value, false);
+ assert_dom_cookie("samesite_lax", e.data.value, false);
+ assert_dom_cookie("samesite_none", e.data.value, true);
+ assert_dom_cookie("samesite_unspecified", e.data.value, false);
+ w.close();
+ }, "Cross-site window shouldn't be able to set `SameSite=Lax` or `SameSite=Strict` cookies.");
+</script>
diff --git a/testing/web-platform/tests/cookies/samesite/setcookie-navigation.https.html b/testing/web-platform/tests/cookies/samesite/setcookie-navigation.https.html
new file mode 100644
index 0000000000..2dbc5526bc
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/setcookie-navigation.https.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ // Asserts that cookies are present or not present (according to `expectation`)
+ // in the cookie string `cookies` with the correct names and value.
+ function assert_cookies_present(cookies, value, expected_cookie_names, expectation) {
+ for (name of expected_cookie_names) {
+ let re = new RegExp("(?:^|; )" + name + "=" + value + "(?:$|;)");
+ let assertion = expectation ? assert_true : assert_false;
+ assertion(re.test(cookies), "`" + name + "=" + value + "` in cookies");
+ }
+ }
+
+ // Navigate from ORIGIN to |origin_to|, expecting the navigation to set SameSite
+ // cookies on |origin_to|.
+ function navigate_test(method, origin_to, title) {
+ promise_test(async function(t) {
+ // The cookies don't need to be cleared on each run because |value| is
+ // a new random value on each run, so on each run we are overwriting and
+ // checking for a cookie with a different random value.
+ let value = "" + Math.random();
+ let url_from = SECURE_ORIGIN + "/cookies/samesite/resources/navigate.html";
+ let url_to = origin_to + "/cookies/resources/setSameSite.py?" + value;
+ var w = window.open(url_from);
+ await wait_for_message('READY', SECURE_ORIGIN);
+ assert_equals(SECURE_ORIGIN, window.origin);
+ assert_equals(SECURE_ORIGIN, w.origin);
+ let command = (method === "POST") ? "post-form" : "navigate";
+ w.postMessage({ type: command, url: url_to }, "*");
+ let message = await wait_for_message('COOKIES_SET', origin_to);
+ let samesite_cookie_names = ['samesite_strict', 'samesite_lax', 'samesite_none', 'samesite_unspecified'];
+ assert_cookies_present(message.data.cookies, value, samesite_cookie_names, true);
+ w.close();
+ }, title);
+ }
+
+ // Opens a page on origin SECURE_ORIGIN containing an iframe on `iframe_origin_from`,
+ // then navigates that iframe to `iframe_origin_to`. Expects that navigation to set
+ // some subset of SameSite cookies.
+ function navigate_iframe_test(iframe_origin_from, iframe_origin_to, cross_site, title) {
+ promise_test(async function(t) {
+ // The cookies don't need to be cleared on each run because |value| is
+ // a new random value on each run, so on each run we are overwriting and
+ // checking for a cookie with a different random value.
+ let value = "" + Math.random();
+ let parent_url = SECURE_ORIGIN + "/cookies/samesite/resources/navigate-iframe.html";
+ let iframe_url_from = iframe_origin_from + "/cookies/samesite/resources/navigate.html";
+ let iframe_url_to = iframe_origin_to + "/cookies/resources/setSameSite.py?" + value;
+ var w = window.open(parent_url);
+ await wait_for_message('LOADED', SECURE_ORIGIN);
+ assert_equals(SECURE_ORIGIN, window.origin);
+ assert_equals(SECURE_ORIGIN, w.origin);
+ // Navigate the frame to its starting location.
+ w.postMessage({ type: 'initialize-iframe', url: iframe_url_from }, '*');
+ await wait_for_message('FRAME_READY', SECURE_ORIGIN);
+ // Have the frame navigate itself, possibly cross-site.
+ w.postMessage({ type: 'navigate-iframe', url: iframe_url_to }, '*');
+ let message = await wait_for_message('FRAME_COOKIES_SET', SECURE_ORIGIN);
+ // Check for the proper cookies.
+ let samesite_none_cookies = ['samesite_none'];
+ let samesite_cookies = ['samesite_strict', 'samesite_lax', 'samesite_unspecified'];
+ assert_cookies_present(message.data.cookies, value, samesite_none_cookies, true);
+ assert_cookies_present(message.data.cookies, value, samesite_cookies, !cross_site);
+ w.close();
+ }, title);
+ }
+
+ navigate_test("GET", SECURE_ORIGIN, "Same-site top-level navigation should be able to set SameSite=* cookies.");
+ navigate_test("GET", SECURE_CROSS_SITE_ORIGIN, "Cross-site top-level navigation should be able to set SameSite=* cookies.");
+ navigate_test("POST", SECURE_ORIGIN, "Same-site top-level POST should be able to set SameSite=* cookies.");
+ navigate_test("POST", SECURE_CROSS_SITE_ORIGIN, "Cross-site top-level POST should be able to set SameSite=* cookies.");
+
+ navigate_iframe_test(SECURE_ORIGIN, SECURE_ORIGIN, false, "Same-site to same-site iframe navigation should be able to set SameSite=* cookies.");
+ navigate_iframe_test(SECURE_CROSS_SITE_ORIGIN, SECURE_ORIGIN, true, "Cross-site to same-site iframe navigation should only be able to set SameSite=None cookies.");
+ navigate_iframe_test(SECURE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, true, "Same-site to cross-site-site iframe navigation should only be able to set SameSite=None cookies.");
+ navigate_iframe_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, true, "Cross-site to cross-site iframe navigation should only be able to set SameSite=None cookies.");
+</script>
diff --git a/testing/web-platform/tests/cookies/samesite/window-open-reload.https.html b/testing/web-platform/tests/cookies/samesite/window-open-reload.https.html
new file mode 100644
index 0000000000..32076c7c97
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/window-open-reload.https.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ // This test opens a popup window to postToParent.py (on the specified
+ // origin). The popup sends a postMessage event back to its opener
+ // (i.e., here) with the cookies it received, which we verify against
+ // expectedStatus. Then, the test sends a message to the popup, telling it to
+ // reload itself via window.location.reload(). Again, the popup posts a
+ // message back here with the cookies it received. These cookies are verified
+ // against expectedStatusReload.
+ function create_test(origin, target, expectedStatus, expectedStatusReload, title) {
+ promise_test(t => {
+ var value = "" + Math.random();
+ return resetSameSiteCookies(origin, value)
+ .then(_ => {
+ return new Promise((resolve, reject) => {
+ var w = window.open(target + "/cookies/resources/postToParent.py");
+
+ var reloaded = false;
+ var msgHandler = e => {
+ try {
+ verifySameSiteCookieState(reloaded ? expectedStatusReload : expectedStatus, value, e.data, DomSameSiteStatus.SAME_SITE);
+ } catch (e) {
+ reject(e);
+ }
+
+ if (reloaded) {
+ window.removeEventListener("message", msgHandler);
+ w.close();
+ resolve("Popup received the cookie.");
+ } else {
+ reloaded = true;
+ w.postMessage("reload", "*");
+ }
+ };
+ window.addEventListener("message", msgHandler);
+
+ if (!w)
+ reject("Popup could not be opened (did you allow the test site in your popup blocker?).");
+ });
+ });
+ }, title);
+ }
+
+ // The reload status is always strictly same-site because this is a site-initiated reload, as opposed to a reload triggered by a user interface element.
+ create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, SameSiteStatus.STRICT, "Reloaded same-host auxiliary navigations are strictly same-site.");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, SameSiteStatus.STRICT, "Reloaded subdomain auxiliary navigations are strictly same-site.");
+ create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.LAX, SameSiteStatus.STRICT, "Reloaded cross-site auxiliary navigations are strictly same-site");
+</script>
diff --git a/testing/web-platform/tests/cookies/samesite/window-open.https.html b/testing/web-platform/tests/cookies/samesite/window-open.https.html
new file mode 100644
index 0000000000..be4225046a
--- /dev/null
+++ b/testing/web-platform/tests/cookies/samesite/window-open.https.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ function create_test(origin, target, expectedStatus, title) {
+ promise_test(t => {
+ var value = "" + Math.random();
+ return resetSameSiteCookies(origin, value)
+ .then(_ => {
+ return new Promise((resolve, reject) => {
+ var w = window.open(target + "/cookies/resources/postToParent.py");
+
+ var msgHandler = e => {
+ window.removeEventListener("message", msgHandler);
+ w.close();
+ try {
+ verifySameSiteCookieState(expectedStatus, value, e.data, DomSameSiteStatus.SAME_SITE);
+ resolve("Popup received the cookie.");
+ } catch (e) {
+ reject(e);
+ }
+ };
+ window.addEventListener("message", msgHandler);
+
+ if (!w)
+ reject("Popup could not be opened (did you allow the test site in your popup blocker?).");
+ });
+ });
+ }, title);
+ }
+
+ // No redirect:
+ create_test(SECURE_ORIGIN, SECURE_ORIGIN, SameSiteStatus.STRICT, "Same-host auxiliary navigations are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN, SameSiteStatus.STRICT, "Subdomain auxiliary navigations are strictly same-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN, SameSiteStatus.LAX, "Cross-site auxiliary navigations are laxly same-site");
+
+ // Redirect from {same-host,subdomain,cross-site} to same-host:
+ create_test(SECURE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to same-host auxiliary navigations are strictly same-site");
+ create_test(SECURE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to same-host auxiliary navigations are strictly same-site");
+ create_test(SECURE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_ORIGIN), SameSiteStatus.LAX, "Cross-site redirecting to same-host auxiliary navigations are laxly same-site");
+
+ // Redirect from {same-host,subdomain,cross-site} to same-host:
+ create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Same-host redirecting to subdomain auxiliary navigations are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.STRICT, "Subdomain redirecting to subdomain auxiliary navigations are strictly same-site");
+ create_test(SECURE_SUBDOMAIN_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_SUBDOMAIN_ORIGIN), SameSiteStatus.LAX, "Cross-site redirecting to subdomain auxiliary navigations are laxly same-site");
+
+ // Redirect from {same-host,subdomain,cross-site} to cross-site:
+ create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.LAX, "Same-host redirecting to cross-site auxiliary navigations are laxly same-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_SUBDOMAIN_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.LAX, "Subdomain redirecting to cross-site auxiliary navigations are laxly same-site");
+ create_test(SECURE_CROSS_SITE_ORIGIN, redirectTo(SECURE_CROSS_SITE_ORIGIN, SECURE_CROSS_SITE_ORIGIN), SameSiteStatus.LAX, "Cross-site redirecting to cross-site auxiliary navigations are laxly same-site");
+</script>
diff --git a/testing/web-platform/tests/cookies/schemeful-same-site/resources/navigateToInsecurePostToParent.html b/testing/web-platform/tests/cookies/schemeful-same-site/resources/navigateToInsecurePostToParent.html
new file mode 100644
index 0000000000..b81b722bf6
--- /dev/null
+++ b/testing/web-platform/tests/cookies/schemeful-same-site/resources/navigateToInsecurePostToParent.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ window.location = INSECURE_ORIGIN + "/cookies/resources/postToParent.py";
+</script>
diff --git a/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-iframe-subresource.tentative.html b/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-iframe-subresource.tentative.html
new file mode 100644
index 0000000000..13397d241a
--- /dev/null
+++ b/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-iframe-subresource.tentative.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/cookies/resources/cookie-helper.sub.js"></script>
+</head>
+<body onload="doTests()">
+ <iframe id="if">
+ </iframe>
+ <script>
+ function doTests() {
+ promise_test(async function(t) {
+ var value = "" + Math.random();
+ await resetSameSiteCookies(SECURE_ORIGIN, value);
+ var child = document.getElementById("if");
+ child.src = SECURE_ORIGIN + "/cookies/samesite/resources/iframe-subresource-report.html";
+
+ // the iframe nested inside if should post COOKIES to here.
+ var e = await wait_for_message("COOKIES");
+ // Cross-scheme iframes should be cross-site and thus the subresources
+ // shouldn't get Lax or Strict cookies.
+ assert_cookie(SECURE_ORIGIN, e.data, "samesite_lax", value, false);
+ assert_cookie(SECURE_ORIGIN, e.data, "samesite_strict", value, false);
+ assert_cookie(SECURE_ORIGIN, e.data, "samesite_none", value, true);
+ }, "SameSite cookies with intervening cross-scheme iframe and subresources");
+ }
+ </script>
+</body>
diff --git a/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-navigation.tentative.html b/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-navigation.tentative.html
new file mode 100644
index 0000000000..c1a86690dc
--- /dev/null
+++ b/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-navigation.tentative.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script>
+ function schemeful_navigation_test(target, expectedSameSiteStatus, title) {
+ promise_test(async function(t) {
+ let value = "" + Math.random();
+ document.cookie = `samesite_strict=${value}; sameSite=strict; path=/`;
+ document.cookie = `samesite_lax=${value}; sameSite=lax; path=/`;
+
+ let url = target + "/cookies/schemeful-same-site/resources/navigateToInsecurePostToParent.html";
+
+ await new Promise((resolve, reject) => {
+ window.onmessage = t.step_func(e => {
+ if (e.source == window.open("", "testwindow" + value)) {
+ e.source.close();
+ const cookies = e.data;
+
+ assert_equals(cookies["samesite_lax"], value, "SameSite=lax cookies can be sent in both cases");
+ if (expectedSameSiteStatus === SameSiteStatus.STRICT) {
+ assert_equals(cookies["samesite_strict"], value, "SameSite=strict cookies can be sent to same-scheme navigations");
+ } else if (expectedSameSiteStatus === SameSiteStatus.LAX) {
+ assert_not_equals(cookies["samesite_strict"], value, "SameSite=strict cookies cannot be sent to cross-scheme navigations");
+ }
+
+ resolve();
+ }
+ else {reject();}
+ });
+
+ var w = window.open(url, "testwindow" + value);
+ });
+
+ },title);}
+
+ schemeful_navigation_test(INSECURE_ORIGIN, SameSiteStatus.STRICT, "Navigate same-scheme");
+ schemeful_navigation_test(SECURE_ORIGIN, SameSiteStatus.LAX, "Navigate cross-scheme");
+</script>
diff --git a/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-subresource.tentative.html b/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-subresource.tentative.html
new file mode 100644
index 0000000000..4ba9286c25
--- /dev/null
+++ b/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-subresource.tentative.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<!-- We're appending an <iframe> to the document's body, so execute tests after we have a body -->
+<body>
+<script>
+ function create_test(target, expectedDomStatus, title) {
+ promise_test(async t => {
+ var cookieValue = "" + Math.random();
+ document.cookie = `dc_samesite_strict=${cookieValue}; sameSite=strict; path=/`;
+ document.cookie = `dc_samesite_lax=${cookieValue}; sameSite=lax; path=/`;
+ // SameSite=None requires `Secure` which complicates the test and we don't
+ // need it, so don't add it.
+
+ await new Promise((resolve, reject) => {
+ var iframe = document.createElement("iframe");
+
+ window.onmessage = t.step_func(e => {
+ if (e.source == iframe.contentWindow) {
+ // Cleanup, then verify cookie state:
+ document.body.removeChild(iframe);
+
+ const cookies = e.data;
+
+ if (expectedDomStatus === DomSameSiteStatus.SAME_SITE) {
+ assert_equals(cookies["dc_samesite_lax"], cookieValue, "SameSite=lax cookies can be sent to same-scheme subresources");
+ assert_equals(cookies["dc_samesite_strict"], cookieValue, "SameSite=strict cookies can be sent to same-scheme subresources");
+ } else if (expectedDomStatus === DomSameSiteStatus.CROSS_SITE) {
+ assert_not_equals(cookies["dc_samesite_lax"], cookieValue, "SameSite=lax cookies cannot be sent to cross-scheme subresources");
+ assert_not_equals(cookies["dc_samesite_strict"], cookieValue, "SameSite=strict cookies cannot be sent to cross-scheme subresources");
+ }
+
+ resolve();
+ }
+ });
+
+ iframe.src = target + "/cookies/resources/postToParent.py";
+ document.body.appendChild(iframe);
+ });
+ }, title);
+ }
+
+ // Test that cross-scheme subresources (iframes in this case) are cross-site.
+ create_test(INSECURE_ORIGIN, DomSameSiteStatus.SAME_SITE, "Same-scheme subresources can send lax/strict cookies");
+ create_test(SECURE_ORIGIN, DomSameSiteStatus.CROSS_SITE, "Cross-scheme subresources cannot sent lax/strict cookies");
+</script>
diff --git a/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-websockets.sub.tentative.html b/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-websockets.sub.tentative.html
new file mode 100644
index 0000000000..7095eee21e
--- /dev/null
+++ b/testing/web-platform/tests/cookies/schemeful-same-site/schemeful-websockets.sub.tentative.html
@@ -0,0 +1,57 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/cookies/resources/testharness-helpers.js"></script>
+ <script src="/cookies/resources/cookie-helper.sub.js"></script>
+</head>
+<body>
+<div id=log></div>
+<script>
+ promise_test(async function (t) {
+ var value = "" + Math.random();
+ document.cookie = `schemeful_same_site_websockets_strict=${value}; sameSite=strict; path=/`;
+ document.cookie = `schemeful_same_site_websockets_lax=${value}; sameSite=lax; path=/`;
+ await credFetch(SECURE_ORIGIN + "/cookies/resources/setSameSiteNone.py?" + value)
+ t.add_cleanup(async function() {
+ await credFetch(origin + "/cookies/resources/drop.py?name=" + "schemeful_same_site_websockets_strict");
+ await credFetch(origin + "/cookies/resources/drop.py?name=" + "schemeful_same_site_websockets_lax");
+ await credFetch(SECURE_ORIGIN + "/cookies/resources/dropSameSiteNone.py");
+ });
+
+ var ws = new WebSocket("ws://{{host}}:{{ports[ws][0]}}/echo-cookie");
+ return new Promise((resolve, reject) => {
+ ws.onclose = t.step_func_done(function () {
+ assert_unreached("'close' should not fire before 'open'.");
+ });
+ ws.onmessage = t.step_func(function (e) {
+ ws.onclose = null;
+ ws.close();
+ // Same-scheme WebSockets should get Lax and Strict cookies.
+ var strictRegex = new RegExp("schemeful_same_site_websockets_strict=" + value);
+ var laxRegex = new RegExp("schemeful_same_site_websockets_lax=" + value);
+ assert_regexp_match(e.data, strictRegex, "Same-scheme strict");
+ assert_regexp_match(e.data, laxRegex, "Same-scheme strict");
+
+ var ws2 = new WebSocket("wss://{{host}}:{{ports[wss][0]}}/echo-cookie");
+ ws2.onclose = t.step_func_done(function () {
+ assert_unreached("'close' should not fire before 'open'.");
+ });
+ ws2.onmessage = t.step_func(function (e2) {
+ ws2.onclose = null;
+ ws2.close();
+ // Cross-scheme WebSockets should only get samesite_none.
+ var noneRegex = new RegExp("samesite_none_secure=" + value);
+ assert_regexp_match(e2.data, noneRegex, "Cross-scheme none");
+ assert_false(strictRegex.test(e2.data), "Cross-scheme strict");
+ assert_false(laxRegex.test(e2.data), "Cross-scheme lax");
+ resolve();
+ });
+ });
+ });
+ }, "Cross-scheme WebSockets are cross-site");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/cookies/secure/set-from-dom.https.sub.html b/testing/web-platform/tests/cookies/secure/set-from-dom.https.sub.html
new file mode 100644
index 0000000000..46997db18a
--- /dev/null
+++ b/testing/web-platform/tests/cookies/secure/set-from-dom.https.sub.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Set 'secure' cookie from `document.cookie` on a secure page</title>
+ <meta name=help href="https://tools.ietf.org/html/draft-west-leave-secure-cookies-alone">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/cookies/resources/testharness-helpers.js"></script>
+</head>
+<body>
+<div id=log></div>
+<script>
+ var tests = [
+ [
+ "'secure' cookie visible in `document.cookie`",
+ function () {
+ document.cookie = "secure_from_secure_dom=1; secure; path=/";
+ assert_not_equals(document.cookie.match(/secure_from_secure_dom=1/), null);
+ this.done();
+ }
+ ],
+ [
+ "'secure' cookie visible in HTTP request",
+ function () {
+ document.cookie = "secure_from_secure_dom=1; secure; path=/";
+ assert_not_equals(document.cookie.match(/secure_from_secure_dom=1/), null);
+ fetch("https://{{host}}:{{ports[https][0]}}/cookies/resources/echo-json.py",
+ { "credentials": "include" })
+ .then(this.step_func(function (r) {
+ return r.json();
+ }))
+ .then(this.step_func_done(function (j) {
+ assert_equals(j["secure_from_secure_dom"], "secure_from_secure_dom=1");
+ }));
+ }
+ ]
+ ];
+
+ function clearKnownCookie() {
+ document.cookie = "secure_from_secure_dom=0; Secure; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/";
+ }
+
+ executeTestsSerially(tests, clearKnownCookie, clearKnownCookie);
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/cookies/secure/set-from-dom.sub.html b/testing/web-platform/tests/cookies/secure/set-from-dom.sub.html
new file mode 100644
index 0000000000..91aa8fff3b
--- /dev/null
+++ b/testing/web-platform/tests/cookies/secure/set-from-dom.sub.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Set 'secure' cookie from `document.cookie` on a non-secure page</title>
+ <meta name=help href="https://tools.ietf.org/html/draft-west-leave-secure-cookies-alone">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/cookies/resources/testharness-helpers.js"></script>
+</head>
+<body>
+<div id=log></div>
+<script>
+ var tests = [
+ [
+ "'secure' cookie not set in `document.cookie`",
+ function () {
+ var originalCookie = document.cookie;
+ document.cookie = "secure_from_nonsecure_dom=1; secure; path=/";
+ assert_equals(document.cookie, originalCookie);
+ this.done();
+ }
+ ],
+ [
+ "'secure' cookie not sent in HTTP request",
+ function () {
+ document.cookie = "secure_from_nonsecure_dom=1; secure; path=/";
+ fetch("https://{{host}}:{{ports[https][0]}}/cookies/resources/echo-json.py", { "credentials": "include" })
+ .then(this.step_func(function (r) {
+ return r.json();
+ }))
+ .then(this.step_func_done(function (j) {
+ assert_equals(j["secure_from_nonsecure_dom"], undefined);
+ }));
+ }
+ ]
+ ];
+
+ function clearKnownCookie() {
+ document.cookie = "secure_from_nonsecure_dom=0; Secure; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/";
+ }
+
+ executeTestsSerially(tests, clearKnownCookie, clearKnownCookie);
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/cookies/secure/set-from-http.https.sub.html b/testing/web-platform/tests/cookies/secure/set-from-http.https.sub.html
new file mode 100644
index 0000000000..6024c5b7f6
--- /dev/null
+++ b/testing/web-platform/tests/cookies/secure/set-from-http.https.sub.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Set 'secure' cookie from `Set-Cookie` HTTP header on a secure page</title>
+ <meta name=help href="https://tools.ietf.org/html/draft-west-leave-secure-cookies-alone">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/cookies/resources/testharness-helpers.js"></script>
+</head>
+<body>
+<div id=log></div>
+<script>
+ function clearKnownCookie() {
+ document.cookie = "secure_from_secure_http=0; Secure; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/";
+ }
+
+ test(function () {
+ assert_not_equals(document.cookie.match(/secure_from_secure_http=1/), null);
+ }, "'secure' cookie present in `document.cookie`");
+
+ promise_test(function (t) {
+ t.add_cleanup(clearKnownCookie);
+ return fetch("https://{{host}}:{{ports[https][0]}}/cookies/resources/echo-json.py",
+ { "credentials": "include" })
+ .then(function (r) {
+ return r.json();
+ })
+ .then(function (j) {
+ assert_equals(j["secure_from_secure_http"], "secure_from_secure_http=1");
+ });
+ }, "'secure' cookie sent in HTTP request");
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/cookies/secure/set-from-http.https.sub.html.headers b/testing/web-platform/tests/cookies/secure/set-from-http.https.sub.html.headers
new file mode 100644
index 0000000000..f4c9147fac
--- /dev/null
+++ b/testing/web-platform/tests/cookies/secure/set-from-http.https.sub.html.headers
@@ -0,0 +1,5 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: secure_from_secure_http=1; Secure; Path=/
diff --git a/testing/web-platform/tests/cookies/secure/set-from-http.sub.html b/testing/web-platform/tests/cookies/secure/set-from-http.sub.html
new file mode 100644
index 0000000000..c80cc34101
--- /dev/null
+++ b/testing/web-platform/tests/cookies/secure/set-from-http.sub.html
@@ -0,0 +1,36 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Set 'secure' cookie from `Set-Cookie` HTTP header on a non-secure page</title>
+ <meta name=help href="https://tools.ietf.org/html/draft-west-leave-secure-cookies-alone">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/cookies/resources/testharness-helpers.js"></script>
+</head>
+<body>
+<div id=log></div>
+<script>
+ function clearKnownCookie() {
+ document.cookie = "secure_from_nonsecure_http=0; Secure; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/";
+ }
+
+ test(function () {
+ assert_equals(document.cookie.match(/secure_from_nonsecure_http=1/), null);
+ }, "'secure' cookie not present in `document.cookie`");
+
+ promise_test(function (t) {
+ t.add_cleanup(clearKnownCookie);
+ return fetch("https://{{host}}:{{ports[https][0]}}/cookies/resources/echo-json.py",
+ { "credentials": "include" })
+ .then(function (r) {
+ return r.json();
+ })
+ .then(function (j) {
+ assert_equals(j["secure_from_nonsecure_http"], undefined);
+ });
+ }, "'secure' cookie not sent in HTTP request");
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/cookies/secure/set-from-http.sub.html.headers b/testing/web-platform/tests/cookies/secure/set-from-http.sub.html.headers
new file mode 100644
index 0000000000..57a45167f0
--- /dev/null
+++ b/testing/web-platform/tests/cookies/secure/set-from-http.sub.html.headers
@@ -0,0 +1,5 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: secure_from_nonsecure_http=1; Secure; Path=/
diff --git a/testing/web-platform/tests/cookies/secure/set-from-ws.sub.html b/testing/web-platform/tests/cookies/secure/set-from-ws.sub.html
new file mode 100644
index 0000000000..b12504450e
--- /dev/null
+++ b/testing/web-platform/tests/cookies/secure/set-from-ws.sub.html
@@ -0,0 +1,45 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Set 'secure' cookie from `Set-Cookie` HTTP header on a non-secure WebSocket</title>
+ <meta name=help href="https://tools.ietf.org/html/draft-west-leave-secure-cookies-alone">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/cookies/resources/testharness-helpers.js"></script>
+</head>
+<body>
+<div id=log></div>
+<script>
+ function clearKnownCookie() {
+ document.cookie = "ws_test_secure_from_nonsecure=0; Secure; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/";
+ }
+
+ async_test(function (t) {
+ t.add_cleanup(clearKnownCookie);
+ assert_equals(document.cookie.match(/ws_test_secure_from_nonsecure=/), null);
+
+ clearKnownCookie();
+ var ws = new WebSocket("ws://{{host}}:{{ports[ws][0]}}/set-cookie-secure?secure_from_nonsecure");
+ ws.onclose = t.step_func_done(function () {
+ assert_unreached("'close' should not fire before 'open'.");
+ });
+ ws.onopen = t.step_func(function (e) {
+ ws.onclose = null;
+ ws.close();
+ assert_false(/ws_test_secure_from_nonsecure=test/.test(document.cookie));
+
+ var ws2 = new WebSocket("wss://{{host}}:{{ports[wss][0]}}/echo-cookie");
+ ws2.onclose = t.step_func_done(function () {
+ assert_unreached("'close' should not fire before 'open'.");
+ });
+ ws2.onmessage = t.step_func_done(function (e) {
+ ws2.onclose = null;
+ ws2.close();
+ assert_false(/ws_test_secure_from_nonsecure=test/.test(e.data));
+ });
+ });
+ }, "'secure' cookie not sent in WSS request when set from WS");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/cookies/secure/set-from-wss.https.sub.html b/testing/web-platform/tests/cookies/secure/set-from-wss.https.sub.html
new file mode 100644
index 0000000000..c5e8b385d0
--- /dev/null
+++ b/testing/web-platform/tests/cookies/secure/set-from-wss.https.sub.html
@@ -0,0 +1,44 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset=utf-8>
+ <title>Set 'secure' cookie from `Set-Cookie` HTTP header on a secure WebSocket</title>
+ <meta name=help href="https://tools.ietf.org/html/draft-west-leave-secure-cookies-alone">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/cookies/resources/testharness-helpers.js"></script>
+</head>
+<body>
+<div id=log></div>
+<script>
+ function clearKnownCookie() {
+ document.cookie = "ws_test_secure_from_secure=0; Secure; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/";
+ }
+
+ async_test(function (t) {
+ t.add_cleanup(clearKnownCookie);
+ assert_equals(document.cookie.match(/ws_test_secure_from_secure=/), null);
+
+ clearKnownCookie();
+ var ws = new WebSocket("wss://{{host}}:{{ports[wss][0]}}/set-cookie-secure?secure_from_secure");
+ ws.onclose = t.step_func_done(function () {
+ assert_unreached("'close' should not fire before 'open'.");
+ });
+ ws.onopen = t.step_func(function (e) {
+ ws.onclose = null;
+ ws.close();
+ assert_regexp_match(document.cookie, /ws_test_secure_from_secure=test/);
+ var ws2 = new WebSocket("wss://{{host}}:{{ports[wss][0]}}/echo-cookie");
+ ws2.onclose = t.step_func_done(function () {
+ assert_unreached("'close' should not fire before 'open'.");
+ });
+ ws2.onmessage = t.step_func_done(function (e) {
+ ws2.onclose = null;
+ ws2.close();
+ assert_regexp_match(e.data, /ws_test_secure_from_secure=test/);
+ });
+ });
+ }, "'secure' cookie not sent in HTTP request");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/cookies/size/attributes.www.sub.html b/testing/web-platform/tests/cookies/size/attributes.www.sub.html
new file mode 100644
index 0000000000..2500daef96
--- /dev/null
+++ b/testing/web-platform/tests/cookies/size/attributes.www.sub.html
@@ -0,0 +1,121 @@
+<!doctype html>
+<html>
+
+<head>
+ <meta charset=utf-8>
+ <title>Test cookie attribute size restrictions</title>
+ <meta name=help href="https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+</head>
+
+<body>
+ <div id=log></div>
+ <script>
+ const host = "{{host}}";
+ const attrSizeTests = [
+ {
+ cookie: `test=1; path=/cookies/size; path=/cookies/siz${"e".repeat(1024)}`,
+ expected: "test=1",
+ name: "Too long path attribute (>1024 bytes) is ignored; previous valid path wins.",
+ defaultPath: false,
+ },
+ {
+ cookie: `test=2; path=/cookies/siz${"e".repeat(1024)}; path=/cookies/size`,
+ expected: "test=2",
+ name: "Too long path attribute (>1024 bytes) is ignored; next valid path wins.",
+ defaultPath: false,
+ },
+ {
+ // Look for the cookie using the default path to ensure that it
+ // doesn't show up if the path attribute actually takes effect.
+ cookie: `test=3; path=/${"a".repeat(1023)};`,
+ expected: "",
+ name: "Max size path attribute (1024 bytes) is not ignored",
+ },
+ {
+ // Look for the cookie using the default path to ensure that it
+ // shows up if the path is ignored.
+ cookie: `test=4; path=/${"a".repeat(1024)};`,
+ expected: "test=4",
+ name: "Too long path attribute (>1024 bytes) is ignored",
+ },
+ {
+ // This page opens on the www subdomain, so we set domain to {{host}}
+ // to see if anything works as expected. Using a valid domain other
+ // than ${host} will cause the cookie to fail to be set.
+
+ // NOTE: the domain we use for testing here is technically invalid per
+ // the RFCs that define the format of domain names, but currently
+ // neither RFC6265bis or the major browsers enforce those restrictions
+ // when parsing cookie domain attributes. If that changes, update these
+ // tests.
+ cookie: `test=5; domain=${host}; domain=${"a".repeat(1024)}.com`,
+ expected: "test=5",
+ name: "Too long domain attribute (>1024 bytes) is ignored; previous valid domain wins.",
+ },
+ {
+ cookie: `test=6; domain=${"a".repeat(1024)}.com; domain=${host}`,
+ expected: "test=6",
+ name: "Too long domain attribute (>1024 bytes) is ignored; next valid domain wins.",
+ },
+ {
+ cookie: `test=7; domain=${"a".repeat(1020)}.com;`,
+ expected: "",
+ name: "Max size domain attribute (1024 bytes) is not ignored"
+ },
+ {
+ cookie: `test=8; domain=${"a".repeat(1021)}.com;`,
+ expected: "test=8",
+ name: "Too long domain attribute (>1024 bytes) is ignored"
+ },
+ {
+ cookie: cookieStringWithNameAndValueLengths(2048, 2048) +
+ `; domain=${"a".repeat(1020)}.com; domain=${host}`,
+ expected: cookieStringWithNameAndValueLengths(2048, 2048),
+ name: "Set cookie with max size name/value pair and max size attribute value",
+ },
+ {
+ // RFC6265bis doesn't specify a maximum size of the entire Set-Cookie
+ // header, although some browsers do
+ cookie: cookieStringWithNameAndValueLengths(2048, 2048) +
+ `; domain=${"a".repeat(1020)}.com` +
+ `; domain=${"a".repeat(1020)}.com` +
+ `; domain=${"a".repeat(1020)}.com` +
+ `; domain=${"a".repeat(1020)}.com; domain=${host}`,
+ expected: cookieStringWithNameAndValueLengths(2048, 2048),
+ name: "Set cookie with max size name/value pair and multiple max size attributes (>8k bytes total)",
+ },
+ {
+ cookie: `test=11; max-age=${"1".repeat(1024)};`,
+ expected: "test=11",
+ name: "Max length Max-Age attribute value (1024 bytes) doesn't cause cookie rejection"
+ },
+ {
+ cookie: `test=12; max-age=${"1".repeat(1025)};`,
+ expected: "test=12",
+ name: "Too long Max-Age attribute value (>1024 bytes) doesn't cause cookie rejection"
+ },
+ {
+ cookie: `test=13; max-age=-${"1".repeat(1023)};`,
+ expected: "",
+ name: "Max length negative Max-Age attribute value (1024 bytes) doesn't get ignored"
+ },
+ {
+ cookie: `test=14; max-age=-${"1".repeat(1024)};`,
+ expected: "test=14",
+ name: "Too long negative Max-Age attribute value (>1024 bytes) gets ignored"
+ },
+ ];
+
+ for (const test of attrSizeTests) {
+ httpCookieTest(test.cookie, test.expected, test.name, test.defaultPath);
+ }
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/cookies/size/name-and-value.html b/testing/web-platform/tests/cookies/size/name-and-value.html
new file mode 100644
index 0000000000..b387bd2d54
--- /dev/null
+++ b/testing/web-platform/tests/cookies/size/name-and-value.html
@@ -0,0 +1,83 @@
+<!doctype html>
+<html>
+
+<head>
+ <meta charset=utf-8>
+ <title>Test cookie name size restrictions</title>
+ <meta name=help href="https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+</head>
+
+<body>
+ <div id=log></div>
+ <script>
+ const nameAndValueSizeTests = [
+ {
+ cookie: cookieStringWithNameAndValueLengths(2048, 2048),
+ expected: cookieStringWithNameAndValueLengths(2048, 2048),
+ name: "Set max-size cookie with largest possible name and value (4096 bytes)",
+ },
+ {
+ cookie: cookieStringWithNameAndValueLengths(4097, 1),
+ expected: "",
+ name: "Ignore cookie with name larger than 4096 and 1 byte value",
+ },
+ {
+ cookie: cookieStringWithNameAndValueLengths(4096, 0),
+ expected: cookieStringWithNameAndValueLengths(4096, 0),
+ name: "Set max-size value-less cookie",
+ },
+ {
+ cookie: cookieStringWithNameAndValueLengths(4097, 0),
+ expected: "",
+ name: "Ignore value-less cookie with name larger than 4096 bytes",
+ },
+ {
+ cookie: cookieStringWithNameAndValueLengths(1, 4095),
+ expected: cookieStringWithNameAndValueLengths(1, 4095),
+ name: "Set max-size cookie with largest possible value (4095 bytes)",
+ },
+ {
+ cookie: cookieStringWithNameAndValueLengths(1, 4096),
+ expected: "",
+ name: "Ignore named cookie (with non-zero length) and value larger than 4095 bytes",
+ },
+ {
+ cookie: cookieStringWithNameAndValueLengths(4096, 1),
+ expected: "",
+ name: "Ignore named cookie with length larger than 4095 bytes, and a non-zero value",
+ },
+ {
+ cookie: cookieStringWithNameAndValueLengths(0, 4096),
+ expected: cookieStringWithNameAndValueLengths(0, 4096).slice(1), // it won't come back with leading =
+ name: "Set max-size name-less cookie",
+ },
+ {
+ cookie: cookieStringWithNameAndValueLengths(0, 4097),
+ expected: "",
+ name: "Ignore name-less cookie with value larger than 4096 bytes",
+ },
+ {
+ cookie: cookieStringWithNameAndValueLengths(0, 4097).slice(1), // slice off leading =
+ expected: "",
+ name: "Ignore name-less cookie (without leading =) with value larger than 4096 bytes",
+ },
+ {
+ cookie: cookieStringWithNameAndValueLengths(2048, 2048) + '; Max-Age:43110;',
+ expected: cookieStringWithNameAndValueLengths(2048, 2048),
+ name: "Set max-size cookie that also has an attribute",
+ },
+ ];
+
+ for (const test of nameAndValueSizeTests) {
+ httpCookieTest(test.cookie, test.expected, test.name);
+ }
+ </script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/cookies/third-party-cookies/resources/test-helpers.js b/testing/web-platform/tests/cookies/third-party-cookies/resources/test-helpers.js
new file mode 100644
index 0000000000..2ae2c46a37
--- /dev/null
+++ b/testing/web-platform/tests/cookies/third-party-cookies/resources/test-helpers.js
@@ -0,0 +1,63 @@
+function testHttpCookies({desc, origin, cookieNames, expectsCookie}) {
+ promise_test(async () => {
+ await assertOriginCanAccessCookies({origin, cookieNames, expectsCookie});
+ }, getCookieTestName(expectsCookie, desc, "HTTP"));
+}
+
+async function assertOriginCanAccessCookies({
+ origin,
+ cookieNames,
+ expectsCookie,
+}) {
+ const resp = await credFetch(`${origin}/cookies/resources/list.py`);
+ const cookies = await resp.json();
+ for (const cookieName of cookieNames) {
+ assert_equals(
+ cookies.hasOwnProperty(cookieName), expectsCookie,
+ getCookieAssertDesc(expectsCookie, cookieName));
+ }
+}
+
+function testDomCookies({desc, cookieNames, expectsCookie}) {
+ test(() => {
+ assertDomCanAccessCookie(cookieNames, expectsCookie);
+ }, getCookieTestName(expectsCookie, desc, "DOM"));
+}
+
+function assertDomCanAccessCookie(cookieNames, expectsCookie) {
+ for (const cookieName of cookieNames) {
+ assert_equals(
+ document.cookie.includes(cookieName + "="), expectsCookie,
+ getCookieAssertDesc(expectsCookie, cookieName));
+ }
+}
+
+function testCookieStoreCookies({desc, cookieNames, expectsCookie}) {
+ if (!window.cookieStore) return;
+ promise_test(async () => {
+ await assertCookieStoreCanAccessCookies(cookieNames, expectsCookie);
+ }, getCookieTestName(expectsCookie, desc, "CookieStore"));
+}
+
+async function assertCookieStoreCanAccessCookies(cookieNames, expectsCookie) {
+ const cookies = await cookieStore.getAll({sameSite: 'none'});
+ for (const cookieName of cookieNames) {
+ assert_equals(
+ !!cookies.find(c => c.name === cookieName), expectsCookie,
+ getCookieAssertDesc(expectsCookie, cookieName));
+ }
+}
+
+function getCookieTestName(expectsCookie, desc, cookieType) {
+ if (expectsCookie) {
+ return `${desc}: Cookies are accessible via ${cookieType}`;
+ }
+ return `${desc}: Cookies are not accessible via ${cookieType}`;
+}
+
+function getCookieAssertDesc(expectsCookie, cookieName) {
+ if (expectsCookie) {
+ return `Expected cookie ${cookieName} to be available`;
+ }
+ return `Expected cookie ${cookieName} to not be available`;
+}
diff --git a/testing/web-platform/tests/cookies/third-party-cookies/resources/third-party-cookies-cross-site-embed.html b/testing/web-platform/tests/cookies/third-party-cookies/resources/third-party-cookies-cross-site-embed.html
new file mode 100644
index 0000000000..2d579c91be
--- /dev/null
+++ b/testing/web-platform/tests/cookies/third-party-cookies/resources/third-party-cookies-cross-site-embed.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<meta name="timeout" content="long">
+<title>Test site embedded in a cross-site context</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script src="/cookies/third-party-cookies/resources/test-helpers.js"></script>
+<body>
+<script>
+
+// Cookies set by the parent window in a 1P context.
+const cookieNames = ["1P_http", "1P_dom"];
+if (window.cookieStore) {
+ cookieNames.push("1P_cs");
+}
+
+testDomCookies({
+ desc: "3P embed",
+ cookieNames,
+ expectsCookie: false,
+});
+
+testCookieStoreCookies({
+ desc: "3P embed",
+ cookieNames,
+ expectsCookie: false,
+});
+
+test(() => {
+ const thirdPartyDomCookieName = "3P_dom";
+ document.cookie =
+ `${thirdPartyDomCookieName}=foobar;Secure;Path=/;SameSite=None`;
+
+ assertDomCanAccessCookie([thirdPartyDomCookieName], false);
+}, "Cross site embed setting DOM cookies");
+
+if (window.cookieStore) {
+ promise_test(async () => {
+ const thirdPartyCsCookieName = "3P_cs";
+ await cookieStore.set({
+ name: thirdPartyCsCookieName,
+ value: "foobar",
+ path: "/",
+ sameSite: "none",
+ }).then(
+ // The promise should reject.
+ () => { assert_unreached(); },
+ () => {});
+
+ await assertCookieStoreCanAccessCookies([thirdPartyCsCookieName], false);
+ }, "Cross site embed setting CookieStore cookies");
+}
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/cookies/third-party-cookies/resources/third-party-cookies-cross-site-window.html b/testing/web-platform/tests/cookies/third-party-cookies/resources/third-party-cookies-cross-site-window.html
new file mode 100644
index 0000000000..99418a6749
--- /dev/null
+++ b/testing/web-platform/tests/cookies/third-party-cookies/resources/third-party-cookies-cross-site-window.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<meta name="timeout" content="long">
+<title>Cross-site window</title>
+<script src="/resources/testharness.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script src="/cookies/third-party-cookies/resources/test-helpers.js"></script>
+
+<body>
+<script>
+
+let origin;
+
+// Test that parent window passed its origin in the URL parameters correctly.
+test(() => {
+ assert_true(window.location.search.startsWith("?origin="));
+ origin = decodeURIComponent(window.location.search.slice(
+ window.location.search.indexOf("?origin=") + 8));
+}, "Cross-site window opened correctly");
+
+// Cookies set by the parent window in a 1P context.
+const cookieNames = ["1P_http", "1P_dom"];
+if (window.cookieStore) {
+ cookieNames.push("1P_cs");
+}
+
+// Test theses cookies are not available on cross-site subresource requests
+// to the origin that set them.
+testHttpCookies({
+ desc: "3P fetch",
+ origin,
+ cookieNames,
+ expectsCookie: false,
+});
+
+promise_test(async () => {
+ const thirdPartyHttpCookie = "3P_http"
+ await credFetch(
+ `${origin}/cookies/resources/set.py?${thirdPartyHttpCookie}=foobar;` +
+ "Secure;Path=/;SameSite=None");
+ await assertOriginCanAccessCookies({
+ origin,
+ cookieNames: ["3P_http"],
+ expectsCookie: false,
+ });
+}, "Cross site window setting HTTP cookies");
+
+// Create a cross-site <iframe> which embeds the cookies' origin into this
+// page.
+const iframe = document.createElement("iframe");
+const url = new URL(
+ "/cookies/third-party-cookies/resources/" +
+ "third-party-cookies-cross-site-embed.html",
+ origin);
+iframe.src = String(url);
+document.body.appendChild(iframe);
+
+fetch_tests_from_window(iframe.contentWindow);
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/cookies/third-party-cookies/third-party-cookies.tentative.https.html b/testing/web-platform/tests/cookies/third-party-cookies/third-party-cookies.tentative.https.html
new file mode 100644
index 0000000000..184649ff5b
--- /dev/null
+++ b/testing/web-platform/tests/cookies/third-party-cookies/third-party-cookies.tentative.https.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<meta charset="utf-8"/>
+<meta name="timeout" content="long">
+<title>Test third-party cookies</title>
+<title>Test partitioned cookies</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/cookies/resources/cookie-helper.sub.js"></script>
+<script src="/cookies/third-party-cookies/resources/test-helpers.js"></script>
+
+<body>
+<script>
+
+document.body.onload = async () => {
+ // Set SameSite=None cookie in a 1P context using HTTP.
+ const attributes = "Secure;Path=/;SameSite=None";
+ const httpCookieName = "1P_http";
+ await credFetch(
+ `${self.origin}/cookies/resources/set.py?${httpCookieName}=foobar;${
+ attributes}`);
+
+ // Set another cookie using document.cookie.
+ const domCookieName = "1P_dom";
+ document.cookie = `${domCookieName}=foobar;${attributes}`;
+
+ const cookieNames = [httpCookieName, domCookieName];
+
+ // Set another cookie using the CookieStore API, if supported.
+ if (window.cookieStore) {
+ const cookieStoreCookieName = "1P_cs";
+ await cookieStore.set({
+ name: cookieStoreCookieName,
+ value: "foobar",
+ path: "/",
+ sameSite: "none",
+ });
+ cookieNames.push(cookieStoreCookieName);
+ }
+
+ // Test that the cookie is available in a first-party context via HTTP.
+ testHttpCookies({
+ desc: "1P window",
+ origin: self.origin,
+ cookieNames,
+ expectsCookie: true,
+ });
+
+ // // Verify that the cookies are available to the DOM as well.
+ testDomCookies({
+ desc: "1P window",
+ cookieNames,
+ expectsCookie: true,
+ });
+ testCookieStoreCookies({
+ desc: "1P window",
+ cookieNames,
+ expectsCookie: true,
+ });
+
+ // Open a cross-site window which will embed the current origin in a
+ // third-party context.
+ const crossSiteUrl = new URL(
+ `./resources/third-party-cookies-cross-site-window.html?origin=${
+ encodeURIComponent(self.origin)}`,
+ get_host_info().HTTPS_NOTSAMESITE_ORIGIN + self.location.pathname);
+ const popup = window.open(crossSiteUrl);
+ fetch_tests_from_window(popup);
+};
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/cookies/value/value-ctl.html b/testing/web-platform/tests/cookies/value/value-ctl.html
new file mode 100644
index 0000000000..5a24064f43
--- /dev/null
+++ b/testing/web-platform/tests/cookies/value/value-ctl.html
@@ -0,0 +1,64 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Test cookie value parsing with control characters</title>
+ <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ // Tests for control characters (CTLs) in a cookie's value.
+ // CTLs are defined by RFC 5234 to be %x00-1F / %x7F.
+ const CTLS = getCtlCharacters();
+
+ // All CTLs, with the exception of %x09 (the tab character), should
+ // cause the cookie to be rejected.
+ for (const ctl of CTLS) {
+ if (ctl.code === 0x09) {
+ domCookieTest(
+ `test=${ctl.code}${ctl.chr}value`,
+ `test=${ctl.code}${ctl.chr}value`,
+ `Cookie with %x${ctl.code.toString(16)} in value is accepted (DOM).`);
+ } else {
+ domCookieTest(
+ `test=${ctl.code}${ctl.chr}value`,
+ '',
+ `Cookie with %x${ctl.code.toString(16)} in value is rejected (DOM).`);
+ }
+ }
+
+ // Note that per RFC 9110, %x00, %x0A, and %x0D characters in the HTTP
+ // header MUST either cause the HTTP message to be rejected or be
+ // replaced with %x20 (space) characters. Both cases will result in a
+ // passing test here. For more info, see:
+ // https://www.rfc-editor.org/rfc/rfc9110.html#section-5.5
+ for (const ctl of CTLS) {
+ if (ctl.code === 0x09) {
+ httpCookieTest(
+ `test=${ctl.code}${ctl.chr}value`,
+ `test=${ctl.code}${ctl.chr}value`,
+ `Cookie with %x${ctl.code.toString(16)} in value is accepted (HTTP).`);
+ } else if (ctl.code === 0x00 || ctl.code === 0x0A || ctl.code === 0x0D) {
+ httpCookieTest(
+ `test${ctl.code}${ctl.chr}name=${ctl.code}`,
+ `test${ctl.code} name=${ctl.code}`,
+ `Cookie with %x${ctl.code.toString(16)} in name is rejected or modified (HTTP).`,
+ /* defaultPath */ true, /* allowFetchFailure */ true);
+ } else {
+ httpCookieTest(
+ `test=${ctl.code}${ctl.chr}value`,
+ '',
+ `Cookie with %x${ctl.code.toString(16)} in value is rejected (HTTP).`);
+ }
+ }
+
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/cookies/value/value.html b/testing/web-platform/tests/cookies/value/value.html
new file mode 100644
index 0000000000..14292cece9
--- /dev/null
+++ b/testing/web-platform/tests/cookies/value/value.html
@@ -0,0 +1,170 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset=utf-8>
+ <title>Test cookie value parsing</title>
+ <meta name=help href="https://tools.ietf.org/html/rfc6265#section-5.2">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="/cookies/resources/cookie-test.js"></script>
+ </head>
+ <body>
+ <div id=log></div>
+ <script>
+ // TODO: there is more to test here, these tests capture the old
+ // ported http-state tests. Feel free to delete this comment when more
+ // are added, or these are split up into logical groups.
+ const valueTests = [
+ {
+ cookie: "test=1, baz=qux",
+ expected: "test=1, baz=qux",
+ name: "Set value containing a comma",
+ },
+ {
+ cookie: 'test="2, baz=qux"',
+ expected: 'test="2, baz=qux"',
+ name: "Set quoted value containing a comma",
+ },
+ {
+ cookie: 'test="3zz;pp" ; ;',
+ expected: 'test="3zz',
+ name: "Ignore values after semicolon",
+ },
+ {
+ cookie: 'test="4zz ;',
+ expected: 'test="4zz',
+ name: "Ignore whitespace at the end of value",
+ },
+ {
+ cookie: 'test="5zzz " "ppp" ;',
+ expected: 'test="5zzz " "ppp"',
+ name: "Set value including quotes and whitespace up until semicolon",
+ },
+ {
+ cookie: 'test=6A"B ;',
+ expected: 'test=6A"B',
+ name: "Set value with a single quote excluding whitespace"
+ },
+ {
+ cookie: "test7",
+ expected: "test7",
+ name: "Set nameless cookie to its value",
+ },
+ {
+ cookie: '"test8\"HHH"',
+ expected: '"test8\"HHH"',
+ name: "Set nameless cookie to its value with an escaped quote",
+ },
+ {
+ cookie: 'test="9',
+ expected: 'test="9',
+ name: "Set value with unbalanced leading quote",
+ },
+ {
+ cookie: "=test10",
+ expected: "test10",
+ name: "Set nameless cookie followed by '=' to its value",
+ },
+ {
+ // 4 + 2 + 4090 = 4096
+ cookie: `test=11${"a".repeat(4090)}`,
+ expected: `test=11${"a".repeat(4090)}`,
+ name: "Set cookie with large name + value ( = 4kb)",
+ },
+ {
+ // 4 + 2 + 4091 = 4097
+ cookie: `test=12${"a".repeat(4091)}`,
+ expected: "",
+ name: "Ignore cookie with large name + value ( > 4kb)",
+ },
+ {
+ cookie: `test=13\nZYX`,
+ expected: "test=13",
+ name: "Set cookie but ignore value after LF",
+ },
+ {
+ cookie: 'test="14 " ;',
+ expected: 'test="14 "',
+ name: "Set cookie ignoring whitespace after value endquote",
+ },
+ {
+ cookie: "test=15 ;",
+ expected: "test=15",
+ name: "Ignore whitespace and ; after value",
+ },
+ {
+ cookie: "test= 16",
+ expected: "test=16",
+ name: "Ignore whitespace preceding value",
+ },
+ {
+ cookie: 'test="17"',
+ expected: 'test="17"',
+ name: "Set cookie with quotes in value",
+ },
+ {
+ cookie: 'test=" 18 "',
+ expected: 'test=" 18 "',
+ name: "Set cookie keeping whitespace inside quoted value",
+ },
+ {
+ cookie: 'test="19;wow"',
+ expected: 'test="19',
+ name: "Set cookie value ignoring characters after semicolon",
+ },
+ {
+ cookie: 'test="20=20"',
+ expected: 'test="20=20"',
+ name: "Set cookie with another = inside quoted value",
+ },
+ {
+ cookie: "test = 21 ; ttt",
+ expected: "test=21",
+ name: "Set cookie ignoring whitespace surrounding value and characters after first semicolon",
+ },
+ {
+ cookie: ["testA=22", "test22=", "testB=22"],
+ expected: "testA=22; test22=; testB=22",
+ name: "Set valueless cookie, given `Set-Cookie: test22=`",
+ },
+ {
+ cookie: "test=%32%33",
+ expected: "test=%32%33",
+ name: "URL-encoded cookie value is not decoded",
+ },
+ {
+ cookie: "test24==",
+ expected: "test24==",
+ name: "Set cookie with value set to =",
+ },
+ {
+ cookie: 'test=25=25',
+ expected: 'test=25=25',
+ name: "Set cookie with one = inside an unquoted value",
+ },
+ {
+ cookie: 'test=26=26=26',
+ expected: 'test=26=26=26',
+ name: "Set cookie with two = inside an unquoted value",
+ },
+ {
+ cookie: 'test=27 test',
+ expected: 'test=27 test',
+ name: "Set cookie with a space character in the value",
+ },
+ {
+ cookie: ' test test28 ;',
+ expected: 'test test28',
+ name: "Set a nameless cookie with a space character in the value",
+ },
+ ];
+
+ for (const test of valueTests) {
+ httpCookieTest(test.cookie, test.expected, test.name, test.defaultPath);
+ }
+ </script>
+ </body>
+</html>