|  | // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #import <Cocoa/Cocoa.h> | 
|  |  | 
|  | #import "content/browser/accessibility/browser_accessibility_mac.h" | 
|  |  | 
|  | #include "base/threading/thread_task_runner_handle.h" | 
|  | #include "base/time/time.h" | 
|  | #import "content/browser/accessibility/browser_accessibility_cocoa.h" | 
|  | #include "content/browser/accessibility/browser_accessibility_manager_mac.h" | 
|  |  | 
|  | namespace content { | 
|  |  | 
|  | // Static. | 
|  | BrowserAccessibility* BrowserAccessibility::Create() { | 
|  | return new BrowserAccessibilityMac(); | 
|  | } | 
|  |  | 
|  | BrowserAccessibilityMac::BrowserAccessibilityMac() | 
|  | : browser_accessibility_cocoa_(NULL) {} | 
|  |  | 
|  | BrowserAccessibilityMac::~BrowserAccessibilityMac() { | 
|  | // Detach this object from |browser_accessibility_cocoa_| so it | 
|  | // no longer has a pointer to this object. | 
|  | [browser_accessibility_cocoa_ detach]; | 
|  |  | 
|  | // Now, release it - but at this point, other processes may have a | 
|  | // reference to the cocoa object. | 
|  | [browser_accessibility_cocoa_ release]; | 
|  | } | 
|  |  | 
|  | void BrowserAccessibilityMac::OnDataChanged() { | 
|  | BrowserAccessibility::OnDataChanged(); | 
|  |  | 
|  | if (browser_accessibility_cocoa_) { | 
|  | [browser_accessibility_cocoa_ childrenChanged]; | 
|  | return; | 
|  | } | 
|  |  | 
|  | // We take ownership of the Cocoa object here. | 
|  | browser_accessibility_cocoa_ = | 
|  | [[BrowserAccessibilityCocoa alloc] initWithObject:this]; | 
|  | } | 
|  |  | 
|  | // Replace a native object and refocus if it had focus. | 
|  | // This will force VoiceOver to re-announce it, and refresh Braille output. | 
|  | void BrowserAccessibilityMac::ReplaceNativeObject() { | 
|  | BrowserAccessibilityCocoa* old_native_obj = browser_accessibility_cocoa_; | 
|  | browser_accessibility_cocoa_ = | 
|  | [[BrowserAccessibilityCocoa alloc] initWithObject:this]; | 
|  |  | 
|  | // Replace child in parent. | 
|  | BrowserAccessibility* parent = PlatformGetParent(); | 
|  | if (!parent) | 
|  | return; | 
|  |  | 
|  | base::scoped_nsobject<NSMutableArray> new_children; | 
|  | NSArray* old_children = [ToBrowserAccessibilityCocoa(parent) children]; | 
|  | for (uint i = 0; i < [old_children count]; ++i) { | 
|  | BrowserAccessibilityCocoa* child = [old_children objectAtIndex:i]; | 
|  | if (child == old_native_obj) | 
|  | [new_children addObject:browser_accessibility_cocoa_]; | 
|  | else | 
|  | [new_children addObject:child]; | 
|  | } | 
|  | [ToBrowserAccessibilityCocoa(parent) swapChildren:&new_children]; | 
|  |  | 
|  | // If focused, fire a focus notification on the new native object. | 
|  | if (manager_->GetFocus() == this) { | 
|  | NSAccessibilityPostNotification( | 
|  | browser_accessibility_cocoa_, | 
|  | NSAccessibilityFocusedUIElementChangedNotification); | 
|  | } | 
|  |  | 
|  | // Destroy after a delay so that VO is securely on the new focus first, | 
|  | // otherwise the focus event will not be announced. | 
|  | // We use 1000ms; however, this magic number isn't necessary to avoid | 
|  | // use-after-free or anything scary like that. The worst case scenario if this | 
|  | // gets destroyed, too early is that VoiceOver announces the wrong thing once. | 
|  | base::scoped_nsobject<BrowserAccessibilityCocoa> retained_destroyed_node( | 
|  | [old_native_obj retain]); | 
|  |  | 
|  | base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce( | 
|  | [](base::scoped_nsobject<BrowserAccessibilityCocoa> destroyed) { | 
|  | if (destroyed && [destroyed instanceActive]) { | 
|  | // Follow destruction pattern from NativeReleaseReference(). | 
|  | [destroyed detach]; | 
|  | [destroyed release]; | 
|  | } | 
|  | }, | 
|  | std::move(retained_destroyed_node)), | 
|  | base::TimeDelta::FromMilliseconds(1000)); | 
|  | } | 
|  |  | 
|  | uint32_t BrowserAccessibilityMac::PlatformChildCount() const { | 
|  | uint32_t child_count = BrowserAccessibility::PlatformChildCount(); | 
|  |  | 
|  | // If this is a table, include the extra fake nodes generated by | 
|  | // AXTableInfo, for the column nodes and the table header container, all of | 
|  | // which are only important on macOS. | 
|  | const std::vector<ui::AXNode*>* extra_mac_nodes = node()->GetExtraMacNodes(); | 
|  | if (!extra_mac_nodes) | 
|  | return child_count; | 
|  |  | 
|  | return child_count + extra_mac_nodes->size(); | 
|  | } | 
|  |  | 
|  | BrowserAccessibility* BrowserAccessibilityMac::PlatformGetChild( | 
|  | uint32_t child_index) const { | 
|  | if (child_index < BrowserAccessibility::PlatformChildCount()) | 
|  | return BrowserAccessibility::PlatformGetChild(child_index); | 
|  |  | 
|  | if (child_index >= PlatformChildCount()) | 
|  | return nullptr; | 
|  |  | 
|  | // If this is a table, include the extra fake nodes generated by | 
|  | // AXTableInfo, for the column nodes and the table header container, all of | 
|  | // which are only important on macOS. | 
|  | const std::vector<ui::AXNode*>* extra_mac_nodes = node()->GetExtraMacNodes(); | 
|  | if (!extra_mac_nodes || extra_mac_nodes->empty()) | 
|  | return nullptr; | 
|  |  | 
|  | child_index -= BrowserAccessibility::PlatformChildCount(); | 
|  | if (child_index < extra_mac_nodes->size()) | 
|  | return manager_->GetFromAXNode((*extra_mac_nodes)[child_index]); | 
|  |  | 
|  | return nullptr; | 
|  | } | 
|  |  | 
|  | BrowserAccessibility* BrowserAccessibilityMac::PlatformGetFirstChild() const { | 
|  | return PlatformGetChild(0); | 
|  | } | 
|  |  | 
|  | BrowserAccessibility* BrowserAccessibilityMac::PlatformGetLastChild() const { | 
|  | const std::vector<ui::AXNode*>* extra_mac_nodes = node()->GetExtraMacNodes(); | 
|  | if (extra_mac_nodes && !extra_mac_nodes->empty()) | 
|  | return manager_->GetFromAXNode(extra_mac_nodes->back()); | 
|  | return BrowserAccessibility::PlatformGetLastChild(); | 
|  | } | 
|  |  | 
|  | BrowserAccessibility* BrowserAccessibilityMac::PlatformGetNextSibling() const { | 
|  | BrowserAccessibility* parent = PlatformGetParent(); | 
|  | if (parent) { | 
|  | uint32_t next_child_index = node()->GetUnignoredIndexInParent() + 1; | 
|  | if (next_child_index >= parent->InternalChildCount() && | 
|  | next_child_index < parent->PlatformChildCount()) { | 
|  | // get the extra_mac_node | 
|  | return parent->PlatformGetChild(next_child_index); | 
|  | } else if (next_child_index >= parent->PlatformChildCount()) { | 
|  | return nullptr; | 
|  | } | 
|  | } | 
|  | return BrowserAccessibility::PlatformGetNextSibling(); | 
|  | } | 
|  |  | 
|  | BrowserAccessibility* BrowserAccessibilityMac::PlatformGetPreviousSibling() | 
|  | const { | 
|  | BrowserAccessibility* parent = PlatformGetParent(); | 
|  | if (parent) { | 
|  | uint32_t previous_child_index = node()->GetUnignoredIndexInParent() - 1; | 
|  | if (previous_child_index >= parent->InternalChildCount() && | 
|  | previous_child_index < parent->PlatformChildCount()) { | 
|  | // get the extra_mac_node | 
|  | return parent->PlatformGetChild(previous_child_index); | 
|  | } else if (previous_child_index < 0) { | 
|  | return nullptr; | 
|  | } | 
|  | } | 
|  | return BrowserAccessibility::PlatformGetPreviousSibling(); | 
|  | } | 
|  |  | 
|  | const BrowserAccessibilityCocoa* ToBrowserAccessibilityCocoa( | 
|  | const BrowserAccessibility* obj) { | 
|  | DCHECK(obj); | 
|  | return static_cast<const BrowserAccessibilityMac*>(obj)->native_view(); | 
|  | } | 
|  |  | 
|  | BrowserAccessibilityCocoa* ToBrowserAccessibilityCocoa( | 
|  | BrowserAccessibility* obj) { | 
|  | DCHECK(obj); | 
|  | return static_cast<BrowserAccessibilityMac*>(obj)->native_view(); | 
|  | } | 
|  |  | 
|  | }  // namespace content |