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