1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "content/browser/accessibility/browser_accessibility_manager_mac.h" 6 7#include "base/bind.h" 8#include "base/location.h" 9#include "base/logging.h" 10#import "base/mac/mac_util.h" 11#import "base/mac/scoped_nsobject.h" 12#include "base/strings/sys_string_conversions.h" 13#include "base/strings/utf_string_conversions.h" 14#include "base/task/post_task.h" 15#include "base/time/time.h" 16#import "content/browser/accessibility/browser_accessibility_cocoa.h" 17#import "content/browser/accessibility/browser_accessibility_mac.h" 18#include "content/public/browser/browser_task_traits.h" 19#include "content/public/browser/browser_thread.h" 20#include "ui/accelerated_widget_mac/accelerated_widget_mac.h" 21#include "ui/accessibility/ax_role_properties.h" 22 23namespace { 24 25// Use same value as in Safari's WebKit. 26const int kLiveRegionChangeIntervalMS = 20; 27 28// Declare undocumented accessibility constants and enums only present in 29// WebKit. 30 31enum AXTextStateChangeType { 32 AXTextStateChangeTypeUnknown, 33 AXTextStateChangeTypeEdit, 34 AXTextStateChangeTypeSelectionMove, 35 AXTextStateChangeTypeSelectionExtend 36}; 37 38enum AXTextSelectionDirection { 39 AXTextSelectionDirectionUnknown, 40 AXTextSelectionDirectionBeginning, 41 AXTextSelectionDirectionEnd, 42 AXTextSelectionDirectionPrevious, 43 AXTextSelectionDirectionNext, 44 AXTextSelectionDirectionDiscontiguous 45}; 46 47enum AXTextSelectionGranularity { 48 AXTextSelectionGranularityUnknown, 49 AXTextSelectionGranularityCharacter, 50 AXTextSelectionGranularityWord, 51 AXTextSelectionGranularityLine, 52 AXTextSelectionGranularitySentence, 53 AXTextSelectionGranularityParagraph, 54 AXTextSelectionGranularityPage, 55 AXTextSelectionGranularityDocument, 56 AXTextSelectionGranularityAll 57}; 58 59enum AXTextEditType { 60 AXTextEditTypeUnknown, 61 AXTextEditTypeDelete, 62 AXTextEditTypeInsert, 63 AXTextEditTypeTyping, 64 AXTextEditTypeDictation, 65 AXTextEditTypeCut, 66 AXTextEditTypePaste, 67 AXTextEditTypeAttributesChange 68}; 69 70NSString* const NSAccessibilityAutocorrectionOccurredNotification = 71 @"AXAutocorrectionOccurred"; 72NSString* const NSAccessibilityLayoutCompleteNotification = @"AXLayoutComplete"; 73NSString* const NSAccessibilityLoadCompleteNotification = @"AXLoadComplete"; 74NSString* const NSAccessibilityInvalidStatusChangedNotification = 75 @"AXInvalidStatusChanged"; 76NSString* const NSAccessibilityLiveRegionCreatedNotification = 77 @"AXLiveRegionCreated"; 78NSString* const NSAccessibilityLiveRegionChangedNotification = 79 @"AXLiveRegionChanged"; 80NSString* const NSAccessibilityExpandedChanged = @"AXExpandedChanged"; 81NSString* const NSAccessibilityMenuItemSelectedNotification = 82 @"AXMenuItemSelected"; 83 84// Attributes used for NSAccessibilitySelectedTextChangedNotification and 85// NSAccessibilityValueChangedNotification. 86NSString* const NSAccessibilityTextStateChangeTypeKey = 87 @"AXTextStateChangeType"; 88NSString* const NSAccessibilityTextStateSyncKey = @"AXTextStateSync"; 89NSString* const NSAccessibilityTextSelectionDirection = 90 @"AXTextSelectionDirection"; 91NSString* const NSAccessibilityTextSelectionGranularity = 92 @"AXTextSelectionGranularity"; 93NSString* const NSAccessibilityTextSelectionChangedFocus = 94 @"AXTextSelectionChangedFocus"; 95NSString* const NSAccessibilityTextChangeElement = @"AXTextChangeElement"; 96NSString* const NSAccessibilityTextEditType = @"AXTextEditType"; 97NSString* const NSAccessibilityTextChangeValue = @"AXTextChangeValue"; 98NSString* const NSAccessibilityTextChangeValueLength = 99 @"AXTextChangeValueLength"; 100NSString* const NSAccessibilityTextChangeValues = @"AXTextChangeValues"; 101 102} // namespace 103 104namespace content { 105 106// static 107BrowserAccessibilityManager* BrowserAccessibilityManager::Create( 108 const ui::AXTreeUpdate& initial_tree, 109 BrowserAccessibilityDelegate* delegate, 110 BrowserAccessibilityFactory* factory) { 111 return new BrowserAccessibilityManagerMac(initial_tree, delegate, factory); 112} 113 114BrowserAccessibilityManagerMac* 115BrowserAccessibilityManager::ToBrowserAccessibilityManagerMac() { 116 return static_cast<BrowserAccessibilityManagerMac*>(this); 117} 118 119BrowserAccessibilityManagerMac::BrowserAccessibilityManagerMac( 120 const ui::AXTreeUpdate& initial_tree, 121 BrowserAccessibilityDelegate* delegate, 122 BrowserAccessibilityFactory* factory) 123 : BrowserAccessibilityManager(delegate, factory) { 124 Initialize(initial_tree); 125 ax_tree()->SetEnableExtraMacNodes(true); 126} 127 128BrowserAccessibilityManagerMac::~BrowserAccessibilityManagerMac() {} 129 130// static 131ui::AXTreeUpdate BrowserAccessibilityManagerMac::GetEmptyDocument() { 132 ui::AXNodeData empty_document; 133 empty_document.id = 1; 134 empty_document.role = ax::mojom::Role::kRootWebArea; 135 ui::AXTreeUpdate update; 136 update.root_id = empty_document.id; 137 update.nodes.push_back(empty_document); 138 return update; 139} 140 141BrowserAccessibility* BrowserAccessibilityManagerMac::GetFocus() const { 142 BrowserAccessibility* focus = BrowserAccessibilityManager::GetFocus(); 143 if (!focus) 144 return nullptr; 145 146 // For editable combo boxes, focus should stay on the combo box so the user 147 // will not be taken out of the combo box while typing. 148 if (focus->GetRole() == ax::mojom::Role::kTextFieldWithComboBox) 149 return focus; 150 151 // Otherwise, follow the active descendant. 152 return GetActiveDescendant(focus); 153} 154 155void BrowserAccessibilityManagerMac::FireFocusEvent( 156 BrowserAccessibility* node) { 157 BrowserAccessibilityManager::FireFocusEvent(node); 158 FireNativeMacNotification(NSAccessibilityFocusedUIElementChangedNotification, 159 node); 160} 161 162void BrowserAccessibilityManagerMac::FireBlinkEvent( 163 ax::mojom::Event event_type, 164 BrowserAccessibility* node) { 165 BrowserAccessibilityManager::FireBlinkEvent(event_type, node); 166 NSString* mac_notification = nullptr; 167 switch (event_type) { 168 case ax::mojom::Event::kAutocorrectionOccured: 169 mac_notification = NSAccessibilityAutocorrectionOccurredNotification; 170 break; 171 case ax::mojom::Event::kLayoutComplete: 172 mac_notification = NSAccessibilityLayoutCompleteNotification; 173 break; 174 default: 175 return; 176 } 177 178 FireNativeMacNotification(mac_notification, node); 179} 180 181void PostAnnouncementNotification(NSString* announcement) { 182 NSDictionary* notification_info = @{ 183 NSAccessibilityAnnouncementKey : announcement, 184 NSAccessibilityPriorityKey : @(NSAccessibilityPriorityLow) 185 }; 186 // Trigger VoiceOver speech and show on Braille display, if available. 187 // The Braille will only appear for a few seconds, and then will be replaced 188 // with the previous announcement. 189 NSAccessibilityPostNotificationWithUserInfo( 190 [NSApp mainWindow], NSAccessibilityAnnouncementRequestedNotification, 191 notification_info); 192} 193 194// Check whether the current batch of events contains the event type. 195bool BrowserAccessibilityManagerMac::IsInGeneratedEventBatch( 196 ui::AXEventGenerator::Event event_type) const { 197 for (const auto& event : event_generator()) { 198 if (event.event_params.event == event_type) 199 return true; // Announcement will already be handled via this event. 200 } 201 return false; 202} 203 204void BrowserAccessibilityManagerMac::FireGeneratedEvent( 205 ui::AXEventGenerator::Event event_type, 206 BrowserAccessibility* node) { 207 BrowserAccessibilityManager::FireGeneratedEvent(event_type, node); 208 if (!node->IsNative()) 209 return; 210 211 auto native_node = ToBrowserAccessibilityCocoa(node); 212 DCHECK(native_node); 213 214 // Refer to |AXObjectCache::postPlatformNotification| in WebKit source code. 215 NSString* mac_notification = nullptr; 216 switch (event_type) { 217 case ui::AXEventGenerator::Event::ACTIVE_DESCENDANT_CHANGED: 218 if (node->GetRole() == ax::mojom::Role::kTree) { 219 mac_notification = NSAccessibilitySelectedRowsChangedNotification; 220 } else if (node->GetRole() == ax::mojom::Role::kTextFieldWithComboBox) { 221 // Even though the selected item in the combo box has changed, we don't 222 // want to post a focus change because this will take the focus out of 223 // the combo box where the user might be typing. 224 mac_notification = NSAccessibilitySelectedChildrenChangedNotification; 225 } else { 226 // In all other cases we should post 227 // |NSAccessibilityFocusedUIElementChangedNotification|, but this is 228 // handled elsewhere. 229 return; 230 } 231 break; 232 case ui::AXEventGenerator::Event::LOAD_COMPLETE: 233 // This notification should only be fired on the top document. 234 // Iframes should use |ax::mojom::Event::kLayoutComplete| to signify that 235 // they have finished loading. 236 if (IsRootTree()) { 237 mac_notification = NSAccessibilityLoadCompleteNotification; 238 } else { 239 mac_notification = NSAccessibilityLayoutCompleteNotification; 240 } 241 break; 242 case ui::AXEventGenerator::Event::PORTAL_ACTIVATED: 243 DCHECK(IsRootTree()); 244 mac_notification = NSAccessibilityLoadCompleteNotification; 245 break; 246 case ui::AXEventGenerator::Event::INVALID_STATUS_CHANGED: 247 mac_notification = NSAccessibilityInvalidStatusChangedNotification; 248 break; 249 case ui::AXEventGenerator::Event::SELECTED_CHILDREN_CHANGED: 250 if (ui::IsTableLike(node->GetRole())) { 251 mac_notification = NSAccessibilitySelectedRowsChangedNotification; 252 } else { 253 // VoiceOver does not read anything if selection changes on the 254 // currently focused object, and the focus did not move. Detect a 255 // selection change in a where the focus did not change. 256 BrowserAccessibility* focus = GetFocus(); 257 BrowserAccessibility* container = 258 focus->PlatformGetSelectionContainer(); 259 260 if (focus && node == container && 261 container->HasState(ax::mojom::State::kMultiselectable) && 262 !IsInGeneratedEventBatch( 263 ui::AXEventGenerator::Event::ACTIVE_DESCENDANT_CHANGED) && 264 !IsInGeneratedEventBatch( 265 ui::AXEventGenerator::Event::FOCUS_CHANGED)) { 266 // Force announcement of current focus / activedescendant, even though 267 // it's not changing. This way, the user can hear the new selection 268 // state of the current object. Because VoiceOver ignores focus events 269 // to an already focused object, this is done by destroying the native 270 // object and creating a new one that receives focus. 271 static_cast<BrowserAccessibilityMac*>(focus)->ReplaceNativeObject(); 272 // Don't fire selected children change, it will sometimes override 273 // announcement of current focus. 274 return; 275 } 276 277 mac_notification = NSAccessibilitySelectedChildrenChangedNotification; 278 } 279 break; 280 case ui::AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED: { 281 mac_notification = NSAccessibilitySelectedTextChangedNotification; 282 // WebKit fires a notification both on the focused object and the page 283 // root. 284 BrowserAccessibility* focus = GetFocus(); 285 if (!focus) 286 break; // Just fire a notification on the root. 287 288 if (base::mac::IsAtLeastOS10_11()) { 289 // |NSAccessibilityPostNotificationWithUserInfo| should be used on OS X 290 // 10.11 or later to notify Voiceover about text selection changes. This 291 // API has been present on versions of OS X since 10.7 but doesn't 292 // appear to be needed by Voiceover before version 10.11. 293 NSDictionary* user_info = 294 GetUserInfoForSelectedTextChangedNotification(); 295 296 BrowserAccessibilityManager* root_manager = GetRootManager(); 297 if (!root_manager) 298 return; 299 BrowserAccessibility* root = root_manager->GetRoot(); 300 if (!root) 301 return; 302 303 NSAccessibilityPostNotificationWithUserInfo( 304 ToBrowserAccessibilityCocoa(focus), mac_notification, user_info); 305 NSAccessibilityPostNotificationWithUserInfo( 306 ToBrowserAccessibilityCocoa(root), mac_notification, user_info); 307 return; 308 } else { 309 NSAccessibilityPostNotification(ToBrowserAccessibilityCocoa(focus), 310 mac_notification); 311 } 312 break; 313 } 314 case ui::AXEventGenerator::Event::CHECKED_STATE_CHANGED: 315 mac_notification = NSAccessibilityValueChangedNotification; 316 break; 317 case ui::AXEventGenerator::Event::VALUE_CHANGED: 318 mac_notification = NSAccessibilityValueChangedNotification; 319 if (base::mac::IsAtLeastOS10_11() && !text_edits_.empty()) { 320 base::string16 deleted_text; 321 base::string16 inserted_text; 322 int32_t id = node->GetId(); 323 const auto iterator = text_edits_.find(id); 324 if (iterator != text_edits_.end()) { 325 AXTextEdit text_edit = iterator->second; 326 deleted_text = text_edit.deleted_text; 327 inserted_text = text_edit.inserted_text; 328 } 329 330 NSDictionary* user_info = GetUserInfoForValueChangedNotification( 331 native_node, deleted_text, inserted_text); 332 333 BrowserAccessibility* root = GetRoot(); 334 if (!root) 335 return; 336 337 NSAccessibilityPostNotificationWithUserInfo( 338 native_node, mac_notification, user_info); 339 NSAccessibilityPostNotificationWithUserInfo( 340 ToBrowserAccessibilityCocoa(root), mac_notification, user_info); 341 return; 342 } 343 break; 344 case ui::AXEventGenerator::Event::LIVE_REGION_CREATED: 345 mac_notification = NSAccessibilityLiveRegionCreatedNotification; 346 break; 347 case ui::AXEventGenerator::Event::ALERT: 348 NSAccessibilityPostNotification( 349 native_node, NSAccessibilityLiveRegionCreatedNotification); 350 // Voiceover requires a live region changed notification to actually 351 // announce the live region. 352 FireGeneratedEvent(ui::AXEventGenerator::Event::LIVE_REGION_CHANGED, 353 node); 354 return; 355 case ui::AXEventGenerator::Event::LIVE_REGION_CHANGED: { 356 // Voiceover seems to drop live region changed notifications if they come 357 // too soon after a live region created notification. 358 // TODO(nektar): Limit the number of changed notifications as well. 359 360 if (never_suppress_or_delay_events_for_testing_) { 361 NSAccessibilityPostNotification( 362 native_node, NSAccessibilityLiveRegionChangedNotification); 363 return; 364 } 365 366 if (base::mac::IsAtMostOS10_13()) { 367 // Use the announcement API to get around OS <= 10.13 VoiceOver bug 368 // where it stops announcing live regions after the first time focus 369 // leaves any content area. 370 // Unfortunately this produces an annoying boing sound with each live 371 // announcement, but the alternative is almost no live region support. 372 PostAnnouncementNotification( 373 base::SysUTF8ToNSString(node->GetLiveRegionText())); 374 return; 375 } 376 377 // Use native VoiceOver support for live regions. 378 base::scoped_nsobject<BrowserAccessibilityCocoa> retained_node( 379 [native_node retain]); 380 base::PostDelayedTask( 381 FROM_HERE, {BrowserThread::UI}, 382 base::BindOnce( 383 [](base::scoped_nsobject<BrowserAccessibilityCocoa> node) { 384 if (node && [node instanceActive]) { 385 NSAccessibilityPostNotification( 386 node, NSAccessibilityLiveRegionChangedNotification); 387 } 388 }, 389 std::move(retained_node)), 390 base::TimeDelta::FromMilliseconds(kLiveRegionChangeIntervalMS)); 391 return; 392 } 393 case ui::AXEventGenerator::Event::ROW_COUNT_CHANGED: 394 mac_notification = NSAccessibilityRowCountChangedNotification; 395 break; 396 case ui::AXEventGenerator::Event::EXPANDED: 397 if (node->GetRole() == ax::mojom::Role::kRow || 398 node->GetRole() == ax::mojom::Role::kTreeItem) { 399 mac_notification = NSAccessibilityRowExpandedNotification; 400 } else { 401 mac_notification = NSAccessibilityExpandedChanged; 402 } 403 break; 404 case ui::AXEventGenerator::Event::COLLAPSED: 405 if (node->GetRole() == ax::mojom::Role::kRow || 406 node->GetRole() == ax::mojom::Role::kTreeItem) { 407 mac_notification = NSAccessibilityRowCollapsedNotification; 408 } else { 409 mac_notification = NSAccessibilityExpandedChanged; 410 } 411 break; 412 case ui::AXEventGenerator::Event::MENU_ITEM_SELECTED: 413 mac_notification = NSAccessibilityMenuItemSelectedNotification; 414 break; 415 case ui::AXEventGenerator::Event::ACCESS_KEY_CHANGED: 416 case ui::AXEventGenerator::Event::ATOMIC_CHANGED: 417 case ui::AXEventGenerator::Event::AUTO_COMPLETE_CHANGED: 418 case ui::AXEventGenerator::Event::BUSY_CHANGED: 419 case ui::AXEventGenerator::Event::CHILDREN_CHANGED: 420 case ui::AXEventGenerator::Event::CONTROLS_CHANGED: 421 case ui::AXEventGenerator::Event::CLASS_NAME_CHANGED: 422 case ui::AXEventGenerator::Event::DESCRIBED_BY_CHANGED: 423 case ui::AXEventGenerator::Event::DESCRIPTION_CHANGED: 424 case ui::AXEventGenerator::Event::DOCUMENT_TITLE_CHANGED: 425 case ui::AXEventGenerator::Event::DROPEFFECT_CHANGED: 426 case ui::AXEventGenerator::Event::ENABLED_CHANGED: 427 case ui::AXEventGenerator::Event::FOCUS_CHANGED: 428 case ui::AXEventGenerator::Event::FLOW_FROM_CHANGED: 429 case ui::AXEventGenerator::Event::FLOW_TO_CHANGED: 430 case ui::AXEventGenerator::Event::GRABBED_CHANGED: 431 case ui::AXEventGenerator::Event::HASPOPUP_CHANGED: 432 case ui::AXEventGenerator::Event::HIERARCHICAL_LEVEL_CHANGED: 433 case ui::AXEventGenerator::Event::IGNORED_CHANGED: 434 case ui::AXEventGenerator::Event::IMAGE_ANNOTATION_CHANGED: 435 case ui::AXEventGenerator::Event::KEY_SHORTCUTS_CHANGED: 436 case ui::AXEventGenerator::Event::LABELED_BY_CHANGED: 437 case ui::AXEventGenerator::Event::LANGUAGE_CHANGED: 438 case ui::AXEventGenerator::Event::LAYOUT_INVALIDATED: 439 case ui::AXEventGenerator::Event::LIVE_REGION_NODE_CHANGED: 440 case ui::AXEventGenerator::Event::LIVE_RELEVANT_CHANGED: 441 case ui::AXEventGenerator::Event::LIVE_STATUS_CHANGED: 442 case ui::AXEventGenerator::Event::LOAD_START: 443 case ui::AXEventGenerator::Event::MULTILINE_STATE_CHANGED: 444 case ui::AXEventGenerator::Event::MULTISELECTABLE_STATE_CHANGED: 445 case ui::AXEventGenerator::Event::NAME_CHANGED: 446 case ui::AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED: 447 case ui::AXEventGenerator::Event::PLACEHOLDER_CHANGED: 448 case ui::AXEventGenerator::Event::POSITION_IN_SET_CHANGED: 449 case ui::AXEventGenerator::Event::READONLY_CHANGED: 450 case ui::AXEventGenerator::Event::RELATED_NODE_CHANGED: 451 case ui::AXEventGenerator::Event::REQUIRED_STATE_CHANGED: 452 case ui::AXEventGenerator::Event::ROLE_CHANGED: 453 case ui::AXEventGenerator::Event::SCROLL_HORIZONTAL_POSITION_CHANGED: 454 case ui::AXEventGenerator::Event::SCROLL_VERTICAL_POSITION_CHANGED: 455 case ui::AXEventGenerator::Event::SELECTED_CHANGED: 456 case ui::AXEventGenerator::Event::SET_SIZE_CHANGED: 457 case ui::AXEventGenerator::Event::SORT_CHANGED: 458 case ui::AXEventGenerator::Event::STATE_CHANGED: 459 case ui::AXEventGenerator::Event::SUBTREE_CREATED: 460 case ui::AXEventGenerator::Event::VALUE_MAX_CHANGED: 461 case ui::AXEventGenerator::Event::VALUE_MIN_CHANGED: 462 case ui::AXEventGenerator::Event::VALUE_STEP_CHANGED: 463 // There are some notifications that aren't meaningful on Mac. 464 // It's okay to skip them. 465 return; 466 } 467 468 FireNativeMacNotification(mac_notification, node); 469} 470 471void BrowserAccessibilityManagerMac::FireNativeMacNotification( 472 NSString* mac_notification, 473 BrowserAccessibility* node) { 474 if (!node->IsNative()) 475 return; 476 477 DCHECK(mac_notification); 478 auto native_node = ToBrowserAccessibilityCocoa(node); 479 DCHECK(native_node); 480 NSAccessibilityPostNotification(native_node, mac_notification); 481} 482 483bool BrowserAccessibilityManagerMac::OnAccessibilityEvents( 484 const AXEventNotificationDetails& details) { 485 text_edits_.clear(); 486 return BrowserAccessibilityManager::OnAccessibilityEvents(details); 487} 488 489void BrowserAccessibilityManagerMac::OnAtomicUpdateFinished( 490 ui::AXTree* tree, 491 bool root_changed, 492 const std::vector<Change>& changes) { 493 BrowserAccessibilityManager::OnAtomicUpdateFinished(tree, root_changed, 494 changes); 495 496 std::set<const BrowserAccessibilityCocoa*> changed_editable_roots; 497 for (const auto& change : changes) { 498 const BrowserAccessibility* obj = GetFromAXNode(change.node); 499 if (obj && obj->IsNative() && obj->HasState(ax::mojom::State::kEditable)) { 500 const BrowserAccessibilityCocoa* editable_root = 501 [ToBrowserAccessibilityCocoa(obj) editableAncestor]; 502 if (editable_root && [editable_root instanceActive]) 503 changed_editable_roots.insert(editable_root); 504 } 505 } 506 507 for (const BrowserAccessibilityCocoa* obj : changed_editable_roots) { 508 DCHECK(obj); 509 const AXTextEdit text_edit = [obj computeTextEdit]; 510 if (!text_edit.IsEmpty()) 511 text_edits_[[obj owner]->GetId()] = text_edit; 512 } 513} 514 515NSDictionary* BrowserAccessibilityManagerMac:: 516 GetUserInfoForSelectedTextChangedNotification() { 517 NSMutableDictionary* user_info = 518 [[[NSMutableDictionary alloc] init] autorelease]; 519 [user_info setObject:@YES forKey:NSAccessibilityTextStateSyncKey]; 520 [user_info setObject:@(AXTextStateChangeTypeUnknown) 521 forKey:NSAccessibilityTextStateChangeTypeKey]; 522 [user_info setObject:@(AXTextSelectionDirectionUnknown) 523 forKey:NSAccessibilityTextSelectionDirection]; 524 [user_info setObject:@(AXTextSelectionGranularityUnknown) 525 forKey:NSAccessibilityTextSelectionGranularity]; 526 [user_info setObject:@YES forKey:NSAccessibilityTextSelectionChangedFocus]; 527 528 int32_t focus_id = ax_tree()->GetUnignoredSelection().focus_object_id; 529 BrowserAccessibility* focus_object = GetFromID(focus_id); 530 if (focus_object) { 531 focus_object = focus_object->PlatformGetClosestPlatformObject(); 532 auto native_focus_object = ToBrowserAccessibilityCocoa(focus_object); 533 if (native_focus_object && [native_focus_object instanceActive]) { 534 [user_info setObject:native_focus_object 535 forKey:NSAccessibilityTextChangeElement]; 536 537 id selected_text = [native_focus_object selectedTextMarkerRange]; 538 if (selected_text) { 539 NSString* const NSAccessibilitySelectedTextMarkerRangeAttribute = 540 @"AXSelectedTextMarkerRange"; 541 [user_info setObject:selected_text 542 forKey:NSAccessibilitySelectedTextMarkerRangeAttribute]; 543 } 544 } 545 } 546 547 return user_info; 548} 549 550NSDictionary* 551BrowserAccessibilityManagerMac::GetUserInfoForValueChangedNotification( 552 const BrowserAccessibilityCocoa* native_node, 553 const base::string16& deleted_text, 554 const base::string16& inserted_text) const { 555 DCHECK(native_node); 556 if (deleted_text.empty() && inserted_text.empty()) 557 return nil; 558 559 NSMutableArray* changes = [[[NSMutableArray alloc] init] autorelease]; 560 if (!deleted_text.empty()) { 561 [changes addObject:@{ 562 NSAccessibilityTextEditType : @(AXTextEditTypeDelete), 563 NSAccessibilityTextChangeValueLength : @(deleted_text.length()), 564 NSAccessibilityTextChangeValue : base::SysUTF16ToNSString(deleted_text) 565 }]; 566 } 567 if (!inserted_text.empty()) { 568 // TODO(nektar): Figure out if this is a paste operation instead of typing. 569 // Changes to Blink would be required. 570 [changes addObject:@{ 571 NSAccessibilityTextEditType : @(AXTextEditTypeTyping), 572 NSAccessibilityTextChangeValueLength : @(inserted_text.length()), 573 NSAccessibilityTextChangeValue : base::SysUTF16ToNSString(inserted_text) 574 }]; 575 } 576 577 return @{ 578 NSAccessibilityTextStateChangeTypeKey : @(AXTextStateChangeTypeEdit), 579 NSAccessibilityTextChangeValues : changes, 580 NSAccessibilityTextChangeElement : native_node 581 }; 582} 583 584id BrowserAccessibilityManagerMac::GetParentView() { 585 return delegate()->AccessibilityGetNativeViewAccessible(); 586} 587 588id BrowserAccessibilityManagerMac::GetWindow() { 589 return delegate()->AccessibilityGetNativeViewAccessibleForWindow(); 590} 591 592} // namespace content 593