#include "tests/cefclient/browser/osr_accessibility_node.h"
#if defined(CEF_USE_ATL)
#include <atlbase.h>
#include <oleacc.h>
#include <string>
#include "tests/cefclient/browser/osr_accessibility_helper.h"
namespace client {
#define DATACHECK(node) (node) ? S_OK : CO_E_OBJNOTCONNECTED
#define VALID_CHILDID(varChild) ((varChild.vt == VT_I4))
namespace {
void ClientToScreen(HWND hwnd, LPRECT lpRect) {
if (lpRect) {
POINT ptTL = {lpRect->left, lpRect->top};
POINT ptBR = {lpRect->right, lpRect->bottom};
ClientToScreen(hwnd, &ptTL);
ClientToScreen(hwnd, &ptBR);
SetRect(lpRect, ptTL.x, ptTL.y, ptBR.x, ptBR.y);
}
}
int AxRoleToMSAARole(const std::string& role_string) {
if (role_string == "alert")
return ROLE_SYSTEM_ALERT;
if (role_string == "application")
return ROLE_SYSTEM_APPLICATION;
if (role_string == "buttonDropDown")
return ROLE_SYSTEM_BUTTONDROPDOWN;
if (role_string == "popUpButton")
return ROLE_SYSTEM_BUTTONMENU;
if (role_string == "checkBox")
return ROLE_SYSTEM_CHECKBUTTON;
if (role_string == "comboBox")
return ROLE_SYSTEM_COMBOBOX;
if (role_string == "dialog")
return ROLE_SYSTEM_DIALOG;
if (role_string == "genericContainer")
return ROLE_SYSTEM_GROUPING;
if (role_string == "group")
return ROLE_SYSTEM_GROUPING;
if (role_string == "image")
return ROLE_SYSTEM_GRAPHIC;
if (role_string == "link")
return ROLE_SYSTEM_LINK;
if (role_string == "locationBar")
return ROLE_SYSTEM_GROUPING;
if (role_string == "menuBar")
return ROLE_SYSTEM_MENUBAR;
if (role_string == "menuItem")
return ROLE_SYSTEM_MENUITEM;
if (role_string == "menuListPopup")
return ROLE_SYSTEM_MENUPOPUP;
if (role_string == "tree")
return ROLE_SYSTEM_OUTLINE;
if (role_string == "treeItem")
return ROLE_SYSTEM_OUTLINEITEM;
if (role_string == "tab")
return ROLE_SYSTEM_PAGETAB;
if (role_string == "tabList")
return ROLE_SYSTEM_PAGETABLIST;
if (role_string == "pane")
return ROLE_SYSTEM_PANE;
if (role_string == "progressIndicator")
return ROLE_SYSTEM_PROGRESSBAR;
if (role_string == "button")
return ROLE_SYSTEM_PUSHBUTTON;
if (role_string == "radioButton")
return ROLE_SYSTEM_RADIOBUTTON;
if (role_string == "scrollBar")
return ROLE_SYSTEM_SCROLLBAR;
if (role_string == "splitter")
return ROLE_SYSTEM_SEPARATOR;
if (role_string == "slider")
return ROLE_SYSTEM_SLIDER;
if (role_string == "staticText")
return ROLE_SYSTEM_STATICTEXT;
if (role_string == "textField")
return ROLE_SYSTEM_TEXT;
if (role_string == "titleBar")
return ROLE_SYSTEM_TITLEBAR;
if (role_string == "toolbar")
return ROLE_SYSTEM_TOOLBAR;
if (role_string == "webView")
return ROLE_SYSTEM_GROUPING;
if (role_string == "window")
return ROLE_SYSTEM_WINDOW;
if (role_string == "client")
return ROLE_SYSTEM_CLIENT;
return ROLE_SYSTEM_CLIENT;
}
static inline int MiddleX(const CefRect& rect) {
return rect.x + rect.width / 2;
}
static inline int MiddleY(const CefRect& rect) {
return rect.y + rect.height / 2;
}
}
struct CefIAccessible : public IAccessible {
public:
STDMETHODIMP QueryInterface(REFIID riid, void** ppvObject) override;
STDMETHODIMP_(ULONG) AddRef() override;
STDMETHODIMP_(ULONG) Release() override;
STDMETHODIMP accHitTest(LONG x_left, LONG y_top, VARIANT* child) override;
STDMETHODIMP accDoDefaultAction(VARIANT var_id) override;
STDMETHODIMP accLocation(LONG* x_left,
LONG* y_top,
LONG* width,
LONG* height,
VARIANT var_id) override;
STDMETHODIMP accNavigate(LONG nav_dir, VARIANT start, VARIANT* end) override;
STDMETHODIMP get_accChild(VARIANT var_child, IDispatch** disp_child) override;
STDMETHODIMP get_accChildCount(LONG* child_count) override;
STDMETHODIMP get_accDefaultAction(VARIANT var_id,
BSTR* default_action) override;
STDMETHODIMP get_accDescription(VARIANT var_id, BSTR* desc) override;
STDMETHODIMP get_accFocus(VARIANT* focus_child) override;
STDMETHODIMP get_accKeyboardShortcut(VARIANT var_id,
BSTR* access_key) override;
STDMETHODIMP get_accName(VARIANT var_id, BSTR* name) override;
STDMETHODIMP get_accParent(IDispatch** disp_parent) override;
STDMETHODIMP get_accRole(VARIANT var_id, VARIANT* role) override;
STDMETHODIMP get_accState(VARIANT var_id, VARIANT* state) override;
STDMETHODIMP get_accHelp(VARIANT var_id, BSTR* help) override;
STDMETHODIMP get_accValue(VARIANT var_id, BSTR* value) override;
STDMETHODIMP put_accValue(VARIANT var_id, BSTR new_value) override;
STDMETHODIMP get_accSelection(VARIANT* selected) override;
STDMETHODIMP accSelect(LONG flags_sel, VARIANT var_id) override;
STDMETHODIMP get_accHelpTopic(BSTR* help_file,
VARIANT var_id,
LONG* topic_id) override;
STDMETHODIMP put_accName(VARIANT var_id, BSTR put_name) override;
STDMETHODIMP GetTypeInfoCount(unsigned int FAR* pctinfo) override;
STDMETHODIMP GetTypeInfo(unsigned int iTInfo,
LCID lcid,
ITypeInfo FAR* FAR* ppTInfo) override;
STDMETHODIMP GetIDsOfNames(REFIID riid,
OLECHAR FAR* FAR* rgszNames,
unsigned int cNames,
LCID lcid,
DISPID FAR* rgDispId) override;
STDMETHODIMP Invoke(DISPID dispIdMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS FAR* pDispParams,
VARIANT FAR* pVarResult,
EXCEPINFO FAR* pExcepInfo,
unsigned int FAR* puArgErr) override;
CefIAccessible(OsrAXNode* node) : ref_count_(0), node_(node) {}
void MarkDestroyed() { node_ = nullptr; }
protected:
virtual ~CefIAccessible() {}
ULONG ref_count_;
OsrAXNode* node_;
};
STDMETHODIMP CefIAccessible::QueryInterface(REFIID riid, void** ppvObject) {
if (riid == IID_IAccessible)
*ppvObject = static_cast<IAccessible*>(this);
else if (riid == IID_IDispatch)
*ppvObject = static_cast<IDispatch*>(this);
else if (riid == IID_IUnknown)
*ppvObject = static_cast<IUnknown*>(this);
else
*ppvObject = nullptr;
if (*ppvObject)
reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
return (*ppvObject) ? S_OK : E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) CefIAccessible::AddRef() {
return InterlockedIncrement((LONG volatile*)&ref_count_);
}
STDMETHODIMP_(ULONG) CefIAccessible::Release() {
ULONG ulRefCnt = InterlockedDecrement((LONG volatile*)&ref_count_);
if (ulRefCnt == 0) {
if (node_)
node_->Destroy();
delete this;
}
return ulRefCnt;
}
STDMETHODIMP CefIAccessible::get_accParent(IDispatch** ppdispParent) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (ppdispParent) {
CefNativeAccessible* parent = node_->GetParentAccessibleObject();
if (!parent) {
HWND hWnd = ::GetParent(node_->GetWindowHandle());
if (hWnd) {
AccessibleObjectFromWindow(hWnd, (DWORD)OBJID_CLIENT, IID_IAccessible,
(void**)(&parent));
}
}
if (parent)
parent->AddRef();
*ppdispParent = parent;
retCode = (*ppdispParent) ? S_OK : S_FALSE;
}
} else {
retCode = E_INVALIDARG;
}
return retCode;
}
STDMETHODIMP CefIAccessible::get_accChildCount(long* pcountChildren) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode) && pcountChildren) {
*pcountChildren = node_->GetChildCount();
} else {
retCode = E_INVALIDARG;
}
return retCode;
}
STDMETHODIMP CefIAccessible::get_accChild(VARIANT varChild,
IDispatch** ppdispChild) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
int numChilds = node_->GetChildCount();
if (numChilds <= 0) {
*ppdispChild = nullptr;
return S_FALSE;
} else {
if (ppdispChild && VALID_CHILDID(varChild)) {
if (varChild.lVal == CHILDID_SELF) {
*ppdispChild = this;
} else {
OsrAXNode* child = node_->ChildAtIndex(varChild.lVal - 1);
if (!child)
child = node_->GetAccessibilityHelper()->GetFocusedNode();
*ppdispChild = child->GetNativeAccessibleObject(node_);
}
if (*ppdispChild == nullptr)
retCode = S_FALSE;
else
(*ppdispChild)->AddRef();
}
}
}
return retCode;
}
STDMETHODIMP CefIAccessible::get_accName(VARIANT varChild, BSTR* pszName) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (pszName && VALID_CHILDID(varChild)) {
std::wstring name = node_->AxName();
CComBSTR bstrResult(name.c_str());
*pszName = bstrResult.Detach();
}
} else {
retCode = E_INVALIDARG;
}
return retCode;
}
STDMETHODIMP CefIAccessible::get_accValue(VARIANT varChild, BSTR* pszValue) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (pszValue && VALID_CHILDID(varChild)) {
std::wstring name = node_->AxValue();
CComBSTR bstrResult(name.c_str());
*pszValue = bstrResult.Detach();
}
} else {
retCode = E_INVALIDARG;
}
return retCode;
}
STDMETHODIMP CefIAccessible::get_accDescription(VARIANT varChild,
BSTR* pszDescription) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (pszDescription && VALID_CHILDID(varChild)) {
std::wstring name = node_->AxDescription();
CComBSTR bstrResult(name.c_str());
*pszDescription = bstrResult.Detach();
}
} else {
retCode = E_INVALIDARG;
}
return retCode;
}
STDMETHODIMP CefIAccessible::get_accRole(VARIANT varChild, VARIANT* pvarRole) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (pvarRole) {
pvarRole->vt = VT_I4;
pvarRole->lVal = AxRoleToMSAARole(node_->AxRole());
} else {
retCode = E_INVALIDARG;
}
}
return retCode;
}
STDMETHODIMP CefIAccessible::get_accState(VARIANT varChild,
VARIANT* pvarState) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (pvarState) {
pvarState->vt = VT_I4;
pvarState->lVal =
(GetFocus() == node_->GetWindowHandle()) ? STATE_SYSTEM_FOCUSED : 0;
pvarState->lVal |= STATE_SYSTEM_PRESSED;
pvarState->lVal |= STATE_SYSTEM_FOCUSABLE;
if (varChild.lVal == CHILDID_SELF) {
DWORD dwStyle = GetWindowLong(node_->GetWindowHandle(), GWL_STYLE);
pvarState->lVal |=
((dwStyle & WS_VISIBLE) == 0) ? STATE_SYSTEM_INVISIBLE : 0;
pvarState->lVal |=
((dwStyle & WS_DISABLED) > 0) ? STATE_SYSTEM_UNAVAILABLE : 0;
}
} else {
retCode = E_INVALIDARG;
}
}
return retCode;
}
STDMETHODIMP CefIAccessible::get_accKeyboardShortcut(
VARIANT varChild,
BSTR* pszKeyboardShortcut) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (pszKeyboardShortcut && VALID_CHILDID(varChild))
*pszKeyboardShortcut = ::SysAllocString(L"None");
else
retCode = E_INVALIDARG;
}
return retCode;
}
STDMETHODIMP CefIAccessible::get_accFocus(VARIANT* pFocusChild) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
OsrAXNode* focusedNode = node_->GetAccessibilityHelper()->GetFocusedNode();
CefNativeAccessible* nativeObj = nullptr;
if (focusedNode)
nativeObj = focusedNode->GetNativeAccessibleObject(nullptr);
if (nativeObj) {
if (nativeObj == this) {
pFocusChild->vt = VT_I4;
pFocusChild->lVal = CHILDID_SELF;
} else {
pFocusChild->vt = VT_DISPATCH;
pFocusChild->pdispVal = nativeObj;
pFocusChild->pdispVal->AddRef();
}
} else {
pFocusChild->vt = VT_EMPTY;
}
}
return retCode;
}
STDMETHODIMP CefIAccessible::get_accSelection(VARIANT* pvarChildren) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (pvarChildren)
pvarChildren->vt = VT_EMPTY;
else
retCode = E_INVALIDARG;
}
return retCode;
}
STDMETHODIMP CefIAccessible::get_accDefaultAction(VARIANT varChild,
BSTR* pszDefaultAction) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (pszDefaultAction && VALID_CHILDID(varChild))
*pszDefaultAction = ::SysAllocString(L"Push");
else
retCode = E_INVALIDARG;
}
return retCode;
}
STDMETHODIMP CefIAccessible::accSelect(long flagsSelect, VARIANT varChild) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (VALID_CHILDID(varChild)) {
HWND hwnd = node_->GetWindowHandle();
if (((flagsSelect & SELFLAG_TAKEFOCUS) > 0) && (GetFocus() == hwnd)) {
RECT rcWnd;
GetClientRect(hwnd, &rcWnd);
InvalidateRect(hwnd, &rcWnd, FALSE);
} else {
retCode = S_FALSE;
}
} else {
retCode = E_INVALIDARG;
}
}
return retCode;
}
STDMETHODIMP CefIAccessible::accLocation(long* pxLeft,
long* pyTop,
long* pcxWidth,
long* pcyHeight,
VARIANT varChild) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (pxLeft && pyTop && pcxWidth && pcyHeight && VALID_CHILDID(varChild)) {
CefRect loc = node_->AxLocation();
RECT rcItem = {loc.x, loc.y, loc.x + loc.width, loc.y + loc.height};
HWND hwnd = node_->GetWindowHandle();
ClientToScreen(hwnd, &rcItem);
*pxLeft = rcItem.left;
*pyTop = rcItem.top;
*pcxWidth = rcItem.right - rcItem.left;
*pcyHeight = rcItem.bottom - rcItem.top;
} else {
retCode = E_INVALIDARG;
}
}
return retCode;
}
STDMETHODIMP CefIAccessible::accNavigate(long navDir,
VARIANT varStart,
VARIANT* pvarEndUpAt) {
return E_NOTIMPL;
}
STDMETHODIMP CefIAccessible::accHitTest(long xLeft,
long yTop,
VARIANT* pvarChild) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode)) {
if (pvarChild) {
pvarChild->vt = VT_EMPTY;
CefRect loc = node_->AxLocation();
RECT rcItem = {loc.x, loc.y, loc.x + loc.width, loc.y + loc.height};
POINT pt = {xLeft, yTop};
ClientToScreen(node_->GetWindowHandle(), &rcItem);
if (PtInRect(&rcItem, pt)) {
pvarChild->vt = VT_I4;
pvarChild->lVal = 1;
}
} else {
retCode = E_INVALIDARG;
}
}
return retCode;
}
STDMETHODIMP CefIAccessible::accDoDefaultAction(VARIANT varChild) {
HRESULT retCode = DATACHECK(node_);
if (SUCCEEDED(retCode) && VALID_CHILDID(varChild)) {
CefRefPtr<CefBrowser> browser = node_->GetBrowser();
if (browser) {
CefMouseEvent mouse_event;
const CefRect& rect = node_->AxLocation();
mouse_event.x = MiddleX(rect);
mouse_event.y = MiddleY(rect);
mouse_event.modifiers = 0;
browser->GetHost()->SendMouseClickEvent(mouse_event, MBT_LEFT, false, 1);
browser->GetHost()->SendMouseClickEvent(mouse_event, MBT_LEFT, true, 1);
}
} else {
retCode = E_INVALIDARG;
}
return retCode;
}
STDMETHODIMP CefIAccessible::put_accName(VARIANT varChild, BSTR szName) {
return E_NOTIMPL;
}
STDMETHODIMP CefIAccessible::put_accValue(VARIANT varChild, BSTR szValue) {
return E_NOTIMPL;
}
STDMETHODIMP CefIAccessible::get_accHelp(VARIANT varChild, BSTR* pszHelp) {
return E_NOTIMPL;
}
STDMETHODIMP CefIAccessible::get_accHelpTopic(BSTR* pszHelpFile,
VARIANT varChild,
long* pidTopic) {
return E_NOTIMPL;
}
STDMETHODIMP CefIAccessible::GetTypeInfoCount(unsigned int FAR* pctinfo) {
return E_NOTIMPL;
}
STDMETHODIMP CefIAccessible::GetTypeInfo(unsigned int iTInfo,
LCID lcid,
ITypeInfo FAR* FAR* ppTInfo) {
return E_NOTIMPL;
}
STDMETHODIMP CefIAccessible::GetIDsOfNames(REFIID riid,
OLECHAR FAR* FAR* rgszNames,
unsigned int cNames,
LCID lcid,
DISPID FAR* rgDispId) {
return E_NOTIMPL;
}
STDMETHODIMP CefIAccessible::Invoke(DISPID dispIdMember,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS FAR* pDispParams,
VARIANT FAR* pVarResult,
EXCEPINFO FAR* pExcepInfo,
unsigned int FAR* puArgErr) {
return E_NOTIMPL;
}
void OsrAXNode::NotifyAccessibilityEvent(std::string event_type) const {
if (event_type == "focus") {
::NotifyWinEvent(EVENT_OBJECT_FOCUS, GetWindowHandle(), OBJID_CLIENT,
node_id_);
}
}
void OsrAXNode::Destroy() {
CefIAccessible* ptr = static_cast<CefIAccessible*>(platform_accessibility_);
if (ptr)
ptr->MarkDestroyed();
platform_accessibility_ = nullptr;
}
CefNativeAccessible* OsrAXNode::GetNativeAccessibleObject(OsrAXNode* parent) {
if (!platform_accessibility_) {
platform_accessibility_ = new CefIAccessible(this);
platform_accessibility_->AddRef();
SetParent(parent);
}
return platform_accessibility_;
}
}
#else
namespace client {
void OsrAXNode::NotifyAccessibilityEvent(std::string event_type) const {}
void OsrAXNode::Destroy() {}
CefNativeAccessible* OsrAXNode::GetNativeAccessibleObject(OsrAXNode* parent) {
return nullptr;
}
}
#endif