[SPC] Add user activation to SPC tests (#33695)

* [SPC] Add user activation to SPC tests

Also adds a test that enforces that SPC must have user activation to be called.

* Fix test
diff --git a/secure-payment-confirmation/authentication-accepted.https.html b/secure-payment-confirmation/authentication-accepted.https.html
index 2e9611f..1677de3 100644
--- a/secure-payment-confirmation/authentication-accepted.https.html
+++ b/secure-payment-confirmation/authentication-accepted.https.html
@@ -42,6 +42,7 @@
     }
   }], PAYMENT_DETAILS);
 
+  await test_driver.bless('user activation');
   const responsePromise = request.show();
 
   const response = await responsePromise;
diff --git a/secure-payment-confirmation/authentication-cross-origin.sub.https.html b/secure-payment-confirmation/authentication-cross-origin.sub.https.html
index a9be91b..efdb798 100644
--- a/secure-payment-confirmation/authentication-cross-origin.sub.https.html
+++ b/secure-payment-confirmation/authentication-cross-origin.sub.https.html
@@ -47,6 +47,7 @@
     }
   }], PAYMENT_DETAILS);
 
+  await test_driver.bless('user activation');
   const responsePromise = request.show();
 
   const response = await responsePromise;
diff --git a/secure-payment-confirmation/authentication-icon-data-url.https.html b/secure-payment-confirmation/authentication-icon-data-url.https.html
index b4c01f3..dbdf666 100644
--- a/secure-payment-confirmation/authentication-icon-data-url.https.html
+++ b/secure-payment-confirmation/authentication-icon-data-url.https.html
@@ -43,6 +43,7 @@
     }
   }], PAYMENT_DETAILS);
 
+  await test_driver.bless('user activation');
   const responsePromise = request.show();
 
   const response = await responsePromise;
diff --git a/secure-payment-confirmation/authentication-in-iframe.sub.https.html b/secure-payment-confirmation/authentication-in-iframe.sub.https.html
index b3a01e3..951b540 100644
--- a/secure-payment-confirmation/authentication-in-iframe.sub.https.html
+++ b/secure-payment-confirmation/authentication-in-iframe.sub.https.html
@@ -36,7 +36,7 @@
   // Wait for the iframe to load.
   const readyPromise = new Promise(resolve => {
       window.addEventListener('message', function handler(evt) {
-        if (evt.source === frame.contentWindow) {
+        if (evt.source === frame.contentWindow && evt.data.type == 'loaded') {
           window.removeEventListener('message', handler);
 
           resolve(evt.data);
@@ -50,7 +50,7 @@
   // race.
   const resultPromise = new Promise(resolve => {
       window.addEventListener('message', function handler(evt) {
-        if (evt.source === frame.contentWindow) {
+        if (evt.source === frame.contentWindow && evt.data.type == 'spc_result') {
           // We're done with the child iframe now.
           document.body.removeChild(frame);
           window.removeEventListener('message', handler);
diff --git a/secure-payment-confirmation/authentication-invalid-icon.https.html b/secure-payment-confirmation/authentication-invalid-icon.https.html
index 8987c7b..84c629f 100644
--- a/secure-payment-confirmation/authentication-invalid-icon.https.html
+++ b/secure-payment-confirmation/authentication-invalid-icon.https.html
@@ -42,6 +42,7 @@
       },
     }
   }], PAYMENT_DETAILS);
+  await test_driver.bless('user activation');
   await promise_rejects_dom(t, "NotSupportedError", request.show());
 
   // Now try an icon that cannot be decoded.
@@ -59,6 +60,7 @@
       },
     }
   }], PAYMENT_DETAILS);
+  await test_driver.bless('user activation');
   await promise_rejects_dom(t, "NotSupportedError", request.show());
 }, 'SPC authentication with an invalid icon');
 
@@ -96,6 +98,7 @@
     }
   }], PAYMENT_DETAILS);
 
+  await test_driver.bless('user activation');
   const responsePromise = request.show();
   const response = await responsePromise;
   await response.complete('success');
diff --git a/secure-payment-confirmation/authentication-rejected.https.html b/secure-payment-confirmation/authentication-rejected.https.html
index 4973748..444733b 100644
--- a/secure-payment-confirmation/authentication-rejected.https.html
+++ b/secure-payment-confirmation/authentication-rejected.https.html
@@ -43,6 +43,7 @@
     }
   }], PAYMENT_DETAILS);
 
+  await test_driver.bless('user activation');
   return promise_rejects_dom(t, "NotAllowedError", request.show());
 }, 'Rejected SPC authentication');
 </script>
diff --git a/secure-payment-confirmation/authentication-requires-user-activation.https.html b/secure-payment-confirmation/authentication-requires-user-activation.https.html
new file mode 100644
index 0000000..6519086
--- /dev/null
+++ b/secure-payment-confirmation/authentication-requires-user-activation.https.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Test for the 'secure-payment-confirmation' payment method authentication - requires user activation</title>
+<link rel="help" href="https://w3c.github.io/secure-payment-confirmation/sctn-authentication">
+<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="utils.sub.js"></script>
+<script>
+'use strict';
+
+promise_test(async t => {
+  const authenticator = await window.test_driver.add_virtual_authenticator(
+      AUTHENTICATOR_OPTS);
+  t.add_cleanup(() => {
+    return window.test_driver.remove_virtual_authenticator(authenticator);
+  });
+
+  await window.test_driver.set_spc_transaction_mode("autoaccept");
+  t.add_cleanup(() => {
+    return window.test_driver.set_spc_transaction_mode("none");
+  });
+
+
+  const credential = await createCredential();
+
+  const challenge = 'server challenge';
+  const payeeOrigin = 'https://merchant.com';
+  const displayName = 'Troycard ***1234';
+  const request = new PaymentRequest([{
+    supportedMethods: 'secure-payment-confirmation',
+    data: {
+      credentialIds: [credential.rawId],
+      challenge: Uint8Array.from(challenge, c => c.charCodeAt(0)),
+      rpId: window.location.hostname,
+      payeeOrigin,
+      timeout: 60000,
+      instrument: {
+        displayName,
+        icon: ICON_URL,
+      },
+    }
+  }], PAYMENT_DETAILS);
+
+  return promise_rejects_dom(t, "SecurityError", request.show());
+}, 'SPC authentication not allowed without a user activation');
+</script>
diff --git a/secure-payment-confirmation/resources/iframe-authenticate.html b/secure-payment-confirmation/resources/iframe-authenticate.html
index 067cb58..e6a634f 100644
--- a/secure-payment-confirmation/resources/iframe-authenticate.html
+++ b/secure-payment-confirmation/resources/iframe-authenticate.html
@@ -1,6 +1,8 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
 <title>SPC Authentication iframe</title>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
 <script src="../utils.sub.js"></script>
 <script>
 'use strict';
@@ -32,6 +34,8 @@
     }
   }], PAYMENT_DETAILS);
 
+  test_driver.set_test_context(window.parent);
+  await test_driver.bless('user activation');
   const responsePromise = request.show();
 
   const response = await responsePromise;
@@ -42,9 +46,9 @@
   // Let our parent know the results. Some WebAuthn fields cannot be cloned, so
   // we have to do some teardown ourselves.
   const clientDataJSON = JSON.parse(arrayBufferToString(cred.response.clientDataJSON))
-  window.parent.postMessage({ id: cred.id, clientDataJSON }, '*');
+  window.parent.postMessage({ type: 'spc_result', id: cred.id, clientDataJSON }, '*');
 });
 
 // Now let our parent know that we are ready to receive the credential ID.
-window.parent.postMessage(true, '*');
+window.parent.postMessage({ type: 'loaded' }, '*');
 </script>