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 20using namespace mozilla; 21using namespace mozilla::a11y; 22 23inline 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 35inline NSString* ToNSString(id aValue) { 36 if ([aValue isKindOfClass:[NSString class]]) { 37 return aValue; 38 } 39 40 return nil; 41} 42 43@interface mozTextAccessible () 44- (long)textLength; 45- (BOOL)isReadOnly; 46- (NSString*)text; 47- (GeckoTextMarkerRange)selection; 48- (GeckoTextMarkerRange)textMarkerRangeFromRange:(NSValue*)range; 49@end 50 51@implementation mozTextAccessible 52 53- (NSString*)moxTitle { 54 return @""; 55} 56 57- (id)moxValue { 58 // Apple's SpeechSynthesisServer expects AXValue to return an AXStaticText 59 // object's AXSelectedText attribute. See bug 674612 for details. 60 // Also if there is no selected text, we return the full text. 61 // See bug 369710 for details. 62 if ([[self moxRole] isEqualToString:NSAccessibilityStaticTextRole]) { 63 NSString* selectedText = [self moxSelectedText]; 64 return (selectedText && [selectedText length]) ? selectedText : [self text]; 65 } 66 67 return [self text]; 68} 69 70- (id)moxRequired { 71 return @([self stateWithMask:states::REQUIRED] != 0); 72} 73 74- (NSString*)moxInvalid { 75 if ([self stateWithMask:states::INVALID] != 0) { 76 // If the attribute exists, it has one of four values: true, false, 77 // grammar, or spelling. We query the attribute value here in order 78 // to find the correct string to return. 79 RefPtr<AccAttributes> attributes; 80 if (LocalAccessible* acc = mGeckoAccessible.AsAccessible()) { 81 HyperTextAccessible* text = acc->AsHyperText(); 82 if (text && text->IsTextRole()) { 83 attributes = text->DefaultTextAttributes(); 84 } 85 } else { 86 RemoteAccessible* proxy = mGeckoAccessible.AsProxy(); 87 proxy->DefaultTextAttributes(&attributes); 88 } 89 90 nsAutoString invalidStr; 91 if (!attributes || 92 !attributes->GetAttribute(nsGkAtoms::invalid, invalidStr)) { 93 return @"true"; 94 } 95 return nsCocoaUtils::ToNSString(invalidStr); 96 } 97 98 // If the flag is not set, we return false. 99 return @"false"; 100} 101 102- (NSNumber*)moxInsertionPointLineNumber { 103 MOZ_ASSERT(!mGeckoAccessible.IsNull()); 104 105 int32_t lineNumber = -1; 106 if (mGeckoAccessible.IsAccessible()) { 107 if (HyperTextAccessible* textAcc = 108 mGeckoAccessible.AsAccessible()->AsHyperText()) { 109 lineNumber = textAcc->CaretLineNumber() - 1; 110 } 111 } else { 112 lineNumber = mGeckoAccessible.AsProxy()->CaretLineNumber() - 1; 113 } 114 115 return (lineNumber >= 0) ? [NSNumber numberWithInt:lineNumber] : nil; 116} 117 118- (NSString*)moxRole { 119 if ([self stateWithMask:states::MULTI_LINE]) { 120 return NSAccessibilityTextAreaRole; 121 } 122 123 return [super moxRole]; 124} 125 126- (NSString*)moxSubrole { 127 MOZ_ASSERT(!mGeckoAccessible.IsNull()); 128 129 if (mRole == roles::PASSWORD_TEXT) { 130 return NSAccessibilitySecureTextFieldSubrole; 131 } 132 133 if (mRole == roles::ENTRY) { 134 LocalAccessible* acc = mGeckoAccessible.AsAccessible(); 135 RemoteAccessible* proxy = mGeckoAccessible.AsProxy(); 136 if ((acc && acc->IsSearchbox()) || (proxy && proxy->IsSearchbox())) { 137 return @"AXSearchField"; 138 } 139 } 140 141 return nil; 142} 143 144- (NSNumber*)moxNumberOfCharacters { 145 return @([self textLength]); 146} 147 148- (NSString*)moxSelectedText { 149 GeckoTextMarkerRange selection = [self selection]; 150 if (!selection.IsValid()) { 151 return nil; 152 } 153 154 return selection.Text(); 155} 156 157- (NSValue*)moxSelectedTextRange { 158 GeckoTextMarkerRange selection = [self selection]; 159 if (!selection.IsValid()) { 160 return nil; 161 } 162 163 GeckoTextMarkerRange fromStartToSelection( 164 GeckoTextMarker(mGeckoAccessible, 0), selection.mStart); 165 166 return [NSValue valueWithRange:NSMakeRange(fromStartToSelection.Length(), 167 selection.Length())]; 168} 169 170- (NSValue*)moxVisibleCharacterRange { 171 // XXX this won't work with Textarea and such as we actually don't give 172 // the visible character range. 173 return [NSValue valueWithRange:NSMakeRange(0, [self textLength])]; 174} 175 176- (BOOL)moxBlockSelector:(SEL)selector { 177 if (selector == @selector(moxSetValue:) && [self isReadOnly]) { 178 return YES; 179 } 180 181 return [super moxBlockSelector:selector]; 182} 183 184- (void)moxSetValue:(id)value { 185 MOZ_ASSERT(!mGeckoAccessible.IsNull()); 186 187 nsString text; 188 nsCocoaUtils::GetStringForNSString(value, text); 189 if (mGeckoAccessible.IsAccessible()) { 190 if (HyperTextAccessible* textAcc = 191 mGeckoAccessible.AsAccessible()->AsHyperText()) { 192 textAcc->ReplaceText(text); 193 } 194 } else { 195 mGeckoAccessible.AsProxy()->ReplaceText(text); 196 } 197} 198 199- (void)moxSetSelectedText:(NSString*)selectedText { 200 MOZ_ASSERT(!mGeckoAccessible.IsNull()); 201 202 NSString* stringValue = ToNSString(selectedText); 203 if (!stringValue) { 204 return; 205 } 206 207 int32_t start = 0, end = 0; 208 nsString text; 209 if (mGeckoAccessible.IsAccessible()) { 210 if (HyperTextAccessible* textAcc = 211 mGeckoAccessible.AsAccessible()->AsHyperText()) { 212 textAcc->SelectionBoundsAt(0, &start, &end); 213 textAcc->DeleteText(start, end - start); 214 nsCocoaUtils::GetStringForNSString(stringValue, text); 215 textAcc->InsertText(text, start); 216 } 217 } else { 218 RemoteAccessible* proxy = mGeckoAccessible.AsProxy(); 219 nsString data; 220 proxy->SelectionBoundsAt(0, data, &start, &end); 221 proxy->DeleteText(start, end - start); 222 nsCocoaUtils::GetStringForNSString(stringValue, text); 223 proxy->InsertText(text, start); 224 } 225} 226 227- (void)moxSetSelectedTextRange:(NSValue*)selectedTextRange { 228 GeckoTextMarkerRange markerRange = 229 [self textMarkerRangeFromRange:selectedTextRange]; 230 231 markerRange.Select(); 232} 233 234- (void)moxSetVisibleCharacterRange:(NSValue*)visibleCharacterRange { 235 MOZ_ASSERT(!mGeckoAccessible.IsNull()); 236 237 NSRange range; 238 if (!ToNSRange(visibleCharacterRange, &range)) { 239 return; 240 } 241 242 if (mGeckoAccessible.IsAccessible()) { 243 if (HyperTextAccessible* textAcc = 244 mGeckoAccessible.AsAccessible()->AsHyperText()) { 245 textAcc->ScrollSubstringTo(range.location, range.location + range.length, 246 nsIAccessibleScrollType::SCROLL_TYPE_TOP_EDGE); 247 } 248 } else { 249 mGeckoAccessible.AsProxy()->ScrollSubstringTo( 250 range.location, range.location + range.length, 251 nsIAccessibleScrollType::SCROLL_TYPE_TOP_EDGE); 252 } 253} 254 255- (NSString*)moxStringForRange:(NSValue*)range { 256 GeckoTextMarkerRange markerRange = [self textMarkerRangeFromRange:range]; 257 258 if (!markerRange.IsValid()) { 259 return nil; 260 } 261 262 return markerRange.Text(); 263} 264 265- (NSAttributedString*)moxAttributedStringForRange:(NSValue*)range { 266 GeckoTextMarkerRange markerRange = [self textMarkerRangeFromRange:range]; 267 268 if (!markerRange.IsValid()) { 269 return nil; 270 } 271 272 return markerRange.AttributedText(); 273} 274 275- (NSValue*)moxRangeForLine:(NSNumber*)line { 276 // XXX: actually get the integer value for the line # 277 return [NSValue valueWithRange:NSMakeRange(0, [self textLength])]; 278} 279 280- (NSNumber*)moxLineForIndex:(NSNumber*)index { 281 // XXX: actually return the line # 282 return @0; 283} 284 285- (NSValue*)moxBoundsForRange:(NSValue*)range { 286 GeckoTextMarkerRange markerRange = [self textMarkerRangeFromRange:range]; 287 288 if (!markerRange.IsValid()) { 289 return nil; 290 } 291 292 return markerRange.Bounds(); 293} 294 295#pragma mark - mozAccessible 296 297- (void)handleAccessibleTextChangeEvent:(NSString*)change 298 inserted:(BOOL)isInserted 299 inContainer:(const AccessibleOrProxy&)container 300 at:(int32_t)start { 301 GeckoTextMarker startMarker(container, start); 302 NSDictionary* userInfo = @{ 303 @"AXTextChangeElement" : self, 304 @"AXTextStateChangeType" : @(AXTextStateChangeTypeEdit), 305 @"AXTextChangeValues" : @[ @{ 306 @"AXTextChangeValue" : (change ? change : @""), 307 @"AXTextChangeValueStartMarker" : startMarker.CreateAXTextMarker(), 308 @"AXTextEditType" : isInserted ? @(AXTextEditTypeTyping) 309 : @(AXTextEditTypeDelete) 310 } ] 311 }; 312 313 mozAccessible* webArea = [self topWebArea]; 314 [webArea moxPostNotification:NSAccessibilityValueChangedNotification 315 withUserInfo:userInfo]; 316 [self moxPostNotification:NSAccessibilityValueChangedNotification 317 withUserInfo:userInfo]; 318 319 [self moxPostNotification:NSAccessibilityValueChangedNotification]; 320} 321 322- (void)handleAccessibleEvent:(uint32_t)eventType { 323 switch (eventType) { 324 default: 325 [super handleAccessibleEvent:eventType]; 326 break; 327 } 328} 329 330#pragma mark - 331 332- (long)textLength { 333 return [[self text] length]; 334} 335 336- (BOOL)isReadOnly { 337 return [self stateWithMask:states::EDITABLE] == 0; 338} 339 340- (NSString*)text { 341 // A password text field returns an empty value 342 if (mRole == roles::PASSWORD_TEXT) { 343 return @""; 344 } 345 346 id<MOXTextMarkerSupport> delegate = [self moxTextMarkerDelegate]; 347 return [delegate 348 moxStringForTextMarkerRange:[delegate 349 moxTextMarkerRangeForUIElement:self]]; 350} 351 352- (GeckoTextMarkerRange)selection { 353 MOZ_ASSERT(!mGeckoAccessible.IsNull()); 354 355 id<MOXTextMarkerSupport> delegate = [self moxTextMarkerDelegate]; 356 GeckoTextMarkerRange selection = 357 [static_cast<MOXTextMarkerDelegate*>(delegate) selection]; 358 359 if (!selection.IsValid() || !selection.Crop(mGeckoAccessible)) { 360 // The selection is not in this accessible. Return invalid range. 361 return GeckoTextMarkerRange(); 362 } 363 364 return selection; 365} 366 367- (GeckoTextMarkerRange)textMarkerRangeFromRange:(NSValue*)range { 368 NSRange r = [range rangeValue]; 369 370 GeckoTextMarker startMarker = 371 GeckoTextMarker::MarkerFromIndex(mGeckoAccessible, r.location); 372 373 GeckoTextMarker endMarker = 374 GeckoTextMarker::MarkerFromIndex(mGeckoAccessible, r.location + r.length); 375 376 return GeckoTextMarkerRange(startMarker, endMarker); 377} 378 379@end 380 381@implementation mozTextLeafAccessible 382 383- (BOOL)moxBlockSelector:(SEL)selector { 384 if (selector == @selector(moxChildren) || selector == @selector 385 (moxTitleUIElement)) { 386 return YES; 387 } 388 389 return [super moxBlockSelector:selector]; 390} 391 392- (NSString*)moxValue { 393 return [super moxTitle]; 394} 395 396- (NSString*)moxTitle { 397 return nil; 398} 399 400- (NSString*)moxLabel { 401 return nil; 402} 403 404- (NSString*)moxStringForRange:(NSValue*)range { 405 MOZ_ASSERT(!mGeckoAccessible.IsNull()); 406 407 NSRange r = [range rangeValue]; 408 GeckoTextMarkerRange textMarkerRange(mGeckoAccessible); 409 textMarkerRange.mStart.mOffset += r.location; 410 textMarkerRange.mEnd.mOffset = 411 textMarkerRange.mStart.mOffset + r.location + r.length; 412 413 return textMarkerRange.Text(); 414} 415 416- (NSAttributedString*)moxAttributedStringForRange:(NSValue*)range { 417 MOZ_ASSERT(!mGeckoAccessible.IsNull()); 418 419 NSRange r = [range rangeValue]; 420 GeckoTextMarkerRange textMarkerRange(mGeckoAccessible); 421 textMarkerRange.mStart.mOffset += r.location; 422 textMarkerRange.mEnd.mOffset = 423 textMarkerRange.mStart.mOffset + r.location + r.length; 424 425 return textMarkerRange.AttributedText(); 426} 427 428- (NSValue*)moxBoundsForRange:(NSValue*)range { 429 MOZ_ASSERT(!mGeckoAccessible.IsNull()); 430 431 NSRange r = [range rangeValue]; 432 GeckoTextMarkerRange textMarkerRange(mGeckoAccessible); 433 434 textMarkerRange.mStart.mOffset += r.location; 435 textMarkerRange.mEnd.mOffset = textMarkerRange.mStart.mOffset + r.length; 436 437 return textMarkerRange.Bounds(); 438} 439 440@end 441