Home | History | Annotate | Download | only in views
      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/memory/weak_ptr.h"
      6 #include "chrome/browser/platform_util.h"
      7 #include "chrome/browser/profiles/profile.h"
      8 #include "chrome/browser/ui/browser.h"
      9 #include "chrome/browser/ui/browser_commands.h"
     10 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     11 #include "chrome/browser/ui/views/constrained_window_views.h"
     12 #include "chrome/browser/ui/views/frame/browser_view.h"
     13 #include "chrome/browser/ui/webui/constrained_web_dialog_ui.h"
     14 #include "chrome/common/url_constants.h"
     15 #include "chrome/test/base/in_process_browser_test.h"
     16 #include "chrome/test/base/ui_test_utils.h"
     17 #include "components/web_modal/web_contents_modal_dialog_manager.h"
     18 #include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
     19 #include "content/public/browser/native_web_keyboard_event.h"
     20 #include "content/public/browser/navigation_controller.h"
     21 #include "content/public/browser/render_view_host.h"
     22 #include "content/public/browser/web_contents.h"
     23 #include "content/public/browser/web_contents_view.h"
     24 #include "ipc/ipc_message.h"
     25 #include "ui/base/accelerators/accelerator.h"
     26 #include "ui/views/controls/textfield/textfield.h"
     27 #include "ui/views/focus/focus_manager.h"
     28 #include "ui/views/layout/fill_layout.h"
     29 #include "ui/views/test/test_widget_observer.h"
     30 #include "ui/views/window/dialog_delegate.h"
     31 #include "ui/web_dialogs/test/test_web_dialog_delegate.h"
     32 
     33 #if defined(USE_AURA) && defined(USE_X11)
     34 #include <X11/Xlib.h>
     35 #include "ui/base/x/x11_util.h"
     36 #endif
     37 
     38 using web_modal::WebContentsModalDialogManager;
     39 
     40 namespace {
     41 
     42 class TestConstrainedDialogContentsView
     43     : public views::View,
     44       public base::SupportsWeakPtr<TestConstrainedDialogContentsView> {
     45  public:
     46   TestConstrainedDialogContentsView()
     47       : text_field_(new views::Textfield) {
     48     SetLayoutManager(new views::FillLayout);
     49     AddChildView(text_field_);
     50   }
     51 
     52   views::View* GetInitiallyFocusedView() {
     53     return text_field_;
     54   }
     55 
     56  private:
     57   views::Textfield* text_field_;
     58   DISALLOW_COPY_AND_ASSIGN(TestConstrainedDialogContentsView);
     59 };
     60 
     61 class TestConstrainedDialog : public views::DialogDelegate {
     62  public:
     63   TestConstrainedDialog()
     64       : contents_((new TestConstrainedDialogContentsView())->AsWeakPtr()),
     65         done_(false) {
     66   }
     67 
     68   virtual ~TestConstrainedDialog() {}
     69 
     70   virtual views::View* GetInitiallyFocusedView() OVERRIDE {
     71     return contents_ ? contents_->GetInitiallyFocusedView() : NULL;
     72   }
     73 
     74   virtual views::View* GetContentsView() OVERRIDE {
     75     return contents_.get();
     76   }
     77 
     78   virtual views::Widget* GetWidget() OVERRIDE {
     79     return contents_ ? contents_->GetWidget() : NULL;
     80   }
     81 
     82   virtual const views::Widget* GetWidget() const OVERRIDE {
     83     return contents_ ? contents_->GetWidget() : NULL;
     84   }
     85 
     86   virtual void DeleteDelegate() OVERRIDE {
     87     // Don't delete the delegate yet.  We need to keep it around for inspection
     88     // later.
     89     EXPECT_TRUE(done_);
     90   }
     91 
     92   virtual bool Accept() OVERRIDE {
     93     done_ = true;
     94     return true;
     95   }
     96 
     97   virtual bool Cancel() OVERRIDE {
     98     done_ = true;
     99     return true;
    100   }
    101 
    102   virtual ui::ModalType GetModalType() const OVERRIDE {
    103 #if defined(USE_ASH)
    104     return ui::MODAL_TYPE_CHILD;
    105 #else
    106     return views::WidgetDelegate::GetModalType();
    107 #endif
    108   }
    109 
    110   bool done() {
    111     return done_;
    112   }
    113 
    114  private:
    115   // contents_ will be freed when the View goes away.
    116   base::WeakPtr<TestConstrainedDialogContentsView> contents_;
    117   bool done_;
    118 
    119   DISALLOW_COPY_AND_ASSIGN(TestConstrainedDialog);
    120 };
    121 
    122 } // namespace
    123 
    124 class ConstrainedWindowViewTest : public InProcessBrowserTest {
    125  public:
    126   ConstrainedWindowViewTest() {
    127   }
    128 };
    129 
    130 // Tests the following:
    131 //
    132 // *) Initially focused view in a constrained dialog receives focus reliably.
    133 //
    134 // *) Constrained windows that are queued don't register themselves as
    135 //    accelerator targets until they are displayed.
    136 IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest, FocusTest) {
    137   content::WebContents* web_contents =
    138       browser()->tab_strip_model()->GetActiveWebContents();
    139   ASSERT_TRUE(web_contents != NULL);
    140   WebContentsModalDialogManager* web_contents_modal_dialog_manager =
    141       WebContentsModalDialogManager::FromWebContents(web_contents);
    142   ASSERT_TRUE(web_contents_modal_dialog_manager != NULL);
    143 
    144   // Create a constrained dialog.  It will attach itself to web_contents.
    145   scoped_ptr<TestConstrainedDialog> test_dialog1(new TestConstrainedDialog);
    146   views::Widget* window1 = CreateWebContentsModalDialogViews(
    147       test_dialog1.get(),
    148       web_contents->GetView()->GetNativeView(),
    149       web_contents_modal_dialog_manager->delegate()->
    150           GetWebContentsModalDialogHost());
    151   web_contents_modal_dialog_manager->ShowDialog(window1->GetNativeView());
    152 
    153   views::FocusManager* focus_manager = window1->GetFocusManager();
    154   ASSERT_TRUE(focus_manager);
    155 
    156   // test_dialog1's text field should be focused.
    157   EXPECT_EQ(test_dialog1->GetInitiallyFocusedView(),
    158             focus_manager->GetFocusedView());
    159 
    160   // Now create a second constrained dialog.  This will also be attached to
    161   // web_contents, but will remain hidden since the test_dialog1 is still
    162   // showing.
    163   scoped_ptr<TestConstrainedDialog> test_dialog2(new TestConstrainedDialog);
    164   views::Widget* window2 = CreateWebContentsModalDialogViews(
    165       test_dialog2.get(),
    166       web_contents->GetView()->GetNativeView(),
    167       web_contents_modal_dialog_manager->delegate()->
    168           GetWebContentsModalDialogHost());
    169   web_contents_modal_dialog_manager->ShowDialog(window2->GetNativeView());
    170   // Should be the same focus_manager.
    171   ASSERT_EQ(focus_manager, window2->GetFocusManager());
    172 
    173   // test_dialog1's text field should still be the view that has focus.
    174   EXPECT_EQ(test_dialog1->GetInitiallyFocusedView(),
    175             focus_manager->GetFocusedView());
    176   ASSERT_TRUE(web_contents_modal_dialog_manager->IsShowingDialog());
    177 
    178   // Now send a VKEY_RETURN to the browser.  This should result in closing
    179   // test_dialog1.
    180   EXPECT_TRUE(focus_manager->ProcessAccelerator(
    181       ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE)));
    182   content::RunAllPendingInMessageLoop();
    183 
    184   EXPECT_TRUE(test_dialog1->done());
    185   EXPECT_FALSE(test_dialog2->done());
    186   EXPECT_TRUE(web_contents_modal_dialog_manager->IsShowingDialog());
    187 
    188   // test_dialog2 will be shown.  Focus should be on test_dialog2's text field.
    189   EXPECT_EQ(test_dialog2->GetInitiallyFocusedView(),
    190             focus_manager->GetFocusedView());
    191 
    192   int tab_with_constrained_window =
    193       browser()->tab_strip_model()->active_index();
    194 
    195   // Create a new tab.
    196   chrome::NewTab(browser());
    197 
    198   // The constrained dialog should no longer be selected.
    199   EXPECT_NE(test_dialog2->GetInitiallyFocusedView(),
    200             focus_manager->GetFocusedView());
    201 
    202   browser()->tab_strip_model()->ActivateTabAt(tab_with_constrained_window,
    203                                               false);
    204 
    205   // Activating the previous tab should bring focus to the constrained window.
    206   EXPECT_EQ(test_dialog2->GetInitiallyFocusedView(),
    207             focus_manager->GetFocusedView());
    208 
    209   // Send another VKEY_RETURN, closing test_dialog2
    210   EXPECT_TRUE(focus_manager->ProcessAccelerator(
    211       ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE)));
    212   content::RunAllPendingInMessageLoop();
    213   EXPECT_TRUE(test_dialog2->done());
    214   EXPECT_FALSE(web_contents_modal_dialog_manager->IsShowingDialog());
    215 }
    216 
    217 // Tests that the constrained window is closed properly when its tab is
    218 // closed.
    219 IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest, TabCloseTest) {
    220   content::WebContents* web_contents =
    221       browser()->tab_strip_model()->GetActiveWebContents();
    222   ASSERT_TRUE(web_contents != NULL);
    223   WebContentsModalDialogManager* web_contents_modal_dialog_manager =
    224       WebContentsModalDialogManager::FromWebContents(web_contents);
    225   ASSERT_TRUE(web_contents_modal_dialog_manager != NULL);
    226 
    227   // Create a constrained dialog.  It will attach itself to web_contents.
    228   scoped_ptr<TestConstrainedDialog> test_dialog(new TestConstrainedDialog);
    229   views::Widget* window = CreateWebContentsModalDialogViews(
    230       test_dialog.get(),
    231       web_contents->GetView()->GetNativeView(),
    232       web_contents_modal_dialog_manager->delegate()->
    233           GetWebContentsModalDialogHost());
    234   web_contents_modal_dialog_manager->ShowDialog(window->GetNativeView());
    235 
    236   bool closed =
    237       browser()->tab_strip_model()->CloseWebContentsAt(
    238           browser()->tab_strip_model()->active_index(),
    239           TabStripModel::CLOSE_NONE);
    240   EXPECT_TRUE(closed);
    241   content::RunAllPendingInMessageLoop();
    242   EXPECT_TRUE(test_dialog->done());
    243 }
    244 
    245 // Tests that the constrained window is hidden when an other tab is selected and
    246 // shown when its tab is selected again.
    247 IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest, TabSwitchTest) {
    248   content::WebContents* web_contents =
    249       browser()->tab_strip_model()->GetActiveWebContents();
    250   ASSERT_TRUE(web_contents != NULL);
    251 
    252   // Create a constrained dialog.  It will attach itself to web_contents.
    253   scoped_ptr<TestConstrainedDialog> test_dialog(new TestConstrainedDialog);
    254   WebContentsModalDialogManager* web_contents_modal_dialog_manager =
    255       WebContentsModalDialogManager::FromWebContents(web_contents);
    256   views::Widget* window = CreateWebContentsModalDialogViews(
    257       test_dialog.get(),
    258       web_contents->GetView()->GetNativeView(),
    259       web_contents_modal_dialog_manager->delegate()->
    260           GetWebContentsModalDialogHost());
    261   web_contents_modal_dialog_manager->ShowDialog(window->GetNativeView());
    262   EXPECT_TRUE(window->IsVisible());
    263 
    264   // Open a new tab. The constrained window should hide itself.
    265   browser()->tab_strip_model()->AppendWebContents(
    266       content::WebContents::Create(
    267           content::WebContents::CreateParams(browser()->profile())),
    268       true);
    269   EXPECT_FALSE(window->IsVisible());
    270 
    271   // Close the new tab. The constrained window should show itself again.
    272   bool closed =
    273       browser()->tab_strip_model()->CloseWebContentsAt(
    274           browser()->tab_strip_model()->active_index(),
    275           TabStripModel::CLOSE_NONE);
    276   EXPECT_TRUE(closed);
    277   EXPECT_TRUE(window->IsVisible());
    278 
    279   // Close the original tab.
    280   browser()->tab_strip_model()->CloseWebContentsAt(
    281       browser()->tab_strip_model()->active_index(),
    282       TabStripModel::CLOSE_NONE);
    283   content::RunAllPendingInMessageLoop();
    284   EXPECT_TRUE(test_dialog->done());
    285 }
    286 
    287 #if defined(OS_WIN) || (defined(USE_AURA) && defined(USE_X11))
    288 
    289 // Forwards the key event which has |key_code| to the renderer.
    290 void ForwardKeyEvent(content::RenderViewHost* host, ui::KeyboardCode key_code) {
    291 #if defined(OS_WIN)
    292   MSG native_key_event = { NULL, WM_KEYDOWN, key_code, 0 };
    293 #elif defined(USE_X11)
    294   XEvent x_event;
    295   ui::InitXKeyEventForTesting(
    296       ui::ET_KEY_PRESSED, key_code, ui::EF_NONE, &x_event);
    297   XEvent* native_key_event = &x_event;
    298 #endif
    299 
    300 #if defined(USE_AURA)
    301   ui::KeyEvent key(native_key_event, false);
    302   ui::KeyEvent* native_ui_key_event = &key;
    303 #elif defined(OS_WIN)
    304   MSG native_ui_key_event = native_key_event;
    305 #endif
    306 
    307   host->ForwardKeyboardEvent(
    308       content::NativeWebKeyboardEvent(native_ui_key_event));
    309 }
    310 
    311 // Tests that backspace is not processed before it's sent to the web contents.
    312 // Flaky on Win Aura and Linux ChromiumOS. See http://crbug.com/170331
    313 #if defined(USE_AURA)
    314 #define MAYBE_BackspaceSentToWebContent DISABLED_BackspaceSentToWebContent
    315 #else
    316 #define MAYBE_BackspaceSentToWebContent BackspaceSentToWebContent
    317 #endif
    318 IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest,
    319                        MAYBE_BackspaceSentToWebContent) {
    320   content::WebContents* web_contents =
    321       browser()->tab_strip_model()->GetActiveWebContents();
    322   ASSERT_TRUE(web_contents != NULL);
    323 
    324   GURL new_tab_url(chrome::kChromeUINewTabURL);
    325   ui_test_utils::NavigateToURL(browser(), new_tab_url);
    326   GURL about_url(chrome::kChromeUIAboutURL);
    327   ui_test_utils::NavigateToURL(browser(), about_url);
    328 
    329   ConstrainedWebDialogDelegate* cwdd = CreateConstrainedWebDialog(
    330       browser()->profile(),
    331       new ui::test::TestWebDialogDelegate(about_url),
    332       NULL,
    333       web_contents);
    334 
    335   content::WindowedNotificationObserver back_observer(
    336       content::NOTIFICATION_LOAD_STOP,
    337       content::Source<content::NavigationController>(
    338           &web_contents->GetController()));
    339   content::RenderViewHost* render_view_host =
    340       cwdd->GetWebContents()->GetRenderViewHost();
    341   ForwardKeyEvent(render_view_host, ui::VKEY_BACK);
    342 
    343   // Backspace is not processed as accelerator before it's sent to web contents.
    344   EXPECT_FALSE(web_contents->GetController().GetPendingEntry());
    345   EXPECT_EQ(about_url.spec(), web_contents->GetURL().spec());
    346 
    347   // Backspace is processed as accelerator after it's sent to web contents.
    348   content::RunAllPendingInMessageLoop();
    349   EXPECT_TRUE(web_contents->GetController().GetPendingEntry());
    350 
    351   // Wait for the navigation to commit, since the URL will not be visible
    352   // until then.
    353   back_observer.Wait();
    354   EXPECT_EQ(new_tab_url.spec(), web_contents->GetURL().spec());
    355 }
    356 
    357 // Fails flakily (once per 10-20 runs) on Win Aura only. http://crbug.com/177482
    358 // Also fails on CrOS.
    359 #if defined(OS_WIN) || defined(OS_CHROMEOS)
    360 #define MAYBE_EscapeCloseConstrainedWindow DISABLED_EscapeCloseConstrainedWindow
    361 #else
    362 #define MAYBE_EscapeCloseConstrainedWindow EscapeCloseConstrainedWindow
    363 #endif
    364 
    365 // Tests that escape closes the constrained window.
    366 IN_PROC_BROWSER_TEST_F(ConstrainedWindowViewTest,
    367                        MAYBE_EscapeCloseConstrainedWindow) {
    368   content::WebContents* web_contents =
    369       browser()->tab_strip_model()->GetActiveWebContents();
    370   ASSERT_TRUE(web_contents != NULL);
    371 
    372   GURL new_tab_url(chrome::kChromeUINewTabURL);
    373   ui_test_utils::NavigateToURL(browser(), new_tab_url);
    374   ConstrainedWebDialogDelegate* cwdd = CreateConstrainedWebDialog(
    375       browser()->profile(),
    376       new ui::test::TestWebDialogDelegate(new_tab_url),
    377       NULL,
    378       web_contents);
    379 
    380   views::Widget* widget =
    381       views::Widget::GetWidgetForNativeView(cwdd->GetNativeDialog());
    382   views::test::TestWidgetObserver observer(widget);
    383 
    384   content::RenderViewHost* render_view_host =
    385       cwdd->GetWebContents()->GetRenderViewHost();
    386   ForwardKeyEvent(render_view_host, ui::VKEY_ESCAPE);
    387 
    388   // Escape is not processed as accelerator before it's sent to web contents.
    389   EXPECT_FALSE(observer.widget_closed());
    390 
    391   content::RunAllPendingInMessageLoop();
    392 
    393   // Escape is processed as accelerator after it's sent to web contents.
    394   EXPECT_TRUE(observer.widget_closed());
    395 }
    396 
    397 #endif  // defined(OS_WIN) || (defined(USE_AURA) && defined(USE_X11))
    398