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