blob: c11ceea8feaa37e02d5741497cf7743b008988e8 [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 <atlbase.h>
#include <atlapp.h>
#include <atlcom.h>
#include <atlcrack.h>
#include <oleacc.h>
#include "base/command_line.h"
#include "base/win/scoped_comptr.h"
#include "base/win/windows_version.h"
#include "content/browser/accessibility/browser_accessibility_state_impl.h"
#include "content/browser/accessibility/browser_accessibility_win.h"
#include "content/common/accessibility_messages.h"
namespace content {
// Some screen readers expect every tab / every unique web content container
// to be in its own HWND, like it was before Aura, but with Aura there's just
// one main HWND for a frame, or even for the whole desktop. So, we need a
// fake HWND as the root of the accessibility tree for each tab.
// We should get rid of this code when the latest two versions of all
// supported screen readers no longer make this assumption.
//
// This class implements a child HWND with zero size, that delegates its
// accessibility implementation to the root of the BrowserAccessibilityManager
// tree. This HWND is hooked up as the parent of the root object in the
// BrowserAccessibilityManager tree, so when any accessibility client
// calls ::WindowFromAccessibleObject, they get this HWND instead of the
// DesktopRootWindowHostWin.
class AccessibleHWND
: public ATL::CWindowImpl<AccessibleHWND,
ATL::CWindow,
ATL::CWinTraits<WS_CHILD> > {
public:
// Unfortunately, some screen readers look for this exact window class
// to enable certain features. It'd be great to remove this.
DECLARE_WND_CLASS_EX(L"Chrome_RenderWidgetHostHWND1", CS_DBLCLKS, 0);
BEGIN_MSG_MAP_EX(AccessibleHWND)
MESSAGE_HANDLER_EX(WM_GETOBJECT, OnGetObject)
END_MSG_MAP()
AccessibleHWND(HWND parent, BrowserAccessibilityManagerWin* manager)
: manager_(manager) {
Create(parent);
ShowWindow(true);
MoveWindow(0, 0, 0, 0);
HRESULT hr = ::CreateStdAccessibleObject(
hwnd(), OBJID_WINDOW, IID_IAccessible,
reinterpret_cast<void **>(window_accessible_.Receive()));
DCHECK(SUCCEEDED(hr));
}
HWND hwnd() {
DCHECK(::IsWindow(m_hWnd));
return m_hWnd;
}
IAccessible* window_accessible() { return window_accessible_; }
private:
LRESULT OnGetObject(UINT message,
WPARAM w_param,
LPARAM l_param) {
if (OBJID_CLIENT != l_param)
return static_cast<LRESULT>(0L);
base::win::ScopedComPtr<IAccessible> root(
manager_->GetRoot()->ToBrowserAccessibilityWin());
return LresultFromObject(IID_IAccessible, w_param,
static_cast<IAccessible*>(root.Detach()));
}
BrowserAccessibilityManagerWin* manager_;
base::win::ScopedComPtr<IAccessible> window_accessible_;
DISALLOW_COPY_AND_ASSIGN(AccessibleHWND);
};
// static
BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
const AccessibilityNodeData& src,
BrowserAccessibilityDelegate* delegate,
BrowserAccessibilityFactory* factory) {
return new BrowserAccessibilityManagerWin(
GetDesktopWindow(), NULL, src, delegate, factory);
}
BrowserAccessibilityManagerWin*
BrowserAccessibilityManager::ToBrowserAccessibilityManagerWin() {
return static_cast<BrowserAccessibilityManagerWin*>(this);
}
BrowserAccessibilityManagerWin::BrowserAccessibilityManagerWin(
HWND parent_hwnd,
IAccessible* parent_iaccessible,
const AccessibilityNodeData& src,
BrowserAccessibilityDelegate* delegate,
BrowserAccessibilityFactory* factory)
: BrowserAccessibilityManager(src, delegate, factory),
parent_hwnd_(parent_hwnd),
parent_iaccessible_(parent_iaccessible),
tracked_scroll_object_(NULL),
is_chrome_frame_(
CommandLine::ForCurrentProcess()->HasSwitch("chrome-frame")) {
}
BrowserAccessibilityManagerWin::~BrowserAccessibilityManagerWin() {
if (tracked_scroll_object_) {
tracked_scroll_object_->Release();
tracked_scroll_object_ = NULL;
}
}
// static
AccessibilityNodeData BrowserAccessibilityManagerWin::GetEmptyDocument() {
AccessibilityNodeData empty_document;
empty_document.id = 0;
empty_document.role = WebKit::WebAXRoleRootWebArea;
empty_document.state =
(1 << WebKit::WebAXStateEnabled) |
(1 << WebKit::WebAXStateReadonly) |
(1 << WebKit::WebAXStateBusy);
return empty_document;
}
void BrowserAccessibilityManagerWin::MaybeCallNotifyWinEvent(DWORD event,
LONG child_id) {
// Don't fire events if this view isn't hooked up to its parent.
if (!parent_iaccessible())
return;
#if defined(USE_AURA)
// If this is an Aura build on Win 7 and complete accessibility is
// enabled, create a fake child HWND to use as the root of the
// accessibility tree. See comments above AccessibleHWND for details.
if (BrowserAccessibilityStateImpl::GetInstance()->IsAccessibleBrowser() &&
!is_chrome_frame_ &&
!accessible_hwnd_ &&
base::win::GetVersion() < base::win::VERSION_WIN8) {
accessible_hwnd_.reset(new AccessibleHWND(parent_hwnd_, this));
parent_hwnd_ = accessible_hwnd_->hwnd();
parent_iaccessible_ = accessible_hwnd_->window_accessible();
}
#endif
::NotifyWinEvent(event, parent_hwnd(), OBJID_CLIENT, child_id);
}
void BrowserAccessibilityManagerWin::AddNodeToMap(BrowserAccessibility* node) {
BrowserAccessibilityManager::AddNodeToMap(node);
LONG unique_id_win = node->ToBrowserAccessibilityWin()->unique_id_win();
unique_id_to_renderer_id_map_[unique_id_win] = node->renderer_id();
}
void BrowserAccessibilityManagerWin::RemoveNode(BrowserAccessibility* node) {
unique_id_to_renderer_id_map_.erase(
node->ToBrowserAccessibilityWin()->unique_id_win());
BrowserAccessibilityManager::RemoveNode(node);
if (node == tracked_scroll_object_) {
tracked_scroll_object_->Release();
tracked_scroll_object_ = NULL;
}
}
void BrowserAccessibilityManagerWin::NotifyAccessibilityEvent(
WebKit::WebAXEvent event_type,
BrowserAccessibility* node) {
LONG event_id = EVENT_MIN;
switch (event_type) {
case WebKit::WebAXEventActiveDescendantChanged:
event_id = IA2_EVENT_ACTIVE_DESCENDANT_CHANGED;
break;
case WebKit::WebAXEventAlert:
event_id = EVENT_SYSTEM_ALERT;
break;
case WebKit::WebAXEventAriaAttributeChanged:
event_id = IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED;
break;
case WebKit::WebAXEventAutocorrectionOccured:
event_id = IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED;
break;
case WebKit::WebAXEventBlur:
// Equivalent to focus on the root.
event_id = EVENT_OBJECT_FOCUS;
node = GetRoot();
break;
case WebKit::WebAXEventCheckedStateChanged:
event_id = EVENT_OBJECT_STATECHANGE;
break;
case WebKit::WebAXEventChildrenChanged:
event_id = EVENT_OBJECT_REORDER;
break;
case WebKit::WebAXEventFocus:
event_id = EVENT_OBJECT_FOCUS;
break;
case WebKit::WebAXEventInvalidStatusChanged:
event_id = EVENT_OBJECT_STATECHANGE;
break;
case WebKit::WebAXEventLiveRegionChanged:
// TODO: try not firing a native notification at all, since
// on Windows, each individual item in a live region that changes
// already gets its own notification.
event_id = EVENT_OBJECT_REORDER;
break;
case WebKit::WebAXEventLoadComplete:
event_id = IA2_EVENT_DOCUMENT_LOAD_COMPLETE;
break;
case WebKit::WebAXEventMenuListItemSelected:
event_id = EVENT_OBJECT_FOCUS;
break;
case WebKit::WebAXEventMenuListValueChanged:
event_id = EVENT_OBJECT_VALUECHANGE;
break;
case WebKit::WebAXEventHide:
event_id = EVENT_OBJECT_HIDE;
break;
case WebKit::WebAXEventShow:
event_id = EVENT_OBJECT_SHOW;
break;
case WebKit::WebAXEventScrolledToAnchor:
event_id = EVENT_SYSTEM_SCROLLINGSTART;
break;
case WebKit::WebAXEventSelectedChildrenChanged:
event_id = EVENT_OBJECT_SELECTIONWITHIN;
break;
case WebKit::WebAXEventSelectedTextChanged:
event_id = IA2_EVENT_TEXT_CARET_MOVED;
break;
case WebKit::WebAXEventTextChanged:
event_id = EVENT_OBJECT_NAMECHANGE;
break;
case WebKit::WebAXEventTextInserted:
event_id = IA2_EVENT_TEXT_INSERTED;
break;
case WebKit::WebAXEventTextRemoved:
event_id = IA2_EVENT_TEXT_REMOVED;
break;
case WebKit::WebAXEventValueChanged:
event_id = EVENT_OBJECT_VALUECHANGE;
break;
default:
// Not all WebKit accessibility events result in a Windows
// accessibility notification.
break;
}
if (event_id != EVENT_MIN) {
// Pass the 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 = node->ToBrowserAccessibilityWin()->unique_id_win();
MaybeCallNotifyWinEvent(event_id, child_id);
}
// If this is a layout complete notification (sent when a container scrolls)
// and there is a descendant tracked object, send a notification on it.
// TODO(dmazzoni): remove once http://crbug.com/113483 is fixed.
if (event_type == WebKit::WebAXEventLayoutComplete &&
tracked_scroll_object_ &&
tracked_scroll_object_->IsDescendantOf(node)) {
MaybeCallNotifyWinEvent(
IA2_EVENT_VISIBLE_DATA_CHANGED,
tracked_scroll_object_->ToBrowserAccessibilityWin()->unique_id_win());
tracked_scroll_object_->Release();
tracked_scroll_object_ = NULL;
}
}
void BrowserAccessibilityManagerWin::TrackScrollingObject(
BrowserAccessibilityWin* node) {
if (tracked_scroll_object_)
tracked_scroll_object_->Release();
tracked_scroll_object_ = node;
tracked_scroll_object_->AddRef();
}
BrowserAccessibilityWin* BrowserAccessibilityManagerWin::GetFromUniqueIdWin(
LONG unique_id_win) {
base::hash_map<LONG, int32>::iterator iter =
unique_id_to_renderer_id_map_.find(unique_id_win);
if (iter != unique_id_to_renderer_id_map_.end()) {
BrowserAccessibility* result = GetFromRendererID(iter->second);
if (result)
return result->ToBrowserAccessibilityWin();
}
return NULL;
}
} // namespace content