| /* |
| * Copyright (C) 2008, 2009, 2010, 2013 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 "TestRunner.h" |
| #include <JavaScriptCore/JSRetainPtr.h> |
| #include <JavaScriptCore/JSStringRef.h> |
| #include <JavaScriptCore/JSStringRefBSTR.h> |
| #include <WebCore/AccessibilityObjectWrapperWin.h> |
| #include <WebCore/COMPtr.h> |
| #include <WebKitLegacy/WebKit.h> |
| #include <comutil.h> |
| #include <oleacc.h> |
| #include <string> |
| #include <wtf/Assertions.h> |
| #include <wtf/text/AtomicString.h> |
| |
| AccessibilityController::AccessibilityController() = default; |
| |
| AccessibilityController::~AccessibilityController() |
| { |
| setLogFocusEvents(false); |
| setLogAccessibilityEvents(false); |
| setLogValueChangeEvents(false); |
| |
| if (m_notificationsEventHook) |
| UnhookWinEvent(m_notificationsEventHook); |
| |
| for (auto& listener : m_notificationListeners.values()) |
| JSValueUnprotect(frame->globalContext(), listener); |
| } |
| |
| AccessibilityUIElement AccessibilityController::elementAtPoint(int x, int y) |
| { |
| // FIXME: implement |
| return { nullptr }; |
| } |
| |
| static COMPtr<IAccessibleComparable> comparableObject(const COMPtr<IServiceProvider>& serviceProvider) |
| { |
| COMPtr<IAccessibleComparable> comparable; |
| serviceProvider->QueryService(SID_AccessibleComparable, __uuidof(IAccessibleComparable), reinterpret_cast<void**>(&comparable)); |
| return comparable; |
| } |
| |
| static COMPtr<IAccessible> findAccessibleObjectById(AccessibilityUIElement parentObject, BSTR idAttribute) |
| { |
| COMPtr<IAccessible> parentIAccessible = parentObject.platformUIElement(); |
| |
| COMPtr<IServiceProvider> serviceProvider(Query, parentIAccessible); |
| if (!serviceProvider) |
| return 0; |
| |
| COMPtr<IAccessibleComparable> comparable = comparableObject(serviceProvider); |
| if (!comparable) |
| return 0; |
| |
| _variant_t value; |
| _bstr_t elementIdAttributeKey(L"AXDRTElementIdAttribute"); |
| if (SUCCEEDED(comparable->get_attribute(elementIdAttributeKey, &value.GetVARIANT()))) { |
| ASSERT(V_VT(&value) == VT_BSTR); |
| if (VARCMP_EQ == ::VarBstrCmp(value.bstrVal, idAttribute, LOCALE_USER_DEFAULT, 0)) |
| return parentIAccessible; |
| } |
| |
| long childCount = parentObject.childrenCount(); |
| if (!childCount) |
| return nullptr; |
| |
| COMPtr<IAccessible> result; |
| for (long i = 0; i < childCount; ++i) { |
| AccessibilityUIElement childAtIndex = parentObject.getChildAtIndex(i); |
| |
| result = findAccessibleObjectById(childAtIndex, idAttribute); |
| if (result) |
| return result; |
| } |
| |
| return nullptr; |
| } |
| |
| AccessibilityUIElement AccessibilityController::accessibleElementById(JSStringRef id) |
| { |
| AccessibilityUIElement rootAccessibilityUIElement = rootElement(); |
| |
| _bstr_t idAttribute(JSStringCopyBSTR(id), false); |
| |
| COMPtr<IAccessible> result = findAccessibleObjectById(rootAccessibilityUIElement, idAttribute); |
| if (result) |
| return AccessibilityUIElement(result); |
| |
| return { nullptr }; |
| } |
| |
| AccessibilityUIElement AccessibilityController::focusedElement() |
| { |
| COMPtr<IAccessible> rootAccessible = rootElement().platformUIElement(); |
| |
| _variant_t vFocus; |
| if (FAILED(rootAccessible->get_accFocus(&vFocus.GetVARIANT()))) |
| return { nullptr }; |
| |
| 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 { nullptr }; |
| |
| COMPtr<IWebViewPrivate2> viewPrivate(Query, view); |
| if (!viewPrivate) |
| return { nullptr }; |
| |
| HWND webViewWindow; |
| if (FAILED(viewPrivate->viewWindow(&webViewWindow))) |
| return { nullptr }; |
| |
| // Make sure the layout is up to date, so we can find all accessible elements. |
| COMPtr<IWebFramePrivate> framePrivate(Query, frame); |
| if (framePrivate) |
| framePrivate->layout(); |
| |
| // 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 { nullptr }; |
| |
| 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_t vChild; |
| |
| HRESULT hr = AccessibleObjectFromEvent(hwnd, idObject, idChild, &parentObject, &vChild.GetVARIANT()); |
| ASSERT(SUCCEEDED(hr)); |
| |
| // Get the name of the focused element, and log it to stdout. |
| _bstr_t nameBSTR; |
| hr = parentObject->get_accName(vChild, &nameBSTR.GetBSTR()); |
| ASSERT(SUCCEEDED(hr)); |
| std::wstring name(nameBSTR, nameBSTR.length()); |
| |
| switch (event) { |
| case EVENT_OBJECT_FOCUS: |
| fprintf(testResult, "Received focus event for object '%S'.\n", name.c_str()); |
| break; |
| |
| case EVENT_OBJECT_SELECTION: |
| fprintf(testResult, "Received selection event for object '%S'.\n", name.c_str()); |
| break; |
| |
| case EVENT_OBJECT_VALUECHANGE: { |
| _bstr_t valueBSTR; |
| hr = parentObject->get_accValue(vChild, &valueBSTR.GetBSTR()); |
| ASSERT(SUCCEEDED(hr)); |
| std::wstring value(valueBSTR, valueBSTR.length()); |
| |
| fprintf(testResult, "Received value change event for object '%S', value '%S'.\n", name.c_str(), value.c_str()); |
| break; |
| } |
| |
| case EVENT_SYSTEM_SCROLLINGSTART: |
| fprintf(testResult, "Received scrolling start event for object '%S'.\n", name.c_str()); |
| break; |
| |
| default: |
| fprintf(testResult, "Received unknown event for object '%S'.\n", name.c_str()); |
| break; |
| } |
| } |
| |
| 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::platformResetToConsistentState() |
| { |
| } |
| |
| 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 logAccessibilityEvents) |
| { |
| if (!!m_allEventsHook == logAccessibilityEvents) |
| return; |
| |
| if (!logAccessibilityEvents) { |
| UnhookWinEvent(m_allEventsHook); |
| m_allEventsHook = 0; |
| return; |
| } |
| |
| // Ensure that accessibility is initialized for the WebView by querying for |
| // the root accessible object. |
| rootElement(); |
| |
| m_allEventsHook = SetWinEventHook(EVENT_MIN, EVENT_MAX, GetModuleHandle(0), logEventProc, GetCurrentProcessId(), 0, WINEVENT_INCONTEXT); |
| |
| ASSERT(m_allEventsHook); |
| } |
| |
| static std::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_t vChild; |
| HRESULT hr = AccessibleObjectFromEvent(hwnd, idObject, idChild, &parentObject, &vChild.GetVARIANT()); |
| if (FAILED(hr) || !parentObject) |
| return; |
| |
| COMPtr<IDispatch> childDispatch; |
| if (FAILED(parentObject->get_accChild(vChild, &childDispatch))) |
| return; |
| |
| COMPtr<IAccessible> childAccessible(Query, childDispatch); |
| sharedFrameLoadDelegate->accessibilityController()->winNotificationReceived(childAccessible, stringEvent(event)); |
| } |
| |
| bool AccessibilityController::addNotificationListener(JSObjectRef functionCallback) |
| { |
| return false; |
| } |
| |
| void AccessibilityController::removeNotificationListener() |
| { |
| } |
| |
| void AccessibilityController::winNotificationReceived(PlatformUIElement element, const std::string& eventName) |
| { |
| for (auto& slot : m_notificationListeners) { |
| COMPtr<IServiceProvider> thisServiceProvider(Query, slot.key); |
| 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; |
| |
| auto jsNotification = adopt(JSStringCreateWithUTF8CString(eventName.c_str())); |
| JSValueRef argument = JSValueMakeString(frame->globalContext(), jsNotification.get()); |
| JSObjectCallAsFunction(frame->globalContext(), slot.value, 0, 1, &argument, 0); |
| } |
| } |
| |
| void AccessibilityController::winAddNotificationListener(PlatformUIElement element, JSObjectRef functionCallback) |
| { |
| if (!m_notificationsEventHook) |
| m_notificationsEventHook = SetWinEventHook(EVENT_MIN, EVENT_MAX, GetModuleHandle(0), notificationListenerProc, GetCurrentProcessId(), 0, WINEVENT_INCONTEXT); |
| |
| JSValueProtect(frame->globalContext(), functionCallback); |
| m_notificationListeners.add(element, functionCallback); |
| } |
| |
| void AccessibilityController::enableEnhancedAccessibility(bool) |
| { |
| // FIXME: implement |
| } |
| |
| bool AccessibilityController::enhancedAccessibilityEnabled() |
| { |
| // FIXME: implement |
| return false; |
| } |
| |
| JSRetainPtr<JSStringRef> AccessibilityController::platformName() const |
| { |
| return adopt(JSStringCreateWithUTF8CString("win")); |
| } |