|
1 /* |
|
2 * Copyright (C) 2007 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 * |
|
8 * 1. Redistributions of source code must retain the above copyright |
|
9 * notice, this list of conditions and the following disclaimer. |
|
10 * 2. Redistributions in binary form must reproduce the above copyright |
|
11 * notice, this list of conditions and the following disclaimer in the |
|
12 * documentation and/or other materials provided with the distribution. |
|
13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
|
14 * its contributors may be used to endorse or promote products derived |
|
15 * from this software without specific prior written permission. |
|
16 * |
|
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
|
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
|
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
|
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
|
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
27 */ |
|
28 |
|
29 #include "DumpRenderTree.h" |
|
30 #include "EventSender.h" |
|
31 |
|
32 #include "DraggingInfo.h" |
|
33 |
|
34 #include <WebCore/COMPtr.h> |
|
35 #include <wtf/Platform.h> |
|
36 #include <JavaScriptCore/JavaScriptCore.h> |
|
37 #include <JavaScriptCore/Assertions.h> |
|
38 #include <WebKit/IWebFrame.h> |
|
39 #include <WebKit/IWebFramePrivate.h> |
|
40 #include <windows.h> |
|
41 |
|
42 static bool down; |
|
43 static bool dragMode = true; |
|
44 static bool replayingSavedEvents; |
|
45 static int timeOffset; |
|
46 static POINT lastMousePosition; |
|
47 |
|
48 static MSG msgQueue[1024]; |
|
49 static unsigned endOfQueue; |
|
50 static unsigned startOfQueue; |
|
51 |
|
52 static bool didDragEnter; |
|
53 DraggingInfo* draggingInfo = 0; |
|
54 |
|
55 static JSValueRef getDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception) |
|
56 { |
|
57 return JSValueMakeBoolean(context, dragMode); |
|
58 } |
|
59 |
|
60 static bool setDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception) |
|
61 { |
|
62 dragMode = JSValueToBoolean(context, value); |
|
63 return true; |
|
64 } |
|
65 |
|
66 static JSValueRef leapForwardCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) |
|
67 { |
|
68 if (argumentCount > 0) { |
|
69 timeOffset += JSValueToNumber(context, arguments[0], exception); |
|
70 ASSERT(!exception || !*exception); |
|
71 } |
|
72 |
|
73 return JSValueMakeUndefined(context); |
|
74 } |
|
75 |
|
76 static DWORD currentEventTime() |
|
77 { |
|
78 return ::GetTickCount() + timeOffset; |
|
79 } |
|
80 |
|
81 static MSG makeMsg(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) |
|
82 { |
|
83 MSG result = {0}; |
|
84 result.hwnd = hwnd; |
|
85 result.message = message; |
|
86 result.wParam = wParam; |
|
87 result.lParam = lParam; |
|
88 result.time = currentEventTime(); |
|
89 result.pt = lastMousePosition; |
|
90 |
|
91 return result; |
|
92 } |
|
93 |
|
94 static LRESULT dispatchMessage(const MSG* msg) |
|
95 { |
|
96 ASSERT(msg); |
|
97 |
|
98 ::TranslateMessage(msg); |
|
99 return ::DispatchMessage(msg); |
|
100 } |
|
101 |
|
102 static JSValueRef mouseDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) |
|
103 { |
|
104 COMPtr<IWebFramePrivate> framePrivate; |
|
105 if (SUCCEEDED(frame->QueryInterface(&framePrivate))) |
|
106 framePrivate->layout(); |
|
107 |
|
108 down = true; |
|
109 MSG msg = makeMsg(webViewWindow, WM_LBUTTONDOWN, 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y)); |
|
110 dispatchMessage(&msg); |
|
111 |
|
112 return JSValueMakeUndefined(context); |
|
113 } |
|
114 |
|
115 static inline POINTL pointl(const POINT& point) |
|
116 { |
|
117 POINTL result; |
|
118 result.x = point.x; |
|
119 result.y = point.y; |
|
120 return result; |
|
121 } |
|
122 |
|
123 static void doMouseUp(MSG msg) |
|
124 { |
|
125 COMPtr<IWebFramePrivate> framePrivate; |
|
126 if (SUCCEEDED(frame->QueryInterface(&framePrivate))) |
|
127 framePrivate->layout(); |
|
128 |
|
129 dispatchMessage(&msg); |
|
130 down = false; |
|
131 |
|
132 if (draggingInfo) { |
|
133 COMPtr<IWebView> webView; |
|
134 COMPtr<IDropTarget> webViewDropTarget; |
|
135 if (SUCCEEDED(frame->webView(&webView)) && SUCCEEDED(webView->QueryInterface(IID_IDropTarget, (void**)&webViewDropTarget))) { |
|
136 POINT screenPoint = msg.pt; |
|
137 ::ClientToScreen(webViewWindow, &screenPoint); |
|
138 HRESULT hr = draggingInfo->dropSource()->QueryContinueDrag(0, 0); |
|
139 DWORD effect = 0; |
|
140 webViewDropTarget->DragOver(0, pointl(screenPoint), &effect); |
|
141 if (hr == DRAGDROP_S_DROP && effect != DROPEFFECT_NONE) { |
|
142 DWORD effect = 0; |
|
143 webViewDropTarget->Drop(draggingInfo->dataObject(), 0, pointl(screenPoint), &effect); |
|
144 } else |
|
145 webViewDropTarget->DragLeave(); |
|
146 |
|
147 delete draggingInfo; |
|
148 draggingInfo = 0; |
|
149 } |
|
150 } |
|
151 } |
|
152 |
|
153 static JSValueRef mouseUpCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) |
|
154 { |
|
155 MSG msg = makeMsg(webViewWindow, WM_LBUTTONUP, 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y)); |
|
156 |
|
157 if (dragMode && !replayingSavedEvents) { |
|
158 msgQueue[endOfQueue++] = msg; |
|
159 replaySavedEvents(); |
|
160 } else |
|
161 doMouseUp(msg); |
|
162 |
|
163 return JSValueMakeUndefined(context); |
|
164 } |
|
165 |
|
166 static void doMouseMove(MSG msg) |
|
167 { |
|
168 COMPtr<IWebFramePrivate> framePrivate; |
|
169 if (SUCCEEDED(frame->QueryInterface(&framePrivate))) |
|
170 framePrivate->layout(); |
|
171 |
|
172 dispatchMessage(&msg); |
|
173 |
|
174 if (down && draggingInfo) { |
|
175 POINT screenPoint = msg.pt; |
|
176 ::ClientToScreen(webViewWindow, &screenPoint); |
|
177 |
|
178 IWebView* webView; |
|
179 COMPtr<IDropTarget> webViewDropTarget; |
|
180 if (SUCCEEDED(frame->webView(&webView)) && SUCCEEDED(webView->QueryInterface(IID_IDropTarget, (void**)&webViewDropTarget))) { |
|
181 DWORD effect = 0; |
|
182 if (didDragEnter) |
|
183 webViewDropTarget->DragOver(MK_LBUTTON, pointl(screenPoint), &effect); |
|
184 else { |
|
185 webViewDropTarget->DragEnter(draggingInfo->dataObject(), MK_LBUTTON, pointl(screenPoint), &effect); |
|
186 didDragEnter = true; |
|
187 } |
|
188 draggingInfo->dropSource()->GiveFeedback(effect); |
|
189 } |
|
190 } |
|
191 } |
|
192 |
|
193 static JSValueRef mouseMoveToCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) |
|
194 { |
|
195 if (argumentCount < 2) |
|
196 return JSValueMakeUndefined(context); |
|
197 |
|
198 lastMousePosition.x = (int)JSValueToNumber(context, arguments[0], exception); |
|
199 ASSERT(!exception || !*exception); |
|
200 lastMousePosition.y = (int)JSValueToNumber(context, arguments[1], exception); |
|
201 ASSERT(!exception || !*exception); |
|
202 |
|
203 MSG msg = makeMsg(webViewWindow, WM_MOUSEMOVE, down ? MK_LBUTTON : 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y)); |
|
204 |
|
205 if (dragMode && down && !replayingSavedEvents) { |
|
206 msgQueue[endOfQueue++] = msg; |
|
207 return JSValueMakeUndefined(context); |
|
208 } |
|
209 |
|
210 doMouseMove(msg); |
|
211 |
|
212 return JSValueMakeUndefined(context); |
|
213 } |
|
214 |
|
215 void replaySavedEvents() |
|
216 { |
|
217 replayingSavedEvents = true; |
|
218 |
|
219 MSG emptyMsg = {0}; |
|
220 while (startOfQueue < endOfQueue) { |
|
221 MSG msg = msgQueue[startOfQueue++]; |
|
222 switch (msg.message) { |
|
223 case WM_LBUTTONUP: |
|
224 doMouseUp(msg); |
|
225 break; |
|
226 case WM_MOUSEMOVE: |
|
227 doMouseMove(msg); |
|
228 break; |
|
229 default: |
|
230 // Not reached |
|
231 break; |
|
232 } |
|
233 } |
|
234 startOfQueue = 0; |
|
235 endOfQueue = 0; |
|
236 |
|
237 replayingSavedEvents = false; |
|
238 } |
|
239 |
|
240 static JSValueRef keyDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) |
|
241 { |
|
242 if (argumentCount < 1) |
|
243 return JSValueMakeUndefined(context); |
|
244 |
|
245 static JSStringRef ctrlKey = JSStringCreateWithUTF8CString("ctrlKey"); |
|
246 static JSStringRef shiftKey = JSStringCreateWithUTF8CString("shiftKey"); |
|
247 static JSStringRef altKey = JSStringCreateWithUTF8CString("altKey"); |
|
248 static JSStringRef metaKey = JSStringCreateWithUTF8CString("metaKey"); |
|
249 static JSStringRef lengthProperty = JSStringCreateWithUTF8CString("length"); |
|
250 |
|
251 COMPtr<IWebFramePrivate> framePrivate; |
|
252 if (SUCCEEDED(frame->QueryInterface(&framePrivate))) |
|
253 framePrivate->layout(); |
|
254 |
|
255 JSStringRef character = JSValueToStringCopy(context, arguments[0], exception); |
|
256 ASSERT(!exception || !*exception); |
|
257 int charCode = JSStringGetCharactersPtr(character)[0]; |
|
258 int virtualKeyCode = toupper(LOBYTE(VkKeyScan(charCode))); |
|
259 JSStringRelease(character); |
|
260 |
|
261 // Hack to map option-delete to ctrl-delete |
|
262 // Remove this when we fix <rdar://problem/5102974> layout tests need a way to decide how to choose the appropriate modifier keys |
|
263 bool convertOptionToCtrl = false; |
|
264 if (virtualKeyCode == VK_DELETE || virtualKeyCode == VK_BACK) |
|
265 convertOptionToCtrl = true; |
|
266 |
|
267 BYTE keyState[256]; |
|
268 if (argumentCount > 1) { |
|
269 ::GetKeyboardState(keyState); |
|
270 |
|
271 BYTE newKeyState[256]; |
|
272 memcpy(newKeyState, keyState, sizeof(keyState)); |
|
273 |
|
274 JSObjectRef modifiersArray = JSValueToObject(context, arguments[1], exception); |
|
275 if (modifiersArray) { |
|
276 int modifiersCount = JSValueToNumber(context, JSObjectGetProperty(context, modifiersArray, lengthProperty, 0), 0); |
|
277 for (int i = 0; i < modifiersCount; ++i) { |
|
278 JSValueRef value = JSObjectGetPropertyAtIndex(context, modifiersArray, i, 0); |
|
279 JSStringRef string = JSValueToStringCopy(context, value, 0); |
|
280 if (JSStringIsEqual(string, ctrlKey)) |
|
281 newKeyState[VK_CONTROL] = 0x80; |
|
282 else if (JSStringIsEqual(string, shiftKey)) |
|
283 newKeyState[VK_SHIFT] = 0x80; |
|
284 else if (JSStringIsEqual(string, altKey)) { |
|
285 if (convertOptionToCtrl) |
|
286 newKeyState[VK_CONTROL] = 0x80; |
|
287 else |
|
288 newKeyState[VK_MENU] = 0x80; |
|
289 } else if (JSStringIsEqual(string, metaKey)) |
|
290 newKeyState[VK_MENU] = 0x80; |
|
291 |
|
292 JSStringRelease(string); |
|
293 } |
|
294 } |
|
295 |
|
296 ::SetKeyboardState(newKeyState); |
|
297 } |
|
298 |
|
299 MSG msg = makeMsg(webViewWindow, WM_KEYDOWN, virtualKeyCode, 0); |
|
300 dispatchMessage(&msg); |
|
301 |
|
302 if (argumentCount > 1) |
|
303 ::SetKeyboardState(keyState); |
|
304 |
|
305 return JSValueMakeUndefined(context); |
|
306 } |
|
307 |
|
308 static JSValueRef textZoomInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) |
|
309 { |
|
310 COMPtr<IWebView> webView; |
|
311 if (FAILED(frame->webView(&webView))) |
|
312 return JSValueMakeUndefined(context); |
|
313 |
|
314 COMPtr<IWebIBActions> webIBActions; |
|
315 if (FAILED(webView->QueryInterface(IID_IWebIBActions, (void**)&webIBActions))) |
|
316 return JSValueMakeUndefined(context); |
|
317 |
|
318 webIBActions->makeTextLarger(0); |
|
319 return JSValueMakeUndefined(context); |
|
320 } |
|
321 |
|
322 static JSValueRef textZoomOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) |
|
323 { |
|
324 COMPtr<IWebView> webView; |
|
325 if (FAILED(frame->webView(&webView))) |
|
326 return JSValueMakeUndefined(context); |
|
327 |
|
328 COMPtr<IWebIBActions> webIBActions; |
|
329 if (FAILED(webView->QueryInterface(IID_IWebIBActions, (void**)&webIBActions))) |
|
330 return JSValueMakeUndefined(context); |
|
331 |
|
332 webIBActions->makeTextSmaller(0); |
|
333 return JSValueMakeUndefined(context); |
|
334 } |
|
335 |
|
336 static JSStaticFunction staticFunctions[] = { |
|
337 { "mouseDown", mouseDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, |
|
338 { "mouseUp", mouseUpCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, |
|
339 { "mouseMoveTo", mouseMoveToCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, |
|
340 { "leapForward", leapForwardCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, |
|
341 { "keyDown", keyDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, |
|
342 { "textZoomIn", textZoomInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, |
|
343 { "textZoomOut", textZoomOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, |
|
344 { 0, 0, 0 } |
|
345 }; |
|
346 |
|
347 static JSStaticValue staticValues[] = { |
|
348 { "dragMode", getDragModeCallback, setDragModeCallback, kJSPropertyAttributeNone }, |
|
349 { 0, 0, 0, 0 } |
|
350 }; |
|
351 |
|
352 static JSClassRef getClass(JSContextRef context) { |
|
353 static JSClassRef eventSenderClass = 0; |
|
354 |
|
355 if (!eventSenderClass) { |
|
356 JSClassDefinition classDefinition = {0}; |
|
357 classDefinition.staticFunctions = staticFunctions; |
|
358 classDefinition.staticValues = staticValues; |
|
359 |
|
360 eventSenderClass = JSClassCreate(&classDefinition); |
|
361 } |
|
362 |
|
363 return eventSenderClass; |
|
364 } |
|
365 |
|
366 JSObjectRef makeEventSender(JSContextRef context) |
|
367 { |
|
368 return JSObjectMake(context, getClass(context), 0); |
|
369 } |