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

  2. As part of that, layout inserts content from the root content object down.

  3. This fires an accessibility insertion notification (nsAccessibilityService::ContentRangeInserted).

  4. That gets the DocAccessible (DocManager::GetDocAccessible).

  5. Because it doesn’t exist yet, the DocAccessible gets created (DocManager::CreateDocOrRootAccessible).

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

  2. It tries to get an existing DocAccessible, but it doesn’t exist yet, so it creates a DocAccessible (DocManager::CreateDocOrRootAccessible).

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

  3. Because it doesn’t exist yet, the DocAccessible gets created (DocManager::CreateDocOrRootAccessible).

Scenario 4: Accessibility service starts after top level document is loaded

  1. When the accessibility service starts, it initializes the ApplicationAccessible (ApplicationAccessible::Init).

  2. As part of that, all documents are iterated.

  3. For each top level document, the DocAccessible is retrieved (DocManager::GetDocAccessible) and thus created (DocManager::CreateDocOrRootAccessible).

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

  2. It gets the DocAccessible for the child document (DocManager::GetDocAccessible).

  3. Because it doesn’t exist yet, the DocAccessible gets created (DocManager::CreateDocOrRootAccessible).

2. Initial tree creation

  1. When a DocAccessible is created, it creates a refresh observer (NotificationController) which performs various processing asynchronously.

  2. When the NotificationController is created, it schedules processing for the next possible refresh tick.

  3. Once a refresh tick occurs, while there is not an interruptible reflow in progress and there is an initialized PresShell, the DocAccessible’s initial update is triggered (DocAccessible::DoInitialUpdate).

  4. For a top level document, the DocAccessibleChild IPC actor is created. 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).

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

  2. NotificationController does not handle any updates for child documents until they are bound to their parent.

  3. After the initial tree creation for the parent document, NotificationController binds the document scheduled in 1).

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.

  2. It then sends a message to the parent process to construct the DocAccessibleParent (BrowserChild::SendPDocAccessibleConstructor).

Scenario 2: Child document

The DocAccessibleChild for a child document is created 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. 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).

  2. It notifies the DocAccessible (DocAccessible::NotifyOfLoad, passing EVENT_DOCUMENT_LOAD_COMPLETE.

  3. That sets the eDOMLoaded LoadState 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.

  6. ProcessLoad fires the EVENT_DOCUMENT_LOAD_COMPLETE event 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).

  2. It detects that DOM loading is already complete.

  3. In response, it sets the eDOMLoaded state 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.

  6. ProcessLoad fires the EVENT_DOCUMENT_LOAD_COMPLETE event 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.