1// Copyright (c) 2012 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 "base/strings/string16.h"
6#include "base/strings/string_util.h"
7#include "content/public/browser/native_web_keyboard_event.h"
8#include "content/public/test/render_view_test.h"
9#include "content/renderer/render_view_impl.h"
10#include "testing/gtest/include/gtest/gtest.h"
11#include "webkit/common/webpreferences.h"
12
13#include <Cocoa/Cocoa.h>
14#include <Carbon/Carbon.h>  // for the kVK_* constants.
15
16namespace content {
17
18NSEvent* CmdDeadKeyEvent(NSEventType type, unsigned short code) {
19  UniChar uniChar = 0;
20  switch(code) {
21    case kVK_UpArrow:
22      uniChar = NSUpArrowFunctionKey;
23      break;
24    case kVK_DownArrow:
25      uniChar = NSDownArrowFunctionKey;
26      break;
27    default:
28      CHECK(false);
29  }
30  NSString* s = [NSString stringWithFormat:@"%C", uniChar];
31
32  return [NSEvent keyEventWithType:type
33                          location:NSZeroPoint
34                     modifierFlags:NSCommandKeyMask
35                         timestamp:0.0
36                      windowNumber:0
37                           context:nil
38                        characters:s
39       charactersIgnoringModifiers:s
40                         isARepeat:NO
41                           keyCode:code];
42}
43
44// Test that cmd-up/down scrolls the page exactly if it is not intercepted by
45// javascript.
46TEST_F(RenderViewTest, MacTestCmdUp) {
47  // Some preprocessor trickery so that we can have literal html in our source,
48  // makes it easier to copy html to and from an html file for testing (the
49  // preprocessor will remove the newlines at the line ends, turning this into
50  // a single long line).
51  #define HTML(s) #s
52  const char* kRawHtml = HTML(
53  <html>
54  <head><title></title>
55  <script type='text/javascript' language='javascript'>
56  function OnKeyEvent(ev) {
57    var result = document.getElementById(ev.type);
58    result.innerText = (ev.which || ev.keyCode) + ',' +
59      ev.shiftKey + ',' +
60      ev.ctrlKey + ',' +
61      ev.metaKey + ',' +
62      ev.altKey;
63    return %s;  /* Replace with "return true;" when testing in an html file. */
64  }
65  function OnScroll(ev) {
66    var result = document.getElementById("scroll");
67    result.innerText = window.pageYOffset;
68    return true;
69  }
70  </script>
71  <style type="text/css">
72  p { border-bottom:5000px solid black; } /* enforce vertical scroll bar */
73  </style>
74  </head>
75  <body
76    onscroll='return OnScroll(event);'
77    onkeydown='return OnKeyEvent(event);'>
78  <div id='keydown' contenteditable='true'> </div>
79  <div id='scroll' contenteditable='true'> </div>
80  <p>p1
81  <p>p2
82  </body>
83  </html>
84  );
85  #undef HTML
86
87  WebPreferences prefs;
88  prefs.enable_scroll_animator = false;
89
90  RenderViewImpl* view = static_cast<RenderViewImpl*>(view_);
91  view->OnUpdateWebPreferences(prefs);
92
93  const int kMaxOutputCharacters = 1024;
94  string16 output;
95  char htmlBuffer[2048];
96
97  NSEvent* arrowDownKeyDown = CmdDeadKeyEvent(NSKeyDown, kVK_DownArrow);
98  NSEvent* arrowUpKeyDown = CmdDeadKeyEvent(NSKeyDown, kVK_UpArrow);
99
100  // First test when javascript does not eat keypresses -- should scroll.
101  sprintf(htmlBuffer, kRawHtml, "true");
102  view->set_send_content_state_immediately(true);
103  LoadHTML(htmlBuffer);
104  render_thread_->sink().ClearMessages();
105
106  const char* kArrowDownScrollDown =
107      "40,false,false,true,false\n10128\np1\n\np2";
108  view->OnSetEditCommandsForNextKeyEvent(
109      EditCommands(1, EditCommand("moveToEndOfDocument", "")));
110  SendNativeKeyEvent(NativeWebKeyboardEvent(arrowDownKeyDown));
111  ProcessPendingMessages();
112  output = GetMainFrame()->contentAsText(kMaxOutputCharacters);
113  EXPECT_EQ(kArrowDownScrollDown, UTF16ToASCII(output));
114
115  const char* kArrowUpScrollUp =
116      "38,false,false,true,false\n0\np1\n\np2";
117  view->OnSetEditCommandsForNextKeyEvent(
118      EditCommands(1, EditCommand("moveToBeginningOfDocument", "")));
119  SendNativeKeyEvent(NativeWebKeyboardEvent(arrowUpKeyDown));
120  ProcessPendingMessages();
121  output = GetMainFrame()->contentAsText(kMaxOutputCharacters);
122  EXPECT_EQ(kArrowUpScrollUp, UTF16ToASCII(output));
123
124
125  // Now let javascript eat the key events -- no scrolling should happen
126  sprintf(htmlBuffer, kRawHtml, "false");
127  view->set_send_content_state_immediately(true);
128  LoadHTML(htmlBuffer);
129  render_thread_->sink().ClearMessages();
130
131  const char* kArrowDownNoScroll =
132      "40,false,false,true,false\np1\n\np2";
133  view->OnSetEditCommandsForNextKeyEvent(
134      EditCommands(1, EditCommand("moveToEndOfDocument", "")));
135  SendNativeKeyEvent(NativeWebKeyboardEvent(arrowDownKeyDown));
136  ProcessPendingMessages();
137  output = GetMainFrame()->contentAsText(kMaxOutputCharacters);
138  EXPECT_EQ(kArrowDownNoScroll, UTF16ToASCII(output));
139
140  const char* kArrowUpNoScroll =
141      "38,false,false,true,false\np1\n\np2";
142  view->OnSetEditCommandsForNextKeyEvent(
143      EditCommands(1, EditCommand("moveToBeginningOfDocument", "")));
144  SendNativeKeyEvent(NativeWebKeyboardEvent(arrowUpKeyDown));
145  ProcessPendingMessages();
146  output = GetMainFrame()->contentAsText(kMaxOutputCharacters);
147  EXPECT_EQ(kArrowUpNoScroll, UTF16ToASCII(output));
148}
149
150}  // namespace content
151