chromium/content/browser/fenced_frame/README.md

# 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:

![drawing](https://docs.google.com/drawings/d/1BFcncZZVogJXnhccqKnE9y23yf42f_AMamwVpGSlLlU/export/png)

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.