Add tentative WPT to verify download in sandbox

General testing idea:
For a network request, the server stores a token in stash.
And after a fixed period of time, we validate the token in the
stash to verify a download has happened. Also assert that no
additional navigation should happen.

In the case of <a download> where the decision of download can be made
before resource fetching, the server sets the token immediately.

In the case of navigation to a download, the server will stream a
response over 1 seconds and set the token at the end only when the
socket has been connected. So it is able to detect any download
cancellation while fetching the resource.

Bug: 539938, 927183
Change-Id: I7b90d46504603f60938a46acee9fbd7d1483988b
Reviewed-on: https://chromium-review.googlesource.com/c/1446395
Reviewed-by: Mike West <mkwst@chromium.org>
Commit-Queue: Yao Xiao <yaoxia@chromium.org>
Cr-Commit-Position: refs/heads/master@{#634249}
diff --git a/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_anchor_download_allow_downloads_without_user_activation.sub.tentative.html b/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_anchor_download_allow_downloads_without_user_activation.sub.tentative.html
new file mode 100644
index 0000000..32409f2
--- /dev/null
+++ b/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_anchor_download_allow_downloads_without_user_activation.sub.tentative.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>&lt;a download&gt; triggered download in sandbox is allowed by allow-downloads-without-user-activation.</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-iframe-element">
+<script src="/resources/testharness.js"></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src="support/iframe_sandbox_download_helper.js"></script>
+<body>
+<script>
+"use strict";
+
+async_test(t => {
+    const token = "{{$id:uuid()}}";
+    var iframe = document.createElement("iframe");
+    iframe.srcdoc = "<a>Download</a>";
+    iframe.sandbox = "allow-same-origin allow-downloads-without-user-activation";
+    iframe.onload = t.step_func(function () {
+        iframe.contentWindow.addEventListener(
+            "unload", t.unreached_func("Unexpected navigation."));
+        var anchor = iframe.contentDocument.getElementsByTagName('a')[0];
+        anchor.href = "support/download_stash.py?token=" + token;
+        anchor.download = null;
+        anchor.click();
+        AssertDownloadSuccess(t, token, DownloadVerifyDelay());
+    });
+
+    document.body.appendChild(iframe);
+}, "<a download> triggered download in sandbox is allowed by allow-downloads-without-user-activation.");
+</script>
+</body>
diff --git a/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_anchor_download_block_downloads_without_user_activation.sub.tentative.html b/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_anchor_download_block_downloads_without_user_activation.sub.tentative.html
new file mode 100644
index 0000000..abd4d7c
--- /dev/null
+++ b/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_anchor_download_block_downloads_without_user_activation.sub.tentative.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>&lt;a download&gt; triggered download in sandbox is blocked.</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-iframe-element">
+<script src="/resources/testharness.js"></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src="support/iframe_sandbox_download_helper.js"></script>
+<body>
+<script>
+"use strict";
+
+async_test(t => {
+    const token = "{{$id:uuid()}}";
+    var iframe = document.createElement("iframe");
+    iframe.srcdoc = "<a>Download</a>";
+    iframe.sandbox = "allow-same-origin";
+    iframe.onload = t.step_func(function () {
+        iframe.contentWindow.addEventListener(
+            "unload", t.unreached_func("Unexpected navigation."));
+        var anchor = iframe.contentDocument.getElementsByTagName('a')[0];
+        anchor.href = "support/download_stash.py?token=" + token;
+        anchor.download = null;
+        anchor.click();
+        AssertDownloadFailure(t, token, DownloadVerifyDelay());
+    });
+
+    document.body.appendChild(iframe);
+}, "<a download> triggered download in sandbox is blocked.");
+</script>
+</body>
diff --git a/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigation_download_allow_downloads_without_user_activation.sub.tentative.html b/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigation_download_allow_downloads_without_user_activation.sub.tentative.html
new file mode 100644
index 0000000..4fee27f
--- /dev/null
+++ b/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigation_download_allow_downloads_without_user_activation.sub.tentative.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Navigation resulted download in sandbox is allowed by allow-downloads-without-user-activation.</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-iframe-element">
+<script src="/resources/testharness.js"></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src="support/iframe_sandbox_download_helper.js"></script>
+<body>
+<script>
+"use strict";
+
+async_test(t => {
+    const token = "{{$id:uuid()}}";
+    var iframe = document.createElement("iframe");
+    iframe.srcdoc = "<a>Download</a>";
+    iframe.sandbox = "allow-same-origin allow-downloads-without-user-activation";
+    iframe.onload = t.step_func(function () {
+        iframe.contentWindow.addEventListener(
+            "unload", t.unreached_func("Unexpected navigation."));
+        var anchor = iframe.contentDocument.getElementsByTagName('a')[0];
+        // Set |finish-delay| to let the server stream a response over a  period
+        // of time, so it's able to catch potential download cancellation by
+        // detecting a socket close.
+        anchor.href = "support/download_stash.py?token=" + token + "&finish-delay=" + StreamDownloadFinishDelay();
+        anchor.click();
+        AssertDownloadSuccess(t, token, StreamDownloadFinishDelay() + DownloadVerifyDelay());
+    });
+
+    document.body.appendChild(iframe);
+}, "Navigation resulted download in sandbox is allowed by allow-downloads-without-user-activation.");
+</script>
+</body>
diff --git a/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigation_download_block_downloads_without_user_activation.sub.tentative.html b/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigation_download_block_downloads_without_user_activation.sub.tentative.html
new file mode 100644
index 0000000..9b9246c
--- /dev/null
+++ b/html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigation_download_block_downloads_without_user_activation.sub.tentative.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Navigation resulted download in sandbox is blocked.</title>
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#attr-iframe-sandbox">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-iframe-element">
+<script src="/resources/testharness.js"></script>
+<script src='/resources/testharnessreport.js'></script>
+<script src="support/iframe_sandbox_download_helper.js"></script>
+<body>
+<script>
+"use strict";
+
+async_test(t => {
+    const token = "{{$id:uuid()}}";
+    var iframe = document.createElement("iframe");
+    iframe.srcdoc = "<a>Download</a>";
+    iframe.sandbox = "allow-same-origin";
+    iframe.onload = t.step_func(function () {
+        iframe.contentWindow.addEventListener(
+            "unload", t.unreached_func("Unexpected navigation."));
+        var anchor = iframe.contentDocument.getElementsByTagName('a')[0];
+        // Set |finish-delay| to let the server stream a response over a  period
+        // of time, so it's able to catch potential download cancellation by
+        // detecting a socket close.
+        anchor.href = "support/download_stash.py?token=" + token + "&finish-delay=" + StreamDownloadFinishDelay();
+        anchor.click();
+        AssertDownloadFailure(t, token, StreamDownloadFinishDelay() + DownloadVerifyDelay());
+    });
+
+    document.body.appendChild(iframe);
+}, "Navigation resulted download in sandbox is blocked.");
+</script>
+</body>
diff --git a/html/semantics/embedded-content/the-iframe-element/support/download_stash.py b/html/semantics/embedded-content/the-iframe-element/support/download_stash.py
new file mode 100644
index 0000000..24e1dfd
--- /dev/null
+++ b/html/semantics/embedded-content/the-iframe-element/support/download_stash.py
@@ -0,0 +1,28 @@
+import time
+
+def main(request, response):
+    token = request.GET["token"]
+    response.status = 200
+    response.headers.append("Content-Type", "text/html")
+    if "verify-token" in request.GET:
+      if request.server.stash.take(token):
+        return 'TOKEN_SET'
+      return 'TOKEN_NOT_SET'
+
+    if "finish-delay" not in request.GET:
+      # <a download>
+      request.server.stash.put(token, True)
+      return
+
+    # navigation to download
+    response.headers.append("Content-Disposition", "attachment")
+    response.write_status_headers()
+    finish_delay = float(request.GET["finish-delay"]) / 1E3
+    count = 10
+    single_delay = finish_delay / count
+    for i in range(count): # pylint: disable=unused-variable
+        time.sleep(single_delay)
+        response.writer.write_content("\n")
+        if not response.writer.flush():
+          return
+    request.server.stash.put(token, True)
diff --git a/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_download_helper.js b/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_download_helper.js
new file mode 100644
index 0000000..7090e766
--- /dev/null
+++ b/html/semantics/embedded-content/the-iframe-element/support/iframe_sandbox_download_helper.js
@@ -0,0 +1,37 @@
+function StreamDownloadFinishDelay() {
+    return 1000;
+}
+
+function DownloadVerifyDelay() {
+    return 1000;
+}
+
+function VerifyDownload(test_obj, token, timeout, expect_download) {
+    var verify_token = test_obj.step_func(function () {
+        var xhr = new XMLHttpRequest();
+        xhr.open('GET', 'support/download_stash.py?verify-token&token=' + token);
+        xhr.onload = test_obj.step_func(function(e) {
+            if (expect_download) {
+              if (xhr.response != "TOKEN_SET") {
+                // Always retry, and rely on the test timeout to conclude that
+                // download didn't happen and to fail the test.
+                test_obj.step_timeout(verify_token, DownloadVerifyDelay());
+                return;
+              }
+            } else {
+              assert_equals(xhr.response, "TOKEN_NOT_SET", "Expect no download to happen, but got one.");
+            }
+            test_obj.done();
+        });
+        xhr.send();
+    });
+    test_obj.step_timeout(verify_token, timeout);
+}
+
+function AssertDownloadSuccess(test_obj, token, timeout) {
+    VerifyDownload(test_obj, token, timeout, true);
+}
+
+function AssertDownloadFailure(test_obj, token, timeout) {
+    VerifyDownload(test_obj, token, timeout, false);
+}