mozActionElements.mm (7011B)
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/. */ 7 8 #import "mozActionElements.h" 9 10 #import "MacUtils.h" 11 #include "LocalAccessible-inl.h" 12 #include "DocAccessible.h" 13 #include "XULTabAccessible.h" 14 #include "HTMLFormControlAccessible.h" 15 16 #include "nsCocoaUtils.h" 17 18 using namespace mozilla::a11y; 19 20 @implementation mozButtonAccessible 21 22 - (NSNumber*)moxHasPopup { 23 return @([self stateWithMask:states::HASPOPUP] != 0); 24 } 25 26 - (NSString*)moxPopupValue { 27 if ([self stateWithMask:states::HASPOPUP] != 0) { 28 return utils::GetAccAttr(self, nsGkAtoms::aria_haspopup); 29 } 30 31 return nil; 32 } 33 34 @end 35 36 @implementation mozPopupButtonAccessible 37 38 - (NSString*)moxTitle { 39 // Popup buttons don't have titles. 40 return @""; 41 } 42 43 - (BOOL)moxBlockSelector:(SEL)selector { 44 if (selector == @selector(moxHasPopup)) { 45 return YES; 46 } 47 48 return [super moxBlockSelector:selector]; 49 } 50 51 - (NSArray*)moxChildren { 52 if ([self stateWithMask:states::EXPANDED] == 0) { 53 // If the popup button is collapsed don't return its children. 54 return @[]; 55 } 56 57 return [super moxChildren]; 58 } 59 60 - (void)stateChanged:(uint64_t)state isEnabled:(BOOL)enabled { 61 [super stateChanged:state isEnabled:enabled]; 62 63 if (state == states::EXPANDED) { 64 // If the EXPANDED state is updated, fire AXMenu events on the 65 // popups child which is the actual menu. 66 if (mozAccessible* popup = (mozAccessible*)[self childAt:0]) { 67 [popup moxPostNotification:(enabled ? @"AXMenuOpened" : @"AXMenuClosed")]; 68 } 69 } 70 } 71 72 @end 73 74 @implementation mozRadioButtonAccessible 75 76 - (NSArray*)moxLinkedUIElements { 77 return [[self getRelationsByType:RelationType::MEMBER_OF] 78 arrayByAddingObjectsFromArray:[super moxLinkedUIElements]]; 79 } 80 81 @end 82 83 @implementation mozCheckboxAccessible 84 85 - (int)isChecked { 86 // check if we're checked or in a mixed state 87 uint64_t state = 88 [self stateWithMask:(states::CHECKED | states::PRESSED | states::MIXED)]; 89 if (state & (states::CHECKED | states::PRESSED)) { 90 return kChecked; 91 } 92 93 if (state & states::MIXED) { 94 return kMixed; 95 } 96 97 return kUnchecked; 98 } 99 100 - (id)moxValue { 101 NS_OBJC_BEGIN_TRY_BLOCK_RETURN; 102 103 return [NSNumber numberWithInt:[self isChecked]]; 104 105 NS_OBJC_END_TRY_BLOCK_RETURN(nil); 106 } 107 108 - (void)stateChanged:(uint64_t)state isEnabled:(BOOL)enabled { 109 [super stateChanged:state isEnabled:enabled]; 110 111 if (state & (states::CHECKED | states::PRESSED | states::MIXED)) { 112 [self moxPostNotification:NSAccessibilityValueChangedNotification]; 113 } 114 } 115 116 @end 117 118 @implementation mozPaneAccessible 119 120 - (NSArray*)moxChildren { 121 // By default, all tab panels are exposed in the a11y tree 122 // even if the tab they represent isn't the active tab. To 123 // prevent VoiceOver from navigating background tab content, 124 // only expose the tab panel that is currently on screen. 125 for (mozAccessible* child in [super moxChildren]) { 126 if (!([child state] & states::OFFSCREEN)) { 127 return [NSArray arrayWithObject:GetObjectOrRepresentedView(child)]; 128 } 129 } 130 MOZ_ASSERT_UNREACHABLE("We have no on screen tab content?"); 131 return @[]; 132 } 133 134 @end 135 136 @implementation mozRangeAccessible 137 138 - (id)moxValue { 139 return [NSNumber numberWithDouble:mGeckoAccessible->CurValue()]; 140 } 141 142 - (id)moxMinValue { 143 return [NSNumber numberWithDouble:mGeckoAccessible->MinValue()]; 144 } 145 146 - (id)moxMaxValue { 147 return [NSNumber numberWithDouble:mGeckoAccessible->MaxValue()]; 148 } 149 150 - (NSString*)moxOrientation { 151 RefPtr<AccAttributes> attributes = mGeckoAccessible->Attributes(); 152 if (attributes) { 153 nsAutoString result; 154 attributes->GetAttribute(nsGkAtoms::aria_orientation, result); 155 if (result.Equals(u"horizontal"_ns)) { 156 return NSAccessibilityHorizontalOrientationValue; 157 } else if (result.Equals(u"vertical"_ns)) { 158 return NSAccessibilityVerticalOrientationValue; 159 } 160 } 161 162 return NSAccessibilityUnknownOrientationValue; 163 } 164 165 - (void)handleAccessibleEvent:(uint32_t)eventType { 166 switch (eventType) { 167 case nsIAccessibleEvent::EVENT_TEXT_VALUE_CHANGE: 168 case nsIAccessibleEvent::EVENT_VALUE_CHANGE: 169 [self moxPostNotification:NSAccessibilityValueChangedNotification]; 170 break; 171 default: 172 [super handleAccessibleEvent:eventType]; 173 break; 174 } 175 } 176 177 @end 178 179 @implementation mozMeterAccessible 180 181 - (NSString*)moxValueDescription { 182 nsAutoString valueDesc; 183 mGeckoAccessible->Value(valueDesc); 184 if (mGeckoAccessible->TagName() != nsGkAtoms::meter) { 185 // We're dealing with an aria meter, which shouldn't get 186 // a value region. 187 return nsCocoaUtils::ToNSString(valueDesc); 188 } 189 190 if (!valueDesc.IsEmpty()) { 191 // Append a comma to separate the existing value description 192 // from the value region. 193 valueDesc.Append(u", "_ns); 194 } 195 // We need to concat the given value description 196 // with a description of the value as either optimal, 197 // suboptimal, or critical. 198 int32_t region; 199 if (mGeckoAccessible->IsRemote()) { 200 region = mGeckoAccessible->AsRemote()->ValueRegion(); 201 } else { 202 HTMLMeterAccessible* localMeter = 203 static_cast<HTMLMeterAccessible*>(mGeckoAccessible->AsLocal()); 204 region = localMeter->ValueRegion(); 205 } 206 207 if (region == 1) { 208 valueDesc.Append(u"Optimal value"_ns); 209 } else if (region == 0) { 210 valueDesc.Append(u"Suboptimal value"_ns); 211 } else { 212 MOZ_ASSERT(region == -1); 213 valueDesc.Append(u"Critical value"_ns); 214 } 215 216 return nsCocoaUtils::ToNSString(valueDesc); 217 } 218 219 @end 220 221 @implementation mozIncrementableAccessible 222 223 - (NSString*)moxValueDescription { 224 nsAutoString valueDesc; 225 mGeckoAccessible->Value(valueDesc); 226 return nsCocoaUtils::ToNSString(valueDesc); 227 } 228 229 - (void)moxSetValue:(id)value { 230 [self setValue:([value doubleValue])]; 231 } 232 233 - (void)moxPerformIncrement { 234 [self changeValueBySteps:1]; 235 } 236 237 - (void)moxPerformDecrement { 238 [self changeValueBySteps:-1]; 239 } 240 241 /* 242 * Updates the accessible's current value by factor and step. 243 * 244 * factor: A signed integer representing the number of times to 245 * apply step to the current value. A positive value will increment, 246 * while a negative one will decrement. 247 * step: An unsigned integer specified by the webauthor and indicating the 248 * amount by which to increment/decrement the current value. 249 */ 250 - (void)changeValueBySteps:(int)factor { 251 MOZ_ASSERT(mGeckoAccessible, "mGeckoAccessible is null"); 252 253 double newValue = 254 mGeckoAccessible->CurValue() + (mGeckoAccessible->Step() * factor); 255 [self setValue:(newValue)]; 256 } 257 258 /* 259 * Updates the accessible's current value to the specified value 260 */ 261 - (void)setValue:(double)value { 262 MOZ_ASSERT(mGeckoAccessible, "mGeckoAccessible is null"); 263 mGeckoAccessible->SetCurValue(value); 264 } 265 266 @end 267 268 @implementation mozDatePickerAccessible 269 270 - (NSString*)moxTitle { 271 return utils::LocalizedString(u"dateField"_ns); 272 } 273 274 @end