diff options
Diffstat (limited to 'accessible/docs')
-rw-r--r-- | accessible/docs/Architecture.md | 65 | ||||
-rw-r--r-- | accessible/docs/ColorsAndHighContrastMode.md | 48 | ||||
-rw-r--r-- | accessible/docs/DocumentAccessibilityLifecycle.md | 104 | ||||
-rw-r--r-- | accessible/docs/GeckoViewThreadTopography.md | 51 | ||||
-rw-r--r-- | accessible/docs/index.rst | 15 |
5 files changed, 283 insertions, 0 deletions
diff --git a/accessible/docs/Architecture.md b/accessible/docs/Architecture.md new file mode 100644 index 0000000000..b74c007787 --- /dev/null +++ b/accessible/docs/Architecture.md @@ -0,0 +1,65 @@ +# Architecture + +This document provides a high-level overview of how the accessibility code is structured. See [the Document Accessibility Lifecycle page](DocumentAccessibilityLifecycle.md#docaccessible-creation) for a lower level description of the code. + +## Process Model +The accessibility component spans multiple processes. In the parent process, it creates an accessibility tree of the Firefox UI and responds to requests from screen readers. In content processes, the accessibility component creates accessibility trees from web content. + +To respond to screen reader requests quickly, the accessibility tree from each content process is cached in the parent process. + +## Accessibility Trees +Accessibility trees can carry different kinds of information: informally, there are "local trees" that represent a document in the current process and "remote trees" that mirror a local tree created in a separate process. + +A local tree can only contain nodes in the current process, i.e. you can visit any node in the tab document and its in-process iframes. However, out-of-process iframes appear as a separate local tree in a different process. + +A remote tree, on the other hand, unifies these trees: you can visit any node in the tab document and both its in-process and out-of-process iframes. + +This means there are multiple accessibility trees for a single tab: one local tree in the content process, one local tree for each out-of-process iframe, and one remote tree in the parent process that mirrors these local trees. The Firefox UI is represented by a single local tree in the parent process. + +### Tree Nodes +An accessibility tree is composed of nodes represented by the `Accessible` class and its subtypes. Below is an example local accessibility tree from [example.com](https://example.com), as printed by `a11y::logging::Tree` (unfortunately, without type information): + +<!-- This isn't very accessible, at least in VoiceOver on Safari: VoiceOver only navigates between each word and I'm not sure if it's even possible to skip the whole block. Ideally, we can improve it. --> +``` +A11Y TREE: Initial subtree; 44:14.388 + { + : 0x107077a00; role: document, name: 'Example Domain', idx: 0, node: 0x105f84800, #document + : 0x105fb8b30; role: heading, name: 'Example Domain', idx: 0, node: 0x107b048b0, h1 + : 0x105fb8c90; role: text leaf, name: 'Example Domain', idx: 0, node: 0x107b05600, #text + : 0x105fb8d40; role: paragraph, idx: 1, node: 0x107b04940, p + : 0x105fb8df0; role: text leaf, name: 'This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.', idx: 0, node: 0x107b05700, #text + : 0x105fb8ea0; role: paragraph, idx: 2, node: 0x107b049d0, p + : 0x107922030; role: link, name: 'More information...', idx: 0, node: 0x107b030e0, a [ href="https://www.iana.org/domains/example" ] + : 0x1079220e0; role: text leaf, name: 'More information...', idx: 0, node: 0x107b05800, #text + } +``` + +<!-- Accessible is not in a code block because VoiceOver (on Safari) will not read full paragraphs if they start with <code> for an unknown reason. --> +Accessible has a direct subtype for different kinds of accessibility trees: `LocalAccessible` for nodes of local trees and `RemoteAccessibleBase` for nodes of remote trees. For example, `LocalAccessible` can be used in content processes for web content and in the parent process for the Firefox UI. The descendants of these two types diverge. + +<!-- LocalAccessible is intentionally not in a code block: see above. --> +LocalAccessible's direct descendant is `AccessibleWrap`. By convention, a class that ends in `Wrap` is a platform-specific implementation so `AccessibleWrap` contains the platform-specific implementations of `Accessible` and `LocalAccessible`. `AccessibleWrap's` direct and indirect subtypes are representations of HTML and XUL nodes such as `HTMLButtonAccessible` and `HTMLListBulletAccessible`. The Document and the root node of the accessibility tree are also represented by the `Accessible` class: `DocAccessible` and `DocAccessibleWrap` as well as `RootAccessible` and `RootAccessibleWrap` extend `AccessibleWrap`. + +<!-- RemoteAccessibleBase is intentionally not in a code block: see above. --> +RemoteAccessibleBase doesn’t have such an extensive type hierarchy. Its primary descendant is `DocAccessibleParent` which is the Document node of a remote tree located in the parent process: its local tree counterpart in a content process is `DocAccessible`. + +Below is a graph that displays the same relationships described above. In the graph, solid lines represent direct descendants while dotted lines represent indirect descendants: +```{mermaid} +flowchart TD +accTitle: Graph of the class hierarchy described above +Accessible --> LocalAccessible[LocalAccessible: local tree] & RemoteAccessibleBase[RemoteAccessibleBase: remote tree] +LocalAccessible --> AccessibleWrap[AccessibleWrap: platform-specific implementation] +AccessibleWrap -.-> DocAccessible & HTMLButtonAccessible & HTMLListBulletAccessible +DocAccessible --> DocAccessibleWrap --> RootAccessible --> RootAccessibleWrap +RemoteAccessibleBase -.-> DocAccessibleParent +``` + +### Platform-Specific Behavior +Accessibility trees differ by platform. The platform-independent tree, composed of types like `LocalAccessible` and `RemoteAccessibleBase`, is marshalled into a platform-specific tree that makes it easier to implement the platform's accessibility API. The platform tree is composed of the following node types: +- Windows: [MsaaAccessible] and [ia2Accessible] +- macOS: [mozAccessible] +- Linux: [ATKObjects and MaiAtkObjects](https://searchfox.org/mozilla-central/rev/d7d2cc647772de15c4c5aa47f74d25d0e379e404/accessible/atk/nsMai.h#87) + +[MsaaAccessible]: https://searchfox.org/mozilla-central/rev/d7d2cc647772de15c4c5aa47f74d25d0e379e404/accessible/windows/msaa/MsaaAccessible.h +[ia2Accessible]: https://searchfox.org/mozilla-central/rev/d7d2cc647772de15c4c5aa47f74d25d0e379e404/accessible/windows/ia2/ia2Accessible.h#21 +[mozAccessible]: https://searchfox.org/mozilla-central/rev/d7d2cc647772de15c4c5aa47f74d25d0e379e404/accessible/mac/mozAccessible.mm diff --git a/accessible/docs/ColorsAndHighContrastMode.md b/accessible/docs/ColorsAndHighContrastMode.md new file mode 100644 index 0000000000..1834da885a --- /dev/null +++ b/accessible/docs/ColorsAndHighContrastMode.md @@ -0,0 +1,48 @@ +# Colors and High Contrast Mode +Firefox offers several customisations to improve the accessibility of colors used to render web content and Firefox chrome. This document describes the customisation options available and their behaviour across platforms. It also describes how these options interact with one another. It is intended for developer reference :) + +## The Colors Dialog +In `about:preferences > Language and Appearance`, you'll find a button labelled "Colors". This button launches the colors dialog, which contains all of our user-facing color customisation options, as well as stylistic customisations like link underlining. This dialog also contains the select that controls what we'll refer to as "Firefox High Contrast Mode", or FF HCM. FF HCM can be enabled "Always", "Never", or "Only with High Contrast Themes". +> Note: FF HCM only affects web content, so changing the option in this select will only alter color usage for web pages. It will not change FF chrome. Current behaviour on chrome pages (ie. `about:` pages) is undefined. + +### User-customisable Colors +Users can choose to override background color, foreground color, visited link color, and/or unvisited link color by selecting a new color from the color inputs in the dialog. Modifications to these colors are stored in their corresponding user preference: +- `browser.background_color` +- `browser.foreground_color` +- `browser.visited_color` +- `browser.anchor_color` + +### Color Usage and System Colors +Before we render any Firefox/web content, we need to select a color palette to render that content _with_. We don't always use the colors a user has selected in the colors dialog. In fact, there are three different sets of colors we can use to style Firefox and/or web content: +- Stand-in colors +- System colors +- Colors-dialog colors + +> Note: Web pages may supply their own style sheets, which override a user's chosen color palette. When FF HCM is set to "Always", or set to "With High Contrast Themes" and OS HCM is enabled, the chosen color palette is _forced_, meaning it cannot be overridden by web pages. FF HCM and OS HCM do not directly change the way a color palette is chosen, but they _do_ change how the color palette is used. + +We decide which set of colors to use in `PreferenceSheet::Load`. If `resistFingerprinting` is enabled, we use stand-in colors. These colors are pre-defined constants and are not dynamically fetched from the operating system. Check out `nsXPLookAndFeel::GetStandinForNativeColor` for more information, as well as the constants themselves. + +If we aren't using stand-in colors, we'll check `browser.display.use_system_colors`, which is set from the "Use system colors" checkbox in the colors dialog. If that pref is true, we'll use system colors to style web content and Firefox chrome. + +System colors are colors queried from the operating system. They help Firefox adapt to OS-level changes that aren't strictly HCM (ie. light/dark themeing). Because these colors are OS-dependent, a user operating Firefox on a Windows machine with system colors enabled will see Firefox differently than a user with system colors enabled on MacOS. + + So, how do we _get_ system colors? Our style system has a set of pre-defined `ColorID`'s in `ServoStyleConsts.h`, which are mapped to platform-specific colors in `widget/[cocoa | android | windows | gtk]/LookAndFeel.cpp`. Depending on the `ColorID` queried, we may do a dynamic fetch or simply return a constant. On MacOS, for example, `ColorID::TextForeground` and `ColorID::TextBackground` are hard-coded to return black and white respectively. `ColorID::Highlight`, on the other hand, queries the OS for `NSColor.selectedTextBackgroundColor`, which is set based on the accent color a user has selected in System Preferences. + > Note: The colors we fetch here are theme-relative. If a user has set their OS to a dark theme, we'll fetch colors from that palette, and likewise for a light theme. Windows HCM, though not strictly a "theme", overrides the colors stored for Windows' light theme, leading to [some confusing code, like this](https://searchfox.org/mozilla-central/rev/b462b11e71b500e084f51e61fbd9e19ea0122c78/layout/style/PreferenceSheet.cpp#202-210). + + Lastly, if we are _not_ using system colors AND we are _not_ styling Firefox chrome AND we are _not_ `resistFingerprinting`, we'll use colors-dialog colors to style web content. + + By default, `browser.display.use_system_colors` is true on Windows and false elsewhere. This means users on Windows will _not_ see their selections in the colors dialog reflected automatically in Firefox. They'll need to uncheck "Use system colors" first. + > Note: This is intentional. When Windows HCM is enabled, the system colors Windows exposes are pulled from the chosen HCM theme. With "Use system colors" checked, a Windows HCM user will see their HCM theme choices reflected in Firefox content automatically. Windows HCM is the most robust HCM offered among the operating systems we support, and so we cater to it here :) + +Users on non-Windows platforms will see their selections in the colors dialog reflected automatically, but they will _not_ see OS changes until they check "Use system colors". + +## High Contrast Mode + +### Operating System High Contrast Mode (OS HCM) + +Operating System HCM (or OS HCM) describes a high contrast customisation that is enabled outside of Firefox, in the settings of a user's operating system. Each of our major desktop operating systems has an OS HCM variant: +- Windows: Settings > Accessibility > Increase Contrast > (select theme) > Apply +- MacOS: System Preferences > Accessibility > Display > Increase Contrast +- Linux: Settings > Themes > High Contrast + +The presence of an OS HCM is stored in `IntID::UseAccessibilityTheme`. diff --git a/accessible/docs/DocumentAccessibilityLifecycle.md b/accessible/docs/DocumentAccessibilityLifecycle.md new file mode 100644 index 0000000000..6c576ec257 --- /dev/null +++ b/accessible/docs/DocumentAccessibilityLifecycle.md @@ -0,0 +1,104 @@ +# Document Accessibility Lifecycle + +## 1. DocAccessible creation +When a DocAccessible is created, it is initially empty. +A DocAccessible can be created in one of several ways: + +### Scenario 1: Accessibility service is already started, layout fires an a11y notification for a new document +1. The layout [PresShell gets initialized (PresShell::Initialize)](https://searchfox.org/mozilla-central/rev/4e87b5392eafe1f1d49017e76f7317b06ec0b1d8/layout/base/PresShell.cpp#1820). +2. As part of that, layout [inserts content from the root content object down](https://searchfox.org/mozilla-central/rev/4e87b5392eafe1f1d49017e76f7317b06ec0b1d8/layout/base/PresShell.cpp#1885). +3. This [fires an accessibility insertion notification (nsAccessibilityService::ContentRangeInserted)](https://searchfox.org/mozilla-central/rev/4e87b5392eafe1f1d49017e76f7317b06ec0b1d8/layout/base/nsCSSFrameConstructor.cpp#6863). +4. That [gets the DocAccessible (DocManager::GetDocAccessible)](https://searchfox.org/mozilla-central/rev/4e87b5392eafe1f1d49017e76f7317b06ec0b1d8/accessible/base/nsAccessibilityService.cpp#463). +5. Because it doesn't exist yet, [the DocAccessible gets created (DocManager::CreateDocOrRootAccessible)](https://searchfox.org/mozilla-central/rev/4e87b5392eafe1f1d49017e76f7317b06ec0b1d8/accessible/base/DocManager.cpp#62). + +### Scenario 2: Accessibility service is already started, DOM loading completes for a new document +For top level content documents, if the accessibility service is started, layout should fire an a11y notification, resulting in scenario 1 above. +For child documents (e.g. in-process iframes), this might not happen because the container Accessible for the child document might not be created yet. +In that case, we can't create a DocAccessible when the layout notification is fired. +If the container Accessible is created when DOM loading completes for the child document, this scenario can occur. + +1. [a11y::DocManager gets notified that the document has stopped loading (DocManager::OnStateChange)](https://searchfox.org/mozilla-central/rev/4e87b5392eafe1f1d49017e76f7317b06ec0b1d8/accessible/base/DocManager.cpp#238). +2. It tries to get an existing DocAccessible, but it doesn't exist yet, so it [creates a DocAccessible (DocManager::CreateDocOrRootAccessible)](https://searchfox.org/mozilla-central/rev/4e87b5392eafe1f1d49017e76f7317b06ec0b1d8/accessible/base/DocManager.cpp#395). + +### Scenario 3: Accessibility service is already started, child document is reached while building accessibility tree for parent document +Child document here refers to a child document in the same process; i.e. an in-process iframe or a parent process document such as an about: page. +Note that scenario 1 or 2 could happen for a child document as well. + +1. While building the accessibility tree for the parent document, an OuterDocAccessible is created (e.g. for a XUL browser or iframe). +2. The [OuterDocAccessible constructor gets the DocAccessible for the child document (DocManager::GetDocAccessible)](https://searchfox.org/mozilla-central/rev/36f79bed679ad7ec46f7cd05868a8f6dc823e1be/accessible/generic/OuterDocAccessible.cpp#56). +3. Because it doesn't exist yet, [the DocAccessible gets created (DocManager::CreateDocOrRootAccessible)](https://searchfox.org/mozilla-central/rev/4e87b5392eafe1f1d49017e76f7317b06ec0b1d8/accessible/base/DocManager.cpp#62). + +### Scenario 4: Accessibility service starts after top level document is loaded +1. When the accessibility service starts, it [initializes the ApplicationAccessible (ApplicationAccessible::Init)](https://searchfox.org/mozilla-central/rev/36f79bed679ad7ec46f7cd05868a8f6dc823e1be/accessible/base/nsAccessibilityService.cpp#1219). +2. As part of that, all documents are iterated. +3. For each top level document, [the DocAccessible is retrieved (DocManager::GetDocAccessible)](https://searchfox.org/mozilla-central/rev/36f79bed679ad7ec46f7cd05868a8f6dc823e1be/accessible/generic/ApplicationAccessible.cpp#130) and [thus created (DocManager::CreateDocOrRootAccessible)](https://searchfox.org/mozilla-central/rev/4e87b5392eafe1f1d49017e76f7317b06ec0b1d8/accessible/base/DocManager.cpp#62). + +Scenario 3 would then apply for any child documents encountered while building the accessibility trees for these top level DocAccessibles. + +### Scenario 5: Document gets focus before layout begins +It is possible for a document to get focus before layout has begun and before DOM loading is complete. +In that case, there will be a PresShell, but it will have no root frame. +Despite this, it is necessary to create the document because otherwise, a11y focus would go nowhere while the document had DOM focus. + +1. [a11y::FocusManager gets notified of a DOM focus change (FocusManager::NotifyOfDOMFocus)](https://searchfox.org/mozilla-central/rev/04dbb1a865894aec20eb02585aa75acccc0b72d5/accessible/base/FocusManager.cpp#126). +2. It gets the DocAccessible for the child document (DocManager::GetDocAccessible). +3. Because it doesn't exist yet, [the DocAccessible gets created (DocManager::CreateDocOrRootAccessible)](https://searchfox.org/mozilla-central/rev/4e87b5392eafe1f1d49017e76f7317b06ec0b1d8/accessible/base/DocManager.cpp#62). + +## 2. Initial tree creation +1. When a DocAccessible is created, it [creates a refresh observer (NotificationController)](https://searchfox.org/mozilla-central/rev/36f79bed679ad7ec46f7cd05868a8f6dc823e1be/accessible/generic/DocAccessible.cpp#368) which performs various processing asynchronously. +2. When the NotificationController is created, it [schedules processing for the next possible refresh tick](https://searchfox.org/mozilla-central/rev/36f79bed679ad7ec46f7cd05868a8f6dc823e1be/accessible/base/NotificationController.cpp#39). +3. Once a refresh tick occurs, [while there is not an interruptible reflow in progress](https://searchfox.org/mozilla-central/rev/36f79bed679ad7ec46f7cd05868a8f6dc823e1be/accessible/base/NotificationController.cpp#615) and there is an initialized PresShell, the DocAccessible's [initial update is triggered (DocAccessible::DoInitialUpdate)](https://searchfox.org/mozilla-central/source/accessible/base/NotificationController.cpp#649). +4. For a top level document, the [DocAccessibleChild IPC actor is created](https://searchfox.org/mozilla-central/rev/36f79bed679ad7ec46f7cd05868a8f6dc823e1be/accessible/generic/DocAccessible.cpp#1752). See the section on IPC actor creation below. +5. The [DOM tree is walked and the accessibility tree is built for the document down (DocAccessible::CacheChildrenInSubtree)](https://searchfox.org/mozilla-central/rev/36f79bed679ad7ec46f7cd05868a8f6dc823e1be/accessible/generic/DocAccessible.cpp#1789). + +Note that the document might still have no layout frame if the PresShell still has no frame; see scenario 5 in DocAccessible creation above. +Nevertheless, DoInitialUpdate must be called because otherwise, we wouldn't create the IPC actor, which would in turn mean remote documents in this state couldn't get a11y focus. + +## 3. Child document binding +Child document here refers to a child document in the same process; e.g. an in-process iframe or a parent process document such as an about: page. + +Child documents need to be a child of their OuterDocAccessible; e.g. the iframe. +However, the child document might be ready before the parent document is. +To deal with this: + +1. When a DocAccessible is created for a child document (DocManager::CreateDocOrRootAccessible), it is [scheduled for binding to its parent (DocAccessible::BindChildDocument)](https://searchfox.org/mozilla-central/rev/1758450798ae14492ba28b695f48143840ad6c5b/accessible/base/DocManager.cpp#505). +2. NotificationController [does not handle any updates for child documents until they are bound to their parent](https://searchfox.org/mozilla-central/rev/1758450798ae14492ba28b695f48143840ad6c5b/accessible/base/NotificationController.cpp#638). +3. After the initial tree creation for the parent document, NotificationController [binds the document scheduled in 1)](https://searchfox.org/mozilla-central/source/accessible/base/NotificationController.cpp#783). + +## 4. IPC actor (DocAccessibleChild/Parent) creation + +### Scenario 1: Top level document +1. As the first part of the DocAccessible's initial update (DocAccessible::DoInitialUpdate), if the document is a top level document, it [creates a DocAccessibleChild](https://searchfox.org/mozilla-central/rev/1758450798ae14492ba28b695f48143840ad6c5b/accessible/generic/DocAccessible.cpp#1757). + - There is also a [code path to handle the case where the DocAccessibleChild has already been created](https://searchfox.org/mozilla-central/source/accessible/generic/DocAccessible.cpp#1755). However, it doesn't seem like this should happen and code coverage information suggests that it doesn't. +2. It then [sends a message to the parent process to construct the DocAccessibleParent (BrowserChild::SendPDocAccessibleConstructor)](https://searchfox.org/mozilla-central/source/accessible/generic/DocAccessible.cpp#1771). + +### Scenario 2: Child document +The [DocAccessibleChild for a child document is created](https://searchfox.org/mozilla-central/source/accessible/base/NotificationController.cpp#909) when the child document is bound to its parent by NotificationController. +There is also a [code path to handle the case where the DocAccessibleChild has already been created](https://searchfox.org/mozilla-central/source/accessible/base/NotificationController.cpp#905). +However, it doesn't seem like this should happen and code coverage information suggests that it doesn't. + +## 5. Document load events + +### Scenario 1: DocAccessible is already created, DOM loading completes +1. [a11y::DocManager gets notified that the document has stopped loading (DocManager::OnStateChange)](https://searchfox.org/mozilla-central/rev/4e87b5392eafe1f1d49017e76f7317b06ec0b1d8/accessible/base/DocManager.cpp#238). +2. It [notifies the DocAccessible (DocAccessible::NotifyOfLoad](https://searchfox.org/mozilla-central/source/accessible/base/DocManager.cpp#399), passing EVENT_DOCUMENT_LOAD_COMPLETE. +3. That [sets the eDOMLoaded LoadState](https://searchfox.org/mozilla-central/rev/4e87b5392eafe1f1d49017e76f7317b06ec0b1d8/accessible/generic/DocAccessible-inl.h#93) and mLoadEventType on the DocAccessible. +4. Something schedules NotificationController processing. This could be the initial update, an insertion, etc. +5. Because the DocAccessible has been marked as loaded, the initial tree has been built and all child documents are loded, [NotificationController calls DocAccessible::ProcessLoad](https://searchfox.org/mozilla-central/source/accessible/base/NotificationController.cpp#815). +6. ProcessLoad [fires the EVENT_DOCUMENT_LOAD_COMPLETE event](https://searchfox.org/mozilla-central/source/accessible/generic/DocAccessible.cpp#1841) as set in 3). + +### Scenario 2: DocAccessible is created some time after DOM loading completed +This can happen if the accessibility service is started late. +It can also happen if a DocAccessible couldn't be created earlier because the PresShell or containre Accessible wasn't created yet. + +1. The [DocAccessible is initialized (DocAccessible::Init)](https://searchfox.org/mozilla-central/rev/8db61933e64b13c4a0ae456bcaccbd86a519ccc5/accessible/generic/DocAccessible.cpp#359). +2. It [detects that DOM loading is already complete](https://searchfox.org/mozilla-central/rev/8db61933e64b13c4a0ae456bcaccbd86a519ccc5/accessible/generic/DocAccessible.cpp#379). +3. In response, it [sets the eDOMLoaded state and mLoadEventType on the DocAccessible](https://searchfox.org/mozilla-central/rev/8db61933e64b13c4a0ae456bcaccbd86a519ccc5/accessible/generic/DocAccessible.cpp#381). +4. Something schedules NotificationController processing. This could be the initial update, an insertion, etc. +5. Because the DocAccessible has been marked as loaded, the initial tree has been built and all child documents are loded, [NotificationController calls DocAccessible::ProcessLoad](https://searchfox.org/mozilla-central/source/accessible/base/NotificationController.cpp#815). +6. ProcessLoad [fires the EVENT_DOCUMENT_LOAD_COMPLETE event](https://searchfox.org/mozilla-central/source/accessible/generic/DocAccessible.cpp#1841) as set in 3). + +### Suppression of document load events for parent process iframes +Note that for documents loaded directly in the parent process, ProcessLoad won't fire a load event for a child document whose parent is still loading. +This is old behavior which does not work in the content process and will probably be removed in future. +See [bug 1700362](https://bugzilla.mozilla.org/show_bug.cgi?id=1700362). diff --git a/accessible/docs/GeckoViewThreadTopography.md b/accessible/docs/GeckoViewThreadTopography.md new file mode 100644 index 0000000000..43265a3b16 --- /dev/null +++ b/accessible/docs/GeckoViewThreadTopography.md @@ -0,0 +1,51 @@ +# GeckoView Thread Topography +Unlike conventional Gecko, GeckoView utilizes two main threads in the parent process. A thread called the "UI" thread that runs the native Java event loop, and a "Gecko" thread that spawns an instance of Gecko. Note, this "Gecko" thread is considered the main thread in conventional Gecko and throughout the source is referred to as such. For example `NS_IsMainThread()` returns `true` when called on the Gecko thread. + +Android's accessibility API's entry point is the UI thread. The most common methods for an accessibility service to query and interact with Gecko have a return value and thus must be performed synchronously. For example [performAction()](https://developer.android.com/reference/android/view/accessibility/AccessibilityNodeProvider#performAction(int,%20int,%20android.os.Bundle)) and [createAccessibilityNodeInfo()](https://developer.android.com/reference/android/view/accessibility/AccessibilityNodeProvider#createAccessibilityNodeInfo(int)). + +## Thread Safety Concerns +As a rule, every data structure and method in Gecko should be considered unsafe to access from GeckoView's UI thread. For example the default reference counting implementation is not thread safe and should not be used outside the Gecko thread. Cross-process messaging must be done from the Gecko thread as well. + +Since the accessibility consumer is in the UI thread we need to either call into the Gecko thread while blocking the UI thread, or make a limited subset of the accessibility API thread safe. We employ both methods for different types of content. + +## In-Process Content +Content that is rendered in the top-level process is rare and mostly consists of "about" pages that give insight to browser internals, like `about:support`. + +Since our Gecko accessibility API queries DOM and Layout, and includes many complex, thread-unsafe data structures, we call into the Gecko thread to retrieve what is needed and block the UI until we get a response from the Gecko thread. For example, to retrieve a "class name" enum that is calculated for a certain accessible the method will look something like this: +```cpp +int SessionAccessibility::GetNodeClassName(int32_t aID) { + int32_t classNameEnum = java::SessionAccessibility::CLASSNAME_VIEW; + RefPtr<SessionAccessibility> self(this); + nsAppShell::SyncRunEvent([this, self, aID, &classNameEnum] { + if (Accessible* acc = GetAccessibleByID(aID)) { + classNameEnum = AccessibleWrap::AndroidClass(acc); + } + }); + + return classNameEnum; +} +``` + +## Out-of-Process Content +Most web content will be rendered in a child process. Historically, we would cache the accessible tree hierarchy and object roles in the parent process and use synchronous IPC to query individual objects for more properties. We are transitioning to a fully cached approach where all fields are either stored or calculated in remote proxy objects in the parent process. + +The "cache" in our definition is any data member associated with the remote `Accessible` or its subclasses, such as `mParent`, `mChildren` and `mCachedFields` in `RemoteAccessibleBase`, or `mAccessibles` in `DocAccessibleParent`. In addition to all those members the cache includes the accessible/id table in `SessionAccessibility`. + +The cache is initialized and modified exclusively by messages from the child process to the parent process. Since the cache is limited to our module and is relatively well scoped, it is possible to synchronize access to it and allow it to be consumed by the UI thread. + +### Global Accessibility Thread Monitor +We have global thread monitor that can be acquired by calling `nsAccessibilityService::GetAndroidMonitor()`. For example, if we were to retrieve the class name enum we would use an RAII wrapped monitor to exclusively access the cache: +```cpp +int SessionAccessibility::GetNodeClassName(int32_t aID) { + MonitorAutoLock mal(nsAccessibilityService::GetAndroidMonitor()); + if (Accessible* acc = GetAccessibleByID(aID)) { + return AccessibleWrap::AndroidClass(acc); + } +} +``` + +#### Gecko Thread Access +As a rule, all incoming IPC messages (ie. all `DocAccessibleParent::Recv` methods) should hold the global monitor. This is because any `Recv` method may indirectly modify the cache, for example a certain event might invalidate or update a cached property. There are currently no deferred tasks that happen as a result of a `Recv` method, so holding the monitor at the start of these methods will assure that the cache is being read or written in a synchronized fashion. + +#### UI Thread Access +All methods that are called in via JNI should be assumed are in the UI thread and should acquire the monitor, unless the are annotated with `@WrapForJNI(dispatchTo = "gecko")`. This assures us that they are synchronized with any potential modifications to the cache happening in the Gecko thread. diff --git a/accessible/docs/index.rst b/accessible/docs/index.rst new file mode 100644 index 0000000000..ee217d47cf --- /dev/null +++ b/accessible/docs/index.rst @@ -0,0 +1,15 @@ +Accessibility +============= + +These pages contain design documents for the accessibility implementation in Gecko. +They live in the mozilla-central repository under the accessible/docs directory. + +The `Accessibility page on the Mozilla Wiki <https://wiki.mozilla.org/Accessibility>`__ contains general information about accessibility and the accessibility team at Mozilla. + +.. toctree:: + :maxdepth: 1 + + Architecture + DocumentAccessibilityLifecycle + GeckoViewThreadTopography + ColorsAndHighContrastMode |