Reviewed by Maciej.
[WebKit-https.git] / WebKitTools / DumpRenderTree / win / EventSender.cpp
1 /*
2  * Copyright (C) 2007, 2008 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     int keyData = 1;
295     bool needsShiftKeyModifier = false;
296     if (JSStringIsEqualToUTF8CString(character, "leftArrow")) {
297         virtualKeyCode = VK_LEFT;
298         keyData += KF_EXTENDED << 16; // In this case, extended means "not keypad".
299     } else if (JSStringIsEqualToUTF8CString(character, "rightArrow")) {
300         virtualKeyCode = VK_RIGHT;
301         keyData += KF_EXTENDED << 16;
302     } else if (JSStringIsEqualToUTF8CString(character, "upArrow")) {
303         virtualKeyCode = VK_UP;
304         keyData += KF_EXTENDED << 16;
305     } else if (JSStringIsEqualToUTF8CString(character, "downArrow")) {
306         virtualKeyCode = VK_DOWN;
307         keyData += KF_EXTENDED << 16;
308     } else if (JSStringIsEqualToUTF8CString(character, "pageUp"))
309         virtualKeyCode = VK_PRIOR;
310     else if (JSStringIsEqualToUTF8CString(character, "pageDown"))
311         virtualKeyCode = VK_NEXT;
312     else if (JSStringIsEqualToUTF8CString(character, "delete"))
313         virtualKeyCode = VK_BACK;
314     else {
315         charCode = JSStringGetCharactersPtr(character)[0];
316         virtualKeyCode = LOBYTE(VkKeyScan(charCode));
317         if (isupper(charCode))
318             needsShiftKeyModifier = true;
319     }
320     JSStringRelease(character);
321
322     BYTE keyState[256];
323     if (argumentCount > 1 || needsShiftKeyModifier) {
324         ::GetKeyboardState(keyState);
325
326         BYTE newKeyState[256];
327         memcpy(newKeyState, keyState, sizeof(keyState));
328
329         if (needsShiftKeyModifier)
330             newKeyState[VK_SHIFT] = 0x80;
331
332         if (argumentCount > 1) {
333             JSObjectRef modifiersArray = JSValueToObject(context, arguments[1], exception);
334             if (modifiersArray) {
335                 int modifiersCount = JSValueToNumber(context, JSObjectGetProperty(context, modifiersArray, lengthProperty, 0), 0);
336                 for (int i = 0; i < modifiersCount; ++i) {
337                     JSValueRef value = JSObjectGetPropertyAtIndex(context, modifiersArray, i, 0);
338                     JSStringRef string = JSValueToStringCopy(context, value, 0);
339                     if (JSStringIsEqualToUTF8CString(string, "ctrlKey"))
340                         newKeyState[VK_CONTROL] = 0x80;
341                     else if (JSStringIsEqualToUTF8CString(string, "shiftKey"))
342                         newKeyState[VK_SHIFT] = 0x80;
343                     else if (JSStringIsEqualToUTF8CString(string, "altKey"))
344                         newKeyState[VK_MENU] = 0x80;
345                     else if (JSStringIsEqualToUTF8CString(string, "metaKey"))
346                         newKeyState[VK_MENU] = 0x80;
347
348                     JSStringRelease(string);
349                 }
350             }
351         }
352
353         ::SetKeyboardState(newKeyState);
354     }
355
356     MSG msg = makeMsg(webViewWindow, (::GetKeyState(VK_MENU) & 0x8000) ? WM_SYSKEYDOWN : WM_KEYDOWN, virtualKeyCode, keyData);
357     if (virtualKeyCode != 255)
358         dispatchMessage(&msg);
359     else {
360         // For characters that do not exist in the active keyboard layout,
361         // ::Translate will not work, so we post an WM_CHAR event ourselves.
362         ::PostMessage(webViewWindow, WM_CHAR, charCode, 0);
363     }
364
365     // Tests expect that all messages are processed by the time keyDown() returns.
366     if (::PeekMessage(&msg, webViewWindow, WM_CHAR, WM_CHAR, PM_REMOVE) || ::PeekMessage(&msg, webViewWindow, WM_SYSCHAR, WM_SYSCHAR, PM_REMOVE))
367         ::DispatchMessage(&msg);
368
369     MSG msgUp = makeMsg(webViewWindow, (::GetKeyState(VK_MENU) & 0x8000) ? WM_SYSKEYUP : WM_KEYUP, virtualKeyCode, keyData);
370     ::DispatchMessage(&msgUp);
371
372     if (argumentCount > 1 || needsShiftKeyModifier)
373         ::SetKeyboardState(keyState);
374
375     return JSValueMakeUndefined(context);
376 }
377
378 // eventSender.dispatchMessage(message, wParam, lParam, time = currentEventTime(), x = lastMousePosition.x, y = lastMousePosition.y)
379 static JSValueRef dispatchMessageCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
380 {
381     if (argumentCount < 3)
382         return JSValueMakeUndefined(context);
383
384     COMPtr<IWebFramePrivate> framePrivate;
385     if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
386         framePrivate->layout();
387     
388     MSG msg = {};
389     msg.hwnd = webViewWindow;
390     msg.message = JSValueToNumber(context, arguments[0], exception);
391     ASSERT(!*exception);
392     msg.wParam = JSValueToNumber(context, arguments[1], exception);
393     ASSERT(!*exception);
394     msg.lParam = static_cast<ULONG_PTR>(JSValueToNumber(context, arguments[2], exception));
395     ASSERT(!*exception);
396     if (argumentCount >= 4) {
397         msg.time = JSValueToNumber(context, arguments[3], exception);
398         ASSERT(!*exception);
399     }
400     if (!msg.time)
401         msg.time = currentEventTime();
402     if (argumentCount >= 6) {
403         msg.pt.x = JSValueToNumber(context, arguments[4], exception);
404         ASSERT(!*exception);
405         msg.pt.y = JSValueToNumber(context, arguments[5], exception);
406         ASSERT(!*exception);
407     } else
408         msg.pt = lastMousePosition;
409
410     ::DispatchMessage(&msg);
411
412     return JSValueMakeUndefined(context);
413 }
414
415 static JSValueRef textZoomInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
416 {
417     COMPtr<IWebView> webView;
418     if (FAILED(frame->webView(&webView)))
419         return JSValueMakeUndefined(context);
420
421     COMPtr<IWebIBActions> webIBActions;
422     if (FAILED(webView->QueryInterface(IID_IWebIBActions, (void**)&webIBActions)))
423         return JSValueMakeUndefined(context);
424
425     webIBActions->makeTextLarger(0);
426     return JSValueMakeUndefined(context);
427 }
428
429 static JSValueRef textZoomOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
430 {
431     COMPtr<IWebView> webView;
432     if (FAILED(frame->webView(&webView)))
433         return JSValueMakeUndefined(context);
434
435     COMPtr<IWebIBActions> webIBActions;
436     if (FAILED(webView->QueryInterface(IID_IWebIBActions, (void**)&webIBActions)))
437         return JSValueMakeUndefined(context);
438
439     webIBActions->makeTextSmaller(0);
440     return JSValueMakeUndefined(context);
441 }
442
443 static JSStaticFunction staticFunctions[] = {
444     { "contextClick", contextClickCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
445     { "mouseDown", mouseDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
446     { "mouseUp", mouseUpCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
447     { "mouseMoveTo", mouseMoveToCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
448     { "leapForward", leapForwardCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
449     { "keyDown", keyDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
450     { "dispatchMessage", dispatchMessageCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
451     { "textZoomIn", textZoomInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
452     { "textZoomOut", textZoomOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
453     { 0, 0, 0 }
454 };
455
456 static JSStaticValue staticValues[] = {
457     { "dragMode", getDragModeCallback, setDragModeCallback, kJSPropertyAttributeNone },
458     { "WM_KEYDOWN", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
459     { "WM_KEYUP", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
460     { "WM_CHAR", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
461     { "WM_DEADCHAR", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
462     { "WM_SYSKEYDOWN", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
463     { "WM_SYSKEYUP", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
464     { "WM_SYSCHAR", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
465     { "WM_SYSDEADCHAR", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
466     { 0, 0, 0, 0 }
467 };
468
469 static JSClassRef getClass(JSContextRef context)
470 {
471     static JSClassRef eventSenderClass = 0;
472
473     if (!eventSenderClass) {
474         JSClassDefinition classDefinition = {0};
475         classDefinition.staticFunctions = staticFunctions;
476         classDefinition.staticValues = staticValues;
477
478         eventSenderClass = JSClassCreate(&classDefinition);
479     }
480
481     return eventSenderClass;
482 }
483
484 JSObjectRef makeEventSender(JSContextRef context)
485 {
486     return JSObjectMake(context, getClass(context), 0);
487 }