summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/fenced-frame
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/fenced-frame
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/fenced-frame')
-rw-r--r--testing/web-platform/tests/fenced-frame/README.md225
-rw-r--r--testing/web-platform/tests/fenced-frame/add-fencedframe-to-detached-iframe.https.html36
-rw-r--r--testing/web-platform/tests/fenced-frame/ancestor-throttle.https.html87
-rw-r--r--testing/web-platform/tests/fenced-frame/anchor-focus.https.html47
-rw-r--r--testing/web-platform/tests/fenced-frame/autofocus-denied.https.html25
-rw-r--r--testing/web-platform/tests/fenced-frame/automatic-beacon-anchor-click-handler.https.html63
-rw-r--r--testing/web-platform/tests/fenced-frame/automatic-beacon-click-handler.https.html61
-rw-r--r--testing/web-platform/tests/fenced-frame/automatic-beacon-component-ad.https.html56
-rw-r--r--testing/web-platform/tests/fenced-frame/automatic-beacon-cross-origin-false.https.html42
-rw-r--r--testing/web-platform/tests/fenced-frame/automatic-beacon-cross-origin-navigation.https.html42
-rw-r--r--testing/web-platform/tests/fenced-frame/automatic-beacon-cross-origin-no-data.https.html47
-rw-r--r--testing/web-platform/tests/fenced-frame/automatic-beacon-cross-origin-no-opt-in.https.html45
-rw-r--r--testing/web-platform/tests/fenced-frame/automatic-beacon-no-destination.https.html44
-rw-r--r--testing/web-platform/tests/fenced-frame/automatic-beacon-no-opt-in.https.html48
-rw-r--r--testing/web-platform/tests/fenced-frame/automatic-beacon-shared-storage.https.html45
-rw-r--r--testing/web-platform/tests/fenced-frame/automatic-beacon-two-events-clear.https.html52
-rw-r--r--testing/web-platform/tests/fenced-frame/automatic-beacon-two-events-persist.https.html48
-rw-r--r--testing/web-platform/tests/fenced-frame/automatic-beacon-unfenced-top.https.html55
-rw-r--r--testing/web-platform/tests/fenced-frame/automatic-beacon-use-ancestor-data.https.html62
-rw-r--r--testing/web-platform/tests/fenced-frame/background-fetch.https.html128
-rw-r--r--testing/web-platform/tests/fenced-frame/background-sync.https.html218
-rw-r--r--testing/web-platform/tests/fenced-frame/badging.https.html79
-rw-r--r--testing/web-platform/tests/fenced-frame/battery_status.https.html19
-rw-r--r--testing/web-platform/tests/fenced-frame/before-unload.https.html25
-rw-r--r--testing/web-platform/tests/fenced-frame/can-load-api.https.html68
-rw-r--r--testing/web-platform/tests/fenced-frame/change-src-attribute-after-config-installation-does-not-trigger-navigation.https.html52
-rw-r--r--testing/web-platform/tests/fenced-frame/client-hints-meta.https.html40
-rw-r--r--testing/web-platform/tests/fenced-frame/client-hints.https.html36
-rw-r--r--testing/web-platform/tests/fenced-frame/client-hints.https.html.headers3
-rw-r--r--testing/web-platform/tests/fenced-frame/compute-pressure.https.html34
-rw-r--r--testing/web-platform/tests/fenced-frame/config-cross-origin-apis.https.html44
-rw-r--r--testing/web-platform/tests/fenced-frame/config-installation-triggers-navigation-of-navigated-fenced-frame.https.html38
-rw-r--r--testing/web-platform/tests/fenced-frame/config-installation-triggers-navigation.https.html34
-rw-r--r--testing/web-platform/tests/fenced-frame/config-with-empty-url-installation-unloads-navigated-fenced-frame.https.html44
-rw-r--r--testing/web-platform/tests/fenced-frame/consume-user-activation.https.html95
-rw-r--r--testing/web-platform/tests/fenced-frame/content-index.https.html150
-rw-r--r--testing/web-platform/tests/fenced-frame/coop-bcg-swap.https.html40
-rw-r--r--testing/web-platform/tests/fenced-frame/create-credential.https.html25
-rw-r--r--testing/web-platform/tests/fenced-frame/create-in-sandbox-and-adopt-outside-sandbox.https.html35
-rw-r--r--testing/web-platform/tests/fenced-frame/csp-allowed.https.html40
-rw-r--r--testing/web-platform/tests/fenced-frame/csp-blocked.https.html65
-rw-r--r--testing/web-platform/tests/fenced-frame/csp-fenced-frame-src-allowed.https.html22
-rw-r--r--testing/web-platform/tests/fenced-frame/csp-fenced-frame-src-blocked.https.html37
-rw-r--r--testing/web-platform/tests/fenced-frame/csp-frame-src-allowed.https.html22
-rw-r--r--testing/web-platform/tests/fenced-frame/csp-frame-src-blocked.https.html36
-rw-r--r--testing/web-platform/tests/fenced-frame/csp-transparent-url.https.html53
-rw-r--r--testing/web-platform/tests/fenced-frame/csp.https.html39
-rw-r--r--testing/web-platform/tests/fenced-frame/cspee.https.html74
-rw-r--r--testing/web-platform/tests/fenced-frame/deep-copy-config.https.html108
-rw-r--r--testing/web-platform/tests/fenced-frame/default-enabled-features-allow-all.https.html74
-rw-r--r--testing/web-platform/tests/fenced-frame/default-enabled-features-allow-all.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/default-enabled-features-allow-none.https.html46
-rw-r--r--testing/web-platform/tests/fenced-frame/default-enabled-features-allow-none.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/default-enabled-features-allow-self.https.html67
-rw-r--r--testing/web-platform/tests/fenced-frame/default-enabled-features-allow-self.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/default-enabled-features-allow-unspecified.https.html59
-rw-r--r--testing/web-platform/tests/fenced-frame/default-enabled-features-attribute-allow.https.html72
-rw-r--r--testing/web-platform/tests/fenced-frame/default-enabled-features-attribute-change.https.html50
-rw-r--r--testing/web-platform/tests/fenced-frame/default-enabled-features-attribute-disallow.https.html33
-rw-r--r--testing/web-platform/tests/fenced-frame/default-enabled-features-attribution-disabled.https.html28
-rw-r--r--testing/web-platform/tests/fenced-frame/default-enabled-features-attribution-disabled.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/default-enabled-features-subframe.https.html43
-rw-r--r--testing/web-platform/tests/fenced-frame/default-enabled-features-unset.https.html29
-rw-r--r--testing/web-platform/tests/fenced-frame/default-enabled-features-unset.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/deprecated-config-apis.https.html42
-rw-r--r--testing/web-platform/tests/fenced-frame/disable-untrusted-network.https.html49
-rw-r--r--testing/web-platform/tests/fenced-frame/disallowed-navigation-to-blob.https.html37
-rw-r--r--testing/web-platform/tests/fenced-frame/disallowed-navigation-to-data.https.html35
-rw-r--r--testing/web-platform/tests/fenced-frame/disallowed-navigation-to-http.https.html40
-rw-r--r--testing/web-platform/tests/fenced-frame/disallowed-navigations-dangling-markup-urn.https.html64
-rw-r--r--testing/web-platform/tests/fenced-frame/disallowed-navigations-dangling-markup.https.html32
-rw-r--r--testing/web-platform/tests/fenced-frame/disallowed-navigations.https.html118
-rw-r--r--testing/web-platform/tests/fenced-frame/document-activeelement.https.html40
-rw-r--r--testing/web-platform/tests/fenced-frame/document-hasfocus.https.html37
-rw-r--r--testing/web-platform/tests/fenced-frame/document-picture-in-picture-denied.https.html28
-rw-r--r--testing/web-platform/tests/fenced-frame/document-referrer.https.html60
-rw-r--r--testing/web-platform/tests/fenced-frame/download.https.html39
-rw-r--r--testing/web-platform/tests/fenced-frame/embedder-coop-coep-blocked.https.html21
-rw-r--r--testing/web-platform/tests/fenced-frame/embedder-coop-coep-blocked.https.html.headers2
-rw-r--r--testing/web-platform/tests/fenced-frame/embedder-csp-not-propagate.https.html24
-rw-r--r--testing/web-platform/tests/fenced-frame/embedder-no-coep.https.html27
-rw-r--r--testing/web-platform/tests/fenced-frame/embedder-require-corp.https.html39
-rw-r--r--testing/web-platform/tests/fenced-frame/embedder-require-corp.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/fedcm-get-credential.https.html25
-rw-r--r--testing/web-platform/tests/fenced-frame/fence-api.https.html29
-rw-r--r--testing/web-platform/tests/fenced-frame/fence-report-event-destination-url.https.html42
-rw-r--r--testing/web-platform/tests/fenced-frame/fence-report-event.https.html28
-rw-r--r--testing/web-platform/tests/fenced-frame/fence-urn-iframes.https.html52
-rw-r--r--testing/web-platform/tests/fenced-frame/fledge-container-size-mutation-observer.https.html48
-rw-r--r--testing/web-platform/tests/fenced-frame/fledge-container-size.https.html102
-rw-r--r--testing/web-platform/tests/fenced-frame/fragment-navigation.https.html50
-rw-r--r--testing/web-platform/tests/fenced-frame/frame-navigation.https.html86
-rw-r--r--testing/web-platform/tests/fenced-frame/gamepad.https.html20
-rw-r--r--testing/web-platform/tests/fenced-frame/get-mode-in-nested-frame.https.html33
-rw-r--r--testing/web-platform/tests/fenced-frame/get-nested-configs.https.html124
-rw-r--r--testing/web-platform/tests/fenced-frame/header-referrer.https.html55
-rw-r--r--testing/web-platform/tests/fenced-frame/header-secFetchDest.https.html38
-rw-r--r--testing/web-platform/tests/fenced-frame/hid.https.html37
-rw-r--r--testing/web-platform/tests/fenced-frame/history-back-and-forward-should-not-work-in-fenced-tree.https.html68
-rw-r--r--testing/web-platform/tests/fenced-frame/history-length-fenced-navigations-replace-do-not-contribute-to-joint.https.html55
-rw-r--r--testing/web-platform/tests/fenced-frame/history-length-outer-page-navigation-not-reflected-in-fenced.https.html78
-rw-r--r--testing/web-platform/tests/fenced-frame/ignore-child-fenced-frame-onload-event.https.html31
-rw-r--r--testing/web-platform/tests/fenced-frame/insecure-context.html33
-rw-r--r--testing/web-platform/tests/fenced-frame/intersection-observer.https.html53
-rw-r--r--testing/web-platform/tests/fenced-frame/invalid-url.https.html17
-rw-r--r--testing/web-platform/tests/fenced-frame/key-scrolling.https.html67
-rw-r--r--testing/web-platform/tests/fenced-frame/key-value-store.https.html43
-rw-r--r--testing/web-platform/tests/fenced-frame/load-ad-with-size.https.html29
-rw-r--r--testing/web-platform/tests/fenced-frame/loading.https.html26
-rw-r--r--testing/web-platform/tests/fenced-frame/location-ancestorOrigins.https.html51
-rw-r--r--testing/web-platform/tests/fenced-frame/mediaDevices-setCaptureHandle.https.html32
-rw-r--r--testing/web-platform/tests/fenced-frame/multiple-component-ads.https.html32
-rw-r--r--testing/web-platform/tests/fenced-frame/navigate-ancestor-by-name.https.html67
-rw-r--r--testing/web-platform/tests/fenced-frame/navigate-ancestor-nested-fenced-frame.https.html20
-rw-r--r--testing/web-platform/tests/fenced-frame/navigate-ancestor-nested-iframe.https.html20
-rw-r--r--testing/web-platform/tests/fenced-frame/navigate-ancestor-top-level-fenced-frame.https.html20
-rw-r--r--testing/web-platform/tests/fenced-frame/navigate-by-name-succeed.https.html37
-rw-r--r--testing/web-platform/tests/fenced-frame/navigate-descendant-by-name.https.html90
-rw-r--r--testing/web-platform/tests/fenced-frame/navigate-related-page-by-name.https.html60
-rw-r--r--testing/web-platform/tests/fenced-frame/navigator-keyboard-layout-map.https.html39
-rw-r--r--testing/web-platform/tests/fenced-frame/navigator-keyboard-lock.https.html25
-rw-r--r--testing/web-platform/tests/fenced-frame/navigator-subapp.https.html30
-rw-r--r--testing/web-platform/tests/fenced-frame/navigator-vibrate.https.html60
-rw-r--r--testing/web-platform/tests/fenced-frame/navigator-virtualkeyboard.https.html23
-rw-r--r--testing/web-platform/tests/fenced-frame/nested-opaque-ad-sizes.https.html44
-rw-r--r--testing/web-platform/tests/fenced-frame/notification.https.html103
-rw-r--r--testing/web-platform/tests/fenced-frame/opaque-ad-sizes-exact-size.https.html27
-rw-r--r--testing/web-platform/tests/fenced-frame/opaque-ad-sizes-special-cases.https.html27
-rw-r--r--testing/web-platform/tests/fenced-frame/payment-handler.https.html60
-rw-r--r--testing/web-platform/tests/fenced-frame/payment-request.https.html37
-rw-r--r--testing/web-platform/tests/fenced-frame/permission-api-denied-non-standard.https.html40
-rw-r--r--testing/web-platform/tests/fenced-frame/permission-api-denied.https.html46
-rw-r--r--testing/web-platform/tests/fenced-frame/permission-geolocation.https.html74
-rw-r--r--testing/web-platform/tests/fenced-frame/permission-notification.https.html28
-rw-r--r--testing/web-platform/tests/fenced-frame/picture-in-picture.https.html31
-rw-r--r--testing/web-platform/tests/fenced-frame/popup-noopener.https.html57
-rw-r--r--testing/web-platform/tests/fenced-frame/prerender.https.html51
-rw-r--r--testing/web-platform/tests/fenced-frame/presentation-receiver.https.html26
-rw-r--r--testing/web-platform/tests/fenced-frame/reinsert.https.html32
-rw-r--r--testing/web-platform/tests/fenced-frame/report-event-inactive-document.https.html48
-rw-r--r--testing/web-platform/tests/fenced-frame/report-event-reserved-event.https.html40
-rw-r--r--testing/web-platform/tests/fenced-frame/report-event-sandboxed-iframe.https.html34
-rw-r--r--testing/web-platform/tests/fenced-frame/resize-lock-input.https.html112
-rw-r--r--testing/web-platform/tests/fenced-frame/resize-lock-zoom.https.html39
-rw-r--r--testing/web-platform/tests/fenced-frame/resize-lock.https.html96
-rw-r--r--testing/web-platform/tests/fenced-frame/resolve-to-config-promise.https.html80
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-csp.https.html18
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-csp.https.html.headers2
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-xfo.https.html18
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-xfo.https.html.headers2
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-inner.https.html33
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-inner.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-nested.https.html22
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-nested.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/automatic-beacon-helper.js104
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/automatic-beacon-store.py44
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/automatic-beacon-unfenced-page.html21
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/background-fetch-inner.https.html47
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/background-fetch-inner.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/background-fetch-sw-inner.https.html38
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/background-fetch-sw-inner.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/background-fetch-sw.js36
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/background-sync-helper.js23
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/background-sync-inner.https.html32
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/background-sync-inner.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/background-sync-sw-inner.https.html41
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/background-sync-sw-inner.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/background-sync-sw.js21
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/badging-sw.js23
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/before-unload-inner.html27
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/before-unload-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/check-header-referrer.py16
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/check-header-sec-fetch-dest.py14
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/client-hints-iframe-inner.sub.https.html11
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/client-hints-iframe-inner.sub.https.html.headers4
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/client-hints-inner.sub.https.html27
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/client-hints-inner.sub.https.html.headers5
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/client-hints-meta-iframe-inner.sub.https.html15
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/client-hints-meta-iframe-inner.sub.https.html.headers2
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/client-hints-meta-inner.sub.https.html31
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/client-hints-meta-inner.sub.https.html.headers2
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/close.html5
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/config-cross-origin-apis-inner.https.html37
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/config-cross-origin-apis-inner.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/config-cross-origin-iframe.https.html30
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/config-cross-origin-iframe.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/config-embed-cross-origin-iframe.https.html18
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/config-embed-cross-origin-iframe.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/content-index-sw.js28
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/cookie-access.https.html11
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/cookie-access.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/create-credential-inner.https.html38
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/create-credential-inner.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/create-popup.html18
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/create-popup.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-allowed-inner.html7
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-allowed-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-blocked-inner.html10
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-blocked-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/csp-frame-src-allowed-inner.html7
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/csp-frame-src-allowed-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/csp-frame-src-blocked-inner.html9
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/csp-frame-src-blocked-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/csp-inner.html37
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/csp-inner.html.headers2
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/csp.pngbin0 -> 4476 bytes
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/dangling-markup-helper.js15
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/default-enabled-features-helper.js53
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/default-enabled-features-navigate.https.html25
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/default-enabled-features-navigate.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-fencedframe.https.html21
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-fencedframe.https.html.headers2
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-iframe.https.html19
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-iframe.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/download-helper.js29
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/download-inner.html34
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/download-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/download-stash.py28
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/dummy.html2
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/embeddee.html7
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/embeddee.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/embedder-policy.js39
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/empty-worker.js1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/fedcm-get-credential-inner.https.html45
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/fedcm-get-credential-inner.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/fence-api-inner.https.html22
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/fence-api-inner.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/fenced-frame-loaded.html13
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/fenced-frame-loaded.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/fenced-frame-set-name-and-report-ready-for-outermost-document-to-navigate.html16
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/fenced-frame-set-name-and-report-ready-for-outermost-document-to-navigate.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/fledge-bidding-logic.py116
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/fledge-decision-logic.py66
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-create-nested.https.html48
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-create-nested.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-simple.https.html13
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-simple.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/frame-with-intersection-observer.html29
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/frame-with-intersection-observer.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/gamepad-inner.html13
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/gamepad-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/get-nested-configs-inner.html10
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/get-nested-configs-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/get-nested-configs-nested-iframe.html10
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/get-nested-configs-nested-iframe.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/get_battery.html17
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/get_battery.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/header-referrer-inner.html40
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/header-referrer-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/header-secFetchDest-inner.html26
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/header-secFetchDest-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/history-back-and-forward-should-not-work-in-fenced-tree-inner.html45
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/history-back-and-forward-should-not-work-in-fenced-tree-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/history-length-fenced-navigations-replace-do-not-contribute-to-joint-inner.html115
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/history-length-fenced-navigations-replace-do-not-contribute-to-joint-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/history-length-outer-page-navigation-not-reflected-in-fenced-inner.html72
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/history-length-outer-page-navigation-not-reflected-in-fenced-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/ignore-child-fenced-frame-onload-event-inner.html41
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/ignore-child-fenced-frame-onload-event-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/key-value-store.py46
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/location-ancestorOrigins-inner.https.html53
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/location-ancestorOrigins-inner.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-destination.https.html17
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-destination.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-fenced-frame.https.html39
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-fenced-frame.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-iframe.https.html38
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-iframe.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-helper.https.html33
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-helper.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-helper.js28
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-test-runner.https.html76
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/navigate-by-name-inner.html45
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/navigate-by-name-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/navigate-by-name-reporting-helper.html34
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/navigate-by-name-reporting-helper.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/navigate-nested-config.html14
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/navigate-nested-config.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-layout-map-inner.html21
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-layout-map-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-lock-inner.html21
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-lock-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/notification-sw.js20
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/opaque-ad-sizes-utils.js47
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/payment-handler-sw.js10
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/permission-api-denied-inner.html15
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/permission-api-denied-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/permission-geolocation-inner.html16
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/permission-geolocation-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/permission-geolocation-test-runner.html24
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/permission-notification-inner.html13
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/permission-notification-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/popup-noopener-destination.html20
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/popup-noopener-inner.html48
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/popup-noopener-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/postmessage-config.html14
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/prerender-inner.html20
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/prerender-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/presentation-receiver-inner.html17
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/presentation-receiver-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/remote-context-executor.https.html29
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/report-url.html7
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/report-url.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/resize-lock-inner.html23
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/resize-lock-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/response-204.py4
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-iframe.sub.html13
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-inner.sub.html6
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-inner.sub.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-looser-restriction.sub.html14
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/sandboxed-features-inner.sub.html20
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/sandboxed-features-inner.sub.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/sandboxed-features-looser-restriction.sub.html19
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/sandboxed-features-looser-restriction.sub.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/sandboxed-features.js126
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-inner.html28
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-sw.js18
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-sw.js.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker.js8
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-inner.html37
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-nested.html25
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-nested.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType.js19
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner-success.html11
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner-success.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner.html16
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate.js18
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/serviceWorker-push-sw.js19
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/unique-cookie-partition-inner.https.html42
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/unique-cookie-partition-inner.https.html.headers2
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/unreached.https.html15
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/utils.js648
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/web-bluetooth-inner.html33
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/web-bluetooth-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/web-nfc-inner.https.html23
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/web-nfc-inner.https.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/web-share-inner.html21
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/web-share-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/window-frameElement-inner.html14
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/window-frameElement-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/window-navigation-204-inner.html23
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/window-navigation-204-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/window-outer-dimensions-inner.html36
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/window-outer-dimensions-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/window-parent-inner.html64
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/window-parent-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/window-top-inner.html63
-rw-r--r--testing/web-platform/tests/fenced-frame/resources/window-top-inner.html.headers1
-rw-r--r--testing/web-platform/tests/fenced-frame/revoke-nested-fenced-frame-in-iframe-navigation.https.html75
-rw-r--r--testing/web-platform/tests/fenced-frame/revoke-nested-fenced-frame-navigation.https.html50
-rw-r--r--testing/web-platform/tests/fenced-frame/revoke-unfenced-top-navigation.https.html47
-rw-r--r--testing/web-platform/tests/fenced-frame/sandbox-attribute.https.html63
-rw-r--r--testing/web-platform/tests/fenced-frame/sandbox-mandatory-flags.https.html136
-rw-r--r--testing/web-platform/tests/fenced-frame/sandboxed-features-alert.https.html14
-rw-r--r--testing/web-platform/tests/fenced-frame/sandboxed-features-confirm.https.html15
-rw-r--r--testing/web-platform/tests/fenced-frame/sandboxed-features-documentdomain.https.html14
-rw-r--r--testing/web-platform/tests/fenced-frame/sandboxed-features-pointerlock.https.html14
-rw-r--r--testing/web-platform/tests/fenced-frame/sandboxed-features-presentation-request.https.html14
-rw-r--r--testing/web-platform/tests/fenced-frame/sandboxed-features-printdialog.https.html14
-rw-r--r--testing/web-platform/tests/fenced-frame/sandboxed-features-prompt.https.html14
-rw-r--r--testing/web-platform/tests/fenced-frame/sandboxed-features-screen-orientation-lock.https.html14
-rw-r--r--testing/web-platform/tests/fenced-frame/script-focus.https.html206
-rw-r--r--testing/web-platform/tests/fenced-frame/scroll-into-view.https.html44
-rw-r--r--testing/web-platform/tests/fenced-frame/selecturl-flexible-size.https.html56
-rw-r--r--testing/web-platform/tests/fenced-frame/self-urn-navigation.https.html37
-rw-r--r--testing/web-platform/tests/fenced-frame/serviceWorker-dedicated-worker.https.html71
-rw-r--r--testing/web-platform/tests/fenced-frame/serviceWorker-frameType.https.html33
-rw-r--r--testing/web-platform/tests/fenced-frame/serviceWorker-push.https.html62
-rw-r--r--testing/web-platform/tests/fenced-frame/set-automatic-beacon.https.html54
-rw-r--r--testing/web-platform/tests/fenced-frame/setting-null-config-navigates-to-about-blank.https.html44
-rw-r--r--testing/web-platform/tests/fenced-frame/show-directory-picker.https.html31
-rw-r--r--testing/web-platform/tests/fenced-frame/show-open-file-picker.https.html32
-rw-r--r--testing/web-platform/tests/fenced-frame/storage-partitioning.https.html188
-rw-r--r--testing/web-platform/tests/fenced-frame/subframe-loading.https.html30
-rw-r--r--testing/web-platform/tests/fenced-frame/unique-cookie-partition.https.html55
-rw-r--r--testing/web-platform/tests/fenced-frame/user-activation.https.html95
-rw-r--r--testing/web-platform/tests/fenced-frame/visual-viewport.https.html82
-rw-r--r--testing/web-platform/tests/fenced-frame/web-bluetooth.https.html26
-rw-r--r--testing/web-platform/tests/fenced-frame/web-nfc.https.html28
-rw-r--r--testing/web-platform/tests/fenced-frame/web-share.https.html26
-rw-r--r--testing/web-platform/tests/fenced-frame/web-usb.https.html29
-rw-r--r--testing/web-platform/tests/fenced-frame/window-close.https.html21
-rw-r--r--testing/web-platform/tests/fenced-frame/window-frameElement.https.html21
-rw-r--r--testing/web-platform/tests/fenced-frame/window-navigation-204.https.html24
-rw-r--r--testing/web-platform/tests/fenced-frame/window-outer-dimensions.https.html53
-rw-r--r--testing/web-platform/tests/fenced-frame/window-parent.https.html44
-rw-r--r--testing/web-platform/tests/fenced-frame/window-top.https.html44
390 files changed, 13215 insertions, 0 deletions
diff --git a/testing/web-platform/tests/fenced-frame/README.md b/testing/web-platform/tests/fenced-frame/README.md
new file mode 100644
index 0000000000..3dc65fbc05
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/README.md
@@ -0,0 +1,225 @@
+# Fenced Frames
+
+This directory contains [Web Platform
+Tests](third_party/blink/web_tests/external/wpt) for the [Fenced
+Frames](https://github.com/shivanigithub/fenced-frame) feature.).
+
+In general, these tests should follow Chromium's [web tests
+guidelines](docs/testing/web_tests_tips.md) and [web-platform-tests
+guidelines](/docs/testing/web_platform_tests.md). This document describes
+how to use the specific fenced frame testing infrastructure.
+
+## How to run tests
+Fenced frames feature needs to be enabled to run tests. A convenient way to
+do this is to define the following variable for fenced frames [virtual test
+suites](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/testing/web_tests.md#virtual-test-suites)
+directories.
+```bash
+export MPTEST=virtual/fenced-frame-mparch/external/wpt/fenced-frame
+```
+
+Then run tests under the virtual test suite. This will include necessary
+fenced frame flags.
+```bash
+third_party/blink/tools/run_web_tests.py -t Default $MPTEST/test-name.https.html
+```
+
+## How to write tests
+
+The `<fencedframe>` element has a strict requirement that it cannot directly
+communicate with or reach its embedder document. The fenced frame does have
+network access however, so we can use a server as a middleman to communicate
+with the outer page. There are two main test patterns we use: remote execution
+(recommended) and message passing (deprecated).
+
+### Remote execution
+
+Remote execution uses the helper `attachFencedFrameContext()` defined in
+[resources/utils.js](resources/utils.js), which requires
+[/common/dispatcher/dispatcher.js](/common/dispatcher/dispatcher.js) and
+[/common/utils.js](/common/utils.js). This returns a fenced frame that is
+wrapped with additional functionality from RemoteContext, which allows you to
+perform a remote procedure call into the frame using the function
+`execute(function, [arguments]=[])`.
+
+This interface allows us to write an entire test in only one file, with minimal
+boilerplate and an obvious control flow between all the frames on the page
+(including nested fenced frames, which can be achieved with nested `execute`
+calls).
+
+Let's see an example of communication between the top-level frame and the fenced
+frame.
+
+```js
+promise_test(async () => {
+ const important_value = "Hello";
+
+ // First, create an empty fenced frame.
+ const frame = attachFencedFrameContext();
+
+ // Next, make a function call into the frame, passing a particular string
+ // "Hello" as an argument. Make sure to `await` the call.
+ const response = await frame.execute((message_from_embedder) => {
+
+ // This code runs inside the fenced frame.
+ if (message_from_embedder == "Hello") {
+ // Message that we received was expected.
+ return "Hello to you too");
+ } else {
+ // Message that we received was *not* expected, let's report an error to
+ // the outer page so it fails the test.
+ return "Unexpected message";
+ }
+
+ }, [important_value]);
+
+ // Assert that the returned value was what we expected.
+ // Keep in mind that in a less contrived example, you can perform this assert
+ // inside the fenced frame.
+ assert_equals(response, "Hello to you too",
+ "The fenced frame received the message, and said hello back to us".)
+}, "Fenced frame and receive and send a greeting");
+```
+
+For test examples, see
+[document-referrer.https.html](document-referrer.https.html),
+[hid.https.html](hid.https.html),
+or [web-usb.https.html](web-usb.https.html).
+
+Some tips to keep in mind while writing tests using remote execution:
+* The functions `attachFencedFrameContext()` and `attachIFrameContext()`
+ optionally take a dictionary of configs as an argument. You can use it to
+ pass:
+ * The API you want to use to generate the fenced frame urn. Either `'fledge'`,
+ `'sharedstorage'`, or default (case-insensitive). When you use this option,
+ the return value becomes a promise so you **must** await it.For example:
+ ```
+ await attachFencedFrameContext({generator_api: 'fledge'});
+ ```
+ * HTML source code to inject into the frame's DOM tree. For example:
+ ```
+ attachFencedFrameContext({html: '<button id="Button">Click me!</button>'});
+ ```
+ * Response headers. For example:
+ ```
+ attachFencedFrameContext({headers: [["Content-Security-Policy", "frame-src 'self'"]]});
+ ```
+ * Attributes to set on the frame. For example:
+ ```
+ attachIFrameContext({attributes: [["csp", "frame-src 'self'"]]})
+ ```
+ * Origin of the url to allow cross-origin test. For example:
+ ```
+ attachIFrameContext({origin:get_host_info().HTTPS_REMOTE_ORIGIN})
+ ```
+ * Number of ad components to create the frame with. Note that this only works
+ with `generator_api: 'fledge'`. Protected Audience supports up to 20 ad
+ components per auction.
+ ```
+ attachFencedFrameContext({num_components: 1});
+ attachIFrameContext({num_components: 20});
+ ```
+ After creating the frame with ad components, the ad component frame won't
+ be created until you explicitly call a special creator from within the
+ frame.
+ ```
+ attachComponentFencedFrameContext(0, {html: "<b>Hello, world!</b>"});
+ attachComponentIFrameContext(19);
+ ```
+ This takes in an index, and, optionally, the `html` and `attributes` fields
+ as described above.
+* There is also a helper `attachIFrameContext()`, which does the same thing
+ but for iframes instead of fencedframes.
+* There is also a helper `replaceFrameContext(frame, {options})` which will
+ replace an existing frame context using the same underlying element (i.e., you
+ can use it to test when happens when you navigate an existing frame).
+* Make sure to `await` the result of an `execute` call, even if it doesn't
+ return anything.
+* In order to save a global variable, you need to explicitly assign to
+ `window.variable_name`. Assigning to `variable_name` without declaring it
+ will not persist across `execute` calls. This is especially important for
+ tests with nested frames, if you want to keep a handle to the nested frame
+ across multiple calls.
+* Remember to declare the function passed to `execute` as async if it itself
+ needs to invoke any async functions, including to create nested frames.
+
+### Message passing (deprecated)
+
+Message passing is done by using the helpers
+defined in
+[resources/utils.js](third_party/blink/web_tests/wpt_internal/fenced_frame/resources/utils.js)
+to send a message to the server, and poll the server for a response. All
+messages have a unique key associated with them so that documents that want to
+receive messages can poll the server for a given message that can be identified
+by a unique key.
+
+Let's see an example of sending a message to the server that a fenced frame will
+receive and respond to.
+
+**outer-page.js:**
+```js
+promise_test(async () => {
+ const important_message_key = token();
+ const fenced_frame_ack_key = token();
+ const important_value = "Hello";
+
+ // First, let's embed a new fenced frame in our test, and pass the key we
+ // just created into it as a parameter.
+ const frame_url = generateURL("resources/page-inner.html",
+ [important_message_key, fenced_frame_ack_key]);
+ attachFencedFrame(frame_url);
+
+ // Then, let's send the message over to the fenced frame.
+ writeValueToServer(important_message_key, important_value);
+
+ // Now that the message has been sent to the fenced frame, let's wait for its
+ // ACK, so that we don't exit the test before the fenced frame gets the
+ // message.
+ const response_from_fenced_frame = await
+ nextValueFromServer(fenced_frame_ack_key);
+ assert_equals(response_from_fenced_frame, "Hello to you too",
+ "The fenced frame received the message, and said hello back to us");
+}, "Fenced frame and receive and send a greeting");
+```
+
+**inner-fenced-frame.js:**
+
+```js
+async function init() { // Needed in order to use top-level await.
+ const [important_message_key, fenced_frame_ack_key] = parseKeylist();
+ const greeting_from_embedder = await nextValueFromServer(important_message_key);
+
+ if (greeting_from_embedder == "Hello") {
+ // Message that we received was expected.
+ writeValueToServer(fenced_frame_ack_key, "Hello to you too");
+ } else {
+ // Message that we received was *not* expected, let's report an error to the
+ // outer page so it fails the test.
+ writeValueToServer(fenced_frame_ack_key, "Unexpected message");
+ }
+}
+
+init();
+```
+
+When you write a new web platform test, it will likely involve passing a _new_
+message like the messages above, to and from the fenced frame. Keep in mind
+that you may have to use a _pair_ of keys, so that when one document writes a
+message associated with one unique key, it can listen for an ACK from the
+receiving document, so that it doesn't write over the message again before the
+receiving document actually reads it. **No two tests should ever use the same
+key to communicate information to and from a fenced frame**, as this will cause
+server-side race conditions.
+
+For a good test example, see
+[window-parent.html](window-parent.html).
+
+## Underlying implementations
+
+This directory contains <fencedframe> tests that exercise the
+`blink::features::kFencedFrames` feature.
+
+## Wrap lines at 80 columns
+
+This is the convention for most Chromium/WPT style tests. Note that
+`git cl format [--js]` does not reformat js code in .html files.
diff --git a/testing/web-platform/tests/fenced-frame/add-fencedframe-to-detached-iframe.https.html b/testing/web-platform/tests/fenced-frame/add-fencedframe-to-detached-iframe.https.html
new file mode 100644
index 0000000000..37c0cd6cba
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/add-fencedframe-to-detached-iframe.https.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>Test Add Fenced Frame to Detached Iframe</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async (t) => {
+ const iframe = attachIFrame("resources/dummy.html");
+
+ const doc = document.querySelector("iframe").contentDocument;
+ iframe.remove();
+
+ const ff = doc.createElement("fencedframe");
+ doc.body.append(ff);
+}, 'Add fenced frame to detached iframe test');
+
+promise_test(async (t) => {
+ const iframe = attachIFrame("resources/dummy.html");
+ const doc = document.querySelector("iframe").contentDocument;
+
+ const nested_iframe = doc.createElement('iframe');
+ nested_iframe.src = "resources/dummy.html";
+ doc.body.append(nested_iframe);
+ const nested_doc = doc.querySelector("iframe").contentDocument;
+
+ iframe.remove();
+
+ const ff = nested_doc.createElement("fencedframe");
+ nested_doc.body.append(ff);
+}, 'Add fenced frame to nested iframe in detached frame test');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/ancestor-throttle.https.html b/testing/web-platform/tests/fenced-frame/ancestor-throttle.https.html
new file mode 100644
index 0000000000..9b6dfb0d30
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/ancestor-throttle.https.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html>
+<title>Test frame-ancestor</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<body>
+
+<script>
+async function runTest(embed_url,
+ cross_origin_to_top_level_fenced_frame, cross_origin_to_top_level_iframe,
+ expected_result) {
+ const ancestor_key = token();
+
+ // Generate the url for the top level fenced frame, including the information
+ // needed to pass on to its nested iframe
+ const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
+ let fenced_frame_url = generateURL(
+ "resources/ancestor-throttle-inner.https.html",
+ [ancestor_key, embed_url, cross_origin_to_top_level_iframe]);
+ if (cross_origin_to_top_level_fenced_frame)
+ fenced_frame_url = getRemoteOriginURL(fenced_frame_url, true);
+
+ attachFencedFrame(fenced_frame_url);
+
+ // There is no API to observe whether the document in the FencedFrame loaded
+ // or not. Instead, set up a timeout. If the document loads, "loaded" will be
+ // sent to the server. Otherwise "blocked" will be sent after 3 seconds.
+ step_timeout(() => {
+ writeValueToServer(ancestor_key, "blocked");
+ }, 3000);
+
+ // Get the result for the fenced frame's nested iframe.
+ const fenced_frame_result = await nextValueFromServer(ancestor_key);
+ assert_equals(fenced_frame_result, expected_result,
+ "The inner iframe was " + expected_result + ".");
+}
+
+promise_test(async () => {
+ return runTest("fenced-frame/resources/" +
+ "ancestor-throttle-nested.https.html?" +
+ "nested_url=ancestor-throttle-iframe-csp.https.html",
+ true, false, "blocked");
+}, "root(origin1)->fenced(origin2)->iframe(origin1) should honor " +
+ "CSP frame-ancestors headers up until the fenced frame root");
+
+promise_test(async () => {
+ return runTest("fenced-frame/resources/" +
+ "ancestor-throttle-nested.https.html?" +
+ "nested_url=ancestor-throttle-iframe-csp.https.html",
+ true, false, "blocked");
+}, "root(origin1)->fenced(origin2)->iframe(origin1) should honor " +
+ "XFO SAMEORIGIN headers up until the fenced frame root");
+
+promise_test(async () => {
+ return runTest("fenced-frame/resources/" +
+ "ancestor-throttle-iframe-csp.https.html", true, true, "loaded");
+}, "root(origin1)->fenced(origin2)->iframe(origin2) should honor " +
+ "CSP frame-ancestors headers up until the fenced frame root");
+
+promise_test(async () => {
+ return runTest("fenced-frame/resources/" +
+ "ancestor-throttle-iframe-xfo.https.html", true, true, "loaded");
+}, "root(origin1)->fenced(origin2)->iframe(origin2) should honor " +
+ "XFO SAMEORIGIN headers up until the fenced frame root");
+
+promise_test(async () => {
+ return runTest("fenced-frame/resources/" +
+ "ancestor-throttle-nested.https.html?" +
+ "nested_url=ancestor-throttle-iframe-csp.https.html",
+ false, true, "blocked");
+}, "root(origin1)->fenced(origin1)->iframe(origin2)->iframe(origin2) should " +
+ "honor CSP frame-ancestors headers up until the fenced frame root");
+
+promise_test(async () => {
+ return runTest("fenced-frame/resources/" +
+ "ancestor-throttle-nested.https.html?" +
+ "nested_url=ancestor-throttle-iframe-csp.https.html",
+ false, true, "blocked");
+}, "root(origin1)->fenced(origin1)->iframe(origin2)->iframe(origin2) should " +
+ "honor XFO SAMEORIGIN headers up until the fenced frame root");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/anchor-focus.https.html b/testing/web-platform/tests/fenced-frame/anchor-focus.https.html
new file mode 100644
index 0000000000..262781f571
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/anchor-focus.https.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<title>Anchor based focusing across a fenced frame boundary</title>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+function attemptAutofocus(frame) {
+ return frame.execute(async () => {
+ let autofocusInput = document.createElement('input');
+ autofocusInput.id = "myinput";
+ document.body.appendChild(autofocusInput);
+ document.location.href = document.location.href + "#myinput";
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ return document.activeElement == autofocusInput;
+ });
+}
+
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ let autofocusIsFocused = await attemptAutofocus(frame);
+ assert_false(autofocusIsFocused,
+ "element should not get focus through anchor focusing");
+}, "Anchor focusing is blocked on an element in a fenced frame " +
+ "without user activation.");
+
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ const actions = new test_driver.Actions();
+ await actions.pointerMove(0, 0, {origin: frame.element})
+ .pointerDown()
+ .pointerUp()
+ .send();
+ let autofocusIsFocused = await attemptAutofocus(frame);
+ assert_true(autofocusIsFocused,
+ "element should get focus through anchor focusing");
+}, "Anchor focusing is allowed on an element in a fenced frame " +
+ "with user activation.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/autofocus-denied.https.html b/testing/web-platform/tests/fenced-frame/autofocus-denied.https.html
new file mode 100644
index 0000000000..ff6955a3b2
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/autofocus-denied.https.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>Autofocusing is blocked in a fenced frame</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ let autofocusIsFocused = await frame.execute(async () => {
+ let autofocusInput = document.createElement('input');
+ autofocusInput.autofocus = true;
+ document.body.appendChild(autofocusInput);
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ return document.activeElement == autofocusInput;
+ });
+ assert_false(autofocusIsFocused, "<input autofocus> received focus");
+}, "Autofocusing is blocked on an element in a fenced frame as "+
+ "it's treated like a cross-origin subframe.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/automatic-beacon-anchor-click-handler.https.html b/testing/web-platform/tests/fenced-frame/automatic-beacon-anchor-click-handler.https.html
new file mode 100644
index 0000000000..8ee1cb517f
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/automatic-beacon-anchor-click-handler.https.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<title>Test window.fence.setReportEventDataForAutomaticBeacons</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const actions = new test_driver.Actions();
+ const fencedframe = await attachFencedFrameContext(
+ {generator_api: 'fledge', automatic_beacon: true,
+ origin: get_host_info().HTTPS_REMOTE_ORIGIN});
+ const new_url = new URL("resources/dummy.html", location.href);
+ let beacon_event = {
+ eventType: "reserved.top_navigation_commit",
+ eventData: "This is the second test's beacon data!",
+ destination: ["buyer"],
+ }
+
+ await fencedframe.execute((new_url, beacon_event) => {
+ let a = document.createElement('a');
+ a.textContent = "Click me!";
+ a.href = new_url;
+ a.target = "_blank";
+ a.style.position = "absolute";
+ a.style.left = "0px";
+ a.style.top = "0px";
+ a.style.width = "100%";
+ a.style.height = "100%";
+
+ // When the anchor link is clicked, the click handler will set the data
+ // before the navigation happens. This test checks to make sure that the
+ // data makes it to the correct place by the time the navigation commits.
+ a.onclick = () => {
+ window.fence.setReportEventDataForAutomaticBeacons(beacon_event);
+ };
+ document.body.appendChild(a);
+
+ }, [new_url, beacon_event]);
+
+ // This will trigger the beacon data storing + navigation.
+ await actions.pointerMove(0, 0, {origin: fencedframe.element})
+ .pointerDown()
+ .pointerUp()
+ .send();
+
+ await verifyBeaconData(beacon_event.eventType, beacon_event.eventData);
+
+ // Leaving this fenced frame around for subsequent tests can lead to
+ // flakiness.
+ document.body.removeChild(fencedframe.element);
+}, 'Set and trigger an automatic beacon in an <a> click handler');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/automatic-beacon-click-handler.https.html b/testing/web-platform/tests/fenced-frame/automatic-beacon-click-handler.https.html
new file mode 100644
index 0000000000..31392fdb99
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/automatic-beacon-click-handler.https.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<title>Test window.fence.setReportEventDataForAutomaticBeacons</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const actions = new test_driver.Actions();
+ const fencedframe = await attachFencedFrameContext(
+ {generator_api: 'fledge', automatic_beacon: true,
+ origin: get_host_info().HTTPS_REMOTE_ORIGIN});
+
+ await fencedframe.execute(() => {
+ // This tests that old automatic beacon data is overwritten in subsequent
+ // calls to setReportEventDataForAutomaticBeacons().
+ let start_beacon_event_old = {
+ eventType: "reserved.top_navigation_start",
+ eventData: "this should not be the data",
+ destination: ["buyer"],
+ }
+ window.fence.setReportEventDataForAutomaticBeacons(start_beacon_event_old);
+ });
+ const start_event = {
+ eventType: "reserved.top_navigation_start",
+ eventData: "This is the start data",
+ destination: ["buyer"],
+ }
+ const commit_event = {
+ eventType: "reserved.top_navigation_commit",
+ eventData: "This is the commit data",
+ destination: ["buyer"],
+ }
+ // This will only set the automatic beacon data when the fenced frame is
+ // clicked.
+ await setupAutomaticBeacon(fencedframe, [start_event, commit_event],
+ NavigationTrigger.Click);
+
+ await actions.pointerMove(0, 0, {origin: fencedframe.element})
+ .pointerDown()
+ .pointerUp()
+ .send();
+
+ await verifyBeaconData(start_event.eventType, start_event.eventData);
+ await verifyBeaconData(commit_event.eventType, commit_event.eventData);
+
+ // Leaving this fenced frame around for subsequent tests can lead to
+ // flakiness.
+ document.body.removeChild(fencedframe.element);
+}, 'Set and trigger an automatic beacon in a click handler');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/automatic-beacon-component-ad.https.html b/testing/web-platform/tests/fenced-frame/automatic-beacon-component-ad.https.html
new file mode 100644
index 0000000000..1b1ef2798a
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/automatic-beacon-component-ad.https.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<title>Test automatic beacons in ad components</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const actions = new test_driver.Actions();
+ const fencedframe = await attachFencedFrameContext({
+ generator_api: 'fledge',
+ automatic_beacon: true,
+ num_components: 1,
+ // These headers will also be given to the component ad.
+ headers: [["Allow-Fenced-Frame-Automatic-Beacons", "true"]]
+ });
+ const new_url = new URL("resources/close.html", location.href);
+ const beacon_event = {
+ eventType: "reserved.top_navigation_start",
+ eventData: "this is the beacon data",
+ destination: ["buyer"],
+ crossOriginExposed: true,
+ }
+
+ await fencedframe.execute(async (new_url, beacon_event) => {
+ window.fence.setReportEventDataForAutomaticBeacons(beacon_event);
+
+ // Add an ad component that will perform the top-level navigation.
+ // The headers are the same as the ones given to `fencedframe`.
+ const ad_component = await attachComponentFencedFrameContext();
+ await ad_component.execute(async (new_url) => {
+ addEventListener("click", (event) => {
+ window.open(new_url);
+ });
+ }, [new_url]);
+ }, [new_url, beacon_event]);
+
+ await actions.pointerMove(0, 0, {origin: fencedframe.element})
+ .pointerDown()
+ .pointerUp()
+ .send();
+
+ // The component frame should not use the data set in its parent.
+ await verifyBeaconData(beacon_event.eventType, "<No data>");
+}, 'Automatic beacon in an ad component should send without data with a ' +
+ 'header opt-in.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/automatic-beacon-cross-origin-false.https.html b/testing/web-platform/tests/fenced-frame/automatic-beacon-cross-origin-false.https.html
new file mode 100644
index 0000000000..24440e4b67
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/automatic-beacon-cross-origin-false.https.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<title>Test window.fence.setReportEventDataForAutomaticBeacons from
+ SharedStorage</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const actions = new test_driver.Actions();
+ const fencedframe = await attachFencedFrameContext({
+ generator_api: 'fledge',
+ automatic_beacon: true,
+ });
+ const beacon_event = {
+ eventType: "reserved.top_navigation_start",
+ eventData: "this is the beacon data",
+ destination: ["buyer"],
+ crossOriginExposed: false,
+ }
+
+ await setupAutomaticBeacon(fencedframe, [beacon_event],
+ "resources/close.html", NavigationTrigger.CrossOriginClick);
+
+ await actions.pointerMove(0, 0, {origin: fencedframe.element})
+ .pointerDown()
+ .pointerUp()
+ .send();
+
+ await verifyBeaconData(beacon_event.eventType, "<No data>");
+}, 'Automatic beacon in a cross-origin subframe should send without data ' +
+ 'when crossOrigin=false.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/automatic-beacon-cross-origin-navigation.https.html b/testing/web-platform/tests/fenced-frame/automatic-beacon-cross-origin-navigation.https.html
new file mode 100644
index 0000000000..c476e80443
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/automatic-beacon-cross-origin-navigation.https.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<title>Test cross-origin automatic beacons</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const actions = new test_driver.Actions();
+ const fencedframe = await attachFencedFrameContext({
+ generator_api: 'fledge',
+ automatic_beacon: true
+ });
+
+ let beacon_event = {
+ eventType: "reserved.top_navigation_start",
+ eventData: "this is the beacon data",
+ destination: ["buyer"],
+ crossOriginExposed: true,
+ }
+
+ await setupAutomaticBeacon(fencedframe, [beacon_event],
+ "resources/close.html", NavigationTrigger.CrossOriginClick);
+
+ await actions.pointerMove(0, 0, {origin: fencedframe.element})
+ .pointerDown()
+ .pointerUp()
+ .send();
+
+ const received_beacon_data =
+ await nextAutomaticBeacon(beacon_event.eventType, beacon_event.eventData);
+}, 'Automatic beacon in a cross-origin subframe');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/automatic-beacon-cross-origin-no-data.https.html b/testing/web-platform/tests/fenced-frame/automatic-beacon-cross-origin-no-data.https.html
new file mode 100644
index 0000000000..dd00721dd8
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/automatic-beacon-cross-origin-no-data.https.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<title>Test cross-origin automatic beacons without data</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const actions = new test_driver.Actions();
+ const fencedframe = await attachFencedFrameContext({
+ generator_api: 'fledge',
+ automatic_beacon: true
+ });
+ const new_url = new URL("resources/close.html", location.href);
+
+ // Add a fenced frame that does not set automatic beacon data.
+ await fencedframe.execute(async (new_url) => {
+ // Add a cross-origin iframe that will perform the top-level navigation.
+ const iframe = await attachIFrameContext({
+ origin: get_host_info().HTTPS_REMOTE_ORIGIN,
+ headers: [['Allow-Fenced-Frame-Automatic-Beacons', 'true']],
+ });
+ await iframe.execute(async (new_url) => {
+ addEventListener("click", (event) => {
+ window.open(new_url);
+ });
+ }, [new_url]);
+ }, [new_url]);
+
+ await actions.pointerMove(0, 0, {origin: fencedframe.element})
+ .pointerDown()
+ .pointerUp()
+ .send();
+
+ await verifyBeaconData("reserved.top_navigation_start", "<No data>");
+ await verifyBeaconData("reserved.top_navigation_commit", "<No data>");
+}, 'Automatic beacon in a cross-origin subframe with no beacon data set');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/automatic-beacon-cross-origin-no-opt-in.https.html b/testing/web-platform/tests/fenced-frame/automatic-beacon-cross-origin-no-opt-in.https.html
new file mode 100644
index 0000000000..fa19d17f89
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/automatic-beacon-cross-origin-no-opt-in.https.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<title>Test cross-origin automatic beacons without opt-in</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const actions = new test_driver.Actions();
+ const fencedframe = await attachFencedFrameContext({
+ generator_api: 'fledge',
+ automatic_beacon: true
+ });
+
+ let beacon_event = {
+ eventType: "reserved.top_navigation_start",
+ eventData: "this is the beacon data",
+ destination: ["buyer"],
+ crossOriginExposed: true,
+ }
+ // Add a cross-origin iframe that will perform the top-level navigation.
+ // Do not set the 'Allow-Fenced-Frame-Automatic-Beacons' header to true.
+ await setupAutomaticBeacon(fencedframe, [beacon_event],
+ "resources/close.html", NavigationTrigger.CrossOriginClickNoOptIn,
+ "_blank");
+
+ await actions.pointerMove(0, 0, {origin: fencedframe.element})
+ .pointerDown()
+ .pointerUp()
+ .send();
+
+ await verifyBeaconData(beacon_event.eventType, beacon_event.eventData, false,
+ t);
+}, 'Automatic beacon in a cross-origin subframe with no opt-in header should ' +
+ 'not send.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/automatic-beacon-no-destination.https.html b/testing/web-platform/tests/fenced-frame/automatic-beacon-no-destination.https.html
new file mode 100644
index 0000000000..696c17f765
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/automatic-beacon-no-destination.https.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<title>Test window.fence.setReportEventDataForAutomaticBeacons</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
+
+<body>
+<script>
+promise_test(async (t) => {
+ const actions = new test_driver.Actions();
+ const fencedframe = await attachFencedFrameContext({
+ generator_api: "fledge",
+ automatic_beacon: true,
+ origin: get_host_info().HTTPS_REMOTE_ORIGIN
+ });
+
+ let beacon_event = {
+ eventType: "reserved.top_navigation_commit",
+ eventData: "This is the beacon data!",
+ destination: ["component-seller"],
+ };
+ await setupAutomaticBeacon(fencedframe, [beacon_event]);
+
+ await actions
+ .pointerMove(0, 0, { origin: fencedframe.element })
+ .pointerDown()
+ .pointerUp()
+ .send();
+
+ // An automatic beacon should be sent out, but no data should be sent as part
+ // of the beacon because the "buyer" destination was not specified in
+ // setReportEventDataForAutomaticBeacons().
+ await verifyBeaconData(beacon_event.eventType, "<No data>");
+}, "Set and trigger an automatic beacon with no destination specified");
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/automatic-beacon-no-opt-in.https.html b/testing/web-platform/tests/fenced-frame/automatic-beacon-no-opt-in.https.html
new file mode 100644
index 0000000000..177a7c6a51
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/automatic-beacon-no-opt-in.https.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<title>Test window.fence.setReportEventDataForAutomaticBeacons opt out</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
+
+<body>
+<script>
+promise_test(async (t) => {
+ const actions = new test_driver.Actions();
+ const fencedframe = await attachFencedFrameContext({
+ generator_api: "fledge",
+ automatic_beacon: true,
+ });
+ const new_url = new URL("resources/dummy.html", location.href);
+
+ await fencedframe.execute(
+ (new_url) => {
+ addEventListener("click", (event) => {
+ window.open(new_url, "_blank");
+ });
+ },
+ [new_url]
+ );
+
+ // An automatic beacon should not be sent out, as the document did not opt in
+ // through the call to setReportEventDataForAutomaticBeacons(), nor through
+ // the 'Allow-Fenced-Frame-Automatic-Beacons' header.
+ // Set up a timeout to ensure that there's enough time to send any potential
+ // automatic beacons.
+ await actions
+ .pointerMove(0, 0, { origin: fencedframe.element })
+ .pointerDown()
+ .pointerUp()
+ .send();
+ await verifyBeaconData("reserved.top_navigation_start", "<No data>", false,
+ t);
+}, "Automatic beacons will not send if the document does not opt in.");
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/automatic-beacon-shared-storage.https.html b/testing/web-platform/tests/fenced-frame/automatic-beacon-shared-storage.https.html
new file mode 100644
index 0000000000..4ee1d0d01b
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/automatic-beacon-shared-storage.https.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<title>Test window.fence.setReportEventDataForAutomaticBeacons from
+ SharedStorage</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const actions = new test_driver.Actions();
+ const fencedframe = await attachFencedFrameContext(
+ {generator_api: 'sharedstorage',
+ origin: get_host_info().HTTPS_REMOTE_ORIGIN});
+
+ let start_event = {
+ eventType: "reserved.top_navigation_start",
+ eventData: "This is the start beacon data!",
+ destination: ["shared-storage-select-url"],
+ }
+ let commit_event = {
+ eventType: "reserved.top_navigation_commit",
+ eventData: "This is the commit beacon data!",
+ destination: ["shared-storage-select-url"],
+ }
+ await setupAutomaticBeacon(fencedframe, [start_event, commit_event]);
+
+ await actions.pointerMove(0, 0, {origin: fencedframe.element})
+ .pointerDown()
+ .pointerUp()
+ .send();
+
+ await verifyBeaconData(start_event.eventType, start_event.eventData);
+ await verifyBeaconData(commit_event.eventType, commit_event.eventData);
+}, 'Set and trigger an automatic beacon in a click handler for SharedStorage');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/automatic-beacon-two-events-clear.https.html b/testing/web-platform/tests/fenced-frame/automatic-beacon-two-events-clear.https.html
new file mode 100644
index 0000000000..f759c0620b
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/automatic-beacon-two-events-clear.https.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<title>Test setReportEventDataForAutomaticBeacons called only once</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const actions = new test_driver.Actions();
+ const fencedframe = await attachFencedFrameContext(
+ {generator_api: 'fledge', automatic_beacon: true,
+ origin: get_host_info().HTTPS_REMOTE_ORIGIN});
+
+ let beacon_event = {
+ eventType: "reserved.top_navigation_commit",
+ eventData: "This is the beacon data!",
+ destination: ["buyer"],
+ once: true,
+ }
+ await setupAutomaticBeacon(fencedframe, [beacon_event],
+ "resources/dummy.html", NavigationTrigger.ClickOnce);
+
+ // The first click should trigger the automatic beacon and clear the beacon
+ // data.
+ await actions.pointerMove(0, 0, {origin: fencedframe.element})
+ .pointerDown()
+ .pointerUp()
+ .send();
+ await verifyBeaconData(beacon_event.eventType, beacon_event.eventData);
+
+ // The second click should not have any associated automatic beacon info, so
+ // no beacon should be sent.
+ // Set up a timeout to ensure that there's enough time to send any potential
+ // automatic beacons.
+ await actions.pointerMove(0, 0, {origin: fencedframe.element})
+ .pointerDown()
+ .pointerUp()
+ .send();
+ await verifyBeaconData(beacon_event.eventType, beacon_event.eventData, false,
+ t);
+}, 'Set expiring automatic beacon but trigger two events in a click handler');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/automatic-beacon-two-events-persist.https.html b/testing/web-platform/tests/fenced-frame/automatic-beacon-two-events-persist.https.html
new file mode 100644
index 0000000000..906a7a0d9f
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/automatic-beacon-two-events-persist.https.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<title>Test setReportEventDataForAutomaticBeacons called only once</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const actions = new test_driver.Actions();
+ const fencedframe = await attachFencedFrameContext(
+ {generator_api: 'fledge', automatic_beacon: true,
+ origin: get_host_info().HTTPS_REMOTE_ORIGIN});
+ // `once` defaults to false.
+ let beacon_event = {
+ eventType: "reserved.top_navigation_commit",
+ eventData: "This is the beacon data!",
+ destination: ["buyer"],
+ }
+
+ await setupAutomaticBeacon(fencedframe, [beacon_event]);
+
+ // The first click should trigger the automatic beacon, but the beacon data
+ // should not be cleared out.
+ await actions.pointerMove(0, 0, {origin: fencedframe.element})
+ .pointerDown()
+ .pointerUp()
+ .send();
+ await verifyBeaconData(beacon_event.eventType, beacon_event.eventData);
+
+ // The second click should still have associated automatic beacon data, and a
+ // beacon should be sent.
+ await actions.pointerMove(0, 0, {origin: fencedframe.element})
+ .pointerDown()
+ .pointerUp()
+ .send();
+ await verifyBeaconData(beacon_event.eventType, beacon_event.eventData);
+}, 'Set persisting automatic beacon but trigger two events in a click handler');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/automatic-beacon-unfenced-top.https.html b/testing/web-platform/tests/fenced-frame/automatic-beacon-unfenced-top.https.html
new file mode 100644
index 0000000000..342e13321f
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/automatic-beacon-unfenced-top.https.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<title>Test automatic beacons sent from an '_unfencedTop' navigation</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/automatic-beacon-helper.js"></script>
+
+<body>
+<script>
+// The actual test is not in this file. Instead, this file sets up a fenced
+// frame with automatic beacon data. It then causes the fenced frame to do an
+// '_unfencedTop' navigation, which will cause the automatic beacon to send. The
+// page that's navigated through '_unfencedTop' hosts the test, which simply
+// checks that the automatic beacon was sent when the navigation happened.
+//
+// The reason we do this is because an '_unfencedTop' navigation replaces this
+// page, so any test running here will stop. Other tests get around this by
+// loading the test in a pop-up, but that doesn't allow the page to receive
+// click events through test_driver.Actions().
+async function init() {
+ const actions = new test_driver.Actions();
+ const fencedframe = await attachFencedFrameContext(
+ {generator_api: 'fledge', automatic_beacon: true,
+ origin: get_host_info().HTTPS_REMOTE_ORIGIN});
+
+ const beacon_event = {
+ eventType: "reserved.top_navigation_commit",
+ eventData: "This is the beacon data!",
+ destination: ["buyer"],
+ randomField: "blah",
+ }
+ await setupAutomaticBeacon(fencedframe, [beacon_event],
+ "resources/automatic-beacon-unfenced-page.html", NavigationTrigger.Click,
+ "_unfencedTop");
+
+ await actions.setContext(window)
+ .pointerMove(0, 0, {origin: fencedframe.element})
+ .pointerDown()
+ .pointerUp()
+ .send();
+
+ // After this point, the top-level frame will be navigated to
+ // 'automatic-beacon-unfenced-page.html', which will verify that the automatic
+ // beacon was sent.
+}
+
+init();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/automatic-beacon-use-ancestor-data.https.html b/testing/web-platform/tests/fenced-frame/automatic-beacon-use-ancestor-data.https.html
new file mode 100644
index 0000000000..39df6f5c73
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/automatic-beacon-use-ancestor-data.https.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<title>Test ancestor data for automatic beacons</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const actions = new test_driver.Actions();
+ const fencedframe = await attachFencedFrameContext({
+ generator_api: 'fledge',
+ automatic_beacon: true
+ });
+ const new_url = new URL("resources/close.html", location.href);
+ const beacon_data = "this is the beacon data";
+ const beacon_type = "reserved.top_navigation_start";
+
+ await fencedframe.execute(async (new_url, beacon_data, beacon_type) => {
+ let beacon_event = {
+ eventType: beacon_type,
+ eventData: beacon_data,
+ destination: ["buyer"],
+ crossOriginExposed: false,
+ }
+ window.fence.setReportEventDataForAutomaticBeacons(beacon_event);
+
+ // Add a same-origin iframe that will perform the top-level navigation.
+ const iframe = await attachIFrameContext();
+ await iframe.execute(async (new_url) => {
+ // Set beacon data for an unrelated event.
+ let unrelated_event = {
+ eventType: "reserved.top_navigation_commit",
+ eventData: "unrelated data",
+ destination: ["buyer"],
+ crossOriginExposed: false,
+ }
+ window.fence.setReportEventDataForAutomaticBeacons(unrelated_event);
+ addEventListener("click", (event) => {
+ window.open(new_url, "_blank");
+ });
+ }, [new_url]);
+ }, [new_url, beacon_data, beacon_type]);
+
+ await actions.pointerMove(0, 0, {origin: fencedframe.element})
+ .pointerDown()
+ .pointerUp()
+ .send();
+
+ // The document should use the beacon data from its parent, since it doesn't
+ // have any beacon data set for `reserved.top_navigation_start`, even though
+ // it does have beacon data set.
+ const received_beacon_data =
+ await nextAutomaticBeacon(beacon_type, beacon_data);
+}, 'Documents should use ancestor beacon data if not available');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/background-fetch.https.html b/testing/web-platform/tests/fenced-frame/background-fetch.https.html
new file mode 100644
index 0000000000..7036f2bb5f
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/background-fetch.https.html
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<title>Test fenced frame does not allow call background fetch</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+
+ <script>
+ const background_fetch_register_key = token();
+
+ async function test(url, expected, message) {
+ attachFencedFrame(url)
+
+ // Get the result for the top-level fenced frame.
+ const fenced_frame_result = await nextValueFromServer(
+ background_fetch_register_key);
+ assert_equals(fenced_frame_result, expected, message);
+ }
+
+ promise_test(async () => {
+ const method = "fetch";
+ const fenced_frame_url = 'resources/background-fetch-inner.https.html';
+ const url = generateURL(fenced_frame_url,
+ [background_fetch_register_key, method]);
+
+ await test(
+ url,
+ '[backgroundFetch.fetch] Failed inside fencedframe as expected',
+ 'backgroundFetch.fetch is disallowed inside a same-origin fenced frame');
+
+ const CROSS_ORIGIN_DESTINATION = getRemoteOriginURL(url);
+ await test(
+ CROSS_ORIGIN_DESTINATION,
+ '[backgroundFetch.fetch] Failed inside fencedframe as expected',
+ 'backgroundFetch.fetch is disallowed inside a cross-origin fenced frame');
+ }, 'backgroundFetch.fetch');
+
+ promise_test(async () => {
+ const method = "get";
+ const fenced_frame_url = 'resources/background-fetch-inner.https.html';
+ const url = generateURL(fenced_frame_url,
+ [background_fetch_register_key, method]);
+ await test(
+ url,
+ '[backgroundFetch.get] Failed inside fencedframe as expected',
+ 'backgroundFetch.get is disallowed inside a same-origin fenced frame');
+
+ const CROSS_ORIGIN_DESTINATION = getRemoteOriginURL(url);
+ await test(
+ CROSS_ORIGIN_DESTINATION,
+ '[backgroundFetch.get] Failed inside fencedframe as expected',
+ 'backgroundFetch.get is disallowed inside a cross-origin fenced frame');
+ }, 'backgroundFetch.get');
+
+ promise_test(async () => {
+ const method = "getIds";
+ const fenced_frame_url = 'resources/background-fetch-inner.https.html';
+ const url = generateURL(fenced_frame_url,
+ [background_fetch_register_key, method]);
+ await test(
+ url,
+ '[backgroundFetch.getIds] Failed inside fencedframe as expected',
+ 'backgroundFetch.getIds is disallowed inside a same-origin fenced frame');
+
+ const CROSS_ORIGIN_DESTINATION = getRemoteOriginURL(url);
+ await test(
+ CROSS_ORIGIN_DESTINATION,
+ '[backgroundFetch.getIds] Failed inside fencedframe as expected',
+ 'backgroundFetch.getIds is disallowed inside a cross-origin fenced frame');
+ }, 'backgroundFetch.getIds');
+
+ promise_test(async () => {
+ const method = "fetch";
+ const fenced_frame_url = 'resources/background-fetch-sw-inner.https.html';
+ const url = generateURL(fenced_frame_url,
+ [background_fetch_register_key, method]);
+
+ await test(
+ url,
+ '[backgroundFetch.fetch] Failed inside fencedframe as expected',
+ 'backgroundFetch.fetch is disallowed inside a same-origin fenced frame');
+
+ const CROSS_ORIGIN_DESTINATION = getRemoteOriginURL(url);
+ await test(
+ CROSS_ORIGIN_DESTINATION,
+ '[backgroundFetch.fetch] Failed inside fencedframe as expected',
+ 'backgroundFetch.fetch is disallowed inside a cross-origin fenced frame');
+ }, 'backgroundFetch.fetch in service worker');
+
+ promise_test(async () => {
+ const method = "get";
+ const fenced_frame_url = 'resources/background-fetch-sw-inner.https.html';
+ const url = generateURL(fenced_frame_url,
+ [background_fetch_register_key, method]);
+ await test(
+ url,
+ '[backgroundFetch.get] Failed inside fencedframe as expected',
+ 'backgroundFetch.get is disallowed inside a same-origin fenced frame');
+
+ const CROSS_ORIGIN_DESTINATION = getRemoteOriginURL(url);
+ await test(
+ CROSS_ORIGIN_DESTINATION,
+ '[backgroundFetch.get] Failed inside fencedframe as expected',
+ 'backgroundFetch.get is disallowed inside a cross-origin fenced frame');
+ }, 'backgroundFetch.get in service worker');
+
+ promise_test(async () => {
+ const method = "getIds";
+ const fenced_frame_url = 'resources/background-fetch-sw-inner.https.html';
+ const url = generateURL(fenced_frame_url,
+ [background_fetch_register_key, method]);
+ await test(
+ url,
+ '[backgroundFetch.getIds] Failed inside fencedframe as expected',
+ 'backgroundFetch.getIds is disallowed inside a same-origin fenced frame');
+
+ const CROSS_ORIGIN_DESTINATION = getRemoteOriginURL(url);
+ await test(
+ CROSS_ORIGIN_DESTINATION,
+ '[backgroundFetch.getIds] Failed inside fencedframe as expected',
+ 'backgroundFetch.getIds is disallowed inside a cross-origin fenced frame');
+ }, 'backgroundFetch.getIds in service worker');
+ </script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/background-sync.https.html b/testing/web-platform/tests/fenced-frame/background-sync.https.html
new file mode 100644
index 0000000000..72eb44750b
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/background-sync.https.html
@@ -0,0 +1,218 @@
+<!DOCTYPE html>
+<title>Test fenced frame does not allow to register background sync</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+<body>
+<script>
+const background_sync_register_key = token();
+const fenced_frame_url = 'resources/background-sync-inner.https.html';
+const fenced_frame_url_for_service_worker_test =
+ 'resources/background-sync-sw-inner.https.html';
+
+const generateFencedFrameUrl = (params, is_worker_test = false) => {
+ const searchParams = new URLSearchParams(params);
+ const frame_url = is_worker_test ?
+ fenced_frame_url_for_service_worker_test : fenced_frame_url;
+
+ return generateURL(`${frame_url}?${searchParams.toString()}`,
+ [background_sync_register_key]);
+};
+
+async function background_sync_test(url, expected_text, message) {
+ attachFencedFrame(url)
+
+ // Get the result for the top-level fenced frame.
+ const fenced_frame_result =
+ await nextValueFromServer(background_sync_register_key);
+ assert_equals(fenced_frame_result, expected_text, message);
+};
+
+const background_sync_message =
+ "Background Sync is not allowed in fenced frames.";
+const periodic_background_sync_message =
+ "Periodic Background Sync is not allowed in fenced frames.";
+
+promise_test(async (t) => {
+ const method = 'register';
+ const frame_url = generateFencedFrameUrl({method});
+ const expected_message = "Failed to execute 'register' on 'SyncManager': " +
+ background_sync_message;
+ await background_sync_test(
+ frame_url,
+ expected_message,
+ 'register() is disallowed inside a same-origin fenced frame');
+
+ const cross_origin_frame_url = getRemoteOriginURL(frame_url);
+ await background_sync_test(
+ cross_origin_frame_url,
+ expected_message,
+ 'register() is disallowed inside a cross-origin fenced frame');
+}, 'background sync register');
+
+promise_test(async (t) => {
+ const method = 'getTags';
+ const frame_url = generateFencedFrameUrl({method});
+ const expected_message = background_sync_message;
+ await background_sync_test(
+ frame_url,
+ expected_message,
+ 'getTags() is disallowed inside a same-origin fenced frame');
+
+ const cross_origin_frame_url = getRemoteOriginURL(frame_url);
+ await background_sync_test(
+ cross_origin_frame_url,
+ expected_message,
+ 'getTags() is disallowed inside a cross-origin fenced frame');
+}, 'background sync getTags');
+
+promise_test(async (t) => {
+ const method = 'register';
+ const frame_url = generateFencedFrameUrl({method}, true);
+ const expected_message = "Failed to execute 'register' on 'SyncManager': " +
+ background_sync_message;
+ await background_sync_test(
+ frame_url,
+ expected_message,
+ 'register() is disallowed inside a same-origin fenced frame');
+ const cross_origin_frame_url = getRemoteOriginURL(frame_url);
+ await background_sync_test(
+ cross_origin_frame_url,
+ expected_message,
+ 'register() is disallowed inside a cross-origin fenced frame');
+}, 'background sync register in service worker');
+
+promise_test(async (t) => {
+ const method = 'getTags';
+ const frame_url = generateFencedFrameUrl({method}, true);
+ const expected_message = background_sync_message;
+ await background_sync_test(
+ frame_url,
+ expected_message,
+ 'getTags() is disallowed inside a same-origin fenced frame');
+ const cross_origin_frame_url = getRemoteOriginURL(frame_url);
+ await background_sync_test(
+ cross_origin_frame_url,
+ expected_message,
+ 'getTags() is disallowed inside a cross-origin fenced frame');
+}, 'background sync getTags in service worker');
+
+promise_test(async (t) => {
+ const params = {
+ method: 'register',
+ periodic: true
+ };
+ const frame_url = generateFencedFrameUrl(params)
+ const expected_message = "Failed to execute 'register' on " +
+ "'PeriodicSyncManager': " + periodic_background_sync_message;
+ await background_sync_test(
+ frame_url,
+ expected_message,
+ 'register() is disallowed inside a same-origin fenced frame');
+
+ const cross_origin_frame_url = getRemoteOriginURL(frame_url);
+ await background_sync_test(
+ cross_origin_frame_url,
+ expected_message,
+ 'register() is disallowed inside a cross-origin fenced frame');
+}, 'periodic sync register');
+
+promise_test(async (t) => {
+ const params = {
+ method: 'getTags',
+ periodic: true
+ };
+ const frame_url = generateFencedFrameUrl(params)
+ const expected_message = periodic_background_sync_message;
+ await background_sync_test(
+ frame_url,
+ expected_message,
+ 'getTags() is disallowed inside a same-origin fenced frame');
+
+ const cross_origin_frame_url = getRemoteOriginURL(frame_url);
+ await background_sync_test(
+ cross_origin_frame_url,
+ expected_message,
+ 'getTags() is disallowed inside a cross-origin fenced frame');
+}, 'periodic sync getTags');
+
+promise_test(async (t) => {
+ const params = {
+ method: 'unregister',
+ periodic: true
+ };
+ const frame_url = generateFencedFrameUrl(params)
+ const expected_message = periodic_background_sync_message;
+ await background_sync_test(
+ frame_url,
+ expected_message,
+ 'unregister() is disallowed inside a same-origin fenced frame');
+
+ const cross_origin_frame_url = getRemoteOriginURL(frame_url);
+ await background_sync_test(
+ cross_origin_frame_url,
+ expected_message,
+ 'unregister() is disallowed inside a cross-origin fenced frame');
+}, 'periodic sync unregister');
+
+promise_test(async (t) => {
+ const params = {
+ method: 'register',
+ periodic: true
+ };
+ const frame_url = generateFencedFrameUrl(params, true)
+ const expected_message = "Failed to execute 'register' on " +
+ "'PeriodicSyncManager': " + periodic_background_sync_message;
+ await background_sync_test(
+ frame_url,
+ expected_message,
+ 'register() is disallowed inside a same-origin fenced frame');
+
+ const cross_origin_frame_url = getRemoteOriginURL(frame_url);
+ await background_sync_test(
+ cross_origin_frame_url,
+ expected_message,
+ 'register() is disallowed inside a cross-origin fenced frame');
+}, 'periodic sync register in service worker');
+
+promise_test(async (t) => {
+ const params = {
+ method: 'getTags',
+ periodic: true
+ };
+ const frame_url = generateFencedFrameUrl(params, true)
+ const expected_message = periodic_background_sync_message;
+ await background_sync_test(
+ frame_url,
+ expected_message,
+ 'getTags() is disallowed inside a same-origin fenced frame');
+
+ const cross_origin_frame_url = getRemoteOriginURL(frame_url);
+ await background_sync_test(
+ cross_origin_frame_url,
+ expected_message,
+ 'getTags() is disallowed inside a cross-origin fenced frame');
+}, 'periodic sync getTags in service worker');
+
+promise_test(async (t) => {
+ const params = {
+ method: 'unregister',
+ periodic: true
+ };
+ const frame_url = generateFencedFrameUrl(params, true)
+ const expected_message = periodic_background_sync_message;
+ await background_sync_test(
+ frame_url,
+ expected_message,
+ 'unregister() is disallowed inside a same-origin fenced frame');
+
+ const cross_origin_frame_url = getRemoteOriginURL(frame_url);
+ await background_sync_test(
+ cross_origin_frame_url,
+ expected_message,
+ 'unregister() is disallowed inside a cross-origin fenced frame');
+}, 'periodic sync unregister in service worker');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/badging.https.html b/testing/web-platform/tests/fenced-frame/badging.https.html
new file mode 100644
index 0000000000..93683e096c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/badging.https.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<title>Test Badging API</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 src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+const createRemoteFunctionForServiceWorkerTest = () => {
+ return async (method, service_worker_url) => {
+ const getController = () => {
+ if (navigator.serviceWorker.controller) {
+ return navigator.serviceWorker.controller;
+ }
+ return new Promise(resolve => {
+ navigator.serviceWorker.addEventListener('controllerchange', () => {
+ resolve(navigator.serviceWorker.controller);
+ });
+ });
+ };
+
+ await navigator.serviceWorker.register(
+ service_worker_url, { scope: location.href });
+ const ctrl = await getController();
+
+ return new Promise(resolve => {
+ ctrl.postMessage(method);
+ navigator.serviceWorker.onmessage = e => {
+ resolve(e.data.name);
+ }
+ });
+ }
+}
+
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ const error_name = await frame.execute(() => {
+ return navigator.setAppBadge(1).catch(e => e.name);
+ });
+ assert_equals(error_name,
+ "NotAllowedError",
+ "The Badge API should cause exception in a fencedfarme");
+}, 'setAppBadge should fail inside a fenced frame');
+
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ const error_name = await frame.execute(() => {
+ return navigator.clearAppBadge().catch(e => e.name);
+ });
+ assert_equals(error_name,
+ "NotAllowedError",
+ "The Badge API should cause exception in a fencedfarme");
+}, 'clearAppBadge should fail inside a fenced frame');
+
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ const error_name = await frame.execute(
+ createRemoteFunctionForServiceWorkerTest(),
+ ['setAppBadge', 'badging-sw.js']);
+ assert_equals(error_name, "NotAllowedError",
+ "The Badge API should cause exception from a service worker " +
+ "in a fencedfarme");
+}, 'setAppBadge should fail from a service worker inside a fenced frame');
+
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ const error_name = await frame.execute(
+ createRemoteFunctionForServiceWorkerTest(),
+ ['clearAppBadge', 'badging-sw.js']);
+ assert_equals(error_name, "NotAllowedError",
+ "The Badge API should cause exception from a service worker " +
+ "in a fencedfarme");
+}, 'clearAppBadge should fail from a service worker inside a fenced frame');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/battery_status.https.html b/testing/web-platform/tests/fenced-frame/battery_status.https.html
new file mode 100644
index 0000000000..d7e1dc0284
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/battery_status.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>Battery status API test</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ const uuid = token();
+ const frame = attachFencedFrame(
+ generateURL(`resources/get_battery.html`, [uuid]));
+ const result = await nextValueFromServer(uuid);
+ assert_equals(result, "NotAllowedError",
+ "battery status API should cause exception");
+}, 'battery status should not be read in the fenced frame.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/before-unload.https.html b/testing/web-platform/tests/fenced-frame/before-unload.https.html
new file mode 100644
index 0000000000..d924b1d1a4
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/before-unload.https.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>Test the before unload event</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async () => {
+ const before_unload_key = token();
+
+ const frame_url = generateURL("resources/before-unload-inner.html",
+ [before_unload_key]);
+
+ attachFencedFrame(frame_url);
+
+ const result = await nextValueFromServer(before_unload_key);
+ assert_equals(result, "Loaded the next url in a fenced frame",
+ "A fenced frame should not fire the before unload event.");
+}, "before unload event in fenced frames");
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/can-load-api.https.html b/testing/web-platform/tests/fenced-frame/can-load-api.https.html
new file mode 100644
index 0000000000..f9996dd5e9
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/can-load-api.https.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<title>Test canLoadOpaqueURL API</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+async function runTest(expected_result, generator_api, attribute_list, header_list, use_fencedframe=false) {
+ const frame = use_fencedframe ?
+ await attachFencedFrameContext({generator_api: generator_api, attributes:attribute_list, headers:header_list}) :
+ await attachIFrameContext({generator_api, generator_api, attributes:attribute_list, headers:header_list});
+ await frame.execute(async (expected_result, attribute_list) => {
+ assert_equals(navigator.canLoadAdAuctionFencedFrame(), expected_result,
+ "A frame with attributes " + attribute_list + " should return " +
+ expected_result + " for canLoadOpaqueURL.");
+ }, [expected_result, attribute_list]);
+}
+
+promise_test(async(t) => {
+ assert_true(navigator.canLoadAdAuctionFencedFrame());
+}, 'canLoadOpaqueURL called on a page that can load a FF should return true');
+
+promise_test(async(t) => {
+ await runTest(true, "sharedstorage",
+ [["width", "300"],
+ ["height", "200"]],
+ [], use_fencedframe=true);
+}, 'canLoadOpaqueURL returns true inside an opaque-ads fenced frame');
+
+promise_test(async(t) => {
+ await runTest(false, "default", [], [], use_fencedframe=true);
+}, 'canLoadOpaqueURL returns false inside an default fenced frame');
+
+promise_test(async(t) => {
+ await runTest(true, "default", [], [["Content-Security-Policy", "fenced-frame-src *"]]);
+ await runTest(true, "default", [], [["Content-Security-Policy", "fenced-frame-src https:"]]);
+ await runTest(true, "default", [], [["Content-Security-Policy", "fenced-frame-src https://*:*"]]);
+}, 'canLoadOpaqueURL returns true for all 3 fenced-frame-src allowed values');
+
+promise_test(async(t) => {
+ await runTest(true, "default", [], [["Content-Security-Policy", "fenced-frame-src *; frame-src 'self'"]]);
+ await runTest(false, "default", [], [["Content-Security-Policy", "fenced-frame-src 'self'; frame-src *"]]);
+ await runTest(true, "default", [], [["Content-Security-Policy", "child-src 'self'; fenced-frame-src https:"]]);
+ await runTest(false, "default", [], [["Content-Security-Policy", "child-src *; fenced-frame-src 'self'"]]);
+}, 'canLoadOpaqueURL ignores fallback CSPs');
+
+promise_test(async(t) => {
+ await runTest(true, "default", [], [["Content-Security-Policy", "img-src 'none';"]]);
+ await runTest(true, "default", [], [["Content-Security-Policy", "font-src 'none';"]]);
+}, 'canLoadOpaqueURL ignores unrelated CSPs');
+
+promise_test(async(t) => {
+ const iframe = attachIFrame("resources/dummy.html");
+ const iframe_ff_class = iframe.contentWindow.HTMLFencedFrameElement;
+
+ // Sanity check to make sure the function returns true as we expect it to
+ // before we remove the frame.
+ assert_true(iframe_ff_class.canLoadOpaqueURL());
+
+ // The one variable we're changing is whether the frame is attached or not.
+ iframe.remove();
+ assert_false(iframe_ff_class.canLoadOpaqueURL());
+}, 'canLoadOpaqueURL returns false in a detached frame');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/change-src-attribute-after-config-installation-does-not-trigger-navigation.https.html b/testing/web-platform/tests/fenced-frame/change-src-attribute-after-config-installation-does-not-trigger-navigation.https.html
new file mode 100644
index 0000000000..7ad73d3a4b
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/change-src-attribute-after-config-installation-does-not-trigger-navigation.https.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/utils.js"></script>
+<title>Test changing a fenced frame's src attribute when there has been a config
+with url installed already does not trigger navigation.</title>
+
+<body>
+
+<script>
+function getTimeoutPromise(t) {
+ return new Promise(resolve =>
+ t.step_timeout(() => resolve("NOT LOADED"), 2000));
+}
+
+promise_test(async (t) => {
+ const fenced_frame_loaded_key = token();
+
+ const url = generateURL(
+ 'resources/fenced-frame-loaded.html', [fenced_frame_loaded_key]);
+ const url_string = url.toString();
+
+ // Create a fenced frame and install an inner config constructed with an url.
+ const fenced_frame = document.createElement('fencedframe');
+ const config = new FencedFrameConfig(url_string);
+ fenced_frame.config = config;
+ document.body.append(fenced_frame);
+
+ // Installing a config to the fenced frame triggers navigation.
+ const load_expected = "fenced frame loaded";
+ const load_actual = await nextValueFromServer(fenced_frame_loaded_key);
+ assert_equals(load_actual, load_expected,
+ "Fenced frame successfully loaded.");
+
+ const src_key = token();
+ const src_url = generateURL(
+ 'resources/fenced-frame-loaded.html', [src_key]);
+
+ // Changing the src attribute, should not trigger navigation.
+ fenced_frame.src = src_url;
+ const src_loaded_promise = nextValueFromServer(src_key);
+ const src_loaded_result = await Promise.any([src_loaded_promise,
+ getTimeoutPromise(t)]);
+ assert_equals(src_loaded_result, "NOT LOADED");
+
+}, 'Changing the src attribute of a fenced frame when a config with url',
+ 'has already been installed does not trigger navigation.');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/client-hints-meta.https.html b/testing/web-platform/tests/fenced-frame/client-hints-meta.https.html
new file mode 100644
index 0000000000..3a4acf1b26
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/client-hints-meta.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<meta http-equiv="Accept-CH"
+ content="sec-ch-viewport-width, sec-ch-ua-reduced"/>
+<meta http-equiv="Feature-Policy"
+ content="ch-viewport-width *, ch-ua-reduced *"/>
+<title>Client hints in fenced frames test</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 src="resources/utils.js"></script>
+<script src="resources/client-hints-common.sub.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<body>
+<script>
+promise_test(async () => {
+ const key = token();
+ const url = generateURL('resources/client-hints-meta-inner.sub.https.html', [key]);
+ const remote_url = getRemoteOriginURL(url);
+ attachFencedFrame(remote_url);
+ const result = JSON.parse(await nextValueFromServer(key));
+
+ // We should not see client hints for the fenced frame root or subframes //
+ // within the fenced frame tree due to the reject-all permission policy used by
+ // fenced frames.
+ const headers = ['root-fenced-frame-headers', 'iframe-headers'];
+ const hints = [
+ 'sec-ch-viewport-width', 'sec-ch-ua-reduced', 'sec-ch-ua-mobile',
+ ];
+ headers.forEach(header => {
+ hints.forEach(hint => {
+ assert_equals(result[header][hint], '');
+ });
+ });
+}, 'fenced frames not send client hints');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/client-hints.https.html b/testing/web-platform/tests/fenced-frame/client-hints.https.html
new file mode 100644
index 0000000000..fc7b8db08d
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/client-hints.https.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>Client hints in fenced frames test</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 src="resources/utils.js"></script>
+<script src="resources/client-hints-common.sub.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<body>
+<script>
+promise_test(async () => {
+ const key = token();
+ const url = generateURL('resources/client-hints-inner.sub.https.html', [key]);
+ const remote_url = getRemoteOriginURL(url);
+ attachFencedFrame(remote_url);
+ const result = JSON.parse(await nextValueFromServer(key));
+
+ // We should not see client hints for the fenced frame root or subframes //
+ // within the fenced frame tree due to the reject-all permission policy used by
+ // fenced frames.
+ const headers = ['root-fenced-frame-headers', 'iframe-headers'];
+ const hints = [
+ 'sec-ch-viewport-width', 'sec-ch-ua-reduced', 'sec-ch-ua-mobile',
+ ];
+ headers.forEach(header => {
+ hints.forEach(hint => {
+ assert_equals(result[header][hint], '');
+ });
+ });
+}, 'fenced frames not send client hints');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/client-hints.https.html.headers b/testing/web-platform/tests/fenced-frame/client-hints.https.html.headers
new file mode 100644
index 0000000000..56ce611418
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/client-hints.https.html.headers
@@ -0,0 +1,3 @@
+Accept-CH: sec-ch-viewport-width, sec-ch-ua-reduced
+Feature-Policy: ch-viewport-width *, ch-ua-reduced *
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/fenced-frame/compute-pressure.https.html b/testing/web-platform/tests/fenced-frame/compute-pressure.https.html
new file mode 100644
index 0000000000..81091afc8e
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/compute-pressure.https.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Verify that Compute Pressure API from a fenced frame is blocked</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ const frame = attachFencedFrameContext({
+ headers: [["Permissions-Policy", "compute-pressure=*"]]
+ });
+ const result = await frame.execute(async () => {
+ try {
+ const observer = new PressureObserver(() => {});
+ await observer.observe('cpu');
+ return 'observation succeeded';
+ } catch (e) {
+ if (e.name == 'NotAllowedError' &&
+ e.message.includes(`Access to the feature "compute pressure" is `
+ + "disallowed by permissions policy.")) {
+ return 'observation failed';
+ } else {
+ return `observation failed with unknown error - ${e.name}: ${e.message}`;
+ }
+ }
+ });
+ assert_equals(result, 'observation failed',
+ 'PressureObserver.observe() fails in a fenced frame.');
+}, 'PressureObserver.observe() fails in a fenced frame.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/config-cross-origin-apis.https.html b/testing/web-platform/tests/fenced-frame/config-cross-origin-apis.https.html
new file mode 100644
index 0000000000..2d6b97e1aa
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/config-cross-origin-apis.https.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<title>Test default permission policy features gating (*)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const key = token();
+
+ const fencedframe = attachFencedFrame(
+ await generateURNFromFledge(
+ "resources/config-cross-origin-apis-inner.https.html",
+ [key]));
+
+ // The fenced frame will send its attribution reporting result and then
+ // attempt to redirect to a remote origin page.
+ const resp = await nextValueFromServer(key);
+ assert_equals(resp, "0",
+ "The call to getNestedConfigs() should not have returned anything.");
+}, 'A fenced frame that navigates itself to a cross origin page loses ' +
+ 'window.fence API access.');
+
+promise_test(async(t) => {
+ const key = token();
+
+ const fencedframe = attachFencedFrame(
+ await generateURNFromFledge(
+ "resources/config-embed-cross-origin-iframe.https.html",
+ [key]));
+
+ const resp = await nextValueFromServer(key);
+ assert_equals(resp, "0",
+ "The call to getNestedConfigs() should not have returned anything.");
+}, 'A cross-origin iframe inside a fenced frame does not get ' +
+ 'window.fence API access.');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/config-installation-triggers-navigation-of-navigated-fenced-frame.https.html b/testing/web-platform/tests/fenced-frame/config-installation-triggers-navigation-of-navigated-fenced-frame.https.html
new file mode 100644
index 0000000000..74f810f6d2
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/config-installation-triggers-navigation-of-navigated-fenced-frame.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/utils.js"></script>
+<title>Test installing an config to a navigated fenced frame triggers
+ navigation.
+</title>
+
+<body>
+
+<script>
+promise_test(async () => {
+ const fenced_frame_loaded_key = token();
+
+ // Create a fenced frame.
+ const fenced_frame = document.createElement('fencedframe');
+ document.body.append(fenced_frame);
+
+ // Create an inner config.
+ const config_navigation_key = token();
+ const config_url = generateURL('resources/fenced-frame-loaded.html',
+ [config_navigation_key]);
+ fenced_frame.config = new FencedFrameConfig(config_url.toString());
+
+ // Installing an inner config to the fenced frame triggers navigation.
+ const config_navigation_expected = "fenced frame loaded";
+ const config_navigation_actual =
+ await nextValueFromServer(config_navigation_key);
+ assert_equals(config_navigation_actual, config_navigation_expected,
+ "Fenced frame successfully navigated by installing an inner config.");
+
+}, 'Installing an inner config to a fenced frame that has navigated triggers',
+ 'navigation.');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/config-installation-triggers-navigation.https.html b/testing/web-platform/tests/fenced-frame/config-installation-triggers-navigation.https.html
new file mode 100644
index 0000000000..6a7238da05
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/config-installation-triggers-navigation.https.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/utils.js"></script>
+<title>Test fenced frame config installation triggers navigation.</title>
+
+<body>
+
+<script>
+promise_test(async () => {
+ const fenced_frame_loaded_key = token();
+
+ const url = generateURL(
+ 'resources/fenced-frame-loaded.html', [fenced_frame_loaded_key]);
+ const url_string = url.toString();
+
+ // Create a fenced frame and install an inner config constructed with an url.
+ const fenced_frame = document.createElement('fencedframe');
+ const config = new FencedFrameConfig(url_string);
+ fenced_frame.config = config;
+ document.body.append(fenced_frame);
+
+ // Installing an inner config to the fenced frame triggers navigation.
+ const load_expected = "fenced frame loaded";
+ const load_actual = await nextValueFromServer(fenced_frame_loaded_key);
+ assert_equals(load_actual, load_expected,
+ "Fenced frame successfully loaded.");
+
+}, 'Installing an inner config to a fenced frame triggers navigation.');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/config-with-empty-url-installation-unloads-navigated-fenced-frame.https.html b/testing/web-platform/tests/fenced-frame/config-with-empty-url-installation-unloads-navigated-fenced-frame.https.html
new file mode 100644
index 0000000000..13e6904cf9
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/config-with-empty-url-installation-unloads-navigated-fenced-frame.https.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/utils.js"></script>
+<title>Test installing a config with empty url to a navigated fenced frame
+ unloads the original document</title>
+
+<body>
+
+<script>
+promise_test(async () => {
+ const fenced_frame_loaded_key = token();
+
+ // Create a fenced frame and install a config constructed with an empty url.
+ const fenced_frame = document.createElement('fencedframe');
+ document.body.append(fenced_frame);
+
+ // Specify the fenced frame's src attribute to an url.
+ const url = generateURL('resources/fenced-frame-loaded.html',
+ [fenced_frame_loaded_key]);
+ fenced_frame.config = new FencedFrameConfig(url);
+
+ // The fenced frame should navigate to the src url.
+ const load_expected = "fenced frame loaded";
+ const load_actual = await nextValueFromServer(fenced_frame_loaded_key);
+ assert_equals(load_actual, load_expected,
+ "Fenced frame successfully loaded by specifying its src attribute.");
+
+ // Create a config with an empty url.
+ const empty_url_config = new FencedFrameConfig('');
+ fenced_frame.config = empty_url_config;
+
+ // Installing a config with an empty url to the fenced frame should unload the
+ // original document.
+ const server_value = await readValueFromServer(fenced_frame_loaded_key);
+ assert_false(server_value.status);
+
+}, 'Installing a config with empty url to a navigated fenced unloads ',
+ 'the original document.');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/consume-user-activation.https.html b/testing/web-platform/tests/fenced-frame/consume-user-activation.https.html
new file mode 100644
index 0000000000..e4ad20d17b
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/consume-user-activation.https.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<title>Test that user activation propagation is fenced.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+// Simulate a click in frame context `frame`.
+async function click(frame) {
+ var actions = new test_driver.Actions();
+ await actions.pointerMove(0, 0, {origin: frame})
+ .pointerDown()
+ .pointerUp()
+ .send();
+}
+
+assert_activations = (sticky_activation, transient_activation, frame_name) => {
+ if (sticky_activation) {
+ assert_true(navigator.userActivation.hasBeenActive,
+ frame_name + " has been activated.");
+ } else {
+ assert_false(navigator.userActivation.hasBeenActive,
+ frame_name + " has not been activated yet.");
+ }
+ if (transient_activation) {
+ assert_true(navigator.userActivation.isActive,
+ frame_name + " is currently active.");
+ } else {
+ assert_false(navigator.userActivation.isActive,
+ frame_name + " is not currently active.");
+ }
+};
+
+promise_test(async () => {
+ // This test checks that consumption of transient user activations is
+ // fenced, i.e. that when the top-level embedder and a fenced frame are
+ // both active, and one of them performs an operation that consumes
+ // transient user activation (sets `navigator.userActivation.isActive`
+ // to `false`), it doesn't affect the other frame.
+
+ // Given a top-level frame A and fenced frame B, the structure of the
+ // test is as follows:
+ // - Activate both A and B with a click
+ // - Consume A's transient user activation with `window.open`
+ // - Check that A is inactive and B is active
+ // - Reactivate A with a click
+ // - Consume B's transient user activation with `window.open`
+ // - Check that A is active and B is inactive
+
+ const B = attachFencedFrameContext();
+
+ // Check that both frames are inactive after loading.
+ assert_activations(false, false, "A");
+ await B.execute(assert_activations, [false, false, "B"]);
+
+ // Send a click to activate the top-level frame, and check user activation.
+ await click(document.documentElement);
+ assert_activations(true, true, "A");
+ await B.execute(assert_activations, [false, false, "B"]);
+
+ // Send a click to activate the fenced frame, and check user activation.
+ await click(B.element);
+ assert_activations(true, true, "A");
+ await B.execute(assert_activations, [true, true, "B"]);
+
+ // Open a window to consume the top-level transient user activation.
+ window.open('about:blank');
+
+ // Check that it consumed the navigation in only the top-level frame.
+ assert_activations(true, false, "A");
+ await B.execute(assert_activations, [true, true, "B"]);
+
+ // Reactivate this frame and check the user activation status.
+ await click(document.documentElement);
+ assert_activations(true, true, "A");
+ await B.execute(assert_activations, [true, true, "B"]);
+
+ // Open a window in the fenced frame to consume its transient activation.
+ await B.execute(() => {
+ window.open('about:blank');
+ });
+
+ // Check that B's transient user activation was consumed.
+ assert_activations(true, true, "A");
+ await B.execute(assert_activations, [true, false, "B"]);
+
+}, 'user-activation');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/content-index.https.html b/testing/web-platform/tests/fenced-frame/content-index.https.html
new file mode 100644
index 0000000000..99af848492
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/content-index.https.html
@@ -0,0 +1,150 @@
+<!DOCTYPE html>
+<title>Test Content Index API</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 src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+const id = 'fenced-frame-id';
+
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ try {
+ await frame.execute(async (id) => {
+ navigator.serviceWorker.register(
+ "empty-worker.js", { scope: location.href });
+ const registration = await navigator.serviceWorker.ready;
+ return registration.index.add({
+ id,
+ title: 'same title',
+ description: 'same description',
+ url: 'resources/'
+ });
+ }, [id]);
+ assert_unreached('index.add executed without error; want error');
+ } catch(e) {
+ assert_equals(e.message, "Failed to execute 'add' on 'ContentIndex': " +
+ "ContentIndex is not allowed in fenced frames.")
+ }
+}, 'index.add should fail inside a fenced frame');
+
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ try {
+ await frame.execute(async (id) => {
+ navigator.serviceWorker.register(
+ "empty-worker.js", { scope: location.href });
+ const registration = await navigator.serviceWorker.ready;
+ return registration.index.delete(id);
+ }, [id]);
+ assert_unreached('index.delete executed without error; want error');
+ } catch(e) {
+ assert_equals(e.message, "Failed to execute 'delete' on 'ContentIndex': " + "ContentIndex is not allowed in fenced frames.");
+ }
+}, 'index.delete should fail inside a fenced frame');
+
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ try {
+ await frame.execute(async () => {
+ navigator.serviceWorker.register(
+ "empty-worker.js", { scope: location.href });
+ const registration = await navigator.serviceWorker.ready;
+ return registration.index.getAll();
+ });
+ assert_unreached('index.getAll executed without error; want error');
+ } catch(e) {
+ assert_equals(e.message, "Failed to execute 'getAll' on 'ContentIndex': " + "ContentIndex is not allowed in fenced frames.");
+ }
+}, 'index.getAll should fail inside a fenced frame');
+
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ const message = await frame.execute(async () => {
+ const getController = () => {
+ if (navigator.serviceWorker.controller) {
+ return navigator.serviceWorker.controller;
+ }
+ return new Promise(resolve => {
+ navigator.serviceWorker.addEventListener('controllerchange', () => {
+ resolve(navigator.serviceWorker.controller);
+ });
+ });
+ };
+
+ navigator.serviceWorker.register(
+ "content-index-sw.js", { scope: location.href });
+ return new Promise(async resolve => {
+ const ctrl = await getController();
+ ctrl.postMessage('add');
+ navigator.serviceWorker.onmessage = e => {
+ resolve(e.data);
+ }
+ });
+ });
+ assert_equals(message, "Failed to execute 'add' on 'ContentIndex': " +
+ "ContentIndex is not allowed in fenced frames.");
+}, 'index.add should fail from the service worker inside a fenced frame');
+
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ const message = await frame.execute(async () => {
+ const getController = () => {
+ if (navigator.serviceWorker.controller) {
+ return navigator.serviceWorker.controller;
+ }
+ return new Promise(resolve => {
+ navigator.serviceWorker.addEventListener('controllerchange', () => {
+ resolve(navigator.serviceWorker.controller);
+ });
+ });
+ };
+
+ navigator.serviceWorker.register(
+ "content-index-sw.js", { scope: location.href });
+ return new Promise(async resolve => {
+ const ctrl = await getController();
+ ctrl.postMessage('delete');
+ navigator.serviceWorker.onmessage = e => {
+ resolve(e.data);
+ }
+ });
+ });
+ assert_equals(message, "Failed to execute 'delete' on 'ContentIndex': " +
+ "ContentIndex is not allowed in fenced frames.");
+}, 'index.delete should fail from the service worker inside a fenced frame');
+
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ const message = await frame.execute(async () => {
+ const getController = () => {
+ if (navigator.serviceWorker.controller) {
+ return navigator.serviceWorker.controller;
+ }
+ return new Promise(resolve => {
+ navigator.serviceWorker.addEventListener('controllerchange', () => {
+ resolve(navigator.serviceWorker.controller);
+ });
+ });
+ };
+
+ navigator.serviceWorker.register(
+ "content-index-sw.js", { scope: location.href });
+ return new Promise(async resolve => {
+ const ctrl = await getController();
+ ctrl.postMessage('getAll');
+ navigator.serviceWorker.onmessage = e => {
+ resolve(e.data);
+ }
+ });
+ });
+ assert_equals(message, "Failed to execute 'getAll' on 'ContentIndex': " +
+ "ContentIndex is not allowed in fenced frames.");
+}, 'index.getAll should fail from the service worker inside a fenced frame');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/coop-bcg-swap.https.html b/testing/web-platform/tests/fenced-frame/coop-bcg-swap.https.html
new file mode 100644
index 0000000000..5a414fdfa1
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/coop-bcg-swap.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<title>Test window.name after bcg swap</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ const fenced_frame = attachFencedFrameContext();
+
+ // This test verifies that COOP is not enabled in fenced frames. To do so, we
+ // set the fenced frame's `window.name` and navigate it to a cross-origin page
+ // with the `Cross-Origin-Opener-Policy: same-origin` response header.
+ await fenced_frame.execute(() => {
+ const remote_origin = get_host_info().REMOTE_ORIGIN;
+ window.name = "test";
+ const remote_url = new URL(location.pathname + location.search, remote_origin);
+
+ let existing_pipe_query = remote_url.searchParams.get('pipe');
+ const all_pipes = existing_pipe_query.split('|');
+ all_pipes.push('header(Cross-Origin-Opener-Policy, same-origin)');
+
+ remote_url.searchParams.set('pipe', all_pipes.join('|'));
+
+ window.executor.suspend(() => {
+ location.href = remote_url;
+ });
+ });
+
+ // Verify that the fenced frame's `window.name` is still there
+ await fenced_frame.execute(() => {
+ assert_equals(window.name, 'test');
+ });
+
+}, "window.name after bcg swap");
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/create-credential.https.html b/testing/web-platform/tests/fenced-frame/create-credential.https.html
new file mode 100644
index 0000000000..15f0558e52
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/create-credential.https.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>Test WebAuthn navigator.credentials.create()</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async () => {
+ const key = token();
+ attachFencedFrame(
+ generateURL('resources/create-credential-inner.https.html', [key]));
+ // Get the result for the fenced frame.
+ const fenced_frame_result = await nextValueFromServer(key);
+ assert_equals(
+ fenced_frame_result,
+ 'createCredential failed',
+ 'credentials.create should fail on fenced frame');
+}, 'navigator.credentials.create');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/create-in-sandbox-and-adopt-outside-sandbox.https.html b/testing/web-platform/tests/fenced-frame/create-in-sandbox-and-adopt-outside-sandbox.https.html
new file mode 100644
index 0000000000..4b4817b863
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/create-in-sandbox-and-adopt-outside-sandbox.https.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Test fenced frame sandbox adoption</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+<script>
+promise_test(async() => {
+ const fenced_frame_loaded_key = token();
+
+ const iframe = document.createElement('iframe');
+ iframe.setAttribute('sandbox', 'allow-same-origin');
+ document.body.append(iframe);
+
+ const inner_document = iframe.contentDocument;
+ const fenced_frame = inner_document.createElement('fencedframe');
+ // The `inner_document` is not suitable to host a fenced frame because its
+ // sandbox flags are too strict.
+ inner_document.body.append(fenced_frame);
+
+ // Per https://dom.spec.whatwg.org/#concept-node-append, this will adopt the
+ // inner fenced frame into the outer (main frame) document.
+ document.body.append(fenced_frame);
+ fenced_frame.config =
+ new FencedFrameConfig(generateURL(
+ 'resources/fenced-frame-loaded.html', [fenced_frame_loaded_key]));
+ const response = await nextValueFromServer(fenced_frame_loaded_key);
+ assert_equals(response, "fenced frame loaded",
+ "The inner frame should be loaded.");
+}, "Adopting a fenced frame from a too-strict document to a suitable " +
+ "document leaves the frame in a functional state");
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/csp-allowed.https.html b/testing/web-platform/tests/fenced-frame/csp-allowed.https.html
new file mode 100644
index 0000000000..8c002bc8a9
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/csp-allowed.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<title>Test opaque fenced frame navigations with allowed CSP</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+for (const resolve_to_config of [true, false]) {
+ const allowedCSPs = ["*", "https:", "https://*:*"];
+ allowedCSPs.forEach((csp) => {
+ promise_test(async() => {
+ setupCSP(csp);
+
+ const key = token();
+ window.addEventListener('securitypolicyviolation', function(e) {
+ // Write to the server even though the listener is in the same file in
+ // the test below.
+ writeValueToServer(key, e.violatedDirective + ";" + e.blockedURI);
+ }, {once: true});
+
+ attachFencedFrame(await runSelectURL("resources/embeddee.html",
+ [key], resolve_to_config));
+
+ const result = await nextValueFromServer(key);
+ assert_equals(result, "PASS",
+ "The fenced frame should load for CSP fenced-frame-src " + csp);
+ }, "Fenced frame loaded for CSP fenced-frame-src " + csp + " using " +
+ (resolve_to_config ? "config" : "urn:uuid"));
+
+ promise_test(async() => {
+ setupCSP(csp);
+ assert_true(navigator.canLoadAdAuctionFencedFrame());
+ }, "Opaque-ads can load API returns true for " + csp + " using " +
+ (resolve_to_config ? "config" : "urn:uuid"));
+ });
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/csp-blocked.https.html b/testing/web-platform/tests/fenced-frame/csp-blocked.https.html
new file mode 100644
index 0000000000..3826fdd7f4
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/csp-blocked.https.html
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<title>Test opaque fenced frame navigations with disallowed CSP blocked</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+for (const resolve_to_config of [true, false]) {
+ const blockedCSPs = ["'none'", "'self'", "data:", "https://*", "https://*:80",
+ "https://b.test:*"];
+ blockedCSPs.forEach((csp) => {
+ promise_test(async() => {
+ setupCSP(csp);
+
+ const key = token();
+ window.addEventListener('securitypolicyviolation', function(e) {
+ // Write to the server even though the listener is in the same file in
+ // the test below.
+ writeValueToServer(key, e.violatedDirective + ";" + e.blockedURI);
+ }, {once: true});
+
+ attachFencedFrame(await runSelectURL("resources/embeddee.html",
+ [key], resolve_to_config));
+
+ const result = await nextValueFromServer(key);
+ assert_equals(result, "fenced-frame-src;",
+ "The fenced frame should not load for CSP fenced-frame-src " + csp);
+ }, "Fenced frame blocked for CSP fenced-frame-src " + csp + " using " +
+ (resolve_to_config ? "config" : "urn:uuid"));
+
+ promise_test(async() => {
+ setupCSP(csp);
+ assert_false(navigator.canLoadAdAuctionFencedFrame());
+ }, "Opaque-ads can load API returns false for " + csp + " using " +
+ (resolve_to_config ? "config" : "urn:uuid"));
+ });
+
+ promise_test(async() => {
+ setupCSP("*", "'self'");
+
+ const key = token();
+ window.addEventListener('securitypolicyviolation', function(e) {
+ // Write to the server even though the listener is in the same file in
+ // the test below.
+ writeValueToServer(key, e.violatedDirective + ";" + e.blockedURI);
+ }, {once: true});
+
+ attachFencedFrame(await runSelectURL("resources/embeddee.html",
+ [key], resolve_to_config));
+
+ const result = await nextValueFromServer(key);
+ assert_equals(result, "fenced-frame-src;",
+ "The fenced frame should not load for CSP frame-src 'self' even if " +
+ "another CSP allows loading a fenced frame.");
+
+ // Test the canLoadOpaqueURL API to ensure it arrives at the same result.
+ assert_false(navigator.canLoadAdAuctionFencedFrame());
+ }, "Fenced frame not loaded using " +
+ (resolve_to_config ? "config" : "urn:uuid") +
+ " if any of CSPs in place disallow loading");
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/csp-fenced-frame-src-allowed.https.html b/testing/web-platform/tests/fenced-frame/csp-fenced-frame-src-allowed.https.html
new file mode 100644
index 0000000000..cf603c29a6
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/csp-fenced-frame-src-allowed.https.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta http-equiv="Content-Security-Policy" content="fenced-frame-src 'self'">
+<title>Test Content-Security-Policy fenced-frame-src</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+<script>
+const key = token();
+
+promise_test(async () => {
+ attachFencedFrame(generateURL(
+ "resources/csp-fenced-frame-src-allowed-inner.html",
+ [key]));
+ const result = await nextValueFromServer(key);
+ assert_equals(result, "loaded",
+ "The fenced frame is loaded as expected");
+}, "csp-fenced-frame-src-allowed");
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/csp-fenced-frame-src-blocked.https.html b/testing/web-platform/tests/fenced-frame/csp-fenced-frame-src-blocked.https.html
new file mode 100644
index 0000000000..46f8778d47
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/csp-fenced-frame-src-blocked.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<meta http-equiv="Content-Security-Policy" content="fenced-frame-src 'none'">
+<title>Test Content-Security-Policy fenced-frame-src</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+<script>
+const key = token();
+
+window.addEventListener('securitypolicyviolation', function(e) {
+ // Write to the server even though the listener is in the same file in the
+ // test below.
+ writeValueToServer(key, e.violatedDirective + ";" + e.blockedURI);
+});
+
+promise_test(async () => {
+ attachFencedFrame(generateURL(
+ "resources/csp-fenced-frame-src-blocked-inner.html",
+ [key]));
+ const result = await nextValueFromServer(key);
+
+ const expected_blocked_uri = generateURL(
+ "resources/csp-fenced-frame-src-blocked-inner.html",
+ [key]).toString();
+ assert_equals(result, "fenced-frame-src;" + expected_blocked_uri,
+ "The fenced frame is blocked because of CSP violation");
+}, "csp-fenced-frame-src-blocked");
+
+promise_test(async () => {
+ assert_false(navigator.canLoadAdAuctionFencedFrame());
+}, "fenced-frame-src none is taken into account with " +
+ "navigator.canLoadAdAuctionFencedFrame");
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/csp-frame-src-allowed.https.html b/testing/web-platform/tests/fenced-frame/csp-frame-src-allowed.https.html
new file mode 100644
index 0000000000..08c51fc3ac
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/csp-frame-src-allowed.https.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta http-equiv="Content-Security-Policy" content="frame-src 'self'">
+<title>Test Content-Security-Policy fenced-frame-src falling back to frame-src</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+<script>
+const key = token();
+
+promise_test(async () => {
+ attachFencedFrame(generateURL(
+ "resources/csp-frame-src-allowed-inner.html",
+ [key]));
+ const result = await nextValueFromServer(key);
+ assert_equals(result, "loaded",
+ "The fenced frame is loaded as expected");
+}, "csp-frame-src-allowed");
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/csp-frame-src-blocked.https.html b/testing/web-platform/tests/fenced-frame/csp-frame-src-blocked.https.html
new file mode 100644
index 0000000000..88e4a8b89f
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/csp-frame-src-blocked.https.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<meta http-equiv="Content-Security-Policy" content="img-src 'self' https: https://*:*">
+<meta http-equiv="Content-Security-Policy" content="frame-src 'none'">
+<title>Test Content-Security-Policy fenced-frame-src falling back to frame-src</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+<script>
+const key = token();
+
+window.addEventListener('securitypolicyviolation', function(e) {
+ // Write to the server even though the listener is in the same file in the
+ // test below.
+ writeValueToServer(key, e.violatedDirective + ";" + e.blockedURI);
+});
+
+promise_test(async () => {
+ attachFencedFrame(generateURL(
+ "resources/csp-frame-src-blocked-inner.html",
+ [key]));
+ const result = await nextValueFromServer(key);
+
+ const expected_blocked_uri = generateURL(
+ "resources/csp-frame-src-blocked-inner.html", [key]).toString();
+ assert_equals(result, "fenced-frame-src;" + expected_blocked_uri,
+ "The fenced frame is blocked because of CSP violation");
+}, "csp-frame-src-blocked");
+
+promise_test(async () => {
+ assert_false(navigator.canLoadAdAuctionFencedFrame());
+}, "frame-src none is taken into account with navigator.canLoadAdAuctionFencedFrame");
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/csp-transparent-url.https.html b/testing/web-platform/tests/fenced-frame/csp-transparent-url.https.html
new file mode 100644
index 0000000000..c1c815d49e
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/csp-transparent-url.https.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<title>Test transparent url navigated in fenced frame interacting with CSP</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+function setupCSP(csp) {
+ let meta = document.createElement('meta');
+ meta.httpEquiv = "Content-Security-Policy";
+ meta.content = "fenced-frame-src " + csp;
+ document.head.appendChild(meta);
+}
+
+const allowedCSPs = ["*", "https:", "'self'"];
+allowedCSPs.forEach((csp) => {
+ promise_test(async(t) => {
+ setupCSP(csp);
+
+ t.step_timeout(t.unreached_func(
+ "The fenced frame should load for CSP fenced-frame-src " + csp), 3000);
+
+ const fencedframe = attachFencedFrameContext();
+ await fencedframe.execute(() => {});
+ }, "Fenced frame loaded for CSP fenced-frame-src " + csp);
+});
+
+const blockedCSPs = ["'none'"];
+blockedCSPs.forEach((csp) => {
+ promise_test(async(t) => {
+ setupCSP(csp);
+
+ const csp_violation = new Promise(resolve => {
+ window.addEventListener("securitypolicyviolation", resolve);
+ });
+
+ const fencedframe = attachFencedFrameContext();
+
+ const fencedframe_loaded = fencedframe.execute(() => {});
+ fencedframe_loaded.then(t.unreached_func(
+ "The fenced frame should not load for CSP fenced-frame-src " + csp));
+
+ const csp_violation_event = await csp_violation;
+ const remote_url = getRemoteContextURL(location.origin).toString();
+ assert_true(csp_violation_event.blockedURI.includes(remote_url),
+ "blockedURI should include the url");
+ }, "Fenced frame blocked for CSP fenced-frame-src " + csp);
+});
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/csp.https.html b/testing/web-platform/tests/fenced-frame/csp.https.html
new file mode 100644
index 0000000000..6daa6a96e8
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/csp.https.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+ <title>Test Content Security Policy</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="resources/utils.js"></script>
+ <script src="/common/utils.js"></script>
+
+ <body>
+
+ <script>
+ promise_test(async () => {
+ const csp_key = token();
+
+ // The 'csp' property does not appear in the IDL definition for
+ // fenced frames, so ensure that the 'csp' property didn't
+ // leak over from the IFrame prototype.
+ assert_equals(HTMLFencedFrameElement.prototype.hasOwnProperty('csp'),
+ false);
+
+ const new_frame = document.createElement('fencedframe');
+ const new_config = new FencedFrameConfig(generateURL(
+ "resources/csp-inner.html",
+ [csp_key]));
+ new_frame.config = new_config;
+
+ // This attribute will be ignored since the IDL for
+ // fenced frames do not support the 'csp' attribute.
+ new_frame.setAttribute("csp", "style-src 'none';");
+ document.body.append(new_frame);
+
+ // Get the result for the top-level fenced frame.
+ const fenced_frame_result = await nextValueFromServer(csp_key);
+ assert_equals(fenced_frame_result, "pass");
+
+ }, "Fenced Frames should not honor the csp attribute from parent page");
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/cspee.https.html b/testing/web-platform/tests/fenced-frame/cspee.https.html
new file mode 100644
index 0000000000..70c9744b74
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/cspee.https.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<title>Test fenced frame in CSPEE</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const iframe = attachIFrameContext({
+ attributes: [["csp", "frame-src *"]],
+ headers: [["Allow-CSP-From", "*"]]
+ });
+ t.step_timeout(() => t.done(), 1000);
+ await iframe.execute(async (t) => {
+ const fencedframe = attachFencedFrameContext({
+ headers: [["Allow-CSP-From", "*"]]
+ });
+ await fencedframe.execute(() => {});
+ });
+ assert_unreached("fenced frame should not be loaded.");
+}, 'fenced frame should not be loaded in CSPEE');
+
+promise_test(async(t) => {
+ const iframe_a = attachIFrameContext({
+ attributes: [["csp", "frame-src *"]],
+ headers: [["Allow-CSP-From", "*"]]
+ });
+ t.step_timeout(() => t.done(), 1000);
+ await iframe_a.execute(async (t) => {
+ const iframe_b = attachIFrameContext({headers: [["Allow-CSP-From", "*"]]});
+ await iframe_b.execute(async (t) => {
+ const fencedframe = attachFencedFrameContext({
+ headers: [["Allow-CSP-From", "*"]]
+ });
+ await fencedframe.execute(() => {});
+ });
+ });
+ assert_unreached("fenced frame should not be loaded.");
+}, 'fenced frame should not be loaded if any ancestor has CSPEE');
+
+promise_test(async(t) => {
+ const iframe = attachIFrameContext({
+ attributes: [["csp", "frame-src *"]],
+ headers: [["Allow-CSP-From", "*"]]
+ });
+ await iframe.execute(async (t) => {
+ assert_false(navigator.canLoadAdAuctionFencedFrame());
+ });
+}, 'canLoadOpaqueURL considers CSPEE headers');
+
+promise_test(async(t) => {
+ const iframe_a = attachIFrameContext({
+ attributes: [["csp", "frame-src *"]],
+ headers: [["Allow-CSP-From", "*"]]
+ });
+ await iframe_a.execute(async (t) => {
+ const iframe_b = attachIFrameContext({headers: [["Allow-CSP-From", "*"]]});
+ await iframe_b.execute(async (t) => {
+ assert_false(navigator.canLoadAdAuctionFencedFrame());
+ });
+ });
+}, 'canLoadOpaqueURL considers CSPEE headers up the ancestor chain');
+
+promise_test(async(t) => {
+ const iframe = attachIFrameContext();
+ await iframe.execute(async (t) => {
+ assert_true(navigator.canLoadAdAuctionFencedFrame());
+ });
+}, 'canLoadOpaqueURL returns true if no CSPEE headers are present in iframe');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/deep-copy-config.https.html b/testing/web-platform/tests/fenced-frame/deep-copy-config.https.html
new file mode 100644
index 0000000000..54a976233c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/deep-copy-config.https.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<title>Test deep copying FencedFrameConfig objects</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const key = token();
+
+ // Create a FencedFrameConfig from a FLEDGE auction, then deep copy it.
+ let old_config = await generateURNFromFledge(
+ "resources/embeddee.html", [key], [], true);
+ assert_true(old_config instanceof FencedFrameConfig);
+ let new_config = structuredClone(old_config);
+
+ const fencedframe = attachFencedFrame(new_config);
+ const response = await nextValueFromServer(key);
+ assert_equals(response, "PASS",
+ "The page should have loaded from the cloned config.");
+}, 'A cloned config with a URN will navigate.');
+
+promise_test(async(t) => {
+ const key = token();
+
+ // Create a FencedFrameConfig from a FLEDGE auction, then deep copy it.
+ let old_config = new FencedFrameConfig(generateURL(
+ "resources/embeddee.html", [key]));
+ assert_true(old_config instanceof FencedFrameConfig);
+ let new_config = structuredClone(old_config);
+
+ const fencedframe = attachFencedFrame(new_config);
+ const response = await nextValueFromServer(key);
+ assert_equals(response, "PASS",
+ "The page should have loaded from the cloned config.");
+}, 'A cloned config with a URL will navigate.');
+
+promise_test(async(t) => {
+ const key = token();
+ const fenced_url = generateURL("resources/embeddee.html", [key]);
+
+ // Create a fenced frame once the config comes in through postMessage.
+ window.addEventListener(
+ "message",
+ (event) => {
+ attachFencedFrame(event.data);
+ },
+ false,
+ );
+
+ // Create an iframe that creates a FencedFrameConfig
+ const frame = await attachIFrameContext(
+ {origin: get_host_info().HTTPS_REMOTE_ORIGIN});
+ await frame.execute(async (fenced_url) => {
+ const config = await generateURNFromFledge(fenced_url, [], [], true);
+ window.parent.postMessage(config, "*");
+ }, [fenced_url]);
+
+ const response = await nextValueFromServer(key);
+ assert_equals(response, "PASS",
+ "The page should have loaded from the postMessage'd config.");
+}, 'A config received through window.postMessage will navigate.');
+
+promise_test(async(t) => {
+ // Create a FencedFrameConfig from a FLEDGE auction.
+ let config = await generateURNFromFledge(
+ "resources/embeddee.html", [], [], true);
+ assert_true(config instanceof FencedFrameConfig);
+
+ assert_throws_dom("DataCloneError", () => {
+ history.pushState(config, "", location.href);
+ }, "The write should fail for a FencedFrameConfig.");
+}, 'A FencedFrameConfig cannot be written to storage.');
+
+promise_test(async(t) => {
+ const key = token();
+
+ // Create a fenced frame once the config comes in through postMessage.
+ window.addEventListener(
+ "message",
+ (event) => {
+ attachFencedFrame(event.data);
+ },
+ false,
+ );
+
+ // The pop-up will generate a FencedFrameConfig from a FLEDGE auction, and
+ // then pass it back into this page through postMessage(). Since config
+ // mappings are only valid within the same frame tree, this page will not be
+ // able to navigate a fenced frame to the config.
+ window.open(generateURL("resources/postmessage-config.html", [key]), "foo");
+
+ // Set up a timeout to ensure that there's enough time for any messages to be
+ // sent from a fenced frame if it loads.
+ const timeout = new Promise(resolve => t.step_timeout(resolve, 1000));
+ const result = await Promise.race([nextValueFromServer(key), timeout]);
+ assert_true(typeof result === "undefined",
+ "The fenced frame should not have loaded.");
+}, 'A FencedFrameConfig sent to a context that does not support it gracefully' +
+ ' fails to load.');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-all.https.html b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-all.https.html
new file mode 100644
index 0000000000..5d38d7a710
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-all.https.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<title>Test default permission policy features gating (*)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/default-enabled-features-helper.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ await runDefaultEnabledFeaturesTest(t, true, get_host_info().ORIGIN);
+ await runDefaultEnabledFeaturesTest(t, true, get_host_info().ORIGIN,
+ generator_api="sharedstorage");
+}, 'Same-origin fenced frame loads when feature policies are *');
+
+promise_test(async(t) => {
+ await runDefaultEnabledFeaturesTest(t, true, get_host_info().REMOTE_ORIGIN);
+ await runDefaultEnabledFeaturesTest(t, true, get_host_info().REMOTE_ORIGIN,
+ generator_api="sharedstorage");
+}, 'Cross-origin fenced frame loads when feature policies are *');
+
+promise_test(async(t) => {
+ // We do this test the "old fashioned way" because a redirect in a fenced
+ // frame remote context will cause it to lose its ability to communicate with
+ // the main page (which results in a timeout).
+ const page1_key = token();
+ const redirect_key = token();
+
+ const fencedframe = attachFencedFrame(
+ await generateURNFromFledge(
+ "resources/default-enabled-features-navigate.https.html",
+ [page1_key, redirect_key]));
+
+ // The fenced frame will send its attribution reporting result and then
+ // attempt to redirect to a remote origin page.
+ const page1_resp = await nextValueFromServer(page1_key);
+ assert_equals(page1_resp, "true",
+ "Attribution reporting should be enabled on the original page.");
+
+ // The fenced frame will send its attribution reporting result and then
+ // attempt to redirect to a remote origin page.
+ const redirect_resp = await nextValueFromServer(redirect_key);
+ assert_equals(redirect_resp, "true",
+ "Attribution reporting should be enabled on the redirected page.");
+}, 'A fenced frame that navigates itself to a cross origin page that allows feature policies ' +
+ 'can still access the feature policies');
+
+promise_test(async(t) => {
+ const fencedframe = await attachFencedFrameContext({
+ origin: get_host_info().REMOTE_ORIGIN});
+
+ await fencedframe.execute(async () => {
+ assert_true(
+ document.featurePolicy.allowsFeature('shared-storage'),
+ "Shared storage should be allowed in the fenced frame.");
+ assert_true(
+ document.featurePolicy.allowsFeature('private-aggregation'),
+ "Private aggregation should be allowed in the fenced frame.");
+ assert_false(
+ document.featurePolicy.allowsFeature('attribution-reporting'),
+ "Attribution reporting should be disallowed in the fenced frame.");
+ assert_false(
+ document.featurePolicy.allowsFeature('sync-xhr'),
+ "USB access should be disallowed in the fenced frame.");
+ }, []);
+}, 'Cross-origin fenced frames default feature policies follow inheritance' +
+ ' rules.');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-all.https.html.headers b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-all.https.html.headers
new file mode 100644
index 0000000000..c3d2f1fcfb
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-all.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: attribution-reporting=(*), private-aggregation=(*), shared-storage=(*) \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-none.https.html b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-none.https.html
new file mode 100644
index 0000000000..1c7d0579eb
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-none.https.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<title>Test default permission policy features gating ()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/default-enabled-features-helper.js"></script>
+
+<body>
+<script>
+// Note: Shared storage will refuse to run selectURL() on this page because its
+// permissions policy is disabled. Therefore, we can only test the FLEDGE case.
+promise_test(async(t) => {
+ await runDefaultEnabledFeaturesTest(t, false, get_host_info().ORIGIN);
+}, 'Same-origin fenced frame does not load when feature policies are none');
+
+promise_test(async(t) => {
+ await runDefaultEnabledFeaturesTest(t, false, get_host_info().REMOTE_ORIGIN);
+}, 'Cross-origin fenced frame does not load when feature policies are none');
+
+promise_test(async(t) => {
+ const fencedframe = await attachFencedFrameContext({
+ origin: get_host_info().REMOTE_ORIGIN});
+
+ await fencedframe.execute(async () => {
+ assert_false(
+ document.featurePolicy.allowsFeature('shared-storage'),
+ "Shared storage should be disallowed in the fenced frame.");
+ assert_false(
+ document.featurePolicy.allowsFeature('private-aggregation'),
+ "Private aggregation should be disallowed in the fenced frame.");
+ assert_false(
+ document.featurePolicy.allowsFeature('attribution-reporting'),
+ "Attribution reporting should be disallowed in the fenced frame.");
+ assert_false(
+ document.featurePolicy.allowsFeature('sync-xhr'),
+ "USB access should be disallowed in the fenced frame.");
+ }, []);
+}, 'Flexible permissions fenced frames can have permissions restricted from ' +
+ 'parent.');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-none.https.html.headers b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-none.https.html.headers
new file mode 100644
index 0000000000..2aa0b2f5ae
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-none.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: attribution-reporting=(), shared-storage=(), private-aggregation=() \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-self.https.html b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-self.https.html
new file mode 100644
index 0000000000..3724a4795e
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-self.https.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<title>Test default permission policy features gating (self)</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/default-enabled-features-helper.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ await runDefaultEnabledFeaturesTest(t, false, get_host_info().ORIGIN);
+ await runDefaultEnabledFeaturesTest(t, false, get_host_info().ORIGIN,
+ generator_api="sharedstorage");
+}, 'Same-origin fenced frame does not load when feature policies are self');
+
+promise_test(async(t) => {
+ await runDefaultEnabledFeaturesTest(t, false, get_host_info().REMOTE_ORIGIN);
+ await runDefaultEnabledFeaturesTest(t, false, get_host_info().REMOTE_ORIGIN,
+ generator_api="sharedstorage");
+}, 'Cross-origin fenced frame does not load when feature policies are self');
+
+promise_test(async(t) => {
+ const fencedframe = await attachFencedFrameContext({
+ origin: get_host_info().ORIGIN});
+
+ await fencedframe.execute(async () => {
+ assert_true(
+ document.featurePolicy.allowsFeature('shared-storage'),
+ "Shared storage should be allowed in the fenced frame.");
+ assert_true(
+ document.featurePolicy.allowsFeature('private-aggregation'),
+ "Private aggregation should be allowed in the fenced frame.");
+ assert_false(
+ document.featurePolicy.allowsFeature('attribution-reporting'),
+ "Attribution reporting should be disallowed in the fenced frame.");
+ assert_false(
+ document.featurePolicy.allowsFeature('sync-xhr'),
+ "USB access should be disallowed in the fenced frame.");
+ }, []);
+}, 'Fenced frames default feature policies should inherit from parent.');
+
+promise_test(async(t) => {
+ const fencedframe = await attachFencedFrameContext({
+ origin: get_host_info().REMOTE_ORIGIN});
+
+ await fencedframe.execute(async () => {
+ assert_false(
+ document.featurePolicy.allowsFeature('shared-storage'),
+ "Shared storage should be disallowed in the fenced frame.");
+ assert_false(
+ document.featurePolicy.allowsFeature('private-aggregation'),
+ "Private aggregation should be disallowed in the fenced frame.");
+ assert_false(
+ document.featurePolicy.allowsFeature('attribution-reporting'),
+ "Attribution reporting should be disallowed in the fenced frame.");
+ assert_false(
+ document.featurePolicy.allowsFeature('sync-xhr'),
+ "USB access should be disallowed in the fenced frame.");
+ }, []);
+}, 'Cross-origin fenced frames default feature policies follow inheritance rules.');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-self.https.html.headers b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-self.https.html.headers
new file mode 100644
index 0000000000..76ab816ec0
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-self.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: attribution-reporting=(self), private-aggregation=(self), shared-storage=(self) \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-unspecified.https.html b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-unspecified.https.html
new file mode 100644
index 0000000000..69e9a157cf
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-unspecified.https.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<title>Test permission policies with no permissions specified</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/default-enabled-features-helper.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const fencedframe = await attachFencedFrameContext({
+ origin: get_host_info().ORIGIN});
+
+ await fencedframe.execute(async () => {
+ assert_true(
+ document.featurePolicy.allowsFeature('shared-storage'),
+ "Shared storage should be allowed in the fenced frame.");
+ assert_true(
+ document.featurePolicy.allowsFeature('private-aggregation'),
+ "Private aggregation should be allowed in the fenced frame.");
+ assert_false(
+ document.featurePolicy.allowsFeature('attribution-reporting'),
+ "Attribution reporting should be disallowed in the fenced frame.");
+ assert_false(
+ document.featurePolicy.allowsFeature('sync-xhr'),
+ "USB access should be disallowed in the fenced frame.");
+ }, []);
+}, 'Fenced frames should inherit features from parent if nothing specified.');
+
+promise_test(async(t) => {
+ const fencedframe = await attachFencedFrameContext({
+ origin: get_host_info().REMOTE_ORIGIN,
+ attributes: [["allow",
+ "shared-storage *; attribution-reporting *; " +
+ "private-aggregation 'none'"]]});
+
+ await fencedframe.execute(async () => {
+ assert_true(
+ document.featurePolicy.allowsFeature('shared-storage'),
+ "Shared storage should be allowed in the fenced frame.");
+ assert_false(
+ document.featurePolicy.allowsFeature('private-aggregation'),
+ "Private aggregation should be disallowed in the fenced frame.");
+ assert_false(
+ document.featurePolicy.allowsFeature('attribution-reporting'),
+ "Attribution reporting should be disallowed in the fenced frame.");
+ assert_false(
+ document.featurePolicy.allowsFeature('sync-xhr'),
+ "USB access should be disallowed in the fenced frame.");
+ }, []);
+}, 'Fenced frames default feature policies should inherit when using `allow` ' +
+ 'but should be able to further restrict the policies.');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-attribute-allow.https.html b/testing/web-platform/tests/fenced-frame/default-enabled-features-attribute-allow.https.html
new file mode 100644
index 0000000000..d1e857cf29
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-attribute-allow.https.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<title>Test default permission policy features with allow="" attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/default-enabled-features-helper.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ await runDefaultEnabledFeaturesTest(t, /*should_load=*/true,
+ get_host_info().ORIGIN,
+ generator_api="fledge",
+ allow="shared-storage *; attribution-reporting *");
+ await runDefaultEnabledFeaturesTest(t, /*should_load=*/true,
+ get_host_info().ORIGIN,
+ generator_api="sharedstorage",
+ allow="shared-storage *; attribution-reporting *");
+}, 'Same-origin fenced frame with allow attribute enabling required features');
+
+promise_test(async(t) => {
+ await runDefaultEnabledFeaturesTest(t, /*should_load=*/true,
+ get_host_info().REMOTE_ORIGIN,
+ generator_api="fledge",
+ allow="shared-storage *; attribution-reporting *");
+ await runDefaultEnabledFeaturesTest(t, /*should_load=*/true,
+ get_host_info().REMOTE_ORIGIN,
+ generator_api="sharedstorage",
+ allow="shared-storage *; attribution-reporting *");
+}, 'Cross-origin fenced frame with allow attribute enabling required features');
+
+promise_test(async(t) => {
+ const fencedframe = await attachFencedFrameContext({
+ generator_api: "fledge",
+ headers: [["Permissions-Policy", "attribution-reporting=()"]],
+ origin: get_host_info().ORIGIN});
+
+ await fencedframe.execute(async () => {
+ assert_false(document.featurePolicy.allowsFeature('attribution-reporting'),
+ "Attribution reporting should NOT be allowed in the fenced frame.");
+ }, []);
+}, 'Delivered policies can further restrict permissions of a fixed ' +
+ 'permissions fenced frame');
+
+promise_test(async(t) => {
+ const fencedframe = await attachFencedFrameContext({
+ headers: [["Permissions-Policy", "shared-storage=()"]],
+ origin: get_host_info().ORIGIN});
+
+ await fencedframe.execute(async () => {
+ assert_false(
+ document.featurePolicy.allowsFeature('shared-storage'),
+ "Shared storage should not be allowed in the fenced frame.");
+ assert_true(
+ document.featurePolicy.allowsFeature('private-aggregation'),
+ "Private aggregation should be allowed in the fenced frame.");
+ assert_false(
+ document.featurePolicy.allowsFeature('attribution-reporting'),
+ "Attribution reporting should be disallowed in the fenced frame.");
+ assert_false(
+ document.featurePolicy.allowsFeature('sync-xhr'),
+ "USB access should be disallowed in the fenced frame.");
+ }, []);
+}, 'Delivered policies can further restrict permissions of a non-opaque ' +
+ 'fenced frame');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-attribute-change.https.html b/testing/web-platform/tests/fenced-frame/default-enabled-features-attribute-change.https.html
new file mode 100644
index 0000000000..09bcde5e31
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-attribute-change.https.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<title>Test changing the allow="" attribute after a navigation</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/default-enabled-features-helper.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const fencedframe = await attachFencedFrameContext({
+ generator_api: 'fledge',
+ attributes: [["allow", "attribution-reporting *"]],
+ origin: get_host_info().ORIGIN});
+
+ fencedframe.element.allow = "attribution-reporting 'none'";
+
+ await fencedframe.execute(async () => {
+ assert_true(document.featurePolicy.allowsFeature('attribution-reporting'),
+ "Changing the allow attribute should do nothing for this navigation.");
+ }, []);
+}, 'Changing the allow attribute is a no-op for the current navigation');
+
+promise_test(async(t) => {
+ const fencedframe = await attachFencedFrameContext({
+ generator_api: 'fledge',
+ attributes: [["allow", "attribution-reporting *"]],
+ origin: get_host_info().ORIGIN});
+
+ fencedframe.element.allow = "attribution-reporting 'none'";
+
+ await fencedframe.execute(async () => {
+ window.executor.suspend(() => {
+ location.reload();
+ });
+ }, []);
+
+ await fencedframe.execute(async () => {
+ assert_true(document.featurePolicy.allowsFeature('attribution-reporting'),
+ "Changing the allow attribute should do nothing on frame refresh.");
+ }, []);
+
+}, 'Changing the allow attribute is a no-op for frame-initiated navigations');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-attribute-disallow.https.html b/testing/web-platform/tests/fenced-frame/default-enabled-features-attribute-disallow.https.html
new file mode 100644
index 0000000000..f9ef9b6db2
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-attribute-disallow.https.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>Test default permission policy features with allow="" attribute</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/default-enabled-features-helper.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ await runDefaultEnabledFeaturesTest(t, /*should_load=*/false,
+ get_host_info().ORIGIN, generator_api="fledge",
+ allow="private-aggregation 'none'");
+ await runDefaultEnabledFeaturesTest(t, /*should_load=*/false,
+ get_host_info().ORIGIN, generator_api="sharedstorage",
+ allow="shared-storage 'none'");
+}, 'Same-origin fenced frame with allow attribute disabling required feature');
+
+promise_test(async(t) => {
+ await runDefaultEnabledFeaturesTest(t, /*should_load=*/false,
+ get_host_info().REMOTE_ORIGIN, generator_api="fledge",
+ allow="private-aggregation 'none'");
+ await runDefaultEnabledFeaturesTest(t, /*should_load=*/false,
+ get_host_info().REMOTE_ORIGIN, generator_api="sharedstorage",
+ allow="shared-storage 'none'");
+}, 'Cross-origin fenced frame with allow attribute disabling required feature');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-attribution-disabled.https.html b/testing/web-platform/tests/fenced-frame/default-enabled-features-attribution-disabled.https.html
new file mode 100644
index 0000000000..9e037e24a3
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-attribution-disabled.https.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Test where attribution-reporting is disabled in the top-level page</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/default-enabled-features-helper.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ await runDefaultEnabledFeaturesTest(t, /*should_load=*/false,
+ get_host_info().ORIGIN, generator_api="fledge",
+ allow="attribution-reporting *;");
+}, 'Same-origin fenced frame with allow attribute enabling required feature ' +
+ 'but page disabling feature.');
+
+promise_test(async(t) => {
+ await runDefaultEnabledFeaturesTest(t, /*should_load=*/false,
+ get_host_info().REMOTE_ORIGIN, generator_api="fledge",
+ allow="attribution-reporting *;");
+}, 'Cross-origin fenced frame with allow attribute enabling required feature ' +
+ 'but page disabling feature.');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-attribution-disabled.https.html.headers b/testing/web-platform/tests/fenced-frame/default-enabled-features-attribution-disabled.https.html.headers
new file mode 100644
index 0000000000..af6d6ecb2e
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-attribution-disabled.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: attribution-reporting=() \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-subframe.https.html b/testing/web-platform/tests/fenced-frame/default-enabled-features-subframe.https.html
new file mode 100644
index 0000000000..a86a02b00a
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-subframe.https.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<title>Test nested iframes inheriting permissions from fenced frames</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const key = token();
+
+ const urn = await runSelectURL(
+ 'resources/default-enabled-features-subframe-fencedframe.https.html',
+ [key, /*should_restrict_select_url=*/false]);
+ const fencedframe = await attachFencedFrame(urn);
+
+ const result = await nextValueFromServer(key);
+ const [allows_shared_storage, allows_select_url] = result.split(",");
+
+ assert_equals(allows_shared_storage, "false");
+ assert_equals(allows_select_url, "true");
+}, 'Iframes nested in fenced frames inherit the fenced frame policies');
+
+promise_test(async(t) => {
+ const key = token();
+
+ const urn = await runSelectURL(
+ 'resources/default-enabled-features-subframe-fencedframe.https.html',
+ [key, /*should_restrict_select_url=*/true]);
+ const fencedframe = await attachFencedFrame(urn);
+
+ const result = await nextValueFromServer(key);
+ const [allows_shared_storage, allows_select_url] = result.split(",");
+
+ assert_equals(allows_shared_storage, "false");
+ assert_equals(allows_select_url, "false");
+}, 'Iframes nested in fenced frames can further restrict policies');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-unset.https.html b/testing/web-platform/tests/fenced-frame/default-enabled-features-unset.https.html
new file mode 100644
index 0000000000..b438d1ff35
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-unset.https.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>Test default permission policy features gating unset</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/default-enabled-features-helper.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ await runDefaultEnabledFeaturesTest(t, /*should_load=*/true,
+ get_host_info().ORIGIN);
+ await runDefaultEnabledFeaturesTest(t, /*should_load=*/true,
+ get_host_info().ORIGIN, generator_api="sharedstorage");
+}, 'Same-origin fenced frame loads when feature policies are unset');
+
+promise_test(async(t) => {
+ await runDefaultEnabledFeaturesTest(t, /*should_load=*/true,
+ get_host_info().REMOTE_ORIGIN);
+ await runDefaultEnabledFeaturesTest(t, /*should_load=*/true,
+ get_host_info().REMOTE_ORIGIN, generator_api="sharedstorage");
+}, 'Cross-origin fenced frame loads when feature policies are unset');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-unset.https.html.headers b/testing/web-platform/tests/fenced-frame/default-enabled-features-unset.https.html.headers
new file mode 100644
index 0000000000..e087474e2a
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-unset.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: geolocation=(*) \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/deprecated-config-apis.https.html b/testing/web-platform/tests/fenced-frame/deprecated-config-apis.https.html
new file mode 100644
index 0000000000..e746336f47
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/deprecated-config-apis.https.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<title>Tests for configs as arguments to `deprecatedReplaceInURN` and `deprecatedURNToURL`.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+
+<body>
+<script>
+
+promise_test(async () => {
+ const expected_url = location.href + '?foo';
+ const config = await generateURNFromFledgeRawURL(location.href + '?${X}', [], true);
+ await navigator.deprecatedReplaceInURN(config, {'${X}': 'foo'});
+ const url = await navigator.deprecatedURNToURL(config, false);
+ assert_equals(url, expected_url, 'The retrieved mapped url should match.');
+}, 'deprecated urn APIs should work with configs generated by an API');
+
+promise_test(async () => {
+ const config = new FencedFrameConfig(location.href);
+ try {
+ await navigator.deprecatedReplaceInURN(config, {});
+ assert_unreached('deprecatedReplaceInURN must throw for default configs');
+ } catch (e) {
+ assert_equals(e.message,
+ "Failed to execute 'deprecatedReplaceInURN' on 'Navigator': "
+ + "Passed config must have a mapped URL.");
+ }
+ try {
+ await navigator.deprecatedURNToURL(config, false);
+ assert_unreached('deprecatedURNToURL must throw for default configs');
+ } catch (e) {
+ assert_equals(e.message,
+ "Failed to execute 'deprecatedURNToURL' on 'Navigator': "
+ + "Passed config must have a mapped URL.");
+ }
+}, 'deprecated urn APIs should not work with configs generated by a WebIDL constructor');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/disable-untrusted-network.https.html b/testing/web-platform/tests/fenced-frame/disable-untrusted-network.https.html
new file mode 100644
index 0000000000..726728e489
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/disable-untrusted-network.https.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<title>Test window.fence.disableUntrustedNetwork availability.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const fencedframe = await attachFencedFrameContext();
+ await fencedframe.execute(async () => {
+ const cross_origin_fenced_frame = await attachFencedFrameContext({
+ origin: get_host_info().HTTPS_REMOTE_ORIGIN});
+ await cross_origin_fenced_frame.execute(async () => {
+ const promise = window.fence.disableUntrustedNetwork();
+ assert_true(typeof promise.then != 'undefined');
+ await promise;
+ });
+
+ const same_origin_iframe = await attachIFrameContext();
+ await same_origin_iframe.execute(async () => {
+ const promise = window.fence.disableUntrustedNetwork();
+ assert_true(typeof promise.then != 'undefined');
+ await promise;
+ });
+
+ const cross_origin_iframe = await attachIFrameContext({
+ origin: get_host_info().HTTPS_REMOTE_ORIGIN});
+ await cross_origin_iframe.execute(async () => {
+ try {
+ const promise = window.fence.disableUntrustedNetwork();
+ await promise;
+ assert_unreached(
+ 'disableUntrustedNetwork should fail when not same-origin to the '
+ + 'mapped url.');
+ } catch (e) {
+ assert_equals(e.name, 'TypeError');
+ }
+ });
+
+ const promise = window.fence.disableUntrustedNetwork();
+ assert_true(typeof promise.then != 'undefined');
+ await promise;
+ });
+}, 'window.fence.disableUntrustedNetwork availability');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/disallowed-navigation-to-blob.https.html b/testing/web-platform/tests/fenced-frame/disallowed-navigation-to-blob.https.html
new file mode 100644
index 0000000000..3722609410
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/disallowed-navigation-to-blob.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Fenced frame disallowed navigations to blob: URL</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/fetch/private-network-access/resources/support.sub.js"></script>
+
+<body>
+<script>
+const kPublicUtils = resolveUrl("resources/utils.js", Server.HTTPS_PUBLIC);
+
+function getTimeoutPromise(t) {
+ return new Promise(resolve =>
+ t.step_timeout(() => resolve("NOT LOADED"), 2000));
+}
+
+// The following tests ensure that an embedder cannot navigate a
+// `mode=opaque-ads` fenced frame to an opaque URN or a fenced frame config
+// object that represents a blob: URL
+for (const resolve_to_config of [true, false]) {
+ promise_test(async t => {
+ const key = token();
+ const blobURL = URL.createObjectURL(
+ new Blob([`${createLocalSource(key, kPublicUtils)}`],
+ {type: 'text/html'}));
+ const select_url_result = await runSelectURL(blobURL);
+ attachFencedFrame(select_url_result);
+ const loaded_promise = nextValueFromServer(key);
+ const result = await Promise.any([loaded_promise, getTimeoutPromise(t)]);
+ assert_equals(result, "NOT LOADED");
+ }, "fenced frame " + (resolve_to_config ? "config" : "urn:uuid") +
+ " => blob: URL");
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/disallowed-navigation-to-data.https.html b/testing/web-platform/tests/fenced-frame/disallowed-navigation-to-data.https.html
new file mode 100644
index 0000000000..99c2e05bee
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/disallowed-navigation-to-data.https.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Fenced frame disallowed navigations to data: URL</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/fetch/private-network-access/resources/support.sub.js"></script>
+
+<body>
+<script>
+const kPublicUtils = resolveUrl("resources/utils.js", Server.HTTPS_PUBLIC);
+
+function getTimeoutPromise(t) {
+ return new Promise(resolve =>
+ t.step_timeout(() => resolve("NOT LOADED"), 2000));
+}
+
+// The following tests ensure that an embedder cannot navigate a
+// `mode=opaque-ads` fenced frame to an opaque URN or a fenced frame config
+// object that represents a data: URL
+for (const resolve_to_config of [true, false]) {
+ promise_test(async t => {
+ const key = token();
+ const select_url_result = await
+ runSelectURL(`data:text/html, ${createLocalSource(key, kPublicUtils)}`);
+ attachFencedFrame(select_url_result);
+ const loaded_promise = nextValueFromServer(key);
+ const result = await Promise.any([loaded_promise, getTimeoutPromise(t)]);
+ assert_equals(result, "NOT LOADED");
+ }, "fenced frame " + (resolve_to_config ? "config" : "urn:uuid") +
+ " => data: URL");
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/disallowed-navigation-to-http.https.html b/testing/web-platform/tests/fenced-frame/disallowed-navigation-to-http.https.html
new file mode 100644
index 0000000000..18ed92851a
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/disallowed-navigation-to-http.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<title>Fenced frame disallowed navigations</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+function getTimeoutPromise(t) {
+ return new Promise(resolve =>
+ t.step_timeout(() => resolve("NOT LOADED"), 2000));
+}
+
+// The following test ensures that an embedder cannot navigate a
+// `mode=opaque-ads` fenced frame to an opaque URN that represents a:
+// - http: URL
+// We split this into a separate test file because `sharedStorage.selectURL()`,
+// which is used to generate the URN in the test, has a limit of 3 calls per
+// origin per pageload. We are unabled to generate this URN from FLEDGE.
+for (const resolve_to_config of [true, false]) {
+ promise_test(async t => {
+ const key = token();
+ const http_url = new URL("resources/embeddee.html",
+ get_host_info().HTTP_ORIGIN + location.pathname);
+ const select_url_result = await runSelectURL(http_url, [key],
+ resolve_to_config);
+ const fencedframe = attachFencedFrame(select_url_result,
+ /*mode=*/'opaque-ads');
+ const loaded_promise = nextValueFromServer(key);
+ const result = await Promise.any([loaded_promise, getTimeoutPromise(t)]);
+ assert_equals(result, "NOT LOADED");
+ }, "fenced frame " + (resolve_to_config ? "config" : "urn:uuid") +
+ " => http: URL");
+}
+</script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/disallowed-navigations-dangling-markup-urn.https.html b/testing/web-platform/tests/fenced-frame/disallowed-navigations-dangling-markup-urn.https.html
new file mode 100644
index 0000000000..a36f98f975
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/disallowed-navigations-dangling-markup-urn.https.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<title>Fenced frame disallowed navigations with potentially-dangling markup</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/fetch/private-network-access/resources/support.sub.js"></script>
+<script src="resources/dangling-markup-helper.js"></script>
+
+<body>
+
+<script>
+// These tests assert that fenced frames cannot be navigated to a urn:uuid URL
+// that represents an HTTPS URLs with dangling markup.
+for (const substring of kDanglingMarkupSubstrings) {
+ promise_test(async t => {
+ const key = token();
+
+ // Copied from from `generateURNFromFlege()`, since we have to modify the
+ // final URL that goes into `interestGroup.ads[0].renderURL` for
+ // `navigator.joinAdInterestGroup()`.
+ const bidding_token = token();
+ const seller_token = token();
+
+ let url_string = generateURL("resources/report-url.html?blocked",
+ [key]).toString();
+ url_string = url_string.replace("blocked", substring);
+
+ const interestGroup = {
+ name: 'testAd1',
+ owner: location.origin,
+ biddingLogicURL: new URL(FLEDGE_BIDDING_URL, location.origin),
+ ads: [{renderURL: url_string, bid: 1}],
+ userBiddingSignals: {biddingToken: bidding_token},
+ trustedBiddingSignalsKeys: ['key1'],
+ adComponents: [],
+ };
+
+ // Pick an arbitrarily high duration to guarantee that we never leave the
+ // ad interest group while the test runs.
+ navigator.joinAdInterestGroup(interestGroup, /*durationSeconds=*/3000000);
+
+ const auctionConfig = {
+ seller: location.origin,
+ interestGroupBuyers: [location.origin],
+ decisionLogicURL: new URL(FLEDGE_DECISION_URL, location.origin),
+ auctionSignals: {biddingToken: bidding_token, sellerToken: seller_token},
+ };
+
+ const urn = await navigator.runAdAuction(auctionConfig);
+
+ const fencedframe = attachFencedFrame(urn);
+ const loaded_promise = nextValueFromServer(key);
+ const result = await Promise.any([loaded_promise, getTimeoutPromise(t)]);
+ assert_equals(result, "NOT LOADED");
+ }, `fenced frame opaque URN => https: URL with dangling markup '${substring}'`);
+}
+
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/disallowed-navigations-dangling-markup.https.html b/testing/web-platform/tests/fenced-frame/disallowed-navigations-dangling-markup.https.html
new file mode 100644
index 0000000000..72f0855d59
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/disallowed-navigations-dangling-markup.https.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>Fenced frame disallowed navigations with potentially-dangling markup</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/fetch/local-network-access/resources/support.sub.js"></script>
+<script src="resources/dangling-markup-helper.js"></script>
+
+<body>
+
+<script>
+// These tests assert that fenced frames cannot be navigated to HTTPs URLs
+// with dangling markup.
+for (const substring of kDanglingMarkupSubstrings) {
+ promise_test(async t => {
+ const key = token();
+ let url_string = generateURL("resources/embeddee.html?blocked", [key]).toString();
+ url_string = url_string.replace("blocked", substring);
+ const fencedframe = attachFencedFrame(url_string);
+ const loaded_promise = nextValueFromServer(key);
+ const result = await Promise.any([loaded_promise, getTimeoutPromise(t)]);
+ assert_equals(result, "NOT LOADED");
+ }, `fenced frame dangling-markup URL with '${substring}'`);
+}
+
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/disallowed-navigations.https.html b/testing/web-platform/tests/fenced-frame/disallowed-navigations.https.html
new file mode 100644
index 0000000000..54fa95f16f
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/disallowed-navigations.https.html
@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<title>Fenced frame disallowed navigations</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/fetch/private-network-access/resources/support.sub.js"></script>
+
+<body>
+
+<script>
+// Baseline tests:
+// - Embedder can navigate iframe to blob: URL
+// - Embedder can navigate iframe to data: URL
+// - Same-origin embedder can navigate iframe to javascript: URL
+// - Embedder can navigate iframe to http: URL
+// Fenced frame tests:
+// - Embedder cannot navigate fenced frame to blob: URL
+// - Embedder cannot navigate fenced frame to data: URL
+// - Same-origin embedder cannot navigate fenced frame to
+// javascript: URL
+// - Embedder cannot navigate fenced frame to http: URL
+
+// Fenced frames are always put in the public IP address space which is the
+// least privileged. In case a navigation to a local data: URL or blob: URL
+// resource is allowed, they would only be able to fetch things that are *also*
+// in the public IP address space. So for the document described by these local
+// URLs, we'll set them up to only communicate back to the outer page via
+// resources obtained in the public address space.
+const kPublicUtils = resolveUrl("resources/utils.js", Server.HTTPS_PUBLIC);
+
+// These are just baseline tests asserting that this test's machinery to load
+// blob:, data:, and javascript: URLs work properly in contexts where they are
+// expected to.
+promise_test(async () => {
+ const key = token();
+ attachIFrame(`data:text/html, ${createLocalSource(key, kPublicUtils)}`);
+ const result = await nextValueFromServer(key);
+ assert_equals(result, "LOADED");
+}, "iframe data: URL");
+
+promise_test(async () => {
+ const key = token();
+ const blobURL = URL.createObjectURL(
+ new Blob([`${createLocalSource(key, kPublicUtils)}`],
+ {type: 'text/html'}));
+ attachIFrame(blobURL);
+ const result = await nextValueFromServer(key);
+ assert_equals(result, "LOADED");
+}, "iframe blob: URL");
+
+promise_test(async () => {
+ const iframe = attachIFrameContext();
+ iframe.src = "javascript:window.jsURLExecuted = true;"
+ await iframe.execute(async () => {
+ assert_equals(window.jsURLExecuted, true);
+ });
+}, "iframe javascript: URL");
+
+// The following tests ensure that an embedder cannot navigate a fenced frame
+// to:
+// - data: URLs
+// - blob: URLs
+// - javascript: URLs
+// - http: URLs
+function getTimeoutPromise(t) {
+ return new Promise(resolve =>
+ t.step_timeout(() => resolve("NOT LOADED"), 2000));
+}
+
+promise_test(async t => {
+ const key = token();
+ attachFencedFrame(`data:text/html, ${createLocalSource(key, kPublicUtils)}`);
+ const loaded_promise = nextValueFromServer(key);
+ const result = await Promise.any([loaded_promise, getTimeoutPromise(t)]);
+ assert_equals(result, "NOT LOADED");
+}, `fenced frame data: URL`);
+
+promise_test(async t => {
+ const key = token();
+ const blobURL = URL.createObjectURL(
+ new Blob([`${createLocalSource(key, kPublicUtils)}`],
+ {type: 'text/html'}));
+ attachFencedFrame(blobURL);
+ const loaded_promise = nextValueFromServer(key);
+ const result = await Promise.any([loaded_promise, getTimeoutPromise(t)]);
+ assert_equals(result, "NOT LOADED");
+}, `fenced frame blob: URL`);
+
+promise_test(async t => {
+ const fencedframe = attachFencedFrameContext();
+ fencedframe.src = "javascript:window.jsURLExecuted = true;"
+ // Just in case the javascript URL executes asynchronously, let's wait for
+ // it.
+ await getTimeoutPromise(t);
+ await fencedframe.execute(async () => {
+ assert_equals(window.jsURLExecuted, undefined);
+ });
+}, `fenced frame javascript: URL`);
+
+promise_test(async t => {
+ const key = token();
+ let http_url = new URL("resources/embeddee.html",
+ get_host_info().HTTP_ORIGIN + location.pathname);
+ http_url = generateURL(http_url, [key]);
+ assert_equals(http_url.protocol, "http:");
+ const fencedframe = attachFencedFrame(http_url);
+ const loaded_promise = nextValueFromServer(key);
+ const result = await Promise.any([loaded_promise, getTimeoutPromise(t)]);
+ assert_equals(result, "NOT LOADED");
+}, `fenced frame http: URL`);
+
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/document-activeelement.https.html b/testing/web-platform/tests/fenced-frame/document-activeelement.https.html
new file mode 100644
index 0000000000..3ac1fd866f
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/document-activeelement.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<title>Test document.activeElement</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const actions = new test_driver.Actions();
+ const fencedframe = await attachFencedFrameContext();
+
+ assert_equals(document.activeElement, document.body);
+
+ await actions.pointerMove(0, 0, {origin: fencedframe.element})
+ .pointerDown()
+ .pointerUp()
+ .send();
+
+ assert_equals(document.activeElement, fencedframe.element);
+}, 'document.activeElement should be the fenced frame when it has focus');
+
+promise_test(async(t) => {
+ const actions = new test_driver.Actions();
+ const fencedframe = await attachFencedFrameContext();
+
+ await fencedframe.execute(() => {
+ assert_equals(document.activeElement, document.body);
+ })
+
+}, "a fenced frame's document.activeElement should be its body when it " +
+ "doesn't have focus");
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/document-hasfocus.https.html b/testing/web-platform/tests/fenced-frame/document-hasfocus.https.html
new file mode 100644
index 0000000000..24ea9988e9
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/document-hasfocus.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Test document.hasFocus</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const actions = new test_driver.Actions();
+ const fencedframe = await attachFencedFrameContext();
+
+ assert_true(document.hasFocus(), "The main document should initially have " +
+ "focus.");
+ await fencedframe.execute(() => {
+ assert_false(document.hasFocus(), "The fenced frame should not initially " +
+ "have focus.");
+ })
+
+ await actions.pointerMove(0, 0, {origin: fencedframe.element})
+ .pointerDown()
+ .pointerUp()
+ .send();
+
+ assert_true(document.hasFocus(), "The main document should have focus.");
+ await fencedframe.execute(() => {
+ assert_true(document.hasFocus(), "The fenced frame should have focus.");
+ })
+}, 'document.hasFocus should be the true when a fenced frame has focus');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/document-picture-in-picture-denied.https.html b/testing/web-platform/tests/fenced-frame/document-picture-in-picture-denied.https.html
new file mode 100644
index 0000000000..3838fd683a
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/document-picture-in-picture-denied.https.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Test that fencedframes cannot open a DocumentPictureInPicture window.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+</body>
+<script>
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ const result = await frame.execute(async () => {
+ await simulateGesture();
+ try {
+ await documentPictureInPicture.requestWindow();
+ return null;
+ } catch(e) {
+ return e.name;
+ }
+ });
+ assert_equals(result, 'NotAllowedError', 'requestWindow() from a fencedframe should trigger a NotAllowedError');
+}, 'fencedframes cannot open a DocumentPictureInPicture window.');
+</script>
diff --git a/testing/web-platform/tests/fenced-frame/document-referrer.https.html b/testing/web-platform/tests/fenced-frame/document-referrer.https.html
new file mode 100644
index 0000000000..2bbc319c7b
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/document-referrer.https.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<title>Test document.referrer referrer</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async () => {
+ const fenced_frame = attachFencedFrameContext();
+
+ // Test the value of `document.referrer` inside a top-level fenced frame.
+ const fenced_frame_url = await fenced_frame.execute(() => {
+ assert_equals(document.referrer, "",
+ "The top-level fenced frame's document.referrer is censored");
+ return location.href;
+ });
+
+ // Test an iframe nested inside a fenced frame.
+ await fenced_frame.execute(async () => {
+ const nested_iframe = attachIFrameContext();
+ await nested_iframe.execute((expected_referrer) => {
+ assert_equals(document.referrer, expected_referrer,
+ `The document.referrer of the iframe inside the fenced
+ frame is not censored`);
+ }, [location.href]);
+ });
+
+ // Test a nested fenced frame.
+ await fenced_frame.execute(async () => {
+ const nested_fenced_frame = attachFencedFrameContext();
+ await nested_fenced_frame.execute(() => {
+ assert_equals(document.referrer, "",
+ `The document.referrer of the nested fenced frame is
+ censored`);
+ });
+ });
+
+ // Test a top-level fenced frame after it navigates itself.
+ // Navigate the fenced frame. (Refresh it, so we can still send it scripts.)
+ await fenced_frame.execute(() => {
+ window.executor.suspend(() => {
+ location.href = location.href;
+ });
+ });
+
+ // Check that it now sees its original URL as the referrer.
+ await fenced_frame.execute((expected_referrer) => {
+ assert_equals(document.referrer, expected_referrer,
+ `The document.referrer of a fenced frame after it navigates
+ itself is not censored.`);
+ }, [fenced_frame_url]);
+
+}, "document.referrer");
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/download.https.html b/testing/web-platform/tests/fenced-frame/download.https.html
new file mode 100644
index 0000000000..ae9cebbe05
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/download.https.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<title>Test fenced frame does not allow triggering download</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+<script src="resources/download-helper.js"></script>
+
+<body>
+ <script>
+ const testTypes = [
+ {
+ type: "anchor",
+ description: "Anchor click triggering download in fenced frames is blocked."
+ },
+ {
+ type: "navigation",
+ description: "Navigation resulted download in fenced frames is blocked."
+ }
+ ];
+
+ testTypes.forEach(({type, description}) => {
+ promise_test(async t => {
+ const download_key = token();
+ const download_ack_key = token();
+
+ // The download link is clicked inside the fenced frame after the loading
+ const fenced_frame_url = generateURL("resources/download-inner.html", [download_key, download_ack_key]) + `&type=${type}`;
+ attachFencedFrame(fenced_frame_url);
+
+ const response = await nextValueFromServer(download_ack_key);
+ assert_equals(response, 'Triggered the action for download');
+
+ const result = await VerifyDownload(t, download_key);
+ assert_false(result, 'Expect no download to happen');
+ }, description);
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/embedder-coop-coep-blocked.https.html b/testing/web-platform/tests/fenced-frame/embedder-coop-coep-blocked.https.html
new file mode 100644
index 0000000000..e0e418577d
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/embedder-coop-coep-blocked.https.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>Test COOP/COEP properties set for a Fenced Frame Tree</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const fencedframe = attachFencedFrameContext();
+ const fencedframe_loaded = fencedframe.execute(() => {});
+ const fencedframe_blocked = new Promise(r => t.step_timeout(r, 1000));
+ assert_equals("blocked", await Promise.any([
+ fencedframe_blocked.then(() => "blocked"),
+ fencedframe_loaded.then(() => "loaded"),
+ ]), "fenced frame should not be loaded.");
+}, 'Create a fencedframe without COEP from an embedder setting COEP');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/embedder-coop-coep-blocked.https.html.headers b/testing/web-platform/tests/fenced-frame/embedder-coop-coep-blocked.https.html.headers
new file mode 100644
index 0000000000..807872014a
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/embedder-coop-coep-blocked.https.html.headers
@@ -0,0 +1,2 @@
+cross-origin-opener-policy:same-origin
+cross-origin-embedder-policy: require-corp \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/embedder-csp-not-propagate.https.html b/testing/web-platform/tests/fenced-frame/embedder-csp-not-propagate.https.html
new file mode 100644
index 0000000000..425cc9661e
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/embedder-csp-not-propagate.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<title>Test embedder CSP not propagate to fenced frame</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const iframe = attachIFrameContext(
+ {headers:[["Content-Security-Policy", "frame-src 'self'"]]});
+ await iframe.execute(async() => {
+ const fencedframe = attachFencedFrameContext();
+ await fencedframe.execute(async() => {
+ const nested_iframe = attachIFrameContext(
+ {origin:get_host_info().HTTPS_REMOTE_ORIGIN});
+ await nested_iframe.execute(() => {});
+ });
+ });
+}, 'Embedder CSP should not propagate to fenced frame');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/embedder-no-coep.https.html b/testing/web-platform/tests/fenced-frame/embedder-no-coep.https.html
new file mode 100644
index 0000000000..75980a1301
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/embedder-no-coep.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>Test COEP properties set for a Fenced Frame Tree</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+<script src="resources/embedder-policy.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ const uuid = token();
+ const frame = setupTest("coep:require-corp", uuid);
+ const result = await nextValueFromServer(uuid);
+ assert_equals(result, "PASS", "embedded page has been loaded.");
+}, "Create fencedframe with COEP:require-corp");
+
+promise_test(async () => {
+ const uuid = token();
+ const frame = setupTest("no coep", uuid);
+ const result = await nextValueFromServer(uuid);
+ assert_equals(result, "PASS",
+ "page without COEP should be able to load page with COEP");
+}, "Create fencedframe without COEP header");
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/embedder-require-corp.https.html b/testing/web-platform/tests/fenced-frame/embedder-require-corp.https.html
new file mode 100644
index 0000000000..9432d0cbb5
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/embedder-require-corp.https.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<meta name="timeout" content="long">
+<title>Test COEP properties set for a Fenced Frame Tree</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+<script src="resources/embedder-policy.js"></script>
+
+<body>
+<script>
+promise_test(async (t) => {
+ const uuid = token();
+ const frame = setupTest("coep:require-corp", uuid);
+ const result = await nextValueFromServer(uuid);
+ assert_equals(result, "PASS", "embedded page has been loaded.");
+}, "Create fencedframe with COEP:require-corp");
+
+promise_test(async (t) => {
+ const uuid = token();
+ t.step_timeout(() => t.done(), 1000);
+ const frame = setupTest("no coep", uuid);
+ const result = await nextValueFromServer(uuid);
+ assert_unreached("embedded page should not be loaded.");
+}, "Create fencedframe without COEP header");
+
+promise_test(async (t) => {
+ const uuid = token();
+ // Make sure a different site is used.
+ hostname = get_host_info().REMOTE_HOST;
+ t.step_timeout(() => t.done(), 1000);
+ const frame = setupTest("coep:require-corp", uuid, hostname=hostname);
+ const result = await nextValueFromServer(uuid);
+ assert_unreached("embedded page should not be loaded.");
+}, "Create fencedframe with a cross site COEP:require-corp and CORP:same-orign");
+</script>
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/embedder-require-corp.https.html.headers b/testing/web-platform/tests/fenced-frame/embedder-require-corp.https.html.headers
new file mode 100644
index 0000000000..8df98474b5
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/embedder-require-corp.https.html.headers
@@ -0,0 +1 @@
+cross-origin-embedder-policy: require-corp
diff --git a/testing/web-platform/tests/fenced-frame/fedcm-get-credential.https.html b/testing/web-platform/tests/fenced-frame/fedcm-get-credential.https.html
new file mode 100644
index 0000000000..396da5946b
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/fedcm-get-credential.https.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>Test FedCM navigator.credentials.get()</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async () => {
+ const key = token();
+ attachFencedFrame(
+ generateURL('resources/fedcm-get-credential-inner.https.html', [key]));
+ // Get the result for the fenced frame.
+ const fenced_frame_result = await nextValueFromServer(key);
+ assert_equals(
+ fenced_frame_result,
+ 'navigator.credentials.get failed',
+ 'navigator.credentials.get should fail on fenced frame');
+}, 'navigator.credentials.get');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/fence-api.https.html b/testing/web-platform/tests/fenced-frame/fence-api.https.html
new file mode 100644
index 0000000000..7d350ff75f
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/fence-api.https.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+<title>Test window.fence object</title>
+
+<body>
+<script>
+promise_test(async () => {
+
+ // Check that window.fence is undefined in the top-level frame.
+ assert_true(window.fence == null,
+ "window.fence should only be visible inside fenced frames.");
+ assert_true(fence == null,
+ "fence should only be visible inside fenced frames.");
+
+ // Create a fenced frame.
+ const fence_api_token = token();
+ const frame_url = generateURL("resources/fence-api-inner.https.html",
+ [fence_api_token]);
+ attachFencedFrame(frame_url);
+
+ // Wait for the fenced frame to complete its fence API tests.
+ await nextValueFromServer(fence_api_token);
+
+}, "window.fence");
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/fence-report-event-destination-url.https.html b/testing/web-platform/tests/fenced-frame/fence-report-event-destination-url.https.html
new file mode 100644
index 0000000000..6c0bdd82dc
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/fence-report-event-destination-url.https.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<title>Test window.fence.reportEvent destination URL.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const fencedframe = await attachFencedFrameContext({generator_api: 'fledge'});
+ await fencedframe.execute(() => {
+ // The destinationURL must be a valid URL.
+ let event = {destinationURL: "foobarbaz"};
+ assert_throws_js(TypeError, () => {window.fence.reportEvent(event);});
+
+ // The destinationURL must be an https URL.
+ event.destinationURL = "http://3pat.com";
+ assert_throws_js(TypeError, () => {window.fence.reportEvent(event);});
+
+ event.destinationURL = "https://3pat.com";
+ window.fence.reportEvent(event);
+
+ // `eventType` isn't allowed.
+ event.eventType = 'click';
+ assert_throws_js(TypeError, () => {window.fence.reportEvent(event);});
+ event.eventType = undefined;
+
+ // `eventData` isn't allowed.
+ event.eventData = 'payload';
+ assert_throws_js(TypeError, () => {window.fence.reportEvent(event);});
+ event.eventData = undefined;
+
+ // `destination` isn't allowed.
+ event.destination = ['buyer'];
+ assert_throws_js(TypeError, () => {window.fence.reportEvent(event);});
+ event.destination = undefined;
+ });
+}, 'window.fence.reportEvent destinationURL');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/fence-report-event.https.html b/testing/web-platform/tests/fenced-frame/fence-report-event.https.html
new file mode 100644
index 0000000000..ce217c7763
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/fence-report-event.https.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Test window.fence.reportEvent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const fencedframe = await attachFencedFrameContext({generator_api: 'fledge'});
+ await fencedframe.execute(() => {
+ let event = {};
+ assert_throws_js(TypeError, () => {window.fence.reportEvent(event);});
+
+ event.eventType = "click";
+ assert_throws_js(TypeError, () => {window.fence.reportEvent(event);});
+
+ event.eventData = "dummy";
+ assert_throws_js(TypeError, () => {window.fence.reportEvent(event);});
+
+ event.destination = ["buyer"];
+ window.fence.reportEvent(event);
+ });
+}, 'window.fence.reportEvent');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/fence-urn-iframes.https.html b/testing/web-platform/tests/fenced-frame/fence-urn-iframes.https.html
new file mode 100644
index 0000000000..cdde07329d
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/fence-urn-iframes.https.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<title>Test window.fence availability in iframes.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+
+promise_test(async(t) => {
+ const frame = await attachIFrameContext();
+ await frame.execute(() => {
+ assert_equals(window.fence, null);
+ });
+}, 'window.fence is unavailable in normal (non-urn) iframes');
+
+promise_test(async(t) => {
+ // window.fence works in urn iframes.
+ var frame = await attachIFrameContext({generator_api: 'fledge'});
+ await frame.execute(() => {
+ assert_not_equals(window.fence, null);
+ });
+ // window.fence works after navigating to a new urn.
+ frame = await replaceFrameContext(frame, {generator_api: 'fledge'});
+ await frame.execute(() => {
+ assert_not_equals(window.fence, null);
+ });
+ // window.fence still works after embedder-initiated navigations to non-urns,
+ // because the concept of "embedder-initiated" navigations is nebulous in
+ // iframes.
+ frame = await replaceFrameContext(frame);
+ await frame.execute(() => {
+ assert_not_equals(window.fence, null);
+ });
+}, 'window.fence is available in urn iframes');
+
+promise_test(async(t) => {
+ // window.fence works in urn iframes.
+ const frame = await attachIFrameContext({generator_api: 'fledge'});
+ await frame.execute(async () => {
+ assert_not_equals(window.fence, null);
+ const nested_frame = await attachIFrameContext();
+ await nested_frame.execute(() => {
+ assert_not_equals(window.fence, null);
+ });
+ });
+}, 'window.fence is available in same-origin subframes of urn iframes');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/fledge-container-size-mutation-observer.https.html b/testing/web-platform/tests/fenced-frame/fledge-container-size-mutation-observer.https.html
new file mode 100644
index 0000000000..b6085afce3
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/fledge-container-size-mutation-observer.https.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<title>Test that mutation observer doesn't break noassert container size setter.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+
+async function checkMutationObserver() {
+ // Create a FLEDGE FF with a constant content size and given container size.
+ const requested_width_1 = '299px';
+ const requested_height_1 = '72px';
+ const frame = await attachFencedFrameContext({
+ generator_api: 'fledge', resolve_to_config: true, ad_with_size: true,
+ requested_size: [requested_width_1, requested_height_1]});
+
+ // Install a mutation observer.
+ const config = { attributes: true, childList: true, subtree: true };
+ const callback = (mutationList, observer) => {
+ throw new Error("mutation observed");
+ }
+ const observer = new MutationObserver(callback);
+ observer.observe(frame.element, config);
+
+ // Modify the container size manually.
+ const modified_width = '121px';
+ const modified_height = '444px';
+ frame.element.width = modified_width;
+ frame.element.height = modified_height;
+
+ // Navigate to a new FLEDGE FF config with a different container size.
+ const requested_width_2 = '321px';
+ const requested_height_2 = '49px';
+ const replaced_frame = await replaceFrameContext(frame, {
+ generator_api: 'fledge', resolve_to_config: true, ad_with_size: true,
+ requested_size: [requested_width_2, requested_height_2]});
+
+ observer.disconnect();
+}
+
+setup({allow_uncaught_exception: true});
+promise_test(async () => { return checkMutationObserver(); }, 'Container size assert no exception ignores mutation observer');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/fledge-container-size.https.html b/testing/web-platform/tests/fenced-frame/fledge-container-size.https.html
new file mode 100644
index 0000000000..5347c841d5
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/fledge-container-size.https.html
@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<title>Test container size in FLEDGE fenced frames.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+async function checkSyntaxError(requested_width, requested_height) {
+ try {
+ const frame = await attachFencedFrameContext({
+ generator_api: "fledge", resolve_to_config: true, ad_with_size: true,
+ requested_size: [requested_width, requested_height]});
+ } catch(e) {
+ assert_equals(e.name, 'TypeError');
+ return;
+ }
+ assert_unreached('Missing expected type error');
+}
+
+async function checkSuccess() {
+ const assert_outer_dimensions =
+ (frame, label, expected_width, expected_height) => {
+ assert_equals(frame.element.width, expected_width,
+ label + ' outer attribute width');
+ assert_equals(frame.element.height, expected_height,
+ label + ' outer attribute height');
+ assert_equals(getComputedStyle(frame.element).width, expected_width,
+ label + ' outer computed width');
+ assert_equals(getComputedStyle(frame.element).height, expected_height,
+ label + ' outer computed height');
+ }
+
+ const assert_inner_dimensions =
+ (label, expected_width, expected_height) => {
+ assert_equals(getComputedStyle(document.documentElement).width, expected_width+'px',
+ label + ' inner computed width');
+ assert_equals(window.innerWidth, expected_width,
+ label + ' inner width');
+ assert_equals(window.innerHeight, expected_height,
+ label + ' inner height');
+ }
+
+ // `ad_with_size` is hardcoded to use '100px' by '50px'.
+ const content_width = 100;
+ const content_height = 50;
+
+ // Create a FLEDGE FF with a constant content size and given container size.
+ const requested_width_1 = '299px';
+ const requested_height_1 = '72px';
+ const frame = await attachFencedFrameContext({
+ generator_api: 'fledge', resolve_to_config: true, ad_with_size: true,
+ requested_size: [requested_width_1, requested_height_1]});
+
+ // The outer size should reflect the container size, and the inner size should reflect the content size.
+ await frame.execute(assert_inner_dimensions, ['First config', content_width, content_height]);
+ assert_outer_dimensions(frame, 'First config', requested_width_1, requested_height_1);
+
+ // Modify the container size manually.
+ const modified_width = '121px';
+ const modified_height = '444px';
+ frame.element.width = modified_width;
+ frame.element.height = modified_height;
+
+ // The outer size should reflect the new size, and the inner size should be unchanged.
+ await frame.execute(assert_inner_dimensions, ['Modified container size', content_width, content_height]);
+ assert_outer_dimensions(frame, 'Modified container size', modified_width, modified_height);
+
+ // Navigate to a new FLEDGE FF config with a different container size.
+ const requested_width_2 = '321px';
+ const requested_height_2 = '49px';
+ const replaced_frame = await replaceFrameContext(frame, {
+ generator_api: 'fledge', resolve_to_config: true, ad_with_size: true,
+ requested_size: [requested_width_2, requested_height_2]});
+
+ // The outer size should reflect the new size, and the inner size should be unchanged.
+ await replaced_frame.execute(assert_inner_dimensions, ['Second config', content_width, content_height]);
+ assert_outer_dimensions(replaced_frame, 'Second config', requested_width_2, requested_height_2);
+
+ // Navigate to a new FLEDGE FF config with no container size.
+ const replaced_frame_2 = await replaceFrameContext(frame, {
+ generator_api: 'fledge', resolve_to_config: true, ad_with_size: true});
+
+ // The dimensions should be unchanged.
+ await replaced_frame_2.execute(assert_inner_dimensions, ['Third config', content_width, content_height]);
+ assert_outer_dimensions(replaced_frame_2, 'Third config', requested_width_2, requested_height_2);
+}
+
+// Type error cases.
+promise_test(async () => { return checkSyntaxError('-299px', '72px'); }, '-299px x 72px');
+promise_test(async () => { return checkSyntaxError('299px', '-72px'); }, '299px x -72px');
+promise_test(async () => { return checkSyntaxError('0px', '0px'); }, '0px x 0px');
+promise_test(async () => { return checkSyntaxError('299px', '72ab'); }, '299px x 72ab');
+promise_test(async () => { return checkSyntaxError('299bc', '72px'); }, '299bc x 72px');
+
+// Success cases.
+promise_test(async () => { return checkSuccess(); }, 'FLEDGE successful container size');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/fragment-navigation.https.html b/testing/web-platform/tests/fenced-frame/fragment-navigation.https.html
new file mode 100644
index 0000000000..1d548e35da
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/fragment-navigation.https.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<title>Test that embedder-initiated fragment navigations are forced to be cross-document.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+async function runTest(first_url_opaque, second_url_opaque) {
+ const frame = await attachFencedFrameContext({generator_api: 'fledge'});
+ let base_url = frame.src;
+ let fragment_url = base_url + "#foo";
+
+ if (first_url_opaque) {
+ base_url = await generateURNFromFledge(base_url, []);
+ }
+
+ if (second_url_opaque) {
+ fragment_url = await generateURNFromFledge(fragment_url, []);
+ }
+
+ // Start the fenced frame at about:blank.
+ await frame.execute(() => {
+ window.executor.suspend(() => { location.href = "about:blank"; });
+ });
+
+ // Navigate the fenced frame to the base url from the embedder, and then
+ // suspend the remote executor.
+ frame.src = base_url;
+ await frame.execute(() => { window.executor.suspend(() => {}); });
+
+ // Navigate the fenced frame to the fragment url from the embedder. Now
+ // the remote executor will only exist if the navigation wasn't considered
+ // same-document.
+ frame.src = fragment_url;
+ await frame.execute(() => {});
+}
+
+promise_test(async () => { await runTest(true, true); },
+ "opaque to opaque fragment navigation");
+promise_test(async () => { await runTest(true, false); },
+ "opaque to transparent fragment navigation");
+promise_test(async () => { await runTest(false, true); },
+ "transparent to opaque fragment navigation");
+promise_test(async () => { await runTest(false, false); },
+ "transparent to transparent fragment navigation");
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/frame-navigation.https.html b/testing/web-platform/tests/fenced-frame/frame-navigation.https.html
new file mode 100644
index 0000000000..f3464c94e9
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/frame-navigation.https.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<title>Test fenced frame navigations (by a parent frame setting its src). </title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async () => {
+ // This test checks that fenced frames can be navigated (given a new src).
+ // The access pattern is as follows, to exercise same- and cross-origin
+ // navigations in root and nested fenced frames.
+
+ // [root] [nested]
+ // simple (origin 1)
+ // create-nested (origin 2)
+ // simple (origin 1)
+ // simple (origin 2)
+ // simple (origin 2)
+ // simple (origin 2)
+
+ const navigation_key = token();
+ const navigation_ack_key = token();
+
+ // Create URL prefixes to simulate different origins.
+ // (www1 and www2 are different origins)
+ const simple_url = generateURL(
+ "resources/frame-navigation-inner-simple.https.html",
+ [navigation_key, navigation_ack_key]);
+ const nested_url = generateURL(
+ "resources/frame-navigation-inner-create-nested.https.html",
+ [navigation_key, navigation_ack_key]);
+ const origin1_simple_url = getRemoteOriginURL(simple_url);
+ const origin2_nested_url = getRemoteOriginURL(nested_url)
+ .toString().replace("www1", "www2");
+ const origin2_simple_url = getRemoteOriginURL(simple_url)
+ .toString().replace("www1", "www2");
+
+ // Create a root fenced frame.
+ root_frame = attachFencedFrame(origin1_simple_url);
+ const root_load1_actual = await nextValueFromServer(navigation_key);
+ const root_load1_expected = "pass";
+ assert_equals(root_load1_actual, root_load1_expected,
+ "The 1st root fenced frame navigation succeeded");
+
+ // Navigate the root fenced frame to a second site (which will nest).
+ root_frame.config = new FencedFrameConfig(origin2_nested_url);
+ const root_load2_actual = await nextValueFromServer(navigation_key);
+ const root_load2_expected = "create-nested";
+ assert_equals(root_load2_actual, root_load2_expected,
+ "The 2nd root fenced frame navigation (cross-origin) succeeded");
+ writeValueToServer(navigation_ack_key, "ACK");
+
+ // Check that all of the nested navigations succeed.
+ const nested_load1_actual = await nextValueFromServer(navigation_key);
+ const nested_load1_expected = "pass";
+ assert_equals(nested_load1_actual, nested_load1_expected,
+ "The 1st nested fenced frame navigation succeeded");
+ writeValueToServer(navigation_ack_key, "ACK");
+
+ const nested_load2_actual = await nextValueFromServer(navigation_key);
+ const nested_load2_expected = "pass";
+ assert_equals(nested_load2_actual, nested_load2_expected,
+ "The 2nd nested fenced frame navigation (cross-origin) succeeded");
+ writeValueToServer(navigation_ack_key, "ACK");
+
+ const nested_load3_actual = await nextValueFromServer(navigation_key);
+ const nested_load3_expected = "pass";
+ assert_equals(nested_load3_actual, nested_load3_expected,
+ "The 3rd nested fenced frame navigation (same-origin) succeeded");
+ writeValueToServer(navigation_ack_key, "ACK");
+
+ // Navigate the root fenced frame.
+ root_frame.config = root_frame.config = new FencedFrameConfig(origin2_simple_url);
+ const root_load3_actual = await nextValueFromServer(navigation_key);
+ const root_load3_expected = "pass";
+ assert_equals(root_load3_actual, root_load3_expected,
+ "The 3rd root fenced frame navigation (same-origin) succeeded");
+
+}, "Fenced frame navigation should succeed");
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/gamepad.https.html b/testing/web-platform/tests/fenced-frame/gamepad.https.html
new file mode 100644
index 0000000000..ef6b91e921
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/gamepad.https.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Gamepad API test</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ const key = token();
+ attachFencedFrame(generateURL('resources/gamepad-inner.html', [key]));
+ const result = await nextValueFromServer(key);
+ assert_equals(
+ result,
+ 'SecurityError',
+ 'getGamepads should throw an error in a fenced frame');
+}, 'Gamepads information should not be read in the fenced frame.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/get-mode-in-nested-frame.https.html b/testing/web-platform/tests/fenced-frame/get-mode-in-nested-frame.https.html
new file mode 100644
index 0000000000..d0888ce2e5
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/get-mode-in-nested-frame.https.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>Test GetFencedFrameMode() on a nested nested iframe.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ // This test has the following setup:
+ // root -> (fencedframe) -> (first_iframe) -> (second_iframe)
+ // (second_iframe) will attempt an _unfencedTop navigation, which will result
+ // in GetFencedframeMode() being called. For this test to pass, it shouldn't
+ // timeout.
+ const fencedframe = await attachFencedFrameContext(
+ {generator_api: 'fledge'});
+ await fencedframe.execute(async () => {
+ const first_iframe = attachIFrameContext();
+ await first_iframe.execute(async () => {
+ const second_iframe = attachIFrameContext();
+ await second_iframe.execute(async () => {
+ // This call will cause FrameTreeNode::GetFencedFrameMode() to be called
+ // for second_iframe. If the browser process doesn't hang (and timeout
+ // the test), then the test passes.
+ window.open("https://www.google.com", "_unfencedTop");
+ });
+ });
+ });
+}, 'Trigger GetFencedFrameMode() in a nested nested iframe');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/get-nested-configs.https.html b/testing/web-platform/tests/fenced-frame/get-nested-configs.https.html
new file mode 100644
index 0000000000..2876e52d14
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/get-nested-configs.https.html
@@ -0,0 +1,124 @@
+<!DOCTYPE html>
+<title>window.fence.getNestedConfigs() test</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+
+<body>
+<script>
+promise_test(async (t) => {
+ const key = token();
+ const urn = await generateURNFromFledge(
+ "resources/get-nested-configs-inner.html", [key]);
+ attachFencedFrame(urn);
+
+ const response = await nextValueFromServer(key);
+ const [length] = response.split(",");
+
+ assert_equals(length, '40', 'There should be 40 nested configurations.');
+}, 'getNestedConfigs() created by FLEDGE should return configurations');
+
+for (const resolve_to_config of [true, false]) {
+ promise_test(async (t) => {
+ const key = token();
+ const select_url_result = await runSelectURL(
+ generateURL("resources/get-nested-configs-inner.html", [key]),
+ [], resolve_to_config);
+ attachFencedFrame(select_url_result);
+
+ const response = await nextValueFromServer(key);
+ const [length, first_url] = response.split(",");
+
+ assert_equals(length, '0', 'There should be 0 nested configurations.');
+ }, 'getNestedConfigs() from a fenced frame with the ' +
+ (resolve_to_config ? 'config' : 'urn:uuid') +
+ ' from sharedStroage.selectURL() should be empty');
+}
+
+promise_test(async (t) => {
+ const key = token();
+ const url = generateURL("resources/get-nested-configs-inner.html", [key]);
+ attachFencedFrame(url, mode='default');
+
+ const response = await nextValueFromServer(key);
+ const [length, first_url] = response.split(",");
+
+ assert_equals(length, '0', 'There should be 0 nested configurations.');
+}, 'getNestedConfigs() from a default mode frame should be empty');
+
+promise_test(async (t) => {
+ const key = token();
+ const urn = await generateURNFromFledge(
+ "resources/get-nested-configs-nested-iframe.html", [key]);
+ attachFencedFrame(urn);
+
+ const response = await nextValueFromServer(key);
+ const [length, first_url] = response.split(",");
+
+ assert_equals(length, '40', 'There should be 40 nested configurations.');
+}, 'getNestedConfigs() should work in a same-origin nested iframe');
+
+promise_test(async (t) => {
+ const key = token();
+
+ const nested_url = generateURL("resources/embeddee.html", [key]);
+
+ // Navigate a fenced frame to `navigate-nested-config.html`. That page will
+ // in turn create a nested fenced frame which will be navigated to the URN of
+ // the first item in the nested configs list, `nested_url`.
+ const urn = await generateURNFromFledge(
+ "resources/navigate-nested-config.html", [key],
+ [nested_url]);
+ attachFencedFrame(urn);
+
+ const response = await nextValueFromServer(key);
+ assert_equals(response, 'PASS', 'The nested URL should load.');
+}, 'Nested configs created by FLEDGE should be navigable by fenced frame');
+
+promise_test(async (t) => {
+ const key = token();
+
+ const nested_url = generateURL("resources/embeddee.html", [key]);
+
+ // Navigate a fenced frame to `navigate-nested-config.html`. That page will
+ // in turn create a nested fenced frame which will be navigated to the URN of
+ // the first item in the nested configs list, `nested_url`.
+ const urn = await generateURNFromFledge(
+ "resources/navigate-nested-config.html", [key],
+ [nested_url]);
+ attachIFrame(urn);
+
+ const response = await nextValueFromServer(key);
+ assert_equals(response, 'PASS', 'The nested URL should load.');
+}, 'Nested configs created by FLEDGE should be navigable by URN iframe');
+
+promise_test(async (t) => {
+ const key = token();
+
+ const nested_url = generateURL("resources/embeddee.html", [key]);
+
+ // Navigate a fenced frame to `navigate-nested-config.html`. That page will
+ // in turn create a nested fenced frame which will be navigated to the URN of
+ // the first item in the nested configs list, `nested_url`. Since this URN
+ // is invalid, the navigation should gracefully fail.
+ const urn = await generateURNFromFledge(
+ "resources/navigate-nested-config.html", [key],
+ []);
+ attachFencedFrame(urn);
+
+ // There is no API to observe whether the document in the FencedFrame loaded
+ // or not. Instead, set up a timeout. If the document loads, "PASS" will be
+ // sent to the server. Otherwise "BLOCKED" will be sent after 1 second.
+ step_timeout(() => {
+ writeValueToServer(key, "BLOCKED");
+ }, 1000);
+
+ const response = await nextValueFromServer(key);
+ assert_equals(response, 'BLOCKED', 'The nested URL should not load.');
+}, 'Navigating an invalid config should be handled gracefully');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/header-referrer.https.html b/testing/web-platform/tests/fenced-frame/header-referrer.https.html
new file mode 100644
index 0000000000..b6323962d8
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/header-referrer.https.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<title>Test `Referer` header</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async () => {
+ const referrer_key = token();
+ const referrer_ack_key = token();
+
+ const check_url = generateURL('resources/check-header-referrer.py',
+ [referrer_key])
+ attachFencedFrame(check_url);
+
+ const expected_referrer = "";
+ const actual_referrer = await nextValueFromServer(referrer_key);
+ assert_equals(actual_referrer, expected_referrer,
+ "The top-level fenced frame has the right value for " +
+ "`Referer` header");
+
+ const inner_url = generateURL('resources/header-referrer-inner.html',
+ [referrer_key, referrer_ack_key])
+ attachFencedFrame(inner_url);
+
+ const iframe_expected_referrer = inner_url.toString().replace("%2C", ",");
+
+ const iframe_actual_referrer = await nextValueFromServer(referrer_key);
+ assert_equals(iframe_actual_referrer, iframe_expected_referrer,
+ "The iframe inside the fenced frame has the right value for " +
+ "`Referer` header");
+
+ writeValueToServer(referrer_ack_key, "ACK");
+
+ const nested_fenced_frame_expected_referrer = "";
+ const nested_fenced_frame_actual_referrer = await nextValueFromServer(referrer_key);
+ assert_equals(nested_fenced_frame_actual_referrer, nested_fenced_frame_expected_referrer,
+ "The nested fenced frame has the right value for " +
+ "`Referer` header");
+
+ writeValueToServer(referrer_ack_key, "ACK");
+
+ const navigate_self_expected_referrer =
+ inner_url.toString().replace("%2C", ",");
+ const navigate_self_actual_referrer = await nextValueFromServer(referrer_key);
+ assert_equals(navigate_self_actual_referrer, navigate_self_expected_referrer,
+ "The fenced frame after it navigates itself has the right value for " +
+ "`Referer` header");
+}, "header.referrer");
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/header-secFetchDest.https.html b/testing/web-platform/tests/fenced-frame/header-secFetchDest.https.html
new file mode 100644
index 0000000000..70b8c32749
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/header-secFetchDest.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<title>Test `Sec-Fetch-Dest` header</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async () => {
+ const sec_fetch_dest_value_key = token();
+ const https_origin_url =
+ getRemoteOriginURL(
+ generateURL(
+ 'resources/check-header-sec-fetch-dest.py',
+ [sec_fetch_dest_value_key]));
+ attachFencedFrame(https_origin_url);
+
+ // Get the result for the top-level fenced frame.
+ const actual_result = await nextValueFromServer(sec_fetch_dest_value_key);
+ assert_equals(actual_result, "b'fencedframe'", "The fenced frame " +
+ "has the right value for " +
+ "`Sec-Fetch-Dest` header");
+
+ const inner_url = generateURL("resources/header-secFetchDest-inner.html",
+ [sec_fetch_dest_value_key]);
+ attachFencedFrame(inner_url);
+
+ const iframe_actual_result = await nextValueFromServer(sec_fetch_dest_value_key);
+ assert_equals(iframe_actual_result, "b'fencedframe'",
+ "The iframe inside the fenced frame has the right value for " +
+ "`Sec-Fetch-Dest` header");
+}, "header.secFetchDest");
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/hid.https.html b/testing/web-platform/tests/fenced-frame/hid.https.html
new file mode 100644
index 0000000000..762ed6715a
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/hid.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Test of Web Bluetooth API</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 src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+
+<body>
+
+<script>
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ const result = await frame.execute(async () => {
+ await simulateGesture();
+ try {
+ await navigator.hid.getDevices();
+ return 'HID getDevice succeeded';
+ } catch(e) {
+ if (e.name == 'SecurityError' &&
+ e.message.includes(
+ 'Access to the feature "hid" is disallowed by permissions policy.')) {
+ return 'HID getDevice failed';
+ } else {
+ return `HID getDevice failed with unknown error - ${e.name}: ${e.message}`;
+ }
+ }
+ });
+ assert_equals(result, 'HID getDevice failed',
+ 'HID getDevice must fail in a fenced frame.');
+}, 'HID getDevice must fail in a fenced frame');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/history-back-and-forward-should-not-work-in-fenced-tree.https.html b/testing/web-platform/tests/fenced-frame/history-back-and-forward-should-not-work-in-fenced-tree.https.html
new file mode 100644
index 0000000000..df50a1d223
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/history-back-and-forward-should-not-work-in-fenced-tree.https.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+<title>history-back-and-forward-should-not-work-in-fenced-tree</title>
+
+<body>
+ <script>
+ // TODO(crbug.com/1262022): Remove references to ShadowDOM now that the
+ // implementation is obsolete.
+ const test_desc = "history.back() and history.forward() should be " +
+ "restricted within a fenced tree in shadowDOM thus have" +
+ " no effect when called within a fenced tree scope.";
+ const history_navigation_performed_key = token();
+ const outer_page_ready_key = token();
+
+ // attach a fenced frame to exeute a series of back and forward history
+ // navigations.
+ attachFencedFrame(
+ generateURL(
+ "resources/history-back-and-forward-should-not-work-in-" +
+ "fenced-tree-inner.html",
+ [history_navigation_performed_key, outer_page_ready_key]));
+
+ promise_test(async function () {
+ await nextValueFromServer(history_navigation_performed_key);
+
+ // Perform a series of history.pushState() to help observe any popstate due
+ // to back and forward history navigations.
+ window.history.pushState(1, document.title, '#tag1');
+ window.history.pushState(2, document.title, '#tag2');
+ window.history.pushState(3, document.title, '#tag3');
+
+ writeValueToServer(outer_page_ready_key, "yes");
+
+ // Assert restricted history.back() within fenced frame.
+ await nextValueFromServer(history_navigation_performed_key);
+ assert_equals(window.history.state, 3, "history.back() should be " +
+ "restricted and will not work when called from a fenced frame.");
+
+ writeValueToServer(outer_page_ready_key, "yes");
+
+ // Assert restricted history.forward() within fenced frame.
+ await nextValueFromServer(history_navigation_performed_key);
+ assert_equals(window.history.state, 3, "history.forward() should be " +
+ "restricted and will not work when called from a fenced frame.");
+
+ writeValueToServer(outer_page_ready_key, "yes");
+
+ // Assert restricted history.back() within iframe in fenced frame.
+ await nextValueFromServer(history_navigation_performed_key);
+ assert_equals(window.history.state, 3, "history.back() should be " +
+ "restricted and will not work within an iframe embeeded in a fenced " +
+ "frame.");
+
+ writeValueToServer(outer_page_ready_key, "yes");
+
+ // Assert restricted history.forward() within iframe in fenced frame.
+ await nextValueFromServer(history_navigation_performed_key);
+ assert_equals(window.history.state, 3, "history.forward() should be " +
+ "restricted and will not work within an iframe embeeded in a fenced " +
+ "frame.");
+ }, test_desc);
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/history-length-fenced-navigations-replace-do-not-contribute-to-joint.https.html b/testing/web-platform/tests/fenced-frame/history-length-fenced-navigations-replace-do-not-contribute-to-joint.https.html
new file mode 100644
index 0000000000..bd1d5f7309
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/history-length-fenced-navigations-replace-do-not-contribute-to-joint.https.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<title>Test history.length in outermost document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ const kInnerAssertion = "Navigations inside of a fenced frame are always " +
+ "replacement navigations and never increase " +
+ "`history.length` inside the fence: ";
+ const kOuterAssertion = "Navigations inside of a fenced frame never " +
+ "contribute to joint session history, or increase " +
+ "`history.length` outside the fence: ";
+ // This is used by
+ // `resources/history-length-fenced-navigations-replace-do-not-contribute-to-joint-inner.html`
+ // to let us know once a navigation is complete inside the fence. The inner
+ // page will test its `history.length` and report to us "PASS <frame type>" or
+ // "FAIL <frame type>" (the history length should be unaffected by the
+ // navigation). If the inner test passes, then we will check our
+ // `history.length` and ensure that the inner fenced navigation did not
+ // contribute to the joint session history.
+ const fenced_navigation_complete_key = token();
+ // This is sent by us to the inner page to let it know that we're finished
+ // observing the effects of the last navigation, and that it should perform
+ // the next one for us to observe.
+ const outer_page_ready_for_next_fenced_navigation_key = token();
+
+ const level = "top-level-fenced-frame";
+
+ attachFencedFrame(generateURL(
+ "resources/history-length-fenced-navigations-replace-do-" +
+ "not-contribute-to-joint-inner.html",
+ [fenced_navigation_complete_key,
+ outer_page_ready_for_next_fenced_navigation_key,
+ level]));
+
+ const tests = ["top-level-fenced-frame", "nested-fenced-frame", "nested-iframe"];
+ for (test_type of tests) {
+ // Wait for the fenced navigations to complete, and then see if they
+ // observable via the outermost `history.length`.
+ let result = await nextValueFromServer(fenced_navigation_complete_key);
+ assert_equals(result, "PASS > " + test_type, kInnerAssertion + test_type);
+ assert_equals(history.length, 1, kOuterAssertion + test_type);
+
+ // Acknowledge the results, and let the fenced frame know that we're ready
+ // to observe more fenced navigations.
+ writeValueToServer(outer_page_ready_for_next_fenced_navigation_key, "READY");
+ }
+}, "All fenced navigations should be replace-only and not contribute to joint " +
+ "session history");
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/history-length-outer-page-navigation-not-reflected-in-fenced.https.html b/testing/web-platform/tests/fenced-frame/history-length-outer-page-navigation-not-reflected-in-fenced.https.html
new file mode 100644
index 0000000000..da64304031
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/history-length-outer-page-navigation-not-reflected-in-fenced.https.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+<title>history-length-outer-page-navigation-not-reflected-in-fenced</title>
+
+<body>
+<script>
+const fenced_history_length_key = token();
+const outer_page_ready_for_next_navigation_key = token();
+
+async function runTest(embed_scope_reporting) {
+ const kAssertionDesc = "history.length Should never reflect the length " +
+ "of joint session history that occured outside " +
+ "of the fenced frame tree. history.length will " +
+ "always return 1."
+
+ ////////////// BEGIN NAVIGATIONS
+ // This block performs a sequence of 'kNavigationLimit' navigations in:
+ // -- the outer page
+ const kNavigationLimit = 5
+
+ const url = new URL(location.href);
+
+ // First, perform some real navigations as well as history.pushState to this
+ // same page. Normally this would increase `history.length`.
+ if (url.searchParams.get("navigationCount") == null)
+ url.searchParams.append("navigationCount", 1);
+
+ let navigationCount = parseInt(url.searchParams.get("navigationCount"));
+
+ if (navigationCount <= kNavigationLimit) {
+ url.searchParams.set('navigationCount', ++navigationCount);
+ location.href = url;
+ history.pushState({} , "");
+ return;
+ }
+ ////////////// END
+
+ // Append an iframe to the outer page for subsequent navigations within
+ // the iframe
+ const iframe = document.createElement('iframe');
+ const embed_scope_iframe = "outer_page::iframe";
+ iframe.src = generateURL("resources/history-length-outer-page-navigation-" +
+ "not-reflected-in-fenced-inner.html",
+ [fenced_history_length_key, outer_page_ready_for_next_navigation_key,
+ embed_scope_iframe, null]);
+ document.body.append(iframe);
+
+ await nextValueFromServer(outer_page_ready_for_next_navigation_key);
+
+ // Append a fenced frame to observe 'history.length' and report it back
+ // to the outer page
+ const embed_scope_fenced_frame = "outer_page::fenced_frame";
+ attachFencedFrame(generateURL(
+ "resources/history-length-outer-page-navigation-" +
+ "not-reflected-in-fenced-inner.html",
+ [fenced_history_length_key, outer_page_ready_for_next_navigation_key,
+ embed_scope_fenced_frame, embed_scope_reporting])
+ );
+
+ // Wait for the 'embed_scope_reporting' to report 'history.length'
+ let result = await nextValueFromServer(fenced_history_length_key);
+ assert_equals(result, "PASS > " + " history.length: 1", kAssertionDesc);
+}
+
+promise_test(async () => {
+ await runTest("outer_page::fenced_frame");
+}, "history.length should not reflect navigations within outer page in " +
+ "fenced frame");
+
+promise_test(async () => {
+ await runTest("outer_page::fenced_frame::iframe");
+}, "history.length should not reflect navigations within outer page in " +
+ "fenced frame nexted iframe");
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/ignore-child-fenced-frame-onload-event.https.html b/testing/web-platform/tests/fenced-frame/ignore-child-fenced-frame-onload-event.https.html
new file mode 100644
index 0000000000..a542c25909
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/ignore-child-fenced-frame-onload-event.https.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>Ignore child fenced frame onload event</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+</body>
+<script>
+ promise_test(async () => {
+ const toplevel_loaded_key = token();
+ const result_key = token();
+
+ // Appends a fencedframe to the top-level doc.
+ attachFencedFrame(generateURL("resources/ignore-child-fenced-frame-onload" +
+ "-event-inner.html", ["fencedframe", toplevel_loaded_key, result_key]));
+
+ // Relays the messsage to the fenced frame when the onload event is fired.
+ window.onload = function () {
+ writeValueToServer(toplevel_loaded_key, "yes");
+ }
+
+ assert_equals(await nextValueFromServer(result_key), "passed",
+ "The parent frame onload event should not be blocked by any onload " +
+ "event from subframes belonging to fenced tree, but should be blocked " +
+ "by that of any non fenced subframe");
+
+ }, "ignore child fenced frame onload event test.");
+</script>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/insecure-context.html b/testing/web-platform/tests/fenced-frame/insecure-context.html
new file mode 100644
index 0000000000..44db6432a4
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/insecure-context.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>Insecure Context Test</title>
+<script src="/common/utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async t => {
+ const loaded_key = token();
+ attachFencedFrame(generateURL(
+ "resources/csp-fenced-frame-src-allowed-inner.html", [loaded_key]));
+
+ // There is no API to observe whether the document in the FencedFrame loaded
+ // or not. Instead, set up a timeout. If the document loads, "loaded" will be
+ // sent to the server. Otherwise "blocked" will be sent after 3 seconds.
+ step_timeout(() => {
+ writeValueToServer(loaded_key, "blocked");
+ }, 3000);
+
+ const message = await nextValueFromServer(loaded_key);
+
+ assert_equals(message, "blocked");
+}, 'FencedFrame is not available in an insecure context');
+
+promise_test(async t => {
+ assert_throws_js(TypeError, () => navigator.canLoadAdAuctionFencedFrame());
+}, 'navigator.canLoadAdAuctionFencedFrame is unavailable in insecure contexts');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/intersection-observer.https.html b/testing/web-platform/tests/fenced-frame/intersection-observer.https.html
new file mode 100644
index 0000000000..592e7c127f
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/intersection-observer.https.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<title>Test Intersection Observer in fenced frame</title>
+<script src="/common/rendering-utils.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+<style>
+fencedframe {
+ width: 100px;
+ height: 100px;
+ position: fixed;
+ top: 0px;
+ left: 0px;
+ border: unset;
+}
+</style>
+
+<body>
+<script>
+promise_test(async (t) => {
+ // first entry after observe.
+ const io_entry_on_registration = token();
+ // entry after transform.
+ const io_entry_on_transform = token();
+ // entry with clip.
+ const io_entry_on_clip = token();
+
+ const frame = attachFencedFrame(generateURL(
+ "resources/frame-with-intersection-observer.html",
+ [io_entry_on_registration, io_entry_on_transform, io_entry_on_clip]));
+
+ let result = await nextValueFromServer(io_entry_on_registration);
+ assert_equals(result, "0,0,100,100,true",
+ "Subscribing to IO dispatches a notification");
+
+ // Apply a transform to the fencedframe and ensure it gets applied to the
+ // intersectionRect.
+ frame.style.transform = 'translate(-10px, -20px)';
+ result = await nextValueFromServer(io_entry_on_transform);
+ assert_equals(result, "10,20,90,80,true",
+ "Transform applies to intersection rect");
+
+ // Now add a clip to the fencedframe which should clip the intersectionRect.
+ frame.style.clipPath = 'inset(10px)';
+ result = await nextValueFromServer(io_entry_on_clip);
+ assert_equals(result, "10,20,80,70,false", "Clip applies to intersection rect");
+}, 'Intersection Observer Test');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/invalid-url.https.html b/testing/web-platform/tests/fenced-frame/invalid-url.https.html
new file mode 100644
index 0000000000..4e17185d81
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/invalid-url.https.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<title>Test Navigate Fenced Frame to Invalid URL</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+<script>
+promise_test(async (t) => {
+ attachFencedFrame('http://localhost:95101/fenced_frame.php');
+}, 'URL with invalid port specified');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/key-scrolling.https.html b/testing/web-platform/tests/fenced-frame/key-scrolling.https.html
new file mode 100644
index 0000000000..7a23a72cb0
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/key-scrolling.https.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<title>Test keyboard scroll bubbling from a fenced frame.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<style>
+ body {
+ /* Make main frame scrollable */
+ width: 200vw;
+ height: 200vh;
+ }
+</style>
+
+<body>
+<script>
+
+// Ensure keyboard scrolling from inside a fenced frame is bubbled out to the
+// embedding frame.
+promise_test(async t => {
+ const frame = attachFencedFrameContext({html: `
+ <!DOCTYPE html>
+ <style>
+ body {
+ /* Make fenced frame scrollable */
+ width: 200vw;
+ height: 200vh;
+ }
+ </style>
+ <script src="/resources/testdriver.js"><\/script>
+ <script src="/resources/testdriver-actions.js"><\/script>
+ <script src="/resources/testdriver-vendor.js"><\/script>
+ `});
+
+ // Scroll the fenced frame to its full extent so that left/down arrow key
+ // scrolling will bubble to the embedder.
+ await frame.execute(async () => {
+ window.scrollTo(10000, 10000);
+ });
+
+ assert_equals(window.scrollX, 0, '[PRECONDITION] main frame has no x scroll.');
+ assert_equals(window.scrollY, 0, '[PRECONDITION] main frame has no y scroll.');
+
+ // Simulate a right arrow and down arrow key in the fenced frame.
+ await frame.execute(async () => {
+ const arrow_right = "\uE014";
+ const arrow_down = "\uE015";
+
+ test_driver.send_keys(document.body, arrow_right);
+ test_driver.send_keys(document.body, arrow_down);
+ });
+
+ // Use step_wait to poll since the scroll may be executed asynchronously
+ // (e.g. IPC to embedder, scroll animation).
+ await t.step_wait(() => window.scrollX > 0, "Wait for horizontal scroll.");
+ assert_greater_than(window.scrollX, 0, 'Horizontal scroll should bubble to main frame.');
+
+ await t.step_wait(() => window.scrollY > 0, "Wait for vertical scroll.");
+ assert_greater_than(window.scrollY, 0, 'Vertical scroll should bubble to main frame.');
+}, 'Keyboard scrolling bubbles out of fenced frame');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/key-value-store.https.html b/testing/web-platform/tests/fenced-frame/key-value-store.https.html
new file mode 100644
index 0000000000..ba6b1c0a4f
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/key-value-store.https.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<title>Test the key value store</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async () => {
+ const key = token();
+ const test_value = "TESTVALUE";
+
+ let server_value = await readValueFromServer(key);
+ assert_false(server_value.status,
+ "The server returns a sentinel value when requesting a value " +
+ "that the stash does not have");
+
+ server_value = await readValueFromServer(key);
+ assert_false(server_value.status,
+ "Requesting a not-set value twice is idempotent");
+
+ writeValueToServer(key, "");
+ server_value = await nextValueFromServer(key);
+ assert_equals(server_value, "",
+ "The server correctly identifies that an empty string was " +
+ "set, and returns it, and not the sentinel value");
+
+ writeValueToServer(key, test_value);
+ server_value = await nextValueFromServer(key);
+ assert_equals(server_value, test_value,
+ "The server correctly sets and returns non-empty strings");
+
+ writeValueToServer(key, "");
+ server_value = await nextValueFromServer(key);
+ assert_equals(server_value, "",
+ "The server correctly identifies empty strings after dealing " +
+ "with non-empty-string values");
+}, "Test the key-value store, specifically with empty strings");
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/load-ad-with-size.https.html b/testing/web-platform/tests/fenced-frame/load-ad-with-size.https.html
new file mode 100644
index 0000000000..c06205f056
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/load-ad-with-size.https.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>Fenced frames loading a winning ad from FLEDGE auction with size</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+ <script>
+ promise_test(async (t) => {
+ var frame = await attachFencedFrameContext({
+ generator_api: "fledge",
+ resolve_to_config: true,
+ ad_with_size: true
+ });
+
+ const assert_dimensions =
+ (expected_width, expected_height) => {
+ getComputedStyle(document.documentElement).width; // Force layout.
+ assert_equals(window.innerWidth, expected_width, "width");
+ assert_equals(window.innerHeight, expected_height, "height");
+ }
+ await frame.execute(assert_dimensions, [100, 50]);
+ }, "Fenced frame loading an ad with size.");
+ </script>
+</body>
+
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/loading.https.html b/testing/web-platform/tests/fenced-frame/loading.https.html
new file mode 100644
index 0000000000..2f39af0395
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/loading.https.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Fenced frames loading tests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+<script>
+ for (const resolve_to_config of [true, false]) {
+ promise_test(async (t) => {
+ const key = token();
+
+ attachFencedFrame(await runSelectURL("resources/embeddee.html", [key],
+ resolve_to_config));
+ const result = await nextValueFromServer(key);
+
+ assert_equals(result, "PASS",
+ "The fenced frame with src=urn:uuid should load");
+ }, "fenced frame loading " +
+ (resolve_to_config ? "a config." : "an urn:uuid."));
+ }
+</script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/location-ancestorOrigins.https.html b/testing/web-platform/tests/fenced-frame/location-ancestorOrigins.https.html
new file mode 100644
index 0000000000..f72a668bdc
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/location-ancestorOrigins.https.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<title>Test location.ancestorOrigins</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async () => {
+ const location_ao_key = token();
+ const location_ao_ack_key = token();
+
+ // We load the top-level fenced frame in a cross-origin, so that we can
+ // more-completely verify the important cross-origin
+ // `location.ancestorOrigins` case.
+ const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
+ const cross_origin_fenced_frame = getRemoteOriginURL(generateURL(
+ 'resources/location-ancestorOrigins-inner.https.html',
+ [location_ao_key, location_ao_ack_key]
+ ), https=true);
+ attachFencedFrame(cross_origin_fenced_frame);
+
+ // Get the result for the top-level fenced frame.
+ const fenced_frame_result = await nextValueFromServer(location_ao_key);
+ assert_equals(fenced_frame_result, "", "The top-level fenced frame has the " +
+ "right ancestor origins");
+
+ // Write an ACK, so that the fenced frame knows it can send message over the
+ // `window_parent_key` channel again.
+ writeValueToServer(location_ao_ack_key, "ACK");
+
+ // Get the result for the iframe inside the fenced frame.
+ const iframe_in_fenced_frame_result = await nextValueFromServer(location_ao_key);
+ assert_equals(iframe_in_fenced_frame_result, cross_origin, "The iframe " +
+ "inside the top-level fenced frame has the right " +
+ "ancestor origins");
+
+ writeValueToServer(location_ao_ack_key, "ACK");
+
+ // Get the result for the nested fenced frame.
+ const nested_fenced_frame_result = await nextValueFromServer(location_ao_key);
+ assert_equals(nested_fenced_frame_result, "", "The nested fenced frame " +
+ "inside the top-level fenced frame has the right ancestor " +
+ "origins");
+}, "location.ancestorOrigins");
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/mediaDevices-setCaptureHandle.https.html b/testing/web-platform/tests/fenced-frame/mediaDevices-setCaptureHandle.https.html
new file mode 100644
index 0000000000..fcc95a401f
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/mediaDevices-setCaptureHandle.https.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>Test MediaDevice navigator.mediaDevices.setCaptureHandleConfig()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ await frame.execute(async () => {
+ // If capture handle is set inside the fenced frame, it should fail because
+ // it should not be the top-level browsing context.
+ // https://w3c.github.io/mediacapture-handle/identity/index.html#set-capture-handle-config
+ try {
+ navigator.mediaDevices.setCaptureHandleConfig({
+ handle: 'dummyhandle',
+ permittedOrigins: ["*"],
+ });
+ throw 'The setCaptureHandleConfig request should not succeed.';
+ } catch (e) {
+ assert_equals(e.name, 'InvalidStateError');
+ assert_equals(e.message,
+ "Failed to execute 'setCaptureHandleConfig' on 'MediaDevices': " +
+ 'Can only be called from the top-level document.');
+ }
+ });
+}, 'navigator.mediaDevices.setCaptureHandleConfig');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/multiple-component-ads.https.html b/testing/web-platform/tests/fenced-frame/multiple-component-ads.https.html
new file mode 100644
index 0000000000..81d506842e
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/multiple-component-ads.https.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>Test loading multiple component ads</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const fencedframe = await attachFencedFrameContext({
+ generator_api: 'fledge',
+ num_components: 2,
+ });
+
+ await fencedframe.execute(async () => {
+ const ad_component_1 = await attachComponentFencedFrameContext(0);
+ const ad_component_2 = await attachComponentFencedFrameContext(1);
+
+ await ad_component_1.execute(() => {
+ window.component_1_var = 4;
+ });
+
+ // Check that ad_component_2 is its own separate frame.
+ await ad_component_2.execute(() => {
+ assert_equals(window.component_1_var, undefined);
+ });
+ });
+}, 'Test creating and using multiple component ads in a FLEDGE auction');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/navigate-ancestor-by-name.https.html b/testing/web-platform/tests/fenced-frame/navigate-ancestor-by-name.https.html
new file mode 100644
index 0000000000..a5df1e9942
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/navigate-ancestor-by-name.https.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<title>Test named frame navigation of ancestors.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ // This test uses the following layout:
+ // A: Top-level frame
+ // B: iframe
+ // C: fencedframe
+ // D: fencedframe
+ // E: iframe
+ //
+ // The purpose is to test that name resolution of navigation targets ignores
+ // ancestors beyond fence boundaries.
+
+ // Create an iframe B.
+ const B = attachIFrameContext();
+ await B.execute(async () => {
+ window.name = "B";
+
+ // Create a fenced frame C inside of it.
+ window.C = attachFencedFrameContext();
+ await window.C.execute(async () => {
+ window.name = "C";
+
+ // Navigate the target "B" from inside the fenced frame.
+ // It should open in a new tab due to fenced name lookup.
+ window.open("resources/dummy.html", "B");
+ });
+ });
+
+ // Observe that it created a new window, and the frame B is still here.
+ await B.execute(async () => {
+ // Create a nested iframe and fenced frame.
+ await window.C.execute(async () => {
+ window.D = attachFencedFrameContext();
+ window.E = attachIFrameContext();
+
+ // Navigate the target "C" from inside the nested fenced frame.
+ // It should open in a new tab due to fenced name lookup.
+ await window.D.execute(async () => {
+ window.open("resources/dummy.html", "C");
+ });
+ });
+ // Observe that it created a new window, and the frame C is still here.
+ await window.C.execute(async () => {
+ // Now attempt to navigate the target "C" from inside the iframe.
+ // It should open in a new tab with a console error, because sandboxed
+ // iframes (inherited from the fenced frame) are not allowed to navigate
+ // their ancestors.
+ await window.E.execute(() => {
+ window.open("resources/dummy.html", "C");
+ });
+ });
+
+ // Observe that C is still here.
+ await window.C.execute(() => {});
+ });
+}, 'navigate named ancestors');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/navigate-ancestor-nested-fenced-frame.https.html b/testing/web-platform/tests/fenced-frame/navigate-ancestor-nested-fenced-frame.https.html
new file mode 100644
index 0000000000..ec41fe8757
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/navigate-ancestor-nested-fenced-frame.https.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Test navigating an ancestor frame from a nested fenced frame</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/navigate-ancestor-helper.js"></script>
+
+<body>
+<script>
+promise_test(async t => {
+ await runNavigateAncestorTest("nested fenced frame", "parent");
+}, "Nested fenced frames that navigate _parent end up navigating themselves");
+
+promise_test(async t => {
+ await runNavigateAncestorTest("nested fenced frame", "top");
+}, "Nested fenced frames that navigate _top end up navigating themselves");
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/navigate-ancestor-nested-iframe.https.html b/testing/web-platform/tests/fenced-frame/navigate-ancestor-nested-iframe.https.html
new file mode 100644
index 0000000000..977eae1748
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/navigate-ancestor-nested-iframe.https.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Test navigating an ancestor frame from a iframe in a fenced frame</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/navigate-ancestor-helper.js"></script>
+
+<body>
+<script>
+promise_test(async t => {
+ await runNavigateAncestorTest("nested iframe", "parent");
+}, "Iframes nested in fenced frames fail to navigate _parent");
+
+promise_test(async t => {
+ await runNavigateAncestorTest("nested iframe", "top");
+}, "Iframes nested in fenced frames fail to navigate _top");
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/navigate-ancestor-top-level-fenced-frame.https.html b/testing/web-platform/tests/fenced-frame/navigate-ancestor-top-level-fenced-frame.https.html
new file mode 100644
index 0000000000..9907f25292
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/navigate-ancestor-top-level-fenced-frame.https.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<title>Test navigating an ancestor frame from a fenced frame</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/navigate-ancestor-helper.js"></script>
+
+<body>
+<script>
+promise_test(async t => {
+ await runNavigateAncestorTest("top-level fenced frame", "parent");
+}, "Top-level fenced frames that navigate _parent end up navigating themselves");
+
+promise_test(async t => {
+ await runNavigateAncestorTest("top-level fenced frame", "top");
+}, "Top-level fenced frames that navigate _top end up navigating themselves");
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/navigate-by-name-succeed.https.html b/testing/web-platform/tests/fenced-frame/navigate-by-name-succeed.https.html
new file mode 100644
index 0000000000..4d558f8d23
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/navigate-by-name-succeed.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Test successful named frame navigation.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ // This test uses the following layout:
+ // A: Top-level frame
+ // B: fencedframe
+ // C: iframe
+ //
+ // The purpose is to test that named target lookups of C succeed in A, i.e.
+ // that lookups work even when the first child of a frame is fenced.
+
+ const fencedframe = attachFencedFrameContext();
+ const iframe = attachIFrameContext();
+
+ // Give the iframe a name.
+ await iframe.execute(() => { window.name = "target_frame"; });
+
+ // Modify state in the iframe, using a JS navigation to the target name.
+ window.open("javascript:window.success=true;", "target_frame");
+
+ // Check that the navigation happened in the iframe.
+ await iframe.execute(() => {
+ // If the JS code didn't run in the iframe, `window.success` would be
+ // undefined.
+ assert_true(window.success, 'The JS code ran in the iframe.');
+ });
+}, 'navigate iframe sibling of fenced frame');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/navigate-descendant-by-name.https.html b/testing/web-platform/tests/fenced-frame/navigate-descendant-by-name.https.html
new file mode 100644
index 0000000000..08ce4b99f3
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/navigate-descendant-by-name.https.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<title>Test named frame navigation of descendants</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+ <!-- This anchor element is clicked via script to navigate a target frame by
+ name. The target frame will always exist inside a fenced frame tree, and
+ therefore shouldn't actually work. The expected behavior is that the
+ navigation should end up in a new top-level browsing context, as per [1],
+ which will communicate back to the main page (via the server stash)
+ letting us know that the navigation succeeded, and did not successfully
+ target a frame inside the fenced frame boundary.
+
+ [1]: https://html.spec.whatwg.org/C/#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name
+ -->
+<script>
+ const kAssertion = "The anchor element did not navigate a frame inside the " +
+ "fenced frame boundary: ";
+ // This is used by `navigate-by-name-reporting-helper.html` to let us know if
+ // the navigation correctly opened a new top-level popup window, or
+ // incorrectly targeted a browsing context inside the fenced frame boundary.
+ const navigation_success_key = token();
+ // This is sent by the `navigate-by-name-inner.html` to indicate that it has
+ // set up a frame whose name is `target_frame`, that we are supposed to try
+ // and navigate by name.
+ const ready_for_navigation_key = token();
+
+ const a = document.createElement("a");
+ a.href = generateURL('resources/navigate-by-name-reporting-helper.html',
+ [navigation_success_key]);
+ a.innerText = "Click to navigate target frame";
+ a.target = "target_frame";
+ document.body.append(a);
+
+async function runTest(test_type) {
+ const fenced_frame =
+ attachFencedFrame(generateURL(
+ `resources/navigate-by-name-inner.html`,
+ [ready_for_navigation_key, test_type]));
+
+ // Wait for the fenced frame to say it is ready for us (the outer page) to
+ // initiate a named frame navigation, targeting a frame inside the fence.
+ let result = await nextValueFromServer(ready_for_navigation_key);
+ assert_equals(result, "READY", "The top-level fenced frame is ready for " +
+ "us to navigate");
+
+ // Now that the fenced frame has a frame whose name is `target_frame`, let's
+ // try and navigate it.
+ a.click();
+ result = await nextValueFromServer(navigation_success_key);
+ assert_equals(result, "PASS", kAssertion + test_type);
+
+ // Get a reference to the window opened up by the anchor navigation, and
+ // close it.
+ const win = window.open("", "target_frame");
+ win.close();
+
+ // Clean up the fenced frame
+ document.body.removeChild(fenced_frame);
+}
+
+promise_test(async() => {
+ // First just test that when we have no real target frame to navigate,
+ // everything works as expected.
+ a.click();
+ const result = await nextValueFromServer(navigation_success_key);
+ assert_equals(result, "PASS", "The initial test works");
+
+ // Get a reference to the already-opened window and close it.
+ const win = window.open("", "target_frame");
+ win.close();
+}, "setup");
+
+promise_test(async () => {
+ return runTest("top-level fenced frame");
+}, "navigate top-level fenced frame by name");
+
+promise_test(async () => {
+ return runTest("nested iframe");
+}, "navigate iframe nested in a fenced frame by name");
+
+promise_test(async () => {
+ return runTest("nested fenced frame");
+}, "navigate nested fenced frame by name");
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/navigate-related-page-by-name.https.html b/testing/web-platform/tests/fenced-frame/navigate-related-page-by-name.https.html
new file mode 100644
index 0000000000..755f32a5a3
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/navigate-related-page-by-name.https.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<title>Test named frame navigation of related pages.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ // A: top-level frame
+ // C: fencedframe
+ // B: auxiliary browsing context
+ //
+ // C should not be able to navigate B.
+
+ // Create an auxiliary browsing context with a particular name.
+ const second_window = attachWindowContext({target: "target_name"});
+
+ // Create a fenced frame, and use the same target name inside of it.
+ const frame = attachFencedFrameContext();
+ await frame.execute(async () => {
+ window.open("resources/dummy.html", "target_name");
+ });
+
+ // Confirm that the top-level frame's related page (`second_window`)
+ // wasn't navigated by the fenced frame, i.e. that name resolution
+ // for related pages is fenced.
+ await second_window.execute(() => {});
+}, 'navigate related pages from inside a fenced frame');
+
+promise_test(async () => {
+ // A: top-level frame
+ // B: auxiliary browsing context
+ // C: fencedframe
+ //
+ // A should not be able to navigate C.
+
+ // Create an auxiliary browsing context.
+ const second_window = attachWindowContext();
+ await second_window.execute(async () => {
+ // Create a fenced frame inside that context and give it a particular name.
+ window.frame = attachFencedFrameContext();
+ await window.frame.execute(() => {
+ window.name = "target_name";
+ });
+ });
+
+ // Navigate that target name from the original top-level frame.
+ window.open("resources/dummy.html", "target_name");
+
+ // Confirm that the fenced frame wasn't navigated.
+ await second_window.execute(async () => {
+ await frame.execute(() => {});
+ });
+}, 'navigate fenced frames inside related pages from the embedder');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/navigator-keyboard-layout-map.https.html b/testing/web-platform/tests/fenced-frame/navigator-keyboard-layout-map.https.html
new file mode 100644
index 0000000000..28cdbc848e
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/navigator-keyboard-layout-map.https.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<title>Test navigator.keyboard.getLayoutMap()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async () => {
+ const keyboard_layout_key = token();
+
+ attachFencedFrame(generateURL(
+ "resources/navigator-keyboard-layout-map-inner.html",
+ [keyboard_layout_key]));
+ const actual_result = await nextValueFromServer(keyboard_layout_key);
+
+ assert_equals(actual_result, "rejected",
+ "The non-opaque fenced frame is not allowed to fetch keyboard map.");
+
+}, "keyboard.getLayoutMap() from non-opaque fenced frame");
+
+promise_test(async () => {
+ const keyboard_layout_key = token();
+
+ const urn = await generateURNFromFledge(
+ "resources/navigator-keyboard-layout-map-inner.html",
+ [keyboard_layout_key]);
+ attachFencedFrame(urn);
+ const actual_result = await nextValueFromServer(keyboard_layout_key);
+
+ assert_equals(actual_result, "rejected",
+ "The opaque fenced frame is not allowed to fetch keyboard map.");
+
+}, "keyboard.getLayoutMap() from opaque fenced frame");
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/navigator-keyboard-lock.https.html b/testing/web-platform/tests/fenced-frame/navigator-keyboard-lock.https.html
new file mode 100644
index 0000000000..74092a41d2
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/navigator-keyboard-lock.https.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>Test navigator.keyboard.lock</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async () => {
+ const keyboard_lock_key = token();
+
+ attachFencedFrame(generateURL(
+ "resources/navigator-keyboard-lock-inner.html",
+ [keyboard_lock_key]));
+ const actual_result = await nextValueFromServer(keyboard_lock_key);
+
+ assert_equals(actual_result, "rejected",
+ "The fenced frame is not allowed to lock keyboard.");
+
+}, "keyboard.lock");
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/navigator-subapp.https.html b/testing/web-platform/tests/fenced-frame/navigator-subapp.https.html
new file mode 100644
index 0000000000..843d256554
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/navigator-subapp.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<title>navigator.subApp API test</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ const result = await frame.execute(async () => {
+ const expected_error_message =
+ 'Cannot read properties of undefined (reading \'list\')';
+ try {
+ const list = await navigator.subApps.list();
+ return 'navigator.subApps.list() succeeded';
+ } catch (e) {
+ if (e.name === 'TypeError' &&
+ e.message === expected_error_message) {
+ return 'navigator.subApps.list() was disallowed';
+ }
+ return 'navigator.subApps.list() failed with unknown error' +
+ `${e.name} ${e.message}`;
+ }
+ });
+ assert_equals(result, 'navigator.subApps.list() was disallowed');
+}, 'navigator.subApps.list() should fail in the fenced frame.');
+</script>
diff --git a/testing/web-platform/tests/fenced-frame/navigator-vibrate.https.html b/testing/web-platform/tests/fenced-frame/navigator-vibrate.https.html
new file mode 100644
index 0000000000..6bcabf4d47
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/navigator-vibrate.https.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<title>Test that navigator.vibrate is disabled in fenced frames.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+// Simulate a click in frame context `frame`.
+async function click(frame) {
+ var actions = new test_driver.Actions();
+ await actions.pointerMove(0, 0, {origin: frame})
+ .pointerDown()
+ .pointerUp()
+ .send();
+}
+
+promise_test(async () => {
+ // This test ensures that vibration is disabled in fenced frames.
+ // It uses a top-level frame A and a fenced frame B.
+ // The structure of the test is as follows:
+ // - Check that B can't vibrate before user activation.
+ // - Check that B can't vibrate after user activation.
+ // - Check that A can't vibrate before user activation.
+ // - Check that A CAN vibrate after user activation.
+
+ const B = attachFencedFrameContext();
+ await B.execute(() => {
+ assert_false(navigator.userActivation.hasBeenActive);
+ var success = navigator.vibrate(100);
+ assert_false(success,
+ "Vibration failed in fenced frame before user activation");
+ });
+
+ await click(B.element);
+ await B.execute(() => {
+ assert_true(navigator.userActivation.hasBeenActive);
+ var success = navigator.vibrate(100);
+ assert_false(success,
+ "Vibration failed in fenced frame, even after user activation");
+ });
+
+ var success = navigator.vibrate(100);
+ assert_false(navigator.userActivation.hasBeenActive);
+ assert_false(success,
+ "Vibration failed in top-level frame before user activation");
+
+ await click(document.documentElement);
+ assert_true(navigator.userActivation.hasBeenActive);
+ var success = navigator.vibrate(100);
+ assert_true(success,
+ "Vibration succeeded in top-level frame after user activation");
+}, 'navigator.vibrate');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/navigator-virtualkeyboard.https.html b/testing/web-platform/tests/fenced-frame/navigator-virtualkeyboard.https.html
new file mode 100644
index 0000000000..4f2a17f2ff
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/navigator-virtualkeyboard.https.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<title>Test navigator.virtualKeyboard.overlaysContent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+
+ const response = await frame.execute(() => {
+ navigator.virtualKeyboard.overlaysContent = true;
+ return navigator.virtualKeyboard.overlaysContent;
+ });
+ assert_equals(response, false, "overlaysContent had no effect");
+}, "virtualKeyboard.overlaysContent");
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/nested-opaque-ad-sizes.https.html b/testing/web-platform/tests/fenced-frame/nested-opaque-ad-sizes.https.html
new file mode 100644
index 0000000000..aa93b962ff
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/nested-opaque-ad-sizes.https.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<title>Test cases when fenced frame size shouldn't be restricted..</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ const allowed_width = 320;
+ const allowed_height = 50;
+
+ // Create an opaque-ads fenced frame with an invalid size.
+ const frame = await attachFencedFrameContext({
+ generator_api: "fledge",
+ resolve_to_config: true,
+ attributes: [["width", allowed_width+1], ["height", allowed_height+1]],
+ num_components: 1
+ });
+
+ await frame.execute(async (allowed_width, allowed_height) => {
+ // Observe that the size gets coerced to the nearest allowed size.
+ assert_equals(window.innerWidth, allowed_width,
+ "The outer opaque-ads fenced frame has its width coerced.");
+ assert_equals(window.innerHeight, allowed_height,
+ "The outer opaque-ads fenced frame has its height coerced.");
+
+ const component_ad_frame = await attachComponentFencedFrameContext(0, {
+ attributes:
+ [["width", allowed_width+1], ["height", allowed_height+1]]});
+
+ await component_ad_frame.execute((allowed_width, allowed_height) => {
+ // Observe that the nested frame's size doesn't get coerced.
+ assert_equals(window.innerWidth, allowed_width+1,
+ "The nested opaque-ads fenced frame has its requested size.");
+ assert_equals(window.innerHeight, allowed_height+1,
+ "The nested opaque-ads fenced frame has its requested size.");
+ }, [allowed_width, allowed_height]);
+ }, [allowed_width, allowed_height]);
+}, "nested fenced frames don't use the size list");
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/notification.https.html b/testing/web-platform/tests/fenced-frame/notification.https.html
new file mode 100644
index 0000000000..636d218e10
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/notification.https.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<title>Test Notification</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 src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ // Notification permission must be allowed or we cannot confirm that
+ // "new Notification" failed in a fenced frame.
+ await test_driver.set_permission({name: 'notifications'}, 'granted', true);
+ assert_equals(Notification.permission, 'granted');
+
+ const frame = attachFencedFrameContext();
+ try {
+ await frame.execute(() => {
+ const notification = new Notification('Test notification');
+ return new Promise((resolve, reject) => {
+ // "new Notification" inside the fenced frame should fail even if it is
+ // allowed in the primary main frame.
+ notification.addEventListener('error', resolve);
+ notification.addEventListener('show', reject);
+ });
+ });
+ } catch(e) {
+ assert_unreached('Notification was shown; want not to be shown.' + e);
+ }
+}, 'new Notification should fail inside a fenced frame');
+
+promise_test(async () => {
+ // Notification permission must be allowed or we cannot confirm that
+ // "new Notification" failed in a fenced frame.
+ await test_driver.set_permission({name: 'notifications'}, 'granted', true);
+ assert_equals(Notification.permission, 'granted');
+
+ const frame = attachFencedFrameContext();
+ const message = await frame.execute(async () => {
+ const getController = () => {
+ if (navigator.serviceWorker.controller) {
+ return navigator.serviceWorker.controller;
+ }
+ return new Promise(resolve => {
+ navigator.serviceWorker.addEventListener('controllerchange', () => {
+ resolve(navigator.serviceWorker.controller);
+ });
+ });
+ };
+
+ await navigator.serviceWorker.register(
+ 'notification-sw.js', { scope: location.href });
+ const ctrl = await getController();
+
+ return new Promise(resolve => {
+ ctrl.postMessage('constructor');
+ navigator.serviceWorker.onmessage = e => {
+ resolve(e.data);
+ };
+ });
+ });
+ assert_equals(
+ message, "Failed to construct 'Notification': Illegal constructor.");
+}, 'new Notification should fail from the service worker in a fenced frame');
+
+promise_test(async () => {
+ // Notification permission must be allowed or we cannot confirm that
+ // "new Notification" failed in a fenced frame.
+ await test_driver.set_permission({name: 'notifications'}, 'granted', true);
+ assert_equals(Notification.permission, 'granted');
+
+ const frame = attachFencedFrameContext();
+ const message = await frame.execute(async () => {
+ const getController = () => {
+ if (navigator.serviceWorker.controller) {
+ return navigator.serviceWorker.controller;
+ }
+ return new Promise(resolve => {
+ navigator.serviceWorker.addEventListener('controllerchange', () => {
+ resolve(navigator.serviceWorker.controller);
+ });
+ });
+ };
+
+ await navigator.serviceWorker.register(
+ 'notification-sw.js', { scope: location.href });
+ const ctrl = await getController();
+
+ return new Promise(resolve => {
+ ctrl.postMessage('showNotification');
+ navigator.serviceWorker.onmessage = e => {
+ resolve(e.data);
+ };
+ });
+ });
+ assert_equals(message,
+ "Failed to execute 'showNotification' on 'ServiceWorkerRegistration': " + "showNotification() is not allowed in fenced frames.",);
+}, 'showNotification() should fail from the service worker in a fenced frame');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/opaque-ad-sizes-exact-size.https.html b/testing/web-platform/tests/fenced-frame/opaque-ad-sizes-exact-size.https.html
new file mode 100644
index 0000000000..b23d3ab0d7
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/opaque-ad-sizes-exact-size.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>Test frame size restrictions in FLEDGE.</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="resources/opaque-ad-sizes-utils.js"></script>
+
+<body>
+<script>
+
+// Exact size cases.
+promise_test(async () => { return runOpaqueAdSizesTest(320, 50, 320, 50); }, '320x50');
+promise_test(async () => { return runOpaqueAdSizesTest(728, 90, 728, 90); }, '728x90');
+promise_test(async () => { return runOpaqueAdSizesTest(970, 90, 970, 90); }, '970x90');
+promise_test(async () => { return runOpaqueAdSizesTest(320, 100, 320, 100); }, '320x100');
+promise_test(async () => { return runOpaqueAdSizesTest(160, 600, 160, 600); }, '160x600');
+promise_test(async () => { return runOpaqueAdSizesTest(300, 250, 300, 250); }, '300x250');
+promise_test(async () => { return runOpaqueAdSizesTest(970, 250, 970, 250); }, '970x250');
+promise_test(async () => { return runOpaqueAdSizesTest(336, 280, 336, 280); }, '336x280');
+promise_test(async () => { return runOpaqueAdSizesTest(320, 480, 320, 480); }, '320x48');
+promise_test(async () => { return runOpaqueAdSizesTest(300, 600, 300, 600); }, '300x600');
+promise_test(async () => { return runOpaqueAdSizesTest(300, 1050, 300, 1050); }, '300x1050');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/opaque-ad-sizes-special-cases.https.html b/testing/web-platform/tests/fenced-frame/opaque-ad-sizes-special-cases.https.html
new file mode 100644
index 0000000000..55cadaeec0
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/opaque-ad-sizes-special-cases.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<title>Test frame size restrictions in FLEDGE.</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="resources/opaque-ad-sizes-utils.js"></script>
+
+<body>
+<script>
+
+// Rounding cases.
+promise_test(async () => { return runOpaqueAdSizesTest(970.1, 250, 970, 250); }, '970.1x250');
+promise_test(async () => { return runOpaqueAdSizesTest(970, 250.1, 970, 250); }, '970x250.1');
+promise_test(async () => { return runOpaqueAdSizesTest(971, 250, 970, 250); }, '971x250');
+promise_test(async () => { return runOpaqueAdSizesTest(970, 251, 970, 250); }, '970x251');
+
+// Edge cases. The particular sizes it rounds to aren't important here, just
+// that it rounds to one of the sizes on the allowed list.
+promise_test(async () => { return runOpaqueAdSizesTest(0, 100, 320, 50); }, '0x100');
+promise_test(async () => { return runOpaqueAdSizesTest(100, 0, 320, 50); }, '100x0');
+promise_test(async () => { return runOpaqueAdSizesTest(Number.MAX_VALUE, Number.MAX_VALUE, 300, 250); }, 'MAXxMAX');
+promise_test(async () => { return runOpaqueAdSizesTest(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, 320, 100); }, 'INFINITYxINFINITY');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/payment-handler.https.html b/testing/web-platform/tests/fenced-frame/payment-handler.https.html
new file mode 100644
index 0000000000..36e5e50726
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/payment-handler.https.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<title>Test Payment Handler API</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 src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ const error_name = await frame.execute(async () => {
+ navigator.serviceWorker.register(
+ "empty-worker.js", { scope: location.href });
+ const registration = await navigator.serviceWorker.ready;
+ try {
+ registration.paymentManager;
+ } catch (e) {
+ return e.name;
+ }
+ });
+ assert_equals(error_name, "NotAllowedError",
+ "paymentManager is not allowed in fenced frames");
+}, 'paymentManager should fail inside a fenced frame');
+
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ const error_name = await frame.execute(async () => {
+ const getController = () => {
+ if (navigator.serviceWorker.controller) {
+ return navigator.serviceWorker.controller;
+ }
+ return new Promise(resolve => {
+ navigator.serviceWorker.addEventListener('controllerchange', () => {
+ resolve(navigator.serviceWorker.controller);
+ });
+ });
+ };
+
+ navigator.serviceWorker.register(
+ "payment-handler-sw.js", { scope: location.href });
+ const ctrl = await getController();
+
+ return new Promise(resolve => {
+ ctrl.postMessage("test");
+ navigator.serviceWorker.onmessage = e => {
+ const error = e.data;
+ resolve(error.name);
+ };
+ });
+ });
+ assert_equals(error_name, "NotAllowedError",
+ "paymentManager is not allowed from the service worker " +
+ "in fenced frames");
+}, 'paymentManager should fail from the service worker inside a fenced frame');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/payment-request.https.html b/testing/web-platform/tests/fenced-frame/payment-request.https.html
new file mode 100644
index 0000000000..0d1bbb0113
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/payment-request.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Test Payment Rrequest API</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 src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ const error_name = await frame.execute(() => {
+ const methods = [{supportedMethods: ['foo']}];
+ const details = {
+ total: {
+ label: 'label',
+ amount: {
+ currency: 'USD',
+ value: '5.00'
+ }
+ }
+ };
+
+ try {
+ new PaymentRequest(methods, details);
+ } catch (e) {
+ return e.name
+ }
+ });
+ assert_equals(error_name, "SecurityError",
+ "PaymentRequest is not allowed in fenced frames");
+}, 'new PaymentRequest should fail inside a fenced frame');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/permission-api-denied-non-standard.https.html b/testing/web-platform/tests/fenced-frame/permission-api-denied-non-standard.https.html
new file mode 100644
index 0000000000..ffc06781f6
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/permission-api-denied-non-standard.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<title>Permission API in fenced frames should always return denied</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 src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+ <script>
+ // See https://cs.chromium.org/chromium/src/third_party/blink/renderer/modules/permissions/permission_descriptor.idl for valid permission names.
+ non_standard_permission_list =
+ ['accessibility-events',
+ 'clipboard-read',
+ 'clipboard-write',
+ 'payment-handler',
+ 'idle-detection',
+ 'periodic-background-sync',
+ 'system-wake-lock',
+ 'storage-access',
+ 'window-management',
+ 'local-fonts'];
+
+ non_standard_permission_list.forEach(function (permission_name) {
+ promise_test(async t => {
+ const permission_key = token();
+
+ attachFencedFrame(generateURL(
+ 'resources/permission-api-denied-inner.html',
+ [permission_key, permission_name]));
+ const actual_result = await nextValueFromServer(permission_key);
+ assert_equals(
+ actual_result, 'result: denied',
+ 'permission API should return denied for ' + permission_name +
+ ' in fenced frames.');
+ });
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/permission-api-denied.https.html b/testing/web-platform/tests/fenced-frame/permission-api-denied.https.html
new file mode 100644
index 0000000000..0d193e73cc
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/permission-api-denied.https.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<title>Permission API in fenced frames should always return denied</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 src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+ <script>
+ // See https://cs.chromium.org/chromium/src/third_party/blink/renderer/modules/permissions/permission_descriptor.idl for valid permission names.
+ standard_permission_list =
+ ['geolocation',
+ 'notifications',
+ 'push',
+ 'midi',
+ 'camera',
+ 'microphone',
+ 'background-fetch',
+ 'background-sync',
+ 'persistent-storage',
+ 'ambient-light-sensor',
+ 'accelerometer',
+ 'gyroscope',
+ 'magnetometer',
+ 'screen-wake-lock',
+ 'nfc',
+ 'display-capture'];
+
+ standard_permission_list.forEach(function (permission_name) {
+ promise_test(async t => {
+ const permission_key = token();
+
+ attachFencedFrame(generateURL(
+ 'resources/permission-api-denied-inner.html',
+ [permission_key, permission_name]));
+ const actual_result = await nextValueFromServer(permission_key);
+ assert_equals(
+ actual_result, 'result: denied',
+ 'permission API should return denied for ' + permission_name +
+ ' in fenced frames.');
+ });
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/permission-geolocation.https.html b/testing/web-platform/tests/fenced-frame/permission-geolocation.https.html
new file mode 100644
index 0000000000..e9ad53511b
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/permission-geolocation.https.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<title>Test permission of geolocation</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 src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+
+<script>
+
+async function runTest(policy_header_in_primary_page,
+ policy_header_in_fenced_frame_page,
+ allow_attribute_iframe_in_fanced_frame) {
+ const permission_geolocation_key = token();
+ const pipe_for_policy = 'pipe=header(Permissions-Policy,geolocation=self)';
+ const test_runner_url =
+ 'resources/permission-geolocation-test-runner.html?' +
+ (policy_header_in_primary_page ? pipe_for_policy : '');
+ let fenced_frame_url_params = [];
+ if (policy_header_in_fenced_frame_page) {
+ fenced_frame_url_params.push(pipe_for_policy);
+ }
+ if (allow_attribute_iframe_in_fanced_frame) {
+ fenced_frame_url_params.push('load_allow_attribute_iframe=true');
+ }
+ const fenced_frame_url = 'permission-geolocation-inner.html?' +
+ fenced_frame_url_params.join('&');
+
+ const win = window.open(generateURL(test_runner_url,
+ [permission_geolocation_key]));
+ await new Promise(resolve => {
+ win.onload = resolve;
+ });
+
+ // Pagehide can be used to detect the document destruction.
+ const pagehidePromise = new Promise(resolve => {
+ win.onpagehide = resolve;
+ });
+
+ await win.runTest(fenced_frame_url);
+ win.close();
+ await pagehidePromise;
+}
+
+promise_test(async t => {
+ await runTest(false, false, false);
+}, 'geolocation permission is not permitted for fenced frames');
+
+promise_test(async t => {
+ await runTest(true, false, false);
+}, 'geolocation permission is not permitted for fenced frames, even if a ' +
+ '`Permissions-Policy` header is sent on the primary page.');
+
+promise_test(async t => {
+ await runTest(false, true, false);
+}, 'geolocation permission is not permitted for fenced frames, even if a ' +
+ '`Permissions-Policy` header is sent on the fenced frame response.');
+
+promise_test(async t => {
+ await runTest(false, false, true);
+}, 'geolocation permission is not permitted for fenced frames, even if an ' +
+ '`allow` attribute is set for an iframe in the fenced frame.');
+
+promise_test(async t => {
+ await runTest(false, true, true);
+}, 'geolocation permission is not permitted for fenced frames, even if a ' +
+ '`Permissions-Policy` header and an `allow` attribute is set for an iframe' +
+ ' in the fenced frame.');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/permission-notification.https.html b/testing/web-platform/tests/fenced-frame/permission-notification.https.html
new file mode 100644
index 0000000000..7934125294
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/permission-notification.https.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Test permission of notification</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 src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async t => {
+ const permission_notification_key = token();
+ await test_driver.set_permission({name: 'notifications'}, 'granted', true);
+
+ attachFencedFrame(generateURL(
+ 'resources/permission-notification-inner.html',
+ [permission_notification_key]));
+ const actual_result = await nextValueFromServer(permission_notification_key);
+
+ assert_equals(
+ actual_result, 'result: denied',
+ 'notification permission should not be granted in the fenced frame.');
+}, 'notification permission should not be granted');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/picture-in-picture.https.html b/testing/web-platform/tests/fenced-frame/picture-in-picture.https.html
new file mode 100644
index 0000000000..9665f88b9c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/picture-in-picture.https.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>Test of picture-in-picture</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ const result = await frame.execute(async () => {
+ const video = document.body.appendChild(document.createElement("video"));
+ try {
+ await video.requestPictureInPicture();
+ return 'PIP request succeeded.';
+ } catch (e) {
+ if (e.name == 'SecurityError') {
+ return 'PIP request failed';
+ } else {
+ return `PIP request failed but not with SecurityError - ${e.name}: ${e.message}`;
+ }
+ }
+ });
+ assert_equals(
+ result, 'PIP request failed',
+ 'PIP request must fail in a fenced frame by permissions policy.');
+}, 'Test HTMLVideoElement.requestPictureInPicture');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/popup-noopener.https.html b/testing/web-platform/tests/fenced-frame/popup-noopener.https.html
new file mode 100644
index 0000000000..9e7e8ce262
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/popup-noopener.https.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<title>Test popup created from a Fenced Frame Tree</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+<script>
+// This key is used by `resources/popup-noopener-destination.html` to let us know if a
+// a new popup window is correctly opened without an opener.
+const popup_noopener_key = token();
+const kAssertion = "window.opener is null ";
+
+// This key is used by `resources/popup-noopener-inner.html` and
+// `resources/create-popup.html to let us know if a
+// a new popup window is correctly opened without an openee reference.
+const popup_openee_key = token();
+const kAssertion_openee = "openee is null ";
+
+// This key is used by `resources/popup-noopener-destination.html` to let us know if a
+// a new popup window is correctly opened without a name.
+const popup_name_key = token();
+const kAssertion_name = "window.name is empty string ";
+
+async function runTest(test_type) {
+ const fenced_frame =
+ attachFencedFrame(generateURL(
+ `resources/popup-noopener-inner.html`,
+ [popup_noopener_key, popup_openee_key, popup_name_key, test_type]));
+
+ result = await nextValueFromServer(popup_openee_key);
+ assert_equals(result, "PASS", kAssertion_openee + test_type);
+
+ result = await nextValueFromServer(popup_noopener_key);
+ assert_equals(result, "PASS", kAssertion + test_type);
+
+ result = await nextValueFromServer(popup_name_key);
+ assert_equals(result, "PASS", kAssertion + test_type);
+
+ // Clean up the fenced frame
+ document.body.removeChild(fenced_frame);
+}
+
+promise_test(async () => {
+ return runTest("top-level fenced frame");
+}, "Create popup from top-level fenced frame");
+
+promise_test(async () => {
+ return runTest("nested iframe");
+}, "Create popup from iframe nested in a fenced frame");
+
+promise_test(async () => {
+ return runTest("nested fenced frame");
+}, "Create popup from nested fenced frame");
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/prerender.https.html b/testing/web-platform/tests/fenced-frame/prerender.https.html
new file mode 100644
index 0000000000..45a39989d2
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/prerender.https.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<title>Test prerendering</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 src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async t => {
+ assert_implements(
+ 'supports' in HTMLScriptElement,
+ 'HTMLScriptElement.supports is not supported');
+ assert_implements(
+ HTMLScriptElement.supports('speculationrules'),
+ '<script type="speculationrules"> is not supported');
+
+ const prerender_ready_key = token();
+ const prerender_loaded_key = token();
+ const prerender_activated_key = token();
+ const url = generateURL('resources/prerender-inner.html',
+ [prerender_ready_key, prerender_loaded_key, prerender_activated_key]);
+
+ // TODO: This test expects that the browser always triggers prerendering when
+ // SpeculationRules is provided. But SpeculationRules allows browsers not to
+ // trigger the speculative resource loading even if SpeculationRules is
+ // specified. So we have to use a new WebDriver API that deterministically
+ // triggers prerendering.
+ // https://github.com/WICG/nav-speculation/blob/main/speculation-rules-testing.md
+ const script = document.createElement('script');
+ script.type = 'speculationrules';
+ script.text = `{"prerender": [{"source": "list", "urls": ["${url}"] }] }`;
+ document.head.appendChild(script);
+
+ const ready_result = await nextValueFromServer(prerender_ready_key);
+ assert_equals(ready_result, 'ready');
+
+ attachFencedFrame(url);
+
+ const loaded_or_acrivated_result = await Promise.race([
+ nextValueFromServer(prerender_loaded_key),
+ nextValueFromServer(prerender_activated_key)
+ ]);
+ assert_equals(loaded_or_acrivated_result, 'loaded');
+}, 'Fenced Frame must not load prerendered page.');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/presentation-receiver.https.html b/testing/web-platform/tests/fenced-frame/presentation-receiver.https.html
new file mode 100644
index 0000000000..c33c72abf0
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/presentation-receiver.https.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Test permission of notification</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 src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async t => {
+ const presentation_receiver_key = token();
+
+ attachFencedFrame(generateURL('resources/presentation-receiver-inner.html',
+ [presentation_receiver_key]));
+ const actual_result = await nextValueFromServer(presentation_receiver_key);
+
+ assert_equals(
+ actual_result, 'denied',
+ 'presentation receiver should not be allowed on fenced-frames.');
+}, 'presentation receiver should not be allowed');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/reinsert.https.html b/testing/web-platform/tests/fenced-frame/reinsert.https.html
new file mode 100644
index 0000000000..b88ff83d00
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/reinsert.https.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>Test Content Security Policy</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async () => {
+ const frame = attachFencedFrame("resources/dummy.html");
+ document.body.removeChild(frame);
+ document.body.append(frame);
+
+}, "Fenced frames should not crash and burn when re-inserting a fenced frame");
+
+promise_test(async () => {
+ const frame = document.createElement("iframe");
+ frame.sandbox = "allow-scripts allow-same-origin";
+ document.body.appendChild(frame);
+ const fenced_frame = frame.contentDocument.createElement("fencedframe");
+ fenced_frame.src = "resources/dummy.html";
+ frame.contentDocument.body.appendChild(fenced_frame);
+ frame.contentDocument.body.removeChild(fenced_frame);
+ frame.contentDocument.body.append(fenced_frame);
+}, "Fenced frames should not crash and burn when re-inserting a fenced frame" +
+ "in a sandboxed iframe which doesn't support child fenced frames.");
+
+</script>
+
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/report-event-inactive-document.https.html b/testing/web-platform/tests/fenced-frame/report-event-inactive-document.https.html
new file mode 100644
index 0000000000..cb59aad233
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/report-event-inactive-document.https.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<title>Test window.fence.reportEvent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const key = token();
+ const urn = await generateURNFromFledge("resources/embeddee.html", [key]);
+ const iframe = document.createElement("iframe");
+ iframe.src = urn;
+ document.body.appendChild(iframe);
+
+ // Wait for the page in the iframe to load and tell us that it's loaded.
+ await nextValueFromServer(key);
+
+ // Get access to the iframe's window's fence object before removing.
+ let inner_fence = iframe.contentWindow.fence;
+
+ // window.fence calls should succeed before the iframe is removed.
+ inner_fence.setReportEventDataForAutomaticBeacons({
+ eventType: "reserved.top_navigation_commit",
+ eventData: 'This is the event data!',
+ destination: ['buyer']
+ });
+
+ // Remove the iframe to make the iframe's document an inactive document.
+ iframe.remove();
+
+ // window.fence calls should fail once the iframe is removed and the document
+ // becomes inactive.
+ try {
+ inner_fence.setReportEventDataForAutomaticBeacons({
+ eventType: "reserved.top_navigation_commit",
+ eventData: 'This is the event data!',
+ destination: ['buyer']
+ });
+ assert_unreached("The call should not have succeeded.");
+ } catch (error) {
+ assert_equals(error.name, "SecurityError");
+ }
+}, 'attempts to call set report event in an inactive document should fail');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/report-event-reserved-event.https.html b/testing/web-platform/tests/fenced-frame/report-event-reserved-event.https.html
new file mode 100644
index 0000000000..0a541bb0c5
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/report-event-reserved-event.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<title>Test window.fence.reportEvent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const fencedframe = await attachFencedFrameContext({
+ generator_api: "fledge",
+ automatic_beacon: true,
+ });
+ const new_url = new URL("resources/dummy.html", location.href);
+ const beacon_data = "This is the beacon data!";
+ const beacon_type = "reserved.top_navigation_commit";
+
+ await fencedframe.execute(
+ (new_url, beacon_data, beacon_type) => {
+ let beacon_event = {
+ eventType: beacon_type,
+ eventData: beacon_data,
+ destination: ["buyer", "seller"],
+ };
+ window.fence.reportEvent(beacon_event);
+ },
+ [new_url, beacon_data, beacon_type]
+ );
+
+ const timeout = new Promise(resolve => t.step_timeout(resolve, 1000));
+ const result = await Promise.race(
+ [nextAutomaticBeacon(beacon_type, beacon_data), timeout]);
+ assert_true(typeof result === "undefined",
+ "A beacon should not have been sent.");
+
+}, 'Reserved events should not be callable through reportEvent()');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/report-event-sandboxed-iframe.https.html b/testing/web-platform/tests/fenced-frame/report-event-sandboxed-iframe.https.html
new file mode 100644
index 0000000000..7298f39e69
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/report-event-sandboxed-iframe.https.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Test that window.fence.reportEvent does not crash in sandboxed iframes</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const fencedframe = await attachFencedFrameContext({generator_api: 'fledge'});
+ await fencedframe.execute(async () => {
+ const iframe = await attachIFrameContext();
+ await iframe.execute(() => {
+ let event = {};
+ event.eventType = "click";
+ event.eventData = "dummy";
+ event.destination = ["buyer"];
+ window.fence.reportEvent(event);
+ });
+
+ const sandbox_iframe = await attachIFrameContext({attributes: [['sandbox', 'allow-scripts']]});
+ await sandbox_iframe.execute(() => {
+ let event = {};
+ event.eventType = "click";
+ event.eventData = "dummy";
+ event.destination = ["buyer"];
+ window.fence.reportEvent(event);
+ });
+ });
+}, 'window.fence.reportEvent in sandboxed (effectively cross-origin) iframe');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resize-lock-input.https.html b/testing/web-platform/tests/fenced-frame/resize-lock-input.https.html
new file mode 100644
index 0000000000..c72075ac25
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resize-lock-input.https.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+ <title>Test FencedFrames Resize Lock</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-actions.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="resources/utils.js"></script>
+ <script src="/common/dispatcher/dispatcher.js"></script>
+ <script src="/common/utils.js"></script>
+
+ <body>
+
+ <script>
+ promise_test(async t => {
+ const fencedframe = attachFencedFrameContext();
+
+ // Set up the inner frame to receive mouse events.
+ await fencedframe.execute(() => {
+ window.testing_touchpoint = 'pending'
+ window.addEventListener('mousedown', async (event) => {
+ window.testing_touchpoint = event.clientX + "," + event.clientY;
+ });
+ });
+
+ let getCoordinates = async () => {
+ return fencedframe.execute(() => {
+ let point = window.testing_touchpoint;
+ window.testing_touchpoint = 'pending';
+ return point;
+ });
+ }
+
+ // Send an event to the origin of the frame.
+ for (let i = 0; i < 3; i++) {
+ await new test_driver.Actions()
+ .setContext(window)
+ .addPointer("finger1", "touch")
+ .pointerMove(10, 10, {origin: "viewport", sourceName: "finger1"})
+ .pointerDown({sourceName: "finger1"})
+ .pointerUp({sourceName: "finger1"})
+ .send();
+ }
+
+ let result = await getCoordinates();
+ assert_equals(result, "0,0", "fenced frame event before resize 1");
+
+ // The frame should be frozen at 300x150. Resize to create a 2x scale
+ // and a horizontal offset of 50px.
+ frame.width = "700";
+ frame.height = "300";
+
+ // Let the inner frame animate in order for the resize to take effect.
+ await fencedframe.execute(async () => {
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ });
+
+ // The hit-test data is replicated in the browser and updated
+ // asynchronously. Wait to ensure the update has finished.
+ t.step_timeout(async () => {
+ // Now send an event to the same location. The event should be
+ // routed to the main frame.
+ let promise = new Promise((resolve, reject) => {
+ window.addEventListener('mousedown', (event) => {
+ let point = event.clientX + "," + event.clientY;
+ assert_equals(result, "10,10", "main frame event after resize");
+ });
+ });
+ for (let i = 0; i < 3; i++) {
+ await new test_driver.Actions()
+ .setContext(window)
+ .addPointer("finger1", "touch")
+ .pointerMove(10, 10, {origin: "viewport", sourceName: "finger1"})
+ .pointerDown({sourceName: "finger1"})
+ .pointerUp({sourceName: "finger1"})
+ .send();
+ }
+ await promise;
+
+ // Send an event to where the origin of the scaled frame should
+ // render.
+ for (let i = 0; i < 3; i++) {
+ await new test_driver.Actions()
+ .setContext(window)
+ .addPointer("finger1", "touch")
+ .pointerMove(60, 10, {origin: "viewport", sourceName: "finger1"})
+ .pointerDown({sourceName: "finger1"})
+ .pointerUp({sourceName: "finger1"})
+ .send();
+ }
+ result = await getCoordinates();
+ assert_equals(result, "0,0", "fenced frame event after resize 1");
+
+ // Send an event where the bottom left of the scaled frame should
+ // render.
+ for (let i = 0; i < 3; i++) {
+ await new test_driver.Actions()
+ .setContext(window)
+ .addPointer("finger1", "touch")
+ .pointerMove(660, 310, {origin: "viewport", sourceName: "finger1"})
+ .pointerDown({sourceName: "finger1"})
+ .pointerUp({sourceName: "finger1"})
+ .send();
+ }
+ result = await getCoordinates();
+ assert_equals(result, "300,150", "fenced frame event after resize 2");
+ }, 1000);
+ }, "Test Resize Lock");
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/resize-lock-zoom.https.html b/testing/web-platform/tests/fenced-frame/resize-lock-zoom.https.html
new file mode 100644
index 0000000000..783f51d84e
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resize-lock-zoom.https.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+ <title>Test FencedFrames does not leak the CSS zoom property</title>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="resources/utils.js"></script>
+ <script src="/common/utils.js"></script>
+
+ <body>
+
+ <script>
+ promise_test(async () => {
+ const resize_lock_inner_page_is_ready_key = token();
+ const resize_lock_resize_is_done_key = token();
+ const resize_lock_report_inner_dimensions_key = token();
+
+ const frame = attachFencedFrame(generateURL(
+ "resources/resize-lock-inner.html",
+ [resize_lock_inner_page_is_ready_key,
+ resize_lock_resize_is_done_key,
+ resize_lock_report_inner_dimensions_key]));
+
+ await nextValueFromServer(resize_lock_inner_page_is_ready_key);
+
+ document.body.style.zoom = '2';
+
+ writeValueToServer(resize_lock_resize_is_done_key,
+ "outer_page_attempted_resize");
+
+ let result =
+ await nextValueFromServer(resize_lock_report_inner_dimensions_key);
+ assert_equals(result, "300x150",
+ "fenced frame dimensions should not be updated by " +
+ "parent page");
+
+ }, "Test Resize Lock");
+ </script>
+
+ </body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/resize-lock.https.html b/testing/web-platform/tests/fenced-frame/resize-lock.https.html
new file mode 100644
index 0000000000..b7c39f6f3a
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resize-lock.https.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<title>Test cases when fenced frame size shouldn't be restricted..</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+async function runTest(api1,
+ expected_initial_size,
+ expected_size_after_resize,
+ api2,
+ expected_size_after_renavigation,
+ expected_size_after_reresize) {
+ const initial_width = 321;
+ const initial_height = 51;
+
+ const resized_width = 729;
+ const resized_height = 91;
+
+ const reresized_width = 971;
+ const reresized_height = 251;
+
+ var frame = await attachFencedFrameContext({
+ generator_api: api1, resolve_to_config: true,
+ attributes: [['width', initial_width], ['height', initial_height]]});
+
+ const assert_dimensions =
+ (expected_width, expected_height) => {
+ getComputedStyle(document.documentElement).width; // Force layout.
+ assert_equals(window.innerWidth, expected_width, "width");
+ assert_equals(window.innerHeight, expected_height, "height");
+ }
+
+ // Check that the initial size of the fenced frame is what we expect.
+ await frame.execute(assert_dimensions, expected_initial_size);
+
+ // Resize the frame, and check that the size is now what we expect.
+ frame.element.width = resized_width;
+ frame.element.height = resized_height;
+ await frame.execute(assert_dimensions, expected_size_after_resize);
+
+ // Perform an embedder-initiated navigation, and check that the size is now
+ // what we expect (it should be based on the new context, rather than the old
+ // context).
+ frame = await replaceFrameContext(frame, {generator_api: api2,
+ resolve_to_config: true});
+ await frame.execute(assert_dimensions, expected_size_after_renavigation);
+
+ // Resize the newly navigated frame, and check the size.
+ frame.element.width = reresized_width;
+ frame.element.height = reresized_height;
+ await frame.execute(assert_dimensions, expected_size_after_reresize);
+}
+
+promise_test(async () => {
+ return runTest('fledge', [320, 50], [320, 50],
+ 'fledge', [728, 90], [728, 90]); },
+ "FLEDGE->FLEDGE");
+promise_test(async () => {
+ return runTest('sharedstorage', [321, 51], [729, 91],
+ 'sharedstorage', [729, 91], [971, 251]); },
+ "sharedStorage->sharedStorage");
+promise_test(async () => {
+ return runTest('default', [321, 51], [729, 91],
+ 'default', [729, 91], [971, 251]); },
+ "default->default");
+promise_test(async () => {
+ return runTest('default', [321, 51], [729, 91],
+ 'fledge', [728, 90], [728, 90]); },
+ "default->FLEDGE");
+promise_test(async () => {
+ return runTest('default', [321, 51], [729, 91],
+ 'sharedStorage', [729, 91], [971, 251]); },
+ "default->sharedStorage");
+promise_test(async () => {
+ return runTest('fledge', [320, 50], [320, 50],
+ 'default', [729, 91], [971, 251]); },
+ "FLEDGE->default");
+promise_test(async () => {
+ return runTest('sharedstorage', [321, 51], [729, 91],
+ 'default', [729, 91], [971, 251]); },
+ "sharedStorage->default");
+promise_test(async () => {
+ return runTest('sharedstorage', [321, 51], [729, 91],
+ 'fledge', [728, 90], [728, 90]); },
+ "sharedStorage->FLEDGE");
+promise_test(async () => {
+ return runTest('fledge', [320, 50], [320, 50],
+ 'sharedstorage', [729, 91], [971, 251]); },
+ "FLEDGE->sharedStorage");
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resolve-to-config-promise.https.html b/testing/web-platform/tests/fenced-frame/resolve-to-config-promise.https.html
new file mode 100644
index 0000000000..97df37ef2b
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resolve-to-config-promise.https.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<title>Test setting auction config's resolveToConfig to a promise</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/default-enabled-features-helper.js"></script>
+
+<body>
+<script>
+// To simulate the time it takes for a promise to resolve, we use a simple
+// timeout that eventually resolves to a boolean.
+function delayValue(value, timeout) {
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve(value);
+ }, timeout);
+ });
+}
+
+promise_test(async(t) => {
+ const key = token();
+
+ const urn = await generateURNFromFledge("resources/embeddee.html", [key],
+ [], delayValue(true, 500));
+ assert_true(urn instanceof FencedFrameConfig);
+ const fencedframe = attachFencedFrame(urn);
+
+ const page1_resp = await nextValueFromServer(key);
+ assert_equals(page1_resp, "PASS",
+ "The page should have loaded.");
+}, 'resolveToConfig set to a promise that resolves to true');
+
+promise_test(async(t) => {
+ const key = token();
+
+ const urn = await generateURNFromFledge("resources/embeddee.html", [key],
+ [], delayValue(false, 500));
+ assert_false(urn instanceof FencedFrameConfig);
+ const fencedframe = attachFencedFrame(urn);
+
+ const page1_resp = await nextValueFromServer(key);
+ assert_equals(page1_resp, "PASS",
+ "The page should have loaded.");
+}, 'resolveToConfig set to a promise that resolves to false');
+
+promise_test(async(t) => {
+ // This tests the case where the resolveToConfig promise resolves before
+ // the auction finishes
+ const key = token();
+
+ const urn = await generateURNFromFledge("resources/embeddee.html", [key],
+ [], delayValue(true, 0));
+ assert_true(urn instanceof FencedFrameConfig);
+ const fencedframe = attachFencedFrame(urn);
+
+ const page1_resp = await nextValueFromServer(key);
+ assert_equals(page1_resp, "PASS",
+ "The page should have loaded.");
+}, 'resolveToConfig set to a promise that immediately resolves');
+
+promise_test(async(t) => {
+ const key = token();
+
+ // This should still resolve, but resolve to a URN.
+ const urn = await generateURNFromFledge("resources/embeddee.html", [key],
+ [], delayValue("invalid", 0));
+ assert_false(urn instanceof FencedFrameConfig);
+ const fencedframe = attachFencedFrame(urn);
+
+ const page1_resp = await nextValueFromServer(key);
+ assert_equals(page1_resp, "PASS",
+ "The page should have loaded.");
+}, 'resolveToConfig set to a promise that resolves to an invalid value');
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-csp.https.html b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-csp.https.html
new file mode 100644
index 0000000000..28fadb296c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-csp.https.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<script src="utils.js"></script>
+<title>Header Inheritance CSP Reporting Page</title>
+<body>
+<script>
+// This file is embedded in an iframe by ancestor-throttle-inner.https.html
+// which in turn has been embedded in a fenced frame by
+// ancestor-throttle.https.html
+async function init() {
+ const [ancestor_key] = parseKeylist();
+ writeValueToServer(ancestor_key, "loaded");
+}
+
+init();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-csp.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-csp.https.html.headers
new file mode 100644
index 0000000000..bb76329b1d
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-csp.https.html.headers
@@ -0,0 +1,2 @@
+Supports-Loading-Mode: fenced-frame
+Content-Security-Policy: frame-ancestors 'self'
diff --git a/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-xfo.https.html b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-xfo.https.html
new file mode 100644
index 0000000000..267aa076c0
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-xfo.https.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<script src="utils.js"></script>
+<title>Header Inheritance XFO Reporting Page</title>
+<body>
+<script>
+// This file is embedded in an iframe by ancestor-throttle-inner.https.html
+// which in turn has been embedded in a fenced frame by
+// ancestor-throttle.https.html
+async function init() {
+ const [ancestor_key] = parseKeylist();
+ writeValueToServer(ancestor_key, "loaded");
+}
+
+init();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-xfo.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-xfo.https.html.headers
new file mode 100644
index 0000000000..63d5019c35
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-xfo.https.html.headers
@@ -0,0 +1,2 @@
+Supports-Loading-Mode: fenced-frame
+X-Frame-Options: SAMEORIGIN
diff --git a/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-inner.https.html
new file mode 100644
index 0000000000..e0977c73f0
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-inner.https.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<script src="utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<title>Header Inheritance Inner Page</title>
+<body>
+<script>
+// This file is embedded in a fenced frame by ancestor-throttle.https.html.
+// This is an intermediate step that embeds another page in an iframe to check
+// that the child page only checks up to this page's origin when deciding
+// if it should load.
+async function init() {
+ const [ancestor_key, embed_url, cross_origin_iframe] =
+ parseKeylist();
+ // The URL will be ancestor-throttle-iframe-*.https.html
+ let iframe_url;
+ if (cross_origin_iframe == "true") {
+ iframe_url = generateURL(new URL(embed_url,
+ get_host_info().HTTPS_REMOTE_ORIGIN), parseKeylist());
+ } else {
+ iframe_url = generateURL(new URL(embed_url,
+ get_host_info().HTTPS_ORIGIN), parseKeylist());
+ }
+
+ const iframe = document.createElement('iframe');
+ iframe.src = iframe_url;
+ document.body.append(iframe);
+}
+
+init();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-inner.https.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-inner.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-nested.https.html b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-nested.https.html
new file mode 100644
index 0000000000..a26b7bfdc2
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-nested.https.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<script src="utils.js"></script>
+<title>Header Inheritance CSP Reporting Page</title>
+<body>
+<script>
+// This file is embedded in an iframe by ancestor-throttle-inner.https.html
+// which in turn has been embedded in a fenced frame by
+// ancestor-throttle.https.html. This in turn will load a same-origin iframe.
+async function init() {
+ const url = new URL(location.href);
+ const embed_url = generateURL(url.searchParams.get("nested_url"),
+ parseKeylist());
+ const iframe = document.createElement('iframe');
+ iframe.src = embed_url;
+ document.body.append(iframe);
+}
+
+init();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-nested.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-nested.https.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-nested.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/automatic-beacon-helper.js b/testing/web-platform/tests/fenced-frame/resources/automatic-beacon-helper.js
new file mode 100644
index 0000000000..d0a4133e84
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/automatic-beacon-helper.js
@@ -0,0 +1,104 @@
+// This is a helper file used for the automatic-beacon-*.https.html tests.
+// To use this, make sure you import these scripts:
+// <script src="/resources/testharness.js"></script>
+// <script src="/resources/testharnessreport.js"></script>
+// <script src="/common/utils.js"></script>
+// <script src="/common/dispatcher/dispatcher.js"></script>
+// <script src="resources/utils.js"></script>
+// <script src="/resources/testdriver.js"></script>
+// <script src="/resources/testdriver-actions.js"></script>
+// <script src="/resources/testdriver-vendor.js"></script>
+// <script src="/common/get-host-info.sub.js"></script>
+
+const NavigationTrigger = {
+ Click: 0,
+ ClickOnce: 1,
+ CrossOriginClick: 2,
+ CrossOriginClickNoOptIn: 3
+};
+
+// Registers an automatic beacon in a given remote context frame, and registers
+// the navigation handler for the frame that will trigger the beacon.
+// remote_context: The context for the fenced frame or URN iframe.
+// beacon_events: An array of FenceEvents to register with the frame.
+// navigation_url: The URL the frame will navigate to.
+// navigation_trigger: How the navigation will be performed. Either through a
+// click, a click with a `once` event, a click in a
+// cross-origin subframe, or a click in a cross-origin
+// subframe with no opt-in header.
+// target: the target of the navigation. Either '_blank' or
+// '_unfencedTop'.
+async function setupAutomaticBeacon(
+ remote_context, beacon_events, navigation_url = 'resources/dummy.html',
+ navigation_trigger = NavigationTrigger.Click, target = '_blank') {
+ const full_url = new URL(navigation_url, location.href);
+ await remote_context.execute(
+ async (
+ NavigationTrigger, beacon_events, navigation_trigger, full_url,
+ target) => {
+ switch (navigation_trigger) {
+ case NavigationTrigger.Click:
+ addEventListener('click', (event) => {
+ beacon_events.forEach((beacon_event) => {
+ window.fence.setReportEventDataForAutomaticBeacons(
+ beacon_event);
+ });
+ window.open(full_url, target);
+ });
+ break;
+ case NavigationTrigger.ClickOnce:
+ beacon_events.forEach((beacon_event) => {
+ window.fence.setReportEventDataForAutomaticBeacons(beacon_event);
+ });
+ addEventListener('click', (event) => {
+ window.open(full_url, target);
+ });
+ break;
+ case NavigationTrigger.CrossOriginClick:
+ case NavigationTrigger.CrossOriginClickNoOptIn:
+ beacon_events.forEach((beacon_event) => {
+ window.fence.setReportEventDataForAutomaticBeacons(beacon_event);
+ });
+ // Add a cross-origin iframe that will perform the top-level
+ // navigation. Do not set the 'Allow-Fenced-Frame-Automatic-Beacons'
+ // header to true.
+ const iframe = await attachIFrameContext({
+ origin: get_host_info().HTTPS_REMOTE_ORIGIN,
+ headers: [[
+ 'Allow-Fenced-Frame-Automatic-Beacons',
+ navigation_trigger == NavigationTrigger.CrossOriginClick ?
+ 'true' :
+ 'false'
+ ]]
+ });
+ await iframe.execute(async (full_url, target) => {
+ addEventListener('click', (event) => {
+ window.open(full_url, target);
+ });
+ }, [full_url, target]);
+ break;
+ }
+ },
+ [NavigationTrigger, beacon_events, navigation_trigger, full_url, target]);
+}
+
+// Checks if an automatic beacon of type `event_type` with contents `event_data`
+// was sent out or not.
+// event_type: The automatic beacon type to check.
+// event_data: The automatic beacon data to check.
+// expected_success: Whether we expect the automatic beacon to be sent.
+// t: The WPT's test object. Only required if
+// expected_success = false.
+async function verifyBeaconData(
+ event_type, event_data, expected_success = true, t) {
+ if (expected_success) {
+ const beacon_initiator_origin =
+ await nextAutomaticBeacon(event_type, event_data);
+ assert_equals(beacon_initiator_origin, get_host_info().HTTPS_ORIGIN);
+ } else {
+ const timeout = new Promise(r => t.step_timeout(r, 1000));
+ const result = await Promise.race(
+ [nextAutomaticBeacon(event_type, event_data), timeout]);
+ assert_true(typeof result === 'undefined');
+ }
+}
diff --git a/testing/web-platform/tests/fenced-frame/resources/automatic-beacon-store.py b/testing/web-platform/tests/fenced-frame/resources/automatic-beacon-store.py
new file mode 100644
index 0000000000..ba1b73201b
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/automatic-beacon-store.py
@@ -0,0 +1,44 @@
+"""
+Automatic beacon store server.
+
+- When a request body is specified, stores the data in the body and serves a 200
+ response without body.
+- When a request body is not specified, serves a 200 response whose body
+ contains the stored value from the automatic beacon. Since the data is stored
+ using a hash of the data as the key, it expects an `expected_body` query
+ parameter to know what key to look up. If the stored value doesn't exist,
+ serves a 200 response with an empty body.
+"""
+import uuid
+import hashlib
+
+NO_DATA_STRING = b"<No data>"
+NOT_SET_STRING = b"<Not set>"
+
+# The server stash requires a uuid to store data. Use a hash of the automatic
+# beacon data as the uuid to store and retrieve the data.
+def string_to_uuid(input):
+ hash_value = hashlib.md5(str(input).encode("UTF-8")).hexdigest()
+ return str(uuid.UUID(hex=hash_value))
+
+def main(request, response):
+ stash = request.server.stash;
+ event_type = request.GET.first(b"type", NO_DATA_STRING)
+
+ # The stash is accessed concurrently by many clients. A lock is used to
+ # avoid interleaved read/write from different clients.
+ with stash.lock:
+ # Requests with a body imply they were sent as an automatic beacon. Note
+ # that this only stores the most recent beacon that was sent.
+ if request.method == "POST":
+ request_body = request.body or NO_DATA_STRING
+ request_headers = request.headers.get("Origin") or NO_DATA_STRING
+ stash.put(string_to_uuid(event_type + request_body),
+ request_headers)
+ return (200, [], b"")
+
+ # Requests without a body imply they were sent as the request from
+ # nextAutomaticBeacon().
+ expected_body = request.GET.first(b"expected_body", NO_DATA_STRING)
+ data = stash.take(string_to_uuid(event_type + expected_body)) or NOT_SET_STRING
+ return(200, [], data)
diff --git a/testing/web-platform/tests/fenced-frame/resources/automatic-beacon-unfenced-page.html b/testing/web-platform/tests/fenced-frame/resources/automatic-beacon-unfenced-page.html
new file mode 100644
index 0000000000..4ce7e0d78a
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/automatic-beacon-unfenced-page.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>Page navigated to by an _unfencedTop navigation</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+
+<body>
+<script>
+ promise_test(async(t) => {
+ // This page is navigated to from an '_unfencedTop' navigation by
+ // '../automatic-beacon-unfenced-top.https.html'. An automatic beacon will
+ // have been sent as a result of the navigation.
+ const beacon_data = "This is the beacon data!";
+ const beacon_initiator_origin = await nextAutomaticBeacon(
+ "reserved.top_navigation_commit", beacon_data);
+ assert_equals(beacon_initiator_origin, get_host_info().HTTPS_ORIGIN);
+ });
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/background-fetch-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/background-fetch-inner.https.html
new file mode 100644
index 0000000000..1bca25a957
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/background-fetch-inner.https.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the result of background fetch</title>
+
+<body>
+ <script>
+ (async function () {
+ const [background_fetch_register_key, method] = parseKeylist();
+ const file = 'background-fetch-inner.https.html.headers';
+
+ navigator.serviceWorker.register("empty-worker.js", { scope: location.href });
+ const registration = await navigator.serviceWorker.ready;
+
+ const url = new URL(location.href);
+
+ let promise;
+ switch (method) {
+ case 'fetch':
+ promise = registration.backgroundFetch.fetch('test-fetch', file);
+ break;
+ case 'get':
+ promise = registration.backgroundFetch.get('test-fetch');
+ break;
+ case 'getIds':
+ promise = registration.backgroundFetch.getIds();
+ break
+ default:
+ promise = Promise.resolve();
+ }
+
+ promise
+ .then(() => {
+ writeValueToServer(background_fetch_register_key,
+ `[backgroundFetch.${method}] Unexpectedly started`);
+ })
+ .catch(() => {
+ writeValueToServer(background_fetch_register_key,
+ `[backgroundFetch.${method}] Failed inside fencedframe as expected`);
+ })
+ .finally(() => {
+ registration.unregister();
+ });
+ })();
+ </script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/background-fetch-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/background-fetch-inner.https.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/background-fetch-inner.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/background-fetch-sw-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/background-fetch-sw-inner.https.html
new file mode 100644
index 0000000000..78e58e5bbf
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/background-fetch-sw-inner.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the result of background fetch in SW</title>
+
+<body>
+ <script type="module">
+ const [background_fetch_register_key, method] = parseKeylist();
+
+ const getController = () => {
+ if (navigator.serviceWorker.controller) {
+ return navigator.serviceWorker.controller;
+ }
+ return new Promise(resolve => {
+ navigator.serviceWorker.addEventListener('controllerchange', () => {
+ resolve(navigator.serviceWorker.controller);
+ });
+ });
+ };
+
+ const sendMessageToServiceWorker = async () => {
+ const ctrl = await getController();
+ return new Promise(resolve => {
+ ctrl.postMessage(method);
+ navigator.serviceWorker.onmessage = e => {
+ resolve(e.data);
+ }
+ });
+ };
+
+ await navigator.serviceWorker.register(
+ "background-fetch-sw.js", { scope: location.href });
+ const data = await sendMessageToServiceWorker();
+
+ writeValueToServer(background_fetch_register_key, data);
+ </script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/background-fetch-sw-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/background-fetch-sw-inner.https.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/background-fetch-sw-inner.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/background-fetch-sw.js b/testing/web-platform/tests/fenced-frame/resources/background-fetch-sw.js
new file mode 100644
index 0000000000..44b7d087b5
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/background-fetch-sw.js
@@ -0,0 +1,36 @@
+self.addEventListener('install', e => e.waitUntil(skipWaiting()));
+self.addEventListener('activate', e => e.waitUntil(clients.claim()));
+
+self.addEventListener('message', async e => {
+ const method = e.data;
+
+ let promise;
+ switch (method) {
+ case 'fetch':
+ promise = self.registration.backgroundFetch.fetch(
+ 'test-fetch', ['background-fetch-inner.https.html.headers'],
+ {title: 'Background Fetch'});
+ break;
+ case 'get':
+ promise = self.registration.backgroundFetch.get('test-fetch')
+ break;
+ case 'getIds':
+ promise = registration.backgroundFetch.getIds();
+ break;
+ default:
+ promise = Promise.resolve();
+ break;
+ }
+
+ const message =
+ await promise
+ .then(() => {
+ return `[backgroundFetch.${method}] Unexpectedly started`;
+ })
+ .catch((e) => {
+ return `[backgroundFetch.${
+ method}] Failed inside fencedframe as expected`;
+ });
+
+ e.source.postMessage(message);
+});
diff --git a/testing/web-platform/tests/fenced-frame/resources/background-sync-helper.js b/testing/web-platform/tests/fenced-frame/resources/background-sync-helper.js
new file mode 100644
index 0000000000..78b69f15de
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/background-sync-helper.js
@@ -0,0 +1,23 @@
+const getOneShotSyncPromise = (registration, method) => {
+ if (method === 'register') {
+ return registration.sync.register('fencedframe-oneshot');
+ } else if (method === 'getTags') {
+ return registration.sync.getTags();
+ }
+ return Promise.resolve();
+};
+
+const getPeriodicSyncPromise = (registration, method) => {
+ if (method === 'register') {
+ return registration.periodicSync.register(
+ 'fencedframe-periodic', {minInterval: 1000});
+ } else if (method === 'getTags') {
+ return registration.periodicSync.getTags();
+ } else if (method === 'unregister') {
+ return registration.periodicSync.unregister('fencedframe-periodic');
+ } else {
+ return Promise.resolve();
+ }
+};
+
+export {getOneShotSyncPromise, getPeriodicSyncPromise}
diff --git a/testing/web-platform/tests/fenced-frame/resources/background-sync-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/background-sync-inner.https.html
new file mode 100644
index 0000000000..81974c803a
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/background-sync-inner.https.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the result of background sync's register</title>
+
+<body>
+<script type="module">
+ import {getOneShotSyncPromise, getPeriodicSyncPromise} from './background-sync-helper.js';
+
+ const [background_sync_register_key] = parseKeylist();
+ const searchParams = new URL(location.href).searchParams;
+ const method = searchParams.get('method');
+ const periodic = searchParams.get('periodic');
+
+ navigator.serviceWorker.register("empty-worker.js", { scope: location.href });
+ const registration = await navigator.serviceWorker.ready;
+
+ try {
+ if (periodic) {
+ await getPeriodicSyncPromise(registration, method);
+ } else {
+ await getOneShotSyncPromise(registration, method);
+ }
+ writeValueToServer(background_sync_register_key, "unexpectedly registered");
+ } catch (e) {
+ writeValueToServer(background_sync_register_key, e.message);
+ } finally {
+ registration.unregister();
+ }
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/background-sync-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/background-sync-inner.https.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/background-sync-inner.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/background-sync-sw-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/background-sync-sw-inner.https.html
new file mode 100644
index 0000000000..b9521a4e20
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/background-sync-sw-inner.https.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the result of background sync's register in SW</title>
+
+<body>
+<script type="module">
+ const [background_sync_register_key] = parseKeylist();
+ const searchParams = new URL(location.href).searchParams;
+ const method = searchParams.get('method');
+ const isPeriodic = searchParams.get('periodic');
+
+ const getController = () => {
+ if (navigator.serviceWorker.controller) {
+ return navigator.serviceWorker.controller;
+ }
+ return new Promise(resolve => {
+ navigator.serviceWorker.addEventListener('controllerchange', () => {
+ resolve(navigator.serviceWorker.controller);
+ });
+ });
+ };
+
+ const sendMessageToServiceWorker = async () => {
+ const ctrl = await getController();
+ return new Promise(resolve => {
+ ctrl.postMessage({method, isPeriodic});
+ navigator.serviceWorker.onmessage = e => {
+ resolve(e.data);
+ }
+ });
+ };
+
+ await navigator.serviceWorker.register(
+ "background-sync-sw.js", { scope: location.href, type: "module" });
+ const data = await sendMessageToServiceWorker();
+
+ writeValueToServer(background_sync_register_key, data);
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/background-sync-sw-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/background-sync-sw-inner.https.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/background-sync-sw-inner.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/background-sync-sw.js b/testing/web-platform/tests/fenced-frame/resources/background-sync-sw.js
new file mode 100644
index 0000000000..5b0c791f0d
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/background-sync-sw.js
@@ -0,0 +1,21 @@
+import {getOneShotSyncPromise, getPeriodicSyncPromise} from './background-sync-helper.js';
+
+self.addEventListener('install', e => e.waitUntil(skipWaiting()));
+self.addEventListener('activate', e => e.waitUntil(clients.claim()));
+
+self.addEventListener('message', async e => {
+ const {method, isPeriodic} = e.data;
+ const promise = isPeriodic ?
+ getPeriodicSyncPromise(self.registration, method) :
+ getOneShotSyncPromise(self.registration, method);
+ const message =
+ await promise
+ .then(() => {
+ return `[background synnc ${method}] Unexpectedly started`;
+ })
+ .catch((e) => {
+ return e.message;
+ });
+
+ e.source.postMessage(message);
+});
diff --git a/testing/web-platform/tests/fenced-frame/resources/badging-sw.js b/testing/web-platform/tests/fenced-frame/resources/badging-sw.js
new file mode 100644
index 0000000000..5bc3c9a190
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/badging-sw.js
@@ -0,0 +1,23 @@
+self.addEventListener('install', e => e.waitUntil(skipWaiting()));
+self.addEventListener('activate', e => e.waitUntil(clients.claim()));
+
+self.addEventListener('message', async e => {
+ const method = e.data;
+
+ let promise;
+ if (method === 'setAppBadge') {
+ promise = self.navigator.setAppBadge(1);
+ } else if (method === 'clearAppBadge') {
+ promise = self.navigator.clearAppBadge();
+ } else {
+ promise = Promise.resolve();
+ }
+
+ const error = await promise
+ .then(() => {
+ return `[Badging API ${method}] Unexpectedly started`;
+ })
+ .catch((e) => e);
+
+ e.source.postMessage(error);
+});
diff --git a/testing/web-platform/tests/fenced-frame/resources/before-unload-inner.html b/testing/web-platform/tests/fenced-frame/resources/before-unload-inner.html
new file mode 100644
index 0000000000..6d23cf88a3
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/before-unload-inner.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the beforeunload event is not fired</title>
+
+<body>
+<script>
+window.onload = () => {
+ const [before_unload_key] = parseKeylist();
+ const url = new URL(location.href);
+ const next_url = url.searchParams.get('next_url');
+
+ if (next_url != null) {
+ writeValueToServer(
+ before_unload_key, 'Loaded the next url in a fenced frame');
+ return;
+ }
+
+ window.onbeforeunload = () => {
+ writeValueToServer(
+ before_unload_key, 'The beforeunload event is unexpectely fired.');
+ };
+
+ location.href =
+ generateURL('before-unload-inner.html?next_url', [before_unload_key]);
+};
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/before-unload-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/before-unload-inner.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/before-unload-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/check-header-referrer.py b/testing/web-platform/tests/fenced-frame/resources/check-header-referrer.py
new file mode 100644
index 0000000000..b06fbc2704
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/check-header-referrer.py
@@ -0,0 +1,16 @@
+import os
+
+from wptserve.utils import isomorphic_decode
+
+
+def main(request, response):
+ response.headers.set(b"supports-loading-mode", b"fenced-frame")
+
+ script = u"""
+ <script src="utils.js"></script>
+ <script>
+ const [referrer_key, _] = parseKeylist();
+ writeValueToServer(referrer_key, "%s")
+ </script>
+ """ % (isomorphic_decode(request.headers.get(b"referer", b"")))
+ return (200, [], script)
diff --git a/testing/web-platform/tests/fenced-frame/resources/check-header-sec-fetch-dest.py b/testing/web-platform/tests/fenced-frame/resources/check-header-sec-fetch-dest.py
new file mode 100644
index 0000000000..98231079b3
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/check-header-sec-fetch-dest.py
@@ -0,0 +1,14 @@
+import os
+
+
+def main(request, response):
+ response.headers.set(b"supports-loading-mode", b"fenced-frame")
+
+ script = u"""
+ <script src="utils.js"></script>
+ <script>
+ const [secfetch_key] = parseKeylist();
+ writeValueToServer(secfetch_key, "%s")
+ </script>
+ """ % (request.headers.get(b"sec-fetch-dest", b"none"))
+ return (200, [], script)
diff --git a/testing/web-platform/tests/fenced-frame/resources/client-hints-iframe-inner.sub.https.html b/testing/web-platform/tests/fenced-frame/resources/client-hints-iframe-inner.sub.https.html
new file mode 100644
index 0000000000..d02abd6957
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/client-hints-iframe-inner.sub.https.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>Client Hint Echoing Iframe</title>
+<body>
+<script>
+window.parent.postMessage({'headers': {
+ 'sec-ch-viewport-width': '{{header_or_default(sec-ch-viewport-width, )}}',
+ 'sec-ch-ua-reduced': '{{header_or_default(sec-ch-ua-reduced, )}}',
+ 'sec-ch-ua-mobile': '{{header_or_default(sec-ch-ua-mobile, )}}',
+}}, '*');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/client-hints-iframe-inner.sub.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/client-hints-iframe-inner.sub.https.html.headers
new file mode 100644
index 0000000000..f500a60ae8
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/client-hints-iframe-inner.sub.https.html.headers
@@ -0,0 +1,4 @@
+Supports-Loading-Mode: fenced-frame
+Accept-CH: sec-ch-viewport-width, sec-ch-ua-reduced
+Feature-Policy: ch-viewport-width *, ch-ua-reduced *
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/fenced-frame/resources/client-hints-inner.sub.https.html b/testing/web-platform/tests/fenced-frame/resources/client-hints-inner.sub.https.html
new file mode 100644
index 0000000000..0271d0290d
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/client-hints-inner.sub.https.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="utils.js"></script>
+<title>Client Hints Helper</title>
+<body>
+<script type="module">
+const [key] = parseKeylist();
+let iframe = document.createElement('iframe');
+let p = new Promise((resolve, reject) => {
+ window.addEventListener('message', e => {
+ resolve(e.data);
+ });
+});
+iframe.src = 'client-hints-iframe-inner.sub.https.html';
+document.body.appendChild(iframe);
+const response = await p;
+const result = {
+ 'root-fenced-frame-headers': {
+ 'sec-ch-viewport-width': '{{header_or_default(sec-ch-viewport-width, )}}',
+ 'sec-ch-ua-reduced': '{{header_or_default(sec-ch-ua-reduced, )}}',
+ 'sec-ch-ua-mobile': '{{header_or_default(sec-ch-ua-mobile, )}}',
+ },
+ 'iframe-headers': response.headers,
+};
+writeValueToServer(key, JSON.stringify(result));
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/client-hints-inner.sub.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/client-hints-inner.sub.https.html.headers
new file mode 100644
index 0000000000..ea4cf59d16
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/client-hints-inner.sub.https.html.headers
@@ -0,0 +1,5 @@
+Supports-Loading-Mode: fenced-frame
+Accept-CH: sec-ch-viewport-width, sec-ch-ua-reduced
+Feature-Policy: ch-viewport-width *, ch-ua-reduced *
+Access-Control-Allow-Origin: *
+
diff --git a/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-iframe-inner.sub.https.html b/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-iframe-inner.sub.https.html
new file mode 100644
index 0000000000..9afb5c6a85
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-iframe-inner.sub.https.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta http-equiv="Accept-CH"
+ content="sec-ch-viewport-width, sec-ch-ua-reduced"/>
+<meta http-equiv="Feature-Policy"
+ content="ch-viewport-width *, ch-ua-reduced *"/>
+<title>Client Hint Echoing Iframe</title>
+<body>
+<script>
+window.parent.postMessage({'headers': {
+ 'sec-ch-viewport-width': '{{header_or_default(sec-ch-viewport-width, )}}',
+ 'sec-ch-ua-reduced': '{{header_or_default(sec-ch-ua-reduced, )}}',
+ 'sec-ch-ua-mobile': '{{header_or_default(sec-ch-ua-mobile, )}}',
+}}, '*');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-iframe-inner.sub.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-iframe-inner.sub.https.html.headers
new file mode 100644
index 0000000000..b7952e5d05
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-iframe-inner.sub.https.html.headers
@@ -0,0 +1,2 @@
+
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-inner.sub.https.html b/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-inner.sub.https.html
new file mode 100644
index 0000000000..b84f16ffd0
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-inner.sub.https.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta http-equiv="Accept-CH"
+ content="sec-ch-viewport-width, sec-ch-ua-reduced"/>
+<meta http-equiv="Feature-Policy"
+ content="ch-viewport-width *, ch-ua-reduced *"/>
+<script src="/resources/testharness.js"></script>
+<script src="utils.js"></script>
+<title>Client Hints Helper</title>
+<body>
+<script type="module">
+const [key] = parseKeylist();
+let iframe = document.createElement('iframe');
+let p = new Promise((resolve, reject) => {
+ window.addEventListener('message', e => {
+ resolve(e.data);
+ });
+});
+iframe.src = 'client-hints-meta-iframe-inner.sub.https.html';
+document.body.appendChild(iframe);
+const response = await p;
+const result = {
+ 'root-fenced-frame-headers': {
+ 'sec-ch-viewport-width': '{{header_or_default(sec-ch-viewport-width, )}}',
+ 'sec-ch-ua-reduced': '{{header_or_default(sec-ch-ua-reduced, )}}',
+ 'sec-ch-ua-mobile': '{{header_or_default(sec-ch-ua-mobile, )}}',
+ },
+ 'iframe-headers': response.headers,
+};
+writeValueToServer(key, JSON.stringify(result));
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-inner.sub.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-inner.sub.https.html.headers
new file mode 100644
index 0000000000..afe7b4f317
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-inner.sub.https.html.headers
@@ -0,0 +1,2 @@
+Supports-Loading-Mode: fenced-frame
+Access-Control-Allow-Origin: *
diff --git a/testing/web-platform/tests/fenced-frame/resources/close.html b/testing/web-platform/tests/fenced-frame/resources/close.html
new file mode 100644
index 0000000000..7fd946d6ff
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/close.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<title>This window will close when it loads</title>
+<script>
+ window.close();
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-apis-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-apis-inner.https.html
new file mode 100644
index 0000000000..211fe216c7
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-apis-inner.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<title>Fenced frame attribution reporting self navigation test</title>
+
+<body>
+<script>
+// This helper function will navigate a fenced frame to a remote origin page.
+// It will then check to make sure that window.fence APIs are not allowed after
+// the navigation.
+const [key] = parseKeylist();
+
+if (location.origin == get_host_info().ORIGIN) {
+ const configs = window.fence.getNestedConfigs();
+ const next_url = getRemoteOriginURL(generateURL(
+ "config-cross-origin-apis-inner.https.html", [key]));
+ location.href = next_url;
+} else {
+ const event = {
+ eventType: "reserved.top_navigation_commit",
+ eventData: "data!",
+ destination: ["buyer"],
+ }
+
+ // These should gracefully fail without badmessaging the renderer.
+ window.fence.setReportEventDataForAutomaticBeacons(event);
+ window.fence.reportEvent(event);
+
+ const configs = window.fence.getNestedConfigs();
+
+ // Report how many configs were obtained. Cross-origin pages should not
+ // obtain any nested configs.
+ writeValueToServer(key, configs.length);
+}
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-apis-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-apis-inner.https.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-apis-inner.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-iframe.https.html b/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-iframe.https.html
new file mode 100644
index 0000000000..17c8347720
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-iframe.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<title>Fenced frame attribution reporting self navigation test</title>
+
+<body>
+<script>
+// This helper function will navigate a child iframe to a remote origin page.
+// It will then check to make sure that window.fence APIs are not allowed after
+// the navigation. This code is meant to run in a fenced frame.
+const [key] = parseKeylist();
+
+const event = {
+ eventType: "reserved.top_navigation_commit",
+ eventData: "data!",
+ destination: ["buyer"],
+}
+
+// These should gracefully fail without badmessaging the renderer.
+window.fence.setReportEventDataForAutomaticBeacons(event);
+window.fence.reportEvent(event);
+
+const configs = window.fence.getNestedConfigs();
+
+// Report how many configs were obtained. Cross-origin pages should not
+// obtain any nested configs.
+writeValueToServer(key, configs.length);
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-iframe.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-iframe.https.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-iframe.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/config-embed-cross-origin-iframe.https.html b/testing/web-platform/tests/fenced-frame/resources/config-embed-cross-origin-iframe.https.html
new file mode 100644
index 0000000000..f21afee011
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/config-embed-cross-origin-iframe.https.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<title>Fenced frame attribution reporting self navigation test</title>
+
+<body>
+<script>
+// This helper function will navigate a child iframe to a remote origin page.
+// It will then check to make sure that window.fence APIs are not allowed after
+// the navigation. This code is meant to run in a fenced frame.
+const [key] = parseKeylist();
+
+const configs = window.fence.getNestedConfigs();
+const next_url = getRemoteOriginURL(generateURL(
+ "config-cross-origin-iframe.https.html", [key]));
+attachIFrame(next_url);
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/config-embed-cross-origin-iframe.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/config-embed-cross-origin-iframe.https.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/config-embed-cross-origin-iframe.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/content-index-sw.js b/testing/web-platform/tests/fenced-frame/resources/content-index-sw.js
new file mode 100644
index 0000000000..c2759d9630
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/content-index-sw.js
@@ -0,0 +1,28 @@
+self.addEventListener('install', e => e.waitUntil(skipWaiting()));
+self.addEventListener('activate', e => e.waitUntil(clients.claim()));
+
+self.addEventListener('message', async event => {
+ const method = event.data;
+ const {index} = self.registration;
+ const id = 'fenced-frame-id-sw';
+
+ let promise;
+ if (method === 'add') {
+ promise = index.add({
+ id,
+ title: 'same title',
+ description: 'same description',
+ url: 'resources/'
+ });
+ } else if (method === 'delete') {
+ promise = index.delete(id);
+ } else if (method === 'getAll') {
+ promise = index.getAll();
+ } else {
+ promise = Promise.resolve();
+ }
+
+ const message = await promise.then(() => 'success').catch(e => e.message);
+
+ event.source.postMessage(message);
+});
diff --git a/testing/web-platform/tests/fenced-frame/resources/cookie-access.https.html b/testing/web-platform/tests/fenced-frame/resources/cookie-access.https.html
new file mode 100644
index 0000000000..34e5681139
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/cookie-access.https.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>Page loaded in a frame in a fenced frame tree</title>
+<script>
+ // This page is loaded either in an iframe or a fenced frame
+ // nested inside a root fenced frame.
+ document.cookie = 'G=nested_in_fenced_frame; SameSite=Lax';
+ const [cookie_value_key] = parseKeylist()
+ const cookie_value = document.cookie;
+ writeValueToServer(cookie_value_key, cookie_value);
+</script>
diff --git a/testing/web-platform/tests/fenced-frame/resources/cookie-access.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/cookie-access.https.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/cookie-access.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/create-credential-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/create-credential-inner.https.html
new file mode 100644
index 0000000000..5725177f21
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/create-credential-inner.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/webauthn/helpers.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the result of navigator.credentials.create</title>
+
+<body>
+<script>
+function base_path() {
+ return location.pathname.replace(/\/[^\/]*$/, '/');
+}
+
+standardSetup(function() {
+ 'use strict';
+ async function init() {
+ // This file is meant to be navigated to from a <fencedframe> element. It
+ // reports back to the page hosting the <fencedframe> whether or not
+ // `navigator.credentials.create` is allowed.
+ const [key] = parseKeylist();
+
+ // Report whether or not `credentials.create` is allowed.
+ createCredential().then(
+ () => {
+ writeValueToServer(key, 'createCredential passed');
+ },
+ () => {
+ writeValueToServer(key, 'createCredential failed');
+ },
+ );
+ }
+
+ init();
+});
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/create-credential-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/create-credential-inner.https.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/create-credential-inner.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/create-popup.html b/testing/web-platform/tests/fenced-frame/resources/create-popup.html
new file mode 100644
index 0000000000..a6cd81ec14
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/create-popup.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>Nested frames in a Fenced Frame tree creating popups</title>
+<script>
+ // It is the document that `popup-noopener-inner.html` loads in a nested
+ // iframe/fenced frame.
+ // It's expected that the opener/openee references should be null, and
+ // window.name should be the empty string.
+ const [popup_noopener_key, popup_openee_key, popup_name_key] = parseKeylist();
+ const src_popup = generateURL(`popup-noopener-destination.html`,
+ [popup_noopener_key, popup_name_key]);
+ const popup = window.open(src_popup, "foo");
+ if (popup) {
+ writeValueToServer(popup_openee_key, "FAIL");
+ } else {
+ writeValueToServer(popup_openee_key, "PASS");
+ }
+</script>
diff --git a/testing/web-platform/tests/fenced-frame/resources/create-popup.html.headers b/testing/web-platform/tests/fenced-frame/resources/create-popup.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/create-popup.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-allowed-inner.html b/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-allowed-inner.html
new file mode 100644
index 0000000000..bdb448c347
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-allowed-inner.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>Page embedded as a fenced frame</title>
+<script>
+ const [key] = parseKeylist();
+ writeValueToServer(key, "loaded");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-allowed-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-allowed-inner.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-allowed-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-blocked-inner.html b/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-blocked-inner.html
new file mode 100644
index 0000000000..990f5ee469
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-blocked-inner.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>Page embedded as a fenced frame</title>
+<script>
+ // This file is expected to be unreachable from
+ // `csp-fenced-frame-src-blocked.html` in the parent directory because of CSP
+ // violation.
+ const [key] = parseKeylist();
+ writeValueToServer(key, "loaded");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-blocked-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-blocked-inner.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-blocked-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-allowed-inner.html b/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-allowed-inner.html
new file mode 100644
index 0000000000..bdb448c347
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-allowed-inner.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>Page embedded as a fenced frame</title>
+<script>
+ const [key] = parseKeylist();
+ writeValueToServer(key, "loaded");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-allowed-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-allowed-inner.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-allowed-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-blocked-inner.html b/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-blocked-inner.html
new file mode 100644
index 0000000000..eb90bb94e9
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-blocked-inner.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>Page embedded as a fenced frame</title>
+<script>
+ // This file is expected to be unreachable from `csp-frame-src-blocked.html`
+ // in the parent directory because of CSP violation.
+ const [key] = parseKeylist();
+ writeValueToServer(key, "loaded");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-blocked-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-blocked-inner.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-blocked-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/csp-inner.html b/testing/web-platform/tests/fenced-frame/resources/csp-inner.html
new file mode 100644
index 0000000000..99df39fdc5
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/csp-inner.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+ <script src="utils.js"></script>
+
+ <style>
+ body {background-color: red;}
+ </style>
+
+ <title>Fenced frame content to test Content Security Policies</title>
+
+ <body>
+ <script>
+ const [csp_key] = parseKeylist();
+
+ function fail() {
+ writeValueToServer(csp_key,
+ "FAIL: img-src policy was not honored in fenced frame");
+ }
+
+ function pass() {
+ // The parent page is going to attempt to pass a
+ // style-src: 'none' CSP to the fenced frame. Make sure that
+ // the header is not honored.
+ const bgcolor = window.getComputedStyle(document.body, null)
+ .getPropertyValue('background-color');
+
+ if (bgcolor != "rgb(255, 0, 0)") {
+ writeValueToServer(csp_key,
+ "FAIL: style-src policy was passed to fenced frame");
+ return;
+ }
+
+ writeValueToServer(csp_key, "pass");
+ }
+ </script>
+ <img src="csp.png" id="my_img" onload="fail();" onerror="pass();">
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/csp-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/csp-inner.html.headers
new file mode 100644
index 0000000000..e89be70a43
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/csp-inner.html.headers
@@ -0,0 +1,2 @@
+Supports-Loading-Mode: fenced-frame
+Content-Security-Policy: img-src 'none' \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/csp.png b/testing/web-platform/tests/fenced-frame/resources/csp.png
new file mode 100644
index 0000000000..53a9748ae0
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/csp.png
Binary files differ
diff --git a/testing/web-platform/tests/fenced-frame/resources/dangling-markup-helper.js b/testing/web-platform/tests/fenced-frame/resources/dangling-markup-helper.js
new file mode 100644
index 0000000000..9e0fff2301
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/dangling-markup-helper.js
@@ -0,0 +1,15 @@
+// These are used in tests that rely on URLs containing dangling markup. See
+// https://github.com/whatwg/fetch/pull/519.
+const kDanglingMarkupSubstrings = [
+ "blo\nck<ed",
+ "blo\rck<ed",
+ "blo\tck<ed",
+ "blo<ck\ned",
+ "blo<ck\red",
+ "blo<ck\ted",
+];
+
+function getTimeoutPromise(t) {
+ return new Promise(resolve =>
+ t.step_timeout(() => resolve("NOT LOADED"), 1500));
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-helper.js b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-helper.js
new file mode 100644
index 0000000000..5b4c292622
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-helper.js
@@ -0,0 +1,53 @@
+// This is a helper file used for the attribution-reporting-*.https.html tests.
+// To use this, make sure you import these scripts:
+// <script src="/resources/testharness.js"></script>
+// <script src="/resources/testharnessreport.js"></script>
+// <script src="/common/utils.js"></script>
+// <script src="/common/dispatcher/dispatcher.js"></script>
+// <script src="resources/utils.js"></script>
+// <script src="/common/get-host-info.sub.js"></script>
+
+async function runDefaultEnabledFeaturesTest(t, should_load, fenced_origin,
+ generator_api="fledge", allow="") {
+ const fencedframe = await attachFencedFrameContext({
+ generator_api: generator_api,
+ attributes: [["allow", allow]],
+ origin: fenced_origin});
+
+ if (!should_load) {
+ const fencedframe_blocked = new Promise(r => t.step_timeout(r, 1000));
+ const fencedframe_loaded = fencedframe.execute(() => {});
+ assert_equals(await Promise.any([
+ fencedframe_blocked.then(() => "blocked"),
+ fencedframe_loaded.then(() => "loaded"),
+ ]), "blocked", "The fenced frame should not be loaded.");
+ return;
+ }
+
+ await fencedframe.execute((generator_api) => {
+ assert_true(
+ document.featurePolicy.allowsFeature('attribution-reporting'),
+ "Attribution reporting should be allowed if the fenced " +
+ "frame loaded using FLEDGE or shared storage.");
+
+ if (generator_api == "fledge") {
+ assert_true(
+ document.featurePolicy.allowsFeature('shared-storage'),
+ "Shared Storage should be allowed if the fenced " +
+ "frame loaded using FLEDGE.");
+ assert_true(
+ document.featurePolicy.allowsFeature('private-aggregation'),
+ "Private Aggregation should be allowed if the fenced " +
+ "frame loaded using FLEDGE.");
+ } else {
+ assert_true(
+ document.featurePolicy.allowsFeature('shared-storage'),
+ "Shared Storage should be allowed if the fenced " +
+ "frame loaded using Shared Storage.");
+ assert_false(
+ document.featurePolicy.allowsFeature('private-aggregation'),
+ "Private Aggregation should be disabled if the fenced " +
+ "frame loaded using Shared Storage.");
+ }
+ }, [generator_api]);
+}
diff --git a/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-navigate.https.html b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-navigate.https.html
new file mode 100644
index 0000000000..6bfb033400
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-navigate.https.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<title>Fenced frame attribution reporting self navigation test</title>
+
+<body>
+<script>
+// This helper function will navigate a fenced frame to a remote origin page.
+// That redirect should succeed to load and the permissions from the previous page should be in
+// place.
+const [key1, key2] = parseKeylist();
+
+const result_val = document.featurePolicy.allowsFeature('attribution-reporting');
+if (location.origin == get_host_info().ORIGIN) {
+ writeValueToServer(key1, result_val);
+
+ const next_url = getRemoteOriginURL(generateURL(
+ "default-enabled-features-navigate.https.html", [key1, key2]));
+ location.href = next_url;
+} else {
+ writeValueToServer(key2, result_val);
+}
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-navigate.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-navigate.https.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-navigate.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-fencedframe.https.html b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-fencedframe.https.html
new file mode 100644
index 0000000000..e098736528
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-fencedframe.https.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+
+<body>
+<script>
+// This page is loaded into a fenced frame. The document policies for this page
+// disable shared storage. This then creates a child iframe to determine if
+// document deliviered policies are reflected in the child frame.
+const [key, should_restrict_select_url] = parseKeylist();
+
+const iframe_url = generateURL(
+ 'default-enabled-features-subframe-iframe.https.html', [key]);
+const iframe = document.createElement("iframe");
+iframe.src = iframe_url;
+if (should_restrict_select_url == "true") {
+ iframe.allow = "shared-storage-select-url 'none';"
+}
+document.body.appendChild(iframe);
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-fencedframe.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-fencedframe.https.html.headers
new file mode 100644
index 0000000000..e52511f18a
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-fencedframe.https.html.headers
@@ -0,0 +1,2 @@
+Supports-Loading-Mode: fenced-frame
+Permissions-Policy: shared-storage=()
diff --git a/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-iframe.https.html b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-iframe.https.html
new file mode 100644
index 0000000000..a3ab056944
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-iframe.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+
+<body>
+<script>
+// This page is loaded into an iframe that is nested within a fenced frame tree.
+// This is used to tell the test whether policies that are restricted by a
+// fenced frame's document policies also are restricted in subframes.
+const [key] = parseKeylist();
+
+const allows_shared_storage =
+ document.featurePolicy.allowsFeature('shared-storage');
+const allows_select_url =
+ document.featurePolicy.allowsFeature('shared-storage-select-url');
+
+writeValueToServer(key, allows_shared_storage + "," + allows_select_url);
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-iframe.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-iframe.https.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-iframe.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/download-helper.js b/testing/web-platform/tests/fenced-frame/resources/download-helper.js
new file mode 100644
index 0000000000..011d5c867f
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/download-helper.js
@@ -0,0 +1,29 @@
+function StreamDownloadFinishDelay() {
+ return 1000;
+}
+
+function DownloadVerifyDelay() {
+ return 1000;
+}
+
+async function VerifyDownload(test_obj, token) {
+ const verifyToken = async (token) => {
+ const url = `resources/download-stash.py?verify-token&token=${token}`;
+ const response = await fetch(url);
+ if (!response.ok) {
+ throw new Error('An error happened in the server');
+ }
+ const message = await response.text();
+ return message === 'TOKEN_SET';
+ };
+
+ return new Promise((resolve) => {
+ test_obj.step_wait(
+ async () => {
+ const result = await verifyToken(token);
+ resolve(result);
+ },
+ 'Check if the download has finished or not',
+ StreamDownloadFinishDelay() + DownloadVerifyDelay());
+ });
+}
diff --git a/testing/web-platform/tests/fenced-frame/resources/download-inner.html b/testing/web-platform/tests/fenced-frame/resources/download-inner.html
new file mode 100644
index 0000000000..9bc816cbf3
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/download-inner.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>The page triggering download embedded as a Fenced Frame</title>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src="download-helper.js"></script>
+ <script src="utils.js"></script>
+ <script>
+ window.addEventListener('DOMContentLoaded', async () => {
+ const [download_key, download_ack_key] = parseKeylist();
+ const type = new URL(location).searchParams.get('type');
+ const href = `download-stash.py?token=${download_key}`;
+
+ if (type == 'anchor') {
+ const anchor = document.querySelector('#download');
+ anchor.href = href;
+ test_driver.click(anchor);
+ } else {
+ const delay = StreamDownloadFinishDelay();
+ location.href = `${href}&finish-delay=${delay}`
+ }
+
+ await writeValueToServer(download_ack_key, 'Triggered the action for download');
+ });
+ </script>
+</head>
+
+<body>
+ <a id="download" download>Download</a>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/resources/download-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/download-inner.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/download-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/download-stash.py b/testing/web-platform/tests/fenced-frame/resources/download-stash.py
new file mode 100644
index 0000000000..497f7cb018
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/download-stash.py
@@ -0,0 +1,28 @@
+import time
+
+
+def main(request, response):
+ token = request.GET[b"token"]
+ response.status = 200
+ response.headers.append(b"Content-Type", b"text/html")
+ if b"verify-token" in request.GET:
+ if request.server.stash.take(token):
+ return u'TOKEN_SET'
+ return u'TOKEN_NOT_SET'
+
+ if b"finish-delay" not in request.GET:
+ # <a download>
+ request.server.stash.put(token, True)
+ return
+
+ # navigation to download
+ response.headers.append(b"Content-Disposition", b"attachment")
+ response.write_status_headers()
+ finish_delay = float(request.GET[b"finish-delay"]) / 1E3
+ count = 10
+ single_delay = finish_delay / count
+ for i in range(count): # pylint: disable=unused-variable
+ time.sleep(single_delay)
+ if not response.writer.write_content(b"\n"):
+ return
+ request.server.stash.put(token, True)
diff --git a/testing/web-platform/tests/fenced-frame/resources/dummy.html b/testing/web-platform/tests/fenced-frame/resources/dummy.html
new file mode 100644
index 0000000000..a0cf50713e
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/dummy.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<title>Dummy page</title>
diff --git a/testing/web-platform/tests/fenced-frame/resources/embeddee.html b/testing/web-platform/tests/fenced-frame/resources/embeddee.html
new file mode 100644
index 0000000000..3423be9aa4
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/embeddee.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>A page embedded as a Fenced Frame for COEP tests</title>
+<script>
+const [uuid] = parseKeylist();
+writeValueToServer(uuid, "PASS");
+</script>
diff --git a/testing/web-platform/tests/fenced-frame/resources/embeddee.html.headers b/testing/web-platform/tests/fenced-frame/resources/embeddee.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/embeddee.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/embedder-policy.js b/testing/web-platform/tests/fenced-frame/resources/embedder-policy.js
new file mode 100644
index 0000000000..8c96afafce
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/embedder-policy.js
@@ -0,0 +1,39 @@
+// This file should be loaded alongside with utils.js.
+//
+// This file is loaded by:
+// - embedder-no-coep.https.html
+// - embedder-require-corp.https.html
+
+// Make input list to be used as a wptserve pipe
+// (https://web-platform-tests.org/writing-tests/server-pipes.html).
+// e.g.
+// args: ['content-type,text/plain','Age,0']
+// return: 'header(content-type,text/plain)|header(Age,0)'
+function generateHeader(headers) {
+ return headers.map((h) => {
+ return 'header(' + h + ')';
+ }).join('|');
+}
+
+// Setup a fenced frame for embedder-* WPTs.
+async function setupTest(test_type, uuid, hostname='') {
+ let headers = ["Supports-Loading-Mode,fenced-frame"];
+ switch (test_type) {
+ case "coep:require-corp":
+ headers.push("cross-origin-embedder-policy,require-corp");
+ headers.push("cross-origin-resource-policy,same-origin");
+ break;
+ case "no coep":
+ break;
+ default:
+ assert_unreachable("unknown test_type:" + test_type);
+ break;
+ }
+ const tmp_url = new URL('resources/embeddee.html', location.href);
+ if (hostname) {
+ tmp_url.hostname = hostname;
+ }
+ tmp_url.searchParams.append("pipe", generateHeader(headers));
+ const url = generateURL(tmp_url.toString(), [uuid]);
+ return attachFencedFrame(url);
+}
diff --git a/testing/web-platform/tests/fenced-frame/resources/empty-worker.js b/testing/web-platform/tests/fenced-frame/resources/empty-worker.js
new file mode 100644
index 0000000000..49ceb2648a
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/empty-worker.js
@@ -0,0 +1 @@
+// Do nothing.
diff --git a/testing/web-platform/tests/fenced-frame/resources/fedcm-get-credential-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/fedcm-get-credential-inner.https.html
new file mode 100644
index 0000000000..f30cd77838
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/fedcm-get-credential-inner.https.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the result of navigator.credentials.get</title>
+
+<body>
+<script>
+function isExpectedErrorMessage(e) {
+ return e.name === 'NotAllowedError' &&
+ e.message ===
+ 'The credential operation is not allowed in a fenced frame tree.';
+}
+
+// This file is meant to be navigated to from a <fencedframe> element. It
+// reports back to the page hosting the <fencedframe> whether or not
+// `navigator.credentials.get` is allowed.
+const [key] = parseKeylist();
+
+const test_options = {
+ federated: {
+ providers: [{
+ configURL: 'https://idp.test/fedcm.json',
+ clientId: '1',
+ nonce: '2',
+ }]
+ }
+};
+navigator.credentials.get(test_options)
+ .then(
+ () => {
+ writeValueToServer(key, 'unexpected passed');
+ },
+ (e) => {
+ if (isExpectedErrorMessage(e)) {
+ writeValueToServer(key, 'navigator.credentials.get failed');
+ } else {
+ writeValueToServer(
+ key, 'navigator.credentials.get failed by unexpected reason');
+ }
+ },
+ );
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/fedcm-get-credential-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/fedcm-get-credential-inner.https.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/fedcm-get-credential-inner.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/fence-api-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/fence-api-inner.https.html
new file mode 100644
index 0000000000..814ea78559
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/fence-api-inner.https.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="utils.js"></script>
+<title>Fenced frame content to test window.fence object</title>
+
+<body>
+<script>
+
+ // Get the token for communication with the parent.
+ const [fence_api_token] = parseKeylist();
+
+ // Check that window.fence is visible inside fenced frames.
+ assert_true(window.fence != null,
+ "window.fence should be visible inside fenced frames");
+ assert_true(fence != null,
+ "fence should be visible inside fenced frames");
+
+ // Tell the parent that the test succeeded.
+ writeValueToServer(fence_api_token, "");
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/fence-api-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/fence-api-inner.https.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/fence-api-inner.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/fenced-frame-loaded.html b/testing/web-platform/tests/fenced-frame/resources/fenced-frame-loaded.html
new file mode 100644
index 0000000000..0054762783
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/fenced-frame-loaded.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<script src="utils.js"></script>
+<title>Fenced frame loaded</title>
+<body>
+<script>
+(async function() {
+ const [parent_key] = parseKeylist();
+ writeValueToServer(parent_key, "fenced frame loaded");
+})();
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/resources/fenced-frame-loaded.html.headers b/testing/web-platform/tests/fenced-frame/resources/fenced-frame-loaded.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/fenced-frame-loaded.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/fenced-frame-set-name-and-report-ready-for-outermost-document-to-navigate.html b/testing/web-platform/tests/fenced-frame/resources/fenced-frame-set-name-and-report-ready-for-outermost-document-to-navigate.html
new file mode 100644
index 0000000000..9b67be775e
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/fenced-frame-set-name-and-report-ready-for-outermost-document-to-navigate.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>Nested fenced frame named navigation helper</title>
+<!-- This is a helper file. It is meant to be the document loaded inside a
+ nested fenced frame by `navigate-by-name-inner.html`. Once this document is
+ loaded and changes its `window.name` to `target_frame`, it reports to the
+ server so that the outermost document can attempt to navigate it by name.
+ (The navigation should not succeed - see the test expectations).
+-->
+<body>
+<script>
+ const [ready_for_navigation_key] = parseKeylist();
+ window.name = "target_frame";
+ writeValueToServer(ready_for_navigation_key, "READY");
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/fenced-frame-set-name-and-report-ready-for-outermost-document-to-navigate.html.headers b/testing/web-platform/tests/fenced-frame/resources/fenced-frame-set-name-and-report-ready-for-outermost-document-to-navigate.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/fenced-frame-set-name-and-report-ready-for-outermost-document-to-navigate.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/fledge-bidding-logic.py b/testing/web-platform/tests/fenced-frame/resources/fledge-bidding-logic.py
new file mode 100644
index 0000000000..c91b31fd02
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/fledge-bidding-logic.py
@@ -0,0 +1,116 @@
+# These functions are used by FLEDGE to determine the logic for the ad buyer.
+# For our testing purposes, we only need the minimal amount of boilerplate
+# code in place to allow them to be invoked properly and move the FLEDGE
+# process along. The tests generally do usually not deal with reporting results,
+# so we leave `reportWin` empty unless we need to call registerAdBeacon(). See
+# `generateURNFromFledge` in "utils.js" to see how this file is used.
+
+from wptserve.utils import isomorphic_decode
+
+def main(request, response):
+ # Set up response headers.
+ headers = [
+ ('Content-Type', 'Application/Javascript'),
+ ('Ad-Auction-Allowed', 'true')
+ ]
+
+ # Parse URL params.
+ requested_size = request.GET.first(b"requested-size", None)
+ ad_with_size = request.GET.first(b"ad-with-size", None)
+ automatic_beacon = request.GET.first(b"automatic-beacon", None)
+
+ # Use URL params to modify Javascript.
+ requested_size_check = ''
+ if requested_size is not None:
+ # request.GET stores URL keys and values in iso-8859-1 binary encoding. We
+ # have to decode the values back to a string to parse width/height. Don't
+ # bother sanitizing the size, because it is sanitized before auction logic
+ # runs already.
+ width, height = isomorphic_decode(requested_size).split('-')
+
+ requested_size_check = (
+ f'''
+ if (!(browserSignals.requestedSize.width === '{width}') &&
+ (browserSignals.requestedSize.height === '{height}')) {{
+ throw new Error('requestedSize missing/incorrect in browserSignals');
+ }}
+ '''
+ )
+
+ render_obj = 'ad.renderURL'
+ if ad_with_size is not None:
+ render_obj = '{ url: ad.renderURL, width: "100px", height: "50px" }'
+
+ component_render_obj = 'component.renderURL'
+ if ad_with_size is not None:
+ component_render_obj = (
+ '''{
+ url: component.renderURL,
+ width: "100px",
+ height: "50px"
+ }
+ '''
+ )
+
+ register_ad_beacon = ''
+ if automatic_beacon is not None:
+ register_ad_beacon = (
+ '''registerAdBeacon({
+ 'reserved.top_navigation_start':
+ browserSignals.interestGroupOwner +
+ '/fenced-frame/resources/automatic-beacon-store.py?type=reserved.top_navigation_start',
+ 'reserved.top_navigation_commit':
+ browserSignals.interestGroupOwner +
+ '/fenced-frame/resources/automatic-beacon-store.py?type=reserved.top_navigation_commit',
+ });
+ '''
+ )
+
+ # Generate Javascript.
+ # Note: Python fstrings use double-brackets ( {{, }} ) to insert bracket
+ # literals instead of substitution sequences.
+ generate_bid = (
+ f'''function generateBid(
+ interestGroup,
+ auctionSignals,
+ perBuyerSignals,
+ trustedBiddingSignals,
+ browserSignals) {{
+ {requested_size_check}
+ const ad = interestGroup.ads[0];
+
+ // `auctionSignals` controls whether or not component auctions are
+ // allowed.
+ let allowComponentAuction = (typeof auctionSignals === 'string' &&
+ auctionSignals.includes('bidderAllowsComponentAuction'));
+
+ let result = {{
+ 'ad': ad,
+ 'bid': 1,
+ 'render': {render_obj},
+ 'allowComponentAuction': allowComponentAuction
+ }};
+ if (interestGroup.adComponents && interestGroup.adComponents.length > 0)
+ result.adComponents = interestGroup.adComponents.map((component) => {{
+ return {component_render_obj};
+ }});
+ return result;
+ }}
+ '''
+ )
+
+ report_win = (
+ f'''function reportWin(
+ auctionSignals,
+ perBuyerSignals,
+ sellerSignals,
+ browserSignals) {{
+ {register_ad_beacon}
+ return;
+ }}
+ '''
+ )
+
+ content = f'{generate_bid}\n{report_win}'
+
+ return (headers, content)
diff --git a/testing/web-platform/tests/fenced-frame/resources/fledge-decision-logic.py b/testing/web-platform/tests/fenced-frame/resources/fledge-decision-logic.py
new file mode 100644
index 0000000000..63b544552c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/fledge-decision-logic.py
@@ -0,0 +1,66 @@
+# These functions are used by FLEDGE to determine the logic for the ad seller.
+# For our testing purposes, we only need the minimal amount of boilerplate
+# code in place to allow them to be invoked properly and move the FLEDGE
+# process along. The tests do not deal with reporting results, so we leave
+# `reportResult` empty. See `generateURNFromFledge` in "utils.js" to see how
+# this file is used.
+
+from wptserve.utils import isomorphic_decode
+
+def main(request, response):
+ # Set up response headers.
+ headers = [
+ ('Content-Type', 'Application/Javascript'),
+ ('Ad-Auction-Allowed', 'true')
+ ]
+
+ # Parse URL params.
+ requested_size = request.GET.first(b"requested-size", None)
+
+ # Use URL params to modify Javascript.
+ requested_size_check = ''
+ if requested_size is not None:
+ # request.GET stores URL keys and values in iso-8859-1 binary encoding. We
+ # have to decode the values back to a string to parse width/height. Don't
+ # bother sanitizing the size, because it is sanitized before auction logic
+ # runs already.
+ width, height = isomorphic_decode(requested_size).split('-')
+
+ requested_size_check = (
+ f'''
+ if (!(auctionConfig.requestedSize.width === '{width}') &&
+ (auctionConfig.requestedSize.height === '{height}')) {{
+ throw new Error('requestedSize missing/incorrect in auctionConfig');
+ }}
+ '''
+ )
+
+ # Generate Javascript.
+ # Note: Python fstrings use double-brackets ( {{, }} ) to insert bracket
+ # literals instead of substitution sequences.
+ score_ad = (
+ f'''function scoreAd(
+ adMetadata,
+ bid,
+ auctionConfig,
+ trustedScoringSignals,
+ browserSignals) {{
+ {requested_size_check}
+ return 2*bid;
+ }}
+ '''
+ )
+
+ report_result = (
+ f'''function reportResult(
+ auctionConfig,
+ browserSignals) {{
+ {requested_size_check}
+ return;
+ }}
+ '''
+ )
+
+ content = f'{score_ad}\n{report_result}'
+
+ return (headers, content)
diff --git a/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-create-nested.https.html b/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-create-nested.https.html
new file mode 100644
index 0000000000..9a56a3d9fb
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-create-nested.https.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="utils.js"></script>
+<title>Test nested fenced frame navigation (by a parent frame setting its src).</title>
+
+<body>
+ <script>
+ async function init() { // Needed in order to use top-level await.
+ // This file is meant to run in a <fencedframe>. It communicates with
+ // the embedder to confirm that nested fenced frames can be navigated.
+
+ const [navigation_key, navigation_ack_key] = parseKeylist();
+
+ // Create URL prefixes to simulate different origins.
+ // (www1 and www2 are different origins)
+ const simple_url = generateURL("frame-navigation-inner-simple.https.html",
+ [navigation_key, navigation_ack_key]);
+
+ const origin1_simple_url = getRemoteOriginURL(simple_url);
+ const origin2_simple_url = getRemoteOriginURL(simple_url)
+ .toString().replace("www1", "www2");
+
+ const url_prefix = location.href + "/../";
+
+ // Tell the embedder that this frame has loaded.
+ writeValueToServer(navigation_key, "create-nested");
+ await nextValueFromServer(navigation_ack_key);
+
+ // Create an inner frame.
+ inner_frame = attachFencedFrame(origin1_simple_url);
+ // Wait for our parent to tell us they're done communicating.
+ await nextValueFromServer(navigation_ack_key);
+
+ // Navigate (cross-origin) and wait.
+ inner_frame.config = new FencedFrameConfig(
+ generateURL(origin2_simple_url, []));
+ await nextValueFromServer(navigation_ack_key);
+
+ // Navigate (same-origin) and wait.
+ inner_frame.config = new FencedFrameConfig(
+ generateURL(origin2_simple_url, []));
+ await nextValueFromServer(navigation_ack_key);
+ }
+
+ init();
+ </script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-create-nested.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-create-nested.https.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-create-nested.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-simple.https.html b/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-simple.https.html
new file mode 100644
index 0000000000..643ea48a76
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-simple.https.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="utils.js"></script>
+<title>Test that a fenced frame successfully loaded.</title>
+
+<body>
+ <script>
+ // This file is meant to run in a <fencedframe>. It reports back to the
+ // outermost page to confirm that loading succeeded.
+ const [navigation_key, navigation_ack_key] = parseKeylist();
+ writeValueToServer(navigation_key, "pass");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-simple.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-simple.https.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-simple.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/frame-with-intersection-observer.html b/testing/web-platform/tests/fenced-frame/resources/frame-with-intersection-observer.html
new file mode 100644
index 0000000000..dd36b20399
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/frame-with-intersection-observer.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/resources/testharness.js"></script>
+<script src="utils.js"></script>
+<div id="target" style="width: 100px; height: 100px; position: fixed; top: 0px; left: 0px"></div>
+<script>
+let next_token = 0;
+function init() {
+ const tokens = parseKeylist();
+ let observer = new IntersectionObserver((entries) => {
+ assert_equals(entries.length, 1);
+ let rect = entries[0].intersectionRect.x + "," +
+ entries[0].intersectionRect.y + "," +
+ entries[0].intersectionRect.width + "," +
+ entries[0].intersectionRect.height + "," +
+ entries[0].isVisible;
+ writeValueToServer(tokens[next_token], rect);
+ next_token = next_token + 1;
+
+ if (next_token == tokens.length) {
+ observer.disconnect();
+ }
+ }, {trackVisibility: true, delay: 100, threshold: [0.6, 0.75]});
+ observer.observe(document.getElementById("target"));
+}
+
+init();
+</script>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/resources/frame-with-intersection-observer.html.headers b/testing/web-platform/tests/fenced-frame/resources/frame-with-intersection-observer.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/frame-with-intersection-observer.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/gamepad-inner.html b/testing/web-platform/tests/fenced-frame/resources/gamepad-inner.html
new file mode 100644
index 0000000000..3e253e4915
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/gamepad-inner.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>getGamepads should throw an error in a fenced frame</title>
+<script>
+const [key] = parseKeylist();
+try {
+ navigator.getGamepads();
+ writeValueToServer(key, 'Expected exception but successed');
+} catch (e) {
+ writeValueToServer(key, e.name);
+}
+</script>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/resources/gamepad-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/gamepad-inner.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/gamepad-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-inner.html b/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-inner.html
new file mode 100644
index 0000000000..122debfe27
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-inner.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>Send the results of getNestedConfigs() to the embedder</title>
+<script>
+const [key] = parseKeylist();
+const configs = window.fence.getNestedConfigs();
+const data_to_send = [configs.length];
+writeValueToServer(key, data_to_send.join(","));
+</script>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-inner.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-nested-iframe.html b/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-nested-iframe.html
new file mode 100644
index 0000000000..9bd5d9f492
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-nested-iframe.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>getGamepads should throw an error in a fenced frame</title>
+<body>
+ <script>
+ const [key] = parseKeylist();
+ attachIFrame(generateURL("get-nested-configs-inner.html", [key]));
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-nested-iframe.html.headers b/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-nested-iframe.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-nested-iframe.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/get_battery.html b/testing/web-platform/tests/fenced-frame/resources/get_battery.html
new file mode 100644
index 0000000000..0532deca4b
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/get_battery.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>getBattery should fail in a fenced frame</title>
+<script>
+async function init() { // Needed in order to use top-level await.
+ const [uuid] = parseKeylist();
+ try {
+ await navigator.getBattery();
+ writeValueToServer(uuid, 'Expected an exception but the call succeeded');
+ } catch (err) {
+ writeValueToServer(uuid, err.name);
+ }
+}
+
+init();
+</script>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/resources/get_battery.html.headers b/testing/web-platform/tests/fenced-frame/resources/get_battery.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/get_battery.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/header-referrer-inner.html b/testing/web-platform/tests/fenced-frame/resources/header-referrer-inner.html
new file mode 100644
index 0000000000..2940dbac8e
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/header-referrer-inner.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the value of header.referrer</title>
+
+<body>
+<script>
+async function init() { // Needed in order to use top-level await.
+ // This file is meant to run in a <fencedframe>. It reports back to the
+ // outermost page the value of `referer` in the request header:
+ // 1.) Nested iframes inside a fenced frame
+ // 2.) Nested fenced frames
+ // 3.) Top-level fenced frames (aka this frame) after initial navigation
+ const [referrer_key, referrer_ack_key] = parseKeylist();
+
+ const referrer_url = generateURL("check-header-referrer.py",
+ [referrer_key, referrer_ack_key]);
+
+ const iframe = document.createElement('iframe');
+ iframe.src = referrer_url;
+ document.body.append(iframe);
+
+ // Wait for ACK, so we know that the outer page has read the last value from
+ // the `referrer_key` stash that the iframe above wrote to, and we can write
+ // to it again.
+ await nextValueFromServer(referrer_ack_key);
+
+ attachFencedFrame(referrer_url);
+
+ // Wait for ACK, so we know that the outer page has read the last value from
+ // the `referrer_key` stash that the nested fenced frame wrote to, and we can
+ // can write to it again.
+ await nextValueFromServer(referrer_ack_key);
+
+ location.href = referrer_url;
+}
+
+init();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/header-referrer-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/header-referrer-inner.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/header-referrer-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/header-secFetchDest-inner.html b/testing/web-platform/tests/fenced-frame/resources/header-secFetchDest-inner.html
new file mode 100644
index 0000000000..aa3fe9e34c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/header-secFetchDest-inner.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the value of `Sec-Fetch-Dest` header</title>
+
+<body>
+<script>
+(() => {
+ // This file is meant to run in a <fencedframe>. It reports back to the
+ // outermost page the value of `Sec-Fetch-Dest` in the request header for
+ // nested iframes inside a fenced frame.
+ const [sec_fetch_dest_value_key] = parseKeylist();
+ const https_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
+ const https_origin_url =
+ getRemoteOriginURL(
+ generateURL(
+ 'check-header-sec-fetch-dest.py',
+ [sec_fetch_dest_value_key]));
+
+ const iframe = document.createElement('iframe');
+ iframe.src = https_origin_url;
+ document.body.append(iframe);
+})();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/header-secFetchDest-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/header-secFetchDest-inner.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/header-secFetchDest-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/history-back-and-forward-should-not-work-in-fenced-tree-inner.html b/testing/web-platform/tests/fenced-frame/resources/history-back-and-forward-should-not-work-in-fenced-tree-inner.html
new file mode 100644
index 0000000000..9620249d76
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/history-back-and-forward-should-not-work-in-fenced-tree-inner.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="utils.js"></script>
+<title>history-back-and-forward-should-not-work-in-fenced-tree-inner</title>
+
+<body>
+ <script>
+ // This is a helper file that will serve as the document loaded inside
+ // a fenced frame by 'history-back-and-forward-should-not-work-in-fenced
+ // -tree' Once loaded, it will sequentially perform the back and forward
+ // history navigations to observe whether these methods were successfuly
+ // restricted for the fenced tree.
+
+ const [history_navigation_performed_key, outer_page_ready_key,
+ embed_scope] = parseKeylist();
+
+ (async function () {
+ const url = new URL(location.href);
+ const test = url.searchParams.get("test");
+
+ writeValueToServer(history_navigation_performed_key, "yes");
+
+ // Execute history.back() within fenced frame and iframe.
+ await nextValueFromServer(outer_page_ready_key);
+ window.history.back();
+ writeValueToServer(history_navigation_performed_key, "yes");
+
+ // Execute history.forward() within fenced frame and iframe.
+ await nextValueFromServer(outer_page_ready_key);
+ window.history.forward();
+ writeValueToServer(history_navigation_performed_key, "yes");
+
+ if (embed_scope === "outerPage::fencedFrame::iframe") return;
+
+ const iframe = document.createElement('iframe');
+ const iframe_embed_scope = "outerPage::fencedFrame::iframe";
+ iframe.src = generateURL(
+ "history-back-and-forward-should-not-work-in-fenced-tree-" +
+ "inner.html",
+ [history_navigation_performed_key, outer_page_ready_key,
+ iframe_embed_scope]);
+ document.body.append(iframe);
+ })();
+ </script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/history-back-and-forward-should-not-work-in-fenced-tree-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/history-back-and-forward-should-not-work-in-fenced-tree-inner.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/history-back-and-forward-should-not-work-in-fenced-tree-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/history-length-fenced-navigations-replace-do-not-contribute-to-joint-inner.html b/testing/web-platform/tests/fenced-frame/resources/history-length-fenced-navigations-replace-do-not-contribute-to-joint-inner.html
new file mode 100644
index 0000000000..726fafd65b
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/history-length-fenced-navigations-replace-do-not-contribute-to-joint-inner.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="utils.js"></script>
+<title>Nested fenced frame named navigation helper</title>
+
+<body>
+<script>
+(async () => {
+ // We need to wait for the window's `load` event to fire, because client-side
+ // redirect navigations that take place before a document is "completely
+ // loaded" [1] are carried out with replacement, as specified in [2]. Just
+ // waiting for `load` is not enough though! After the `load` event is fired
+ // (but before a document is marked "completely loaded"), a microtask
+ // checkpoint is performed, which is where the below `Promise`'s `then`
+ // handler is invoked (i.e., the rest of the script). So if we just resolve
+ // the promise and continue, the whole script continues in the next immediate
+ // microtask before the document is completely loaded. So we have to queue
+ // another task so that we only continue executing once the document is
+ // considered completely loaded, and then `location.href` assignments will not
+ // be made with replacement history handling.
+ //
+ // [1]: https://html.spec.whatwg.org/C#the-end:completely-finish-loading
+ // [2]: https://html.spec.whatwg.org/#the-location-interface:completely-loaded
+ await new Promise(resolve => {
+ window.onload = e => {
+ setTimeout(resolve, 0);
+ };
+ });
+
+ const kNavigationLimit = 5;
+ // This is a helper file meant to be loaded inside a fenced frame. It performs
+ // various navigations inside of the "fence" defined by this document, and
+ // ensures that they are all done in a replace-only fashion [1].
+ // Once we ensure that they are all done with replacement, we report back to
+ // the outermost page via the server stash, and it ensures that there was no
+ // impact on the joint session history as observed from beyond the fence.
+ //
+ // [1]: https://html.spec.whatwg.org/C/#hh-replace
+
+ // See documentation in the outer page.
+ const [fenced_navigation_complete_key,
+ outer_page_ready_for_next_fenced_navigation_key,
+ level] = parseKeylist();
+
+ const url = new URL(location.href);
+ const is_top_level_fenced_frame = (level == "top-level-fenced-frame");
+
+ ////////////// Navigation code that may impact `history.length` should go here
+ // The code in this block performs navigations that will run inside:
+ // - The top-level fenced frame
+ // - The nested fenced frame
+ // - The nested iframe
+
+ // First, perform some real navigations to this same page. Normally this would
+ // increase `history.length`.
+ if (url.searchParams.get("navigationNumber") == null)
+ url.searchParams.append("navigationNumber", 0);
+
+ let navigationNumber = parseInt(url.searchParams.get("navigationNumber"));
+
+ if (navigationNumber <= kNavigationLimit) {
+ url.searchParams.set('navigationNumber', navigationNumber + 1);
+ location.href = url;
+ return;
+ }
+
+ // At this point we're done performing 5 subsequent navigations...
+
+ // Next, perform `history.pushState()`s.
+ history.pushState({} , "");
+ history.pushState({} , "");
+ history.pushState({} , "");
+ ////////////// END
+
+ // Finally observe `history.length` from within the fenced frame, and report
+ // the results back to the outermost page.
+ if (history.length == 1) {
+ writeValueToServer(fenced_navigation_complete_key, "PASS > " +
+ level);
+ } else {
+ writeValueToServer(fenced_navigation_complete_key,
+ "FAIL > " + level + " history.length: " +
+ history.length);
+ }
+
+ // We're only testing fenced frames, nested fenced frames, and iframes nested
+ // within fenced frames. The below code adds a nested fenced frame and a
+ // nested iframe, so it should only be reached by the top-level fenced frame.
+ if (level != "top-level-fenced-frame")
+ return;
+
+ // Only top-level fenced frames will attach a nested fenced frame and run the
+ // same tests there.
+ await nextValueFromServer(outer_page_ready_for_next_fenced_navigation_key);
+ const nested_fenced_frame_level = "nested-fenced-frame";
+ attachFencedFrame(generateURL(
+ "history-length-fenced-navigations-replace-do-not-" +
+ "contribute-to-joint-inner.html",
+ [fenced_navigation_complete_key,
+ outer_page_ready_for_next_fenced_navigation_key,
+ nested_fenced_frame_level]));
+
+ await nextValueFromServer(outer_page_ready_for_next_fenced_navigation_key);
+ const iframe = document.createElement('iframe');
+ const nested_iframe_level = "nested-iframe";
+ iframe.src = generateURL(
+ "history-length-fenced-navigations-replace-do-not-contribute-to-joint-" +
+ "inner.html",
+ [fenced_navigation_complete_key,
+ outer_page_ready_for_next_fenced_navigation_key,
+ nested_iframe_level]);
+ document.body.append(iframe);
+})();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/history-length-fenced-navigations-replace-do-not-contribute-to-joint-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/history-length-fenced-navigations-replace-do-not-contribute-to-joint-inner.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/history-length-fenced-navigations-replace-do-not-contribute-to-joint-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/history-length-outer-page-navigation-not-reflected-in-fenced-inner.html b/testing/web-platform/tests/fenced-frame/resources/history-length-outer-page-navigation-not-reflected-in-fenced-inner.html
new file mode 100644
index 0000000000..2bdb90ab64
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/history-length-outer-page-navigation-not-reflected-in-fenced-inner.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>history-length-outer-page-navigation-not-reflected-in-fenced-inner</title>
+
+<body>
+<script>
+(async () => {
+ // This is a helper file that will servec as the document loaded inside
+ // a fenced frame b 'history-length-outer-page-navigation-not-reflected-in-
+ // fenced' Once loaded, it reports to the observed value of history.length to
+ // the server so that the outer document can assert the value is 1.
+
+ const [fenced_history_length_key, outer_page_ready_for_next_navigation_key,
+ embed_scope, embed_scope_reporting] =
+ parseKeylist();
+
+ const url = new URL(location.href);
+
+ if (embed_scope == "outer_page::iframe"){
+ ////////////// BEGIN NAVIGATIONS
+ // This block performs a sequence of 'kNavigationLimit' navigations in:
+ // -- the iframe
+ const kNavigationLimit = 5
+
+ const url = new URL(location.href);
+
+ // First, perform some real navigations as well as history.pushState to
+ // this same page. Normally this would increase `history.length`.
+ if (url.searchParams.get("navigationCount") == null)
+ url.searchParams.append("navigationCount", 1);
+
+ let navigationCount = parseInt(url.searchParams.get("navigationCount"));
+
+ if (navigationCount <= kNavigationLimit) {
+ url.searchParams.set('navigationCount', ++navigationCount);
+ location.href = url;
+ history.pushState({} , "");
+ return;
+ }
+ ////////////// END
+ writeValueToServer(outer_page_ready_for_next_navigation_key, "READY");
+ return
+ }
+
+ if (embed_scope == embed_scope_reporting) {
+ // Observe 'history.length' from within the 'embed_scope_reporting',
+ // and report the results back to the outer page.
+ if (history.length == 1) {
+ writeValueToServer(fenced_history_length_key,
+ "PASS > " + " history.length: " + history.length);
+ } else {
+ writeValueToServer(fenced_history_length_key,
+ "FAIL > " + " history.length: " + history.length);
+ }
+ return
+ }
+
+ if (embed_scope_reporting == "outer_page::fenced_frame::iframe") {
+ // Append an iframe to the 'outer_page::fenced_frame' to report
+ // history.length to the outer_page from within the iframe
+ const iframe = document.createElement('iframe');
+ const embed_scope = "outer_page::fenced_frame::iframe";
+ iframe.src = generateURL(
+ "history-length-outer-page-navigation-not-reflected-in-fenced-inner.html",
+ [fenced_history_length_key, outer_page_ready_for_next_navigation_key,
+ embed_scope, embed_scope_reporting]);
+ document.body.append(iframe);
+ return
+ }
+})();
+</script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/history-length-outer-page-navigation-not-reflected-in-fenced-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/history-length-outer-page-navigation-not-reflected-in-fenced-inner.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/history-length-outer-page-navigation-not-reflected-in-fenced-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/ignore-child-fenced-frame-onload-event-inner.html b/testing/web-platform/tests/fenced-frame/resources/ignore-child-fenced-frame-onload-event-inner.html
new file mode 100644
index 0000000000..4fe496f29c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/ignore-child-fenced-frame-onload-event-inner.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<title>child frame with delayed onload event</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+
+<body>
+</body>
+<script>
+ (function () {
+ const [element_type, toplevel_loaded_key, result_key] = parseKeylist();
+
+ // Delays the onload event of the iframe for 2 sec.
+ if (element_type == "iframe") {
+ const img = document.createElement("img");
+ img.src = "/common/square.png?pipe=trickle(d2)";
+ document.body.appendChild(img);
+ return;
+ }
+
+ const iframe = document.createElement('iframe');
+ iframe.src = generateURL("ignore-child-fenced-frame-onload-event-inner." +
+ "html", ["iframe"]);
+ document.body.append(iframe);
+
+ let iframe_loaded = false;
+ let result = "passed";
+ window.onload = async function () {
+ const toplevel_loaded = await readValueFromServer(toplevel_loaded_key);
+ if (!toplevel_loaded.status || !iframe_loaded)
+ result = "failed";
+ writeValueToServer(result_key, result);
+ }
+
+ iframe.onload = function () {
+ iframe_loaded = true;
+ }
+ })();
+</script>
+
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/resources/ignore-child-fenced-frame-onload-event-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/ignore-child-fenced-frame-onload-event-inner.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/ignore-child-fenced-frame-onload-event-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/key-value-store.py b/testing/web-platform/tests/fenced-frame/resources/key-value-store.py
new file mode 100644
index 0000000000..c9fd81b2a3
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/key-value-store.py
@@ -0,0 +1,46 @@
+"""Key-Value store server.
+
+The request takes "key=" and "value=" URL parameters. The key must be UUID
+generated by token().
+
+- When only the "key=" is specified, serves a 200 response whose body contains
+ the stored value specified by the key. If the stored value doesn't exist,
+ serves a 200 response with an empty body.
+- When both the "key=" and "value=" are specified, stores the pair and serves
+ a 200 response without body.
+"""
+
+
+def main(request, response):
+ key = request.GET.get(b"key")
+ value = request.GET.get(b"value")
+
+ # Store the value.
+ # We have two ways to check the truthiness of `value`:
+ # 1. `if value`
+ # 2. `if value != None`
+ # While (1) is the most intuitive, we actually need (2), which is a looser
+ # check. We need the looser check so that if the URL contains `&value=` to
+ # set the value equal to the empty string (a case we need to support), this
+ # condition still evaluates to true and we enter this branch.
+ if value != None:
+ # We opted for (2) above which is the looser of the truthiness tests
+ # that lets empty strings into this branch. So you might think that when
+ # the URL contains `&value=`, then the `value` variable here would be
+ # equal `""`, but instead it equals the empty byte string. If you just
+ # store that empty byte string into the stash and retrieve it later, you
+ # actually get <Not set> because it doesn't recognize the empty byte
+ # string as a real value, so we instead have to override it to the empty
+ # normal string, and then we can store it for later use. This is
+ # because we have to support storage and retrieval of empty strings.
+ if type(value) is bytes and value == b'':
+ value = ""
+
+ request.server.stash.put(key, value)
+ return (200, [], b"")
+
+ # Get the value.
+ data = request.server.stash.take(key)
+ if not data and data != "":
+ return (200, [], b"<Not set>")
+ return (200, [], data)
diff --git a/testing/web-platform/tests/fenced-frame/resources/location-ancestorOrigins-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/location-ancestorOrigins-inner.https.html
new file mode 100644
index 0000000000..3c9411c520
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/location-ancestorOrigins-inner.https.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the value of location.ancestorOrigins</title>
+
+<body>
+<script>
+async function init() { // Needed in order to use top-level await.
+ // This file is meant to run in a <fencedframe>. It reports back to the
+ // outermost page the value of `location.ancestorOrigins` correct for:
+ // 1.) Top-level fenced frames
+ // 2.) Nested iframes inside a fenced frame
+ // 3.) Nested fenced frames
+ const url = new URL(location.href);
+
+ const [location_ao_key, location_ao_ack_key, nested] = parseKeylist();
+
+ const is_nested_fenced_frame = nested == "nested";
+
+ // Report `location.ancestorOrigins`.
+ writeValueToServer(location_ao_key, Array.from(location.ancestorOrigins).join());
+
+ // If this page is a nested fenced frame, all we need to do is report the
+ // top-level value.
+ if (is_nested_fenced_frame)
+ return;
+
+ // Wait for ACK, so we know that the outer page has read the last value from
+ // the `location_ao_key` stash and we can write to it again.
+ await nextValueFromServer(location_ao_ack_key);
+
+ const nested_url = generateURL("location-ancestorOrigins-inner.https.html",
+ [location_ao_key, location_ao_ack_key, "nested"]);
+
+ // Send `location.ancestorOrigins` from an iframe.
+ const iframe = document.createElement('iframe');
+ iframe.src = nested_url;
+ const load_promise = new Promise((resolve, reject) => {
+ iframe.onload = resolve;
+ iframe.onerror = reject;
+ });
+ document.body.append(iframe);
+
+ // Wait for ACK, so we know that the outer page has read the ancestorOrigins
+ // from the iframe.
+ await nextValueFromServer(location_ao_ack_key);
+
+ attachFencedFrame(nested_url);
+}
+
+init();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/location-ancestorOrigins-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/location-ancestorOrigins-inner.https.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/location-ancestorOrigins-inner.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-destination.https.html b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-destination.https.html
new file mode 100644
index 0000000000..f12849c8ec
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-destination.https.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>Nested fenced frame named navigation helper</title>
+<!-- See `navigate-ancestor-from-nested-{fencedframe,iframe}.https.html` for
+ more documentation -->
+<script>
+ // This is a helper file. It is the document that
+ // `navigate-ancestor-from-nested{fencedframe,iframe}-helper.https.html`
+ // explicitly navigates the "correct" ancestor frame to, for any test run by
+ // `navigate-ancestor-test-runner.https.html`. The test itself is responsible
+ // for verifying that this document loaded in the correct frame. We just
+ // simply report that we successfully wound up in this document, to indicate
+ // that we're finished.
+ const [navigate_ancestor_key] = parseKeylist();
+ writeValueToServer(navigate_ancestor_key,
+ "navigate-ancestor-destination.https.html");
+</script>
diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-destination.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-destination.https.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-destination.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-fenced-frame.https.html b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-fenced-frame.https.html
new file mode 100644
index 0000000000..74800b969f
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-fenced-frame.https.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<title>Navigate ancestor helper</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+
+<body>
+<script>
+(async () => {
+ // This document is loaded into a fenced frame by
+ // `navigate-ancestor-test-runner.https.html`. It creates a nested fenced
+ // frame and navigates it to `navigate-ancestor-helper.https.html`.
+
+ // - navigate_ancestor_key:
+ // Sent by `navigate-ancestor-destination.https.html`. We listen to it, and
+ // report back to our embedder that it loaded correctly and did not navigate
+ // *this* frame.
+ // - navigate_ancestor_from_nested_key:
+ // Sent by us to our embedder to indicate (depending on the message) either:
+ // - PASS: The nested fenced frame was navigated correctly when it tried to
+ // navigate its ancestor (parent or top) frame
+ // - FAIL: When the nested fenced frame tried to navigate its ancestor
+ // frame, it actually navigated *this frame*, which is wrong
+ const [navigate_ancestor_key, navigate_ancestor_from_nested_key,
+ ancestor_type] = parseKeylist();
+
+ window.onbeforeunload = e => {
+ writeValueToServer(navigate_ancestor_from_nested_key,
+ `FAIL nested fenced frame ${ancestor_type}`);
+ }
+
+ attachFencedFrame(generateURL(`navigate-ancestor-helper.https.html`,
+ [navigate_ancestor_key, ancestor_type]));
+ await nextValueFromServer(navigate_ancestor_key);
+ window.onbeforeunload = null;
+ writeValueToServer(navigate_ancestor_from_nested_key, "PASS");
+})();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-fenced-frame.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-fenced-frame.https.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-fenced-frame.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-iframe.https.html b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-iframe.https.html
new file mode 100644
index 0000000000..63a0cca8b4
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-iframe.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<title>Navigate ancestor helper from nested fenced frame</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+
+<body>
+<script>
+(async () => {
+ // This document is loaded into a fenced frame by
+ // `navigate-ancestor-test-runner.https.html`. It creates a nested iframe and
+ // navigates it to `navigate-ancestor-helper.https.html`.
+
+ // navigate_ancestor_from_nested_key sent by us to our embedder to
+ // indicate that an message was sent from the nested iframe when it failed to
+ // navigate the ancestor (this) frame.
+ const [navigate_ancestor_key, navigate_ancestor_from_nested_key,
+ ancestor_type] = parseKeylist();
+
+ // An message should be sent from the iframe.
+ window.addEventListener('message', (e) => {
+ window.onbeforeunload = null;
+ writeValueToServer(
+ navigate_ancestor_from_nested_key,
+ `PASS: [${ancestor_type}] ${e.data}`);
+ });
+
+ // When the iframe tries to navigate its ancestor frame, it should not
+ // navigate *this* frame, because the sandboxed navigation browsing context
+ // flag [1] must be set in fenced frame trees.
+ // [1] https://html.spec.whatwg.org/multipage/origin.html#sandboxed-navigation-browsing-context-flag
+ const iframe = document.createElement('iframe');
+ iframe.src = generateURL(`navigate-ancestor-helper.https.html`,
+ [navigate_ancestor_key, ancestor_type]);
+ document.body.append(iframe);
+})();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-iframe.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-iframe.https.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-iframe.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-helper.https.html b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-helper.https.html
new file mode 100644
index 0000000000..2cd8fcf786
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-helper.https.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>Navigate ancestor helper</title>
+
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="utils.js"></script>
+<body>
+<script>
+(async () => {
+ // This document is loaded into either a top-level fenced frame, a nested
+ // fenced frame, or an iframe in a top-level fenced frame. In any case, this
+ // document is always the inner-most document in any test. It navigates an
+ // ancestor frame by clicking the anchor above via script. When this document
+ // is loaded in a fenced frame, the frame that should actually navigate is
+ // this one, since fenced frames are top-level browsing contexts.
+ // When this document is loaded into a top-level fenced frame or a nested
+ // fenced frame, we test that the right frame is navigated in
+ // `navigate-ancestor-test-runner.https.html`. When this document is loaded
+ // into an iframe in a top-level fenced frame, we test that the navigation is
+ // blocked due to the sandbox behavior of fenced frame trees.
+ const [navigate_ancestor_key, ancestor_type] = parseKeylist();
+ const url = generateURL(`navigate-ancestor-destination.https.html`,
+ [navigate_ancestor_key]);
+ await simulateGesture();
+ try {
+ window[ancestor_type].location = url;
+ } catch (e) {
+ window[ancestor_type].postMessage('location change failed.');
+ }
+})();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-helper.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-helper.https.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-helper.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-helper.js b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-helper.js
new file mode 100644
index 0000000000..ade17c69f2
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-helper.js
@@ -0,0 +1,28 @@
+
+async function runNavigateAncestorTest(test_type, ancestor_type) {
+ // See documentation in `resources/navigate-ancestor-test-runner.https.html`.
+ // For each test type here, this document opens a new auxiliary window that
+ // runs the actual test. The tests in some way or another, direct a frame
+ // *inside* a fenced frame to navigate an ancestor frame via an
+ // <a target="_parent|_top"></a>. We need to run the real test in a new window
+ // so that if that window ends up navigating unexpectedly (because the fenced
+ // frame can accidentally navigated its embedder, for example) we can detect
+ // it from ths page, which never navigates away.
+ const navigate_ancestor_key = token();
+ const navigate_ancestor_from_nested_key = token();
+
+ const win = window.open(generateURL(
+ "resources/navigate-ancestor-test-runner.https.html",
+ [navigate_ancestor_key, navigate_ancestor_from_nested_key]));
+ await new Promise(resolve => {
+ win.onload = resolve;
+ });
+
+ const pagehidePromise = new Promise(resolve => {
+ win.onpagehide = resolve;
+ });
+
+ await win.runTest(test_type, ancestor_type);
+ win.close();
+ await pagehidePromise;
+}
diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-test-runner.https.html b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-test-runner.https.html
new file mode 100644
index 0000000000..d0f2e8d694
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-test-runner.https.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<title>Test navigating an ancestor frame from within a fenced frame</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+
+<body>
+<script>
+// This function is called by `window.opener`, which is a same-origin window.
+window.runTest = function(test_type, ancestor_type) {
+ // Messages by this key are sent from
+ // `navigate-ancestor-destination.https.html` to let us know if the "_parent"
+ // navigations performed inside fenced frames landed on the right page.
+ // If somehow *this document* gets navigated unexpectedly, the test will fail
+ // given `beforeunloadPromise` below.
+ // For "nested" tests, this document hosts a top-level fenced frame navigated
+ // to `navigate-ancestor-from-nested-{fenced-frame,iframe}.https.html`,
+ // which itself hosts a nested fenced frame or iframe. The top-level fenced
+ // frame will wait for the right confirmation that the nested document has
+ // operated correctly, and report back to *us* that everything is OK via this
+ // key below.
+ const [navigate_ancestor_key, navigate_ancestor_from_nested_key] =
+ parseKeylist();
+
+ const beforeunloadPromise = new Promise((resolve, reject) => {
+ window.onbeforeunload = e => {
+ reject(`The top-level test runner document does not navigate when a ` +
+ `${test_type} navigates ${ancestor_type}`);
+ }
+ });
+
+ let test_promise = null;
+ switch (test_type) {
+ case 'top-level fenced frame':
+ // This fenced frame will attempt to navigate its parent to
+ // `navigate-ancestor-destination.https.html`. It should end up navigating
+ // *itself* since it is a top-level browsing context. Just in case it
+ // accidentally navigates *this* frame, we have an `onbeforeunload`
+ // handler that will automatically fail the test before.
+ attachFencedFrame(generateURL(
+ `navigate-ancestor-helper.https.html`,
+ [navigate_ancestor_key, ancestor_type]));
+ test_promise = nextValueFromServer(navigate_ancestor_key);
+ break;
+ case 'nested fenced frame':
+ attachFencedFrame(generateURL(
+ `navigate-ancestor-from-nested-fenced-frame.https.html`,
+ [navigate_ancestor_key, navigate_ancestor_from_nested_key,
+ ancestor_type]));
+ test_promise = nextValueFromServer(navigate_ancestor_from_nested_key)
+ .then(message => {
+ if (message != "PASS") {
+ throw message;
+ }
+ });
+ break;
+ case 'nested iframe':
+ attachFencedFrame(generateURL(
+ `navigate-ancestor-from-nested-iframe.https.html`,
+ [navigate_ancestor_key, navigate_ancestor_from_nested_key,
+ ancestor_type]));
+ test_promise = nextValueFromServer(navigate_ancestor_from_nested_key)
+ .then(message => {
+ if (message != `PASS: [${ancestor_type}] location change failed.`) {
+ throw message;
+ }
+ });
+
+ break;
+ }
+
+ return Promise.race([test_promise, beforeunloadPromise]);
+}
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-inner.html b/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-inner.html
new file mode 100644
index 0000000000..c7d7d6f278
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-inner.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="utils.js"></script>
+<title>Fenced frame hosting named frames</title>
+
+<body>
+<script>
+async function init() {
+ // This file is meant to run in a <fencedframe>. It sets up multiple frames
+ // all with the name `target_frame` in the following arrangements:
+ // 1.) A top-level fenced frame
+ // 2.) An iframe within a fenced frame
+ // 3.) A nested fenced frame
+ // Navigations to all of the above should fail, and thus should open a new
+ // top-level popup window instead of navigating these frames.
+
+ const [ready_for_navigation_key, test_type] = parseKeylist();
+
+ switch (test_type) {
+ case "top-level fenced frame":
+ // Set up the named frame and report to the outer document that we're ready
+ // for it to try and navigate the named frame.
+ window.name = "target_frame";
+ writeValueToServer(ready_for_navigation_key, "READY");
+ break;
+ case "nested iframe":
+ const iframe = document.createElement('iframe');
+ iframe.name = "target_frame";
+ document.body.append(iframe);
+ writeValueToServer(ready_for_navigation_key, "READY");
+ break;
+ case "nested fenced frame":
+ // This fenced frame will report to the outermost document when it is ready.
+ const ff =
+ attachFencedFrame(generateURL(
+ "fenced-frame-set-name-and-report-ready-for-" +
+ "outermost-document-to-navigate.html",
+ [ready_for_navigation_key]));
+ break;
+ }
+}
+
+init();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-inner.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-reporting-helper.html b/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-reporting-helper.html
new file mode 100644
index 0000000000..d3bd955697
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-reporting-helper.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>Navigate reporting helper</title>
+<!-- This document is used as a helper by `../navigate-by-name.html`. That test
+ attempts to navigate various frames all named `target_frame`, to this
+ document. All of these navigations should fail, due to the frames being
+ unreachable to the initiator (because of the "fence" of the fenced frame).
+ As a result, this document should always load in a new top-level
+ "outermost" pop-up window.
+-->
+
+<script>
+const [navigation_success_key] = parseKeylist();
+
+// We're currently using `window.opener` as a proxy for "did this load in a new
+// outermost popup window?". Note that if we try and test navigations initiated
+// from inside a fenced frame and they open up in a new outermost popup, there
+// will be no opener by default (crbug.com/1250694) so using `window.opener` as
+// a signal will be insufficient. In order to test anchor navigations to this
+// document from within a fenced frame, we'll need a better signal for
+// outermost-ness. We should consider adding a value to `document.loadingMode`
+// for fenced frames:
+// https://wicg.github.io/nav-speculation/prerendering.htmlprerendering.html#browsing-context-loading-mode.
+//
+// Alternatively if we really want to detect if this loaded inside a fenced
+// frame, we could just remove the opt-in headers and then implementations that
+// support fenced frame opt-ins would timeout if they somehow don't honor the
+// fence on named frame navigations, but that's not a very good outcome.
+if (window.opener) {
+ writeValueToServer(navigation_success_key, "PASS");
+} else {
+ writeValueToServer(navigation_success_key, "FAIL");
+}
+</script>
diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-reporting-helper.html.headers b/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-reporting-helper.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-reporting-helper.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-nested-config.html b/testing/web-platform/tests/fenced-frame/resources/navigate-nested-config.html
new file mode 100644
index 0000000000..85c5194c6c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/navigate-nested-config.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="utils.js"></script>
+<title>Navigate a fenced frame to a nested config</title>
+<body>
+<script>
+ const [key] = parseKeylist();
+ const configs = window.fence.getNestedConfigs();
+ const ff = document.createElement("fencedframe");
+ ff.config = configs[0]
+ document.body.appendChild(ff);
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-nested-config.html.headers b/testing/web-platform/tests/fenced-frame/resources/navigate-nested-config.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/navigate-nested-config.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-layout-map-inner.html b/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-layout-map-inner.html
new file mode 100644
index 0000000000..59170c7512
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-layout-map-inner.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the value of navigator.keyboard.getLayoutMap</title>
+
+<body>
+<script>
+async function init() { // Needed in order to use top-level await.
+ // This file is meant to be navigated to from a <fencedframe> element. It
+ // reports back to the page hosting the <fencedframe> whether or not
+ // `keyboard.getLayoutMap` is allowed.
+ const keyboard_layout_key = parseKeylist();
+ // Report whether or not `navigator.keyboard.getLayoutMap()` is allowed.
+ navigator.keyboard.getLayoutMap().then(
+ () => { writeValueToServer(keyboard_layout_key, "resolved"); },
+ () => { writeValueToServer(keyboard_layout_key, "rejected");},
+ );
+}
+
+init();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-layout-map-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-layout-map-inner.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-layout-map-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-lock-inner.html b/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-lock-inner.html
new file mode 100644
index 0000000000..105166c7ad
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-lock-inner.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the value of navigator.keyboard.lock</title>
+
+<body>
+<script>
+async function init() { // Needed in order to use top-level await.
+ // This file is meant to be navigated to from a <fencedframe> element. It
+ // reports back to the page hosting the <fencedframe> whether or not
+ // `keyboard.lock` is allowed.
+ const [keyboard_lock_key] = parseKeylist();
+ // Report whether or not `navigator.keyboard.lock()` is allowed.
+ navigator.keyboard.lock().then(
+ () => { writeValueToServer(keyboard_lock_key, "resolved"); },
+ () => { writeValueToServer(keyboard_lock_key, "rejected");},
+ );
+}
+
+init();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-lock-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-lock-inner.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-lock-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/notification-sw.js b/testing/web-platform/tests/fenced-frame/resources/notification-sw.js
new file mode 100644
index 0000000000..e9b1e2b9dd
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/notification-sw.js
@@ -0,0 +1,20 @@
+self.addEventListener('install', e => e.waitUntil(skipWaiting()));
+self.addEventListener('activate', e => e.waitUntil(clients.claim()));
+
+self.addEventListener('message', async event => {
+ const method = event.data;
+
+ if (method === 'constructor') {
+ try {
+ new Notification('test');
+ } catch (e) {
+ event.source.postMessage(e.message);
+ }
+ } else if (method === 'showNotification') {
+ try {
+ await self.registration.showNotification('test', {body: 'test'});
+ } catch (e) {
+ event.source.postMessage(e.message);
+ }
+ }
+});
diff --git a/testing/web-platform/tests/fenced-frame/resources/opaque-ad-sizes-utils.js b/testing/web-platform/tests/fenced-frame/resources/opaque-ad-sizes-utils.js
new file mode 100644
index 0000000000..edf8640f20
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/opaque-ad-sizes-utils.js
@@ -0,0 +1,47 @@
+async function runOpaqueAdSizesTest(input_width, input_height, output_width, output_height) {
+ // Attach a FLEDGE fenced frame whose outer container has dimensions
+ // `input_width` by `input_height`.
+ const frame = await attachFencedFrameContext({
+ generator_api: "fledge", resolve_to_config: true, attributes: [
+ ["width", input_width], ["height", input_height]]});
+
+ const assert_dimensions =
+ (label, input_width, input_height, output_width, output_height) => {
+ assert_equals(getComputedStyle(document.documentElement).width,
+ output_width+"px",
+ label + " the computed width coerces to " + output_width);
+ assert_equals(window.innerWidth, output_width,
+ label + " the innerWidth " + input_width + " coerces to " + output_width);
+ assert_equals(window.innerHeight, output_height,
+ label + " the innerHeight " + input_height + " coerces to " + output_height);
+ }
+
+ // Assert that the fenced frame sees its dimensions rounded to the nearest
+ // ad size.
+ await frame.execute(assert_dimensions,
+ ["After navigation", input_width, input_height, output_width, output_height]);
+
+ // Assert that the embedder sees the fenced frame's original dimensions.
+ assert_equals(frame.width, input_width.toString(),
+ "The outer container width is the requested width.");
+ assert_equals(frame.height, input_height.toString(),
+ "The outer container height is the requested height.");
+
+ // Resize the fenced frame's outer container.
+ const new_size_x = 320;
+ const new_size_y = 50;
+ frame.width = new_size_x;
+ frame.height = new_size_y;
+
+ // Refresh the fenced frame.
+ await frame.execute(() => {
+ window.executor.suspend(() => {
+ location.href = location.href;
+ });
+ });
+
+ // Observe that navigations after the first don't change the fenced frame's
+ // inner dimensions.
+ await frame.execute(assert_dimensions,
+ ["After resizing", input_width, input_height, output_width, output_height]);
+}
diff --git a/testing/web-platform/tests/fenced-frame/resources/payment-handler-sw.js b/testing/web-platform/tests/fenced-frame/resources/payment-handler-sw.js
new file mode 100644
index 0000000000..8b5e83cddf
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/payment-handler-sw.js
@@ -0,0 +1,10 @@
+self.addEventListener('install', e => e.waitUntil(skipWaiting()));
+self.addEventListener('activate', e => e.waitUntil(clients.claim()));
+
+self.addEventListener('message', event => {
+ try {
+ self.registration.paymentManager;
+ } catch (e) {
+ event.source.postMessage(e);
+ }
+});
diff --git a/testing/web-platform/tests/fenced-frame/resources/permission-api-denied-inner.html b/testing/web-platform/tests/fenced-frame/resources/permission-api-denied-inner.html
new file mode 100644
index 0000000000..06724ac061
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/permission-api-denied-inner.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the result of navigator.permissions.query</title>
+
+<body>
+ <script>
+ (async () => {
+ const [permission_key, permission_name] = parseKeylist();
+ // Push permission without userVisibleOnly:true is not supported.
+ let user_visible_only = permission_name === 'push' ? true : false;
+ const result = await navigator.permissions.query({ name: permission_name, userVisibleOnly: user_visible_only });
+ writeValueToServer(permission_key, `result: ${result.state}`);
+ })();
+ </script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/permission-api-denied-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/permission-api-denied-inner.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/permission-api-denied-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/permission-geolocation-inner.html b/testing/web-platform/tests/fenced-frame/resources/permission-geolocation-inner.html
new file mode 100644
index 0000000000..07c3e662bf
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/permission-geolocation-inner.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the result of navigator.geolocation.getCurrentPosition()</title>
+
+<body>
+<script>
+(async () => {
+ const [permission_geolocation_key] = parseKeylist();
+ const result = await new Promise(resolve => {
+ navigator.geolocation.getCurrentPosition(
+ () => resolve('granted'), () => resolve('denied'));
+ });
+ writeValueToServer(permission_geolocation_key, `result: ${result}`);
+})();
+</script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/permission-geolocation-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/permission-geolocation-inner.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/permission-geolocation-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/permission-geolocation-test-runner.html b/testing/web-platform/tests/fenced-frame/resources/permission-geolocation-test-runner.html
new file mode 100644
index 0000000000..724a35ce9a
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/permission-geolocation-test-runner.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<title>Fenced frame content to report the result of navigator.geolocation.getCurrentPosition()</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 src="utils.js"></script>
+
+<body>
+<script>
+
+window.runTest = async (fenced_frame_url) => {
+ const [permission_geolocation_key] = parseKeylist();
+ await test_driver.set_permission({name: 'geolocation'}, 'granted', true);
+
+ attachFencedFrame(generateURL(fenced_frame_url, [permission_geolocation_key]));
+ const actual_result = await nextValueFromServer(permission_geolocation_key);
+
+ assert_equals(
+ actual_result, 'result: denied',
+ 'geolocation permission is not permitted for fenced frames.');
+};
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/permission-notification-inner.html b/testing/web-platform/tests/fenced-frame/resources/permission-notification-inner.html
new file mode 100644
index 0000000000..d01d10034c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/permission-notification-inner.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the result of Notification.requestPermission</title>
+
+<body>
+<script>
+(async () => {
+ const [permission_notification_key] = parseKeylist();
+ const result = await Notification.requestPermission();
+ writeValueToServer(permission_notification_key, `result: ${result}`);
+})();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/permission-notification-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/permission-notification-inner.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/permission-notification-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/popup-noopener-destination.html b/testing/web-platform/tests/fenced-frame/resources/popup-noopener-destination.html
new file mode 100644
index 0000000000..30cc21f22c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/popup-noopener-destination.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>Destination page opened by a frame in a Fenced Frame tree</title>
+<script>
+ // It is the document that `popup-noopener-inner.html` loads in a new
+ // window/tab from a root fenced frame, an iframe in a fenced frame and from
+ // a nested fenced frame. It's expected that any popup opened from a Fenced
+ // Frame tree cannot reach the opener.
+ const [popup_noopener_key, popup_name_key] = parseKeylist();
+ if (window.opener) {
+ writeValueToServer(popup_noopener_key, "FAIL: window.opener is not null");
+ } else {
+ writeValueToServer(popup_noopener_key, "PASS");
+ }
+ if (window.name) {
+ writeValueToServer(popup_name_key, "FAIL: window.name is not empty");
+ } else {
+ writeValueToServer(popup_name_key, "PASS");
+ }
+</script>
diff --git a/testing/web-platform/tests/fenced-frame/resources/popup-noopener-inner.html b/testing/web-platform/tests/fenced-frame/resources/popup-noopener-inner.html
new file mode 100644
index 0000000000..6a79fd21b2
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/popup-noopener-inner.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="utils.js"></script>
+<title>Fenced frame creating popups</title>
+
+<body>
+<script>
+async function init() {
+ // This file is meant to run in a <fencedframe>. It sets up multiple frames
+ // in the following arrangements:
+ // 1.) A top-level fenced frame
+ // 2.) An iframe within a fenced frame
+ // 3.) A nested fenced frame
+ // All of the above frames create a popup which should not return a reference
+ // to the new window.
+
+ const [popup_noopener_key, popup_openee_key, popup_name_key, test_type] =
+ parseKeylist();
+
+ switch (test_type) {
+ case "top-level fenced frame":
+ src_popup = generateURL(`popup-noopener-destination.html`,
+ [popup_noopener_key, popup_name_key]);
+ const popup = window.open(src_popup, "foo");
+ if (popup) {
+ writeValueToServer(popup_openee_key, "FAIL");
+ } else {
+ writeValueToServer(popup_openee_key, "PASS");
+ }
+ break;
+ case "nested iframe":
+ const iframe = document.createElement('iframe');
+ document.body.append(iframe);
+ iframe.src = generateURL(`create-popup.html`,
+ [popup_noopener_key, popup_openee_key, popup_name_key]);
+ break;
+ case "nested fenced frame":
+ const ff =
+ attachFencedFrame(generateURL(`create-popup.html`,
+ [popup_noopener_key, popup_openee_key, popup_name_key]));
+ break;
+ }
+
+}
+
+init();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/popup-noopener-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/popup-noopener-inner.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/popup-noopener-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/postmessage-config.html b/testing/web-platform/tests/fenced-frame/resources/postmessage-config.html
new file mode 100644
index 0000000000..3c20190420
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/postmessage-config.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>A page opened in a pop-up that sends a FencedFrameConfig</title>
+<script>
+ async function init() {
+ const [key] = parseKeylist();
+
+ const config = await generateURNFromFledge(
+ "embeddee.html", [key], [], true);
+
+ window.opener.postMessage(config, "*");
+ }
+ init();
+</script>
diff --git a/testing/web-platform/tests/fenced-frame/resources/prerender-inner.html b/testing/web-platform/tests/fenced-frame/resources/prerender-inner.html
new file mode 100644
index 0000000000..a523ef31c1
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/prerender-inner.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the result of prerendering</title>
+
+<body>
+<script>
+(() => {
+ [prerender_ready_key, prerender_loaded_key, prerender_activated_key] =
+ parseKeylist();
+ document.addEventListener('prerenderingchange', () => {
+ writeValueToServer(prerender_activated_key, 'activated');
+ });
+ if (document.prerendering) {
+ writeValueToServer(prerender_ready_key, 'ready');
+ } else {
+ writeValueToServer(prerender_loaded_key, 'loaded');
+ }
+})();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/prerender-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/prerender-inner.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/prerender-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/presentation-receiver-inner.html b/testing/web-platform/tests/fenced-frame/resources/presentation-receiver-inner.html
new file mode 100644
index 0000000000..2e170dd91b
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/presentation-receiver-inner.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the result of navigator.presentation.receiver</title>
+
+<body>
+<script>
+(async () => {
+ const [presentation_receiver_key] = parseKeylist();
+ const result = await navigator.presentation.receiver;
+ if (result == null) {
+ writeValueToServer(presentation_receiver_key, "denied");
+ } else {
+ writeValueToServer(presentation_receiver_key, "allowed");
+ }
+})();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/presentation-receiver-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/presentation-receiver-inner.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/presentation-receiver-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/remote-context-executor.https.html b/testing/web-platform/tests/fenced-frame/resources/remote-context-executor.https.html
new file mode 100644
index 0000000000..6b2f5ccc00
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/remote-context-executor.https.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<title>Script to wait for instructions from RemoteContext.</title>
+
+<body>
+<script>
+window.addEventListener("load", async () => {
+ // Find the uuid to communicate with the parent.
+ const uuid = new URLSearchParams(window.location.search).get('uuid');
+
+ // Wait for the window to have its size computed and become visible,
+ // so that simulated user gestures will be handled properly.
+ while (window.innerWidth == 0) {
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ }
+
+ // Create a RemoteContext Executor, which will wait in the background
+ // for scripts to execute.
+ window.executor = new Executor(uuid);
+});
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/report-url.html b/testing/web-platform/tests/fenced-frame/resources/report-url.html
new file mode 100644
index 0000000000..e0b7d0982a
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/report-url.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>A page embedded as a fenced frame that reports the document URL</title>
+<script>
+const [uuid] = parseKeylist();
+writeValueToServer(uuid, location.href);
+</script>
diff --git a/testing/web-platform/tests/fenced-frame/resources/report-url.html.headers b/testing/web-platform/tests/fenced-frame/resources/report-url.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/report-url.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/resize-lock-inner.html b/testing/web-platform/tests/fenced-frame/resources/resize-lock-inner.html
new file mode 100644
index 0000000000..fbaf436330
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/resize-lock-inner.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+ <script src="utils.js"></script>
+ <title>Fenced frame content to report any changes in inner dimensions</title>
+
+ <body>
+ <script>
+ async function init() {
+ const [resize_lock_inner_page_is_ready_key,
+ resize_lock_resize_is_done_key,
+ resize_lock_report_inner_dimensions_key] = parseKeylist();
+
+ writeValueToServer(resize_lock_inner_page_is_ready_key, "ready");
+
+ await nextValueFromServer(resize_lock_resize_is_done_key);
+
+ const response = window.innerWidth + "x" + window.innerHeight;
+ writeValueToServer(resize_lock_report_inner_dimensions_key, response);
+ }
+
+ init();
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/resize-lock-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/resize-lock-inner.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/resize-lock-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/response-204.py b/testing/web-platform/tests/fenced-frame/resources/response-204.py
new file mode 100644
index 0000000000..e6cf8d4ac9
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/response-204.py
@@ -0,0 +1,4 @@
+def main(request, response):
+ response_headers = []
+ body = "No content"
+ return (204, response_headers, body) \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-iframe.sub.html b/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-iframe.sub.html
new file mode 100644
index 0000000000..7ee8b7d98f
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-iframe.sub.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<title>Iframe content to load a fenced frame and report a value to the server</title>
+<script src="utils.js"></script>
+
+<body>
+<script>
+ const fencedframe = document.createElement("fencedframe");
+ fencedframe.config = new FencedFrameConfig(
+ generateURL("sandbox-mandatory-flags-inner.sub.html?key={{GET[key]}}" +
+ "&value={{GET[value]}}", []));
+ document.body.appendChild(fencedframe);
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-inner.sub.html b/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-inner.sub.html
new file mode 100644
index 0000000000..5f400b5bde
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-inner.sub.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<title>Fenced frame content to report a value to the server</title>
+
+<body>
+<img src="key-value-store.py?key={{GET[key]}}&value={{GET[value]}}">
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-inner.sub.html.headers b/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-inner.sub.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-inner.sub.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-looser-restriction.sub.html b/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-looser-restriction.sub.html
new file mode 100644
index 0000000000..0ad64c1a5c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-looser-restriction.sub.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Iframe content to load a nested sandboxed iframe with all mandatory allow-* flags</title>
+
+<body>
+<iframe
+ src="sandbox-mandatory-flags-iframe.sub.html?key={{GET[key]}}&value={{GET[value]}}"
+ sandbox="allow-same-origin
+ allow-forms
+ allow-scripts
+ allow-popups
+ allow-popups-to-escape-sandbox
+ allow-top-navigation-by-user-activation">
+</iframe>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-inner.sub.html b/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-inner.sub.html
new file mode 100644
index 0000000000..f3bcbc8ba1
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-inner.sub.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="utils.js"></script>
+<script src="sandboxed-features.js"></script>
+<body>
+<script>
+(async () => {
+ try {
+ await {{GET[test_func]}}();
+ } catch (e) {
+ writeValueToServer('{{GET[key]}}', e.message);
+ return;
+ }
+ writeValueToServer('{{GET[key]}}', 'done');
+})()
+</script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-inner.sub.html.headers b/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-inner.sub.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-inner.sub.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-looser-restriction.sub.html b/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-looser-restriction.sub.html
new file mode 100644
index 0000000000..44584440e1
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-looser-restriction.sub.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<body>
+<iframe
+ src="sandboxed-features-inner.sub.html?key={{GET[key]}}&test_func={{GET[test_func]}}"
+ sandbox="allow-forms
+ allow-modals
+ allow-orientation-lock
+ allow-pointer-lock
+ allow-popups
+ allow-popups-to-escape-sandbox
+ allow-presentation
+ allow-same-origin
+ allow-scripts
+ allow-top-navigation
+ allow-top-navigation-by-user-activation
+ allow-downloads">
+</iframe>
+
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-looser-restriction.sub.html.headers b/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-looser-restriction.sub.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-looser-restriction.sub.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/sandboxed-features.js b/testing/web-platform/tests/fenced-frame/resources/sandboxed-features.js
new file mode 100644
index 0000000000..1cbd4a48f3
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/sandboxed-features.js
@@ -0,0 +1,126 @@
+const run_in_fenced_frame = (func_name, description, is_nested) => {
+ promise_test(async test => {
+ const key = token();
+ const url = is_nested ?
+ 'resources/sandboxed-features-looser-restriction.sub.html?' :
+ 'resources/sandboxed-features-inner.sub.html?';
+ let params = new URLSearchParams();
+ params.set('key', key);
+ params.set('test_func', func_name);
+ const frame = document.createElement('fencedframe');
+ const frame_url = 'resources/sandboxed-features-inner.sub.html?' +
+ params.toString();
+ const config = new FencedFrameConfig(generateURL(frame_url, []));
+ frame.config = config;
+ test.add_cleanup(() => {
+ frame.remove();
+ });
+ document.body.appendChild(frame);
+ assert_equals(await nextValueFromServer(key), 'done');
+ }, description);
+};
+
+const run_sanboxed_feature_test = (func_name, description) => {
+ run_in_fenced_frame(func_name, description, false);
+ run_in_fenced_frame(func_name, description + '[looser sandboxed]', true);
+};
+
+async function test_prompt() {
+ assert_equals(
+ window.prompt('Test prompt'),
+ null,
+ 'window.prompt() must synchronously return null in a fenced frame without' +
+ ' blocking on user input.');
+}
+
+async function test_alert() {
+ assert_equals(
+ window.alert('Test alert'),
+ undefined,
+ 'window.alert() must synchronously return undefined in a fenced frame' +
+ ' without blocking on user input.');
+}
+
+async function test_confirm() {
+ assert_equals(
+ window.confirm('Test confirm'),
+ false,
+ 'window.confirm() must synchronously return false in a fenced frame' +
+ ' without blocking on user input.');
+}
+
+async function test_print() {
+ assert_equals(
+ window.print(),
+ undefined,
+ 'window.print() must synchronously return undefined in a fenced frame' +
+ ' without blocking on user input.');
+
+ assert_equals(
+ document.execCommand('print', false, null),
+ false,
+ 'execCommand(\'print\') must synchronously return false in a fenced frame' +
+ ' without blocking on user input.');
+}
+
+async function test_document_domain() {
+ assert_throws_dom('SecurityError', () => {
+ document.domain = 'example.test';
+ });
+ assert_throws_dom('SecurityError', () => {
+ document.domain = document.domain;
+ });
+ assert_throws_dom('SecurityError', () => {
+ (new Document).domain = document.domain;
+ });
+ assert_throws_dom('SecurityError', () => {
+ document.implementation.createHTMLDocument().domain = document.domain;
+ });
+ assert_throws_dom('SecurityError', () => {
+ document.implementation.createDocument(null, '').domain = document.domain;
+ });
+ assert_throws_dom('SecurityError', () => {
+ document.createElement('template').content.ownerDocument.domain =
+ document.domain;
+ });
+}
+
+async function test_presentation_request() {
+ assert_throws_dom('SecurityError', () => {
+ new PresentationRequest([location.href]);
+ });
+}
+
+async function test_screen_orientation_lock() {
+ try {
+ await screen.orientation.lock('portrait');
+ } catch (e) {
+ assert_equals(
+ e.name,
+ 'SecurityError',
+ 'orientation.lock() must throw a SecurityError in a fenced frame.');
+ return;
+ }
+ assert_unreached('orientation.lock() must throw an error');
+}
+
+async function test_pointer_lock() {
+ await simulateGesture();
+
+ const canvas = document.createElement('canvas');
+ document.body.appendChild(canvas);
+ const pointerlockerror_promise = new Promise(resolve => {
+ document.addEventListener('pointerlockerror', resolve);
+ });
+ try {
+ await canvas.requestPointerLock();
+ } catch (e) {
+ assert_equals(
+ e.name,
+ 'SecurityError',
+ 'orientation.lock() must throws a SecurityError in a fenced frame.');
+ await pointerlockerror_promise;
+ return;
+ }
+ assert_unreached('requestPointerLock() must fail in a fenced frame');
+}
diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-inner.html b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-inner.html
new file mode 100644
index 0000000000..02f28bd82e
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-inner.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+
+<body>
+ <script type="module">
+ // Ask the worker to do a fetch request that will be handled by the service
+ // worker via postMessage.
+ const checkIfServiceWorkerCanControlWebWorker = async () => {
+ const dedicated_worker = new Worker('serviceWorker-dedicated-worker.js');
+ return new Promise((resolve, reject) => {
+ dedicated_worker.addEventListener('message', e => {
+ resolve(e.data)
+ });
+ dedicated_worker.postMessage('fetch');
+ })
+ }
+
+ const [key] = parseKeylist();
+ const url = new URL(location.href);
+ if (url.searchParams.get('useServiceWorkerInFencedFrame')) {
+ await navigator.serviceWorker.register('serviceWorker-dedicated-worker-sw.js');
+ await navigator.serviceWorker.ready;
+ }
+
+ const result = await checkIfServiceWorkerCanControlWebWorker();
+ writeValueToServer(key, result);
+ </script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-inner.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-sw.js b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-sw.js
new file mode 100644
index 0000000000..027995a218
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-sw.js
@@ -0,0 +1,18 @@
+self.addEventListener('fetch', async (e) => {
+ if (e.request.url.includes('fenced_frame_dedicated_worker_test')) {
+ e.respondWith(new Response('OK'));
+ return;
+ }
+
+ e.respondWith(fetch(e.request).catch(() => {
+ return new Response('not found');
+ }));
+})
+
+self.addEventListener('install', () => {
+ return self.skipWaiting();
+});
+
+self.addEventListener('activate', () => {
+ return self.clients.claim();
+});
diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-sw.js.headers b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-sw.js.headers
new file mode 100644
index 0000000000..d0b9633bb0
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-sw.js.headers
@@ -0,0 +1 @@
+Service-Worker-Allowed: /
diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker.js b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker.js
new file mode 100644
index 0000000000..8a9fa5ef36
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker.js
@@ -0,0 +1,8 @@
+self.addEventListener('message', async (e) => {
+ if (e.data === 'fetch') {
+ // Send a request to non-existing URL but handled by SW.
+ const res = await fetch('./fenced_frame_dedicated_worker_test');
+ const data = res.ok ? await res.text() : res.statusText;
+ self.postMessage(data);
+ }
+});
diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-inner.html b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-inner.html
new file mode 100644
index 0000000000..103236e52a
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-inner.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<body>
+<script>
+
+function getFrameType(service_worker, url) {
+ return new Promise((resolve, reject) => {
+ const channel = new MessageChannel();
+ channel.port1.onmessage = e => {
+ resolve(e.data);
+ };
+ service_worker.postMessage({port:channel.port2, url:url},
+ [channel.port2]);
+ });
+}
+
+(async function() {
+ await navigator.serviceWorker.register('serviceWorker-frameType.js');
+ const registration = await navigator.serviceWorker.ready;
+ const service_worker = registration.active;
+
+ const [frame_type_key, frame_type_ack_key] = parseKeylist();
+
+ const frame_type = await getFrameType(service_worker, location.href);
+ writeValueToServer(frame_type_key, frame_type);
+
+ // Wait for ACK, so we know that the outer page has read the last value from
+ // the `serviceWorker.frameType` stash and we can write to it again.
+ await nextValueFromServer(frame_type_ack_key);
+
+ const iframe = document.createElement('iframe');
+ iframe.src = generateURL("serviceWorker-frameType-nested.html",
+ [frame_type_key]);
+ document.body.append(iframe);
+})();
+</script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-inner.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-nested.html b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-nested.html
new file mode 100644
index 0000000000..10bb7ff8bd
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-nested.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<body>
+<script>
+
+function getFrameType(service_worker, url) {
+ return new Promise((resolve, reject) => {
+ const channel = new MessageChannel();
+ channel.port1.onmessage = e => {
+ resolve(e.data);
+ };
+ service_worker.postMessage({port:channel.port2, url:url},
+ [channel.port2]);
+ });
+}
+
+(async function() {
+ const service_worker = navigator.serviceWorker.controller;
+ const frame_type = await getFrameType(service_worker, location.href);
+
+ const [frame_type_key] = parseKeylist();
+ writeValueToServer(frame_type_key, frame_type);
+})();
+</script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-nested.html.headers b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-nested.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-nested.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType.js b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType.js
new file mode 100644
index 0000000000..91003fc131
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType.js
@@ -0,0 +1,19 @@
+self.onmessage = function(e) {
+ var port = e.data.port;
+ var url = e.data.url;
+
+ e.waitUntil(self.clients.matchAll({includeUncontrolled: true})
+ .then(function(clients) {
+ var frame_type = "none";
+ for (client of clients) {
+ if (client.url === url) {
+ frame_type = client.frameType;
+ break;
+ }
+ }
+ port.postMessage(frame_type);
+ })
+ .catch(e => {
+ port.postMessage('clients.matchAll() rejected: ' + e);
+ }));
+}; \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner-success.html b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner-success.html
new file mode 100644
index 0000000000..4d77d9e9a6
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner-success.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<body>
+<script>
+
+(async function() {
+ const [navigate_key] = parseKeylist();
+ writeValueToServer(navigate_key, 'success');
+})();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner-success.html.headers b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner-success.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner-success.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner.html b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner.html
new file mode 100644
index 0000000000..aaf330f4f6
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<body>
+<script>
+
+(async function() {
+ await navigator.serviceWorker.register('serviceWorker-navigate.js');
+ const registration = await navigator.serviceWorker.ready;
+ const service_worker = registration.active;
+
+ const [navigate_key] = parseKeylist();
+
+ service_worker.postMessage({key:navigate_key, url:location.href});
+})();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate.js b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate.js
new file mode 100644
index 0000000000..a7a4db52ee
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate.js
@@ -0,0 +1,18 @@
+self.importScripts('utils.js');
+
+self.onmessage = function(e) {
+ var key = e.data.key;
+ var url = e.data.url;
+
+ e.waitUntil(self.clients.claim().then(() => {
+ return self.clients.matchAll({type: 'window'});
+ }).then(clients => {
+ return clients.map(client => {
+ // Check to make sure WindowClient.navigate() is supported.
+ if (client.url === url) {
+ return client.navigate(generateURL('serviceWorker-navigate-inner-success.html',
+ [key]));
+ }
+ });
+ }));
+};
diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-push-sw.js b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-push-sw.js
new file mode 100644
index 0000000000..e344b45fd8
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-push-sw.js
@@ -0,0 +1,19 @@
+self.addEventListener('install', e => e.waitUntil(skipWaiting()));
+self.addEventListener('activate', e => e.waitUntil(clients.claim()));
+
+self.addEventListener('message', async e => {
+ const method = e.data;
+
+ const promise = method === 'subscribe' ?
+ self.registration.pushManager.subscribe({userVisibleOnly: true}) :
+ Promise.resolve();
+ const message = await promise
+ .then(() => {
+ return `${method}: Unexpectedly started`;
+ })
+ .catch((e) => {
+ return e.message;
+ });
+
+ e.source.postMessage(message);
+});
diff --git a/testing/web-platform/tests/fenced-frame/resources/unique-cookie-partition-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/unique-cookie-partition-inner.https.html
new file mode 100644
index 0000000000..1cf3fc8680
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/unique-cookie-partition-inner.https.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="utils.js"></script>
+<title>Fenced frame accessing cookies</title>
+
+<body>
+<script>
+async function init() {
+ // This file is meant to run in a <fencedframe>. It sets up multiple frames
+ // in the following arrangements:
+ // 1.) A top-level fenced frame
+ // 2.) An iframe within a fenced frame
+ // 3.) A nested fenced frame
+
+ // Set cookies in the root fenced frame via document and cookieStore APIs.
+ const [cookie_value_key, test_type] = parseKeylist();
+ document.cookie = 'C=fenced; SameSite=Lax';
+ document.cookie = 'D=fenced; SameSite=None; Secure';
+ await cookieStore.set('E', 'fenced');
+
+ const cookie_access_url = generateURL("cookie-access.https.html",
+ [cookie_value_key]);
+
+ switch (test_type) {
+ case "top-level fenced frame":
+ const cookie_value = document.cookie;
+ writeValueToServer(cookie_value_key, cookie_value);
+ break;
+ case "nested iframe":
+ const iframe = document.createElement('iframe');
+ document.body.append(iframe);
+ iframe.src = cookie_access_url;
+ break;
+ case "nested fenced frame":
+ const ff = attachFencedFrame(cookie_access_url);
+ break;
+ }
+}
+
+init();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/unique-cookie-partition-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/unique-cookie-partition-inner.https.html.headers
new file mode 100644
index 0000000000..e2b453f463
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/unique-cookie-partition-inner.https.html.headers
@@ -0,0 +1,2 @@
+Supports-Loading-Mode: fenced-frame
+Set-Cookie: F=fenced; SameSite=Lax
diff --git a/testing/web-platform/tests/fenced-frame/resources/unreached.https.html b/testing/web-platform/tests/fenced-frame/resources/unreached.https.html
new file mode 100644
index 0000000000..bd389ec4fb
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/unreached.https.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>File used to assert that navigations do not succeed.</title>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+
+<body>
+<script>
+ promise_test(async(t) => {
+ assert_unreached('This navigation should not have succeeded.');
+ });
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/utils.js b/testing/web-platform/tests/fenced-frame/resources/utils.js
new file mode 100644
index 0000000000..cbea173f17
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/utils.js
@@ -0,0 +1,648 @@
+const STORE_URL = '/fenced-frame/resources/key-value-store.py';
+const BEACON_URL = '/fenced-frame/resources/automatic-beacon-store.py';
+const REMOTE_EXECUTOR_URL = '/fenced-frame/resources/remote-context-executor.https.html';
+
+// If your test needs to modify FLEDGE bidding or decision logic, you should
+// update the generated JS in the corresponding handler below.
+const FLEDGE_BIDDING_URL = '/fenced-frame/resources/fledge-bidding-logic.py';
+const FLEDGE_DECISION_URL = '/fenced-frame/resources/fledge-decision-logic.py';
+
+// Creates a URL that includes a list of stash key UUIDs that are being used
+// in the test. This allows us to generate UUIDs on the fly and let anything
+// (iframes, fenced frames, pop-ups, etc...) that wouldn't have access to the
+// original UUID variable know what the UUIDs are.
+// @param {string} href - The base url of the page being navigated to
+// @param {string list} keylist - The list of key UUIDs to be used. Note that
+// order matters when extracting the keys
+function generateURL(href, keylist) {
+ const ret_url = new URL(href, location.href);
+ ret_url.searchParams.append("keylist", keylist.join(','));
+ return ret_url;
+}
+
+function getRemoteContextURL(origin) {
+ return new URL(REMOTE_EXECUTOR_URL, origin);
+}
+
+async function runSelectRawURL(href, resolve_to_config = false) {
+ try {
+ await sharedStorage.worklet.addModule(
+ "/shared-storage/resources/simple-module.js");
+ } catch (e) {
+ // Shared Storage needs to have a module added before we can operate on it.
+ // It is generated on the fly with this call, and since there's no way to
+ // tell through the API if a module already exists, wrap the addModule call
+ // in a try/catch so that if it runs a second time in a test, it will
+ // gracefully fail rather than bring the whole test down.
+ }
+ return await sharedStorage.selectURL(
+ 'test-url-selection-operation', [{url: href,
+ reportingMetadata: {
+ 'reserved.top_navigation_start': BEACON_URL +
+ "?type=reserved.top_navigation_start",
+ 'reserved.top_navigation_commit': BEACON_URL +
+ "?type=reserved.top_navigation_commit",
+ }}], {
+ data: {'mockResult': 0},
+ resolveToConfig: resolve_to_config,
+ keepAlive: true,
+ });
+}
+
+// Similar to generateURL, but creates
+// 1. An urn:uuid if `resolve_to_config` is false.
+// 2. A fenced frame config object if `resolve_to_config` is true.
+// This relies on a mock Shared Storage auction, since it is the simplest
+// WP-exposed way to turn a url into an urn:uuid or a fenced frame config.
+// Note: this function, unlike generateURL, is asynchronous and needs to be
+// called with an await operator.
+// @param {string} href - The base url of the page being navigated to
+// @param {string list} keylist - The list of key UUIDs to be used. Note that
+// order matters when extracting the keys
+// @param {boolean} [resolve_to_config = false] - Determines whether the result
+// of `sharedStorage.selectURL()`
+// is an urn:uuid or a fenced
+// frame config.
+// Note:
+// 1. There is a limit of 3 calls per origin per pageload for
+// `sharedStorage.selectURL()`, so `runSelectURL()` must also respect this
+// limit.
+// 2. If `resolve_to_config` is true, blink feature `FencedFramesAPIChanges`
+// needs to be enabled for `selectURL()` to return a fenced frame config.
+// Otherwise `selectURL()` will fall back to the old behavior that returns an
+// urn:uuid.
+async function runSelectURL(href, keylist = [], resolve_to_config = false) {
+ const full_url = generateURL(href, keylist);
+ return await runSelectRawURL(full_url, resolve_to_config);
+}
+
+async function generateURNFromFledgeRawURL(
+ href, nested_urls, resolve_to_config = false, ad_with_size = false,
+ requested_size = null, automatic_beacon = false) {
+ const bidding_token = token();
+ const seller_token = token();
+
+ const ad_components_list = nested_urls.map((url) => {
+ return ad_with_size ?
+ { renderURL: url, sizeGroup: "group1" } :
+ { renderURL: url }
+ });
+
+ let interestGroup =
+ {
+ name: 'testAd1',
+ owner: location.origin,
+ biddingLogicURL: new URL(FLEDGE_BIDDING_URL, location.origin),
+ ads: [{renderURL: href, bid: 1}],
+ userBiddingSignals: {biddingToken: bidding_token},
+ trustedBiddingSignalsKeys: ['key1'],
+ adComponents: ad_components_list,
+ };
+
+ let biddingURLParams =
+ new URLSearchParams(interestGroup.biddingLogicURL.search);
+ if (requested_size)
+ biddingURLParams.set(
+ 'requested-size', requested_size[0] + '-' + requested_size[1]);
+ if (ad_with_size)
+ biddingURLParams.set('ad-with-size', 1);
+ if (automatic_beacon)
+ biddingURLParams.set('automatic-beacon', 1);
+ interestGroup.biddingLogicURL.search = biddingURLParams;
+
+ if (ad_with_size) {
+ interestGroup.ads[0].sizeGroup = 'group1';
+ interestGroup.adSizes = {'size1': {width: '100px', height: '50px'}};
+ interestGroup.sizeGroups = {'group1': ['size1']};
+ }
+
+ // Pick an arbitrarily high duration to guarantee that we never leave the
+ // ad interest group while the test runs.
+ navigator.joinAdInterestGroup(interestGroup, /*durationSeconds=*/3000000);
+
+ let auctionConfig = {
+ seller: location.origin,
+ interestGroupBuyers: [location.origin],
+ decisionLogicURL: new URL(FLEDGE_DECISION_URL, location.origin),
+ auctionSignals: {biddingToken: bidding_token, sellerToken: seller_token},
+ resolveToConfig: resolve_to_config
+ };
+
+ if (requested_size) {
+ let decisionURLParams =
+ new URLSearchParams(auctionConfig.decisionLogicURL.search);
+ decisionURLParams.set(
+ 'requested-size', requested_size[0] + '-' + requested_size[1]);
+ auctionConfig.decisionLogicURL.search = decisionURLParams;
+
+ auctionConfig['requestedSize'] = {width: requested_size[0], height: requested_size[1]};
+ }
+
+ return navigator.runAdAuction(auctionConfig);
+}
+
+// Similar to runSelectURL, but uses FLEDGE instead of Shared Storage as the
+// auctioning tool.
+// Note: this function, unlike generateURL, is asynchronous and needs to be
+// called with an await operator. @param {string} href - The base url of the
+// page being navigated to @param {string list} keylist - The list of key UUIDs
+// to be used. Note that order matters when extracting the keys
+// @param {string} href - The base url of the page being navigated to
+// @param {string list} keylist - The list of key UUIDs to be used. Note that
+// order matters when extracting the keys
+// @param {string list} nested_urls - A list of urls that will eventually become
+// the nested configs/ad components
+// @param {boolean} [resolve_to_config = false] - Determines whether the result
+// of `navigator.runAdAuction()`
+// is an urn:uuid or a fenced
+// frame config.
+// @param {boolean} [ad_with_size = false] - Determines whether the auction is
+// run with ad sizes specified.
+// @param {boolean} [automatic_beacon = false] - If true, FLEDGE logic will
+// register an automatic beacon
+// after completion.
+async function generateURNFromFledge(
+ href, keylist, nested_urls = [], resolve_to_config = false,
+ ad_with_size = false, requested_size = null, automatic_beacon = false) {
+ const full_url = generateURL(href, keylist);
+ return generateURNFromFledgeRawURL(
+ full_url, nested_urls, resolve_to_config, ad_with_size, requested_size,
+ automatic_beacon);
+}
+
+// Extracts a list of UUIDs from the from the current page's URL.
+// @returns {string list} - The list of UUIDs extracted from the page. This can
+// be read into multiple variables using the
+// [key1, key2, etc...] = parseKeyList(); pattern.
+function parseKeylist() {
+ const url = new URL(location.href);
+ const keylist = url.searchParams.get("keylist");
+ return keylist.split(',');
+}
+
+// Converts a same-origin URL to a cross-origin URL
+// @param {URL} url - The URL object whose origin is being converted
+// @param {boolean} [https=true] - Whether or not to use the HTTPS origin
+//
+// @returns {URL} The new cross-origin URL
+function getRemoteOriginURL(url, https=true) {
+ const same_origin = location.origin;
+ const cross_origin = https ? get_host_info().HTTPS_REMOTE_ORIGIN
+ : get_host_info().HTTP_REMOTE_ORIGIN;
+ return new URL(url.toString().replace(same_origin, cross_origin));
+}
+
+// Builds a URL to be used as a remote context executor.
+function generateRemoteContextURL(headers, origin) {
+ // Generate the unique id for the parent/child channel.
+ const uuid = token();
+
+ // Use the absolute path of the remote context executor source file, so that
+ // nested contexts will work.
+ const url = getRemoteContextURL(origin ? origin : location.origin);
+ url.searchParams.append('uuid', uuid);
+
+ // Add the header to allow loading in a fenced frame.
+ headers.push(["Supports-Loading-Mode", "fenced-frame"]);
+
+ // Transform the headers into the expected format.
+ // https://web-platform-tests.org/writing-tests/server-pipes.html#headers
+ function escape(s) {
+ return s.replace('(', '\\(').replace(')', '\\)');
+ }
+ const formatted_headers = headers.map((header) => {
+ return `header(${escape(header[0])}, ${escape(header[1])})`;
+ });
+ url.searchParams.append('pipe', formatted_headers.join('|'));
+
+ return [uuid, url];
+}
+
+function buildRemoteContextForObject(object, uuid, html) {
+ // https://github.com/web-platform-tests/wpt/blob/master/common/dispatcher/README.md
+ const context = new RemoteContext(uuid);
+ if (html) {
+ context.execute_script(
+ (html_source) => {
+ document.body.insertAdjacentHTML('beforebegin', html_source);
+ },
+ [html]);
+ }
+
+ // We need a little bit of boilerplate in the handlers because Proxy doesn't
+ // work so nicely with HTML elements.
+ const handler = {
+ get: (target, key) => {
+ if (key == "execute") {
+ return context.execute_script;
+ }
+ if (key == "element") {
+ return object;
+ }
+ if (key in target) {
+ return target[key];
+ }
+ return context[key];
+ },
+ set: (target, key, value) => {
+ target[key] = value;
+ return value;
+ }
+ };
+
+ const proxy = new Proxy(object, handler);
+ return proxy;
+}
+
+// Attaches an object that waits for scripts to execute from RemoteContext.
+// (In practice, this is either a frame or a window.)
+// Returns a proxy for the object that first resolves to the object itself,
+// then resolves to the RemoteContext if the property isn't found.
+// The proxy also has an extra attribute `execute`, which is an alias for the
+// remote context's `execute_script(fn, args=[])`.
+function attachContext(object_constructor, html, headers, origin) {
+ const [uuid, url] = generateRemoteContextURL(headers, origin);
+ const object = object_constructor(url);
+ return buildRemoteContextForObject(object, uuid, html);
+}
+
+// TODO(crbug.com/1347953): Update this function to also test
+// `sharedStorage.selectURL()` that returns a fenced frame config object.
+// This should be done after fixing the following flaky tests that use this
+// function.
+// 1. crbug.com/1372536: resize-lock-input.https.html
+// 2. crbug.com/1394559: unfenced-top.https.html
+async function attachOpaqueContext(
+ generator_api, resolve_to_config, ad_with_size, requested_size,
+ automatic_beacon, object_constructor, html, headers, origin,
+ num_components) {
+ const [uuid, url] = generateRemoteContextURL(headers, origin);
+
+ let components_list = [];
+ for (let i = 0; i < num_components; i++) {
+ let [component_uuid, component_url] =
+ generateRemoteContextURL(headers, origin);
+ // This field will be read by attachComponentFrameContext() in order to
+ // know what uuid to point to when building the remote context.
+ html += '<input type=\'hidden\' id=\'component_uuid_' + i + '\' value=\'' +
+ component_uuid + '\'>';
+ components_list.push(component_url);
+ }
+
+ const id = await (
+ generator_api == 'fledge' ?
+ generateURNFromFledge(
+ url, [], components_list, resolve_to_config, ad_with_size,
+ requested_size, automatic_beacon) :
+ runSelectURL(url, [], resolve_to_config));
+ const object = object_constructor(id);
+ return buildRemoteContextForObject(object, uuid, html);
+}
+
+function attachPotentiallyOpaqueContext(
+ generator_api, resolve_to_config, ad_with_size, requested_size,
+ automatic_beacon, frame_constructor, html, headers, origin,
+ num_components) {
+ generator_api = generator_api.toLowerCase();
+ if (generator_api == 'fledge' || generator_api == 'sharedstorage') {
+ return attachOpaqueContext(
+ generator_api, resolve_to_config, ad_with_size, requested_size,
+ automatic_beacon, frame_constructor, html, headers, origin,
+ num_components);
+ } else {
+ return attachContext(frame_constructor, html, headers, origin);
+ }
+}
+
+function attachFrameContext(
+ element_name, generator_api, resolve_to_config, ad_with_size,
+ requested_size, automatic_beacon, html, headers, attributes, origin,
+ num_components) {
+ frame_constructor = (id) => {
+ frame = document.createElement(element_name);
+ attributes.forEach(attribute => {
+ frame.setAttribute(attribute[0], attribute[1]);
+ });
+ if (element_name == "iframe") {
+ frame.src = id;
+ } else if (id instanceof FencedFrameConfig) {
+ frame.config = id;
+ } else {
+ const config = new FencedFrameConfig(id);
+ frame.config = config;
+ }
+ document.body.append(frame);
+ return frame;
+ };
+ return attachPotentiallyOpaqueContext(
+ generator_api, resolve_to_config, ad_with_size, requested_size,
+ automatic_beacon, frame_constructor, html, headers, origin,
+ num_components);
+}
+
+function replaceFrameContext(frame_proxy, {
+ generator_api = '',
+ resolve_to_config = false,
+ ad_with_size = false,
+ requested_size = null,
+ automatic_beacon = false,
+ html = '',
+ headers = [],
+ origin = ''
+} = {}) {
+ frame_constructor = (id) => {
+ if (frame_proxy.element.nodeName == "IFRAME") {
+ frame_proxy.element.src = id;
+ } else if (id instanceof FencedFrameConfig) {
+ frame_proxy.element.config = id;
+ } else {
+ const config = new FencedFrameConfig(id);
+ frame_proxy.element.config = config;
+ }
+ return frame_proxy.element;
+ };
+ return attachPotentiallyOpaqueContext(
+ generator_api, resolve_to_config, ad_with_size, requested_size,
+ automatic_beacon, frame_constructor, html, headers, origin);
+}
+
+// Attach a fenced frame that waits for scripts to execute.
+// Takes as input a(n optional) dictionary of configs:
+// - generator_api: the name of the API that should generate the urn/config.
+// Supports (case-insensitive) "fledge" and "sharedstorage", or any other
+// value as a default.
+// If you generate a urn, then you need to await the result of this function.
+// - resolve_to_config: whether a config should be used. (currently only works
+// for FLEDGE and sharedStorage generator_api)
+// - ad_with_size: whether an ad auction is run with size specified for the ads
+// and ad components. (currently only works for FLEDGE)
+// - requested_size: A 2-element list with the width and height for
+// requestedSize in the FLEDGE auction config. This is different from
+// ad_with_size, which refers to size information provided alongside the ads
+// themselves.
+// - automatic_beacon: If true and generator_api = "fledge", an automatic beacon
+// will be registered for a top-level navigation after the FLEDGE auction
+// completes.
+// - html: extra HTML source code to inject into the loaded frame
+// - headers: an array of header pairs [[key, value], ...]
+// - attributes: an array of attribute pairs to set on the frame [[key, value],
+// ...]
+// - origin: origin of the url, default to location.origin if not set
+// Returns a proxy that acts like the frame HTML element, but with an extra
+// function `execute`. See `attachFrameContext` or the README for more details.
+function attachFencedFrameContext({
+ generator_api = '',
+ resolve_to_config = false,
+ ad_with_size = false,
+ requested_size = null,
+ automatic_beacon = false,
+ html = '',
+ headers = [],
+ attributes = [],
+ origin = '',
+ num_components = 0
+} = {}) {
+ return attachFrameContext(
+ 'fencedframe', generator_api, resolve_to_config, ad_with_size,
+ requested_size, automatic_beacon, html, headers, attributes, origin,
+ num_components);
+}
+
+// Attach an iframe that waits for scripts to execute.
+// See `attachFencedFrameContext` for more details.
+function attachIFrameContext({
+ generator_api = '',
+ automatic_beacon = false,
+ html = '',
+ headers = [],
+ attributes = [],
+ origin = '',
+ num_components = 0
+} = {}) {
+ return attachFrameContext(
+ 'iframe', generator_api, resolve_to_config = false, ad_with_size = false,
+ requested_size = null, automatic_beacon, html, headers, attributes,
+ origin, num_components);
+}
+
+// Open a window that waits for scripts to execute.
+// Returns a proxy that acts like the window object, but with an extra
+// function `execute`. See `attachContext` for more details.
+function attachWindowContext({target="_blank", html="", headers=[], origin=""}={}) {
+ window_constructor = (url) => {
+ return window.open(url, target);
+ }
+
+ return attachContext(window_constructor, html, headers, origin);
+}
+
+// Attaches an ad component in a fenced frame. For this to work, this must be
+// called in a frame that was generated with attachFrameContext() using the
+// Protected Audience API (generator_api: 'fledge').
+function attachComponentFencedFrameContext(
+ index = 0, {attributes = [], html = ''} = {}) {
+ const urn = window.fence.getNestedConfigs()[index];
+ return attachComponentFrameContext(
+ index, 'fencedframe', urn, attributes, html);
+}
+
+// Same as attachComponentFencedFrameContext, but in a urn iframe.
+function attachComponentIFrameContext(
+ index = 0, {attributes = [], html = ''} = {}) {
+ const urn = navigator.adAuctionComponents(index + 1)[index];
+ return attachComponentFrameContext(index, 'iframe', urn, attributes, html);
+}
+
+function attachComponentFrameContext(
+ index, element_name, urn, attributes, html) {
+ assert_not_equals(
+ document.getElementById('component_uuid_' + index), null,
+ 'Component frames can only be attached to frames loaded with ' +
+ 'attach*FrameContext() with `num_components` set to at least ' +
+ (index + 1) + '.');
+
+ let frame = document.createElement(element_name);
+ attributes.forEach(attribute => {
+ frame.setAttribute(attribute[0], attribute[1]);
+ });
+ if (element_name == 'iframe') {
+ frame.src = urn;
+ } else {
+ frame.config = urn;
+ }
+ document.body.append(frame);
+ const context_uuid = document.getElementById('component_uuid_' + index).value;
+ return buildRemoteContextForObject(frame, context_uuid, html);
+}
+
+// Converts a key string into a key uuid using a cryptographic hash function.
+// This function only works in secure contexts (HTTPS).
+async function stringToStashKey(string) {
+ // Compute a SHA-256 hash of the input string, and convert it to hex.
+ const data = new TextEncoder().encode(string);
+ const digest = await crypto.subtle.digest('SHA-256', data);
+ const digest_array = Array.from(new Uint8Array(digest));
+ const digest_as_hex = digest_array.map(b => b.toString(16).padStart(2, '0')).join('');
+
+ // UUIDs are structured as 8X-4X-4X-4X-12X.
+ // Use the first 32 hex digits and ignore the rest.
+ const digest_slices = [digest_as_hex.slice(0,8),
+ digest_as_hex.slice(8,12),
+ digest_as_hex.slice(12,16),
+ digest_as_hex.slice(16,20),
+ digest_as_hex.slice(20,32)];
+ return digest_slices.join('-');
+}
+
+// Create a fenced frame. Then navigate it using the given `target`, which can
+// be either an urn:uuid or a fenced frame config object.
+function attachFencedFrame(target) {
+ assert_implements(
+ window.HTMLFencedFrameElement,
+ 'The HTMLFencedFrameElement should be exposed on the window object');
+
+ const fenced_frame = document.createElement('fencedframe');
+
+ if (target instanceof FencedFrameConfig) {
+ fenced_frame.config = target;
+ } else {
+ const config = new FencedFrameConfig(target);
+ fenced_frame.config = config;
+ }
+
+ document.body.append(fenced_frame);
+ return fenced_frame;
+}
+
+function attachIFrame(url) {
+ const iframe = document.createElement('iframe');
+ iframe.src = url;
+ document.body.append(iframe);
+ return iframe;
+}
+
+// Reads the value specified by `key` from the key-value store on the server.
+async function readValueFromServer(key) {
+ // Resolve the key if it is a Promise.
+ key = await key;
+
+ const serverURL = `${STORE_URL}?key=${key}`;
+ const response = await fetch(serverURL);
+ if (!response.ok)
+ throw new Error('An error happened in the server');
+ const value = await response.text();
+
+ // The value is not stored in the server.
+ if (value === "<Not set>")
+ return { status: false };
+
+ return { status: true, value: value };
+}
+
+// Convenience wrapper around the above getter that will wait until a value is
+// available on the server.
+async function nextValueFromServer(key) {
+ // Resolve the key if it is a Promise.
+ key = await key;
+
+ while (true) {
+ // Fetches the test result from the server.
+ const { status, value } = await readValueFromServer(key);
+ if (!status) {
+ // The test result has not been stored yet. Retry after a while.
+ await new Promise(resolve => setTimeout(resolve, 20));
+ continue;
+ }
+
+ return value;
+ }
+}
+
+// Checks the automatic beacon data server to see if it has received an
+// automatic beacon with a given event type and body.
+async function readAutomaticBeaconDataFromServer(event_type, expected_body) {
+ let serverURL = `${BEACON_URL}`;
+ const response = await fetch(serverURL + "?" + new URLSearchParams({
+ type: event_type,
+ expected_body: expected_body,
+ }));
+ if (!response.ok)
+ throw new Error('An error happened in the server ' + response.status);
+ const value = await response.text();
+
+ // The value is not stored in the server.
+ if (value === "<Not set>")
+ return { status: false };
+
+ return { status: true, value: value };
+}
+
+// Convenience wrapper around the above getter that will wait until a value is
+// available on the server. The server uses a hash of the concatenated event
+// type and beacon data as the key when storing the beacon in the database. To
+// retrieve it, we need to supply the endpoint with both pieces of information.
+async function nextAutomaticBeacon(event_type, expected_body) {
+ while (true) {
+ // Fetches the test result from the server.
+ const { status, value } =
+ await readAutomaticBeaconDataFromServer(event_type, expected_body);
+ if (!status) {
+ // The test result has not been stored yet. Retry after a while.
+ await new Promise(resolve => setTimeout(resolve, 20));
+ continue;
+ }
+
+ return value;
+ }
+}
+
+// Writes `value` for `key` in the key-value store on the server.
+async function writeValueToServer(key, value, origin = '') {
+ // Resolve the key if it is a Promise.
+ key = await key;
+
+ const serverURL = `${origin}${STORE_URL}?key=${key}&value=${value}`;
+ await fetch(serverURL, {"mode": "no-cors"});
+}
+
+// Simulates a user gesture.
+async function simulateGesture() {
+ // Wait until the window size is initialized.
+ while (window.innerWidth == 0) {
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ }
+ await test_driver.bless('simulate gesture');
+}
+
+// Fenced frames are always put in the public IP address space which is the
+// least privileged. In case a navigation to a local data: URL or blob: URL
+// resource is allowed, they would only be able to fetch things that are *also*
+// in the public IP address space. So for the document described by these local
+// URLs, we'll set them up to only communicate back to the outer page via
+// resources obtained in the public address space.
+function createLocalSource(key, url) {
+ return `
+ <head>
+ <script src="${url}"><\/script>
+ </head>
+ <body>
+ <script>
+ writeValueToServer("${key}", "LOADED", /*origin=*/"${url.origin}");
+ <\/script>
+ </body>
+ `;
+}
+
+function setupCSP(csp, second_csp=null) {
+ let meta = document.createElement('meta');
+ meta.httpEquiv = "Content-Security-Policy";
+ meta.content = "fenced-frame-src " + csp;
+ document.head.appendChild(meta);
+
+ if (second_csp != null) {
+ let second_meta = document.createElement('meta');
+ second_meta.httpEquiv = "Content-Security-Policy";
+ second_meta.content = "frame-src " + second_csp;
+ document.head.appendChild(second_meta);
+ }
+}
diff --git a/testing/web-platform/tests/fenced-frame/resources/web-bluetooth-inner.html b/testing/web-platform/tests/fenced-frame/resources/web-bluetooth-inner.html
new file mode 100644
index 0000000000..3236886b97
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/web-bluetooth-inner.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="utils.js"></script>
+<title>Fenced frame content to test Web Bluetooth</title>
+
+<body>
+<button id="button">Button</button>
+<script>
+(async () => {
+ await simulateGesture();
+ const [bluetooth_request_device_key] = parseKeylist();
+ try {
+ await navigator.bluetooth.requestDevice({filters: [{name: 'device'}]});
+ writeValueToServer(bluetooth_request_device_key,
+ 'Web Bluetooth requestDevice() succeeded');
+ } catch(e) {
+ if (e.name == 'NotAllowedError' &&
+ e.message.includes(
+ 'Web Bluetooth is not allowed in a fenced frame tree.')) {
+ writeValueToServer(bluetooth_request_device_key,
+ 'Web Bluetooth requestDevice() failed');
+ } else {
+ writeValueToServer(
+ bluetooth_request_device_key,
+ 'Web Bluetooth requestDevice() failed with unknown error - ' +
+ `${e.name}: ${e.message}`);
+ }
+ }
+})();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/web-bluetooth-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/web-bluetooth-inner.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/web-bluetooth-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/web-nfc-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/web-nfc-inner.https.html
new file mode 100644
index 0000000000..682805d5d2
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/web-nfc-inner.https.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the value of Web NFC API</title>
+
+<body>
+<script>
+async function init() {
+ const [ndef_write_key, ndef_scan_key] = parseKeylist();
+
+ const ndef = new NDEFReader();
+ ndef.write("Hello").then(
+ () => { writeValueToServer(ndef_write_key, "resolved"); },
+ () => { writeValueToServer(ndef_write_key, "rejected"); }
+ );
+ ndef.scan().then(
+ () => { writeValueToServer(ndef_scan_key, "resolved"); },
+ () => { writeValueToServer(ndef_scan_key, "rejected"); }
+ );
+}
+
+init();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/web-nfc-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/web-nfc-inner.https.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/web-nfc-inner.https.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/web-share-inner.html b/testing/web-platform/tests/fenced-frame/resources/web-share-inner.html
new file mode 100644
index 0000000000..aada6f04e1
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/web-share-inner.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="utils.js"></script>
+<title>Fenced frame content to test Web Share</title>
+
+<body>
+<script>
+(async () => {
+ await simulateGesture();
+ const [navigator_share_key] = parseKeylist();
+ try {
+ await navigator.share({text: 'hello world'});
+ writeValueToServer(navigator_share_key, 'Web Share succeeded');
+ } catch(error) {
+ writeValueToServer(navigator_share_key, 'Web Share failed');
+ }
+})();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/web-share-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/web-share-inner.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/web-share-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/window-frameElement-inner.html b/testing/web-platform/tests/fenced-frame/resources/window-frameElement-inner.html
new file mode 100644
index 0000000000..897d9a0d59
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/window-frameElement-inner.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the value of window.frameElement</title>
+
+<body>
+<script>
+(async () => {
+ // Report whether or not `window.frameElement` is null
+ const [frame_element_key] = parseKeylist();
+ let result = (window.frameElement == null) ? "PASS" : "FAIL";
+ writeValueToServer(frame_element_key, result);
+})();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/window-frameElement-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/window-frameElement-inner.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/window-frameElement-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/window-navigation-204-inner.html b/testing/web-platform/tests/fenced-frame/resources/window-navigation-204-inner.html
new file mode 100644
index 0000000000..e5e5adef1d
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/window-navigation-204-inner.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the value of window.navigation</title>
+
+<body>
+<script>
+function init() {
+ // This file is meant to be navigated to from a <fencedframe> element. It
+ // reports back to the page hosting the <fencedframe> after manual timeout
+ // indicating that the 204 navigation succeeds without navigating away.
+ location.href = "response-204.py";
+
+ step_timeout(function() {
+ const [window_data_key] = parseKeylist();
+ writeValueToServer(window_data_key, "still in page");
+ }, 1000);
+}
+
+init();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/window-navigation-204-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/window-navigation-204-inner.html.headers
new file mode 100644
index 0000000000..1b63235b7c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/window-navigation-204-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/fenced-frame/resources/window-outer-dimensions-inner.html b/testing/web-platform/tests/fenced-frame/resources/window-outer-dimensions-inner.html
new file mode 100644
index 0000000000..81dee800fc
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/window-outer-dimensions-inner.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the result of prerendering</title>
+
+<body>
+ <script>
+ async function report() { // Needed in order to use top-level await.
+ // This file is meant to run in a <fencedframe>. It reports its dimensions
+ // back to the outermost page, which in turn checks for correctness.
+ const [window_outer_size_key, window_inner_size_key, dimension,
+ extra_children] = parseKeylist();
+
+ const url = new URL(location.href);
+
+ if (extra_children == "0") {
+ let outer_result = (dimension == "width") ?
+ window.outerWidth : window.outerHeight;
+
+ let inner_result = (dimension == "width") ?
+ window.innerWidth : window.innerHeight;
+
+ writeValueToServer(window_outer_size_key, outer_result);
+ writeValueToServer(window_inner_size_key, inner_result);
+ } else {
+ const iframe = document.createElement('iframe');
+ const frame_url = generateURL('window-outer-dimensions-inner.html',
+ [window_outer_size_key, window_inner_size_key, dimension,
+ (parseInt(extra_children) - 1)]);
+ iframe.src = frame_url;
+ document.body.append(iframe);
+ }
+
+ }
+ report();
+ </script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/window-outer-dimensions-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/window-outer-dimensions-inner.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/window-outer-dimensions-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/window-parent-inner.html b/testing/web-platform/tests/fenced-frame/resources/window-parent-inner.html
new file mode 100644
index 0000000000..9008d7d923
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/window-parent-inner.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the value of window.parent</title>
+
+<body>
+<script>
+async function init() { // Needed in order to use top-level await.
+ // This file is meant to run in a <fencedframe>. It reports back to the
+ // outermost page whether or not the value of `window.parent` was correct for:
+ // 1.) Top-level fenced frames
+ // 2.) Nested iframes inside a fenced frame
+ // 3.) Nested fenced frames
+ const url = new URL(location.href);
+
+ const [window_parent_key, window_parent_ack_key, nested] = parseKeylist();
+ const is_nested_fenced_frame = (nested == "nested");
+
+ // Report whether or not `window.parent` was correct.
+ let pass_string = "";
+ if (is_nested_fenced_frame)
+ pass_string = "pass: fenced frame > fenced frame";
+ else
+ pass_string = "pass: fenced frame";
+
+ let result = (window.parent == window) ? pass_string : "fail";
+ writeValueToServer(window_parent_key, result);
+
+ // If this page is a nested fenced frame, all we need to do is report the
+ // top-level value.
+ if (is_nested_fenced_frame)
+ return;
+
+ // Wait for ACK, so we know that the outer page has read the last value from
+ // the `window_parent_key` stash and we can write to it again.
+ await nextValueFromServer(window_parent_ack_key);
+
+ // Now test `window.parent` inside an iframe.
+ const iframe = document.createElement('iframe');
+ iframe.src = "dummy.html";
+ const load_promise = new Promise((resolve, reject) => {
+ iframe.onload = resolve;
+ iframe.onerror = reject;
+ });
+ document.body.append(iframe);
+
+ await load_promise;
+
+ // Report whether or not the subframe's `window.parent` was correct.
+ result = (iframe.contentWindow.parent == window) ?
+ "pass: fenced frame > iframe" : "fail";
+ writeValueToServer(window_parent_key, result);
+
+ // Wait for ACK, so we know that the outer page has read the last value from
+ // the `window_parent_key` stash and we can write to it again.
+ await nextValueFromServer(window_parent_ack_key);
+
+ attachFencedFrame(generateURL("window-parent-inner.html",
+ [window_parent_key, window_parent_ack_key, "nested"]));
+}
+
+init();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/window-parent-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/window-parent-inner.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/window-parent-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/resources/window-top-inner.html b/testing/web-platform/tests/fenced-frame/resources/window-top-inner.html
new file mode 100644
index 0000000000..ddc30bf71b
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/window-top-inner.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="utils.js"></script>
+<title>Fenced frame content to report the value of window.top</title>
+
+<body>
+<script>
+async function init() { // Needed in order to use top-level await.
+ // This file is meant to run in a <fencedframe>. It reports back to the
+ // outermost page whether or not the value of `window.top` was correct for:
+ // 1.) Top-level fenced frames
+ // 2.) Nested iframes inside a fenced frame
+ // 3.) Nested fenced frames
+ const url = new URL(location.href);
+
+ const [window_top_key, window_top_ack_key, nested] = parseKeylist();
+
+ // Report whether or not `window.top` was correct.
+ let pass_string = "";
+ if (nested == "nested")
+ pass_string = "pass: fenced frame > fenced frame";
+ else
+ pass_string = "pass: fenced frame";
+
+ let result = (window.top == window) ? pass_string : "fail";
+ writeValueToServer(window_top_key, result);
+
+ // If this page is a nested fenced frame, all we need to do is report the
+ // top-level value.
+ if (nested == "nested")
+ return;
+
+ // Wait for ACK, so we know that the outer page has read the last value from
+ // the `window_top_key` stash and we can write to it again.
+ await nextValueFromServer(window_top_ack_key);
+
+ // Now test `window.top` inside an iframe.
+ const iframe = document.createElement('iframe');
+ iframe.src = "dummy.html";
+ const load_promise = new Promise((resolve, reject) => {
+ iframe.onload = resolve;
+ iframe.onerror = reject;
+ });
+ document.body.append(iframe);
+
+ await load_promise;
+
+ // Report whether or not the subframe's `window.top` was correct.
+ result = (iframe.contentWindow.top == window) ?
+ "pass: fenced frame > iframe" : "fail";
+ writeValueToServer(window_top_key, result);
+
+ // Wait for ACK, so we know that the outer page has read the last value from
+ // the `window_top_key` stash and we can write to it again.
+ await nextValueFromServer(window_top_ack_key);
+
+ attachFencedFrame(generateURL("window-top-inner.html",
+ [window_top_key, window_top_ack_key, "nested"]));
+}
+
+init();
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/resources/window-top-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/window-top-inner.html.headers
new file mode 100644
index 0000000000..6247f6d632
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/resources/window-top-inner.html.headers
@@ -0,0 +1 @@
+Supports-Loading-Mode: fenced-frame \ No newline at end of file
diff --git a/testing/web-platform/tests/fenced-frame/revoke-nested-fenced-frame-in-iframe-navigation.https.html b/testing/web-platform/tests/fenced-frame/revoke-nested-fenced-frame-in-iframe-navigation.https.html
new file mode 100644
index 0000000000..3a44c3c2f4
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/revoke-nested-fenced-frame-in-iframe-navigation.https.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<title>Test that window.fence.disableUntrustedNetwork disables
+ embedder-initiated navigation of FF -> IF -> FF.</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+
+// Run a test with a fenced frame nested in an iframe nested in a fenced frame.
+// If `should_disable_network` is true, window.fence.disableUntrustedNetwork
+// will be called before creating the nested fenced frame.
+// If `use_urn_iframe` is true, the nested iframe will be a urn iframe.
+async function ff_if_ff_test(t, should_disable_network, use_urn_iframe, should_succeed) {
+const fencedframe = await attachFencedFrameContext({generator_api: 'sharedstorage'});
+ const navigation_promise =
+ fencedframe.execute(async (should_disable_network, use_urn_iframe) => {
+ let args = {};
+ if (use_urn_iframe) {
+ args = {generator_api: 'sharedstorage'};
+ }
+ const nested_iframe = await attachIFrameContext(args);
+ await nested_iframe.execute(() => {});
+ if (should_disable_network) {
+ await window.fence.disableUntrustedNetwork();
+ }
+ return nested_iframe.execute(async () => {
+ const nested_fenced_frame = await attachFencedFrameContext({
+ generator_api: 'sharedstorage'});
+ return nested_fenced_frame.execute(() => { return 'nav success'; });
+ });
+ },
+ [should_disable_network, use_urn_iframe]);
+ if (should_succeed) {
+ const result = await navigation_promise;
+ assert_equals(result, 'nav success');
+ } else {
+ const result = await Promise.race([navigation_promise,
+ new Promise((resolve, reject) => t.step_timeout(
+ () => resolve('timeout'), 1000))
+ ]);
+ assert_equals(result, 'timeout');
+ }
+}
+
+promise_test(async(t) => {
+ await ff_if_ff_test(t, /*should_disable_network=*/false,
+ /*use_urn_iframe=*/false,
+ /*should_succeed=*/true);
+}, 'FF->IF->FF navigation works');
+
+promise_test(async(t) => {
+ await ff_if_ff_test(t, /*should_disable_network=*/false,
+ /*use_urn_iframe=*/true,
+ /*should_succeed=*/true);
+}, 'FF->UIF->FF navigation works');
+
+promise_test(async(t) => {
+ await ff_if_ff_test(t, /*should_disable_network=*/true,
+ /*use_urn_iframe=*/false,
+ /*should_succeed=*/false);
+}, 'window.fence.disableUntrustedNetwork disables FF->IF->FF navigation');
+
+promise_test(async(t) => {
+ await ff_if_ff_test(t, /*should_disable_network=*/true,
+ /*use_urn_iframe=*/true,
+ /*should_succeed=*/false);
+}, 'window.fence.disableUntrustedNetwork disables FF->UF->FF navigation');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/revoke-nested-fenced-frame-navigation.https.html b/testing/web-platform/tests/fenced-frame/revoke-nested-fenced-frame-navigation.https.html
new file mode 100644
index 0000000000..b80350a588
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/revoke-nested-fenced-frame-navigation.https.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<title>Test that window.fence.disableUntrustedNetwork disables
+ embedder-initiated navigation of nested fenced frames.</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+
+// Run a test with a fenced frame nested in a fenced frame.
+// If `should_disable_network` is true, window.fence.disableUntrustedNetwork
+// will be called before creating the nested fenced frame.
+async function ff_ff_test(t, should_disable_network, should_succeed) {
+ const fencedframe = await attachFencedFrameContext();
+ const navigation_promise =
+ fencedframe.execute(async (should_disable_network) => {
+ if (should_disable_network) {
+ await window.fence.disableUntrustedNetwork();
+ }
+ const nested_fenced_frame = await attachFencedFrameContext();
+ return nested_fenced_frame.execute(() => { return 'nav success'; }); },
+ [should_disable_network]);
+ if (should_succeed) {
+ const result = await navigation_promise;
+ assert_equals(result, 'nav success');
+ } else {
+ const result = await Promise.race([
+ navigation_promise,
+ new Promise((resolve, reject) => t.step_timeout(
+ () => resolve('timeout'), 2000))]);
+ assert_equals(result, 'timeout');
+ }
+}
+
+promise_test(async(t) => {
+ await ff_ff_test(t, /*should_disable_network=*/false,
+ /*should_succeed=*/true);
+}, 'FF->FF navigation works');
+
+promise_test(async(t) => {
+ await ff_ff_test(t, /*should_disable_network=*/true,
+ /*should_succeed=*/false);
+}, 'window.fence.disableUntrustedNetwork disables FF->FF navigation');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/revoke-unfenced-top-navigation.https.html b/testing/web-platform/tests/fenced-frame/revoke-unfenced-top-navigation.https.html
new file mode 100644
index 0000000000..873404768f
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/revoke-unfenced-top-navigation.https.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<title>Test that window.fence.disableUntrustedNetwork disables
+ _unfencedTop navigations.</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+<script>
+
+promise_test(async(t) => {
+ const fencedframe = await attachFencedFrameContext({generator_api: 'fledge'});
+ await fencedframe.execute(() => {});
+
+ const actions = new test_driver.Actions();
+ await actions.setContext(window)
+ .pointerMove(0, 0, {origin: fencedframe.element})
+ .pointerDown()
+ .pointerUp()
+ .send();
+
+ const destination_url = new URL('resources/unreached.https.html', location.href);
+ fencedframe.execute(async (url) => {
+ await window.fence.disableUntrustedNetwork();
+ // After disabling network, _unfencedTop navigations should not work.
+ assert_true(navigator.userActivation.isActive,
+ 'The frame should have user activation.')
+ const result = window.open(url, '_unfencedTop');
+ assert_equals(result, null, '_unfencedTop did not return a window.');
+ }, [destination_url]);
+
+ // Wait a few seconds.
+ await new Promise((resolve, reject) =>
+ t.step_timeout(() => resolve('timeout'), 3000));
+
+ // Confirm that the fenced frame is still there.
+ await fencedframe.execute(() => {});
+}, 'window.fence.disableUntrustedNetwork disables _unfencedTop navigations');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/sandbox-attribute.https.html b/testing/web-platform/tests/fenced-frame/sandbox-attribute.https.html
new file mode 100644
index 0000000000..1458145e43
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/sandbox-attribute.https.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<title>Test fenced frame sandbox attribute.</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+
+async function runTest(t, sandbox_flags, success) {
+ const frame = await attachFencedFrameContext({
+ generator_api: 'fledge', resolve_to_config: true,
+ attributes: [['sandbox', sandbox_flags]]});
+
+ assert_equals(frame.element.sandbox.value, sandbox_flags);
+ if (sandbox_flags) {
+ assert_equals(frame.element.sandbox.length, sandbox_flags.split(' ').length);
+ } else {
+ assert_equals(frame.element.sandbox.length, 0);
+ }
+
+ const result = await Promise.any([
+ frame.execute(() => { return 'success';}),
+ new Promise(resolve => t.step_timeout(() => resolve('failure'), 2000))]);
+ if (success) {
+ assert_equals(result, 'success');
+ } else {
+ assert_equals(result, 'failure');
+ }
+}
+
+// We omit test cases that lack the sandbox attribute, because that's covered
+// by every other test that doesn't explicitly use the `sandbox` attribute.
+
+promise_test(async t => {
+ return runTest(t, '', false);
+}, 'Navigation fails with no allowed features');
+
+promise_test(async t => {
+ return runTest(t, 'allow-same-origin allow-forms allow-scripts allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation', true);
+}, 'Navigation succeeds with exactly the required unsandboxed features');
+
+promise_test(async t => {
+ return runTest(t, 'allow-same-origin allow-forms allow-scripts allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation allow-pointer-lock', true);
+}, 'Navigation succeeds with extra unsandboxed features');
+
+promise_test(async t => {
+ return runTest(t, 'allow-same-origin allow-forms allow-scripts allow-popups allow-popups-to-escape-sandbox', false);
+}, 'Navigation fails with too few unsandboxed features');
+
+promise_test(async t => {
+ return runTest(t, 'foo bar baz', false);
+}, 'Navigation fails with malformed sandbox flags');
+
+promise_test(async t => {
+ return runTest(t, 'allow-same-origin allow-forms allow-scripts allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation allow-foobarbaz', true);
+}, 'Navigation fails with the required unsandboxed features, plus some malformed ones');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/sandbox-mandatory-flags.https.html b/testing/web-platform/tests/fenced-frame/sandbox-mandatory-flags.https.html
new file mode 100644
index 0000000000..57a5bc49cf
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/sandbox-mandatory-flags.https.html
@@ -0,0 +1,136 @@
+<!DOCTYPE html>
+<meta name=timeout content=long>
+<title>Test of sandbox mandatory flags</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+
+<body>
+
+<script>
+
+const mandatory_flags = [
+ 'allow-same-origin',
+ 'allow-forms',
+ 'allow-scripts',
+ 'allow-popups',
+ 'allow-popups-to-escape-sandbox',
+ 'allow-top-navigation-by-user-activation'];
+
+promise_test(async t => {
+ const key = token();
+ const value = 'fenced frame loaded';
+ const iframe = document.createElement('iframe');
+ iframe.src =
+ 'resources/sandbox-mandatory-flags-iframe.sub.html?key=' + key +
+ '&value=' + value;
+ mandatory_flags.forEach(flag => {
+ iframe.sandbox.add(flag);
+ });
+
+ document.body.appendChild(iframe);
+ t.add_cleanup(() => {
+ iframe.remove();
+ });
+ const result = await nextValueFromServer(key);
+ assert_equals(result, value, 'The fenced frame must be loaded.');
+}, 'Sandboxed Iframe with mandatory flags can load a fenced frame.');
+
+promise_test(async t => {
+ const key = token();
+ // Try to load a fenced frame in a sandboxed iframe like this:
+ // <iframe sandbox="|mandatory_flags| without |missing_flag|"
+ // src="sandbox-mandatory-flags-iframe.sub.html">
+ // <fencedframe src="sandbox-mandatory-flags-inner.sub.html">
+ // <img src="key-value-store.py?key=|key|&value=|value|">
+ // <fencedframe>
+ // </iframe>
+ // But this should fail because the sandboxed iframe is loaded without
+ // |missing_flag|.
+ for (let missing_flag of mandatory_flags) {
+ const value =
+ 'a fenced frame was loaded in a sandboxed iframe without ' +
+ missing_flag + '.';
+ const iframe = document.createElement('iframe');
+ iframe.src =
+ 'resources/sandbox-mandatory-flags-iframe.sub.html?key=' + key +
+ '&value=' + value;
+ mandatory_flags.forEach(flag => {
+ if (flag != missing_flag) {
+ iframe.sandbox.add(flag);
+ }
+ });
+ document.body.appendChild(iframe);
+ t.add_cleanup(() => {
+ iframe.remove();
+ });
+ }
+ t.step_timeout(() => t.done(), 3000);
+ let server_value = await nextValueFromServer(key);
+ assert_unreached('fenced frame should not be loaded, but ' + server_value);
+}, 'Sandboxed Iframe without one of mandatory flag must fail to load a fenced' +
+ ' frame.');
+
+promise_test(async t => {
+ const key = token();
+ // Try to load a fenced frame in a nested sandboxed iframe like this:
+ // <iframe sandbox="|mandatory_flags| without |missing_flag|"
+ // src="sandbox-mandatory-flags-looser-restriction.sub.html">
+ // <iframe sandbox="|mandatory_flags|"
+ // src="sandbox-mandatory-flags-iframe.sub.html">
+ // <fencedframe src="resources/sandbox-mandatory-flags-inner.sub.html">
+ // <img src="key-value-store.py?key=|key|&value=|value|">
+ // <fencedframe>
+ // </iframe>
+ // </iframe>
+ // But this should fail because the nested iframe is loaded sandboxed
+ // without |missing_flag|.
+ for (let missing_flag of mandatory_flags) {
+ const value =
+ 'a fenced frame was loaded in a nested sandboxed iframe without ' +
+ missing_flag + '.';
+ const iframe = document.createElement('iframe');
+ iframe.src =
+ 'resources/sandbox-mandatory-flags-looser-restriction.sub.html?key=' +
+ key + '&value=' + value;
+ mandatory_flags.forEach(flag => {
+ if (flag != missing_flag) {
+ iframe.sandbox.add(flag);
+ }
+ });
+ document.body.appendChild(iframe);
+ t.add_cleanup(() => {
+ iframe.remove();
+ });
+ }
+ t.step_timeout(() => t.done(), 3000);
+ let server_value = await nextValueFromServer(key);
+ assert_unreached('fenced frame should not be loaded, but ' + server_value);
+}, 'Nested sandboxed iframe without one of mandatory flag must fail to load a' +
+ 'fenced frame even when the inner nested sandboxed iframe has all ' +
+ 'mandatory allow- flags.');
+
+promise_test(async t => {
+ const key = token();
+ // allow-scripts is needed to run iframe.execute, so we will test every other
+ // sandbox flag
+ for (let missing_flag of
+ mandatory_flags.filter(word => word != "allow-scripts")) {
+ const value =
+ 'canLoadOpaqueURL returned true even with flag ' +
+ missing_flag + ' not set.';
+ const flags_to_add = mandatory_flags
+ .filter(word => word != missing_flag)
+ .join(" ");
+ const iframe = attachIFrameContext(
+ {attributes: [["sandbox", flags_to_add]]});
+ await iframe.execute(async (t) => {
+ assert_false(navigator.canLoadAdAuctionFencedFrame());
+ });
+ }
+}, 'navigator.canLoadAdAuctionFencedFrame considers mandatory sandbox flags');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/sandboxed-features-alert.https.html b/testing/web-platform/tests/fenced-frame/sandboxed-features-alert.https.html
new file mode 100644
index 0000000000..6d3f83208d
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/sandboxed-features-alert.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Test of sandboxed features - alert</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+<script src="resources/sandboxed-features.js"></script>
+
+<body>
+<script>
+run_sanboxed_feature_test('test_alert',
+ 'The fenced frame must fail to open an alert dialog.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/sandboxed-features-confirm.https.html b/testing/web-platform/tests/fenced-frame/sandboxed-features-confirm.https.html
new file mode 100644
index 0000000000..bb55f15ff0
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/sandboxed-features-confirm.https.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>Test of sandbox features - confirm</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+<script src="resources/sandboxed-features.js"></script>
+
+<body>
+<script>
+run_sanboxed_feature_test('test_confirm',
+ 'The fenced frame must fail to open a confirm dialog.');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/sandboxed-features-documentdomain.https.html b/testing/web-platform/tests/fenced-frame/sandboxed-features-documentdomain.https.html
new file mode 100644
index 0000000000..e00ab23980
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/sandboxed-features-documentdomain.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Test of sandboxed features - document.domain</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+<script src="resources/sandboxed-features.js"></script>
+
+<body>
+<script>
+run_sanboxed_feature_test('test_document_domain',
+ 'The fenced frame must fail to change Document.domain.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/sandboxed-features-pointerlock.https.html b/testing/web-platform/tests/fenced-frame/sandboxed-features-pointerlock.https.html
new file mode 100644
index 0000000000..af598798b2
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/sandboxed-features-pointerlock.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Test of sandboxed features - pointer lock</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+<script src="resources/sandboxed-features.js"></script>
+
+<body>
+<script>
+run_sanboxed_feature_test('test_pointer_lock',
+ 'The fenced frame must fail to call requestPointerLock().');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/sandboxed-features-presentation-request.https.html b/testing/web-platform/tests/fenced-frame/sandboxed-features-presentation-request.https.html
new file mode 100644
index 0000000000..7f9b1d7bd6
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/sandboxed-features-presentation-request.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Test of sandboxed features - PresentationRequest</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+<script src="resources/sandboxed-features.js"></script>
+
+<body>
+<script>
+run_sanboxed_feature_test('test_presentation_request',
+ 'The fenced frame must fail to create a PresentationRequest.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/sandboxed-features-printdialog.https.html b/testing/web-platform/tests/fenced-frame/sandboxed-features-printdialog.https.html
new file mode 100644
index 0000000000..b03f7a22d8
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/sandboxed-features-printdialog.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Test of sandboxed features - print</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+<script src="resources/sandboxed-features.js"></script>
+
+<body>
+<script>
+run_sanboxed_feature_test('test_print',
+ 'The fenced frame must fail to print the page.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/sandboxed-features-prompt.https.html b/testing/web-platform/tests/fenced-frame/sandboxed-features-prompt.https.html
new file mode 100644
index 0000000000..1207f4a765
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/sandboxed-features-prompt.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Test of sandboxed features - prompt</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+<script src="resources/sandboxed-features.js"></script>
+
+<body>
+<script>
+run_sanboxed_feature_test('test_prompt',
+ 'The fenced frame must fail to open a prompt.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/sandboxed-features-screen-orientation-lock.https.html b/testing/web-platform/tests/fenced-frame/sandboxed-features-screen-orientation-lock.https.html
new file mode 100644
index 0000000000..4e80d92e1d
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/sandboxed-features-screen-orientation-lock.https.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<title>Test of sandboxed features - screen.orientation.lock</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+<script src="resources/sandboxed-features.js"></script>
+
+<body>
+<script>
+run_sanboxed_feature_test('test_screen_orientation_lock',
+ 'The fenced frame must fail to call screen.orientation.lock().');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/script-focus.https.html b/testing/web-platform/tests/fenced-frame/script-focus.https.html
new file mode 100644
index 0000000000..0bef98219b
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/script-focus.https.html
@@ -0,0 +1,206 @@
+<!DOCTYPE html>
+<title>Test Script-Based Focus for Fenced Frames</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+
+<script src="/common/get-host-info.sub.js"></script>
+
+<body>
+<script>
+async function AttemptButtonFocus(frame, expecting_focus) {
+ await frame.execute(async (expecting_focus) => {
+ const button = document.createElement("button");
+ document.body.append(button);
+ button.focus();
+ assert_equals(document.activeElement == button, expecting_focus,
+ "Button's focus should match expected focus");
+ }, [expecting_focus]);
+}
+
+async function ClickOn(element, actions) {
+ // Wait until the window size is initialized.
+ while (window.innerWidth == 0) {
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ }
+ await actions.pointerMove(0, 0, {origin: element})
+ .pointerDown()
+ .pointerUp()
+ .send();
+}
+
+async function SetupTest(click=true) {
+ // Clean up any leftover frames from prior tests.
+ document.querySelectorAll("fencedframe").forEach(e => {
+ e.remove();
+ })
+
+ const actions = new test_driver.Actions();
+
+ const frame = attachFencedFrameContext();
+ const fencedframe_element = frame.element;
+
+ if (click)
+ await ClickOn(document.body, actions);
+
+ return [actions, frame, fencedframe_element];
+}
+
+promise_test(async () => {
+ const [actions, ff1, ff1_element] = await SetupTest(false);
+
+ await ClickOn(ff1_element, actions);
+ await AttemptButtonFocus(ff1, true);
+
+ const button = document.createElement("button");
+ document.body.append(button);
+ button.focus();
+ assert_true(document.activeElement == button,
+ "The button should have focus");
+ assert_false(navigator.userActivation.isActive,
+ "Window should not have user activation");
+}, "An embedder can focus out of a fenced frame");
+
+promise_test(async () => {
+ const [actions, frame, fencedframe_element] = await SetupTest();
+
+ await AttemptButtonFocus(frame, false);
+ await ClickOn(fencedframe_element, actions);
+ await AttemptButtonFocus(frame, true);
+}, "Fenced frames can't pull script focus until getting user activation");
+
+promise_test(async t => {
+ const [actions, frame, fencedframe_element] = await SetupTest();
+
+ await ClickOn(fencedframe_element, actions);
+ await ClickOn(document.body, actions);
+
+ await AttemptButtonFocus(frame, true);
+
+ // Give the browser time to receive the focus event before attempting
+ // another focus.
+ await t.step_timeout(async () => {await AttemptButtonFocus(frame, true);},
+ 500);
+}, "Focused fenced frames can move programmatic focus within frame");
+
+promise_test(async () => {
+ const [actions, frame, fencedframe_element] = await SetupTest();
+
+ await ClickOn(fencedframe_element, actions);
+ await ClickOn(document.body, actions);
+
+ // This will pull focus across a frame boundary and consume user activation.
+ await AttemptButtonFocus(frame, true);
+
+ await ClickOn(document.body, actions);
+ await AttemptButtonFocus(frame, false);
+}, "Script focus into a fenced frame consumes user activation");
+
+promise_test(async () => {
+ const [actions, ff1, ff1_element] = await SetupTest();
+
+ const ff2 = attachFencedFrameContext();
+ const ff2_element = ff2.element;
+
+ await ClickOn(ff1_element, actions);
+
+ await AttemptButtonFocus(ff1, true);
+ await AttemptButtonFocus(ff2, false);
+}, "Another fenced frame cannot pull focus out of a focused fenced frame");
+
+promise_test(async () => {
+ const [actions, ff1, ff1_element] = await SetupTest();
+
+ await ClickOn(ff1_element, actions);
+ await AttemptButtonFocus(ff1, true);
+
+ await ff1.execute(async () => {
+ const ff2 = attachFencedFrameContext();
+
+ await ff2.execute(async () => {
+ const button = document.createElement("button");
+ document.body.append(button);
+ button.focus();
+ assert_false(document.activeElement == button,
+ "The button should not have focus");
+ assert_false(navigator.userActivation.isActive,
+ "The fenced frame should not have user activation");
+ });
+ });
+}, "A fenced frame nested in another fenced frame cannot pull focus");
+
+promise_test(async () => {
+ const [actions, ff1, ff1_element] = await SetupTest();
+
+ await ClickOn(document.body, actions);
+
+ const button = document.createElement("button");
+ document.body.append(button);
+ button.focus();
+ assert_equals(document.activeElement, button,
+ "The button in the main page should have focus.");
+
+ await ff1.execute(async () => {
+ assert_false(navigator.userActivation.isActive,
+ "The fenced frame should not have user activation.");
+ window.focus();
+ });
+
+ assert_equals(document.activeElement, button,
+ "The button in the main page should still have focus.");
+}, "A fenced frame cannot pull window.focus() without user activation");
+
+promise_test(async () => {
+ const [actions, ff1, ff1_element] = await SetupTest();
+
+ await ClickOn(ff1_element, actions);
+ await ClickOn(document.body, actions);
+
+ const button = document.createElement("button");
+ document.body.append(button);
+ button.focus();
+ assert_equals(document.activeElement, button,
+ "The button should have focus.");
+
+ await ff1.execute(async () => {
+ assert_true(navigator.userActivation.isActive,
+ "The fenced frame should have user activation.");
+ window.focus();
+ assert_false(navigator.userActivation.isActive,
+ "The fenced frame's user activation should be consumed by the focus");
+ });
+
+ assert_equals(document.activeElement, document.body,
+ "The main page's focus should be pulled away from the button.");
+}, "A fenced frame can pull window.focus() after user activation");
+
+promise_test(async () => {
+ var actions = new test_driver.Actions();
+
+ const frame = attachIFrameContext(
+ {origin: get_host_info().HTTPS_REMOTE_ORIGIN});
+ const iframe_element =
+ document.body.getElementsByTagName('iframe')[0];
+
+ await frame.execute(async () => {
+ const button = document.createElement("button");
+ document.body.append(button);
+ button.focus();
+ assert_equals(document.activeElement, button,
+ "The button in the iframe should have focus.");
+ }, [true]);
+
+ const button = document.createElement("button");
+ document.body.append(button);
+ button.focus();
+ assert_equals(document.activeElement, button,
+ "The button in the main page should have focus.");
+}, "An cross-origin iframe can pull focus back and forth without activation");
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/scroll-into-view.https.html b/testing/web-platform/tests/fenced-frame/scroll-into-view.https.html
new file mode 100644
index 0000000000..5188f39c69
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/scroll-into-view.https.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<title>Test scrollIntoView() inside a fenced frame</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<!-- This spacer is used to ensure that the fenced frame will be out of view
+ unless a scroll is performed. -->
+<div style="height: 2000px;"></div>
+<script>
+promise_test(async(t) => {
+ const fencedframe = await attachFencedFrameContext();
+
+ const start_y_offset = window.pageYOffset;
+
+ await fencedframe.execute(() => {
+ // Ensure that any elements created are out of view until a scroll is
+ // performed.
+ const spacer = document.createElement("div");
+ spacer.style = "height: 2000px;";
+ document.body.appendChild(spacer);
+
+ const start_fenced_y_offset = window.pageYOffset;
+
+ const button = document.createElement("button");
+ document.body.appendChild(button);
+ button.scrollIntoView();
+
+ const end_fenced_y_offset = window.pageYOffset;
+ assert_not_equals(start_fenced_y_offset, end_fenced_y_offset,
+ "The inner page should have scrolled.");
+ }, []);
+
+ const end_y_offset = window.pageYOffset;
+ assert_equals(start_y_offset, end_y_offset,
+ "The outer page should not have scrolled.");
+
+}, 'scrollIntoView() inside a fenced frame should not scroll ancestors');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/selecturl-flexible-size.https.html b/testing/web-platform/tests/fenced-frame/selecturl-flexible-size.https.html
new file mode 100644
index 0000000000..6b1a07e73a
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/selecturl-flexible-size.https.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<title>Test frame size behavior in selectURL fenced frames.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+async function runTest(original_width, original_height, new_width, new_height) {
+ // Attach a selectURL fenced frame whose outer container has dimensions
+ // `original_width` by `original_height`.
+ const frame = await attachFencedFrameContext({
+ generator_api: "sharedstorage", resolve_to_config: true,
+ attributes: [["width", original_width], ["height", original_height]]});
+
+ const assert_dimensions =
+ (label, original_width, original_height,
+ expected_width, expected_height) => {
+ assert_equals(getComputedStyle(document.documentElement).width,
+ expected_width+"px",
+ label + " the computed width (originally " + original_width
+ + ") should be " + expected_width);
+ assert_equals(window.innerWidth, expected_width,
+ label + " the innerWidth (originally " + original_width
+ + ") should be " + expected_width);
+ assert_equals(window.innerHeight, expected_height,
+ label + " the innerHeight (originally " + original_height
+ + ") should be " + expected_height);
+ }
+
+ // Assert that the fenced frame sees the original dimensions.
+ await frame.execute(assert_dimensions, ["After navigation",
+ original_width, original_height, original_width, original_height]);
+
+ // Assert that the embedder sees the fenced frame's original dimensions.
+ assert_equals(frame.width, original_width.toString(),
+ "The outer container width is the requested width.");
+ assert_equals(frame.height, original_height.toString(),
+ "The outer container height is the requested height.");
+
+ // Resize the fenced frame's outer container.
+ frame.width = new_width;
+ frame.height = new_height;
+
+ // Observe that the selectURL fenced frame sees the new size.
+ await frame.execute(assert_dimensions, ["After resizing",
+ original_width, original_height, new_width, new_height]);
+}
+
+// Exact size cases.
+promise_test(async () => { return runTest(299, 72, 100, 101); }, '299x72->100x101');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/self-urn-navigation.https.html b/testing/web-platform/tests/fenced-frame/self-urn-navigation.https.html
new file mode 100644
index 0000000000..4b1e989e8a
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/self-urn-navigation.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Test that fenced frame-initiated self urn navigations fail.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+function getTimeoutPromise(t) {
+ return new Promise(resolve =>
+ t.step_timeout(() => resolve("NOT LOADED"), 2000));
+}
+
+// A fenced frame root should not be able to navigate itself to a urn:uuid.
+promise_test(async t => {
+ const frame = attachFencedFrameContext();
+ urn = await runSelectURL(frame.src);
+
+ // Send the urn:uuid to the fenced frame over the network, and attempt to
+ // self-"refresh" to that urn.
+ await frame.execute(async (urn) => {
+ window.executor.suspend(() => {
+ location.href = urn;
+ });
+ }, [urn]);
+
+ // The navigation should fail as intended.
+ const urn_load_success_promise = frame.execute(() => {});
+ const urn_load_failure_promise = getTimeoutPromise(t);
+ const result =
+ await Promise.any([urn_load_success_promise, urn_load_failure_promise]);
+ assert_equals(result, "NOT LOADED");
+}, 'fenced frame-initiated self urn navigation');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/serviceWorker-dedicated-worker.https.html b/testing/web-platform/tests/fenced-frame/serviceWorker-dedicated-worker.https.html
new file mode 100644
index 0000000000..92533e3873
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/serviceWorker-dedicated-worker.https.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<title>Service Worker: Check if dedicated workers are controlled</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+ <script>
+ const frameUrl = './resources/serviceWorker-dedicated-worker-inner.html';
+ const unregisterAllSW = async () => {
+ const regs = await navigator.serviceWorker.getRegistrations();
+ return Promise.all(regs.map(reg => reg.unregister()));
+ };
+
+ promise_test(async t => {
+ t.add_cleanup(unregisterAllSW);
+ const key = token();
+
+ // Inside the fenced frame, the service worker is registered and fetch
+ // request is triggered from the dedicated worker to the url that is
+ // handled in the service worker.
+ const url = `${frameUrl}?useServiceWorkerInFencedFrame=true`;
+ attachFencedFrame(generateURL(url, [key]));
+ const result = await nextValueFromServer(key);
+ assert_equals(result, "OK");
+ }, "Fenced frame's service workers can control fenced frame's dedicated workers");
+
+ promise_test(async t => {
+ t.add_cleanup(unregisterAllSW);
+ const key = token();
+
+ // Set a service worker in the fenced frame. Inside the fenced frame, a
+ // dedicated worker is created and triggers a fetch request. But we don't
+ // use the fetch request result in this test. This test will check if the
+ // dedicated worker in the parent frame is controlled by the SW in FF.
+ const url = `${frameUrl}?useServiceWorkerInFencedFrame=true`;
+ attachFencedFrame(generateURL(url, [key]));
+ await nextValueFromServer(key);
+
+ const checkIfWorkerIsControlled = async () => {
+ const dedicated_worker = new Worker('resources/serviceWorker-dedicated-worker.js');
+ return new Promise((resolve, reject) => {
+ dedicated_worker.addEventListener('message', e => {
+ resolve(e.data)
+ });
+ dedicated_worker.postMessage('fetch');
+ });
+ }
+
+ const result = await checkIfWorkerIsControlled()
+ assert_equals(result, "Not Found");
+ }, "Fenced frame's service workers can not control the dedicated workers in the parent frame");
+
+ promise_test(async t => {
+ t.add_cleanup(unregisterAllSW);
+ const key = token();
+
+ // Register a service worker in the parent frame.
+ await navigator.serviceWorker.register('resources/serviceWorker-dedicated-worker-sw.js', { scope: '/' });
+ await navigator.serviceWorker.ready;
+
+ // Inside the fenced frame, fetch request to unexisting URL is triggered
+ // from the dedicated worker.
+ attachFencedFrame(generateURL(frameUrl, [key]));
+
+ const result = await nextValueFromServer(key);
+ assert_equals(result, "Not Found");
+ }, "Service workers in the parent frame of fenced frames can not control dedicated workers in fenced frames");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/serviceWorker-frameType.https.html b/testing/web-platform/tests/fenced-frame/serviceWorker-frameType.https.html
new file mode 100644
index 0000000000..ac0bc07f3d
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/serviceWorker-frameType.https.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<title>Service Worker: Clients.matchAll with includeUncontrolled</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+<body>
+<script>
+
+promise_test(async t => {
+ const frame_type_key = token();
+ const frame_type_ack_key = token();
+
+ attachFencedFrame(generateURL('resources/serviceWorker-frameType-inner.html',
+ [frame_type_key, frame_type_ack_key]));
+
+ const frame_type_result = await nextValueFromServer(frame_type_key);
+ assert_equals(frame_type_result, "top-level",
+ "The service worker for the top-level fenced frame has the " +
+ "right value for `serviceWorker.frameType`");
+
+ // Write an ACK, so that the fenced frame knows it can send message over the
+ // `serviceWorker.frameType` channel again.
+ writeValueToServer(frame_type_ack_key, "ACK");
+
+ const nested_frame_type_result = await nextValueFromServer(frame_type_key);
+ assert_equals(nested_frame_type_result, "nested",
+ "The service worker for the iframe inside the top-level " +
+ "fenced frame has the right value for `serviceWorker.frameType`");
+}, 'serviceWorker.frameType');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/serviceWorker-push.https.html b/testing/web-platform/tests/fenced-frame/serviceWorker-push.https.html
new file mode 100644
index 0000000000..cb460d161e
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/serviceWorker-push.https.html
@@ -0,0 +1,62 @@
+<!doctype html>
+<html>
+<head>
+<title>Service Worker: Push Messaging Test</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 src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+</head>
+<body>
+<script>
+ promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ try {
+ await frame.execute(async () => {
+ await navigator.serviceWorker.register(
+ 'empty-worker.js', { scope: location.href });
+ const registration= await navigator.serviceWorker.ready;
+ return await registration.pushManager.subscribe({
+ userVisibleOnly: true
+ });
+ });
+ assert_unreached('subscribe() executed without error; want error');
+ } catch(e) {
+ assert_equals(e.message,
+ "Failed to execute 'subscribe' on 'PushManager': subscribe() is not " +
+ "allowed in fenced frames.");
+ }
+ }, 'subscribe() should fail inside a fenced frame');
+
+ promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ const message = await frame.execute(async () => {
+ const getController = () => {
+ if (navigator.serviceWorker.controller) {
+ return navigator.serviceWorker.controller;
+ }
+ return new Promise(resolve => {
+ navigator.serviceWorker.addEventListener('controllerchange', () => {
+ resolve(navigator.serviceWorker.controller);
+ });
+ });
+ };
+ await navigator.serviceWorker.register(
+ 'serviceWorker-push-sw.js', { scope: location.href });
+ return new Promise(async resolve => {
+ const ctrl = await getController();
+ ctrl.postMessage('subscribe');
+ navigator.serviceWorker.onmessage = e => {
+ resolve(e.data);
+ }
+ });
+ });
+ assert_equals(message, "Failed to execute 'subscribe' on " +
+ "'PushManager': subscribe() is not allowed in fenced frames.");
+ }, 'subscribe() should fail from the service worker inside a fenced frame');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/set-automatic-beacon.https.html b/testing/web-platform/tests/fenced-frame/set-automatic-beacon.https.html
new file mode 100644
index 0000000000..c178370739
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/set-automatic-beacon.https.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<title>Test window.fence.setReportEventDataForAutomaticBeacons</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async(t) => {
+ const fencedframe = await attachFencedFrameContext(
+ {generator_api: 'fledge'});
+ await fencedframe.execute(() => {
+ let event = {
+ eventType: "reserved.top_navigation_commit",
+ eventData: "a".repeat(64000),
+ destination: ["buyer"],
+ }
+ window.fence.setReportEventDataForAutomaticBeacons(event);
+ });
+}, 'setReportEventDataForAutomaticBeacons works at the size limit');
+
+promise_test(async(t) => {
+ const fencedframe = await attachFencedFrameContext(
+ {generator_api: 'fledge'});
+ await fencedframe.execute(() => {
+ let event = {
+ eventType: "reserved.top_navigation_commit",
+ eventData: "a".repeat(64001),
+ destination: ["buyer"],
+ }
+ assert_throws_dom("SecurityError", () => {
+ window.fence.setReportEventDataForAutomaticBeacons(event);
+ });
+ });
+}, 'setReportEventDataForAutomaticBeacons fails over the size limit');
+
+promise_test(async(t) => {
+ const fencedframe = await attachFencedFrameContext(
+ {generator_api: 'fledge'});
+ await fencedframe.execute(() => {
+ let event = {
+ eventType: "reserved.top_navigation_commit",
+ eventData: "a".repeat(32),
+ destination: ["invalid"],
+ }
+ assert_throws_js(TypeError, () => {
+ window.fence.setReportEventDataForAutomaticBeacons(event);
+ });
+ });
+}, 'setReportEventDataForAutomaticBeacons fails for invalid destination');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/setting-null-config-navigates-to-about-blank.https.html b/testing/web-platform/tests/fenced-frame/setting-null-config-navigates-to-about-blank.https.html
new file mode 100644
index 0000000000..2595fd64c9
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/setting-null-config-navigates-to-about-blank.https.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/utils.js"></script>
+<title>Test fenced frame null config installation triggers about:blank navigation.</title>
+
+<body>
+
+ <script>
+ promise_test(async (t) => {
+ var frame_context = attachFencedFrameContext();
+
+ // Ensure remote context responds.
+ let alive_indicator = await Promise.race([
+ frame_context.execute(() => 'alive'),
+ new Promise((resolve, reject) => t.step_timeout(
+ () => reject('timed_out'), 3000))
+ ]);
+ assert_equals(alive_indicator, 'alive');
+
+ assert_not_equals(frame_context.element.config, null);
+ // Navigates to about:blank. Because this navigates away from the page
+ // that the frame_context.element.config is currently pointing to, the
+ // FencedFrame's portion of the remote context handling code will be
+ // removed.
+ frame_context.element.config = null;
+
+ // This call should not succeed, because we should have navigated to
+ // about:blank. Note that because the code has been deleted as described
+ // above, we can't actually inspect the URL to determine it is
+ // about:blank; we have to use our timeout as a proxy.
+ let timeout_indicator = await Promise.any([
+ frame_context.execute(() => 'alive'),
+ new Promise(resolve => t.step_timeout(
+ () => resolve('timed_out'), 3000))
+ ]);
+ assert_equals(timeout_indicator, 'timed_out');
+ }, "Test that a fenced frame with a config explicitly set to null navigates to about:blank");
+ </script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/show-directory-picker.https.html b/testing/web-platform/tests/fenced-frame/show-directory-picker.https.html
new file mode 100644
index 0000000000..ae4494f51b
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/show-directory-picker.https.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>showDirectoryPicker API test</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 src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ const result = await frame.execute(async () => {
+ await simulateGesture();
+ try {
+ const dir = await window.showDirectoryPicker();
+ return 'opened directory picker';
+ } catch (e) {
+ if (e.name === 'SecurityError' &&
+ e.message === "Failed to execute 'showDirectoryPicker' on 'Window': Cross origin sub frames aren't allowed to show a file picker.") {
+ return 'Access to the directory picker was disallowed';
+ }
+ return `showDirectoryPicker failed with unknown error ${e.name} ${e.message}`;
+ }
+ });
+ assert_equals(result, 'Access to the directory picker was disallowed');
+}, 'Directory information should not be read in the fenced frame.');
+</script>
diff --git a/testing/web-platform/tests/fenced-frame/show-open-file-picker.https.html b/testing/web-platform/tests/fenced-frame/show-open-file-picker.https.html
new file mode 100644
index 0000000000..2f99fdfb3b
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/show-open-file-picker.https.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>showOpenFilePicker API test</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 src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ const result = await frame.execute(async () => {
+ await simulateGesture();
+ try {
+ const dir = await window.showOpenFilePicker();
+ return 'opened file picker';
+ } catch (e) {
+ if (e.name === 'SecurityError' &&
+ e.message === "Failed to execute 'showOpenFilePicker' on 'Window': Cross origin sub frames aren't allowed to show a file picker.") {
+ return 'Access to the open file picker was disallowed';
+ }
+ return `showOpenFilePicker failed with unknown error ${e.name} ${e.message}`;
+ }
+ });
+ assert_equals(result, 'Access to the open file picker was disallowed');
+}, 'Directory information should not be read in the fenced frame.');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/storage-partitioning.https.html b/testing/web-platform/tests/fenced-frame/storage-partitioning.https.html
new file mode 100644
index 0000000000..36b4395339
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/storage-partitioning.https.html
@@ -0,0 +1,188 @@
+<!DOCTYPE html>
+<title>Test storage partitioning in fenced frames</title>
+<meta name="timeout" content="long">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+
+// `getter(key)` : reads the value of `key`, null if not set
+// `setter(key, value)`: sets `key` to `value`
+async function runTest(getter, setter) {
+ const key = "key";
+ const outer_value = "outer";
+ const inner_value = "inner";
+
+ // Set the value in the top-level frame, and check that it worked.
+ await setter(key, outer_value);
+ assert_equals(await getter(key), outer_value,
+ "Stored the value in the top-level frame.");
+
+ // Attach a fenced frame.
+ const frame = attachFencedFrameContext();
+
+ // Check that the outer value isn't visible.
+ const inner_before_set = await frame.execute(getter, [key]);
+ assert_equals(inner_before_set, null,
+ "The outer value isn't visible inside the fenced frame.");
+
+ // Set the value inside the fenced frame, and check that it worked.
+ await frame.execute(setter, [key, inner_value]);
+ const inner_after_set = await frame.execute(getter, [key]);
+ assert_equals(inner_after_set, inner_value,
+ "Stored the value in the fenced frame.");
+
+ // Check that the inner value isn't visible in the top-level frame.
+ assert_equals(await getter(key), outer_value,
+ "The inner value isn't visible outside the fenced frame.");
+
+ // Perform an embedder-initiated navigation that will fail.
+ const original_config = frame.config;
+ frame.config = new FencedFrameConfig("resources/response-204.py");
+ await step_timeout(() => {}, 1000);
+
+ // Check that the failed navigation didn't change the storage partition.
+ // (The partition nonce should be reinitialized on navigation commit.)
+ const inner_after_failure = await frame.execute(getter, [key]);
+ assert_equals(inner_after_failure, inner_value,
+ "The inner value is still present after the failed navigation.");
+
+ // Refresh the fenced frame from within.
+ await frame.execute(() => {
+ window.executor.suspend(() => { location.href = location.href; });
+ });
+
+ // Check that the storage partition is the same.
+ const inner_after_inner_refresh = await frame.execute(getter, [key]);
+ assert_equals(inner_after_inner_refresh, inner_value,
+ "The inner value is the same after a fencedframe-initiated refresh.");
+
+ // Refresh the fenced frame from the embedder.
+ await frame.execute(() => window.executor.suspend(() => {}));
+ frame.element.config = original_config;
+
+ // Check that there is a blank slate.
+ const inner_after_embedder_refresh = await frame.execute(getter, [key]);
+ assert_equals(inner_after_embedder_refresh, null,
+ "The inner value is gone after an embedder-initiated refresh.");
+}
+
+promise_test(async () => {
+ return runTest(
+ (_) => { return document.cookie || null; },
+ (_, value) => { document.cookie = value;}
+ );
+}, 'document.cookie');
+
+promise_test(async () => {
+ return runTest(
+ (key) => { return localStorage.getItem(key); },
+ (key, value) => { return localStorage.setItem(key, value); }
+ );
+}, 'localStorage');
+
+promise_test(async () => {
+ return runTest(
+ (key) => { return sessionStorage.getItem(key); },
+ (key, value) => { return sessionStorage.setItem(key, value); }
+ );
+}, 'sessionStorage');
+
+promise_test(async () => {
+ return runTest(
+ async (key) => {
+ const newCache = await caches.open('test-cache');
+ const response = await newCache.match(key);
+ if (!response) {
+ return null;
+ }
+ return response.text();
+ },
+ async (key, value) => {
+ const newCache = await caches.open('test-cache');
+ return newCache.put(key, new Response(value));
+ }
+ );
+}, 'Cache API');
+
+promise_test(async () => {
+ return runTest(
+ async (key) => {
+ const root = await navigator.storage.getDirectory();
+ const draftHandle = await root.getFileHandle(key, { create: true });
+ const file = await draftHandle.getFile();
+ const text = await file.text();
+ return text || null;
+ },
+ async (key, value) => {
+ const root = await navigator.storage.getDirectory();
+ const draftHandle = await root.getFileHandle(key, { create: true });
+ const writable = await draftHandle.createWritable()
+ await writable.truncate(0);
+ await writable.write(value);
+ await writable.close();
+ }
+ );
+}, 'File System Access API');
+
+promise_test(async () => {
+ return runTest(
+ async (key) => {
+ const openRequest = indexedDB.open('test-db', 2);
+ const db = await new Promise((resolve) => {
+ openRequest.onsuccess = (event) => {
+ resolve(event.target.result);
+ };
+ openRequest.onupgradeneeded = (event) => {
+ const db = event.target.result;
+ const objStore = db.createObjectStore('test-tbl', {keyPath: 'key'});
+ objStore.transaction.oncomplete = (event) => {
+ resolve(db);
+ };
+ };
+ });
+ const readRequest = db.transaction(['test-tbl'])
+ .objectStore('test-tbl')
+ .get(key);
+ return new Promise((resolve) => {
+ readRequest.onsuccess = (event) => {
+ if (!event.target.result) {
+ resolve(null);
+ } else {
+ resolve(event.target.result['value']);
+ }
+ };
+ });
+ },
+ async (key, value) => {
+ const openRequest = indexedDB.open('test-db', 2);
+ const db = await new Promise((resolve) => {
+ openRequest.onsuccess = (event) => {
+ resolve(event.target.result);
+ };
+ openRequest.onupgradeneeded = (event) => {
+ const db = event.target.result;
+ const objStore = db.createObjectStore('test-tbl', {keyPath: 'key'});
+ objStore.transaction.oncomplete = (event) => {
+ resolve(db);
+ };
+ };
+ });
+ const writeRequest = db.transaction(['test-tbl'], 'readwrite')
+ .objectStore('test-tbl')
+ .add({'key': key, 'value': value});
+ return new Promise((resolve) => {
+ writeRequest.onsuccess = (event) => {
+ resolve(event.target.result);
+ };
+ });
+ }
+ );
+}, 'IndexedDB');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/subframe-loading.https.html b/testing/web-platform/tests/fenced-frame/subframe-loading.https.html
new file mode 100644
index 0000000000..758bdd87fa
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/subframe-loading.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<title>Test Subframe Loading Disabler</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+<script>
+
+promise_test(async () => {
+ const container = document.body.appendChild(document.createElement("div"));
+
+ // Create a helper iframe that triggers loading the new fenced frame
+ // simultaneously when the parent container is removed.
+ const helperFrame = container.appendChild(document.createElement("iframe"));
+
+ helperFrame.contentWindow.onunload = function() {
+ const fenced_frame = document.createElement("fencedframe");
+ fenced_frame.src = "resources/dummy.https.html";
+ container.appendChild(fenced_frame);
+ };
+
+ // If the fenced frame loads when it's not supposed to,
+ // a DCHECK will catch that and cause the test to crash.
+ document.body.removeChild(container);
+
+}, "Fenced frames should not load if its parent has subframe loading disabled");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/fenced-frame/unique-cookie-partition.https.html b/testing/web-platform/tests/fenced-frame/unique-cookie-partition.https.html
new file mode 100644
index 0000000000..8ecd56ffc3
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/unique-cookie-partition.https.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<title>Test cookies accessed from a Fenced Frame Tree</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+<script>
+const cookie_value_key = token();
+const kAssertion = "Cookie accessed from unique fenced frame tree partition ";
+const kAssertion_outer = "Cookie values changed in the fenced frame tree should not impact the outer frame";
+
+async function runTest(test_type) {
+ document.cookie = 'A=outer; SameSite=Lax';
+ document.cookie = 'B=outer; SameSite=None; Secure';
+ const fenced_frame =
+ attachFencedFrame(generateURL(
+ `resources/unique-cookie-partition-inner.https.html`,
+ [cookie_value_key, test_type]));
+
+ result = await nextValueFromServer(cookie_value_key);
+ switch (test_type) {
+ case "top-level fenced frame":
+ assert_equals(result, "F=fenced; C=fenced; D=fenced; E=fenced", kAssertion + test_type);
+ break;
+ case "nested iframe":
+ assert_equals(result, "F=fenced; C=fenced; D=fenced; G=nested_in_fenced_frame; E=fenced", kAssertion + test_type);
+ break;
+ case "nested fenced frame":
+ assert_equals(result, "G=nested_in_fenced_frame", kAssertion + test_type);
+ break;
+ }
+
+ // The cookie values changed in the fenced frame tree should not impact the outer frame.
+ const result_outer_frame = document.cookie;
+ assert_equals(result_outer_frame, "A=outer; B=outer", kAssertion_outer);
+
+ // Clean up the fenced frame
+ document.body.removeChild(fenced_frame);
+}
+
+promise_test(async () => {
+ return runTest("top-level fenced frame");
+}, "Cookie access from top-level fenced frame");
+
+promise_test(async () => {
+ return runTest("nested iframe");
+}, "Cookie access from iframe nested in a fenced frame");
+
+promise_test(async () => {
+ return runTest("nested fenced frame");
+}, "Cookie access from nested fenced frame");
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/user-activation.https.html b/testing/web-platform/tests/fenced-frame/user-activation.https.html
new file mode 100644
index 0000000000..3ca0dca49b
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/user-activation.https.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<title>Test that user activation propagation is fenced.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+// Simulate a click in frame context `frame`.
+async function click(frame) {
+ var actions = new test_driver.Actions();
+ await actions.pointerMove(0, 0, {origin: frame})
+ .pointerDown()
+ .pointerUp()
+ .send();
+}
+
+assert_activation = (should_be_active, frame_name) => {
+ if (should_be_active) {
+ assert_true(navigator.userActivation.hasBeenActive,
+ frame_name + " has been activated.");
+ assert_true(navigator.userActivation.isActive,
+ frame_name + " is currently active.");
+ } else {
+ assert_false(navigator.userActivation.hasBeenActive,
+ frame_name + " has not been activated yet.");
+ assert_false(navigator.userActivation.isActive,
+ frame_name + " is not currently active.");
+ }
+};
+
+promise_test(async () => {
+ // This test ensures that user activations (e.g. click events) don't
+ // propagate across fenced frame boundaries. Specifically, activations
+ // are visible through the `navigator.userActivation` object.
+ //
+ // The layout of the page is as follows:
+ // A: top-level frame
+ // B: iframe
+ // C: fencedframe
+ // D: iframe
+ // E: fencedframe
+ //
+ // This order is chosen to test all kinds of fenced tree traversal. We:
+ // - Click in C and check that only C gets activated (not A, B, D, or E)
+ // - Click in A and check that only A, B, and D get activated (not E)
+ // - Click in B and D and check that E wasn't activated
+
+ const B = attachIFrameContext();
+ const C = attachFencedFrameContext();
+ const D = attachIFrameContext();
+ const E = attachFencedFrameContext();
+
+ // Define some helpers to check activations more concisely.
+ const frames = [[B, "B"], [C, "C"], [D, "D"], [E, "E"]];
+ const assert_activations = async (should_be_actives) => {
+ assert_equals(frames.length, should_be_actives.length);
+ for ([i, [frame, frame_name]] of frames.entries()) {
+ await frame.execute(assert_activation, [should_be_actives[i], frame_name]);
+ }
+ };
+
+ // Check that all the frames are inactive before we start.
+ assert_activation(false, "A");
+ await assert_activations([false/*B*/, false/*C*/, false/*D*/, false/*E*/]);
+
+ // Simulate a click in C (the first fenced frame).
+ await click(C.element);
+
+ // Check that only C has been activated.
+ assert_activation(false, "A");
+ await assert_activations([false/*B*/, true/*C*/, false/*D*/, false/*E*/]);
+
+ // Simulate a click in A (the top-level site).
+ await click(document.documentElement);
+
+ // Check that A, B, and D were activated.
+ assert_activation(true, "A");
+ await assert_activations([true/*B*/, true/*C*/, true/*D*/, false/*E*/]);
+
+ // Simulate a click in B and D (the two iframes).
+ await click(B.element);
+ await click(D.element);
+
+ // Check that E has still not been activated.
+ assert_activation(true, "A");
+ await assert_activations([true/*B*/, true/*C*/, true/*D*/, false/*E*/]);
+}, 'user-activation');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/visual-viewport.https.html b/testing/web-platform/tests/fenced-frame/visual-viewport.https.html
new file mode 100644
index 0000000000..7870f11e8c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/visual-viewport.https.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<title>Test visualViewport inside a fenced frame.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-actions.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+function pinch_zoom_in() {
+ return new test_driver.Actions()
+ .setContext(window)
+ .addPointer("finger1", "touch")
+ .addPointer("finger2", "touch")
+ .pointerMove(400, 250, {origin: "viewport", sourceName: "finger1"})
+ .pointerMove(400, 350, {origin: "viewport", sourceName: "finger2"})
+ .pointerDown({sourceName: "finger1"})
+ .pointerDown({sourceName: "finger2"})
+ .pointerMove(400, 200, {origin: "viewport", sourceName: "finger1"})
+ .pointerMove(400, 400, {origin: "viewport", sourceName: "finger2"})
+ .pointerUp({sourceName: "finger1"})
+ .pointerUp({sourceName: "finger2"})
+ .send();
+}
+
+promise_test(async () => {
+ // Create a fenced frame, and use the same target name inside of it.
+ const frame = attachFencedFrameContext({html: `
+ <!DOCTYPE html>
+ <style>
+ body {
+ /* Make fenced frame scrollable */
+ width: 200vw;
+ height: 200vh;
+ }
+
+ ::-webkit-scrollbar {
+ display: none;
+ }
+ </style>`});
+
+ const is_mac = navigator.platform.indexOf('Mac') == 0;
+
+ // Mac doesn't support pinch zooming via test driver so just avoid trying.
+ if (!is_mac) {
+ await pinch_zoom_in();
+
+ // Run the test zoomed in to ensure the fenced frame doesn't incorrectly
+ // bring values in from its embedder.
+ assert_greater_than(window.visualViewport.scale, 1,
+ '[PRECONDITION] outer window pinch-zoomed in');
+ }
+
+ await frame.execute(async (width, height) => {
+ window.scrollTo(30, 40);
+ assert_equals(window.scrollX, 30, '[PRECONDITION] document scrolled x');
+ assert_equals(window.scrollY, 40, '[PRECONDITION] document scrolled y');
+
+ assert_equals(window.visualViewport.width, width,
+ 'visualViewport.width matches fencedframe width');
+ assert_equals(window.visualViewport.height, height,
+ 'visualViewport.height matches fencedframe height');
+ assert_equals(window.visualViewport.scale, 1,
+ 'visualViewport.scale is 1');
+ assert_equals(window.visualViewport.offsetLeft, 0,
+ 'visualViewport.offsetLeft is 0');
+ assert_equals(window.visualViewport.offsetTop, 0,
+ 'visualViewport.offsetTop is 0');
+ assert_equals(window.visualViewport.pageLeft, window.scrollX,
+ 'visualViewport.pageLeft reflects only window scroll offset');
+ assert_equals(window.visualViewport.pageTop, window.scrollY,
+ 'visualViewport.pageTop reflects only window scroll offset');
+ }, [frame.clientWidth, frame.clientHeight]);
+
+}, 'visualViewport values inside fenced frame');
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/web-bluetooth.https.html b/testing/web-platform/tests/fenced-frame/web-bluetooth.https.html
new file mode 100644
index 0000000000..88bbd6ec37
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/web-bluetooth.https.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Test of Web Bluetooth API</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 src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async t => {
+ const bluetooth_request_device_key = token();
+
+ attachFencedFrame(generateURL('resources/web-bluetooth-inner.html',
+ [bluetooth_request_device_key]));
+ const result = await nextValueFromServer(bluetooth_request_device_key);
+
+ assert_equals(
+ result, 'Web Bluetooth requestDevice() failed',
+ 'Web Bluetooth requestDevice() must fail in a fenced frame.');
+}, 'Web Bluetooth requestDevice() must fail in a fenced frame');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/web-nfc.https.html b/testing/web-platform/tests/fenced-frame/web-nfc.https.html
new file mode 100644
index 0000000000..c7de9d81f9
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/web-nfc.https.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<title>Test Web NFC API</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async () => {
+ const ndef_write_key = token();
+ const ndef_scan_key = token();
+
+ attachFencedFrame(generateURL("resources/web-nfc-inner.https.html",
+ [ndef_write_key, ndef_scan_key]));
+
+ let result = await nextValueFromServer(ndef_write_key);
+ assert_equals(result, "rejected",
+ "The fenced frame is not allowed to NDEFReader.write().");
+ result = await nextValueFromServer(ndef_scan_key);
+ assert_equals(result, "rejected",
+ "The fenced frame is not allowed to NDEFReader.scan().");
+
+}, "Test Web NFC API");
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/web-share.https.html b/testing/web-platform/tests/fenced-frame/web-share.https.html
new file mode 100644
index 0000000000..13d182b2d1
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/web-share.https.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Test of Web Share</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 src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async t => {
+ const navigator_share_key = token();
+
+ attachFencedFrame(generateURL('resources/web-share-inner.html',
+ [navigator_share_key]));
+ const result = await nextValueFromServer(navigator_share_key);
+
+ assert_equals(
+ result, 'Web Share failed',
+ 'Web Share in a fenced frame must fail.');
+}, 'Web Sharelock in a fenced frame must fail');
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/web-usb.https.html b/testing/web-platform/tests/fenced-frame/web-usb.https.html
new file mode 100644
index 0000000000..3156f68ea7
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/web-usb.https.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>Test WebUSB navigator.usb.requestDevice()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ await frame.execute(async () => {
+ // Request USB access inside the fenced frame. It should fail.
+ // https://github.com/shivanigithub/fenced-frame#security-considerations.
+ try {
+ await navigator.usb.requestDevice({ filters: [{ vendorId: 0}] });
+ throw 'The USB request should not succeed.';
+ } catch (e) {
+ assert_equals(e.name, 'SecurityError');
+ assert_equals(e.message,
+ "Failed to execute 'requestDevice' on 'USB': " +
+ 'Access to the feature "usb" is disallowed by permissions policy.',
+ 'Fenced frame has the right error for usb.requestDevice.');
+ }
+ });
+}, 'navigator.usb.requestDevice');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/window-close.https.html b/testing/web-platform/tests/fenced-frame/window-close.https.html
new file mode 100644
index 0000000000..b581a0324c
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/window-close.https.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>Test window.close has no effect</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+<script>
+promise_test(async () => {
+ const frame = attachFencedFrameContext();
+ await frame.execute(async () => {
+ // This should have no effect for fenced frames.
+ window.close();
+ // window.closed will be true if the window closing steps have begun.
+ assert_false(window.closed);
+ });
+}, 'window.close');
+</script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/window-frameElement.https.html b/testing/web-platform/tests/fenced-frame/window-frameElement.https.html
new file mode 100644
index 0000000000..bd4532b1eb
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/window-frameElement.https.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>Test window.parent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async () => {
+ const frame_element_key = token();
+
+ attachFencedFrame(generateURL("resources/window-frameElement-inner.html",
+ [frame_element_key]));
+ const fenced_frame_result = await nextValueFromServer(frame_element_key);
+ assert_equals(fenced_frame_result, "PASS");
+}, "window.frameElement null for same-origin fenced frames");
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/window-navigation-204.https.html b/testing/web-platform/tests/fenced-frame/window-navigation-204.https.html
new file mode 100644
index 0000000000..6722060330
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/window-navigation-204.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<title>Test window.navigation.204</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/utils.js"></script>
+<script src="/common/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async () => {
+ const window_data_key = token();
+
+ attachFencedFrame(generateURL("resources/window-navigation-204-inner.html",
+ [window_data_key]));
+
+ const actual_result = await nextValueFromServer(window_data_key);
+ assert_equals(actual_result, "still in page",
+ "The fenced frame has the right value for `204 response` upon " +
+ "subsequent navigation resulting in HTTP 204");
+}, "window.navigation.204");
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/window-outer-dimensions.https.html b/testing/web-platform/tests/fenced-frame/window-outer-dimensions.https.html
new file mode 100644
index 0000000000..c6a64ef193
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/window-outer-dimensions.https.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<title>Test window.prompt</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+ <script>
+ async function runTest(dimension, extra_children) {
+ const window_outer_size_key = token();
+ const window_inner_size_key = token();
+
+ const window_outer_size_from_top_frame = (dimension == "width" ?
+ window.outerWidth : window.outerHeight).toString();
+
+ const frame_url = generateURL(
+ 'resources/window-outer-dimensions-inner.html',
+ [window_outer_size_key, window_inner_size_key, dimension, extra_children]);
+ const new_frame = attachFencedFrame(frame_url);
+
+ // Get the outer and inner width/height from either the top level
+ // fencedframe or the nested iframe.
+ const result_outer = await nextValueFromServer(window_outer_size_key);
+ const result_inner = await nextValueFromServer(window_inner_size_key);
+
+ assert_not_equals(result_outer,
+ window_outer_size_from_top_frame,
+ "Outer " + dimension + " not read in fenced frame"
+ + " tree.");
+
+ assert_equals(result_outer,
+ result_inner,
+ "Fencedframe's outer/inner " + dimension + " match.");
+ }
+
+ promise_test(async () => {
+ return runTest("width", 0);
+ }, "window.outerWidth");
+
+ promise_test(async () => {
+ return runTest("height", 0);
+ }, "window.outerHeight");
+
+ promise_test(async () => {
+ return runTest("width", 1);
+ }, "window.outerWidth nested iframe");
+
+ promise_test(async () => {
+ return runTest("height", 1);
+ }, "window.outerHeight nested iframe");
+ </script>
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/window-parent.https.html b/testing/web-platform/tests/fenced-frame/window-parent.https.html
new file mode 100644
index 0000000000..c2f3604050
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/window-parent.https.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<title>Test window.parent</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async () => {
+ const window_parent_key = token();
+ const window_parent_ack_key = token();
+
+ attachFencedFrame(generateURL("resources/window-parent-inner.html",
+ [window_parent_key, window_parent_ack_key]));
+
+ // Get the result for the top-level fenced frame.
+ const fenced_frame_result = await nextValueFromServer(window_parent_key);
+ assert_equals(fenced_frame_result, "pass: fenced frame", "The top-level " +
+ "fenced frame has the right value for " +
+ "`window.parent`");
+
+ // Write an ACK, so that the fenced frame knows it can send message over the
+ // `window_parent_key` channel again.
+ writeValueToServer(window_parent_ack_key, "ACK");
+
+ // Get the result for the iframe inside the fenced frame.
+ const iframe_in_fenced_frame_result = await nextValueFromServer(window_parent_key);
+ assert_equals(iframe_in_fenced_frame_result, "pass: fenced frame > iframe",
+ "The iframe inside the top-level fenced frame has the right " +
+ "value for `window.parent`");
+
+ writeValueToServer(window_parent_ack_key, "ACK");
+
+ // Get the result for the nested fenced frame.
+ const nested_fenced_frame_result = await nextValueFromServer(window_parent_key);
+ assert_equals(nested_fenced_frame_result, "pass: fenced frame > fenced frame",
+ "The nested fenced frame inside the top-level fenced frame " +
+ "has the right value for `window.parent`");
+}, "window.parent");
+</script>
+
+</body>
diff --git a/testing/web-platform/tests/fenced-frame/window-top.https.html b/testing/web-platform/tests/fenced-frame/window-top.https.html
new file mode 100644
index 0000000000..9a8f39f08b
--- /dev/null
+++ b/testing/web-platform/tests/fenced-frame/window-top.https.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<title>Test window.top</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/utils.js"></script>
+<script src="resources/utils.js"></script>
+
+<body>
+
+<script>
+promise_test(async () => {
+ const window_top_key = token();
+ const window_top_ack_key = token();
+
+ attachFencedFrame(generateURL("resources/window-top-inner.html",
+ [window_top_key, window_top_ack_key, "not nested"]));
+
+ // Get the result for the top-level fenced frame.
+ const fenced_frame_result = await nextValueFromServer(window_top_key);
+ assert_equals(fenced_frame_result, "pass: fenced frame", "The top-level " +
+ "fenced frame has the right value for " +
+ "`window.top`");
+
+ // Write an ACK, so that the fenced frame knows it can send message over the
+ // `window_top_key` channel again.
+ writeValueToServer(window_top_ack_key, "ACK");
+
+ // Get the result for the iframe inside the fenced frame.
+ const iframe_in_fenced_frame_result = await nextValueFromServer(window_top_key);
+ assert_equals(iframe_in_fenced_frame_result, "pass: fenced frame > iframe",
+ "The iframe inside the top-level fenced frame has the right " +
+ "value for `window.top`");
+
+ writeValueToServer(window_top_ack_key, "ACK");
+
+ // Get the result for the nested fenced frame.
+ const nested_fenced_frame_result = await nextValueFromServer(window_top_key);
+ assert_equals(nested_fenced_frame_result, "pass: fenced frame > fenced frame",
+ "The nested fenced frame inside the top-level fenced frame " +
+ "has the right value for `window.top`");
+}, "window.top");
+</script>
+
+</body>