chromium/docs/accessibility/browser/offscreen.md

# Offscreen, Invisible and Size

This document explains how Chrome interprets the guidelines to apply the labels
Offscreen and Invisible to nodes, and how the bounding box is calculated.

## Background

In general, screen reading tools may be interested in all nodes regardless of
whether they are presented to sighted users, but other Accessibility tools may
care what is visible to sighted users.

Specifically, tools like Select-to-Speak and Switch Access should not look at
nodes which are “offscreen”, “invisible”, or size=(0,0), as these are not
visible on the screen for mouse interactions. On the other hand, ChromeVox and
other screen readers may care about some of those nodes, which allow developers
to insert buttons visible only to users with a screen reader, or to navigate
below the fold.

## Offscreen
In Chrome, we define Offscreen as:

>Any object is offscreen if it is fully clipped or scrolled out of view by any
of its ancestors so that it is not rendered on the screen.

For example, the staticText node here is offscreen:
```html
<div style="width:0; height; 0; overflow: hidden">
  This text should be marked "offscreen", although its parent is not.
</div>
```

As background, [MSDN](https://msdn.microsoft.com/en-us/library/dd373609(VS.85).aspx)
defines Offscreen as an object is not rendered, but not because it was
programmatically hidden:

>The object is clipped or has scrolled out of view, but it is not
programmatically hidden. If the user makes the viewport larger, more of the
object will be visible on the computer screen.

In Chrome, we interpret this to mean that an object is fully clipped or
scrolled out of view by its parent or ancestors. The main difference is that
we are being explicit that any ancestor clipping a node can make it offscreen,
not just a rootWebArea or scrollable ancestor.

### Technical Implementation
Offscreen is calculated in [AXTree::RelativeToTreeBounds](https://cs.chromium.org/chromium/src/ui/accessibility/ax_tree.cc).
In this function, we walk up the accessibility tree adjusting a node's bounding
box to the frame of its ancestors. If the box is clipped because it lies
outside an ancestor's bounds, and that ancestor clips its children (i.e.
overflow:hidden, overflow:scroll, or it is a rootWebArea), offscreen is set to
true.

## Invisible
A node is marked Invisible in Chrome if it is hidden programmatically. In some
cases, invisible nodes are simply excluded from the accessibility tree. Chrome
defines invisible as:

>Invisible means that a node or its ancestor is explicitly invisible.

This is the same as the definition from [MSDN](https://msdn.microsoft.com/en-us/library/dd373609(VS.85).aspx):

>The object is programmatically hidden.

For example, these nodes are invisible:

```html
<div style="display:none">
  This text should be marked 'invisible', along with its parent div.
</div>

<div style="visibility:hidden">
  This text should also be marked 'invisible' along with its parent div.
</div>
```

### Technical implementation
See `AXObject::IsVisible()` ([source](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/accessibility/ax_object.cc)).

Note: `Opacity: 0` is explicitly not treated as invisible.

## Bounding box calculation
A node's bounding box (location and size) are calculated based on its
intrinsic width, height and location, and the sizes of its ancestors.
We calculate size clipped by ancestors by default, but can also expose an
unclipped size through the [automation API](https://developer.chrome.com/extensions/automation).

The unclipped bounding box is helpful if you want to know the current
coordinates of an element that's scrolled out of view, so you know how
much to scroll to bring it in view.

The clipped bounding box is helpful if you want to draw a visible bounding
box around the element on the screen. Clipping the bounding box helps
sighted users understand what container the element is in, even if it's
not currently visible. Without clipping you end up with elements floating
outside of windows.

### Technical implementation
A node's location and size are calculated in[AXTree::RelativeToTreeBounds](https://cs.chromium.org/chromium/src/ui/accessibility/ax_tree.cc),
and so closely tied to the offscreen calculation. In this function, we walk up
the accessibility tree adjusting a node's bounding box to the frame of its
ancestors.

In general, this calculation is straight forward. But there are several edge
cases:

* If a node has no intrinsic size, its size will be taken from the union of
its children.

```html
    <!-- The outer div here has no size, so we use its child for its bounding box -->
    <div style="visibility:hidden" aria-hidden=false>
      <div style="visibility:visible">
        Visible text
      </div>
    </div>
```

* If a node still has no size after that union, its bounds will be set to the
size of the nearest ancestor which has size. However, this node will be marked
`offscreen`, because it isn't visible to the user.

    * This is useful for exposing nodes to screen reader users.

In addition, [AXTree::RelativeToTreeBounds](https://cs.chromium.org/chromium/src/ui/accessibility/ax_tree.cc)
is used to determine how location and size may be clipped by ancestors,
allowing bounding boxes to reflect the location of a node clipped to its
ancestors.

* If a node is fully clipped by its ancestors such that the intersection of its
bounds and an ancestor's bounds are size 0, it will be pushed to the nearest
edge of the ancestor with width 1 or height 1.

    * We use width and height 1 instead of 0 to distinguish between empty or
    unknown sized nodes vs known small or clipped sized nodes.

Both clipped and unclipped location and size are exposed through the
[Chrome automation API](https://developer.chrome.com/extensions/automation).

* `location` is the global location of a node as clipped by its ancestors. If
a node is fully scrolled off a rootWebArea in X, for example, its location will
be the height of the rootWebArea, and its height will be 1. The Y position and width will be unchanged.

* `unclippedLocation` is the global location of a node ignoring any clipping
by ancestors. If a node is fully scrolled off a rootWebArea in X, for example,
its location will simply be larger than the height of the rootWebArea, and its
size will also be unchanged.