// 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 #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 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 retained_destroyed_node( [old_native_obj retain]); base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::BindOnce( [](base::scoped_nsobject 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* 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* 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* 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(obj)->native_view(); } BrowserAccessibilityCocoa* ToBrowserAccessibilityCocoa( BrowserAccessibility* obj) { DCHECK(obj); return static_cast(obj)->native_view(); } } // namespace content