diff options
Diffstat (limited to 'accessible/docs')
-rw-r--r-- | accessible/docs/Architecture.md | 65 | ||||
-rw-r--r-- | accessible/docs/ColorsAndHighContrastMode.md | 50 | ||||
-rw-r--r-- | accessible/docs/DocumentAccessibilityLifecycle.md | 104 | ||||
-rw-r--r-- | accessible/docs/GeckoViewThreadTopography.md | 51 | ||||
-rw-r--r-- | accessible/docs/HCMMediaQueries.md | 306 | ||||
-rw-r--r-- | accessible/docs/HCMSettings.md | 45 | ||||
-rw-r--r-- | accessible/docs/fx_view_fc.jpg | bin | 0 -> 65120 bytes | |||
-rw-r--r-- | accessible/docs/fxview.jpg | bin | 0 -> 52575 bytes | |||
-rw-r--r-- | accessible/docs/fxview_ic.jpg | bin | 0 -> 77858 bytes | |||
-rw-r--r-- | accessible/docs/index.rst | 17 |
10 files changed, 638 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..30fd51d17c --- /dev/null +++ b/accessible/docs/ColorsAndHighContrastMode.md @@ -0,0 +1,50 @@ +# 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". + +For a simplified flow chart of this decision tree, check out our [HCM Settings page](/https://firefox-source-docs.mozilla.org/accessible/HCMSettings.html) + +## 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/HCMMediaQueries.md b/accessible/docs/HCMMediaQueries.md new file mode 100644 index 0000000000..c4b57de281 --- /dev/null +++ b/accessible/docs/HCMMediaQueries.md @@ -0,0 +1,306 @@ +# High Contrast Mode (HCM) Media Queries + +Firefox supports both the `prefers-contrast` and `forced-colors` media queries. These queries detect a user's display settings (in the browser and/or in their OS), and allow authors to adjust content styling accordingly. These queries are not interchangeable, and we should be intentional about using one or the other (or both!) when designing for accessibility. + +## What activates these queries? + +### Forced Colors +The `forced-colors` media query has two values: `active` and `none`. The resolved value of this media query is determined by the browser based on user settings (OS and Firefox). +* The `none` state indicates documents can use any colors to render content. +* The `active` state indicates a user has OS or browser settings that limit the color palette available to the User Agent. + +In Firefox's default configuration, the `active` state is triggered when a user has enabled Windows High Contrast Mode or [Firefox High Contrast Mode](https://firefox-source-docs.mozilla.org/accessible/ColorsAndHighContrastMode.html#the-colors-dialog) (FF HCM). Both of these forced-colors modes offer users a limited amount of color customisation. In Windows, users can override seven colors, and with Firefox High Contrast Mode users can override four. MacOS's "Increase Contrast" setting does **not** trigger `forced-colors: active` because it does not limit the color palette available to UA's. +> **NOTE:** MacOS's "Increase Contrast" setting **will** trigger `forced-colors: active` (in non-chrome web content) if it is enabled in combination with FF HCM's "Only with High Contrast Themes" option. This is also true on Linux. Because [FF HCM defaults to "Never" on non-Windows platforms](https://searchfox.org/mozilla-central/rev/896042a1a71066254ceb5291f016ca3dbca21cb7/modules/libpref/init/StaticPrefList.yaml#1195-1199), enabling "Increase Contrast" or Linux High Contrast Themes alone will not trigger `forced-colors: active`. Similarly, if a user disables FF HCM while Win HCM is enabled, `forced-colors` (in content) will evaluate to `none`. + +#### CSS System Colors + +Colors that are overridden by the forced-colors modes list above are inherited into the browser's [CSS System Colors](https://www.w3.org/TR/css-color-4/#css-system-colors). Designers must use these colors exclusively in their designs when designing for forced-colors modes. CSS System Colors are intended to be used in pairs: for example the `ButtonFace` and `ButtonText` colors should be used together to create styling for interactive controls. The full pairings are listed in the [system color pairings](https://www.w3.org/TR/css-color-4/#system-color-pairings) section of the CSS spec. Mozilla has additional design guidance for using CSS System Colors in our [HCM Design Guide](https://wiki.mozilla.org/Accessibility/Design_Guide). You can also check out our other docs to [learn more about color overriding](https://firefox-source-docs.mozilla.org/accessible/ColorsAndHighContrastMode.html). + +### Prefers Contrast +The `prefers-contrast` media query has four values: `more`, `less`, `custom`, and `no-preference`. + +* The `more` state is triggered when a user has macOS's "Increase Contrast" setting enabled, or when a forced-colors mode is active **and** the ratio between a user's [chosen foreground and background](https://searchfox.org/mozilla-central/rev/655f49c541108e3d0a232aa7173fbcb9af88d80b/layout/style/PreferenceSheet.cpp#123-126) is higher than 7:1. This check is done [in our style system](https://searchfox.org/mozilla-central/rev/655f49c541108e3d0a232aa7173fbcb9af88d80b/layout/style/nsMediaFeatures.cpp#287-308). +* The `less` state is triggered when a forced-colors mode is active and the foreground/background ratio is less than 4.5. +* When a forced-colors mode is enabled with a ratio between these two values, we return `custom`. +* Otherwise, if no forced-colors mode is active, and macOS's "Increase Contrast" is off, the media query resolves to `no-preference`. + +## So what do I use? + +Generally, this boils down to which operating systems you want your styling to apply to. In the past, we've often written code to target _all_ platforms within `prefers-contrast` blocks because the kinds of changes we make to target forced-colors mode also benefit "Increase Contrast" users on macOS; they allow system colors to propagate to the UA. The palette that macOS supplies when "Increase Contrast" is enabled is _also_ inherited to CSS System Colors, and may offer improvements over the standard palette for a design. It is still possible that changes made for forced-colors modes benefit "Increase Contrast" users, however we strongly encourage developers to consider whether diversifying our designs for these modes provides better UX. + +On macOS, we aren't limited to the OS palette. Instead of modifying a design to use fewer colors, we can simply increase the contrast ratio of the existing colors to 7:1 to satisfy [the enhanced contrast criterion of Web Content Accessibility Guidelines (WCAG)](https://www.w3.org/WAI/WCAG22/Understanding/contrast-enhanced.html) for better accessibility. Consider the following designs. + +Standard UX on macOS: + +![Firefox view page. The navigation tabs are rendered to match the custom browser theme -- yellow on dark grey, with the active tab rendered light Firefox blue on blue-grey. Page content is rendered yellow on dark grey. Interactive components are a slightly lighter grey, difficult to distinguish against the page background.](fxview.jpg) + +UX with increased contrast of existing colors on macOS: + +![Firefox view page. The navigation tabs are rendered yellow on darker grey, with the active tab rendered white on darker blue. Page content is rendered yellow on dark grey with white borders around interactive components.](fxview_ic.jpg) + +Changes present in this example belong in a `prefers-contrast: more` block. This is **assuming these changes are appropriately overridden in the cascade for `forced-colors: active` users**. If they are not overridden, an additional query like `@media (-moz-platform: macos)` or `.. and (not (forced-colors))` should be combined with `prefers-contrast: more` to ensure these changes only apply to macOS. +For users who prefer less contrast, we can supply colors that lower the contrast ratios in the existing design and encase those changes in a `prefers-contrast: less` block. + +UX with limited color palette in FF HCM on macOS: + +![Firefox view page. The navigation tabs are rendered white on dark grey, with the active tab rendered black on white. Page content is rendered white on black with borders around interactive components.](fx_view_fc.jpg) + +In forced-colors modes, we require that designs reduce their palettes for compatibility. To accomplish this, we use CSS System Colors instead of the original design colors. This allows the overridden palette to "show through", since colors from the various forced-colors modes are inherited into CSS System Colors. It is possible that using CSS System Colors also increases contrast for users on macOS, but this more drastically changes the design at hand, and can detract from the overall user experience for users who are simply looking for more contrast. Designers should consider the additional guidelines on border use, removing gradients, etc. in our [HCM Design Guide](https://wiki.mozilla.org/Accessibility/Design_Guide). + +> **NOTE:** Firefox's chrome style sheets are subject to different rules than web-author style sheets. + +Firefox's chrome style sheets (including those in our `about:` pages) are not prevented from using colors that aren't system colors, even if a forced colors mode is enabled. In webpages, however, Firefox forces CSS System Colors regardless of the author's original specification. This is a safety mechanism to ensure pages render appropriately even if no `@media (prefers-contrast)` or `@media (forced-colors)` styling was specified. To avoid this overriding, web authors can use the [forced-color-adjust: none;](https://developer.mozilla.org/en-US/docs/Web/CSS/forced-color-adjust) CSS property. + +## Writing Maintainable Frontend Code + +Where possible, we prefer color overriding for `prefers-contrast` and `forced-colors` take place in one block at the `root` level rather than in many blocks on an element-by-element basis. We encourage developers to use [design systems tokens](https://searchfox.org/mozilla-central/rev/c130c69b7b863d5e28ab9524b65c27c7a9507c48/toolkit/themes/shared/design-system/tokens-shared.css) with semantic naming when overriding. Consider the following. + +```css +:root { + --page-background: #cccccc; + --page-text-color: #000000; + + @media (forced-colors) { + --page-background: Canvas; + --page-text-color: CanvasText; + } +} + +body { + color: var(--page-text-color); + background-color: var(--page-background); +} +``` + +Here, we've defined one set of color variables on `:root` and subsequently overridden them in a `forced-colors` block. We don't need to re-write the styling for `body` within our media query because the vars it references will correctly adapt when `forced-colors` is active. + +Though `forced-colors` overriding doesn't happen in Firefox chrome style sheets, our style system _does_ force all instances of `transparent` to the [default color](https://searchfox.org/mozilla-central/rev/655f49c541108e3d0a232aa7173fbcb9af88d80b/servo/components/style/properties/cascade.rs#468-481) for a color attribute (usually `CanvasText`). This allows us to specify styles that appear only in forced-colors mode without an additional CSS keyword. It is also sometimes beneficial to define vars at the `:root` level with an initial value (eg. `transparent`, `0px`, `none`) so the vars exist for HCM overriding later (see: `--dialog-border-width`). + +```css +:root { + /* ... */ + --dialog-background: #ffffff; + --dialog-color: #bbbbbb; + --dialog-border-color: transparent; + --dialog-border-width: 0px; + + @media (forced-colors) { + /* ... */ + --dialog-background: Canvas; + --dialog-color: CanvasText; + /* No override for --dialog-borer-color, since it'll become + * CanvasText when HCM is enabled, which matches the HCM spec. */ + --dialog-border-width: 1px; + } +} + +.dialog { + color: var(--dialog-color); + background-color: var(--dialog-background); + border: var(--dialog-borer-width) solid var(--dialog-border-color); +} + +/* ... */ +``` + +In general, it is best to do overriding at the `:root` level, even if additional variables are required. It is _not_ recommended to do overriding on a class-by-class or element-by-element basis, like below: + +```css +:root { + /* ... */ + --button-background: #ffffff; + --button-color: #bbbbbb; + + @media (forced-colors) { + /* ... */ + --button-background: ButtonFace; + --button-color: ButtonText; + --button-border-color: var(--button-color); + } +} + +@media (forced-colors) { + /* BAD: These rules are generic and should be outside of a @media block */ + .destroyButton { + color: var(--button-color); + background-color: var(--button-background); + border: 1px solid var(--button-border-color); + } +} + +@media (not (forced-colors)) { + /* BAD: These rules are generic and should be outside of a @media block */ + .destroyButton { + color: var(--light-grey-20); + background-color: var(--red-60); + } +} +/*...*/ +``` + +## Putting it all together! + +Let's walk through an example on this sample website. + +<iframe src="https://mreschenberg.com/sample_site.html" style="width: 100%; height: 60vh;"></iframe> + +The majority of our site styling is done via color overriding on the root block. +```css +:root { + /* General */ + --background: #202124; + --color: #cccccc; + --border-color: transparent; + --border-size: 0; + --table-background: #303134; + /* Note */ + --note-background: gold; + --note-color: var(--table-background); + --note-opacity: .7; + /* General Controls */ + --control-background: gray; + --control-color: var(--color); + --control-border-size: var(--border-size); + --control-border-color: var(--border-color); + /* Destructive Button */ + --destructive-button-background: tomato; + --destructive-button-color: var(--table-background); +} +``` + +How might we adapt this website for users who prefer increased contrast? What about for users with forced colors? + +Let's start with users who prefer increased contrast. We might decide to make the background and foreground easier to read by making the foreground text lighter, increasing the contrast ratio. We might also remove the note's opacity and darken its text color. We don't need to remove the note's golden background, since prefers-contrast doesn't require a reduced color palette. +```css +:root { + /* ... */ + @media (prefers-contrast) and (not (forced-colors)) { + /* General */ + --color: white; + /* Note */ + --note-color: black; + --note-opacity: 1; + /* ... */ + } +} +``` +To address these same issues in forced-colors mode, we should replace the colors with the semantically appropriate system color. Unlike our work in the `prefers-contrast` block above, we do need to modify the note's background, since `gold` is not a system color. We might end up with something like this: +```css +:root { + /* ... */ + @media (forced-colors) { + /* General */ + --background: Canvas; + --color: CanvasText; + --table-background: var(--background); + /* Note */ + --note-background: var(--background); + --note-color: var(--color); + --note-opacity: 1; + /* ... */ + } +} +``` + +After this change, you'll notice our page background, table background, and note background all share the same color. This makes them difficult to differentiate. To address this, we can add a contrasting border and override the previously transparent `--border-color` variable and its corresponding `--border-size`. This var applies to content areas but not controls. +```css +:root { + /* ... */ + @media (forced-colors) { + /* General */ + --background: Canvas; + --color: CanvasText; + --border-color: var(--color); + --border-size: 1px; + --table-background: var(--background); + /* Note */ + --note-background: var(--background); + --note-color: var(--color); + --note-opacity: 1; + /* ... */ + } +} +``` + +Next, let's look at the controls this page uses for the web form. We've got an input, a checkbox, a submit button, and a clear button. In the prefers-contrast case, we should ensure the controls contrast from the background as much as possible. It's possible to do this via color alone, but it can help to add borders for additional contrast. Here, again, we don't need to get rid of the clear button's `tomato` background, but we can update it to something brighter. +```css +:root { + /* ... */ + @media (prefers-contrast) and (not (forced-colors)) { + /* ... */ + /* General Controls */ + --control-background: color-mix(in srgb, gray, lightgray); + --control-color: black; + --control-border-size: 1px; + --control-border-color: gray; + /* Destructive Button */ + --destructive-button-background: red; + --destructive-button-color: white; + /* ... */ + } +} +``` + +Finally, let's style the page's controls for `forced-colors`. Unlike in prefers-contrast, we can't use color to differentiate between the submit button and the clear form button -- both should inherit from our button CSS system colors. +```css +:root { + /* ... */ + @media (forced-colors) { + /* General */ + --background: Canvas; + --color: CanvasText; + --border-color: var(--color); + --border-size: 1px; + --table-background: var(--background); + /* Note */ + --note-background: var(--background); + --note-color: var(--color); + --note-opacity: 1; + /* General Controls */ + --control-background: ButtonFace; + --control-color: ButtonText; + --control-border-color: ButtonText; + /* Destructive Button */ + --destructive-button-background: var(--control-background); + --destructive-button-color: var(--control-color); + } +} +``` + +If we wanted to make the submit button stand out as a primary button, we could invert the styling on that button in particular. Something like: +```css +:root { + /* ... */ + @media (forced-colors) { + /* General */ + --background: Canvas; + --color: CanvasText; + --border-color: var(--color); + --border-size: 1px; + --table-background: var(--background); + /* Note */ + --note-background: var(--background); + --note-color: var(--color); + --note-opacity: 1; + /* General Controls */ + --control-background: ButtonFace; + --control-color: ButtonText; + --control-border-color: ButtonText; + /* Destructive Button */ + --destructive-button-background: var(--control-background); + --destructive-button-color: var(--control-color); + } + + #submit { + background: var(--control-color); + color: var(--control-background); + border: var(--control-border-size) solid currentColor; + } +} +``` + +Now we've got a site that functions in multiple HCM scenerios :) Visit the <a href="https://mreschenberg.com/sample_site.html">live site</a> with HCM or Increase Contrast enabled to test it for yourself. +A few takeaways: +- `prefers-contrast` and `forced-colors` are not mutually exclusive. If you write two independent `forced-colors` and `prefers-contrast` media query blocks, they'll both apply when FF HCM is enabled (assuming your foreground/background contrast ratio is high, which it is by default). Adding an `and (not (forced-colors))` clause to your `@media (prefers-contrast)` declaration can help make the two blocks distinct if you'd like mac-centric styling in one and `forced-colors` styling in the other. +- `prefers-contrast` requires specific, case-by-case overriding, whereas `forced-colors` is largely about inheretince. In the former, page regions that are differentiated with color should stay that way (albeit with more contrasting colors). In `forced-colors` all page regions that aren't interactive should use `Canvas`/`CanvasText` and rely on borders for distinction. Once you've set `--background: Canvas;` at the root level, for ex. subsequent background vars should inherit from it. +- Where possible, aim to do overriding at the `:root` level using CSS variables, this makes it easier to update code in the future. diff --git a/accessible/docs/HCMSettings.md b/accessible/docs/HCMSettings.md new file mode 100644 index 0000000000..014019828b --- /dev/null +++ b/accessible/docs/HCMSettings.md @@ -0,0 +1,45 @@ +# HCM Settings + +Several Firefox settings work together to determine how web content and browser chrome are rendered. They can be hard to keep track of! Use the flowcharts below for quick reference. + +## Settings that control color usage in browser chrome +- OS HCM: + - Windows: High Contrast Mode in OS accessibility settings + - macOS: Increase Contrast in OS accessibility settings + - Linux: High Contrast Theme in OS accessibility settings +- FF Theme (AKA FF Colorway) +Note: OS HCM settings will only trigger HCM color usage in chrome if a user's FF theme is set to "system auto". If they have a pre-selected colorway or other FF theme (including explicit "Dark" or "Light") they will not see color changes upon enabling OS HCM. + +```{mermaid} +flowchart TD +A[Is OS HCM enabeld?] +A -->|Yes| B[Is FF's theme set to System Auto?] +B --> |Yes| C[Use OS HCM colors to render browser chrome] +B -->|No| D[Use FF theme colors to render browser chrome] +A -->|No| D +``` + +## Settings that control color usage in content +- Colors Dialog (about:preferences > Manage Colors) + - Dropdown with options: Always, Only with High Contrast Themes, and Never + - Use System Colors checkbox + - Text, Background, Visited and Unvisited Link color inputs +- Extensions like Dark Reader, or changes to user.css, may override author specified colors independent of HCM. + +```{mermaid} +flowchart TD +A[What is the value of the dropdown in the colors dialog?] +A -->|Always|C + +A -->|Only with High Contrast Themes| B[Is a OS HCM enabled?] +B -->|Yes| C[Is the Use System Colors checkbox checked?] +C -->|Yes, and OS HCM is on| D[Use OS HCM colors to render web content] +C -->|Yes, and OS HCM is off| D2[Use OS dark/light colors to render web content] +C-->|No| E[Use colors dialog colors to render web content] + +B -->|No| F[Is a color-modifying web extension or color-modifying user.css change active?] +F -->|Yes| G[Use web extension/user.css provided colors to render web content] +F -->|No| H[Use author-provided colors to render web content] + +A -->|Never|F +``` diff --git a/accessible/docs/fx_view_fc.jpg b/accessible/docs/fx_view_fc.jpg Binary files differnew file mode 100644 index 0000000000..6ba63d2742 --- /dev/null +++ b/accessible/docs/fx_view_fc.jpg diff --git a/accessible/docs/fxview.jpg b/accessible/docs/fxview.jpg Binary files differnew file mode 100644 index 0000000000..c889feae92 --- /dev/null +++ b/accessible/docs/fxview.jpg diff --git a/accessible/docs/fxview_ic.jpg b/accessible/docs/fxview_ic.jpg Binary files differnew file mode 100644 index 0000000000..1f9b85f8dd --- /dev/null +++ b/accessible/docs/fxview_ic.jpg diff --git a/accessible/docs/index.rst b/accessible/docs/index.rst new file mode 100644 index 0000000000..881d49ca9b --- /dev/null +++ b/accessible/docs/index.rst @@ -0,0 +1,17 @@ +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 + HCMSettings + HCMMediaQueries |