Home | History | Annotate | Download | only in web_contents
      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 "content/browser/web_contents/touch_editable_impl_aura.h"
      6 
      7 #include "base/command_line.h"
      8 #include "base/run_loop.h"
      9 #include "base/strings/utf_string_conversions.h"
     10 #include "base/test/test_timeouts.h"
     11 #include "base/values.h"
     12 #include "content/browser/renderer_host/render_view_host_impl.h"
     13 #include "content/browser/web_contents/web_contents_impl.h"
     14 #include "content/browser/web_contents/web_contents_view_aura.h"
     15 #include "content/public/browser/web_contents_view.h"
     16 #include "content/public/common/content_switches.h"
     17 #include "content/public/test/browser_test_utils.h"
     18 #include "content/public/test/test_utils.h"
     19 #include "content/shell/browser/shell.h"
     20 #include "content/test/content_browser_test.h"
     21 #include "content/test/content_browser_test_utils.h"
     22 #include "ui/aura/root_window.h"
     23 #include "ui/aura/test/event_generator.h"
     24 #include "ui/aura/window.h"
     25 #include "ui/base/ui_base_switches.h"
     26 #include "ui/compositor/scoped_animation_duration_scale_mode.h"
     27 #include "ui/events/event_utils.h"
     28 
     29 namespace content {
     30 
     31 class TestTouchEditableImplAura : public TouchEditableImplAura {
     32  public:
     33   TestTouchEditableImplAura()
     34       : selection_changed_callback_arrived_(false),
     35         waiting_for_selection_changed_callback_(false),
     36         gesture_ack_callback_arrived_(false),
     37         waiting_for_gesture_ack_callback_(false) {}
     38 
     39   virtual void Reset() {
     40     selection_changed_callback_arrived_ = false;
     41     waiting_for_selection_changed_callback_ = false;
     42     gesture_ack_callback_arrived_ = false;
     43     waiting_for_gesture_ack_callback_ = false;
     44   }
     45 
     46   virtual void OnSelectionOrCursorChanged(const gfx::Rect& anchor,
     47                                           const gfx::Rect& focus) OVERRIDE {
     48     selection_changed_callback_arrived_ = true;
     49     TouchEditableImplAura::OnSelectionOrCursorChanged(anchor, focus);
     50     if (waiting_for_selection_changed_callback_)
     51       selection_changed_wait_run_loop_->Quit();
     52   }
     53 
     54   virtual void GestureEventAck(int gesture_event_type) OVERRIDE {
     55     gesture_ack_callback_arrived_ = true;
     56     TouchEditableImplAura::GestureEventAck(gesture_event_type);
     57     if (waiting_for_gesture_ack_callback_)
     58       gesture_ack_wait_run_loop_->Quit();
     59   }
     60 
     61   virtual void WaitForSelectionChangeCallback() {
     62     if (selection_changed_callback_arrived_)
     63       return;
     64     waiting_for_selection_changed_callback_ = true;
     65     selection_changed_wait_run_loop_.reset(new base::RunLoop());
     66     selection_changed_wait_run_loop_->Run();
     67   }
     68 
     69   virtual void WaitForGestureAck() {
     70     if (gesture_ack_callback_arrived_)
     71       return;
     72     waiting_for_gesture_ack_callback_ = true;
     73     gesture_ack_wait_run_loop_.reset(new base::RunLoop());
     74     gesture_ack_wait_run_loop_->Run();
     75   }
     76 
     77  protected:
     78   virtual ~TestTouchEditableImplAura() {}
     79 
     80  private:
     81   bool selection_changed_callback_arrived_;
     82   bool waiting_for_selection_changed_callback_;
     83   bool gesture_ack_callback_arrived_;
     84   bool waiting_for_gesture_ack_callback_;
     85   scoped_ptr<base::RunLoop> selection_changed_wait_run_loop_;
     86   scoped_ptr<base::RunLoop> gesture_ack_wait_run_loop_;
     87 
     88   DISALLOW_COPY_AND_ASSIGN(TestTouchEditableImplAura);
     89 };
     90 
     91 // This class ignores mouse-moved, mouse-entered and mouse-exited events
     92 // without passing them to TouchEditableImplAura. Normally, we should not
     93 // receive these events; but, sometimes we receive them which breaks the tests
     94 // and makes them flaky: crbug.com/276992.
     95 class TestTouchEditableImplAuraIgnoreMouseMovement
     96     : public TestTouchEditableImplAura {
     97  public:
     98   TestTouchEditableImplAuraIgnoreMouseMovement() {}
     99 
    100   virtual bool HandleInputEvent(const ui::Event* event) OVERRIDE {
    101     if (event->type() == ui::ET_MOUSE_ENTERED ||
    102         event->type() == ui::ET_MOUSE_MOVED ||
    103         event->type() == ui::ET_MOUSE_EXITED) {
    104       return false;
    105     }
    106     return TestTouchEditableImplAura::HandleInputEvent(event);
    107   }
    108 
    109  protected:
    110   virtual ~TestTouchEditableImplAuraIgnoreMouseMovement() {}
    111 
    112  private:
    113   DISALLOW_COPY_AND_ASSIGN(TestTouchEditableImplAuraIgnoreMouseMovement);
    114 };
    115 
    116 class TouchEditableImplAuraTest : public ContentBrowserTest {
    117  public:
    118   TouchEditableImplAuraTest() {}
    119 
    120   virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
    121     command_line->AppendSwitch(switches::kEnableTouchEditing);
    122   }
    123 
    124   // Executes the javascript synchronously and makes sure the returned value is
    125   // freed properly.
    126   void ExecuteSyncJSFunction(RenderViewHost* rvh, const std::string& jscript) {
    127     scoped_ptr<base::Value> value =
    128         content::ExecuteScriptAndGetValue(rvh, jscript);
    129   }
    130 
    131   // Starts the test server and navigates to the given url. Sets a large enough
    132   // size to the root window.  Returns after the navigation to the url is
    133   // complete.
    134   void StartTestWithPage(const std::string& url) {
    135     ASSERT_TRUE(test_server()->Start());
    136     GURL test_url(test_server()->GetURL(url));
    137     NavigateToURL(shell(), test_url);
    138     aura::Window* content =
    139         shell()->web_contents()->GetView()->GetContentNativeView();
    140     content->GetDispatcher()->SetHostSize(gfx::Size(800, 600));
    141   }
    142 
    143   void TestTouchSelectionOriginatingFromWebpage() {
    144     ASSERT_NO_FATAL_FAILURE(
    145         StartTestWithPage("files/touch_selection.html"));
    146     WebContentsImpl* web_contents =
    147         static_cast<WebContentsImpl*>(shell()->web_contents());
    148     RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>(
    149         web_contents->GetRenderViewHost());
    150     WebContentsViewAura* view_aura = static_cast<WebContentsViewAura*>(
    151         web_contents->GetView());
    152     TestTouchEditableImplAura* touch_editable =
    153         new TestTouchEditableImplAuraIgnoreMouseMovement;
    154     view_aura->SetTouchEditableForTest(touch_editable);
    155     RenderWidgetHostViewAura* rwhva = static_cast<RenderWidgetHostViewAura*>(
    156         web_contents->GetRenderWidgetHostView());
    157     aura::Window* content = web_contents->GetView()->GetContentNativeView();
    158     aura::test::EventGenerator generator(content->GetRootWindow(), content);
    159     gfx::Rect bounds = content->GetBoundsInRootWindow();
    160 
    161     touch_editable->Reset();
    162     ExecuteSyncJSFunction(view_host, "select_all_text()");
    163     touch_editable->WaitForSelectionChangeCallback();
    164 
    165     // Tap inside selection to bring up selection handles.
    166     generator.GestureTapAt(gfx::Point(bounds.x() + 10, bounds.y() + 10));
    167     EXPECT_EQ(touch_editable->rwhva_, rwhva);
    168 
    169     scoped_ptr<base::Value> value =
    170         content::ExecuteScriptAndGetValue(view_host, "get_selection()");
    171     std::string selection;
    172     value->GetAsString(&selection);
    173 
    174     // Check if selection handles are showing.
    175     EXPECT_TRUE(touch_editable->touch_selection_controller_.get());
    176     EXPECT_STREQ("Some text we can select", selection.c_str());
    177 
    178     // Lets move the handles a bit to modify the selection
    179     touch_editable->Reset();
    180     generator.GestureScrollSequence(
    181         gfx::Point(10, 47),
    182         gfx::Point(30, 47),
    183         base::TimeDelta::FromMilliseconds(20),
    184         5);
    185     touch_editable->WaitForSelectionChangeCallback();
    186 
    187     EXPECT_TRUE(touch_editable->touch_selection_controller_.get());
    188     value = content::ExecuteScriptAndGetValue(view_host, "get_selection()");
    189     value->GetAsString(&selection);
    190 
    191     // It is hard to tell what exactly the selection would be now. But it would
    192     // definitely be less than whatever was selected before.
    193     EXPECT_GT(std::strlen("Some text we can select"), selection.size());
    194   }
    195 
    196   void TestTouchSelectionOnLongPress() {
    197     ASSERT_NO_FATAL_FAILURE(
    198         StartTestWithPage("files/touch_selection.html"));
    199     WebContentsImpl* web_contents =
    200         static_cast<WebContentsImpl*>(shell()->web_contents());
    201     RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>(
    202         web_contents->GetRenderViewHost());
    203     WebContentsViewAura* view_aura = static_cast<WebContentsViewAura*>(
    204         web_contents->GetView());
    205     TestTouchEditableImplAura* touch_editable = new TestTouchEditableImplAura;
    206     view_aura->SetTouchEditableForTest(touch_editable);
    207     RenderWidgetHostViewAura* rwhva = static_cast<RenderWidgetHostViewAura*>(
    208         web_contents->GetRenderWidgetHostView());
    209     aura::Window* content = web_contents->GetView()->GetContentNativeView();
    210     aura::test::EventGenerator generator(content->GetRootWindow(), content);
    211     gfx::Rect bounds = content->GetBoundsInRootWindow();
    212     EXPECT_EQ(touch_editable->rwhva_, rwhva);
    213 
    214     // Long press to select word.
    215     ui::GestureEvent long_press(ui::ET_GESTURE_LONG_PRESS,
    216                                 10,
    217                                 10,
    218                                 0,
    219                                 ui::EventTimeForNow(),
    220                                 ui::GestureEventDetails(
    221                                     ui::ET_GESTURE_LONG_PRESS, 0, 0),
    222                                 1);
    223     touch_editable->Reset();
    224     rwhva->OnGestureEvent(&long_press);
    225     touch_editable->WaitForSelectionChangeCallback();
    226 
    227     // Check if selection handles are showing.
    228     ui::TouchSelectionController* controller =
    229         touch_editable->touch_selection_controller_.get();
    230     EXPECT_TRUE(controller);
    231 
    232     scoped_ptr<base::Value> value =
    233         content::ExecuteScriptAndGetValue(view_host, "get_selection()");
    234     std::string selection;
    235     value->GetAsString(&selection);
    236     EXPECT_STREQ("Some", selection.c_str());
    237   }
    238 
    239   void TestTouchSelectionHiddenWhenScrolling() {
    240     ASSERT_NO_FATAL_FAILURE(
    241         StartTestWithPage("files/touch_selection.html"));
    242     WebContentsImpl* web_contents =
    243         static_cast<WebContentsImpl*>(shell()->web_contents());
    244     RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>(
    245         web_contents->GetRenderViewHost());
    246     WebContentsViewAura* view_aura = static_cast<WebContentsViewAura*>(
    247         web_contents->GetView());
    248     TestTouchEditableImplAura* touch_editable = new TestTouchEditableImplAura;
    249     view_aura->SetTouchEditableForTest(touch_editable);
    250     RenderWidgetHostViewAura* rwhva = static_cast<RenderWidgetHostViewAura*>(
    251         web_contents->GetRenderWidgetHostView());
    252     aura::Window* content = web_contents->GetView()->GetContentNativeView();
    253     aura::test::EventGenerator generator(content->GetRootWindow(), content);
    254     gfx::Rect bounds = content->GetBoundsInRootWindow();
    255     EXPECT_EQ(touch_editable->rwhva_, rwhva);
    256 
    257     // Long press to select word.
    258     ui::GestureEvent long_press(ui::ET_GESTURE_LONG_PRESS,
    259                                 10,
    260                                 10,
    261                                 0,
    262                                 ui::EventTimeForNow(),
    263                                 ui::GestureEventDetails(
    264                                     ui::ET_GESTURE_LONG_PRESS, 0, 0),
    265                                 1);
    266     touch_editable->Reset();
    267     rwhva->OnGestureEvent(&long_press);
    268     touch_editable->WaitForSelectionChangeCallback();
    269 
    270     // Check if selection handles are showing.
    271     ui::TouchSelectionController* controller =
    272         touch_editable->touch_selection_controller_.get();
    273     EXPECT_TRUE(controller);
    274 
    275     scoped_ptr<base::Value> value =
    276         content::ExecuteScriptAndGetValue(view_host, "get_selection()");
    277     std::string selection;
    278     value->GetAsString(&selection);
    279     EXPECT_STREQ("Some", selection.c_str());
    280 
    281     // Start scrolling. Handles should get hidden.
    282     ui::GestureEvent scroll_begin(ui::ET_GESTURE_SCROLL_BEGIN,
    283                                   10,
    284                                   10,
    285                                   0,
    286                                   ui::EventTimeForNow(),
    287                                   ui::GestureEventDetails(
    288                                       ui::ET_GESTURE_LONG_PRESS, 0, 0),
    289                                   1);
    290     rwhva->OnGestureEvent(&scroll_begin);
    291     EXPECT_FALSE(touch_editable->touch_selection_controller_.get());
    292 
    293     // Handles should come back after scroll ends.
    294     ui::GestureEvent scroll_end(ui::ET_GESTURE_SCROLL_END,
    295                                 10,
    296                                 10,
    297                                 0,
    298                                 ui::EventTimeForNow(),
    299                                 ui::GestureEventDetails(
    300                                     ui::ET_GESTURE_LONG_PRESS, 0, 0),
    301                                 1);
    302     rwhva->OnGestureEvent(&scroll_end);
    303     EXPECT_TRUE(touch_editable->touch_selection_controller_.get());
    304   }
    305 
    306   void TestTouchCursorInTextfield() {
    307     ASSERT_NO_FATAL_FAILURE(
    308         StartTestWithPage("files/touch_selection.html"));
    309     WebContentsImpl* web_contents =
    310         static_cast<WebContentsImpl*>(shell()->web_contents());
    311     RenderViewHostImpl* view_host = static_cast<RenderViewHostImpl*>(
    312         web_contents->GetRenderViewHost());
    313     WebContentsViewAura* view_aura = static_cast<WebContentsViewAura*>(
    314         web_contents->GetView());
    315     TestTouchEditableImplAura* touch_editable =
    316         new TestTouchEditableImplAuraIgnoreMouseMovement;
    317     view_aura->SetTouchEditableForTest(touch_editable);
    318     RenderWidgetHostViewAura* rwhva = static_cast<RenderWidgetHostViewAura*>(
    319         web_contents->GetRenderWidgetHostView());
    320     aura::Window* content = web_contents->GetView()->GetContentNativeView();
    321     aura::test::EventGenerator generator(content->GetRootWindow(), content);
    322     gfx::Rect bounds = content->GetBoundsInRootWindow();
    323     EXPECT_EQ(touch_editable->rwhva_, rwhva);
    324 
    325     ExecuteSyncJSFunction(view_host, "focus_textfield()");
    326     touch_editable->WaitForSelectionChangeCallback();
    327 
    328     // Tap textfield
    329     touch_editable->Reset();
    330     generator.GestureTapAt(gfx::Point(bounds.x() + 50, bounds.y() + 40));
    331     touch_editable->WaitForSelectionChangeCallback();
    332     // No Tap Down Ack is coming, it's async.
    333     touch_editable->Reset();
    334     touch_editable->WaitForGestureAck();  // Wait for Tap Ack.
    335 
    336     // Check if cursor handle is showing.
    337     ui::TouchSelectionController* controller =
    338         touch_editable->touch_selection_controller_.get();
    339     EXPECT_NE(ui::TEXT_INPUT_TYPE_NONE, touch_editable->text_input_type_);
    340     EXPECT_TRUE(controller);
    341 
    342     scoped_ptr<base::Value> value =
    343         content::ExecuteScriptAndGetValue(view_host, "get_cursor_position()");
    344     int cursor_pos = -1;
    345     value->GetAsInteger(&cursor_pos);
    346     EXPECT_NE(-1, cursor_pos);
    347 
    348     // Move the cursor handle.
    349     generator.GestureScrollSequence(
    350         gfx::Point(50, 59),
    351         gfx::Point(10, 59),
    352         base::TimeDelta::FromMilliseconds(20),
    353         1);
    354     touch_editable->WaitForSelectionChangeCallback();
    355     EXPECT_TRUE(touch_editable->touch_selection_controller_.get());
    356     value = content::ExecuteScriptAndGetValue(view_host,
    357                                               "get_cursor_position()");
    358     int new_cursor_pos = -1;
    359     value->GetAsInteger(&new_cursor_pos);
    360     EXPECT_NE(-1, new_cursor_pos);
    361     // Cursor should have moved.
    362     EXPECT_NE(new_cursor_pos, cursor_pos);
    363   }
    364 
    365  private:
    366   DISALLOW_COPY_AND_ASSIGN(TouchEditableImplAuraTest);
    367 };
    368 
    369 IN_PROC_BROWSER_TEST_F(TouchEditableImplAuraTest,
    370                        TouchSelectionOriginatingFromWebpageTest) {
    371   TestTouchSelectionOriginatingFromWebpage();
    372 }
    373 
    374 IN_PROC_BROWSER_TEST_F(TouchEditableImplAuraTest,
    375                        TestTouchSelectionHiddenWhenScrolling) {
    376   TestTouchSelectionHiddenWhenScrolling();
    377 }
    378 
    379 IN_PROC_BROWSER_TEST_F(TouchEditableImplAuraTest,
    380                        TouchSelectionOnLongPressTest) {
    381   TestTouchSelectionOnLongPress();
    382 }
    383 
    384 IN_PROC_BROWSER_TEST_F(TouchEditableImplAuraTest,
    385                        TouchCursorInTextfieldTest) {
    386   TestTouchCursorInTextfield();
    387 }
    388 
    389 }  // namespace content
    390