1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "ui/events/blink/web_input_event_builders_win.h"
6 
7 #include "base/win/windowsx_shim.h"
8 #include "ui/base/ui_base_features.h"
9 #include "ui/display/win/screen_win.h"
10 #include "ui/events/blink/blink_event_util.h"
11 #include "ui/events/event_utils.h"
12 
13 using blink::WebInputEvent;
14 using blink::WebKeyboardEvent;
15 using blink::WebMouseEvent;
16 using blink::WebMouseWheelEvent;
17 
18 namespace ui {
19 
20 static const unsigned long kDefaultScrollLinesPerWheelDelta = 3;
21 static const unsigned long kDefaultScrollCharsPerWheelDelta = 1;
22 static const unsigned long kScrollPercentPerLineOrChar = 5;
23 
24 // WebMouseEvent --------------------------------------------------------------
25 
26 static int g_last_click_count = 0;
27 static base::TimeTicks g_last_click_time;
28 
GetRelativeCursorPos(HWND hwnd)29 static LPARAM GetRelativeCursorPos(HWND hwnd) {
30   POINT pos = {-1, -1};
31   GetCursorPos(&pos);
32   ScreenToClient(hwnd, &pos);
33   return MAKELPARAM(pos.x, pos.y);
34 }
35 
Build(HWND hwnd,UINT message,WPARAM wparam,LPARAM lparam,base::TimeTicks time_stamp,blink::WebPointerProperties::PointerType pointer_type)36 WebMouseEvent WebMouseEventBuilder::Build(
37     HWND hwnd,
38     UINT message,
39     WPARAM wparam,
40     LPARAM lparam,
41     base::TimeTicks time_stamp,
42     blink::WebPointerProperties::PointerType pointer_type) {
43   WebInputEvent::Type type = WebInputEvent::Type::kUndefined;
44   WebMouseEvent::Button button = WebMouseEvent::Button::kNoButton;
45   switch (message) {
46     case WM_MOUSEMOVE:
47       type = WebInputEvent::kMouseMove;
48       if (wparam & MK_LBUTTON)
49         button = WebMouseEvent::Button::kLeft;
50       else if (wparam & MK_MBUTTON)
51         button = WebMouseEvent::Button::kMiddle;
52       else if (wparam & MK_RBUTTON)
53         button = WebMouseEvent::Button::kRight;
54       else
55         button = WebMouseEvent::Button::kNoButton;
56       break;
57     case WM_MOUSELEAVE:
58     case WM_NCMOUSELEAVE:
59       // TODO(rbyers): This should be MouseLeave but is disabled temporarily.
60       // See http://crbug.com/450631
61       type = WebInputEvent::kMouseMove;
62       button = WebMouseEvent::Button::kNoButton;
63       // set the current mouse position (relative to the client area of the
64       // current window) since none is specified for this event
65       lparam = GetRelativeCursorPos(hwnd);
66       break;
67     case WM_LBUTTONDOWN:
68     case WM_LBUTTONDBLCLK:
69       type = WebInputEvent::kMouseDown;
70       button = WebMouseEvent::Button::kLeft;
71       break;
72     case WM_MBUTTONDOWN:
73     case WM_MBUTTONDBLCLK:
74       type = WebInputEvent::kMouseDown;
75       button = WebMouseEvent::Button::kMiddle;
76       break;
77     case WM_RBUTTONDOWN:
78     case WM_RBUTTONDBLCLK:
79       type = WebInputEvent::kMouseDown;
80       button = WebMouseEvent::Button::kRight;
81       break;
82     case WM_XBUTTONDOWN:
83     case WM_XBUTTONDBLCLK:
84       type = WebInputEvent::kMouseDown;
85       if ((HIWORD(wparam) & XBUTTON1))
86         button = WebMouseEvent::Button::kBack;
87       else if ((HIWORD(wparam) & XBUTTON2))
88         button = WebMouseEvent::Button::kForward;
89       break;
90     case WM_LBUTTONUP:
91       type = WebInputEvent::kMouseUp;
92       button = WebMouseEvent::Button::kLeft;
93       break;
94     case WM_MBUTTONUP:
95       type = WebInputEvent::kMouseUp;
96       button = WebMouseEvent::Button::kMiddle;
97       break;
98     case WM_RBUTTONUP:
99       type = WebInputEvent::kMouseUp;
100       button = WebMouseEvent::Button::kRight;
101       break;
102     case WM_XBUTTONUP:
103       type = WebInputEvent::kMouseUp;
104       if ((HIWORD(wparam) & XBUTTON1))
105         button = WebMouseEvent::Button::kBack;
106       else if ((HIWORD(wparam) & XBUTTON2))
107         button = WebMouseEvent::Button::kForward;
108       break;
109     default:
110       NOTREACHED();
111   }
112 
113   // set modifiers:
114   int modifiers =
115       ui::EventFlagsToWebEventModifiers(ui::GetModifiersFromKeyState());
116   if (wparam & MK_CONTROL)
117     modifiers |= WebInputEvent::kControlKey;
118   if (wparam & MK_SHIFT)
119     modifiers |= WebInputEvent::kShiftKey;
120   if (wparam & MK_LBUTTON)
121     modifiers |= WebInputEvent::kLeftButtonDown;
122   if (wparam & MK_MBUTTON)
123     modifiers |= WebInputEvent::kMiddleButtonDown;
124   if (wparam & MK_RBUTTON)
125     modifiers |= WebInputEvent::kRightButtonDown;
126   if (wparam & MK_XBUTTON1)
127     modifiers |= WebInputEvent::kBackButtonDown;
128   if (wparam & MK_XBUTTON2)
129     modifiers |= WebInputEvent::kForwardButtonDown;
130 
131   WebMouseEvent result(type, modifiers, time_stamp);
132   result.pointer_type = pointer_type;
133   result.button = button;
134 
135   // set position fields:
136   result.SetPositionInWidget(GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam));
137 
138   POINT global_point = {result.PositionInWidget().x(),
139                         result.PositionInWidget().y()};
140   ClientToScreen(hwnd, &global_point);
141 
142   // We need to convert the global point back to DIP before using it.
143   gfx::PointF dip_global_point = display::win::ScreenWin::ScreenToDIPPoint(
144       gfx::PointF(global_point.x, global_point.y));
145 
146   result.SetPositionInScreen(dip_global_point.x(), dip_global_point.y());
147 
148   // calculate number of clicks:
149 
150   // This differs slightly from the WebKit code in WebKit/win/WebView.cpp
151   // where their original code looks buggy.
152   static int last_click_position_x;
153   static int last_click_position_y;
154   static WebMouseEvent::Button last_click_button = WebMouseEvent::Button::kLeft;
155 
156   base::TimeTicks current_time = result.TimeStamp();
157   bool cancel_previous_click =
158       (abs(last_click_position_x - result.PositionInWidget().x()) >
159        (::GetSystemMetrics(SM_CXDOUBLECLK) / 2)) ||
160       (abs(last_click_position_y - result.PositionInWidget().y()) >
161        (::GetSystemMetrics(SM_CYDOUBLECLK) / 2)) ||
162       ((current_time - g_last_click_time).InMilliseconds() >
163        ::GetDoubleClickTime());
164 
165   if (result.GetType() == WebInputEvent::kMouseDown) {
166     if (!cancel_previous_click && (result.button == last_click_button)) {
167       ++g_last_click_count;
168     } else {
169       g_last_click_count = 1;
170       last_click_position_x = result.PositionInWidget().x();
171       last_click_position_y = result.PositionInWidget().y();
172     }
173     g_last_click_time = current_time;
174     last_click_button = result.button;
175   } else if (result.GetType() == WebInputEvent::kMouseMove ||
176              result.GetType() == WebInputEvent::kMouseLeave) {
177     if (cancel_previous_click) {
178       g_last_click_count = 0;
179       last_click_position_x = 0;
180       last_click_position_y = 0;
181       g_last_click_time = base::TimeTicks();
182     }
183   }
184   result.click_count = g_last_click_count;
185 
186   return result;
187 }
188 
189 // WebMouseWheelEvent ---------------------------------------------------------
190 
Build(HWND hwnd,UINT message,WPARAM wparam,LPARAM lparam,base::TimeTicks time_stamp,blink::WebPointerProperties::PointerType pointer_type)191 WebMouseWheelEvent WebMouseWheelEventBuilder::Build(
192     HWND hwnd,
193     UINT message,
194     WPARAM wparam,
195     LPARAM lparam,
196     base::TimeTicks time_stamp,
197     blink::WebPointerProperties::PointerType pointer_type) {
198   WebMouseWheelEvent result(
199       WebInputEvent::kMouseWheel,
200       ui::EventFlagsToWebEventModifiers(ui::GetModifiersFromKeyState()),
201       time_stamp);
202 
203   result.button = WebMouseEvent::Button::kNoButton;
204   result.pointer_type = pointer_type;
205 
206   // Get key state, coordinates, and wheel delta from event.
207   UINT key_state;
208   float wheel_delta;
209   bool horizontal_scroll = false;
210   if ((message == WM_VSCROLL) || (message == WM_HSCROLL)) {
211     // Synthesize mousewheel event from a scroll event.  This is needed to
212     // simulate middle mouse scrolling in some laptops.  Use GetAsyncKeyState
213     // for key state since we are synthesizing the input event.
214     key_state = 0;
215     if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
216       key_state |= MK_SHIFT;
217     if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
218       key_state |= MK_CONTROL;
219     // NOTE: There doesn't seem to be a way to query the mouse button state
220     // in this case.
221 
222     POINT cursor_position = {0};
223     GetCursorPos(&cursor_position);
224     result.SetPositionInScreen(cursor_position.x, cursor_position.y);
225 
226     switch (LOWORD(wparam)) {
227       case SB_LINEUP:  // == SB_LINELEFT
228         wheel_delta = WHEEL_DELTA;
229         break;
230       case SB_LINEDOWN:  // == SB_LINERIGHT
231         wheel_delta = -WHEEL_DELTA;
232         break;
233       case SB_PAGEUP:
234         wheel_delta = 1;
235         result.delta_units = ui::ScrollGranularity::kScrollByPage;
236         break;
237       case SB_PAGEDOWN:
238         wheel_delta = -1;
239         result.delta_units = ui::ScrollGranularity::kScrollByPage;
240         break;
241       default:  // We don't supoprt SB_THUMBPOSITION or SB_THUMBTRACK here.
242         wheel_delta = 0;
243         break;
244     }
245 
246     if (message == WM_HSCROLL)
247       horizontal_scroll = true;
248   } else {
249     // Non-synthesized event; we can just read data off the event.
250     key_state = GET_KEYSTATE_WPARAM(wparam);
251 
252     result.SetPositionInScreen(GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam));
253 
254     // Currently we leave hasPreciseScrollingDeltas false, even for trackpad
255     // scrolls that generate WM_MOUSEWHEEL, since we don't have a good way to
256     // distinguish these from real mouse wheels (crbug.com/545234).
257     wheel_delta = static_cast<float>(GET_WHEEL_DELTA_WPARAM(wparam));
258 
259     if (message == WM_MOUSEHWHEEL) {
260       horizontal_scroll = true;
261       wheel_delta = -wheel_delta;  // Windows is <- -/+ ->, WebKit <- +/- ->.
262     }
263   }
264 
265   // Set modifiers based on key state.
266   int modifiers = result.GetModifiers();
267   if (key_state & MK_SHIFT)
268     modifiers |= WebInputEvent::kShiftKey;
269   if (key_state & MK_CONTROL)
270     modifiers |= WebInputEvent::kControlKey;
271   if (key_state & MK_LBUTTON)
272     modifiers |= WebInputEvent::kLeftButtonDown;
273   if (key_state & MK_MBUTTON)
274     modifiers |= WebInputEvent::kMiddleButtonDown;
275   if (key_state & MK_RBUTTON)
276     modifiers |= WebInputEvent::kRightButtonDown;
277   result.SetModifiers(modifiers);
278 
279   // Set coordinates by translating event coordinates from screen to client.
280   POINT client_point = {result.PositionInScreen().x(),
281                         result.PositionInScreen().y()};
282   MapWindowPoints(0, hwnd, &client_point, 1);
283   result.SetPositionInWidget(client_point.x, client_point.y);
284 
285   // |wheel_delta| is expressed in multiples or divisions of WHEEL_DELTA,
286   // divide this out here to get the number of wheel ticks.
287   float num_ticks = wheel_delta / WHEEL_DELTA;
288   float scroll_delta = num_ticks;
289   if (horizontal_scroll) {
290     unsigned long scroll_chars = kDefaultScrollCharsPerWheelDelta;
291     SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &scroll_chars, 0);
292     scroll_delta *= static_cast<float>(scroll_chars);
293   } else {
294     unsigned long scroll_lines = kDefaultScrollLinesPerWheelDelta;
295     SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &scroll_lines, 0);
296     if (scroll_lines == WHEEL_PAGESCROLL)
297       result.delta_units = ui::ScrollGranularity::kScrollByPage;
298     else
299       scroll_delta *= static_cast<float>(scroll_lines);
300   }
301 
302   if (result.delta_units != ui::ScrollGranularity::kScrollByPage) {
303     if (base::FeatureList::IsEnabled(features::kPercentBasedScrolling)) {
304       // If percent-based scrolling is enabled, the scroll_delta represents
305       // the percentage amount (out of 1, i.e. 1 == 100%) the targeted scroller
306       // should scroll. This percentage will be resolved against the size of
307       // the scroller in the renderer process.
308       scroll_delta *= kScrollPercentPerLineOrChar / 100.f;
309       result.delta_units = ui::ScrollGranularity::kScrollByPercentage;
310     } else {
311       // Convert wheel delta amount to a number of pixels to scroll.
312       //
313       // How many pixels should we scroll per line?  Gecko uses the height of
314       // the current line, which means scroll distance changes as you go through
315       // the page or go to different pages.  IE 8 is ~60 px/line, although the
316       // value seems to vary slightly by page and zoom level.  Also, IE defaults
317       // to smooth scrolling while Firefox doesn't, so it can get away with
318       // somewhat larger scroll values without feeling as jerky.  Here we use
319       // 100 px per three lines (the default scroll amount is three lines per
320       // wheel tick). Even though we have smooth scrolling, we don't make this
321       // as large as IE because subjectively IE feels like it scrolls farther
322       // than you want while reading articles.
323       static const float kScrollbarPixelsPerLine = 100.0f / 3.0f;
324 
325       // TODO(pkasting): Should probably have a different multiplier for
326       // horizontal scrolls here.
327       scroll_delta *= kScrollbarPixelsPerLine;
328     }
329   }
330 
331   // Set scroll amount based on above calculations.  WebKit expects positive
332   // deltaY to mean "scroll up" and positive deltaX to mean "scroll left".
333   if (horizontal_scroll) {
334     result.delta_x = scroll_delta;
335     result.wheel_ticks_x = num_ticks;
336   } else {
337     result.delta_y = scroll_delta;
338     result.wheel_ticks_y = num_ticks;
339   }
340 
341   return result;
342 }
343 
344 }  // namespace ui
345