Commit 67fb859d authored by James Teh's avatar James Teh
Browse files

Bug 1190882: If the focused accessible is removed from the tree, fire a11y...

Bug 1190882: If the focused accessible is removed from the tree, fire a11y focus on the document. r=eeejay

If the DOM focus is removed before something else is focused, the document gets DOM focus, but no blur event is fired (bug 559561).
This means that no a11y focus event is fired, so clients aren't notified.
This is particularly problematic for screen readers when dismissing some ARIA dialogs, as the screen reader doesn't know that focus has returned to the top level document.

Differential Revision:

extra : moz-landing-system : lando
parent 2d81bf72
......@@ -99,6 +99,10 @@ FocusManager::FocusDisposition FocusManager::IsInOrContainsFocus(
return eNone;
bool FocusManager::WasLastFocused(const Accessible* aAccessible) const {
return mLastFocus == aAccessible;
void FocusManager::NotifyOfDOMFocus(nsISupports* aTarget) {
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eFocus))
......@@ -340,6 +344,7 @@ void FocusManager::ProcessFocusEvent(AccEvent* aEvent) {
RefPtr<AccEvent> focusEvent = new AccEvent(nsIAccessibleEvent::EVENT_FOCUS,
target, aEvent->FromUserInput());
mLastFocus = target;
// Fire scrolling_start event when the document receives the focus if it has
// an anchor jump. If an accessible within the document receive the focus
......@@ -70,6 +70,18 @@ class FocusManager {
enum FocusDisposition { eNone, eFocused, eContainsFocus, eContainedByFocus };
FocusDisposition IsInOrContainsFocus(const Accessible* aAccessible) const;
* Return true if the given accessible was the last accessible focused.
* This is useful to detect the case where the last focused accessible was
* removed before something else was focused. This can happen in one of two
* ways:
* 1. The DOM focus was removed. DOM doesn't fire a blur event when this
* happens; see bug 559561.
* 2. The accessibility focus was an active item (e.g. aria-activedescendant)
* and that item was removed.
bool WasLastFocused(const Accessible* aAccessible) const;
// Focus notifications and processing (don't use until you know what you do).
......@@ -124,6 +136,7 @@ class FocusManager {
RefPtr<Accessible> mActiveItem;
RefPtr<Accessible> mLastFocus;
RefPtr<Accessible> mActiveARIAMenubar;
......@@ -1216,9 +1216,9 @@ void DocAccessible::UnbindFromDocument(Accessible* aAccessible) {
"Unbinding the unbound accessible!");
// Fire focus event on accessible having DOM focus if active item was removed
// Fire focus event on accessible having DOM focus if last focus was removed
// from the tree.
if (FocusMgr()->IsActiveItem(aAccessible)) {
if (FocusMgr()->WasLastFocused(aAccessible)) {
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eFocus))
......@@ -34,6 +34,7 @@ skip-if = os == 'win' || os == 'linux'
skip-if = webrender
<title>Test removal of focused accessible</title>
<link rel="stylesheet" type="text/css"
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
<script type="application/javascript"
<script type="application/javascript">
async function setFocus(aNodeToFocus, aExpectedFocus) {
let expected = aExpectedFocus || aNodeToFocus;
let focused = waitForEventPromise(EVENT_FOCUS, expected);
info("Focusing " +;
await focused;
ok(true, + " focused after " + + ".focus()");
async function expectFocusAfterRemove(aNodeToRemove, aExpectedFocus) {
let focused = waitForEventPromise(EVENT_FOCUS, aExpectedFocus);
info("Removing " +;
await focused;
let friendlyExpected = aExpectedFocus == document ?
"document" :;
ok(true, friendlyExpected + " focused after " + + " removed");
async function doTests() {
info("Testing removal of focused node itself");
let button = getNode("button");
await setFocus(button);
await expectFocusAfterRemove(button, document);
info("Testing removal of focused node's parent");
let dialog = getNode("dialog");
let dialogButton = getNode("dialogButton");
await setFocus(dialogButton);
await expectFocusAfterRemove(dialog, document);
info("Testing removal of aria-activedescendant target");
let listbox = getNode("listbox");
let option = getNode("option");
await setFocus(listbox, option);
await expectFocusAfterRemove(option, listbox);
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
<button id="button"></button>
<div role="dialog" id="dialog">
<button id="dialogButton"></button>
<div role="listbox" id="listbox" tabindex="0" aria-activedescendant="option">
<div role="option" id="option"></div>
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment