| /* |
| * 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 <WebCore/COMPtr.h> |
| #include <wtf/Platform.h> |
| #include <JavaScriptCore/JavaScriptCore.h> |
| #include <JavaScriptCore/Assertions.h> |
| #include <WebKit/IWebFrame.h> |
| #include <WebKit/IWebFramePrivate.h> |
| #include <windows.h> |
| |
| 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 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 mouseDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) |
| { |
| COMPtr<IWebFramePrivate> 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<IWebFramePrivate> framePrivate; |
| if (SUCCEEDED(frame->QueryInterface(&framePrivate))) |
| framePrivate->layout(); |
| |
| dispatchMessage(&msg); |
| down = false; |
| |
| if (draggingInfo) { |
| COMPtr<IWebView> webView; |
| COMPtr<IDropTarget> 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<IWebFramePrivate> framePrivate; |
| if (SUCCEEDED(frame->QueryInterface(&framePrivate))) |
| framePrivate->layout(); |
| |
| dispatchMessage(&msg); |
| |
| if (down && draggingInfo) { |
| POINT screenPoint = msg.pt; |
| ::ClientToScreen(webViewWindow, &screenPoint); |
| |
| IWebView* webView; |
| COMPtr<IDropTarget> 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); |
| } |
| } |
| } |
| |
| 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 JSStringRef ctrlKey = JSStringCreateWithUTF8CString("ctrlKey"); |
| static JSStringRef shiftKey = JSStringCreateWithUTF8CString("shiftKey"); |
| static JSStringRef altKey = JSStringCreateWithUTF8CString("altKey"); |
| static JSStringRef metaKey = JSStringCreateWithUTF8CString("metaKey"); |
| static JSStringRef lengthProperty = JSStringCreateWithUTF8CString("length"); |
| |
| COMPtr<IWebFramePrivate> framePrivate; |
| if (SUCCEEDED(frame->QueryInterface(&framePrivate))) |
| framePrivate->layout(); |
| |
| JSStringRef character = JSValueToStringCopy(context, arguments[0], exception); |
| ASSERT(!exception || !*exception); |
| int charCode = JSStringGetCharactersPtr(character)[0]; |
| int virtualKeyCode = toupper(LOBYTE(VkKeyScan(charCode))); |
| JSStringRelease(character); |
| |
| // Hack to map option-delete to ctrl-delete |
| // Remove this when we fix <rdar://problem/5102974> layout tests need a way to decide how to choose the appropriate modifier keys |
| bool convertOptionToCtrl = false; |
| if (virtualKeyCode == VK_DELETE || virtualKeyCode == VK_BACK) |
| convertOptionToCtrl = true; |
| |
| BYTE keyState[256]; |
| if (argumentCount > 1) { |
| ::GetKeyboardState(keyState); |
| |
| BYTE newKeyState[256]; |
| memcpy(newKeyState, keyState, sizeof(keyState)); |
| |
| 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 (JSStringIsEqual(string, ctrlKey)) |
| newKeyState[VK_CONTROL] = 0x80; |
| else if (JSStringIsEqual(string, shiftKey)) |
| newKeyState[VK_SHIFT] = 0x80; |
| else if (JSStringIsEqual(string, altKey)) { |
| if (convertOptionToCtrl) |
| newKeyState[VK_CONTROL] = 0x80; |
| else |
| newKeyState[VK_MENU] = 0x80; |
| } else if (JSStringIsEqual(string, metaKey)) |
| newKeyState[VK_MENU] = 0x80; |
| |
| JSStringRelease(string); |
| } |
| } |
| |
| ::SetKeyboardState(newKeyState); |
| } |
| |
| MSG msg = makeMsg(webViewWindow, WM_KEYDOWN, virtualKeyCode, 0); |
| dispatchMessage(&msg); |
| |
| if (argumentCount > 1) |
| ::SetKeyboardState(keyState); |
| |
| return JSValueMakeUndefined(context); |
| } |
| |
| static JSValueRef textZoomInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) |
| { |
| COMPtr<IWebView> webView; |
| if (FAILED(frame->webView(&webView))) |
| return JSValueMakeUndefined(context); |
| |
| COMPtr<IWebIBActions> 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<IWebView> webView; |
| if (FAILED(frame->webView(&webView))) |
| return JSValueMakeUndefined(context); |
| |
| COMPtr<IWebIBActions> webIBActions; |
| if (FAILED(webView->QueryInterface(IID_IWebIBActions, (void**)&webIBActions))) |
| return JSValueMakeUndefined(context); |
| |
| webIBActions->makeTextSmaller(0); |
| return JSValueMakeUndefined(context); |
| } |
| |
| static JSStaticFunction staticFunctions[] = { |
| { "mouseDown", mouseDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, |
| { "mouseUp", mouseUpCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, |
| { "mouseMoveTo", mouseMoveToCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, |
| { "leapForward", leapForwardCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, |
| { "keyDown", keyDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, |
| { "textZoomIn", textZoomInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, |
| { "textZoomOut", textZoomOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, |
| { 0, 0, 0 } |
| }; |
| |
| static JSStaticValue staticValues[] = { |
| { "dragMode", getDragModeCallback, setDragModeCallback, 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); |
| } |