1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/accessible/src/mac/mozTextAccessible.mm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,515 @@ 1.4 +/* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "Accessible-inl.h" 1.10 +#include "HyperTextAccessible-inl.h" 1.11 +#include "TextLeafAccessible.h" 1.12 + 1.13 +#include "nsCocoaUtils.h" 1.14 +#include "nsObjCExceptions.h" 1.15 + 1.16 +#import "mozTextAccessible.h" 1.17 + 1.18 +using namespace mozilla::a11y; 1.19 + 1.20 +inline bool 1.21 +ToNSRange(id aValue, NSRange* aRange) 1.22 +{ 1.23 + NS_PRECONDITION(aRange, "aRange is nil"); 1.24 + 1.25 + if ([aValue isKindOfClass:[NSValue class]] && 1.26 + strcmp([(NSValue*)aValue objCType], @encode(NSRange)) == 0) { 1.27 + *aRange = [aValue rangeValue]; 1.28 + return true; 1.29 + } 1.30 + 1.31 + return false; 1.32 +} 1.33 + 1.34 +inline NSString* 1.35 +ToNSString(id aValue) 1.36 +{ 1.37 + if ([aValue isKindOfClass:[NSString class]]) { 1.38 + return aValue; 1.39 + } 1.40 + 1.41 + return nil; 1.42 +} 1.43 + 1.44 +@interface mozTextAccessible () 1.45 +- (NSString*)subrole; 1.46 +- (NSString*)selectedText; 1.47 +- (NSValue*)selectedTextRange; 1.48 +- (NSValue*)visibleCharacterRange; 1.49 +- (long)textLength; 1.50 +- (BOOL)isReadOnly; 1.51 +- (NSNumber*)caretLineNumber; 1.52 +- (void)setText:(NSString*)newText; 1.53 +- (NSString*)text; 1.54 +- (NSString*)stringFromRange:(NSRange*)range; 1.55 +@end 1.56 + 1.57 +@implementation mozTextAccessible 1.58 + 1.59 +- (id)initWithAccessible:(AccessibleWrap*)accessible 1.60 +{ 1.61 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; 1.62 + 1.63 + if ((self = [super initWithAccessible:accessible])) { 1.64 + mGeckoTextAccessible = accessible->AsHyperText(); 1.65 + CallQueryInterface(accessible, &mGeckoEditableTextAccessible); 1.66 + } 1.67 + return self; 1.68 + 1.69 + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; 1.70 +} 1.71 + 1.72 +- (BOOL)accessibilityIsIgnored 1.73 +{ 1.74 + return !mGeckoAccessible; 1.75 +} 1.76 + 1.77 +- (NSArray*)accessibilityAttributeNames 1.78 +{ 1.79 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; 1.80 + 1.81 + static NSMutableArray* supportedAttributes = nil; 1.82 + if (!supportedAttributes) { 1.83 + // text-specific attributes to supplement the standard one 1.84 + supportedAttributes = [[NSMutableArray alloc] initWithObjects: 1.85 + NSAccessibilitySelectedTextAttribute, // required 1.86 + NSAccessibilitySelectedTextRangeAttribute, // required 1.87 + NSAccessibilityNumberOfCharactersAttribute, // required 1.88 + NSAccessibilityVisibleCharacterRangeAttribute, // required 1.89 + NSAccessibilityInsertionPointLineNumberAttribute, 1.90 + @"AXRequired", 1.91 + @"AXInvalid", 1.92 + nil 1.93 + ]; 1.94 + [supportedAttributes addObjectsFromArray:[super accessibilityAttributeNames]]; 1.95 + } 1.96 + return supportedAttributes; 1.97 + 1.98 + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; 1.99 +} 1.100 + 1.101 +- (id)accessibilityAttributeValue:(NSString*)attribute 1.102 +{ 1.103 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; 1.104 + 1.105 + if ([attribute isEqualToString:NSAccessibilityNumberOfCharactersAttribute]) 1.106 + return [NSNumber numberWithInt:[self textLength]]; 1.107 + 1.108 + if ([attribute isEqualToString:NSAccessibilityInsertionPointLineNumberAttribute]) 1.109 + return [self caretLineNumber]; 1.110 + 1.111 + if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) 1.112 + return [self selectedTextRange]; 1.113 + 1.114 + if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) 1.115 + return [self selectedText]; 1.116 + 1.117 + if ([attribute isEqualToString:NSAccessibilityTitleAttribute]) 1.118 + return @""; 1.119 + 1.120 + if ([attribute isEqualToString:NSAccessibilityValueAttribute]) { 1.121 + // Apple's SpeechSynthesisServer expects AXValue to return an AXStaticText 1.122 + // object's AXSelectedText attribute. See bug 674612 for details. 1.123 + // Also if there is no selected text, we return the full text. 1.124 + // See bug 369710 for details. 1.125 + if ([[self role] isEqualToString:NSAccessibilityStaticTextRole]) { 1.126 + NSString* selectedText = [self selectedText]; 1.127 + return (selectedText && [selectedText length]) ? selectedText : [self text]; 1.128 + } 1.129 + 1.130 + return [self text]; 1.131 + } 1.132 + 1.133 + if ([attribute isEqualToString:@"AXRequired"]) 1.134 + return [NSNumber numberWithBool:!!(mGeckoAccessible->State() & states::REQUIRED)]; 1.135 + 1.136 + if ([attribute isEqualToString:@"AXInvalid"]) 1.137 + return [NSNumber numberWithBool:!!(mGeckoAccessible->State() & states::INVALID)]; 1.138 + 1.139 + if ([attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute]) 1.140 + return [self visibleCharacterRange]; 1.141 + 1.142 + // let mozAccessible handle all other attributes 1.143 + return [super accessibilityAttributeValue:attribute]; 1.144 + 1.145 + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; 1.146 +} 1.147 + 1.148 +- (NSArray*)accessibilityParameterizedAttributeNames 1.149 +{ 1.150 + static NSArray* supportedParametrizedAttributes = nil; 1.151 + // text specific parametrized attributes 1.152 + if (!supportedParametrizedAttributes) { 1.153 + supportedParametrizedAttributes = [[NSArray alloc] initWithObjects: 1.154 + NSAccessibilityStringForRangeParameterizedAttribute, 1.155 + NSAccessibilityLineForIndexParameterizedAttribute, 1.156 + NSAccessibilityRangeForLineParameterizedAttribute, 1.157 + NSAccessibilityAttributedStringForRangeParameterizedAttribute, 1.158 + NSAccessibilityBoundsForRangeParameterizedAttribute, 1.159 +#if DEBUG 1.160 + NSAccessibilityRangeForPositionParameterizedAttribute, 1.161 + NSAccessibilityRangeForIndexParameterizedAttribute, 1.162 + NSAccessibilityRTFForRangeParameterizedAttribute, 1.163 + NSAccessibilityStyleRangeForIndexParameterizedAttribute, 1.164 +#endif 1.165 + nil 1.166 + ]; 1.167 + } 1.168 + return supportedParametrizedAttributes; 1.169 +} 1.170 + 1.171 +- (id)accessibilityAttributeValue:(NSString*)attribute forParameter:(id)parameter 1.172 +{ 1.173 + if (!mGeckoTextAccessible) 1.174 + return nil; 1.175 + 1.176 + if ([attribute isEqualToString:NSAccessibilityStringForRangeParameterizedAttribute]) { 1.177 + NSRange range; 1.178 + if (!ToNSRange(parameter, &range)) { 1.179 +#if DEBUG 1.180 + NSLog(@"%@: range not set", attribute); 1.181 +#endif 1.182 + return @""; 1.183 + } 1.184 + 1.185 + return [self stringFromRange:&range]; 1.186 + } 1.187 + 1.188 + if ([attribute isEqualToString:NSAccessibilityRangeForLineParameterizedAttribute]) { 1.189 + // XXX: actually get the integer value for the line # 1.190 + return [NSValue valueWithRange:NSMakeRange(0, [self textLength])]; 1.191 + } 1.192 + 1.193 + if ([attribute isEqualToString:NSAccessibilityAttributedStringForRangeParameterizedAttribute]) { 1.194 + NSRange range; 1.195 + if (!ToNSRange(parameter, &range)) { 1.196 +#if DEBUG 1.197 + NSLog(@"%@: range not set", attribute); 1.198 +#endif 1.199 + return @""; 1.200 + } 1.201 + 1.202 + return [[[NSAttributedString alloc] initWithString:[self stringFromRange:&range]] autorelease]; 1.203 + } 1.204 + 1.205 + if ([attribute isEqualToString:NSAccessibilityLineForIndexParameterizedAttribute]) { 1.206 + // XXX: actually return the line # 1.207 + return [NSNumber numberWithInt:0]; 1.208 + } 1.209 + 1.210 + if ([attribute isEqualToString:NSAccessibilityBoundsForRangeParameterizedAttribute]) { 1.211 + NSRange range; 1.212 + if (!ToNSRange(parameter, &range)) { 1.213 +#if DEBUG 1.214 + NSLog(@"%@:no range", attribute); 1.215 +#endif 1.216 + return nil; 1.217 + } 1.218 + 1.219 + int32_t start = range.location; 1.220 + int32_t end = start + range.length; 1.221 + nsIntRect bounds = mGeckoTextAccessible->TextBounds(start, end); 1.222 + 1.223 + return [NSValue valueWithRect:nsCocoaUtils::GeckoRectToCocoaRect(bounds)]; 1.224 + } 1.225 + 1.226 +#if DEBUG 1.227 + NSLog(@"unhandled attribute:%@ forParameter:%@", attribute, parameter); 1.228 +#endif 1.229 + 1.230 + return nil; 1.231 +} 1.232 + 1.233 +- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute 1.234 +{ 1.235 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; 1.236 + 1.237 + if ([attribute isEqualToString:NSAccessibilityValueAttribute]) 1.238 + return ![self isReadOnly]; 1.239 + 1.240 + if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute] || 1.241 + [attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute] || 1.242 + [attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute]) 1.243 + return YES; 1.244 + 1.245 + return [super accessibilityIsAttributeSettable:attribute]; 1.246 + 1.247 + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); 1.248 +} 1.249 + 1.250 +- (void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute 1.251 +{ 1.252 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.253 + 1.254 + if (!mGeckoTextAccessible) 1.255 + return; 1.256 + 1.257 + if ([attribute isEqualToString:NSAccessibilityValueAttribute]) { 1.258 + [self setText:ToNSString(value)]; 1.259 + 1.260 + return; 1.261 + } 1.262 + 1.263 + if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) { 1.264 + NSString* stringValue = ToNSString(value); 1.265 + if (!stringValue) 1.266 + return; 1.267 + 1.268 + int32_t start = 0, end = 0; 1.269 + mGeckoTextAccessible->SelectionBoundsAt(0, &start, &end); 1.270 + mGeckoTextAccessible->DeleteText(start, end - start); 1.271 + 1.272 + nsString text; 1.273 + nsCocoaUtils::GetStringForNSString(stringValue, text); 1.274 + mGeckoTextAccessible->InsertText(text, start); 1.275 + } 1.276 + 1.277 + if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) { 1.278 + NSRange range; 1.279 + if (!ToNSRange(value, &range)) 1.280 + return; 1.281 + 1.282 + mGeckoTextAccessible->SetSelectionBoundsAt(0, range.location, 1.283 + range.location + range.length); 1.284 + return; 1.285 + } 1.286 + 1.287 + if ([attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute]) { 1.288 + NSRange range; 1.289 + if (!ToNSRange(value, &range)) 1.290 + return; 1.291 + 1.292 + mGeckoTextAccessible->ScrollSubstringTo(range.location, range.location + range.length, 1.293 + nsIAccessibleScrollType::SCROLL_TYPE_TOP_EDGE); 1.294 + return; 1.295 + } 1.296 + 1.297 + [super accessibilitySetValue:value forAttribute:attribute]; 1.298 + 1.299 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.300 +} 1.301 + 1.302 +- (NSString*)subrole 1.303 +{ 1.304 + if(mRole == roles::PASSWORD_TEXT) 1.305 + return NSAccessibilitySecureTextFieldSubrole; 1.306 + 1.307 + return nil; 1.308 +} 1.309 + 1.310 +- (void)expire 1.311 +{ 1.312 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.313 + 1.314 + mGeckoTextAccessible = nullptr; 1.315 + NS_IF_RELEASE(mGeckoEditableTextAccessible); 1.316 + [super expire]; 1.317 + 1.318 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.319 +} 1.320 + 1.321 +#pragma mark - 1.322 + 1.323 +- (BOOL)isReadOnly 1.324 +{ 1.325 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; 1.326 + 1.327 + if ([[self role] isEqualToString:NSAccessibilityStaticTextRole]) 1.328 + return YES; 1.329 + 1.330 + if (mGeckoEditableTextAccessible) 1.331 + return (mGeckoAccessible->State() & states::READONLY) == 0; 1.332 + 1.333 + return NO; 1.334 + 1.335 + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); 1.336 +} 1.337 + 1.338 +- (NSNumber*)caretLineNumber 1.339 +{ 1.340 + int32_t lineNumber = mGeckoTextAccessible ? 1.341 + mGeckoTextAccessible->CaretLineNumber() - 1 : -1; 1.342 + 1.343 + return (lineNumber >= 0) ? [NSNumber numberWithInt:lineNumber] : nil; 1.344 +} 1.345 + 1.346 +- (void)setText:(NSString*)aNewString 1.347 +{ 1.348 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.349 + 1.350 + if (mGeckoEditableTextAccessible) { 1.351 + nsString text; 1.352 + nsCocoaUtils::GetStringForNSString(aNewString, text); 1.353 + mGeckoEditableTextAccessible->SetTextContents(text); 1.354 + } 1.355 + 1.356 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.357 +} 1.358 + 1.359 +- (NSString*)text 1.360 +{ 1.361 + if (!mGeckoAccessible || !mGeckoTextAccessible) 1.362 + return nil; 1.363 + 1.364 + // A password text field returns an empty value 1.365 + if (mRole == roles::PASSWORD_TEXT) 1.366 + return @""; 1.367 + 1.368 + nsAutoString text; 1.369 + mGeckoTextAccessible->TextSubstring(0, 1.370 + nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT, 1.371 + text); 1.372 + return nsCocoaUtils::ToNSString(text); 1.373 +} 1.374 + 1.375 +- (long)textLength 1.376 +{ 1.377 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; 1.378 + 1.379 + if (!mGeckoAccessible || !mGeckoTextAccessible) 1.380 + return 0; 1.381 + 1.382 + return mGeckoTextAccessible ? mGeckoTextAccessible->CharacterCount() : 0; 1.383 + 1.384 + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0); 1.385 +} 1.386 + 1.387 +- (long)selectedTextLength 1.388 +{ 1.389 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; 1.390 + 1.391 + if (mGeckoTextAccessible) { 1.392 + int32_t start = 0, end = 0; 1.393 + mGeckoTextAccessible->SelectionBoundsAt(0, &start, &end); 1.394 + return (end - start); 1.395 + } 1.396 + return 0; 1.397 + 1.398 + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0); 1.399 +} 1.400 + 1.401 +- (NSString*)selectedText 1.402 +{ 1.403 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; 1.404 + 1.405 + if (mGeckoTextAccessible) { 1.406 + int32_t start = 0, end = 0; 1.407 + mGeckoTextAccessible->SelectionBoundsAt(0, &start, &end); 1.408 + if (start != end) { 1.409 + nsAutoString selText; 1.410 + mGeckoTextAccessible->TextSubstring(start, end, selText); 1.411 + return nsCocoaUtils::ToNSString(selText); 1.412 + } 1.413 + } 1.414 + return nil; 1.415 + 1.416 + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; 1.417 +} 1.418 + 1.419 +- (NSValue*)selectedTextRange 1.420 +{ 1.421 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; 1.422 + 1.423 + if (mGeckoTextAccessible) { 1.424 + int32_t start = 0; 1.425 + int32_t end = 0; 1.426 + int32_t count = mGeckoTextAccessible->SelectionCount(); 1.427 + 1.428 + if (count) { 1.429 + mGeckoTextAccessible->SelectionBoundsAt(0, &start, &end); 1.430 + return [NSValue valueWithRange:NSMakeRange(start, end - start)]; 1.431 + } 1.432 + 1.433 + start = mGeckoTextAccessible->CaretOffset(); 1.434 + return [NSValue valueWithRange:NSMakeRange(start != -1 ? start : 0, 0)]; 1.435 + } 1.436 + return [NSValue valueWithRange:NSMakeRange(0, 0)]; 1.437 + 1.438 + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; 1.439 +} 1.440 + 1.441 +- (NSValue*)visibleCharacterRange 1.442 +{ 1.443 + // XXX this won't work with Textarea and such as we actually don't give 1.444 + // the visible character range. 1.445 + return [NSValue valueWithRange: 1.446 + NSMakeRange(0, mGeckoTextAccessible ? 1.447 + mGeckoTextAccessible->CharacterCount() : 0)]; 1.448 +} 1.449 + 1.450 +- (void)valueDidChange 1.451 +{ 1.452 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.453 + 1.454 + NSAccessibilityPostNotification(GetObjectOrRepresentedView(self), 1.455 + NSAccessibilityValueChangedNotification); 1.456 + 1.457 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.458 +} 1.459 + 1.460 +- (void)selectedTextDidChange 1.461 +{ 1.462 + NSAccessibilityPostNotification(GetObjectOrRepresentedView(self), 1.463 + NSAccessibilitySelectedTextChangedNotification); 1.464 +} 1.465 + 1.466 +- (NSString*)stringFromRange:(NSRange*)range 1.467 +{ 1.468 + NS_PRECONDITION(mGeckoTextAccessible && range, "no Gecko text accessible or range"); 1.469 + 1.470 + nsAutoString text; 1.471 + mGeckoTextAccessible->TextSubstring(range->location, 1.472 + range->location + range->length, text); 1.473 + return nsCocoaUtils::ToNSString(text); 1.474 +} 1.475 + 1.476 +@end 1.477 + 1.478 +@implementation mozTextLeafAccessible 1.479 + 1.480 +- (NSArray*)accessibilityAttributeNames 1.481 +{ 1.482 + static NSMutableArray* supportedAttributes = nil; 1.483 + if (!supportedAttributes) { 1.484 + supportedAttributes = [[super accessibilityAttributeNames] mutableCopy]; 1.485 + [supportedAttributes removeObject:NSAccessibilityChildrenAttribute]; 1.486 + } 1.487 + 1.488 + return supportedAttributes; 1.489 +} 1.490 + 1.491 +- (id)accessibilityAttributeValue:(NSString*)attribute 1.492 +{ 1.493 + if ([attribute isEqualToString:NSAccessibilityTitleAttribute]) 1.494 + return @""; 1.495 + 1.496 + if ([attribute isEqualToString:NSAccessibilityValueAttribute]) 1.497 + return [self text]; 1.498 + 1.499 + return [super accessibilityAttributeValue:attribute]; 1.500 +} 1.501 + 1.502 +- (NSString*)text 1.503 +{ 1.504 + if (!mGeckoAccessible) 1.505 + return nil; 1.506 + 1.507 + return nsCocoaUtils::ToNSString(mGeckoAccessible->AsTextLeaf()->Text()); 1.508 +} 1.509 + 1.510 +- (long)textLength 1.511 +{ 1.512 + if (!mGeckoAccessible) 1.513 + return 0; 1.514 + 1.515 + return mGeckoAccessible->AsTextLeaf()->Text().Length(); 1.516 +} 1.517 + 1.518 +@end