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