1 /* 2 * Copyright (C) 2008, 2009, 2010 Apple Inc. All Rights Reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "AccessibilityController.h" 28 29 #include "AccessibilityUIElement.h" 30 #include "DumpRenderTree.h" 31 #include "FrameLoadDelegate.h" 32 #include <JavaScriptCore/Assertions.h> 33 #include <JavaScriptCore/JSRetainPtr.h> 34 #include <JavaScriptCore/JSStringRef.h> 35 #include <WebCore/COMPtr.h> 36 #include <WebKit/WebKit.h> 37 #include <oleacc.h> 38 #include <string> 39 40 using namespace std; 41 42 AccessibilityController::AccessibilityController() 43 : m_focusEventHook(0) 44 , m_scrollingStartEventHook(0) 45 , m_valueChangeEventHook(0) 46 , m_allEventsHook(0) 47 { 48 } 49 50 AccessibilityController::~AccessibilityController() 51 { 52 setLogFocusEvents(false); 53 setLogValueChangeEvents(false); 54 55 if (m_allEventsHook) 56 UnhookWinEvent(m_allEventsHook); 57 58 for (HashMap<PlatformUIElement, JSObjectRef>::iterator it = m_notificationListeners.begin(); it != m_notificationListeners.end(); ++it) 59 JSValueUnprotect(frame->globalContext(), it->second); 60 } 61 62 AccessibilityUIElement AccessibilityController::elementAtPoint(int x, int y) 63 { 64 // FIXME: implement 65 return 0; 66 } 67 68 AccessibilityUIElement AccessibilityController::focusedElement() 69 { 70 COMPtr<IAccessible> rootAccessible = rootElement().platformUIElement(); 71 72 VARIANT vFocus; 73 if (FAILED(rootAccessible->get_accFocus(&vFocus))) 74 return 0; 75 76 if (V_VT(&vFocus) == VT_I4) { 77 ASSERT(V_I4(&vFocus) == CHILDID_SELF); 78 // The root accessible object is the focused object. 79 return rootAccessible; 80 } 81 82 ASSERT(V_VT(&vFocus) == VT_DISPATCH); 83 // We have an IDispatch; query for IAccessible. 84 return COMPtr<IAccessible>(Query, V_DISPATCH(&vFocus)); 85 } 86 87 AccessibilityUIElement AccessibilityController::rootElement() 88 { 89 COMPtr<IWebView> view; 90 if (FAILED(frame->webView(&view))) 91 return 0; 92 93 COMPtr<IWebViewPrivate> viewPrivate(Query, view); 94 if (!viewPrivate) 95 return 0; 96 97 HWND webViewWindow; 98 if (FAILED(viewPrivate->viewWindow((OLE_HANDLE*)&webViewWindow))) 99 return 0; 100 101 // Get the root accessible object by querying for the accessible object for the 102 // WebView's window. 103 COMPtr<IAccessible> rootAccessible; 104 if (FAILED(AccessibleObjectFromWindow(webViewWindow, static_cast<DWORD>(OBJID_CLIENT), __uuidof(IAccessible), reinterpret_cast<void**>(&rootAccessible)))) 105 return 0; 106 107 return rootAccessible; 108 } 109 110 static void CALLBACK logEventProc(HWINEVENTHOOK, DWORD event, HWND hwnd, LONG idObject, LONG idChild, DWORD, DWORD) 111 { 112 // Get the accessible object for this event. 113 COMPtr<IAccessible> parentObject; 114 115 VARIANT vChild; 116 VariantInit(&vChild); 117 118 HRESULT hr = AccessibleObjectFromEvent(hwnd, idObject, idChild, &parentObject, &vChild); 119 ASSERT(SUCCEEDED(hr)); 120 121 // Get the name of the focused element, and log it to stdout. 122 BSTR nameBSTR; 123 hr = parentObject->get_accName(vChild, &nameBSTR); 124 ASSERT(SUCCEEDED(hr)); 125 wstring name(nameBSTR, ::SysStringLen(nameBSTR)); 126 SysFreeString(nameBSTR); 127 128 switch (event) { 129 case EVENT_OBJECT_FOCUS: 130 printf("Received focus event for object '%S'.\n", name.c_str()); 131 break; 132 133 case EVENT_OBJECT_VALUECHANGE: { 134 BSTR valueBSTR; 135 hr = parentObject->get_accValue(vChild, &valueBSTR); 136 ASSERT(SUCCEEDED(hr)); 137 wstring value(valueBSTR, ::SysStringLen(valueBSTR)); 138 SysFreeString(valueBSTR); 139 140 printf("Received value change event for object '%S', value '%S'.\n", name.c_str(), value.c_str()); 141 break; 142 } 143 144 case EVENT_SYSTEM_SCROLLINGSTART: 145 printf("Received scrolling start event for object '%S'.\n", name.c_str()); 146 break; 147 148 default: 149 printf("Received unknown event for object '%S'.\n", name.c_str()); 150 break; 151 } 152 153 VariantClear(&vChild); 154 } 155 156 void AccessibilityController::setLogFocusEvents(bool logFocusEvents) 157 { 158 if (!!m_focusEventHook == logFocusEvents) 159 return; 160 161 if (!logFocusEvents) { 162 UnhookWinEvent(m_focusEventHook); 163 m_focusEventHook = 0; 164 return; 165 } 166 167 // Ensure that accessibility is initialized for the WebView by querying for 168 // the root accessible object. 169 rootElement(); 170 171 m_focusEventHook = SetWinEventHook(EVENT_OBJECT_FOCUS, EVENT_OBJECT_FOCUS, GetModuleHandle(0), logEventProc, GetCurrentProcessId(), 0, WINEVENT_INCONTEXT); 172 173 ASSERT(m_focusEventHook); 174 } 175 176 void AccessibilityController::setLogValueChangeEvents(bool logValueChangeEvents) 177 { 178 if (!!m_valueChangeEventHook == logValueChangeEvents) 179 return; 180 181 if (!logValueChangeEvents) { 182 UnhookWinEvent(m_valueChangeEventHook); 183 m_valueChangeEventHook = 0; 184 return; 185 } 186 187 // Ensure that accessibility is initialized for the WebView by querying for 188 // the root accessible object. 189 rootElement(); 190 191 m_valueChangeEventHook = SetWinEventHook(EVENT_OBJECT_VALUECHANGE, EVENT_OBJECT_VALUECHANGE, GetModuleHandle(0), logEventProc, GetCurrentProcessId(), 0, WINEVENT_INCONTEXT); 192 193 ASSERT(m_valueChangeEventHook); 194 } 195 196 void AccessibilityController::setLogScrollingStartEvents(bool logScrollingStartEvents) 197 { 198 if (!!m_scrollingStartEventHook == logScrollingStartEvents) 199 return; 200 201 if (!logScrollingStartEvents) { 202 UnhookWinEvent(m_scrollingStartEventHook); 203 m_scrollingStartEventHook = 0; 204 return; 205 } 206 207 // Ensure that accessibility is initialized for the WebView by querying for 208 // the root accessible object. 209 rootElement(); 210 211 m_scrollingStartEventHook = SetWinEventHook(EVENT_SYSTEM_SCROLLINGSTART, EVENT_SYSTEM_SCROLLINGSTART, GetModuleHandle(0), logEventProc, GetCurrentProcessId(), 0, WINEVENT_INCONTEXT); 212 213 ASSERT(m_scrollingStartEventHook); 214 } 215 216 void AccessibilityController::setLogAccessibilityEvents(bool) 217 { 218 } 219 220 static string stringEvent(DWORD event) 221 { 222 switch(event) { 223 case EVENT_OBJECT_VALUECHANGE: 224 return "value change event"; 225 default: 226 return "unknown event"; 227 } 228 } 229 230 static void CALLBACK notificationListenerProc(HWINEVENTHOOK, DWORD event, HWND hwnd, LONG idObject, LONG idChild, DWORD, DWORD) 231 { 232 // Get the accessible object for this event. 233 COMPtr<IAccessible> parentObject; 234 235 VARIANT vChild; 236 VariantInit(&vChild); 237 238 HRESULT hr = AccessibleObjectFromEvent(hwnd, idObject, idChild, &parentObject, &vChild); 239 if (FAILED(hr) || !parentObject) 240 return; 241 242 COMPtr<IDispatch> childDispatch; 243 if (FAILED(parentObject->get_accChild(vChild, &childDispatch))) { 244 VariantClear(&vChild); 245 return; 246 } 247 248 COMPtr<IAccessible> childAccessible(Query, childDispatch); 249 250 sharedFrameLoadDelegate->accessibilityController()->notificationReceived(childAccessible, stringEvent(event)); 251 252 VariantClear(&vChild); 253 } 254 255 static COMPtr<IAccessibleComparable> comparableObject(const COMPtr<IServiceProvider>& serviceProvider) 256 { 257 COMPtr<IAccessibleComparable> comparable; 258 serviceProvider->QueryService(SID_AccessibleComparable, __uuidof(IAccessibleComparable), reinterpret_cast<void**>(&comparable)); 259 return comparable; 260 } 261 262 void AccessibilityController::notificationReceived(PlatformUIElement element, const string& eventName) 263 { 264 for (HashMap<PlatformUIElement, JSObjectRef>::iterator it = m_notificationListeners.begin(); it != m_notificationListeners.end(); ++it) { 265 COMPtr<IServiceProvider> thisServiceProvider(Query, it->first); 266 if (!thisServiceProvider) 267 continue; 268 269 COMPtr<IAccessibleComparable> thisComparable = comparableObject(thisServiceProvider); 270 if (!thisComparable) 271 continue; 272 273 COMPtr<IServiceProvider> elementServiceProvider(Query, element); 274 if (!elementServiceProvider) 275 continue; 276 277 COMPtr<IAccessibleComparable> elementComparable = comparableObject(elementServiceProvider); 278 if (!elementComparable) 279 continue; 280 281 BOOL isSame = FALSE; 282 thisComparable->isSameObject(elementComparable.get(), &isSame); 283 if (!isSame) 284 continue; 285 286 JSRetainPtr<JSStringRef> jsNotification(Adopt, JSStringCreateWithUTF8CString(eventName.c_str())); 287 JSValueRef argument = JSValueMakeString(frame->globalContext(), jsNotification.get()); 288 JSObjectCallAsFunction(frame->globalContext(), it->second, NULL, 1, &argument, NULL); 289 } 290 } 291 292 void AccessibilityController::addNotificationListener(PlatformUIElement element, JSObjectRef functionCallback) 293 { 294 if (!m_allEventsHook) 295 m_allEventsHook = SetWinEventHook(EVENT_MIN, EVENT_MAX, GetModuleHandle(0), notificationListenerProc, GetCurrentProcessId(), 0, WINEVENT_INCONTEXT); 296 297 JSValueProtect(frame->globalContext(), functionCallback); 298 m_notificationListeners.add(element, functionCallback); 299 } 300