/* * Copyright (C) 2007 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "DumpRenderTree.h" #include "EventSender.h" #include "DraggingInfo.h" #include #include #include #include #include #include #include static bool down; static bool dragMode = true; static bool replayingSavedEvents; static int timeOffset; static POINT lastMousePosition; static MSG msgQueue[1024]; static unsigned endOfQueue; static unsigned startOfQueue; static bool didDragEnter; DraggingInfo* draggingInfo = 0; static JSValueRef getDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception) { return JSValueMakeBoolean(context, dragMode); } static bool setDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception) { dragMode = JSValueToBoolean(context, value); return true; } static JSValueRef getConstantCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception) { if (JSStringIsEqualToUTF8CString(propertyName, "WM_KEYDOWN")) return JSValueMakeNumber(context, WM_KEYDOWN); if (JSStringIsEqualToUTF8CString(propertyName, "WM_KEYUP")) return JSValueMakeNumber(context, WM_KEYUP); if (JSStringIsEqualToUTF8CString(propertyName, "WM_CHAR")) return JSValueMakeNumber(context, WM_CHAR); if (JSStringIsEqualToUTF8CString(propertyName, "WM_DEADCHAR")) return JSValueMakeNumber(context, WM_DEADCHAR); if (JSStringIsEqualToUTF8CString(propertyName, "WM_SYSKEYDOWN")) return JSValueMakeNumber(context, WM_SYSKEYDOWN); if (JSStringIsEqualToUTF8CString(propertyName, "WM_SYSKEYUP")) return JSValueMakeNumber(context, WM_SYSKEYUP); if (JSStringIsEqualToUTF8CString(propertyName, "WM_SYSCHAR")) return JSValueMakeNumber(context, WM_SYSCHAR); if (JSStringIsEqualToUTF8CString(propertyName, "WM_SYSDEADCHAR")) return JSValueMakeNumber(context, WM_SYSDEADCHAR); ASSERT_NOT_REACHED(); return JSValueMakeUndefined(context); } static JSValueRef leapForwardCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { if (argumentCount > 0) { timeOffset += JSValueToNumber(context, arguments[0], exception); ASSERT(!exception || !*exception); } return JSValueMakeUndefined(context); } static DWORD currentEventTime() { return ::GetTickCount() + timeOffset; } static MSG makeMsg(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { MSG result = {0}; result.hwnd = hwnd; result.message = message; result.wParam = wParam; result.lParam = lParam; result.time = currentEventTime(); result.pt = lastMousePosition; return result; } static LRESULT dispatchMessage(const MSG* msg) { ASSERT(msg); ::TranslateMessage(msg); return ::DispatchMessage(msg); } static JSValueRef contextClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { COMPtr framePrivate; if (SUCCEEDED(frame->QueryInterface(&framePrivate))) framePrivate->layout(); down = true; MSG msg = makeMsg(webViewWindow, WM_RBUTTONDOWN, 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y)); dispatchMessage(&msg); down = false; msg = makeMsg(webViewWindow, WM_RBUTTONUP, 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y)); dispatchMessage(&msg); return JSValueMakeUndefined(context); } static JSValueRef mouseDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { COMPtr framePrivate; if (SUCCEEDED(frame->QueryInterface(&framePrivate))) framePrivate->layout(); down = true; MSG msg = makeMsg(webViewWindow, WM_LBUTTONDOWN, 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y)); dispatchMessage(&msg); return JSValueMakeUndefined(context); } static inline POINTL pointl(const POINT& point) { POINTL result; result.x = point.x; result.y = point.y; return result; } static void doMouseUp(MSG msg) { COMPtr framePrivate; if (SUCCEEDED(frame->QueryInterface(&framePrivate))) framePrivate->layout(); dispatchMessage(&msg); down = false; if (draggingInfo) { COMPtr webView; COMPtr webViewDropTarget; if (SUCCEEDED(frame->webView(&webView)) && SUCCEEDED(webView->QueryInterface(IID_IDropTarget, (void**)&webViewDropTarget))) { POINT screenPoint = msg.pt; ::ClientToScreen(webViewWindow, &screenPoint); HRESULT hr = draggingInfo->dropSource()->QueryContinueDrag(0, 0); DWORD effect = 0; webViewDropTarget->DragOver(0, pointl(screenPoint), &effect); if (hr == DRAGDROP_S_DROP && effect != DROPEFFECT_NONE) { DWORD effect = 0; webViewDropTarget->Drop(draggingInfo->dataObject(), 0, pointl(screenPoint), &effect); } else webViewDropTarget->DragLeave(); delete draggingInfo; draggingInfo = 0; } } } static JSValueRef mouseUpCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { MSG msg = makeMsg(webViewWindow, WM_LBUTTONUP, 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y)); if (dragMode && !replayingSavedEvents) { msgQueue[endOfQueue++] = msg; replaySavedEvents(); } else doMouseUp(msg); return JSValueMakeUndefined(context); } static void doMouseMove(MSG msg) { COMPtr framePrivate; if (SUCCEEDED(frame->QueryInterface(&framePrivate))) framePrivate->layout(); dispatchMessage(&msg); if (down && draggingInfo) { POINT screenPoint = msg.pt; ::ClientToScreen(webViewWindow, &screenPoint); IWebView* webView; COMPtr webViewDropTarget; if (SUCCEEDED(frame->webView(&webView)) && SUCCEEDED(webView->QueryInterface(IID_IDropTarget, (void**)&webViewDropTarget))) { DWORD effect = 0; if (didDragEnter) webViewDropTarget->DragOver(MK_LBUTTON, pointl(screenPoint), &effect); else { webViewDropTarget->DragEnter(draggingInfo->dataObject(), MK_LBUTTON, pointl(screenPoint), &effect); didDragEnter = true; } draggingInfo->dropSource()->GiveFeedback(effect); replaySavedEvents(); } } } static JSValueRef mouseMoveToCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { if (argumentCount < 2) return JSValueMakeUndefined(context); lastMousePosition.x = (int)JSValueToNumber(context, arguments[0], exception); ASSERT(!exception || !*exception); lastMousePosition.y = (int)JSValueToNumber(context, arguments[1], exception); ASSERT(!exception || !*exception); MSG msg = makeMsg(webViewWindow, WM_MOUSEMOVE, down ? MK_LBUTTON : 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y)); if (dragMode && down && !replayingSavedEvents) { msgQueue[endOfQueue++] = msg; return JSValueMakeUndefined(context); } doMouseMove(msg); return JSValueMakeUndefined(context); } void replaySavedEvents() { replayingSavedEvents = true; MSG emptyMsg = {0}; while (startOfQueue < endOfQueue) { MSG msg = msgQueue[startOfQueue++]; switch (msg.message) { case WM_LBUTTONUP: doMouseUp(msg); break; case WM_MOUSEMOVE: doMouseMove(msg); break; default: // Not reached break; } } startOfQueue = 0; endOfQueue = 0; replayingSavedEvents = false; } static JSValueRef keyDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { if (argumentCount < 1) return JSValueMakeUndefined(context); static const JSStringRef lengthProperty = JSStringCreateWithUTF8CString("length"); COMPtr framePrivate; if (SUCCEEDED(frame->QueryInterface(&framePrivate))) framePrivate->layout(); JSStringRef character = JSValueToStringCopy(context, arguments[0], exception); ASSERT(!*exception); int virtualKeyCode; int charCode = 0; bool needsShiftKeyModifier = false; if (JSStringIsEqualToUTF8CString(character, "leftArrow")) virtualKeyCode = VK_LEFT; else if (JSStringIsEqualToUTF8CString(character, "rightArrow")) virtualKeyCode = VK_RIGHT; else if (JSStringIsEqualToUTF8CString(character, "upArrow")) virtualKeyCode = VK_UP; else if (JSStringIsEqualToUTF8CString(character, "downArrow")) virtualKeyCode = VK_DOWN; else if (JSStringIsEqualToUTF8CString(character, "delete")) virtualKeyCode = VK_BACK; else { charCode = JSStringGetCharactersPtr(character)[0]; virtualKeyCode = LOBYTE(VkKeyScan(charCode)); if (isupper(charCode)) needsShiftKeyModifier = true; } JSStringRelease(character); BYTE keyState[256]; if (argumentCount > 1 || needsShiftKeyModifier) { ::GetKeyboardState(keyState); BYTE newKeyState[256]; memcpy(newKeyState, keyState, sizeof(keyState)); if (needsShiftKeyModifier) newKeyState[VK_SHIFT] = 0x80; if (argumentCount > 1) { JSObjectRef modifiersArray = JSValueToObject(context, arguments[1], exception); if (modifiersArray) { int modifiersCount = JSValueToNumber(context, JSObjectGetProperty(context, modifiersArray, lengthProperty, 0), 0); for (int i = 0; i < modifiersCount; ++i) { JSValueRef value = JSObjectGetPropertyAtIndex(context, modifiersArray, i, 0); JSStringRef string = JSValueToStringCopy(context, value, 0); if (JSStringIsEqualToUTF8CString(string, "ctrlKey")) newKeyState[VK_CONTROL] = 0x80; else if (JSStringIsEqualToUTF8CString(string, "shiftKey")) newKeyState[VK_SHIFT] = 0x80; else if (JSStringIsEqualToUTF8CString(string, "altKey")) newKeyState[VK_MENU] = 0x80; else if (JSStringIsEqualToUTF8CString(string, "metaKey")) newKeyState[VK_MENU] = 0x80; JSStringRelease(string); } } } ::SetKeyboardState(newKeyState); } MSG msg = makeMsg(webViewWindow, WM_KEYDOWN, virtualKeyCode, 0); if (virtualKeyCode != 255) dispatchMessage(&msg); else { // For characters that do not exist in the active keyboard layout, // ::Translate will not work, so we post an WM_CHAR event ourselves. ::PostMessage(webViewWindow, WM_CHAR, charCode, 0); } // Tests expect that all messages are processed by the time keyDown() returns. if (::PeekMessage(&msg, webViewWindow, WM_CHAR, WM_CHAR, PM_REMOVE)) ::DispatchMessage(&msg); MSG msgUp = makeMsg(webViewWindow, WM_KEYUP, virtualKeyCode, 0); ::DispatchMessage(&msgUp); if (argumentCount > 1 || needsShiftKeyModifier) ::SetKeyboardState(keyState); return JSValueMakeUndefined(context); } // eventSender.dispatchMessage(message, wParam, lParam, time = currentEventTime(), x = lastMousePosition.x, y = lastMousePosition.y) static JSValueRef dispatchMessageCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { if (argumentCount < 3) return JSValueMakeUndefined(context); COMPtr framePrivate; if (SUCCEEDED(frame->QueryInterface(&framePrivate))) framePrivate->layout(); MSG msg = {}; msg.hwnd = webViewWindow; msg.message = JSValueToNumber(context, arguments[0], exception); ASSERT(!*exception); msg.wParam = JSValueToNumber(context, arguments[1], exception); ASSERT(!*exception); msg.lParam = static_cast(JSValueToNumber(context, arguments[2], exception)); ASSERT(!*exception); if (argumentCount >= 4) { msg.time = JSValueToNumber(context, arguments[3], exception); ASSERT(!*exception); } if (!msg.time) msg.time = currentEventTime(); if (argumentCount >= 6) { msg.pt.x = JSValueToNumber(context, arguments[4], exception); ASSERT(!*exception); msg.pt.y = JSValueToNumber(context, arguments[5], exception); ASSERT(!*exception); } else msg.pt = lastMousePosition; ::DispatchMessage(&msg); return JSValueMakeUndefined(context); } static JSValueRef textZoomInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { COMPtr webView; if (FAILED(frame->webView(&webView))) return JSValueMakeUndefined(context); COMPtr webIBActions; if (FAILED(webView->QueryInterface(IID_IWebIBActions, (void**)&webIBActions))) return JSValueMakeUndefined(context); webIBActions->makeTextLarger(0); return JSValueMakeUndefined(context); } static JSValueRef textZoomOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { COMPtr webView; if (FAILED(frame->webView(&webView))) return JSValueMakeUndefined(context); COMPtr webIBActions; if (FAILED(webView->QueryInterface(IID_IWebIBActions, (void**)&webIBActions))) return JSValueMakeUndefined(context); webIBActions->makeTextSmaller(0); return JSValueMakeUndefined(context); } static JSStaticFunction staticFunctions[] = { { "contextClick", contextClickCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "mouseDown", mouseDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "mouseUp", mouseUpCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "mouseMoveTo", mouseMoveToCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "leapForward", leapForwardCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "keyDown", keyDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "dispatchMessage", dispatchMessageCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "textZoomIn", textZoomInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "textZoomOut", textZoomOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { 0, 0, 0 } }; static JSStaticValue staticValues[] = { { "dragMode", getDragModeCallback, setDragModeCallback, kJSPropertyAttributeNone }, { "WM_KEYDOWN", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone }, { "WM_KEYUP", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone }, { "WM_CHAR", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone }, { "WM_DEADCHAR", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone }, { "WM_SYSKEYDOWN", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone }, { "WM_SYSKEYUP", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone }, { "WM_SYSCHAR", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone }, { "WM_SYSDEADCHAR", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone }, { 0, 0, 0, 0 } }; static JSClassRef getClass(JSContextRef context) { static JSClassRef eventSenderClass = 0; if (!eventSenderClass) { JSClassDefinition classDefinition = {0}; classDefinition.staticFunctions = staticFunctions; classDefinition.staticValues = staticValues; eventSenderClass = JSClassCreate(&classDefinition); } return eventSenderClass; } JSObjectRef makeEventSender(JSContextRef context) { return JSObjectMake(context, getClass(context), 0); }