diff --git a/accessible/mac/MOXAccessibleProtocol.h b/accessible/mac/MOXAccessibleProtocol.h index 07ea53f27f2d..05f450c27385 100644 --- a/accessible/mac/MOXAccessibleProtocol.h +++ b/accessible/mac/MOXAccessibleProtocol.h @@ -124,6 +124,9 @@ // AXRequired - (NSNumber* _Nullable)moxRequired; +// AXElementBusy +- (NSNumber* _Nullable)moxElementBusy; + // AXDOMIdentifier - (NSString* _Nullable)moxDOMIdentifier; diff --git a/accessible/mac/MOXWebAreaAccessible.h b/accessible/mac/MOXWebAreaAccessible.h index f46e562700b4..1ef11af50c09 100644 --- a/accessible/mac/MOXWebAreaAccessible.h +++ b/accessible/mac/MOXWebAreaAccessible.h @@ -38,6 +38,9 @@ using namespace mozilla::a11y; // override - (BOOL)moxBlockSelector:(SEL)selector; +// override +- (void)moxPostNotification:(NSString*)notification; + // override - (void)handleAccessibleEvent:(uint32_t)eventType; diff --git a/accessible/mac/MOXWebAreaAccessible.mm b/accessible/mac/MOXWebAreaAccessible.mm index 74504df1cf6e..b91732325c91 100644 --- a/accessible/mac/MOXWebAreaAccessible.mm +++ b/accessible/mac/MOXWebAreaAccessible.mm @@ -223,9 +223,23 @@ using namespace mozilla::a11y; return YES; } + if (selector == @selector(moxElementBusy)) { + // Don't confuse aria-busy with a document's busy state. + return YES; + } + return [super moxBlockSelector:selector]; } +- (void)moxPostNotification:(NSString*)notification { + if (![notification isEqualToString:@"AXElementBusyChanged"]) { + // Suppress AXElementBusyChanged since it uses gecko's BUSY state + // to tell VoiceOver about aria-busy changes. We use that state + // differently in documents. + [super moxPostNotification:notification]; + } +} + - (id)rootGroup { NSArray* children = [super moxUnignoredChildren]; if (mRole != roles::APPLICATION && [children count] == 1 && diff --git a/accessible/mac/mozAccessible.h b/accessible/mac/mozAccessible.h index 5c188e194b83..4f5b7b09dfd7 100644 --- a/accessible/mac/mozAccessible.h +++ b/accessible/mac/mozAccessible.h @@ -208,6 +208,9 @@ inline mozAccessible* GetNativeFromGeckoAccessible( // override - (NSNumber*)moxRequired; +// override +- (NSNumber*)moxElementBusy; + // override - (id)moxEditableAncestor; diff --git a/accessible/mac/mozAccessible.mm b/accessible/mac/mozAccessible.mm index 705cda3131cb..717fa7cb6730 100644 --- a/accessible/mac/mozAccessible.mm +++ b/accessible/mac/mozAccessible.mm @@ -111,7 +111,7 @@ using namespace mozilla::a11y; static const uint64_t kCachedStates = states::CHECKED | states::PRESSED | states::MIXED | states::EXPANDED | states::CURRENT | states::SELECTED | states::TRAVERSED | states::LINKED | - states::HASPOPUP; + states::HASPOPUP | states::BUSY; static const uint64_t kCacheInitialized = ((uint64_t)0x1) << 63; - (uint64_t)state { @@ -143,19 +143,20 @@ static const uint64_t kCacheInitialized = ((uint64_t)0x1) << 63; } - (void)stateChanged:(uint64_t)state isEnabled:(BOOL)enabled { - if ((state & kCachedStates) == 0) { - return; + if ((state & kCachedStates) != 0) { + if (!(mCachedState & kCacheInitialized)) { + [self state]; + } else { + if (enabled) { + mCachedState |= state; + } else { + mCachedState &= ~state; + } + } } - if (!(mCachedState & kCacheInitialized)) { - [self state]; - return; - } - - if (enabled) { - mCachedState |= state; - } else { - mCachedState &= ~state; + if (state == states::BUSY) { + [self moxPostNotification:@"AXElementBusyChanged"]; } } @@ -789,6 +790,10 @@ struct RoleDescrComparator { return @([self stateWithMask:states::REQUIRED] != 0); } +- (NSNumber*)moxElementBusy { + return @([self stateWithMask:states::BUSY] != 0); +} + - (mozAccessible*)topWebArea { AccessibleOrProxy doc = [self geckoDocument]; while (!doc.IsNull()) { diff --git a/accessible/tests/browser/mac/browser.ini b/accessible/tests/browser/mac/browser.ini index ed634797d5ff..81258af48b14 100644 --- a/accessible/tests/browser/mac/browser.ini +++ b/accessible/tests/browser/mac/browser.ini @@ -46,3 +46,4 @@ skip-if = os == 'mac' && debug # Bug 1664577 [browser_menulist.js] [browser_rich_listbox.js] [browser_live_regions.js] +[browser_aria_busy.js] diff --git a/accessible/tests/browser/mac/browser_aria_busy.js b/accessible/tests/browser/mac/browser_aria_busy.js new file mode 100644 index 000000000000..e75d334e295b --- /dev/null +++ b/accessible/tests/browser/mac/browser_aria_busy.js @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +/* import-globals-from ../../mochitest/role.js */ +/* import-globals-from ../../mochitest/states.js */ +loadScripts( + { name: "role.js", dir: MOCHITESTS_DIR }, + { name: "states.js", dir: MOCHITESTS_DIR } +); + +/** + * Test aria-busy + */ +addAccessibleTask( + `
Hello
`, + async (browser, accDoc) => { + let section = getNativeInterface(accDoc, "section"); + + ok(!section.getAttributeValue("AXElementBusy"), "section is not busy"); + + let busyChanged = waitForMacEvent("AXElementBusyChanged", "section"); + await SpecialPowers.spawn(browser, [], () => { + content.document + .getElementById("section") + .setAttribute("aria-busy", "true"); + }); + await busyChanged; + + ok(section.getAttributeValue("AXElementBusy"), "section is busy"); + + busyChanged = waitForMacEvent("AXElementBusyChanged", "section"); + await SpecialPowers.spawn(browser, [], () => { + content.document + .getElementById("section") + .setAttribute("aria-busy", "false"); + }); + await busyChanged; + + ok(!section.getAttributeValue("AXElementBusy"), "section is not busy"); + } +);