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