mozTextAccessible.mm (10554B)
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 #include "AccAttributes.h" 9 #include "HyperTextAccessible-inl.h" 10 #include "LocalAccessible-inl.h" 11 #include "mozilla/a11y/PDocAccessible.h" 12 #include "nsCocoaUtils.h" 13 #include "nsObjCExceptions.h" 14 #include "TextLeafAccessible.h" 15 16 #import "mozTextAccessible.h" 17 #import "GeckoTextMarker.h" 18 #import "MOXTextMarkerDelegate.h" 19 20 using namespace mozilla; 21 using namespace mozilla::a11y; 22 23 inline bool ToNSRange(id aValue, NSRange* aRange) { 24 MOZ_ASSERT(aRange, "aRange is nil"); 25 26 if ([aValue isKindOfClass:[NSValue class]] && 27 strcmp([(NSValue*)aValue objCType], @encode(NSRange)) == 0) { 28 *aRange = [aValue rangeValue]; 29 return true; 30 } 31 32 return false; 33 } 34 35 inline NSString* ToNSString(id aValue) { 36 if ([aValue isKindOfClass:[NSString class]]) { 37 return aValue; 38 } 39 40 return nil; 41 } 42 43 static GeckoTextMarkerRange GetSelectionInObject(mozAccessible* aObj) { 44 id<MOXTextMarkerSupport> delegate = [aObj moxTextMarkerDelegate]; 45 GeckoTextMarkerRange selection = 46 [static_cast<MOXTextMarkerDelegate*>(delegate) selection]; 47 48 if (!selection.IsValid() || !selection.Crop([aObj geckoAccessible])) { 49 // The selection is not in this accessible. Return invalid range. 50 return GeckoTextMarkerRange(); 51 } 52 53 return selection; 54 } 55 56 static GeckoTextMarkerRange GetTextMarkerRangeFromRange(mozAccessible* aObj, 57 NSValue* aRange) { 58 NSRange r = [aRange rangeValue]; 59 Accessible* acc = [aObj geckoAccessible]; 60 61 GeckoTextMarker startMarker = 62 GeckoTextMarker::MarkerFromIndex(acc, r.location); 63 64 GeckoTextMarker endMarker = 65 GeckoTextMarker::MarkerFromIndex(acc, r.location + r.length); 66 67 return GeckoTextMarkerRange(startMarker, endMarker); 68 } 69 70 @implementation mozAccessible (TextField) 71 72 - (NSNumber*)moxInsertionPointLineNumber { 73 MOZ_ASSERT(mGeckoAccessible); 74 75 int32_t lineNumber = -1; 76 if (HyperTextAccessibleBase* textAcc = mGeckoAccessible->AsHyperTextBase()) { 77 lineNumber = textAcc->CaretLineNumber() - 1; 78 } 79 80 return (lineNumber >= 0) ? [NSNumber numberWithInt:lineNumber] : nil; 81 } 82 83 - (NSNumber*)moxNumberOfCharacters { 84 return @([[self moxValue] length]); 85 } 86 87 - (NSString*)moxSelectedText { 88 GeckoTextMarkerRange selection = GetSelectionInObject(self); 89 if (!selection.IsValid()) { 90 return nil; 91 } 92 93 return selection.Text(); 94 } 95 96 - (NSValue*)moxSelectedTextRange { 97 GeckoTextMarkerRange selection = GetSelectionInObject(self); 98 if (!selection.IsValid()) { 99 return nil; 100 } 101 102 GeckoTextMarkerRange fromStartToSelection( 103 GeckoTextMarker(mGeckoAccessible, 0), selection.Start()); 104 105 return [NSValue valueWithRange:NSMakeRange(fromStartToSelection.Length(), 106 selection.Length())]; 107 } 108 109 - (NSValue*)moxVisibleCharacterRange { 110 // XXX this won't work with Textarea and such as we actually don't give 111 // the visible character range. 112 return [NSValue valueWithRange:NSMakeRange(0, [[self moxValue] length])]; 113 } 114 115 - (void)moxSetValue:(id)value { 116 MOZ_ASSERT(mGeckoAccessible); 117 118 nsString text; 119 nsCocoaUtils::GetStringForNSString(value, text); 120 if (HyperTextAccessibleBase* textAcc = mGeckoAccessible->AsHyperTextBase()) { 121 textAcc->ReplaceText(text); 122 } 123 } 124 125 - (void)moxSetSelectedText:(NSString*)selectedText { 126 MOZ_ASSERT(mGeckoAccessible); 127 128 NSString* stringValue = ToNSString(selectedText); 129 if (!stringValue) { 130 return; 131 } 132 133 HyperTextAccessibleBase* textAcc = mGeckoAccessible->AsHyperTextBase(); 134 if (!textAcc) { 135 return; 136 } 137 int32_t start = 0, end = 0; 138 textAcc->SelectionBoundsAt(0, &start, &end); 139 nsString text; 140 nsCocoaUtils::GetStringForNSString(stringValue, text); 141 textAcc->SelectionBoundsAt(0, &start, &end); 142 textAcc->DeleteText(start, end - start); 143 textAcc->InsertText(text, start); 144 } 145 146 - (void)moxSetSelectedTextRange:(NSValue*)selectedTextRange { 147 GeckoTextMarkerRange markerRange = 148 GetTextMarkerRangeFromRange(self, selectedTextRange); 149 150 if (markerRange.IsValid()) { 151 markerRange.Select(); 152 } 153 } 154 155 - (void)moxSetVisibleCharacterRange:(NSValue*)visibleCharacterRange { 156 MOZ_ASSERT(mGeckoAccessible); 157 158 NSRange range; 159 if (!ToNSRange(visibleCharacterRange, &range)) { 160 return; 161 } 162 163 if (HyperTextAccessibleBase* textAcc = mGeckoAccessible->AsHyperTextBase()) { 164 textAcc->ScrollSubstringTo(range.location, range.location + range.length, 165 nsIAccessibleScrollType::SCROLL_TYPE_TOP_EDGE); 166 } 167 } 168 169 - (NSString*)moxStringForRange:(NSValue*)range { 170 GeckoTextMarkerRange markerRange = GetTextMarkerRangeFromRange(self, range); 171 172 if (!markerRange.IsValid()) { 173 return nil; 174 } 175 176 return markerRange.Text(); 177 } 178 179 - (NSAttributedString*)moxAttributedStringForRange:(NSValue*)range { 180 GeckoTextMarkerRange markerRange = GetTextMarkerRangeFromRange(self, range); 181 182 if (!markerRange.IsValid()) { 183 return nil; 184 } 185 186 return markerRange.AttributedText(); 187 } 188 189 - (NSValue*)moxRangeForLine:(NSNumber*)line { 190 // XXX: actually get the integer value for the line # 191 return [NSValue valueWithRange:NSMakeRange(0, [[self moxValue] length])]; 192 } 193 194 - (NSNumber*)moxLineForIndex:(NSNumber*)index { 195 // XXX: actually return the line # 196 return @0; 197 } 198 199 - (NSValue*)moxBoundsForRange:(NSValue*)range { 200 GeckoTextMarkerRange markerRange = GetTextMarkerRangeFromRange(self, range); 201 202 if (!markerRange.IsValid()) { 203 return nil; 204 } 205 206 return markerRange.Bounds(); 207 } 208 209 - (BOOL)moxIsTextField { 210 return !mGeckoAccessible->HasNumericValue() && 211 mGeckoAccessible->IsEditableRoot(); 212 } 213 214 - (BOOL)blockTextFieldMethod:(SEL)selector { 215 // These are the editable text methods defined in this category. 216 // We want to block them in certain cases. 217 if (selector != @selector(moxNumberOfCharacters) && 218 selector != @selector(moxInsertionPointLineNumber) && 219 selector != @selector(moxSelectedText) && 220 selector != @selector(moxSelectedTextRange) && 221 selector != @selector(moxVisibleCharacterRange) && 222 selector != @selector(moxSetSelectedText:) && 223 selector != @selector(moxSetSelectedTextRange:) && 224 selector != @selector(moxSetVisibleCharacterRange:) && 225 selector != @selector(moxStringForRange:) && 226 selector != @selector(moxAttributedStringForRange:) && 227 selector != @selector(moxRangeForLine:) && 228 selector != @selector(moxLineForIndex:) && 229 selector != @selector(moxBoundsForRange:) && 230 selector != @selector(moxSetValue:)) { 231 return NO; 232 } 233 234 if ([[mozAccessible class] instanceMethodForSelector:selector] != 235 [self methodForSelector:selector]) { 236 // This method was overridden by a subclass, so let it through. 237 return NO; 238 } 239 240 if (![self moxIsTextField]) { 241 // This is not an editable root, so block these methods. 242 return YES; 243 } 244 245 if (selector == @selector(moxSetValue:) && 246 [self stateWithMask:states::EDITABLE] == 0) { 247 // The editable is read-only, so block setValue: 248 // Bug 1995330 - should rely on READONLY/UNAVAILABLE here. 249 return YES; 250 } 251 252 // Let these methods through. 253 return NO; 254 } 255 256 - (void)handleAccessibleTextChangeEvent:(NSString*)change 257 inserted:(BOOL)isInserted 258 inContainer:(Accessible*)container 259 at:(int32_t)start { 260 MOZ_ASSERT([self moxIsTextField]); 261 GeckoTextMarker startMarker(container, start); 262 NSDictionary* userInfo = @{ 263 @"AXTextChangeElement" : self, 264 @"AXTextStateChangeType" : @(AXTextStateChangeTypeEdit), 265 @"AXTextChangeValues" : @[ @{ 266 @"AXTextChangeValue" : (change ? change : @""), 267 @"AXTextChangeValueStartMarker" : 268 (__bridge id)startMarker.CreateAXTextMarker(), 269 @"AXTextEditType" : isInserted ? @(AXTextEditTypeTyping) 270 : @(AXTextEditTypeDelete) 271 } ] 272 }; 273 274 mozAccessible* webArea = [self topWebArea]; 275 [webArea moxPostNotification:NSAccessibilityValueChangedNotification 276 withUserInfo:userInfo]; 277 [self moxPostNotification:NSAccessibilityValueChangedNotification 278 withUserInfo:userInfo]; 279 280 [self moxPostNotification:NSAccessibilityValueChangedNotification]; 281 } 282 283 @end 284 285 @implementation mozTextLeafAccessible 286 287 - (BOOL)moxBlockSelector:(SEL)selector { 288 if (selector == @selector(moxChildren) || selector == @selector 289 (moxTitleUIElement)) { 290 return YES; 291 } 292 293 return [super moxBlockSelector:selector]; 294 } 295 296 - (NSString*)moxValue { 297 MOZ_ASSERT(mGeckoAccessible); 298 299 nsAutoString name; 300 mGeckoAccessible->Name(name); 301 302 if (nsCoreUtils::IsWhitespaceString(name)) { 303 return nil; 304 } 305 306 return nsCocoaUtils::ToNSString(name); 307 } 308 309 - (NSString*)moxTitle { 310 return nil; 311 } 312 313 - (NSString*)moxLabel { 314 return nil; 315 } 316 317 - (BOOL)moxIgnoreWithParent:(mozAccessible*)parent { 318 // Don't render text nodes that are completely empty 319 // or those that should be ignored based on our 320 // standard ignore rules 321 return [self moxValue] == nil || [super moxIgnoreWithParent:parent]; 322 } 323 324 static GeckoTextMarkerRange TextMarkerSubrange(Accessible* aAccessible, 325 NSValue* aRange) { 326 GeckoTextMarkerRange textMarkerRange(aAccessible); 327 GeckoTextMarker start = textMarkerRange.Start(); 328 GeckoTextMarker end = textMarkerRange.End(); 329 330 NSRange r = [aRange rangeValue]; 331 start.Offset() += r.location; 332 end.Offset() = start.Offset() + r.length; 333 334 textMarkerRange = GeckoTextMarkerRange(start, end); 335 // Crop range to accessible 336 textMarkerRange.Crop(aAccessible); 337 338 return textMarkerRange; 339 } 340 341 - (NSString*)moxStringForRange:(NSValue*)range { 342 MOZ_ASSERT(mGeckoAccessible); 343 GeckoTextMarkerRange textMarkerRange = 344 TextMarkerSubrange(mGeckoAccessible, range); 345 346 return textMarkerRange.Text(); 347 } 348 349 - (NSAttributedString*)moxAttributedStringForRange:(NSValue*)range { 350 MOZ_ASSERT(mGeckoAccessible); 351 GeckoTextMarkerRange textMarkerRange = 352 TextMarkerSubrange(mGeckoAccessible, range); 353 354 return textMarkerRange.AttributedText(); 355 } 356 357 - (NSValue*)moxBoundsForRange:(NSValue*)range { 358 MOZ_ASSERT(mGeckoAccessible); 359 GeckoTextMarkerRange textMarkerRange = 360 TextMarkerSubrange(mGeckoAccessible, range); 361 362 return textMarkerRange.Bounds(); 363 } 364 365 @end