| /* |
| * Copyright (C) 2008, 2009, 2010 Apple Inc. All Rights Reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "AccessibilityController.h" |
| |
| #include "AccessibilityUIElement.h" |
| #include "DumpRenderTree.h" |
| #include "FrameLoadDelegate.h" |
| #include <JavaScriptCore/Assertions.h> |
| #include <JavaScriptCore/JSRetainPtr.h> |
| #include <JavaScriptCore/JSStringRef.h> |
| #include <WebCore/COMPtr.h> |
| #include <WebKit/WebKit.h> |
| #include <oleacc.h> |
| #include <string> |
| |
| using namespace std; |
| |
| AccessibilityController::AccessibilityController() |
| : m_focusEventHook(0) |
| , m_scrollingStartEventHook(0) |
| , m_valueChangeEventHook(0) |
| , m_allEventsHook(0) |
| { |
| } |
| |
| AccessibilityController::~AccessibilityController() |
| { |
| setLogFocusEvents(false); |
| setLogValueChangeEvents(false); |
| |
| if (m_allEventsHook) |
| UnhookWinEvent(m_allEventsHook); |
| |
| for (HashMap<PlatformUIElement, JSObjectRef>::iterator it = m_notificationListeners.begin(); it != m_notificationListeners.end(); ++it) |
| JSValueUnprotect(frame->globalContext(), it->second); |
| } |
| |
| AccessibilityUIElement AccessibilityController::elementAtPoint(int x, int y) |
| { |
| // FIXME: implement |
| return 0; |
| } |
| |
| AccessibilityUIElement AccessibilityController::focusedElement() |
| { |
| COMPtr<IAccessible> rootAccessible = rootElement().platformUIElement(); |
| |
| VARIANT vFocus; |
| if (FAILED(rootAccessible->get_accFocus(&vFocus))) |
| return 0; |
| |
| if (V_VT(&vFocus) == VT_I4) { |
| ASSERT(V_I4(&vFocus) == CHILDID_SELF); |
| // The root accessible object is the focused object. |
| return rootAccessible; |
| } |
| |
| ASSERT(V_VT(&vFocus) == VT_DISPATCH); |
| // We have an IDispatch; query for IAccessible. |
| return COMPtr<IAccessible>(Query, V_DISPATCH(&vFocus)); |
| } |
| |
| AccessibilityUIElement AccessibilityController::rootElement() |
| { |
| COMPtr<IWebView> view; |
| if (FAILED(frame->webView(&view))) |
| return 0; |
| |
| COMPtr<IWebViewPrivate> viewPrivate(Query, view); |
| if (!viewPrivate) |
| return 0; |
| |
| HWND webViewWindow; |
| if (FAILED(viewPrivate->viewWindow((OLE_HANDLE*)&webViewWindow))) |
| return 0; |
| |
| // Get the root accessible object by querying for the accessible object for the |
| // WebView's window. |
| COMPtr<IAccessible> rootAccessible; |
| if (FAILED(AccessibleObjectFromWindow(webViewWindow, static_cast<DWORD>(OBJID_CLIENT), __uuidof(IAccessible), reinterpret_cast<void**>(&rootAccessible)))) |
| return 0; |
| |
| return rootAccessible; |
| } |
| |
| static void CALLBACK logEventProc(HWINEVENTHOOK, DWORD event, HWND hwnd, LONG idObject, LONG idChild, DWORD, DWORD) |
| { |
| // Get the accessible object for this event. |
| COMPtr<IAccessible> parentObject; |
| |
| VARIANT vChild; |
| VariantInit(&vChild); |
| |
| HRESULT hr = AccessibleObjectFromEvent(hwnd, idObject, idChild, &parentObject, &vChild); |
| ASSERT(SUCCEEDED(hr)); |
| |
| // Get the name of the focused element, and log it to stdout. |
| BSTR nameBSTR; |
| hr = parentObject->get_accName(vChild, &nameBSTR); |
| ASSERT(SUCCEEDED(hr)); |
| wstring name(nameBSTR, ::SysStringLen(nameBSTR)); |
| SysFreeString(nameBSTR); |
| |
| switch (event) { |
| case EVENT_OBJECT_FOCUS: |
| printf("Received focus event for object '%S'.\n", name.c_str()); |
| break; |
| |
| case EVENT_OBJECT_VALUECHANGE: { |
| BSTR valueBSTR; |
| hr = parentObject->get_accValue(vChild, &valueBSTR); |
| ASSERT(SUCCEEDED(hr)); |
| wstring value(valueBSTR, ::SysStringLen(valueBSTR)); |
| SysFreeString(valueBSTR); |
| |
| printf("Received value change event for object '%S', value '%S'.\n", name.c_str(), value.c_str()); |
| break; |
| } |
| |
| case EVENT_SYSTEM_SCROLLINGSTART: |
| printf("Received scrolling start event for object '%S'.\n", name.c_str()); |
| break; |
| |
| default: |
| printf("Received unknown event for object '%S'.\n", name.c_str()); |
| break; |
| } |
| |
| VariantClear(&vChild); |
| } |
| |
| void AccessibilityController::setLogFocusEvents(bool logFocusEvents) |
| { |
| if (!!m_focusEventHook == logFocusEvents) |
| return; |
| |
| if (!logFocusEvents) { |
| UnhookWinEvent(m_focusEventHook); |
| m_focusEventHook = 0; |
| return; |
| } |
| |
| // Ensure that accessibility is initialized for the WebView by querying for |
| // the root accessible object. |
| rootElement(); |
| |
| m_focusEventHook = SetWinEventHook(EVENT_OBJECT_FOCUS, EVENT_OBJECT_FOCUS, GetModuleHandle(0), logEventProc, GetCurrentProcessId(), 0, WINEVENT_INCONTEXT); |
| |
| ASSERT(m_focusEventHook); |
| } |
| |
| void AccessibilityController::setLogValueChangeEvents(bool logValueChangeEvents) |
| { |
| if (!!m_valueChangeEventHook == logValueChangeEvents) |
| return; |
| |
| if (!logValueChangeEvents) { |
| UnhookWinEvent(m_valueChangeEventHook); |
| m_valueChangeEventHook = 0; |
| return; |
| } |
| |
| // Ensure that accessibility is initialized for the WebView by querying for |
| // the root accessible object. |
| rootElement(); |
| |
| m_valueChangeEventHook = SetWinEventHook(EVENT_OBJECT_VALUECHANGE, EVENT_OBJECT_VALUECHANGE, GetModuleHandle(0), logEventProc, GetCurrentProcessId(), 0, WINEVENT_INCONTEXT); |
| |
| ASSERT(m_valueChangeEventHook); |
| } |
| |
| void AccessibilityController::setLogScrollingStartEvents(bool logScrollingStartEvents) |
| { |
| if (!!m_scrollingStartEventHook == logScrollingStartEvents) |
| return; |
| |
| if (!logScrollingStartEvents) { |
| UnhookWinEvent(m_scrollingStartEventHook); |
| m_scrollingStartEventHook = 0; |
| return; |
| } |
| |
| // Ensure that accessibility is initialized for the WebView by querying for |
| // the root accessible object. |
| rootElement(); |
| |
| m_scrollingStartEventHook = SetWinEventHook(EVENT_SYSTEM_SCROLLINGSTART, EVENT_SYSTEM_SCROLLINGSTART, GetModuleHandle(0), logEventProc, GetCurrentProcessId(), 0, WINEVENT_INCONTEXT); |
| |
| ASSERT(m_scrollingStartEventHook); |
| } |
| |
| void AccessibilityController::setLogAccessibilityEvents(bool) |
| { |
| } |
| |
| static string stringEvent(DWORD event) |
| { |
| switch(event) { |
| case EVENT_OBJECT_VALUECHANGE: |
| return "value change event"; |
| default: |
| return "unknown event"; |
| } |
| } |
| |
| static void CALLBACK notificationListenerProc(HWINEVENTHOOK, DWORD event, HWND hwnd, LONG idObject, LONG idChild, DWORD, DWORD) |
| { |
| // Get the accessible object for this event. |
| COMPtr<IAccessible> parentObject; |
| |
| VARIANT vChild; |
| VariantInit(&vChild); |
| |
| HRESULT hr = AccessibleObjectFromEvent(hwnd, idObject, idChild, &parentObject, &vChild); |
| if (FAILED(hr) || !parentObject) |
| return; |
| |
| COMPtr<IDispatch> childDispatch; |
| if (FAILED(parentObject->get_accChild(vChild, &childDispatch))) { |
| VariantClear(&vChild); |
| return; |
| } |
| |
| COMPtr<IAccessible> childAccessible(Query, childDispatch); |
| |
| sharedFrameLoadDelegate->accessibilityController()->notificationReceived(childAccessible, stringEvent(event)); |
| |
| VariantClear(&vChild); |
| } |
| |
| static COMPtr<IAccessibleComparable> comparableObject(const COMPtr<IServiceProvider>& serviceProvider) |
| { |
| COMPtr<IAccessibleComparable> comparable; |
| serviceProvider->QueryService(SID_AccessibleComparable, __uuidof(IAccessibleComparable), reinterpret_cast<void**>(&comparable)); |
| return comparable; |
| } |
| |
| void AccessibilityController::notificationReceived(PlatformUIElement element, const string& eventName) |
| { |
| for (HashMap<PlatformUIElement, JSObjectRef>::iterator it = m_notificationListeners.begin(); it != m_notificationListeners.end(); ++it) { |
| COMPtr<IServiceProvider> thisServiceProvider(Query, it->first); |
| if (!thisServiceProvider) |
| continue; |
| |
| COMPtr<IAccessibleComparable> thisComparable = comparableObject(thisServiceProvider); |
| if (!thisComparable) |
| continue; |
| |
| COMPtr<IServiceProvider> elementServiceProvider(Query, element); |
| if (!elementServiceProvider) |
| continue; |
| |
| COMPtr<IAccessibleComparable> elementComparable = comparableObject(elementServiceProvider); |
| if (!elementComparable) |
| continue; |
| |
| BOOL isSame = FALSE; |
| thisComparable->isSameObject(elementComparable.get(), &isSame); |
| if (!isSame) |
| continue; |
| |
| JSRetainPtr<JSStringRef> jsNotification(Adopt, JSStringCreateWithUTF8CString(eventName.c_str())); |
| JSValueRef argument = JSValueMakeString(frame->globalContext(), jsNotification.get()); |
| JSObjectCallAsFunction(frame->globalContext(), it->second, NULL, 1, &argument, NULL); |
| } |
| } |
| |
| void AccessibilityController::addNotificationListener(PlatformUIElement element, JSObjectRef functionCallback) |
| { |
| if (!m_allEventsHook) |
| m_allEventsHook = SetWinEventHook(EVENT_MIN, EVENT_MAX, GetModuleHandle(0), notificationListenerProc, GetCurrentProcessId(), 0, WINEVENT_INCONTEXT); |
| |
| JSValueProtect(frame->globalContext(), functionCallback); |
| m_notificationListeners.add(element, functionCallback); |
| } |