Reviewed by Adam Roben.
[WebKit-https.git] / WebKitTools / DumpRenderTree / win / EventSender.cpp
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 getConstantCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
67 {
68     if (JSStringIsEqualToUTF8CString(propertyName, "WM_KEYDOWN"))
69         return JSValueMakeNumber(context, WM_KEYDOWN);
70     if (JSStringIsEqualToUTF8CString(propertyName, "WM_KEYUP"))
71         return JSValueMakeNumber(context, WM_KEYUP);
72     if (JSStringIsEqualToUTF8CString(propertyName, "WM_CHAR"))
73         return JSValueMakeNumber(context, WM_CHAR);
74     if (JSStringIsEqualToUTF8CString(propertyName, "WM_DEADCHAR"))
75         return JSValueMakeNumber(context, WM_DEADCHAR);
76     if (JSStringIsEqualToUTF8CString(propertyName, "WM_SYSKEYDOWN"))
77         return JSValueMakeNumber(context, WM_SYSKEYDOWN);
78     if (JSStringIsEqualToUTF8CString(propertyName, "WM_SYSKEYUP"))
79         return JSValueMakeNumber(context, WM_SYSKEYUP);
80     if (JSStringIsEqualToUTF8CString(propertyName, "WM_SYSCHAR"))
81         return JSValueMakeNumber(context, WM_SYSCHAR);
82     if (JSStringIsEqualToUTF8CString(propertyName, "WM_SYSDEADCHAR"))
83         return JSValueMakeNumber(context, WM_SYSDEADCHAR);
84     ASSERT_NOT_REACHED();
85     return JSValueMakeUndefined(context);
86 }
87
88 static JSValueRef leapForwardCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
89 {
90     if (argumentCount > 0) {
91         timeOffset += JSValueToNumber(context, arguments[0], exception);
92         ASSERT(!exception || !*exception);
93     }
94
95     return JSValueMakeUndefined(context);
96 }
97
98 static DWORD currentEventTime()
99 {
100     return ::GetTickCount() + timeOffset;
101 }
102
103 static MSG makeMsg(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
104 {
105     MSG result = {0};
106     result.hwnd = hwnd;
107     result.message = message;
108     result.wParam = wParam;
109     result.lParam = lParam;
110     result.time = currentEventTime();
111     result.pt = lastMousePosition;
112
113     return result;
114 }
115
116 static LRESULT dispatchMessage(const MSG* msg)
117 {
118     ASSERT(msg);
119
120     ::TranslateMessage(msg);
121     return ::DispatchMessage(msg);
122 }
123
124 static JSValueRef contextClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
125 {
126     COMPtr<IWebFramePrivate> framePrivate;
127     if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
128         framePrivate->layout();
129
130     down = true;
131     MSG msg = makeMsg(webViewWindow, WM_RBUTTONDOWN, 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y));
132     dispatchMessage(&msg);
133     down = false;
134     msg = makeMsg(webViewWindow, WM_RBUTTONUP, 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y));
135     dispatchMessage(&msg);
136     
137     return JSValueMakeUndefined(context);
138 }
139
140 static JSValueRef mouseDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
141 {
142     COMPtr<IWebFramePrivate> framePrivate;
143     if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
144         framePrivate->layout();
145
146     down = true;
147     MSG msg = makeMsg(webViewWindow, WM_LBUTTONDOWN, 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y));
148     dispatchMessage(&msg);
149
150     return JSValueMakeUndefined(context);
151 }
152
153 static inline POINTL pointl(const POINT& point)
154 {
155     POINTL result;
156     result.x = point.x;
157     result.y = point.y;
158     return result;
159 }
160
161 static void doMouseUp(MSG msg)
162 {
163     COMPtr<IWebFramePrivate> framePrivate;
164     if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
165         framePrivate->layout();
166
167     dispatchMessage(&msg);
168     down = false;
169
170     if (draggingInfo) {
171         COMPtr<IWebView> webView;
172         COMPtr<IDropTarget> webViewDropTarget;
173         if (SUCCEEDED(frame->webView(&webView)) && SUCCEEDED(webView->QueryInterface(IID_IDropTarget, (void**)&webViewDropTarget))) {
174             POINT screenPoint = msg.pt;
175             ::ClientToScreen(webViewWindow, &screenPoint);
176             HRESULT hr = draggingInfo->dropSource()->QueryContinueDrag(0, 0);
177             DWORD effect = 0;
178             webViewDropTarget->DragOver(0, pointl(screenPoint), &effect);
179             if (hr == DRAGDROP_S_DROP && effect != DROPEFFECT_NONE) {
180                 DWORD effect = 0;
181                 webViewDropTarget->Drop(draggingInfo->dataObject(), 0, pointl(screenPoint), &effect);
182             } else
183                 webViewDropTarget->DragLeave();
184
185             delete draggingInfo;
186             draggingInfo = 0;
187         }
188     }
189 }
190
191 static JSValueRef mouseUpCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
192 {
193     MSG msg = makeMsg(webViewWindow, WM_LBUTTONUP, 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y));
194
195     if (dragMode && !replayingSavedEvents) {
196         msgQueue[endOfQueue++] = msg;
197         replaySavedEvents();
198     } else
199         doMouseUp(msg);
200
201     return JSValueMakeUndefined(context);
202 }
203
204 static void doMouseMove(MSG msg)
205 {
206     COMPtr<IWebFramePrivate> framePrivate;
207     if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
208         framePrivate->layout();
209
210     dispatchMessage(&msg);
211
212     if (down && draggingInfo) {
213         POINT screenPoint = msg.pt;
214         ::ClientToScreen(webViewWindow, &screenPoint);
215
216         IWebView* webView;
217         COMPtr<IDropTarget> webViewDropTarget;
218         if (SUCCEEDED(frame->webView(&webView)) && SUCCEEDED(webView->QueryInterface(IID_IDropTarget, (void**)&webViewDropTarget))) {
219             DWORD effect = 0;
220             if (didDragEnter)
221                 webViewDropTarget->DragOver(MK_LBUTTON, pointl(screenPoint), &effect);
222             else {
223                 webViewDropTarget->DragEnter(draggingInfo->dataObject(), MK_LBUTTON, pointl(screenPoint), &effect);
224                 didDragEnter = true;
225             }
226             draggingInfo->dropSource()->GiveFeedback(effect);
227             replaySavedEvents();
228         }
229     }
230 }
231
232 static JSValueRef mouseMoveToCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
233 {
234     if (argumentCount < 2)
235         return JSValueMakeUndefined(context);
236
237     lastMousePosition.x = (int)JSValueToNumber(context, arguments[0], exception);
238     ASSERT(!exception || !*exception);
239     lastMousePosition.y = (int)JSValueToNumber(context, arguments[1], exception);
240     ASSERT(!exception || !*exception);
241
242     MSG msg = makeMsg(webViewWindow, WM_MOUSEMOVE, down ? MK_LBUTTON : 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y));
243
244     if (dragMode && down && !replayingSavedEvents) {
245         msgQueue[endOfQueue++] = msg;
246         return JSValueMakeUndefined(context);
247     }
248
249     doMouseMove(msg);
250
251     return JSValueMakeUndefined(context);
252 }
253
254 void replaySavedEvents()
255 {
256     replayingSavedEvents = true;
257
258     MSG emptyMsg = {0};
259     while (startOfQueue < endOfQueue) {
260         MSG msg = msgQueue[startOfQueue++];
261         switch (msg.message) {
262             case WM_LBUTTONUP:
263                 doMouseUp(msg);
264                 break;
265             case WM_MOUSEMOVE:
266                 doMouseMove(msg);
267                 break;
268             default:
269                 // Not reached
270                 break;
271         }
272     }
273     startOfQueue = 0;
274     endOfQueue = 0;
275
276     replayingSavedEvents = false;
277 }
278
279 static JSValueRef keyDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
280 {
281     if (argumentCount < 1)
282         return JSValueMakeUndefined(context);
283
284     static const JSStringRef lengthProperty = JSStringCreateWithUTF8CString("length");
285
286     COMPtr<IWebFramePrivate> framePrivate;
287     if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
288         framePrivate->layout();
289     
290     JSStringRef character = JSValueToStringCopy(context, arguments[0], exception);
291     ASSERT(!*exception);
292     int virtualKeyCode;
293     int charCode = 0;
294     bool needsShiftKeyModifier = false;
295     if (JSStringIsEqualToUTF8CString(character, "leftArrow"))
296         virtualKeyCode = VK_LEFT;
297     else if (JSStringIsEqualToUTF8CString(character, "rightArrow"))
298         virtualKeyCode = VK_RIGHT;
299     else if (JSStringIsEqualToUTF8CString(character, "upArrow"))
300         virtualKeyCode = VK_UP;
301     else if (JSStringIsEqualToUTF8CString(character, "downArrow"))
302         virtualKeyCode = VK_DOWN;
303     else if (JSStringIsEqualToUTF8CString(character, "delete"))
304         virtualKeyCode = VK_BACK;
305     else {
306         charCode = JSStringGetCharactersPtr(character)[0];
307         virtualKeyCode = LOBYTE(VkKeyScan(charCode));
308         if (isupper(charCode))
309             needsShiftKeyModifier = true;
310     }
311     JSStringRelease(character);
312
313     BYTE keyState[256];
314     if (argumentCount > 1 || needsShiftKeyModifier) {
315         ::GetKeyboardState(keyState);
316
317         BYTE newKeyState[256];
318         memcpy(newKeyState, keyState, sizeof(keyState));
319
320         if (needsShiftKeyModifier)
321             newKeyState[VK_SHIFT] = 0x80;
322
323         if (argumentCount > 1) {
324             JSObjectRef modifiersArray = JSValueToObject(context, arguments[1], exception);
325             if (modifiersArray) {
326                 int modifiersCount = JSValueToNumber(context, JSObjectGetProperty(context, modifiersArray, lengthProperty, 0), 0);
327                 for (int i = 0; i < modifiersCount; ++i) {
328                     JSValueRef value = JSObjectGetPropertyAtIndex(context, modifiersArray, i, 0);
329                     JSStringRef string = JSValueToStringCopy(context, value, 0);
330                     if (JSStringIsEqualToUTF8CString(string, "ctrlKey"))
331                         newKeyState[VK_CONTROL] = 0x80;
332                     else if (JSStringIsEqualToUTF8CString(string, "shiftKey"))
333                         newKeyState[VK_SHIFT] = 0x80;
334                     else if (JSStringIsEqualToUTF8CString(string, "altKey"))
335                         newKeyState[VK_MENU] = 0x80;
336                     else if (JSStringIsEqualToUTF8CString(string, "metaKey"))
337                         newKeyState[VK_MENU] = 0x80;
338
339                     JSStringRelease(string);
340                 }
341             }
342         }
343
344         ::SetKeyboardState(newKeyState);
345     }
346
347     MSG msg = makeMsg(webViewWindow, WM_KEYDOWN, virtualKeyCode, 0);
348     if (virtualKeyCode != 255)
349         dispatchMessage(&msg);
350     else {
351         // For characters that do not exist in the active keyboard layout,
352         // ::Translate will not work, so we post an WM_CHAR event ourselves.
353         ::PostMessage(webViewWindow, WM_CHAR, charCode, 0);
354     }
355
356     // Tests expect that all messages are processed by the time keyDown() returns.
357     if (::PeekMessage(&msg, webViewWindow, WM_CHAR, WM_CHAR, PM_REMOVE))
358         ::DispatchMessage(&msg);
359
360     MSG msgUp = makeMsg(webViewWindow, WM_KEYUP, virtualKeyCode, 0);
361     ::DispatchMessage(&msgUp);
362
363     if (argumentCount > 1 || needsShiftKeyModifier)
364         ::SetKeyboardState(keyState);
365
366     return JSValueMakeUndefined(context);
367 }
368
369 // eventSender.dispatchMessage(message, wParam, lParam, time = currentEventTime(), x = lastMousePosition.x, y = lastMousePosition.y)
370 static JSValueRef dispatchMessageCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
371 {
372     if (argumentCount < 3)
373         return JSValueMakeUndefined(context);
374
375     COMPtr<IWebFramePrivate> framePrivate;
376     if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
377         framePrivate->layout();
378     
379     MSG msg = {};
380     msg.hwnd = webViewWindow;
381     msg.message = JSValueToNumber(context, arguments[0], exception);
382     ASSERT(!*exception);
383     msg.wParam = JSValueToNumber(context, arguments[1], exception);
384     ASSERT(!*exception);
385     msg.lParam = static_cast<ULONG_PTR>(JSValueToNumber(context, arguments[2], exception));
386     ASSERT(!*exception);
387     if (argumentCount >= 4) {
388         msg.time = JSValueToNumber(context, arguments[3], exception);
389         ASSERT(!*exception);
390     }
391     if (!msg.time)
392         msg.time = currentEventTime();
393     if (argumentCount >= 6) {
394         msg.pt.x = JSValueToNumber(context, arguments[4], exception);
395         ASSERT(!*exception);
396         msg.pt.y = JSValueToNumber(context, arguments[5], exception);
397         ASSERT(!*exception);
398     } else
399         msg.pt = lastMousePosition;
400
401     ::DispatchMessage(&msg);
402
403     return JSValueMakeUndefined(context);
404 }
405
406 static JSValueRef textZoomInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
407 {
408     COMPtr<IWebView> webView;
409     if (FAILED(frame->webView(&webView)))
410         return JSValueMakeUndefined(context);
411
412     COMPtr<IWebIBActions> webIBActions;
413     if (FAILED(webView->QueryInterface(IID_IWebIBActions, (void**)&webIBActions)))
414         return JSValueMakeUndefined(context);
415
416     webIBActions->makeTextLarger(0);
417     return JSValueMakeUndefined(context);
418 }
419
420 static JSValueRef textZoomOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
421 {
422     COMPtr<IWebView> webView;
423     if (FAILED(frame->webView(&webView)))
424         return JSValueMakeUndefined(context);
425
426     COMPtr<IWebIBActions> webIBActions;
427     if (FAILED(webView->QueryInterface(IID_IWebIBActions, (void**)&webIBActions)))
428         return JSValueMakeUndefined(context);
429
430     webIBActions->makeTextSmaller(0);
431     return JSValueMakeUndefined(context);
432 }
433
434 static JSStaticFunction staticFunctions[] = {
435     { "contextClick", contextClickCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
436     { "mouseDown", mouseDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
437     { "mouseUp", mouseUpCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
438     { "mouseMoveTo", mouseMoveToCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
439     { "leapForward", leapForwardCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
440     { "keyDown", keyDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
441     { "dispatchMessage", dispatchMessageCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
442     { "textZoomIn", textZoomInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
443     { "textZoomOut", textZoomOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
444     { 0, 0, 0 }
445 };
446
447 static JSStaticValue staticValues[] = {
448     { "dragMode", getDragModeCallback, setDragModeCallback, kJSPropertyAttributeNone },
449     { "WM_KEYDOWN", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
450     { "WM_KEYUP", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
451     { "WM_CHAR", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
452     { "WM_DEADCHAR", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
453     { "WM_SYSKEYDOWN", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
454     { "WM_SYSKEYUP", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
455     { "WM_SYSCHAR", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
456     { "WM_SYSDEADCHAR", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
457     { 0, 0, 0, 0 }
458 };
459
460 static JSClassRef getClass(JSContextRef context)
461 {
462     static JSClassRef eventSenderClass = 0;
463
464     if (!eventSenderClass) {
465         JSClassDefinition classDefinition = {0};
466         classDefinition.staticFunctions = staticFunctions;
467         classDefinition.staticValues = staticValues;
468
469         eventSenderClass = JSClassCreate(&classDefinition);
470     }
471
472     return eventSenderClass;
473 }
474
475 JSObjectRef makeEventSender(JSContextRef context)
476 {
477     return JSObjectMake(context, getClass(context), 0);
478 }