blob: 5a4fd1c4cc2e7c00b502599af5317bb7ead4ae23 [file] [log] [blame]
// 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.
#include "content/browser/accessibility/browser_accessibility_manager_win.h"
#include <stddef.h>
#include <stdint.h>
#include <vector>
#include "base/command_line.h"
#include "base/win/scoped_comptr.h"
#include "base/win/windows_version.h"
#include "content/browser/accessibility/browser_accessibility_event_win.h"
#include "content/browser/accessibility/browser_accessibility_state_impl.h"
#include "content/browser/accessibility/browser_accessibility_win.h"
#include "content/browser/renderer_host/legacy_render_widget_host_win.h"
#include "content/common/accessibility_messages.h"
#include "ui/base/win/atl_module.h"
namespace content {
// static
BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
const ui::AXTreeUpdate& initial_tree,
BrowserAccessibilityDelegate* delegate,
BrowserAccessibilityFactory* factory) {
return new BrowserAccessibilityManagerWin(initial_tree, delegate, factory);
}
BrowserAccessibilityManagerWin*
BrowserAccessibilityManager::ToBrowserAccessibilityManagerWin() {
return static_cast<BrowserAccessibilityManagerWin*>(this);
}
BrowserAccessibilityManagerWin::BrowserAccessibilityManagerWin(
const ui::AXTreeUpdate& initial_tree,
BrowserAccessibilityDelegate* delegate,
BrowserAccessibilityFactory* factory)
: BrowserAccessibilityManager(delegate, factory),
load_complete_pending_(false) {
ui::win::CreateATLModuleIfNeeded();
Initialize(initial_tree);
ui::GetIAccessible2UsageObserverList().AddObserver(this);
}
BrowserAccessibilityManagerWin::~BrowserAccessibilityManagerWin() {
// Destroy the tree in the subclass, rather than in the inherited
// destructor, otherwise our overrides of functions like
// OnNodeWillBeDeleted won't be called.
tree_.reset(NULL);
ui::GetIAccessible2UsageObserverList().RemoveObserver(this);
}
// static
ui::AXTreeUpdate
BrowserAccessibilityManagerWin::GetEmptyDocument() {
ui::AXNodeData empty_document;
empty_document.id = 0;
empty_document.role = ui::AX_ROLE_ROOT_WEB_AREA;
empty_document.AddState(ui::AX_STATE_BUSY);
ui::AXTreeUpdate update;
update.root_id = empty_document.id;
update.nodes.push_back(empty_document);
return update;
}
HWND BrowserAccessibilityManagerWin::GetParentHWND() {
BrowserAccessibilityDelegate* delegate = GetDelegateFromRootManager();
if (!delegate)
return NULL;
return delegate->AccessibilityGetAcceleratedWidget();
}
IAccessible* BrowserAccessibilityManagerWin::GetParentIAccessible() {
BrowserAccessibilityDelegate* delegate = GetDelegateFromRootManager();
if (!delegate)
return NULL;
return delegate->AccessibilityGetNativeViewAccessible();
}
void BrowserAccessibilityManagerWin::OnIAccessible2Used() {
// When IAccessible2 APIs have been used elsewhere in the codebase,
// enable basic web accessibility support. (Full screen reader support is
// detected later when specific more advanced APIs are accessed.)
BrowserAccessibilityStateImpl::GetInstance()->AddAccessibilityModeFlags(
AccessibilityMode::kNativeAPIs | AccessibilityMode::kWebContents);
}
void BrowserAccessibilityManagerWin::UserIsReloading() {
if (GetRoot()) {
(new BrowserAccessibilityEventWin(
BrowserAccessibilityEvent::FromRenderFrameHost,
ui::AX_EVENT_NONE,
IA2_EVENT_DOCUMENT_RELOAD,
GetRoot()))->Fire();
}
}
BrowserAccessibility* BrowserAccessibilityManagerWin::GetFocus() {
BrowserAccessibility* focus = BrowserAccessibilityManager::GetFocus();
return GetActiveDescendant(focus);
}
void BrowserAccessibilityManagerWin::NotifyAccessibilityEvent(
BrowserAccessibilityEvent::Source source,
ui::AXEvent event_type,
BrowserAccessibility* node) {
bool can_fire_events = CanFireEvents();
// TODO(dmazzoni): A better fix would be to always have a HWND.
// http://crbug.com/521877
if (event_type == ui::AX_EVENT_LOAD_COMPLETE && can_fire_events)
load_complete_pending_ = false;
if (load_complete_pending_ && can_fire_events && GetRoot()) {
load_complete_pending_ = false;
NotifyAccessibilityEvent(BrowserAccessibilityEvent::FromPendingLoadComplete,
ui::AX_EVENT_LOAD_COMPLETE,
GetRoot());
}
if (!can_fire_events &&
!load_complete_pending_ &&
event_type == ui::AX_EVENT_LOAD_COMPLETE &&
GetRoot() &&
!GetRoot()->HasState(ui::AX_STATE_OFFSCREEN) &&
GetRoot()->PlatformChildCount() > 0) {
load_complete_pending_ = true;
}
if (event_type == ui::AX_EVENT_BLUR) {
// Equivalent to focus on the root.
event_type = ui::AX_EVENT_FOCUS;
node = GetRoot();
}
if (event_type == ui::AX_EVENT_DOCUMENT_SELECTION_CHANGED) {
// Fire the event on the object where the focus of the selection is.
int32_t focus_id = GetTreeData().sel_focus_object_id;
BrowserAccessibility* focus_object = GetFromID(focus_id);
if (focus_object) {
(new BrowserAccessibilityEventWin(
source,
ui::AX_EVENT_NONE,
IA2_EVENT_TEXT_CARET_MOVED,
focus_object))->Fire();
return;
}
}
BrowserAccessibilityManager::NotifyAccessibilityEvent(
source, event_type, node);
}
BrowserAccessibilityEvent::Result
BrowserAccessibilityManagerWin::FireWinAccessibilityEvent(
BrowserAccessibilityEventWin* event) {
const BrowserAccessibility* target = event->target();
ui::AXEvent event_type = event->event_type();
LONG win_event_type = event->win_event_type();
BrowserAccessibilityDelegate* root_delegate = GetDelegateFromRootManager();
if (!root_delegate)
return BrowserAccessibilityEvent::FailedBecauseFrameIsDetached;
HWND hwnd = root_delegate->AccessibilityGetAcceleratedWidget();
if (!hwnd)
return BrowserAccessibilityEvent::FailedBecauseNoWindow;
// Don't fire events when this document might be stale as the user has
// started navigating to a new document.
if (user_is_navigating_away_)
return BrowserAccessibilityEvent::DiscardedBecauseUserNavigatingAway;
if (!target)
return BrowserAccessibilityEvent::FailedBecauseNoFocus;
// Inline text boxes are an internal implementation detail, we don't
// expose them to Windows.
if (target->GetRole() == ui::AX_ROLE_INLINE_TEXT_BOX)
return BrowserAccessibilityEvent::NotNeededOnThisPlatform;
if ((event_type == ui::AX_EVENT_LIVE_REGION_CREATED ||
event_type == ui::AX_EVENT_LIVE_REGION_CHANGED) &&
target->GetBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_BUSY)) {
return BrowserAccessibilityEvent::DiscardedBecauseLiveRegionBusy;
}
event->set_target(target);
// It doesn't make sense to fire a REORDER event on a leaf node; that
// happens when the target has internal children inline text boxes.
if (win_event_type == EVENT_OBJECT_REORDER &&
target->PlatformChildCount() == 0) {
return BrowserAccessibilityEvent::NotNeededOnThisPlatform;
}
// Pass the negation of this node's unique id in the |child_id|
// argument to NotifyWinEvent; the AT client will then call get_accChild
// on the HWND's accessibility object and pass it that same id, which
// we can use to retrieve the IAccessible for this node.
LONG child_id = -target->unique_id();
::NotifyWinEvent(win_event_type, hwnd, OBJID_CLIENT, child_id);
return BrowserAccessibilityEvent::Sent;
}
bool BrowserAccessibilityManagerWin::CanFireEvents() {
BrowserAccessibilityDelegate* root_delegate = GetDelegateFromRootManager();
if (!root_delegate)
return false;
HWND hwnd = root_delegate->AccessibilityGetAcceleratedWidget();
return hwnd != nullptr;
}
void BrowserAccessibilityManagerWin::FireFocusEvent(
BrowserAccessibilityEvent::Source source,
BrowserAccessibility* node) {
DCHECK(node);
// On Windows, we always fire a FOCUS event on the root of a frame before
// firing a focus event within that frame.
if (node->manager() != last_focused_manager_ &&
node != node->manager()->GetRoot()) {
BrowserAccessibilityEvent::Create(source,
ui::AX_EVENT_FOCUS,
node->manager()->GetRoot())->Fire();
}
BrowserAccessibilityManager::FireFocusEvent(source, node);
}
gfx::Rect BrowserAccessibilityManagerWin::GetViewBounds() {
// We have to take the device scale factor into account on Windows.
BrowserAccessibilityDelegate* delegate = GetDelegateFromRootManager();
if (delegate) {
gfx::Rect bounds = delegate->AccessibilityGetViewBounds();
if (device_scale_factor() > 0.0 && device_scale_factor() != 1.0)
bounds = ScaleToEnclosingRect(bounds, device_scale_factor());
return bounds;
}
return gfx::Rect();
}
void BrowserAccessibilityManagerWin::OnNodeCreated(ui::AXTree* tree,
ui::AXNode* node) {
DCHECK(node);
BrowserAccessibilityManager::OnNodeCreated(tree, node);
BrowserAccessibility* obj = GetFromAXNode(node);
if (!obj)
return;
if (!obj->IsNative())
return;
}
void BrowserAccessibilityManagerWin::OnAtomicUpdateFinished(
ui::AXTree* tree,
bool root_changed,
const std::vector<ui::AXTreeDelegate::Change>& changes) {
BrowserAccessibilityManager::OnAtomicUpdateFinished(
tree, root_changed, changes);
// Do a sequence of Windows-specific updates on each node. Each one is
// done in a single pass that must complete before the next step starts.
// The first step moves win_attributes_ to old_win_attributes_ and then
// recomputes all of win_attributes_ other than IAccessibleText.
for (size_t i = 0; i < changes.size(); ++i) {
const ui::AXNode* changed_node = changes[i].node;
DCHECK(changed_node);
BrowserAccessibility* obj = GetFromAXNode(changed_node);
if (obj && obj->IsNative() && !obj->PlatformIsChildOfLeaf())
ToBrowserAccessibilityWin(obj)
->GetCOM()
->UpdateStep1ComputeWinAttributes();
}
// The next step updates the hypertext of each node, which is a
// concatenation of all of its child text nodes, so it can't run until
// the text of all of the nodes was computed in the previous step.
for (size_t i = 0; i < changes.size(); ++i) {
const ui::AXNode* changed_node = changes[i].node;
DCHECK(changed_node);
BrowserAccessibility* obj = GetFromAXNode(changed_node);
if (obj && obj->IsNative() && !obj->PlatformIsChildOfLeaf())
ToBrowserAccessibilityWin(obj)->GetCOM()->UpdateStep2ComputeHypertext();
}
// The third step fires events on nodes based on what's changed - like
// if the name, value, or description changed, or if the hypertext had
// text inserted or removed. It's able to figure out exactly what changed
// because we still have old_win_attributes_ populated.
// This step has to run after the previous two steps complete because the
// client may walk the tree when it receives any of these events.
// At the end, it deletes old_win_attributes_ since they're not needed
// anymore.
for (size_t i = 0; i < changes.size(); ++i) {
const ui::AXNode* changed_node = changes[i].node;
DCHECK(changed_node);
BrowserAccessibility* obj = GetFromAXNode(changed_node);
if (obj && obj->IsNative() && !obj->PlatformIsChildOfLeaf()) {
ToBrowserAccessibilityWin(obj)->GetCOM()->UpdateStep3FireEvents(
changes[i].type == AXTreeDelegate::SUBTREE_CREATED);
}
}
}
} // namespace content