| # Fenced Frame: Guidelines for Feature Behavior |
| |
| Fenced frames are a new HTML element that allows a page to be embedded in |
| another page while preventing any information from being exchanged between the |
| two pages. To maintain this “fence” between a fenced frame and its embedder, any |
| new features need to consider how they should behave inside a fenced frame. |
| Specifically, should they treat the fenced frame as the top-most frame? Or |
| should the feature be allowed to traverse past the fenced frame to get |
| information about its embedder? |
| |
| |
| ### Decision chart |
| |
| The decision chart below is a basic guide to determine how a fenced frame should |
| behave when using a feature: |
| |
|  |
| |
| Below are more details for each node: |
| |
| |
| ### Does this feature need to access the outer frame tree to work properly? |
| |
| Some features need to interact with either a frame’s immediate ancestor, the |
| nearest main frame in the frame tree, or the outermost main frame in the frame |
| tree. |
| |
| * User activation needs to have access to a frame’s ancestors (and their |
| ancestors) to give all of them user activation. However, the feature will |
| not break if it can’t cross the fenced frame boundary to a frame in the |
| outer frame tree. |
| * `window.top` needs to get access to the nearest main frame in the frame tree |
| (the top-most frame as far as the context is concerned). Even though the |
| behavior will be different than if it were to get the outermost main frame, |
| it does not fundamentally break the functionality of this feature. |
| * Accessibility trees use the outermost main frame as the root to build the |
| entire accessibility tree from. If a frame in a fenced frame tree doesn’t |
| have access to the outermost main frame, then the accessibility tree won’t |
| be built properly and accessibility functionality will break. |
| |
| If your feature only needs the immediate ancestor or the nearest main frame, |
| then fenced frames should **act as a main frame** with your feature.. |
| |
| If you determine that your feature needs a fenced frame to access the outer |
| frame tree (i.e. **act as an iframe**), you must ensure that no information can |
| leak across the fenced boundary. If any information can leak across the |
| boundary, the feature must be disabled. If the feature cannot be completely |
| disabled, a new approach might need to be formulated (e.g. the ongoing work |
| regarding intersection observer API). |
| |
| |
| ### Will it be possible for the web platform to access this information? |
| |
| As a direct result of your feature, would you be able to write some JavaScript |
| code whose output changes based on the information your feature knows about |
| ancestors in the embedder frame tree? If you decide that your feature needs to |
| give a fenced frame access to information about its ancestors, this next check |
| is very important. You should ensure that the information gathered from an outer |
| frame tree does not end up in a place where it can be observed on the web |
| platform (i.e. through JavaScript). If it is possible for information to cross |
| the fenced frame boundary in a web-observable way, and there’s no way to patch |
| it, the feature must be disabled in fenced frames. |
| |
| If you can guarantee that your information is |
| [k-anonymized](https://github.com/WICG/turtledove/blob/main/FLEDGE_k_anonymity_server.md) |
| through something like |
| [FLEDGE](https://github.com/WICG/turtledove/blob/main/FLEDGE.md), or scrubbed |
| via something like link decoration mitigation, even though information is |
| flowing across the fenced frame boundary, it might be anonymized enough to allow |
| the feature. |
| |
| |
| ### Act as main frame. |
| |
| Unless the feature needs access to something outside of a fenced frame, this is |
| how fenced frames will most likely act with your feature. This means that the |
| feature will think that the fenced frame root is the root of the entire frame |
| tree. This also means that accidentally leaking data across the fenced frame |
| boundary is a lot less likely. |
| |
| This is accomplished by calling helper functions like |
| `RenderFrameHostImpl::GetParent()` and `RenderFrameHostImpl::GetMainFrame()`. |
| |
| * In `RenderFrameHostImpl::SetWindowRect`, there’s a check to make sure the |
| call came from the outermost document. Because fenced frames are meant to |
| create a boundary and be their own root frame, having the fenced frame **act |
| as a main frame** is the correct behavior here. It does not need to know |
| that there is another frame above it, since it should be acting as if it |
| were its own separate tab. |
| * The `window.top` JavaScript call, if it could reach beyond the fenced frame |
| root, could allow the fenced frame to learn about its embedder. Having |
| `window.top` stop at the fenced frame root will not completely break the |
| feature, since it will just be acting as if the fenced frame were its own |
| separate tab. Because of that, the fenced frame should **act as the main |
| frame** for this call. |
| * The same logic applies for other calls like `window.parent`, |
| `window.postMessage`, and `history.length`. |
| |
| |
| ### Act as iframe. |
| |
| This feature will be made aware of frames above the fenced frame root, and know |
| that there is an outermost root that is not within the fenced frame tree. This |
| path requires extra care, since it becomes possible to have a corner case |
| accidentally leak data across the fenced frame boundary. |
| |
| This is accomplished by calling helper functions like |
| `RenderFrameHostImpl::GetParentOrOuterDocument()` and |
| `RenderFrameHostImpl::GetOutermostMainFrame()`. |
| |
| * The accessibility tree feature assumes that there is one root frame that |
| everything else branches off of. This is needed to build the accessibility |
| tree, which must have only one root and be able to see everything. That |
| information is exposed to screen readers and other accessibility features, |
| but is never given directly to the web platform. In this case, it is okay |
| for the fenced frame to **act as an iframe**. |
| * When passing focus between frames after the user tab-focuses (switches focus |
| by hitting the tab key), a child fenced frame needs to know what its parent |
| is in order to pass focus off to it. If it’s not allowed to know, it will |
| only be able to pass focus to child frames. The web platform never learns |
| who sent focus to it. That information stays solely in the browser, and all |
| the web platform learns is that it now has focus. Because of that, it is |
| okay for the fenced frame to **act as an iframe**. |
| * Prerendering uses the root frame tree node’s ID as the key to find the |
| pre-rendered page. If it attempted to pass in the ID of the fenced frame |
| node instead, it would not be able to find the pre-rendered page. In this |
| case, the fenced frame would need to **act as an iframe**. |
| * Extensions are a special case. By default, they have access to everything in |
| every tab. For ad blocking extensions specifically, they need to be able to |
| see inside of a fenced frame to know whether the URL being loaded is an ad |
| or not (not breaking content blockers is an invariant of the fenced frame |
| design). Therefore, a fenced frame needs to **act as an iframe** so the ad |
| blocker can cross the fence and work as expected. [See the integration with |
| extensions explainer document for more |
| details](https://github.com/WICG/fenced-frame/blob/master/explainer/integration_with_web_platform.md#extensions). |
| |
| |
| ### Disable in fenced frames. |
| |
| If a feature needs to know about something outside of a fenced frame to function |
| properly, but it’s impossible to expose that information without introducing a |
| leak, the only option is to disallow the feature inside a fenced frame entirely. |
| |
| * [Permissions policies](https://www.w3.org/TR/permissions-policy-1/) inherit |
| from a frame’s parent. For cases where the url loaded in the fenced frame is |
| not set by the embedder, we need to make sure other ways of communicating |
| information are restricted. By delegating permissions, a fenced frame is |
| allowed to learn what its parent’s permissions policy is (either through |
| their headers or through the allow=”” attribute in the frame object), which |
| opens the door to fingerprinting. Tests can easily be written in JavaScript |
| to determine whether a policy is enabled or disabled. So, to prevent that, |
| inheritance needs to be **disabled in a fenced frame**. |
| * With the opaque ads configuration design, setting permissions policies |
| [will be allowed in a fenced |
| frame](https://docs.google.com/document/d/11QaI40IAr12CDFrIUQbugxmS9LfircghHUghW-EDzMk/edit#heading=h.1mqtrutx4yv4). |
| However, the actual policies will be checked by a [k-anonymity |
| check](https://github.com/WICG/turtledove/blob/main/FLEDGE_k_anonymity_server.md), |
| and the URL will not win the FLEDGE auction and not load in the frame if |
| it fails the check. This has the effect of decoupling this information |
| from the parent frame. While the fenced frame will learn about the |
| permissions it was allowed to be created in, it won’t know which ones |
| came from the page and which ones came from FLEDGE, and they are |
| guaranteed to be k-anonymous enough for fingerprinting to not work. |
| * Modifying parameters in a fenced frame (such as the dimensions of the frame |
| or its mode) is an easy way for an embedding page to pass information |
| through the fence. Modifying parameters requires information to be passed |
| from an embedding page into the fenced frame, but as soon as a parameter is |
| modified, the fenced frame can easily access it. So, modifying parameters |
| needs to be **disabled in a fenced frame**. |
| * In the case of resizing a fenced frame, we do allow the resizing call to |
| go through. However, this only resizes the outer frame in a fenced |
| frame. The inner frame retains its original dimensions, but is visually |
| scaled to fit in the new outer frame. As far as the page embedded in the |
| fenced frame is concerned, its dimensions did not change. |
| * Navigating a fenced frame to a URL like a “javascript:” url could allow |
| arbitrary data to flow across the fence. So, this is **disabled in a fenced |
| frame**. |
| |
| |
| ## Writing Tests |
| |
| There are 2 instances where you will need to write tests to ensure your feature |
| works as expected with fenced frames: |
| |
| * If it is possible for the **web platform to gain access to information** |
| from your feature (regardless of whether you **disable the feature** or have |
| fenced frames **act as a main frame**), then you will need to write a test |
| to make sure that the information can’t be leaked across a fenced frame |
| boundary. |
| * If you are disabling your feature, the test can be as simple as making |
| sure the feature doesn’t work inside a fenced frame. |
| * If you are having your feature **act as a main frame**, the test should |
| ensure that your feature is not introducing information to a fenced |
| frame that changes if the conditions of the embedding frame change. |
| * For example, `window.top` works inside a fenced frame, but it just |
| returns the fenced frame root. To ensure that this is working |
| correctly and not actually returning the outermost main frame, there |
| should be [a test confirming this |
| behavior](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/web_tests/wpt_internal/fenced_frame/window-top.https.html). |
| * If you are having a fenced frame act as an iframe for your feature, you will |
| need to write a test to ensure that the feature is working properly inside a |
| fenced frame. This test can be as simple as triggering the feature in a |
| fenced frame and verifying the output is what you expect it to be. |
| |
| There is already infrastructure set up to help you write fenced frames tests. |
| |
| * For **web platform tests**, there is a [directory for fenced frame |
| tests](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/web_tests/wpt_internal/fenced_frame/). |
| In it, you’ll find [a utils file with helper |
| functions](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/web_tests/wpt_internal/fenced_frame/resources/utils.js) |
| to speed up the test writing process, as well as other tests you can use as |
| inspiration. |
| * For **browser tests**, there is a [fenced frame test helper |
| file](https://source.chromium.org/chromium/chromium/src/+/main:content/public/test/fenced_frame_test_util.h) |
| that serves the same purpose. For inspiration, the |
| [fenced\_frame\_browsertest.cc](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/fenced_frame/fenced_frame_browsertest.cc) |
| file has a lot of tests that can be used as a starting point. There are |
| [fenced frame tests in other |
| files](https://source.chromium.org/search?q=fencedframe%20file:test.cc$&start=1) |
| that you can also use. |
| * For **unit tests**, the `RenderFrameHostTester` class has an |
| [AppendFencedFrame](https://source.chromium.org/chromium/chromium/src/+/main:content/public/test/test_renderer_host.h;l=153-156;drc=aaea55c708c63d53a89fb525484aa94747599714) |
| function. However, double check the file you’re adding a test to, since it |
| might already have [helper functions in its testing |
| class](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/media/media_engagement_contents_observer_unittest.cc;l=1423;drc=c94a0d209dee1da75c4131360b75702a8245dd5c) |
| to create fenced frames. |