1// Copyright (c) 2011 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/utf_string_conversions.h"
6#include "content/common/frame_messages.h"
7#include "content/public/test/render_view_test.h"
8#include "content/renderer/render_frame_impl.h"
9#include "content/renderer/render_view_impl.h"
10#include "testing/gtest/include/gtest/gtest.h"
11#include "third_party/WebKit/public/web/WebView.h"
12#include "third_party/WebKit/public/platform/WebSize.h"
13
14// Tests for the external select popup menu (Mac specific).
15
16namespace content {
17namespace {
18
19const char* const kSelectID = "mySelect";
20const char* const kEmptySelectID = "myEmptySelect";
21
22}  // namespace
23
24class ExternalPopupMenuTest : public RenderViewTest {
25 public:
26  ExternalPopupMenuTest() {}
27
28  RenderViewImpl* view() {
29    return static_cast<RenderViewImpl*>(view_);
30  }
31
32  RenderFrameImpl* frame() {
33    return view()->GetMainRenderFrame();
34  }
35
36  virtual void SetUp() {
37    RenderViewTest::SetUp();
38    // We need to set this explictly as RenderMain is not run.
39    blink::WebView::setUseExternalPopupMenus(true);
40
41    std::string html = "<select id='mySelect' onchange='selectChanged(this)'>"
42                       "  <option>zero</option>"
43                       "  <option selected='1'>one</option>"
44                       "  <option>two</option>"
45                       "</select>"
46                       "<select id='myEmptySelect'>"
47                       "</select>";
48    if (ShouldRemoveSelectOnChange()) {
49      html += "<script>"
50              "  function selectChanged(select) {"
51              "    select.parentNode.removeChild(select);"
52              "  }"
53              "</script>";
54    }
55
56    // Load the test page.
57    LoadHTML(html.c_str());
58
59    // Set a minimum size and give focus so simulated events work.
60    view()->webwidget()->resize(blink::WebSize(500, 500));
61    view()->webwidget()->setFocus(true);
62  }
63
64  int GetSelectedIndex() {
65    base::string16 script(base::ASCIIToUTF16(kSelectID));
66    script.append(base::ASCIIToUTF16(".selectedIndex"));
67    int selected_index = -1;
68    ExecuteJavaScriptAndReturnIntValue(script, &selected_index);
69    return selected_index;
70  }
71
72 protected:
73  virtual bool ShouldRemoveSelectOnChange() const { return false; }
74
75  DISALLOW_COPY_AND_ASSIGN(ExternalPopupMenuTest);
76};
77
78// Normal case: test showing a select popup, canceling/selecting an item.
79TEST_F(ExternalPopupMenuTest, NormalCase) {
80  IPC::TestSink& sink = render_thread_->sink();
81
82  // Click the text field once.
83  EXPECT_TRUE(SimulateElementClick(kSelectID));
84
85  // We should have sent a message to the browser to show the popup menu.
86  const IPC::Message* message =
87      sink.GetUniqueMessageMatching(FrameHostMsg_ShowPopup::ID);
88  ASSERT_TRUE(message != NULL);
89  Tuple1<FrameHostMsg_ShowPopup_Params> param;
90  FrameHostMsg_ShowPopup::Read(message, &param);
91  ASSERT_EQ(3U, param.a.popup_items.size());
92  EXPECT_EQ(1, param.a.selected_item);
93
94  // Simulate the user canceling the popup; the index should not have changed.
95  frame()->OnSelectPopupMenuItem(-1);
96  EXPECT_EQ(1, GetSelectedIndex());
97
98  // Show the pop-up again and this time make a selection.
99  EXPECT_TRUE(SimulateElementClick(kSelectID));
100  frame()->OnSelectPopupMenuItem(0);
101  EXPECT_EQ(0, GetSelectedIndex());
102
103  // Show the pop-up again and make another selection.
104  sink.ClearMessages();
105  EXPECT_TRUE(SimulateElementClick(kSelectID));
106  message = sink.GetUniqueMessageMatching(FrameHostMsg_ShowPopup::ID);
107  ASSERT_TRUE(message != NULL);
108  FrameHostMsg_ShowPopup::Read(message, &param);
109  ASSERT_EQ(3U, param.a.popup_items.size());
110  EXPECT_EQ(0, param.a.selected_item);
111}
112
113// Page shows popup, then navigates away while popup showing, then select.
114TEST_F(ExternalPopupMenuTest, ShowPopupThenNavigate) {
115  // Click the text field once.
116  EXPECT_TRUE(SimulateElementClick(kSelectID));
117
118  // Now we navigate to another pager.
119  LoadHTML("<blink>Awesome page!</blink>");
120
121  // Now the user selects something, we should not crash.
122  frame()->OnSelectPopupMenuItem(-1);
123}
124
125// An empty select should not cause a crash when clicked.
126// http://crbug.com/63774
127TEST_F(ExternalPopupMenuTest, EmptySelect) {
128  EXPECT_TRUE(SimulateElementClick(kEmptySelectID));
129}
130
131class ExternalPopupMenuRemoveTest : public ExternalPopupMenuTest {
132 public:
133  ExternalPopupMenuRemoveTest() {}
134
135 protected:
136  virtual bool ShouldRemoveSelectOnChange() const OVERRIDE { return true; }
137};
138
139// Tests that nothing bad happen when the page removes the select when it
140// changes. (http://crbug.com/61997)
141TEST_F(ExternalPopupMenuRemoveTest, RemoveOnChange) {
142  // Click the text field once to show the popup.
143  EXPECT_TRUE(SimulateElementClick(kSelectID));
144
145  // Select something, it causes the select to be removed from the page.
146  frame()->OnSelectPopupMenuItem(0);
147
148  // Just to check the soundness of the test, call SimulateElementClick again.
149  // It should return false as the select has been removed.
150  EXPECT_FALSE(SimulateElementClick(kSelectID));
151}
152
153class ExternalPopupMenuDisplayNoneTest : public ExternalPopupMenuTest {
154  public:
155  ExternalPopupMenuDisplayNoneTest() {}
156
157  virtual void SetUp() {
158    RenderViewTest::SetUp();
159    // We need to set this explictly as RenderMain is not run.
160    blink::WebView::setUseExternalPopupMenus(true);
161
162    std::string html = "<select id='mySelect'>"
163                       "  <option value='zero'>zero</option>"
164                       "  <optgroup label='hide' style='display: none'>"
165                       "    <option value='one'>one</option>"
166                       "  </optgroup>"
167                       "  <option value='two'>two</option>"
168                       "  <option value='three'>three</option>"
169                       "  <option value='four'>four</option>"
170                       "  <option value='five'>five</option>"
171                       "</select>";
172    // Load the test page.
173    LoadHTML(html.c_str());
174
175    // Set a minimum size and give focus so simulated events work.
176    view()->webwidget()->resize(blink::WebSize(500, 500));
177    view()->webwidget()->setFocus(true);
178  }
179
180};
181
182TEST_F(ExternalPopupMenuDisplayNoneTest, SelectItem) {
183  IPC::TestSink& sink = render_thread_->sink();
184
185  // Click the text field once to show the popup.
186  EXPECT_TRUE(SimulateElementClick(kSelectID));
187
188  // Read the message sent to browser to show the popup menu.
189  const IPC::Message* message =
190      sink.GetUniqueMessageMatching(FrameHostMsg_ShowPopup::ID);
191  ASSERT_TRUE(message != NULL);
192  Tuple1<FrameHostMsg_ShowPopup_Params> param;
193  FrameHostMsg_ShowPopup::Read(message, &param);
194  // Number of items should match item count minus the number
195  // of "display: none" items.
196  ASSERT_EQ(5U, param.a.popup_items.size());
197
198  // Select index 1 item. This should select item with index 2,
199  // skipping the item with 'display: none'
200  frame()->OnSelectPopupMenuItem(1);
201
202  EXPECT_EQ(2, GetSelectedIndex());
203}
204
205}  // namespace content
206