Home | History | Annotate | Download | only in input
      1 // Copyright 2013 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 "content/browser/renderer_host/input/web_input_event_builders_gtk.h"
      6 
      7 #include <gdk/gdk.h>
      8 #include <gdk/gdkkeysyms.h>
      9 #include <gtk/gtk.h>
     10 
     11 #include "base/logging.h"
     12 #include "content/browser/renderer_host/input/web_input_event_util_posix.h"
     13 #include "third_party/WebKit/public/web/WebInputEvent.h"
     14 #include "ui/events/keycodes/keyboard_code_conversion_gtk.h"
     15 
     16 using blink::WebInputEvent;
     17 using blink::WebMouseEvent;
     18 using blink::WebMouseWheelEvent;
     19 using blink::WebKeyboardEvent;
     20 
     21 namespace {
     22 
     23 // For click count tracking.
     24 static int num_clicks = 0;
     25 static GdkWindow* last_click_event_window = 0;
     26 static gint last_click_time = 0;
     27 static gint last_click_x = 0;
     28 static gint last_click_y = 0;
     29 static WebMouseEvent::Button last_click_button = WebMouseEvent::ButtonNone;
     30 
     31 bool ShouldForgetPreviousClick(GdkWindow* window, gint time, gint x, gint y) {
     32   static GtkSettings* settings = gtk_settings_get_default();
     33 
     34   if (window != last_click_event_window)
     35     return true;
     36 
     37   gint double_click_time = 250;
     38   gint double_click_distance = 5;
     39   g_object_get(G_OBJECT(settings),
     40                "gtk-double-click-time",
     41                &double_click_time,
     42                "gtk-double-click-distance",
     43                &double_click_distance,
     44                NULL);
     45   return (time - last_click_time) > double_click_time ||
     46          std::abs(x - last_click_x) > double_click_distance ||
     47          std::abs(y - last_click_y) > double_click_distance;
     48 }
     49 
     50 void ResetClickCountState() {
     51   num_clicks = 0;
     52   last_click_event_window = 0;
     53   last_click_time = 0;
     54   last_click_x = 0;
     55   last_click_y = 0;
     56   last_click_button = blink::WebMouseEvent::ButtonNone;
     57 }
     58 
     59 bool IsKeyPadKeyval(guint keyval) {
     60   // Keypad keyvals all fall into one range.
     61   return keyval >= GDK_KP_Space && keyval <= GDK_KP_9;
     62 }
     63 
     64 double GdkEventTimeToWebEventTime(guint32 time) {
     65   // Convert from time in ms to time in sec.
     66   return time / 1000.0;
     67 }
     68 
     69 int GdkStateToWebEventModifiers(guint state) {
     70   int modifiers = 0;
     71   if (state & GDK_SHIFT_MASK)
     72     modifiers |= WebInputEvent::ShiftKey;
     73   if (state & GDK_CONTROL_MASK)
     74     modifiers |= WebInputEvent::ControlKey;
     75   if (state & GDK_MOD1_MASK)
     76     modifiers |= WebInputEvent::AltKey;
     77   if (state & GDK_META_MASK)
     78     modifiers |= WebInputEvent::MetaKey;
     79   if (state & GDK_BUTTON1_MASK)
     80     modifiers |= WebInputEvent::LeftButtonDown;
     81   if (state & GDK_BUTTON2_MASK)
     82     modifiers |= WebInputEvent::MiddleButtonDown;
     83   if (state & GDK_BUTTON3_MASK)
     84     modifiers |= WebInputEvent::RightButtonDown;
     85   if (state & GDK_LOCK_MASK)
     86     modifiers |= WebInputEvent::CapsLockOn;
     87   if (state & GDK_MOD2_MASK)
     88     modifiers |= WebInputEvent::NumLockOn;
     89   return modifiers;
     90 }
     91 
     92 ui::KeyboardCode GdkEventToWindowsKeyCode(const GdkEventKey* event) {
     93   static const unsigned int kHardwareCodeToGDKKeyval[] = {
     94     0,                 // 0x00:
     95     0,                 // 0x01:
     96     0,                 // 0x02:
     97     0,                 // 0x03:
     98     0,                 // 0x04:
     99     0,                 // 0x05:
    100     0,                 // 0x06:
    101     0,                 // 0x07:
    102     0,                 // 0x08:
    103     0,                 // 0x09: GDK_Escape
    104     GDK_1,             // 0x0A: GDK_1
    105     GDK_2,             // 0x0B: GDK_2
    106     GDK_3,             // 0x0C: GDK_3
    107     GDK_4,             // 0x0D: GDK_4
    108     GDK_5,             // 0x0E: GDK_5
    109     GDK_6,             // 0x0F: GDK_6
    110     GDK_7,             // 0x10: GDK_7
    111     GDK_8,             // 0x11: GDK_8
    112     GDK_9,             // 0x12: GDK_9
    113     GDK_0,             // 0x13: GDK_0
    114     GDK_minus,         // 0x14: GDK_minus
    115     GDK_equal,         // 0x15: GDK_equal
    116     0,                 // 0x16: GDK_BackSpace
    117     0,                 // 0x17: GDK_Tab
    118     GDK_q,             // 0x18: GDK_q
    119     GDK_w,             // 0x19: GDK_w
    120     GDK_e,             // 0x1A: GDK_e
    121     GDK_r,             // 0x1B: GDK_r
    122     GDK_t,             // 0x1C: GDK_t
    123     GDK_y,             // 0x1D: GDK_y
    124     GDK_u,             // 0x1E: GDK_u
    125     GDK_i,             // 0x1F: GDK_i
    126     GDK_o,             // 0x20: GDK_o
    127     GDK_p,             // 0x21: GDK_p
    128     GDK_bracketleft,   // 0x22: GDK_bracketleft
    129     GDK_bracketright,  // 0x23: GDK_bracketright
    130     0,                 // 0x24: GDK_Return
    131     0,                 // 0x25: GDK_Control_L
    132     GDK_a,             // 0x26: GDK_a
    133     GDK_s,             // 0x27: GDK_s
    134     GDK_d,             // 0x28: GDK_d
    135     GDK_f,             // 0x29: GDK_f
    136     GDK_g,             // 0x2A: GDK_g
    137     GDK_h,             // 0x2B: GDK_h
    138     GDK_j,             // 0x2C: GDK_j
    139     GDK_k,             // 0x2D: GDK_k
    140     GDK_l,             // 0x2E: GDK_l
    141     GDK_semicolon,     // 0x2F: GDK_semicolon
    142     GDK_apostrophe,    // 0x30: GDK_apostrophe
    143     GDK_grave,         // 0x31: GDK_grave
    144     0,                 // 0x32: GDK_Shift_L
    145     GDK_backslash,     // 0x33: GDK_backslash
    146     GDK_z,             // 0x34: GDK_z
    147     GDK_x,             // 0x35: GDK_x
    148     GDK_c,             // 0x36: GDK_c
    149     GDK_v,             // 0x37: GDK_v
    150     GDK_b,             // 0x38: GDK_b
    151     GDK_n,             // 0x39: GDK_n
    152     GDK_m,             // 0x3A: GDK_m
    153     GDK_comma,         // 0x3B: GDK_comma
    154     GDK_period,        // 0x3C: GDK_period
    155     GDK_slash,         // 0x3D: GDK_slash
    156     0,                 // 0x3E: GDK_Shift_R
    157     0,                 // 0x3F:
    158     0,                 // 0x40:
    159     0,                 // 0x41:
    160     0,                 // 0x42:
    161     0,                 // 0x43:
    162     0,                 // 0x44:
    163     0,                 // 0x45:
    164     0,                 // 0x46:
    165     0,                 // 0x47:
    166     0,                 // 0x48:
    167     0,                 // 0x49:
    168     0,                 // 0x4A:
    169     0,                 // 0x4B:
    170     0,                 // 0x4C:
    171     0,                 // 0x4D:
    172     0,                 // 0x4E:
    173     0,                 // 0x4F:
    174     0,                 // 0x50:
    175     0,                 // 0x51:
    176     0,                 // 0x52:
    177     0,                 // 0x53:
    178     0,                 // 0x54:
    179     0,                 // 0x55:
    180     0,                 // 0x56:
    181     0,                 // 0x57:
    182     0,                 // 0x58:
    183     0,                 // 0x59:
    184     0,                 // 0x5A:
    185     0,                 // 0x5B:
    186     0,                 // 0x5C:
    187     0,                 // 0x5D:
    188     0,                 // 0x5E:
    189     0,                 // 0x5F:
    190     0,                 // 0x60:
    191     0,                 // 0x61:
    192     0,                 // 0x62:
    193     0,                 // 0x63:
    194     0,                 // 0x64:
    195     0,                 // 0x65:
    196     0,                 // 0x66:
    197     0,                 // 0x67:
    198     0,                 // 0x68:
    199     0,                 // 0x69:
    200     0,                 // 0x6A:
    201     0,                 // 0x6B:
    202     0,                 // 0x6C:
    203     0,                 // 0x6D:
    204     0,                 // 0x6E:
    205     0,                 // 0x6F:
    206     0,                 // 0x70:
    207     0,                 // 0x71:
    208     0,                 // 0x72:
    209     GDK_Super_L,       // 0x73: GDK_Super_L
    210     GDK_Super_R,       // 0x74: GDK_Super_R
    211   };
    212 
    213   // |windows_key_code| has to include a valid virtual-key code even when we
    214   // use non-US layouts, e.g. even when we type an 'A' key of a US keyboard
    215   // on the Hebrew layout, |windows_key_code| should be VK_A.
    216   // On the other hand, |event->keyval| value depends on the current
    217   // GdkKeymap object, i.e. when we type an 'A' key of a US keyboard on
    218   // the Hebrew layout, |event->keyval| becomes GDK_hebrew_shin and this
    219   // ui::WindowsKeyCodeForGdkKeyCode() call returns 0.
    220   // To improve compatibilty with Windows, we use |event->hardware_keycode|
    221   // for retrieving its Windows key-code for the keys when the
    222   // WebCore::windows_key_codeForEvent() call returns 0.
    223   // We shouldn't use |event->hardware_keycode| for keys that GdkKeymap
    224   // objects cannot change because |event->hardware_keycode| doesn't change
    225   // even when we change the layout options, e.g. when we swap a control
    226   // key and a caps-lock key, GTK doesn't swap their
    227   // |event->hardware_keycode| values but swap their |event->keyval| values.
    228   ui::KeyboardCode windows_key_code =
    229       ui::WindowsKeyCodeForGdkKeyCode(event->keyval);
    230   if (windows_key_code)
    231     return windows_key_code;
    232 
    233   if (event->hardware_keycode < arraysize(kHardwareCodeToGDKKeyval)) {
    234     int keyval = kHardwareCodeToGDKKeyval[event->hardware_keycode];
    235     if (keyval)
    236       return ui::WindowsKeyCodeForGdkKeyCode(keyval);
    237   }
    238 
    239   // This key is one that keyboard-layout drivers cannot change.
    240   // Use |event->keyval| to retrieve its |windows_key_code| value.
    241   return ui::WindowsKeyCodeForGdkKeyCode(event->keyval);
    242 }
    243 
    244 // Normalizes event->state to make it Windows/Mac compatible. Since the way
    245 // of setting modifier mask on X is very different than Windows/Mac as shown
    246 // in http://crbug.com/127142#c8, the normalization is necessary.
    247 guint NormalizeEventState(const GdkEventKey* event) {
    248   guint mask = 0;
    249   switch (GdkEventToWindowsKeyCode(event)) {
    250     case ui::VKEY_CONTROL:
    251     case ui::VKEY_LCONTROL:
    252     case ui::VKEY_RCONTROL:
    253       mask = GDK_CONTROL_MASK;
    254       break;
    255     case ui::VKEY_SHIFT:
    256     case ui::VKEY_LSHIFT:
    257     case ui::VKEY_RSHIFT:
    258       mask = GDK_SHIFT_MASK;
    259       break;
    260     case ui::VKEY_MENU:
    261     case ui::VKEY_LMENU:
    262     case ui::VKEY_RMENU:
    263       mask = GDK_MOD1_MASK;
    264       break;
    265     case ui::VKEY_CAPITAL:
    266       mask = GDK_LOCK_MASK;
    267       break;
    268     default:
    269       return event->state;
    270   }
    271   if (event->type == GDK_KEY_PRESS)
    272     return event->state | mask;
    273   return event->state & ~mask;
    274 }
    275 
    276 // Gets the corresponding control character of a specified key code. See:
    277 // http://en.wikipedia.org/wiki/Control_characters
    278 // We emulate Windows behavior here.
    279 int GetControlCharacter(ui::KeyboardCode windows_key_code, bool shift) {
    280   if (windows_key_code >= ui::VKEY_A && windows_key_code <= ui::VKEY_Z) {
    281     // ctrl-A ~ ctrl-Z map to \x01 ~ \x1A
    282     return windows_key_code - ui::VKEY_A + 1;
    283   }
    284   if (shift) {
    285     // following graphics chars require shift key to input.
    286     switch (windows_key_code) {
    287       // ctrl-@ maps to \x00 (Null byte)
    288       case ui::VKEY_2:
    289         return 0;
    290       // ctrl-^ maps to \x1E (Record separator, Information separator two)
    291       case ui::VKEY_6:
    292         return 0x1E;
    293       // ctrl-_ maps to \x1F (Unit separator, Information separator one)
    294       case ui::VKEY_OEM_MINUS:
    295         return 0x1F;
    296       // Returns 0 for all other keys to avoid inputting unexpected chars.
    297       default:
    298         return 0;
    299     }
    300   } else {
    301     switch (windows_key_code) {
    302       // ctrl-[ maps to \x1B (Escape)
    303       case ui::VKEY_OEM_4:
    304         return 0x1B;
    305       // ctrl-\ maps to \x1C (File separator, Information separator four)
    306       case ui::VKEY_OEM_5:
    307         return 0x1C;
    308       // ctrl-] maps to \x1D (Group separator, Information separator three)
    309       case ui::VKEY_OEM_6:
    310         return 0x1D;
    311       // ctrl-Enter maps to \x0A (Line feed)
    312       case ui::VKEY_RETURN:
    313         return 0x0A;
    314       // Returns 0 for all other keys to avoid inputting unexpected chars.
    315       default:
    316         return 0;
    317     }
    318   }
    319 }
    320 
    321 }  // namespace
    322 
    323 namespace content {
    324 
    325 // WebKeyboardEvent -----------------------------------------------------------
    326 
    327 WebKeyboardEvent WebKeyboardEventBuilder::Build(const GdkEventKey* event) {
    328   WebKeyboardEvent result;
    329 
    330   result.timeStampSeconds = GdkEventTimeToWebEventTime(event->time);
    331   result.modifiers = GdkStateToWebEventModifiers(NormalizeEventState(event));
    332 
    333   switch (event->type) {
    334     case GDK_KEY_RELEASE:
    335       result.type = WebInputEvent::KeyUp;
    336       break;
    337     case GDK_KEY_PRESS:
    338       result.type = WebInputEvent::RawKeyDown;
    339       break;
    340     default:
    341       NOTREACHED();
    342   }
    343 
    344   // According to MSDN:
    345   // http://msdn.microsoft.com/en-us/library/ms646286(VS.85).aspx
    346   // Key events with Alt modifier and F10 are system key events.
    347   // We just emulate this behavior. It's necessary to prevent webkit from
    348   // processing keypress event generated by alt-d, etc.
    349   // F10 is not special on Linux, so don't treat it as system key.
    350   if (result.modifiers & WebInputEvent::AltKey)
    351     result.isSystemKey = true;
    352 
    353   // The key code tells us which physical key was pressed (for example, the
    354   // A key went down or up).  It does not determine whether A should be lower
    355   // or upper case.  This is what text does, which should be the keyval.
    356   ui::KeyboardCode windows_key_code = GdkEventToWindowsKeyCode(event);
    357   result.windowsKeyCode = GetWindowsKeyCodeWithoutLocation(windows_key_code);
    358   result.modifiers |= GetLocationModifiersFromWindowsKeyCode(windows_key_code);
    359   result.nativeKeyCode = event->hardware_keycode;
    360 
    361   if (result.windowsKeyCode == ui::VKEY_RETURN) {
    362     // We need to treat the enter key as a key press of character \r.  This
    363     // is apparently just how webkit handles it and what it expects.
    364     result.unmodifiedText[0] = '\r';
    365   } else {
    366     // FIXME: fix for non BMP chars
    367     result.unmodifiedText[0] =
    368         static_cast<int>(gdk_keyval_to_unicode(event->keyval));
    369   }
    370 
    371   // If ctrl key is pressed down, then control character shall be input.
    372   if (result.modifiers & WebInputEvent::ControlKey) {
    373     result.text[0] =
    374       GetControlCharacter(ui::KeyboardCode(result.windowsKeyCode),
    375                           result.modifiers & WebInputEvent::ShiftKey);
    376   } else {
    377     result.text[0] = result.unmodifiedText[0];
    378   }
    379 
    380   result.setKeyIdentifierFromWindowsKeyCode();
    381 
    382   // FIXME: Do we need to set IsAutoRepeat?
    383   if (IsKeyPadKeyval(event->keyval))
    384     result.modifiers |= WebInputEvent::IsKeyPad;
    385 
    386   return result;
    387 }
    388 
    389 WebKeyboardEvent WebKeyboardEventBuilder::Build(wchar_t character,
    390                                                      int state,
    391                                                      double timeStampSeconds) {
    392   // keyboardEvent(const GdkEventKey*) depends on the GdkEventKey object and
    393   // it is hard to use/ it from signal handlers which don't use GdkEventKey
    394   // objects (e.g. GtkIMContext signal handlers.) For such handlers, this
    395   // function creates a WebInputEvent::Char event without using a
    396   // GdkEventKey object.
    397   WebKeyboardEvent result;
    398   result.type = blink::WebInputEvent::Char;
    399   result.timeStampSeconds = timeStampSeconds;
    400   result.modifiers = GdkStateToWebEventModifiers(state);
    401   result.windowsKeyCode = character;
    402   result.nativeKeyCode = character;
    403   result.text[0] = character;
    404   result.unmodifiedText[0] = character;
    405 
    406   // According to MSDN:
    407   // http://msdn.microsoft.com/en-us/library/ms646286(VS.85).aspx
    408   // Key events with Alt modifier and F10 are system key events.
    409   // We just emulate this behavior. It's necessary to prevent webkit from
    410   // processing keypress event generated by alt-d, etc.
    411   // F10 is not special on Linux, so don't treat it as system key.
    412   if (result.modifiers & WebInputEvent::AltKey)
    413     result.isSystemKey = true;
    414 
    415   return result;
    416 }
    417 
    418 // WebMouseEvent --------------------------------------------------------------
    419 
    420 WebMouseEvent WebMouseEventBuilder::Build(const GdkEventButton* event) {
    421   WebMouseEvent result;
    422 
    423   result.timeStampSeconds = GdkEventTimeToWebEventTime(event->time);
    424 
    425   result.modifiers = GdkStateToWebEventModifiers(event->state);
    426   result.x = static_cast<int>(event->x);
    427   result.y = static_cast<int>(event->y);
    428   result.windowX = result.x;
    429   result.windowY = result.y;
    430   result.globalX = static_cast<int>(event->x_root);
    431   result.globalY = static_cast<int>(event->y_root);
    432   result.clickCount = 0;
    433 
    434   switch (event->type) {
    435     case GDK_BUTTON_PRESS:
    436       result.type = WebInputEvent::MouseDown;
    437       break;
    438     case GDK_BUTTON_RELEASE:
    439       result.type = WebInputEvent::MouseUp;
    440       break;
    441     case GDK_3BUTTON_PRESS:
    442     case GDK_2BUTTON_PRESS:
    443     default:
    444       NOTREACHED();
    445   }
    446 
    447   result.button = WebMouseEvent::ButtonNone;
    448   if (event->button == 1)
    449     result.button = WebMouseEvent::ButtonLeft;
    450   else if (event->button == 2)
    451     result.button = WebMouseEvent::ButtonMiddle;
    452   else if (event->button == 3)
    453     result.button = WebMouseEvent::ButtonRight;
    454 
    455   if (result.type == WebInputEvent::MouseDown) {
    456     bool forgetPreviousClick = ShouldForgetPreviousClick(
    457         event->window, event->time, event->x, event->y);
    458 
    459     if (!forgetPreviousClick && result.button == last_click_button) {
    460       ++num_clicks;
    461     } else {
    462       num_clicks = 1;
    463 
    464       last_click_event_window = event->window;
    465       last_click_x = event->x;
    466       last_click_y = event->y;
    467       last_click_button = result.button;
    468     }
    469     last_click_time = event->time;
    470   }
    471   result.clickCount = num_clicks;
    472 
    473   return result;
    474 }
    475 
    476 WebMouseEvent WebMouseEventBuilder::Build(const GdkEventMotion* event) {
    477   WebMouseEvent result;
    478 
    479   result.timeStampSeconds = GdkEventTimeToWebEventTime(event->time);
    480   result.modifiers = GdkStateToWebEventModifiers(event->state);
    481   result.x = static_cast<int>(event->x);
    482   result.y = static_cast<int>(event->y);
    483   result.windowX = result.x;
    484   result.windowY = result.y;
    485   result.globalX = static_cast<int>(event->x_root);
    486   result.globalY = static_cast<int>(event->y_root);
    487 
    488   switch (event->type) {
    489     case GDK_MOTION_NOTIFY:
    490       result.type = WebInputEvent::MouseMove;
    491       break;
    492     default:
    493       NOTREACHED();
    494   }
    495 
    496   result.button = WebMouseEvent::ButtonNone;
    497   if (event->state & GDK_BUTTON1_MASK)
    498     result.button = WebMouseEvent::ButtonLeft;
    499   else if (event->state & GDK_BUTTON2_MASK)
    500     result.button = WebMouseEvent::ButtonMiddle;
    501   else if (event->state & GDK_BUTTON3_MASK)
    502     result.button = WebMouseEvent::ButtonRight;
    503 
    504   if (ShouldForgetPreviousClick(event->window, event->time, event->x, event->y))
    505     ResetClickCountState();
    506 
    507   return result;
    508 }
    509 
    510 WebMouseEvent WebMouseEventBuilder::Build(const GdkEventCrossing* event) {
    511   WebMouseEvent result;
    512 
    513   result.timeStampSeconds = GdkEventTimeToWebEventTime(event->time);
    514   result.modifiers = GdkStateToWebEventModifiers(event->state);
    515   result.x = static_cast<int>(event->x);
    516   result.y = static_cast<int>(event->y);
    517   result.windowX = result.x;
    518   result.windowY = result.y;
    519   result.globalX = static_cast<int>(event->x_root);
    520   result.globalY = static_cast<int>(event->y_root);
    521 
    522   switch (event->type) {
    523     case GDK_ENTER_NOTIFY:
    524     case GDK_LEAVE_NOTIFY:
    525       // Note that if we sent MouseEnter or MouseLeave to WebKit, it
    526       // wouldn't work - they don't result in the proper JavaScript events.
    527       // MouseMove does the right thing.
    528       result.type = WebInputEvent::MouseMove;
    529       break;
    530     default:
    531       NOTREACHED();
    532   }
    533 
    534   result.button = WebMouseEvent::ButtonNone;
    535   if (event->state & GDK_BUTTON1_MASK)
    536     result.button = WebMouseEvent::ButtonLeft;
    537   else if (event->state & GDK_BUTTON2_MASK)
    538     result.button = WebMouseEvent::ButtonMiddle;
    539   else if (event->state & GDK_BUTTON3_MASK)
    540     result.button = WebMouseEvent::ButtonRight;
    541 
    542   if (ShouldForgetPreviousClick(event->window, event->time, event->x, event->y))
    543     ResetClickCountState();
    544 
    545   return result;
    546 }
    547 
    548 // WebMouseWheelEvent ---------------------------------------------------------
    549 
    550 float WebMouseWheelEventBuilder::ScrollbarPixelsPerTick() {
    551   // How much should we scroll per mouse wheel event?
    552   // - Windows uses 3 lines by default and obeys a system setting.
    553   // - Mozilla has a pref that lets you either use the "system" number of lines
    554   //   to scroll, or lets the user override it.
    555   //   For the "system" number of lines, it appears they've hardcoded 3.
    556   //   See case NS_MOUSE_SCROLL in content/events/src/nsEventStateManager.cpp
    557   //   and InitMouseScrollEvent in widget/src/gtk2/nsCommonWidget.cpp .
    558   // - Gtk makes the scroll amount a function of the size of the scroll bar,
    559   //   which is not available to us here.
    560   // Instead, we pick a number that empirically matches Firefox's behavior.
    561   return 160.0f / 3.0f;
    562 }
    563 
    564 WebMouseWheelEvent WebMouseWheelEventBuilder::Build(
    565     const GdkEventScroll* event) {
    566   WebMouseWheelEvent result;
    567 
    568   result.type = WebInputEvent::MouseWheel;
    569   result.button = WebMouseEvent::ButtonNone;
    570 
    571   result.timeStampSeconds = GdkEventTimeToWebEventTime(event->time);
    572   result.modifiers = GdkStateToWebEventModifiers(event->state);
    573   result.x = static_cast<int>(event->x);
    574   result.y = static_cast<int>(event->y);
    575   result.windowX = result.x;
    576   result.windowY = result.y;
    577   result.globalX = static_cast<int>(event->x_root);
    578   result.globalY = static_cast<int>(event->y_root);
    579 
    580   static const float scrollbarPixelsPerTick = ScrollbarPixelsPerTick();
    581   switch (event->direction) {
    582     case GDK_SCROLL_UP:
    583       result.deltaY = scrollbarPixelsPerTick;
    584       result.wheelTicksY = 1;
    585       break;
    586     case GDK_SCROLL_DOWN:
    587       result.deltaY = -scrollbarPixelsPerTick;
    588       result.wheelTicksY = -1;
    589       break;
    590     case GDK_SCROLL_LEFT:
    591       result.deltaX = scrollbarPixelsPerTick;
    592       result.wheelTicksX = 1;
    593       break;
    594     case GDK_SCROLL_RIGHT:
    595       result.deltaX = -scrollbarPixelsPerTick;
    596       result.wheelTicksX = -1;
    597       break;
    598   }
    599 
    600   return result;
    601 }
    602 
    603 }  // namespace content
    604