|
1 /* |
|
2 * Copyright (C) 2010 Google 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 are |
|
6 * met: |
|
7 * |
|
8 * * Redistributions of source code must retain the above copyright |
|
9 * notice, this list of conditions and the following disclaimer. |
|
10 * * Redistributions in binary form must reproduce the above |
|
11 * copyright notice, this list of conditions and the following disclaimer |
|
12 * in the documentation and/or other materials provided with the |
|
13 * distribution. |
|
14 * * Neither the name of Google Inc. nor the names of its |
|
15 * contributors may be used to endorse or promote products derived from |
|
16 * this software without specific prior written permission. |
|
17 * |
|
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
29 */ |
|
30 |
|
31 // This file contains the definition for EventSender. |
|
32 // |
|
33 // Some notes about drag and drop handling: |
|
34 // Windows drag and drop goes through a system call to doDragDrop. At that |
|
35 // point, program control is given to Windows which then periodically makes |
|
36 // callbacks into the webview. This won't work for layout tests, so instead, |
|
37 // we queue up all the mouse move and mouse up events. When the test tries to |
|
38 // start a drag (by calling EvenSendingController::doDragDrop), we take the |
|
39 // events in the queue and replay them. |
|
40 // The behavior of queuing events and replaying them can be disabled by a |
|
41 // layout test by setting eventSender.dragMode to false. |
|
42 |
|
43 #include "config.h" |
|
44 #include "EventSender.h" |
|
45 |
|
46 #include "TestShell.h" |
|
47 #include "base/keyboard_codes.h" |
|
48 #include "base/time.h" |
|
49 #include "public/WebDragData.h" |
|
50 #include "public/WebDragOperation.h" |
|
51 #include "public/WebPoint.h" |
|
52 #include "public/WebString.h" |
|
53 #include "public/WebTouchPoint.h" |
|
54 #include "public/WebView.h" |
|
55 #include "webkit/support/webkit_support.h" |
|
56 #include <wtf/Deque.h> |
|
57 #include <wtf/StringExtras.h> |
|
58 |
|
59 #if OS(WINDOWS) |
|
60 #include "public/win/WebInputEventFactory.h" |
|
61 #endif |
|
62 |
|
63 // FIXME: layout before each event? |
|
64 |
|
65 using namespace base; |
|
66 using namespace std; |
|
67 using namespace WebKit; |
|
68 |
|
69 WebPoint EventSender::lastMousePos; |
|
70 WebMouseEvent::Button EventSender::pressedButton = WebMouseEvent::ButtonNone; |
|
71 WebMouseEvent::Button EventSender::lastButtonType = WebMouseEvent::ButtonNone; |
|
72 |
|
73 struct SavedEvent { |
|
74 enum SavedEventType { |
|
75 Unspecified, |
|
76 MouseUp, |
|
77 MouseMove, |
|
78 LeapForward |
|
79 }; |
|
80 |
|
81 SavedEventType type; |
|
82 WebMouseEvent::Button buttonType; // For MouseUp. |
|
83 WebPoint pos; // For MouseMove. |
|
84 int milliseconds; // For LeapForward. |
|
85 |
|
86 SavedEvent() |
|
87 : type(Unspecified) |
|
88 , buttonType(WebMouseEvent::ButtonNone) |
|
89 , milliseconds(0) {} |
|
90 }; |
|
91 |
|
92 static WebDragData currentDragData; |
|
93 static WebDragOperation currentDragEffect; |
|
94 static WebDragOperationsMask currentDragEffectsAllowed; |
|
95 static bool replayingSavedEvents = false; |
|
96 static Deque<SavedEvent> mouseEventQueue; |
|
97 static int touchModifiers; |
|
98 static Vector<WebTouchPoint> touchPoints; |
|
99 |
|
100 // Time and place of the last mouse up event. |
|
101 static double lastClickTimeSec = 0; |
|
102 static WebPoint lastClickPos; |
|
103 static int clickCount = 0; |
|
104 |
|
105 // maximum distance (in space and time) for a mouse click |
|
106 // to register as a double or triple click |
|
107 static const double multipleClickTimeSec = 1; |
|
108 static const int multipleClickRadiusPixels = 5; |
|
109 |
|
110 // How much we should scroll per event - the value here is chosen to |
|
111 // match the WebKit impl and layout test results. |
|
112 static const float scrollbarPixelsPerTick = 40.0f; |
|
113 |
|
114 inline bool outsideMultiClickRadius(const WebPoint& a, const WebPoint& b) |
|
115 { |
|
116 return ((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)) > |
|
117 multipleClickRadiusPixels * multipleClickRadiusPixels; |
|
118 } |
|
119 |
|
120 // Used to offset the time the event hander things an event happened. This is |
|
121 // done so tests can run without a delay, but bypass checks that are time |
|
122 // dependent (e.g., dragging has a timeout vs selection). |
|
123 static uint32 timeOffsetMs = 0; |
|
124 |
|
125 static double getCurrentEventTimeSec() |
|
126 { |
|
127 return (TimeTicks::Now().ToInternalValue() / Time::kMicrosecondsPerMillisecond + timeOffsetMs) / 1000.0; |
|
128 } |
|
129 |
|
130 static void advanceEventTime(int32_t deltaMs) |
|
131 { |
|
132 timeOffsetMs += deltaMs; |
|
133 } |
|
134 |
|
135 static void initMouseEvent(WebInputEvent::Type t, WebMouseEvent::Button b, |
|
136 const gfx::Point& pos, WebMouseEvent* e) |
|
137 { |
|
138 e->type = t; |
|
139 e->button = b; |
|
140 e->modifiers = 0; |
|
141 e->x = pos.x(); |
|
142 e->y = pos.y(); |
|
143 e->globalX = pos.x(); |
|
144 e->globalY = pos.y(); |
|
145 e->timeStampSeconds = getCurrentEventTimeSec(); |
|
146 e->clickCount = clickCount; |
|
147 } |
|
148 |
|
149 // Returns true if the specified key is the system key. |
|
150 static bool applyKeyModifier(const string& modifierName, WebInputEvent* event) |
|
151 { |
|
152 bool isSystemKey = false; |
|
153 const char* characters = modifierName.c_str(); |
|
154 if (!strcmp(characters, "ctrlKey") |
|
155 #if !OS(MAC_OS_X) |
|
156 || !strcmp(characters, "addSelectionKey") |
|
157 #endif |
|
158 ) { |
|
159 event->modifiers |= WebInputEvent::ControlKey; |
|
160 } else if (!strcmp(characters, "shiftKey") || !strcmp(characters, "rangeSelectionKey")) |
|
161 event->modifiers |= WebInputEvent::ShiftKey; |
|
162 else if (!strcmp(characters, "altKey")) { |
|
163 event->modifiers |= WebInputEvent::AltKey; |
|
164 #if !OS(MAC_OS_X) |
|
165 // On Windows all keys with Alt modifier will be marked as system key. |
|
166 // We keep the same behavior on Linux and everywhere non-Mac, see: |
|
167 // WebKit/chromium/src/gtk/WebInputEventFactory.cpp |
|
168 // If we want to change this behavior on Linux, this piece of code must be |
|
169 // kept in sync with the related code in above file. |
|
170 isSystemKey = true; |
|
171 #endif |
|
172 #if OS(MAC_OS_X) |
|
173 } else if (!strcmp(characters, "metaKey") || !strcmp(characters, "addSelectionKey")) { |
|
174 event->modifiers |= WebInputEvent::MetaKey; |
|
175 // On Mac only command key presses are marked as system key. |
|
176 // See the related code in: WebKit/chromium/src/mac/WebInputEventFactory.cpp |
|
177 // It must be kept in sync with the related code in above file. |
|
178 isSystemKey = true; |
|
179 #else |
|
180 } else if (!strcmp(characters, "metaKey")) { |
|
181 event->modifiers |= WebInputEvent::MetaKey; |
|
182 #endif |
|
183 } |
|
184 return isSystemKey; |
|
185 } |
|
186 |
|
187 static bool applyKeyModifiers(const CppVariant* argument, WebInputEvent* event) |
|
188 { |
|
189 bool isSystemKey = false; |
|
190 if (argument->isObject()) { |
|
191 Vector<string> modifiers = argument->toStringVector(); |
|
192 for (Vector<string>::const_iterator i = modifiers.begin(); i != modifiers.end(); ++i) |
|
193 isSystemKey |= applyKeyModifier(*i, event); |
|
194 } else if (argument->isString()) |
|
195 isSystemKey = applyKeyModifier(argument->toString(), event); |
|
196 return isSystemKey; |
|
197 } |
|
198 |
|
199 // Get the edit command corresponding to a keyboard event. |
|
200 // Returns true if the specified event corresponds to an edit command, the name |
|
201 // of the edit command will be stored in |*name|. |
|
202 bool getEditCommand(const WebKeyboardEvent& event, string* name) |
|
203 { |
|
204 #if OS(MAC_OS_X) |
|
205 // We only cares about Left,Right,Up,Down keys with Command or Command+Shift |
|
206 // modifiers. These key events correspond to some special movement and |
|
207 // selection editor commands, and was supposed to be handled in |
|
208 // WebKit/chromium/src/EditorClientImpl.cpp. But these keys will be marked |
|
209 // as system key, which prevents them from being handled. Thus they must be |
|
210 // handled specially. |
|
211 if ((event.modifiers & ~WebKeyboardEvent::ShiftKey) != WebKeyboardEvent::MetaKey) |
|
212 return false; |
|
213 |
|
214 switch (event.windowsKeyCode) { |
|
215 case base::VKEY_LEFT: |
|
216 *name = "MoveToBeginningOfLine"; |
|
217 break; |
|
218 case base::VKEY_RIGHT: |
|
219 *name = "MoveToEndOfLine"; |
|
220 break; |
|
221 case base::VKEY_UP: |
|
222 *name = "MoveToBeginningOfDocument"; |
|
223 break; |
|
224 case base::VKEY_DOWN: |
|
225 *name = "MoveToEndOfDocument"; |
|
226 break; |
|
227 default: |
|
228 return false; |
|
229 } |
|
230 |
|
231 if (event.modifiers & WebKeyboardEvent::ShiftKey) |
|
232 name->append("AndModifySelection"); |
|
233 |
|
234 return true; |
|
235 #else |
|
236 return false; |
|
237 #endif |
|
238 } |
|
239 |
|
240 // Key event location code introduced in DOM Level 3. |
|
241 // See also: http://www.w3.org/TR/DOM-Level-3-Events/#events-keyboardevents |
|
242 enum KeyLocationCode { |
|
243 DOMKeyLocationStandard = 0x00, |
|
244 DOMKeyLocationLeft = 0x01, |
|
245 DOMKeyLocationRight = 0x02, |
|
246 DOMKeyLocationNumpad = 0x03 |
|
247 }; |
|
248 |
|
249 EventSender::EventSender(TestShell* shell) |
|
250 : m_methodFactory(this) |
|
251 , m_shell(shell) |
|
252 { |
|
253 // Initialize the map that associates methods of this class with the names |
|
254 // they will use when called by JavaScript. The actual binding of those |
|
255 // names to their methods will be done by calling bindToJavaScript() (defined |
|
256 // by CppBoundClass, the parent to EventSender). |
|
257 bindMethod("mouseDown", &EventSender::mouseDown); |
|
258 bindMethod("mouseUp", &EventSender::mouseUp); |
|
259 bindMethod("contextClick", &EventSender::contextClick); |
|
260 bindMethod("mouseMoveTo", &EventSender::mouseMoveTo); |
|
261 bindMethod("mouseWheelTo", &EventSender::mouseWheelTo); |
|
262 bindMethod("leapForward", &EventSender::leapForward); |
|
263 bindMethod("keyDown", &EventSender::keyDown); |
|
264 bindMethod("dispatchMessage", &EventSender::dispatchMessage); |
|
265 bindMethod("enableDOMUIEventLogging", &EventSender::enableDOMUIEventLogging); |
|
266 bindMethod("fireKeyboardEventsToElement", &EventSender::fireKeyboardEventsToElement); |
|
267 bindMethod("clearKillRing", &EventSender::clearKillRing); |
|
268 bindMethod("textZoomIn", &EventSender::textZoomIn); |
|
269 bindMethod("textZoomOut", &EventSender::textZoomOut); |
|
270 bindMethod("zoomPageIn", &EventSender::zoomPageIn); |
|
271 bindMethod("zoomPageOut", &EventSender::zoomPageOut); |
|
272 bindMethod("scheduleAsynchronousClick", &EventSender::scheduleAsynchronousClick); |
|
273 bindMethod("beginDragWithFiles", &EventSender::beginDragWithFiles); |
|
274 bindMethod("addTouchPoint", &EventSender::addTouchPoint); |
|
275 bindMethod("cancelTouchPoint", &EventSender::cancelTouchPoint); |
|
276 bindMethod("clearTouchPoints", &EventSender::clearTouchPoints); |
|
277 bindMethod("releaseTouchPoint", &EventSender::releaseTouchPoint); |
|
278 bindMethod("updateTouchPoint", &EventSender::updateTouchPoint); |
|
279 bindMethod("setTouchModifier", &EventSender::setTouchModifier); |
|
280 bindMethod("touchCancel", &EventSender::touchCancel); |
|
281 bindMethod("touchEnd", &EventSender::touchEnd); |
|
282 bindMethod("touchMove", &EventSender::touchMove); |
|
283 bindMethod("touchStart", &EventSender::touchStart); |
|
284 |
|
285 // When set to true (the default value), we batch mouse move and mouse up |
|
286 // events so we can simulate drag & drop. |
|
287 bindProperty("dragMode", &dragMode); |
|
288 #if OS(WINDOWS) |
|
289 bindProperty("WM_KEYDOWN", &wmKeyDown); |
|
290 bindProperty("WM_KEYUP", &wmKeyUp); |
|
291 bindProperty("WM_CHAR", &wmChar); |
|
292 bindProperty("WM_DEADCHAR", &wmDeadChar); |
|
293 bindProperty("WM_SYSKEYDOWN", &wmSysKeyDown); |
|
294 bindProperty("WM_SYSKEYUP", &wmSysKeyUp); |
|
295 bindProperty("WM_SYSCHAR", &wmSysChar); |
|
296 bindProperty("WM_SYSDEADCHAR", &wmSysDeadChar); |
|
297 #endif |
|
298 } |
|
299 |
|
300 void EventSender::reset() |
|
301 { |
|
302 // The test should have finished a drag and the mouse button state. |
|
303 ASSERT(currentDragData.isNull()); |
|
304 currentDragData.reset(); |
|
305 currentDragEffect = WebKit::WebDragOperationNone; |
|
306 currentDragEffectsAllowed = WebKit::WebDragOperationNone; |
|
307 pressedButton = WebMouseEvent::ButtonNone; |
|
308 dragMode.set(true); |
|
309 #if OS(WINDOWS) |
|
310 wmKeyDown.set(WM_KEYDOWN); |
|
311 wmKeyUp.set(WM_KEYUP); |
|
312 wmChar.set(WM_CHAR); |
|
313 wmDeadChar.set(WM_DEADCHAR); |
|
314 wmSysKeyDown.set(WM_SYSKEYDOWN); |
|
315 wmSysKeyUp.set(WM_SYSKEYUP); |
|
316 wmSysChar.set(WM_SYSCHAR); |
|
317 wmSysDeadChar.set(WM_SYSDEADCHAR); |
|
318 #endif |
|
319 lastMousePos = WebPoint(0, 0); |
|
320 lastClickTimeSec = 0; |
|
321 lastClickPos = WebPoint(0, 0); |
|
322 clickCount = 0; |
|
323 lastButtonType = WebMouseEvent::ButtonNone; |
|
324 timeOffsetMs = 0; |
|
325 touchModifiers = 0; |
|
326 touchPoints.clear(); |
|
327 } |
|
328 |
|
329 WebView* EventSender::webview() |
|
330 { |
|
331 return m_shell->webView(); |
|
332 } |
|
333 |
|
334 void EventSender::doDragDrop(const WebDragData& dragData, WebDragOperationsMask mask) |
|
335 { |
|
336 WebMouseEvent event; |
|
337 initMouseEvent(WebInputEvent::MouseDown, pressedButton, lastMousePos, &event); |
|
338 WebPoint clientPoint(event.x, event.y); |
|
339 WebPoint screenPoint(event.globalX, event.globalY); |
|
340 currentDragData = dragData; |
|
341 currentDragEffectsAllowed = mask; |
|
342 currentDragEffect = webview()->dragTargetDragEnter(dragData, 0, clientPoint, screenPoint, currentDragEffectsAllowed); |
|
343 |
|
344 // Finish processing events. |
|
345 replaySavedEvents(); |
|
346 } |
|
347 |
|
348 WebMouseEvent::Button EventSender::getButtonTypeFromButtonNumber(int buttonCode) |
|
349 { |
|
350 if (!buttonCode) |
|
351 return WebMouseEvent::ButtonLeft; |
|
352 if (buttonCode == 2) |
|
353 return WebMouseEvent::ButtonRight; |
|
354 return WebMouseEvent::ButtonMiddle; |
|
355 } |
|
356 |
|
357 int EventSender::getButtonNumberFromSingleArg(const CppArgumentList& arguments) |
|
358 { |
|
359 int buttonCode = 0; |
|
360 if (arguments.size() > 0 && arguments[0].isNumber()) |
|
361 buttonCode = arguments[0].toInt32(); |
|
362 return buttonCode; |
|
363 } |
|
364 |
|
365 void EventSender::updateClickCountForButton(WebMouseEvent::Button buttonType) |
|
366 { |
|
367 if ((getCurrentEventTimeSec() - lastClickTimeSec < multipleClickTimeSec) |
|
368 && (!outsideMultiClickRadius(lastMousePos, lastClickPos)) |
|
369 && (buttonType == lastButtonType)) |
|
370 ++clickCount; |
|
371 else { |
|
372 clickCount = 1; |
|
373 lastButtonType = buttonType; |
|
374 } |
|
375 } |
|
376 |
|
377 // |
|
378 // Implemented javascript methods. |
|
379 // |
|
380 |
|
381 void EventSender::mouseDown(const CppArgumentList& arguments, CppVariant* result) |
|
382 { |
|
383 if (result) // Could be 0 if invoked asynchronously. |
|
384 result->setNull(); |
|
385 |
|
386 webview()->layout(); |
|
387 |
|
388 int buttonNumber = getButtonNumberFromSingleArg(arguments); |
|
389 ASSERT(buttonNumber != -1); |
|
390 |
|
391 WebMouseEvent::Button buttonType = getButtonTypeFromButtonNumber(buttonNumber); |
|
392 |
|
393 updateClickCountForButton(buttonType); |
|
394 |
|
395 WebMouseEvent event; |
|
396 pressedButton = buttonType; |
|
397 initMouseEvent(WebInputEvent::MouseDown, buttonType, lastMousePos, &event); |
|
398 if (arguments.size() >= 2 && (arguments[1].isObject() || arguments[1].isString())) |
|
399 applyKeyModifiers(&(arguments[1]), &event); |
|
400 webview()->handleInputEvent(event); |
|
401 } |
|
402 |
|
403 void EventSender::mouseUp(const CppArgumentList& arguments, CppVariant* result) |
|
404 { |
|
405 if (result) // Could be 0 if invoked asynchronously. |
|
406 result->setNull(); |
|
407 |
|
408 webview()->layout(); |
|
409 |
|
410 int buttonNumber = getButtonNumberFromSingleArg(arguments); |
|
411 ASSERT(buttonNumber != -1); |
|
412 |
|
413 WebMouseEvent::Button buttonType = getButtonTypeFromButtonNumber(buttonNumber); |
|
414 |
|
415 if (isDragMode() && !replayingSavedEvents) { |
|
416 SavedEvent savedEvent; |
|
417 savedEvent.type = SavedEvent::MouseUp; |
|
418 savedEvent.buttonType = buttonType; |
|
419 mouseEventQueue.append(savedEvent); |
|
420 replaySavedEvents(); |
|
421 } else { |
|
422 WebMouseEvent event; |
|
423 initMouseEvent(WebInputEvent::MouseUp, buttonType, lastMousePos, &event); |
|
424 if (arguments.size() >= 2 && (arguments[1].isObject() || arguments[1].isString())) |
|
425 applyKeyModifiers(&(arguments[1]), &event); |
|
426 doMouseUp(event); |
|
427 } |
|
428 } |
|
429 |
|
430 void EventSender::doMouseUp(const WebMouseEvent& e) |
|
431 { |
|
432 webview()->handleInputEvent(e); |
|
433 |
|
434 pressedButton = WebMouseEvent::ButtonNone; |
|
435 lastClickTimeSec = e.timeStampSeconds; |
|
436 lastClickPos = lastMousePos; |
|
437 |
|
438 // If we're in a drag operation, complete it. |
|
439 if (currentDragData.isNull()) |
|
440 return; |
|
441 WebPoint clientPoint(e.x, e.y); |
|
442 WebPoint screenPoint(e.globalX, e.globalY); |
|
443 |
|
444 currentDragEffect = webview()->dragTargetDragOver(clientPoint, screenPoint, currentDragEffectsAllowed); |
|
445 if (currentDragEffect) |
|
446 webview()->dragTargetDrop(clientPoint, screenPoint); |
|
447 else |
|
448 webview()->dragTargetDragLeave(); |
|
449 webview()->dragSourceEndedAt(clientPoint, screenPoint, currentDragEffect); |
|
450 webview()->dragSourceSystemDragEnded(); |
|
451 |
|
452 currentDragData.reset(); |
|
453 } |
|
454 |
|
455 void EventSender::mouseMoveTo(const CppArgumentList& arguments, CppVariant* result) |
|
456 { |
|
457 result->setNull(); |
|
458 |
|
459 if (arguments.size() < 2 || !arguments[0].isNumber() || !arguments[1].isNumber()) |
|
460 return; |
|
461 webview()->layout(); |
|
462 |
|
463 WebPoint mousePos(arguments[0].toInt32(), arguments[1].toInt32()); |
|
464 |
|
465 if (isDragMode() && pressedButton == WebMouseEvent::ButtonLeft && !replayingSavedEvents) { |
|
466 SavedEvent savedEvent; |
|
467 savedEvent.type = SavedEvent::MouseMove; |
|
468 savedEvent.pos = mousePos; |
|
469 mouseEventQueue.append(savedEvent); |
|
470 } else { |
|
471 WebMouseEvent event; |
|
472 initMouseEvent(WebInputEvent::MouseMove, pressedButton, mousePos, &event); |
|
473 doMouseMove(event); |
|
474 } |
|
475 } |
|
476 |
|
477 void EventSender::mouseWheelTo(const CppArgumentList& arguments, CppVariant* result) |
|
478 { |
|
479 result->setNull(); |
|
480 |
|
481 if (arguments.size() < 2 || !arguments[0].isNumber() || !arguments[1].isNumber()) |
|
482 return; |
|
483 |
|
484 // Force a layout here just to make sure every position has been |
|
485 // determined before we send events (as well as all the other methods |
|
486 // that send an event do). The layout test calling this |
|
487 // (scrollbars/overflow-scrollbar-horizontal-wheel-scroll.html, only one |
|
488 // for now) does not rely on this though. |
|
489 webview()->layout(); |
|
490 |
|
491 int horizontal = arguments[0].toInt32(); |
|
492 int vertical = arguments[1].toInt32(); |
|
493 |
|
494 WebMouseWheelEvent event; |
|
495 initMouseEvent(WebInputEvent::MouseWheel, pressedButton, lastMousePos, &event); |
|
496 event.wheelTicksX = static_cast<float>(horizontal); |
|
497 event.wheelTicksY = static_cast<float>(vertical); |
|
498 event.deltaX = -horizontal * scrollbarPixelsPerTick; |
|
499 event.deltaY = -vertical * scrollbarPixelsPerTick; |
|
500 webview()->handleInputEvent(event); |
|
501 } |
|
502 |
|
503 void EventSender::doMouseMove(const WebMouseEvent& e) |
|
504 { |
|
505 lastMousePos = WebPoint(e.x, e.y); |
|
506 |
|
507 webview()->handleInputEvent(e); |
|
508 |
|
509 if (pressedButton == WebMouseEvent::ButtonNone || currentDragData.isNull()) |
|
510 return; |
|
511 WebPoint clientPoint(e.x, e.y); |
|
512 WebPoint screenPoint(e.globalX, e.globalY); |
|
513 currentDragEffect = webview()->dragTargetDragOver(clientPoint, screenPoint, currentDragEffectsAllowed); |
|
514 } |
|
515 |
|
516 void EventSender::keyDown(const CppArgumentList& arguments, CppVariant* result) |
|
517 { |
|
518 result->setNull(); |
|
519 if (arguments.size() < 1 || !arguments[0].isString()) |
|
520 return; |
|
521 bool generateChar = false; |
|
522 |
|
523 // FIXME: I'm not exactly sure how we should convert the string to a key |
|
524 // event. This seems to work in the cases I tested. |
|
525 // FIXME: Should we also generate a KEY_UP? |
|
526 string codeStr = arguments[0].toString(); |
|
527 |
|
528 // Convert \n -> VK_RETURN. Some layout tests use \n to mean "Enter", when |
|
529 // Windows uses \r for "Enter". |
|
530 int code = 0; |
|
531 int text = 0; |
|
532 bool needsShiftKeyModifier = false; |
|
533 if ("\n" == codeStr) { |
|
534 generateChar = true; |
|
535 text = code = base::VKEY_RETURN; |
|
536 } else if ("rightArrow" == codeStr) |
|
537 code = base::VKEY_RIGHT; |
|
538 else if ("downArrow" == codeStr) |
|
539 code = base::VKEY_DOWN; |
|
540 else if ("leftArrow" == codeStr) |
|
541 code = base::VKEY_LEFT; |
|
542 else if ("upArrow" == codeStr) |
|
543 code = base::VKEY_UP; |
|
544 else if ("delete" == codeStr) |
|
545 code = base::VKEY_DELETE; |
|
546 else if ("pageUp" == codeStr) |
|
547 code = base::VKEY_PRIOR; |
|
548 else if ("pageDown" == codeStr) |
|
549 code = base::VKEY_NEXT; |
|
550 else if ("home" == codeStr) |
|
551 code = base::VKEY_HOME; |
|
552 else if ("end" == codeStr) |
|
553 code = base::VKEY_END; |
|
554 else { |
|
555 // Compare the input string with the function-key names defined by the |
|
556 // DOM spec (i.e. "F1",...,"F24"). If the input string is a function-key |
|
557 // name, set its key code. |
|
558 for (int i = 1; i <= 24; ++i) { |
|
559 char functionChars[10]; |
|
560 snprintf(functionChars, 10, "F%d", i); |
|
561 string functionKeyName(functionChars); |
|
562 if (functionKeyName == codeStr) { |
|
563 code = base::VKEY_F1 + (i - 1); |
|
564 break; |
|
565 } |
|
566 } |
|
567 if (!code) { |
|
568 WebString webCodeStr = WebString::fromUTF8(codeStr.data(), codeStr.size()); |
|
569 ASSERT(webCodeStr.length() == 1); |
|
570 text = code = webCodeStr.data()[0]; |
|
571 needsShiftKeyModifier = needsShiftModifier(code); |
|
572 if ((code & 0xFF) >= 'a' && (code & 0xFF) <= 'z') |
|
573 code -= 'a' - 'A'; |
|
574 generateChar = true; |
|
575 } |
|
576 } |
|
577 |
|
578 // For one generated keyboard event, we need to generate a keyDown/keyUp |
|
579 // pair; refer to EventSender.cpp in WebKit/WebKitTools/DumpRenderTree/win. |
|
580 // On Windows, we might also need to generate a char event to mimic the |
|
581 // Windows event flow; on other platforms we create a merged event and test |
|
582 // the event flow that that platform provides. |
|
583 WebKeyboardEvent eventDown, eventChar, eventUp; |
|
584 eventDown.type = WebInputEvent::RawKeyDown; |
|
585 eventDown.modifiers = 0; |
|
586 eventDown.windowsKeyCode = code; |
|
587 if (generateChar) { |
|
588 eventDown.text[0] = text; |
|
589 eventDown.unmodifiedText[0] = text; |
|
590 } |
|
591 eventDown.setKeyIdentifierFromWindowsKeyCode(); |
|
592 |
|
593 if (arguments.size() >= 2 && (arguments[1].isObject() || arguments[1].isString())) |
|
594 eventDown.isSystemKey = applyKeyModifiers(&(arguments[1]), &eventDown); |
|
595 |
|
596 if (needsShiftKeyModifier) |
|
597 eventDown.modifiers |= WebInputEvent::ShiftKey; |
|
598 |
|
599 // See if KeyLocation argument is given. |
|
600 if (arguments.size() >= 3 && arguments[2].isNumber()) { |
|
601 int location = arguments[2].toInt32(); |
|
602 if (location == DOMKeyLocationNumpad) |
|
603 eventDown.modifiers |= WebInputEvent::IsKeyPad; |
|
604 } |
|
605 |
|
606 eventChar = eventUp = eventDown; |
|
607 eventUp.type = WebInputEvent::KeyUp; |
|
608 // EventSender.m forces a layout here, with at least one |
|
609 // test (fast/forms/focus-control-to-page.html) relying on this. |
|
610 webview()->layout(); |
|
611 |
|
612 // In the browser, if a keyboard event corresponds to an editor command, |
|
613 // the command will be dispatched to the renderer just before dispatching |
|
614 // the keyboard event, and then it will be executed in the |
|
615 // RenderView::handleCurrentKeyboardEvent() method, which is called from |
|
616 // third_party/WebKit/WebKit/chromium/src/EditorClientImpl.cpp. |
|
617 // We just simulate the same behavior here. |
|
618 string editCommand; |
|
619 if (getEditCommand(eventDown, &editCommand)) |
|
620 m_shell->webViewHost()->setEditCommand(editCommand, ""); |
|
621 |
|
622 webview()->handleInputEvent(eventDown); |
|
623 |
|
624 m_shell->webViewHost()->clearEditCommand(); |
|
625 |
|
626 if (generateChar) { |
|
627 eventChar.type = WebInputEvent::Char; |
|
628 eventChar.keyIdentifier[0] = '\0'; |
|
629 webview()->handleInputEvent(eventChar); |
|
630 } |
|
631 |
|
632 webview()->handleInputEvent(eventUp); |
|
633 } |
|
634 |
|
635 void EventSender::dispatchMessage(const CppArgumentList& arguments, CppVariant* result) |
|
636 { |
|
637 result->setNull(); |
|
638 |
|
639 #if OS(WINDOWS) |
|
640 if (arguments.size() == 3) { |
|
641 // Grab the message id to see if we need to dispatch it. |
|
642 int msg = arguments[0].toInt32(); |
|
643 |
|
644 // WebKit's version of this function stuffs a MSG struct and uses |
|
645 // TranslateMessage and DispatchMessage. We use a WebKeyboardEvent, which |
|
646 // doesn't need to receive the DeadChar and SysDeadChar messages. |
|
647 if (msg == WM_DEADCHAR || msg == WM_SYSDEADCHAR) |
|
648 return; |
|
649 |
|
650 webview()->layout(); |
|
651 |
|
652 unsigned long lparam = static_cast<unsigned long>(arguments[2].toDouble()); |
|
653 webview()->handleInputEvent(WebInputEventFactory::keyboardEvent(0, msg, arguments[1].toInt32(), lparam)); |
|
654 } else |
|
655 ASSERT_NOT_REACHED(); |
|
656 #endif |
|
657 } |
|
658 |
|
659 bool EventSender::needsShiftModifier(int keyCode) |
|
660 { |
|
661 // If code is an uppercase letter, assign a SHIFT key to |
|
662 // eventDown.modifier, this logic comes from |
|
663 // WebKit/WebKitTools/DumpRenderTree/Win/EventSender.cpp |
|
664 return (keyCode & 0xFF) >= 'A' && (keyCode & 0xFF) <= 'Z'; |
|
665 } |
|
666 |
|
667 void EventSender::leapForward(const CppArgumentList& arguments, CppVariant* result) |
|
668 { |
|
669 result->setNull(); |
|
670 |
|
671 if (arguments.size() < 1 || !arguments[0].isNumber()) |
|
672 return; |
|
673 |
|
674 int milliseconds = arguments[0].toInt32(); |
|
675 if (isDragMode() && pressedButton == WebMouseEvent::ButtonLeft && !replayingSavedEvents) { |
|
676 SavedEvent savedEvent; |
|
677 savedEvent.type = SavedEvent::LeapForward; |
|
678 savedEvent.milliseconds = milliseconds; |
|
679 mouseEventQueue.append(savedEvent); |
|
680 } else |
|
681 doLeapForward(milliseconds); |
|
682 } |
|
683 |
|
684 void EventSender::doLeapForward(int milliseconds) |
|
685 { |
|
686 advanceEventTime(milliseconds); |
|
687 } |
|
688 |
|
689 // Apple's port of WebKit zooms by a factor of 1.2 (see |
|
690 // WebKit/WebView/WebView.mm) |
|
691 void EventSender::textZoomIn(const CppArgumentList&, CppVariant* result) |
|
692 { |
|
693 webview()->setZoomLevel(true, webview()->zoomLevel() + 1); |
|
694 result->setNull(); |
|
695 } |
|
696 |
|
697 void EventSender::textZoomOut(const CppArgumentList&, CppVariant* result) |
|
698 { |
|
699 webview()->setZoomLevel(true, webview()->zoomLevel() - 1); |
|
700 result->setNull(); |
|
701 } |
|
702 |
|
703 void EventSender::zoomPageIn(const CppArgumentList&, CppVariant* result) |
|
704 { |
|
705 webview()->setZoomLevel(false, webview()->zoomLevel() + 1); |
|
706 result->setNull(); |
|
707 } |
|
708 |
|
709 void EventSender::zoomPageOut(const CppArgumentList&, CppVariant* result) |
|
710 { |
|
711 webview()->setZoomLevel(false, webview()->zoomLevel() - 1); |
|
712 result->setNull(); |
|
713 } |
|
714 |
|
715 void EventSender::replaySavedEvents() |
|
716 { |
|
717 replayingSavedEvents = true; |
|
718 while (!mouseEventQueue.isEmpty()) { |
|
719 SavedEvent e = mouseEventQueue.takeFirst(); |
|
720 |
|
721 switch (e.type) { |
|
722 case SavedEvent::MouseMove: { |
|
723 WebMouseEvent event; |
|
724 initMouseEvent(WebInputEvent::MouseMove, pressedButton, e.pos, &event); |
|
725 doMouseMove(event); |
|
726 break; |
|
727 } |
|
728 case SavedEvent::LeapForward: |
|
729 doLeapForward(e.milliseconds); |
|
730 break; |
|
731 case SavedEvent::MouseUp: { |
|
732 WebMouseEvent event; |
|
733 initMouseEvent(WebInputEvent::MouseUp, e.buttonType, lastMousePos, &event); |
|
734 doMouseUp(event); |
|
735 break; |
|
736 } |
|
737 default: |
|
738 ASSERT_NOT_REACHED(); |
|
739 } |
|
740 } |
|
741 |
|
742 replayingSavedEvents = false; |
|
743 } |
|
744 |
|
745 void EventSender::contextClick(const CppArgumentList& arguments, CppVariant* result) |
|
746 { |
|
747 result->setNull(); |
|
748 |
|
749 webview()->layout(); |
|
750 |
|
751 updateClickCountForButton(WebMouseEvent::ButtonRight); |
|
752 |
|
753 // Generate right mouse down and up. |
|
754 |
|
755 WebMouseEvent event; |
|
756 pressedButton = WebMouseEvent::ButtonRight; |
|
757 initMouseEvent(WebInputEvent::MouseDown, WebMouseEvent::ButtonRight, lastMousePos, &event); |
|
758 webview()->handleInputEvent(event); |
|
759 |
|
760 initMouseEvent(WebInputEvent::MouseUp, WebMouseEvent::ButtonRight, lastMousePos, &event); |
|
761 webview()->handleInputEvent(event); |
|
762 |
|
763 pressedButton = WebMouseEvent::ButtonNone; |
|
764 } |
|
765 |
|
766 void EventSender::scheduleAsynchronousClick(const CppArgumentList& arguments, CppVariant* result) |
|
767 { |
|
768 result->setNull(); |
|
769 |
|
770 webkit_support::PostTaskFromHere(m_methodFactory.NewRunnableMethod( |
|
771 &EventSender::mouseDown, arguments, static_cast<CppVariant*>(0))); |
|
772 webkit_support::PostTaskFromHere(m_methodFactory.NewRunnableMethod( |
|
773 &EventSender::mouseUp, arguments, static_cast<CppVariant*>(0))); |
|
774 } |
|
775 |
|
776 void EventSender::beginDragWithFiles(const CppArgumentList& arguments, CppVariant* result) |
|
777 { |
|
778 currentDragData.initialize(); |
|
779 Vector<string> files = arguments[0].toStringVector(); |
|
780 for (size_t i = 0; i < files.size(); ++i) |
|
781 currentDragData.appendToFileNames(webkit_support::GetAbsoluteWebStringFromUTF8Path(files[i])); |
|
782 currentDragEffectsAllowed = WebKit::WebDragOperationCopy; |
|
783 |
|
784 // Provide a drag source. |
|
785 webview()->dragTargetDragEnter(currentDragData, 0, lastMousePos, lastMousePos, currentDragEffectsAllowed); |
|
786 |
|
787 // dragMode saves events and then replays them later. We don't need/want that. |
|
788 dragMode.set(false); |
|
789 |
|
790 // Make the rest of eventSender think a drag is in progress. |
|
791 pressedButton = WebMouseEvent::ButtonLeft; |
|
792 |
|
793 result->setNull(); |
|
794 } |
|
795 |
|
796 void EventSender::addTouchPoint(const CppArgumentList& arguments, CppVariant* result) |
|
797 { |
|
798 result->setNull(); |
|
799 |
|
800 WebTouchPoint touchPoint; |
|
801 touchPoint.state = WebTouchPoint::StatePressed; |
|
802 touchPoint.position = WebPoint(arguments[0].toInt32(), arguments[1].toInt32()); |
|
803 touchPoint.id = touchPoints.size(); |
|
804 touchPoints.append(touchPoint); |
|
805 } |
|
806 |
|
807 void EventSender::clearTouchPoints(const CppArgumentList&, CppVariant* result) |
|
808 { |
|
809 result->setNull(); |
|
810 touchPoints.clear(); |
|
811 } |
|
812 |
|
813 void EventSender::releaseTouchPoint(const CppArgumentList& arguments, CppVariant* result) |
|
814 { |
|
815 result->setNull(); |
|
816 |
|
817 const unsigned index = arguments[0].toInt32(); |
|
818 ASSERT(index < touchPoints.size()); |
|
819 |
|
820 WebTouchPoint* touchPoint = &touchPoints[index]; |
|
821 touchPoint->state = WebTouchPoint::StateReleased; |
|
822 } |
|
823 |
|
824 void EventSender::setTouchModifier(const CppArgumentList& arguments, CppVariant* result) |
|
825 { |
|
826 result->setNull(); |
|
827 |
|
828 int mask = 0; |
|
829 const string keyName = arguments[0].toString(); |
|
830 if (keyName == "shift") |
|
831 mask = WebInputEvent::ShiftKey; |
|
832 else if (keyName == "alt") |
|
833 mask = WebInputEvent::AltKey; |
|
834 else if (keyName == "ctrl") |
|
835 mask = WebInputEvent::ControlKey; |
|
836 else if (keyName == "meta") |
|
837 mask = WebInputEvent::MetaKey; |
|
838 |
|
839 if (arguments[1].toBoolean()) |
|
840 touchModifiers |= mask; |
|
841 else |
|
842 touchModifiers &= ~mask; |
|
843 } |
|
844 |
|
845 void EventSender::updateTouchPoint(const CppArgumentList& arguments, CppVariant* result) |
|
846 { |
|
847 result->setNull(); |
|
848 |
|
849 const unsigned index = arguments[0].toInt32(); |
|
850 ASSERT(index < touchPoints.size()); |
|
851 |
|
852 WebPoint position(arguments[1].toInt32(), arguments[2].toInt32()); |
|
853 WebTouchPoint* touchPoint = &touchPoints[index]; |
|
854 touchPoint->state = WebTouchPoint::StateMoved; |
|
855 touchPoint->position = position; |
|
856 } |
|
857 |
|
858 void EventSender::cancelTouchPoint(const CppArgumentList& arguments, CppVariant* result) |
|
859 { |
|
860 result->setNull(); |
|
861 |
|
862 const unsigned index = arguments[0].toInt32(); |
|
863 ASSERT(index < touchPoints.size()); |
|
864 |
|
865 WebTouchPoint* touchPoint = &touchPoints[index]; |
|
866 touchPoint->state = WebTouchPoint::StateCancelled; |
|
867 } |
|
868 |
|
869 void EventSender::sendCurrentTouchEvent(const WebInputEvent::Type type) |
|
870 { |
|
871 ASSERT(static_cast<unsigned>(WebTouchEvent::touchPointsLengthCap) > touchPoints.size()); |
|
872 WebTouchEvent touchEvent; |
|
873 touchEvent.type = type; |
|
874 touchEvent.modifiers = touchModifiers; |
|
875 touchEvent.touchPointsLength = touchPoints.size(); |
|
876 for (unsigned i = 0; i < touchPoints.size(); ++i) |
|
877 touchEvent.touchPoints[i] = touchPoints[i]; |
|
878 webview()->handleInputEvent(touchEvent); |
|
879 |
|
880 for (unsigned i = 0; i < touchPoints.size(); ++i) { |
|
881 WebTouchPoint* touchPoint = &touchPoints[i]; |
|
882 if (touchPoint->state == WebTouchPoint::StateReleased) { |
|
883 touchPoints.remove(i); |
|
884 --i; |
|
885 } else |
|
886 touchPoint->state = WebTouchPoint::StateStationary; |
|
887 } |
|
888 } |
|
889 |
|
890 void EventSender::touchEnd(const CppArgumentList&, CppVariant* result) |
|
891 { |
|
892 result->setNull(); |
|
893 sendCurrentTouchEvent(WebInputEvent::TouchEnd); |
|
894 } |
|
895 |
|
896 void EventSender::touchMove(const CppArgumentList&, CppVariant* result) |
|
897 { |
|
898 result->setNull(); |
|
899 sendCurrentTouchEvent(WebInputEvent::TouchMove); |
|
900 } |
|
901 |
|
902 void EventSender::touchStart(const CppArgumentList&, CppVariant* result) |
|
903 { |
|
904 result->setNull(); |
|
905 sendCurrentTouchEvent(WebInputEvent::TouchStart); |
|
906 } |
|
907 |
|
908 void EventSender::touchCancel(const CppArgumentList&, CppVariant* result) |
|
909 { |
|
910 result->setNull(); |
|
911 sendCurrentTouchEvent(WebInputEvent::TouchCancel); |
|
912 } |
|
913 |
|
914 // |
|
915 // Unimplemented stubs |
|
916 // |
|
917 |
|
918 void EventSender::enableDOMUIEventLogging(const CppArgumentList&, CppVariant* result) |
|
919 { |
|
920 result->setNull(); |
|
921 } |
|
922 |
|
923 void EventSender::fireKeyboardEventsToElement(const CppArgumentList&, CppVariant* result) |
|
924 { |
|
925 result->setNull(); |
|
926 } |
|
927 |
|
928 void EventSender::clearKillRing(const CppArgumentList&, CppVariant* result) |
|
929 { |
|
930 result->setNull(); |
|
931 } |