Bug 1905259 - Add updated thumbs up and down icons, add CSS animation to active state...
[gecko.git] / accessible / mac / mozActionElements.mm
blob1d0d97bf83503f0a41d0abf65f252deffd1c3263
1 /* clang-format off */
2 /* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
3 /* clang-format on */
4 /* This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #import "mozActionElements.h"
10 #import "MacUtils.h"
11 #include "LocalAccessible-inl.h"
12 #include "DocAccessible.h"
13 #include "XULTabAccessible.h"
14 #include "HTMLFormControlAccessible.h"
16 #include "nsCocoaUtils.h"
17 #include "mozilla/FloatingPoint.h"
19 using namespace mozilla::a11y;
21 @implementation mozButtonAccessible
23 - (NSNumber*)moxHasPopup {
24   return @([self stateWithMask:states::HASPOPUP] != 0);
27 - (NSString*)moxPopupValue {
28   if ([self stateWithMask:states::HASPOPUP] != 0) {
29     return utils::GetAccAttr(self, nsGkAtoms::aria_haspopup);
30   }
32   return nil;
35 @end
37 @implementation mozPopupButtonAccessible
39 - (NSString*)moxTitle {
40   // Popup buttons don't have titles.
41   return @"";
44 - (BOOL)moxBlockSelector:(SEL)selector {
45   if (selector == @selector(moxHasPopup)) {
46     return YES;
47   }
49   return [super moxBlockSelector:selector];
52 - (NSArray*)moxChildren {
53   if ([self stateWithMask:states::EXPANDED] == 0) {
54     // If the popup button is collapsed don't return its children.
55     return @[];
56   }
58   return [super moxChildren];
61 - (void)stateChanged:(uint64_t)state isEnabled:(BOOL)enabled {
62   [super stateChanged:state isEnabled:enabled];
64   if (state == states::EXPANDED) {
65     // If the EXPANDED state is updated, fire AXMenu events on the
66     // popups child which is the actual menu.
67     if (mozAccessible* popup = (mozAccessible*)[self childAt:0]) {
68       [popup moxPostNotification:(enabled ? @"AXMenuOpened" : @"AXMenuClosed")];
69     }
70   }
73 @end
75 @implementation mozRadioButtonAccessible
77 - (NSArray*)moxLinkedUIElements {
78   return [[self getRelationsByType:RelationType::MEMBER_OF]
79       arrayByAddingObjectsFromArray:[super moxLinkedUIElements]];
82 @end
84 @implementation mozCheckboxAccessible
86 - (int)isChecked {
87   // check if we're checked or in a mixed state
88   uint64_t state =
89       [self stateWithMask:(states::CHECKED | states::PRESSED | states::MIXED)];
90   if (state & (states::CHECKED | states::PRESSED)) {
91     return kChecked;
92   }
94   if (state & states::MIXED) {
95     return kMixed;
96   }
98   return kUnchecked;
101 - (id)moxValue {
102   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
104   return [NSNumber numberWithInt:[self isChecked]];
106   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
109 - (void)stateChanged:(uint64_t)state isEnabled:(BOOL)enabled {
110   [super stateChanged:state isEnabled:enabled];
112   if (state & (states::CHECKED | states::PRESSED | states::MIXED)) {
113     [self moxPostNotification:NSAccessibilityValueChangedNotification];
114   }
117 @end
119 @implementation mozPaneAccessible
121 - (NSArray*)moxChildren {
122   // By default, all tab panels are exposed in the a11y tree
123   // even if the tab they represent isn't the active tab. To
124   // prevent VoiceOver from navigating background tab content,
125   // only expose the tab panel that is currently on screen.
126   for (mozAccessible* child in [super moxChildren]) {
127     if (!([child state] & states::OFFSCREEN)) {
128       return [NSArray arrayWithObject:GetObjectOrRepresentedView(child)];
129     }
130   }
131   MOZ_ASSERT_UNREACHABLE("We have no on screen tab content?");
132   return @[];
135 @end
137 @implementation mozRangeAccessible
139 - (id)moxValue {
140   return [NSNumber numberWithDouble:mGeckoAccessible->CurValue()];
143 - (id)moxMinValue {
144   return [NSNumber numberWithDouble:mGeckoAccessible->MinValue()];
147 - (id)moxMaxValue {
148   return [NSNumber numberWithDouble:mGeckoAccessible->MaxValue()];
151 - (NSString*)moxOrientation {
152   RefPtr<AccAttributes> attributes = mGeckoAccessible->Attributes();
153   if (attributes) {
154     nsAutoString result;
155     attributes->GetAttribute(nsGkAtoms::aria_orientation, result);
156     if (result.Equals(u"horizontal"_ns)) {
157       return NSAccessibilityHorizontalOrientationValue;
158     } else if (result.Equals(u"vertical"_ns)) {
159       return NSAccessibilityVerticalOrientationValue;
160     }
161   }
163   return NSAccessibilityUnknownOrientationValue;
166 - (void)handleAccessibleEvent:(uint32_t)eventType {
167   switch (eventType) {
168     case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE:
169     case nsIAccessibleEvent::EVENT_VALUE_CHANGE:
170       [self moxPostNotification:NSAccessibilityValueChangedNotification];
171       break;
172     default:
173       [super handleAccessibleEvent:eventType];
174       break;
175   }
178 @end
180 @implementation mozMeterAccessible
182 - (NSString*)moxValueDescription {
183   nsAutoString valueDesc;
184   mGeckoAccessible->Value(valueDesc);
185   if (mGeckoAccessible->TagName() != nsGkAtoms::meter) {
186     // We're dealing with an aria meter, which shouldn't get
187     // a value region.
188     return nsCocoaUtils::ToNSString(valueDesc);
189   }
191   if (!valueDesc.IsEmpty()) {
192     // Append a comma to separate the existing value description
193     // from the value region.
194     valueDesc.Append(u", "_ns);
195   }
196   // We need to concat the given value description
197   // with a description of the value as either optimal,
198   // suboptimal, or critical.
199   int32_t region;
200   if (mGeckoAccessible->IsRemote()) {
201     region = mGeckoAccessible->AsRemote()->ValueRegion();
202   } else {
203     HTMLMeterAccessible* localMeter =
204         static_cast<HTMLMeterAccessible*>(mGeckoAccessible->AsLocal());
205     region = localMeter->ValueRegion();
206   }
208   if (region == 1) {
209     valueDesc.Append(u"Optimal value"_ns);
210   } else if (region == 0) {
211     valueDesc.Append(u"Suboptimal value"_ns);
212   } else {
213     MOZ_ASSERT(region == -1);
214     valueDesc.Append(u"Critical value"_ns);
215   }
217   return nsCocoaUtils::ToNSString(valueDesc);
220 @end
222 @implementation mozIncrementableAccessible
224 - (NSString*)moxValueDescription {
225   nsAutoString valueDesc;
226   mGeckoAccessible->Value(valueDesc);
227   return nsCocoaUtils::ToNSString(valueDesc);
230 - (void)moxSetValue:(id)value {
231   [self setValue:([value doubleValue])];
234 - (void)moxPerformIncrement {
235   [self changeValueBySteps:1];
238 - (void)moxPerformDecrement {
239   [self changeValueBySteps:-1];
243  * Updates the accessible's current value by factor and step.
245  * factor: A signed integer representing the number of times to
246  *    apply step to the current value. A positive value will increment,
247  *    while a negative one will decrement.
248  * step: An unsigned integer specified by the webauthor and indicating the
249  *    amount by which to increment/decrement the current value.
250  */
251 - (void)changeValueBySteps:(int)factor {
252   MOZ_ASSERT(mGeckoAccessible, "mGeckoAccessible is null");
254   double newValue =
255       mGeckoAccessible->CurValue() + (mGeckoAccessible->Step() * factor);
256   [self setValue:(newValue)];
260  * Updates the accessible's current value to the specified value
261  */
262 - (void)setValue:(double)value {
263   MOZ_ASSERT(mGeckoAccessible, "mGeckoAccessible is null");
264   mGeckoAccessible->SetCurValue(value);
267 @end
269 @implementation mozDatePickerAccessible
271 - (NSString*)moxTitle {
272   return utils::LocalizedString(u"dateField"_ns);
275 @end