diff --git a/accessible/mac/AccessibleWrap.mm b/accessible/mac/AccessibleWrap.mm index 0d801eefeebc..0e06aa86e913 100644 --- a/accessible/mac/AccessibleWrap.mm +++ b/accessible/mac/AccessibleWrap.mm @@ -128,6 +128,18 @@ nsresult AccessibleWrap::HandleAccEvent(AccEvent* aEvent) { } break; } + case nsIAccessibleEvent::EVENT_STATE_CHANGE: + if (Accessible* accessible = aEvent->GetAccessible()) { + accessible->GetNativeInterface((void**)&nativeAcc); + if (nativeAcc) { + AccStateChangeEvent* event = downcast_accEvent(aEvent); + [nativeAcc stateChanged:event->GetState() isEnabled:event->IsStateEnabled()]; + return NS_OK; + } else { + return NS_ERROR_FAILURE; + } + } + break; default: break; } diff --git a/accessible/mac/Platform.mm b/accessible/mac/Platform.mm index 14c1303f2778..2074c4c8537f 100644 --- a/accessible/mac/Platform.mm +++ b/accessible/mac/Platform.mm @@ -131,8 +131,11 @@ void ProxyEvent(ProxyAccessible* aProxy, uint32_t aEventType) { } } -void ProxyStateChangeEvent(ProxyAccessible* aProxy, uint64_t, bool) { - // mac doesn't care about state change events +void ProxyStateChangeEvent(ProxyAccessible* aProxy, uint64_t aState, bool aEnabled) { + mozAccessible* wrapper = GetNativeFromProxy(aProxy); + if (wrapper) { + [wrapper stateChanged:aState isEnabled:aEnabled]; + } } void ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset) { diff --git a/accessible/mac/mozAccessible.h b/accessible/mac/mozAccessible.h index 46eedfb5c517..d68425fe209b 100644 --- a/accessible/mac/mozAccessible.h +++ b/accessible/mac/mozAccessible.h @@ -62,6 +62,11 @@ static const uintptr_t IS_PROXY = 1; * The role of our gecko accessible. */ mozilla::a11y::role mRole; + + /** + * A cache of a subset of our states. + */ + uint64_t mCachedState; } // return the Accessible for this mozAccessible if it exists. @@ -135,6 +140,12 @@ static const uintptr_t IS_PROXY = 1; // Get gecko accessible's state filtered through given mask. - (uint64_t)stateWithMask:(uint64_t)mask; +// Notify of a state change, so the cache can be altered. +- (void)stateChanged:(uint64_t)state isEnabled:(BOOL)enabled; + +// Invalidate cached state. +- (void)invalidateState; + #pragma mark - // invalidates and removes all our children from our cached array. diff --git a/accessible/mac/mozAccessible.mm b/accessible/mac/mozAccessible.mm index 8cd56b6982bd..e8d12525b95f 100644 --- a/accessible/mac/mozAccessible.mm +++ b/accessible/mac/mozAccessible.mm @@ -245,22 +245,57 @@ static inline NSMutableArray* ConvertToNSArray(nsTArray& aArra NS_OBJC_END_TRY_ABORT_BLOCK_NIL; } +static const uint64_t kCachedStates = + states::CHECKED states::PRESSED | states::MIXED | states::EXPANDED; +static const uint64_t kCacheInitialized = ((uint64_t)0x1) << 63; + - (uint64_t)state { + uint64_t state = 0; if (AccessibleWrap* accWrap = [self getGeckoAccessible]) { - return accWrap->State(); + state = accWrap->State(); } if (ProxyAccessible* proxy = [self getProxyAccessible]) { - return proxy->State(); + state = proxy->State(); } - return 0; + if (!(mCachedState & kCacheInitialized)) { + mCachedState = state & kCachedStates; + mCachedState |= kCacheInitialized; + } + + return state; } - (uint64_t)stateWithMask:(uint64_t)mask { + if ((mask & kCachedStates) == mask && (mCachedState & kCacheInitialized) != 0) { + return mCachedState & mask; + } + return [self state] & mask; } +- (void)stateChanged:(uint64_t)state isEnabled:(BOOL)enabled { + if ((state & kCachedStates) == 0) { + return; + } + + if (!(mCachedState & kCacheInitialized)) { + [self state]; + return; + } + + if (enabled) { + mCachedState |= state; + } else { + mCachedState &= ~state; + } +} + +- (void)invalidateState { + mCachedState = 0; +} + - (id)accessibilityAttributeValue:(NSString*)attribute { NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; @@ -622,6 +657,8 @@ static inline NSMutableArray* ConvertToNSArray(nsTArray& aArra } else if (proxy) { proxy->DoAction(0); } + // Activating accessible may alter its state. + [self invalidateState]; } } @@ -1248,6 +1285,8 @@ struct RoleDescrComparator { [self invalidateChildren]; + [self invalidateState]; + mGeckoAccessible = 0; [self postNotification:NSAccessibilityUIElementDestroyedNotification];