2 * Copyright (C) 2007 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
29 #include "DumpRenderTree.h"
30 #include "EventSender.h"
32 #include "DraggingInfo.h"
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>
43 static bool dragMode = true;
44 static bool replayingSavedEvents;
45 static int timeOffset;
46 static POINT lastMousePosition;
48 static MSG msgQueue[1024];
49 static unsigned endOfQueue;
50 static unsigned startOfQueue;
52 static bool didDragEnter;
53 DraggingInfo* draggingInfo = 0;
55 static JSValueRef getDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
57 return JSValueMakeBoolean(context, dragMode);
60 static bool setDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception)
62 dragMode = JSValueToBoolean(context, value);
66 static JSValueRef getConstantCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
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);
85 return JSValueMakeUndefined(context);
88 static JSValueRef leapForwardCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
90 if (argumentCount > 0) {
91 timeOffset += JSValueToNumber(context, arguments[0], exception);
92 ASSERT(!exception || !*exception);
95 return JSValueMakeUndefined(context);
98 static DWORD currentEventTime()
100 return ::GetTickCount() + timeOffset;
103 static MSG makeMsg(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
107 result.message = message;
108 result.wParam = wParam;
109 result.lParam = lParam;
110 result.time = currentEventTime();
111 result.pt = lastMousePosition;
116 static LRESULT dispatchMessage(const MSG* msg)
120 ::TranslateMessage(msg);
121 return ::DispatchMessage(msg);
124 static JSValueRef contextClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
126 COMPtr<IWebFramePrivate> framePrivate;
127 if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
128 framePrivate->layout();
131 MSG msg = makeMsg(webViewWindow, WM_RBUTTONDOWN, 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y));
132 dispatchMessage(&msg);
134 msg = makeMsg(webViewWindow, WM_RBUTTONUP, 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y));
135 dispatchMessage(&msg);
137 return JSValueMakeUndefined(context);
140 static JSValueRef mouseDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
142 COMPtr<IWebFramePrivate> framePrivate;
143 if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
144 framePrivate->layout();
147 MSG msg = makeMsg(webViewWindow, WM_LBUTTONDOWN, 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y));
148 dispatchMessage(&msg);
150 return JSValueMakeUndefined(context);
153 static inline POINTL pointl(const POINT& point)
161 static void doMouseUp(MSG msg)
163 COMPtr<IWebFramePrivate> framePrivate;
164 if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
165 framePrivate->layout();
167 dispatchMessage(&msg);
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);
178 webViewDropTarget->DragOver(0, pointl(screenPoint), &effect);
179 if (hr == DRAGDROP_S_DROP && effect != DROPEFFECT_NONE) {
181 webViewDropTarget->Drop(draggingInfo->dataObject(), 0, pointl(screenPoint), &effect);
183 webViewDropTarget->DragLeave();
191 static JSValueRef mouseUpCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
193 MSG msg = makeMsg(webViewWindow, WM_LBUTTONUP, 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y));
195 if (dragMode && !replayingSavedEvents) {
196 msgQueue[endOfQueue++] = msg;
201 return JSValueMakeUndefined(context);
204 static void doMouseMove(MSG msg)
206 COMPtr<IWebFramePrivate> framePrivate;
207 if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
208 framePrivate->layout();
210 dispatchMessage(&msg);
212 if (down && draggingInfo) {
213 POINT screenPoint = msg.pt;
214 ::ClientToScreen(webViewWindow, &screenPoint);
217 COMPtr<IDropTarget> webViewDropTarget;
218 if (SUCCEEDED(frame->webView(&webView)) && SUCCEEDED(webView->QueryInterface(IID_IDropTarget, (void**)&webViewDropTarget))) {
221 webViewDropTarget->DragOver(MK_LBUTTON, pointl(screenPoint), &effect);
223 webViewDropTarget->DragEnter(draggingInfo->dataObject(), MK_LBUTTON, pointl(screenPoint), &effect);
226 draggingInfo->dropSource()->GiveFeedback(effect);
232 static JSValueRef mouseMoveToCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
234 if (argumentCount < 2)
235 return JSValueMakeUndefined(context);
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);
242 MSG msg = makeMsg(webViewWindow, WM_MOUSEMOVE, down ? MK_LBUTTON : 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y));
244 if (dragMode && down && !replayingSavedEvents) {
245 msgQueue[endOfQueue++] = msg;
246 return JSValueMakeUndefined(context);
251 return JSValueMakeUndefined(context);
254 void replaySavedEvents()
256 replayingSavedEvents = true;
259 while (startOfQueue < endOfQueue) {
260 MSG msg = msgQueue[startOfQueue++];
261 switch (msg.message) {
276 replayingSavedEvents = false;
279 static JSValueRef keyDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
281 if (argumentCount < 1)
282 return JSValueMakeUndefined(context);
284 static const JSStringRef lengthProperty = JSStringCreateWithUTF8CString("length");
286 COMPtr<IWebFramePrivate> framePrivate;
287 if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
288 framePrivate->layout();
290 JSStringRef character = JSValueToStringCopy(context, arguments[0], exception);
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;
306 charCode = JSStringGetCharactersPtr(character)[0];
307 virtualKeyCode = LOBYTE(VkKeyScan(charCode));
308 if (isupper(charCode))
309 needsShiftKeyModifier = true;
311 JSStringRelease(character);
314 if (argumentCount > 1 || needsShiftKeyModifier) {
315 ::GetKeyboardState(keyState);
317 BYTE newKeyState[256];
318 memcpy(newKeyState, keyState, sizeof(keyState));
320 if (needsShiftKeyModifier)
321 newKeyState[VK_SHIFT] = 0x80;
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;
339 JSStringRelease(string);
344 ::SetKeyboardState(newKeyState);
347 MSG msg = makeMsg(webViewWindow, WM_KEYDOWN, virtualKeyCode, 0);
348 if (virtualKeyCode != 255)
349 dispatchMessage(&msg);
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);
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);
360 MSG msgUp = makeMsg(webViewWindow, WM_KEYUP, virtualKeyCode, 0);
361 ::DispatchMessage(&msgUp);
363 if (argumentCount > 1 || needsShiftKeyModifier)
364 ::SetKeyboardState(keyState);
366 return JSValueMakeUndefined(context);
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)
372 if (argumentCount < 3)
373 return JSValueMakeUndefined(context);
375 COMPtr<IWebFramePrivate> framePrivate;
376 if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
377 framePrivate->layout();
380 msg.hwnd = webViewWindow;
381 msg.message = JSValueToNumber(context, arguments[0], exception);
383 msg.wParam = JSValueToNumber(context, arguments[1], exception);
385 msg.lParam = static_cast<ULONG_PTR>(JSValueToNumber(context, arguments[2], exception));
387 if (argumentCount >= 4) {
388 msg.time = JSValueToNumber(context, arguments[3], exception);
392 msg.time = currentEventTime();
393 if (argumentCount >= 6) {
394 msg.pt.x = JSValueToNumber(context, arguments[4], exception);
396 msg.pt.y = JSValueToNumber(context, arguments[5], exception);
399 msg.pt = lastMousePosition;
401 ::DispatchMessage(&msg);
403 return JSValueMakeUndefined(context);
406 static JSValueRef textZoomInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
408 COMPtr<IWebView> webView;
409 if (FAILED(frame->webView(&webView)))
410 return JSValueMakeUndefined(context);
412 COMPtr<IWebIBActions> webIBActions;
413 if (FAILED(webView->QueryInterface(IID_IWebIBActions, (void**)&webIBActions)))
414 return JSValueMakeUndefined(context);
416 webIBActions->makeTextLarger(0);
417 return JSValueMakeUndefined(context);
420 static JSValueRef textZoomOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
422 COMPtr<IWebView> webView;
423 if (FAILED(frame->webView(&webView)))
424 return JSValueMakeUndefined(context);
426 COMPtr<IWebIBActions> webIBActions;
427 if (FAILED(webView->QueryInterface(IID_IWebIBActions, (void**)&webIBActions)))
428 return JSValueMakeUndefined(context);
430 webIBActions->makeTextSmaller(0);
431 return JSValueMakeUndefined(context);
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 },
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 },
460 static JSClassRef getClass(JSContextRef context)
462 static JSClassRef eventSenderClass = 0;
464 if (!eventSenderClass) {
465 JSClassDefinition classDefinition = {0};
466 classDefinition.staticFunctions = staticFunctions;
467 classDefinition.staticValues = staticValues;
469 eventSenderClass = JSClassCreate(&classDefinition);
472 return eventSenderClass;
475 JSObjectRef makeEventSender(JSContextRef context)
477 return JSObjectMake(context, getClass(context), 0);