blob: 27e23fb2d953cc8ded6a3dc1122fc1129fca96bb [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/web_contents_modal_dialog_manager_views_mac.h"
#import <Cocoa/Cocoa.h>
#include "base/location.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/test/test_timeouts.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/views/scoped_macviews_browser_mode.h"
#include "components/constrained_window/constrained_window_views.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"
#include "ui/views/window/dialog_delegate.h"
#include "url/gurl.h"
using views::Widget;
namespace {
class WebContentsModalDialogManagerViewsMacTest : public InProcessBrowserTest,
public views::WidgetObserver {
public:
WebContentsModalDialogManagerViewsMacTest() {}
Widget* ShowViewsDialogOn(int web_contents_index, bool activate) {
if (activate)
browser()->tab_strip_model()->ActivateTabAt(web_contents_index, true);
content::WebContents* web_contents =
browser()->tab_strip_model()->GetWebContentsAt(web_contents_index);
DCHECK(web_contents);
// Show a dialog as a constrained window modal to the current tab.
Widget* widget = constrained_window::ShowWebModalDialogViews(new TestDialog,
web_contents);
widget->AddObserver(this);
return widget;
}
void WaitForClose() {
last_destroyed_ = nullptr;
base::RunLoop run_loop;
run_loop_ = &run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), TestTimeouts::action_timeout());
run_loop.Run();
run_loop_ = nullptr;
}
// views::WidgetObserver:
void OnWidgetDestroying(views::Widget* widget) override {
destroy_count_++;
widget->RemoveObserver(this);
last_destroyed_ = widget;
if (run_loop_)
run_loop_->Quit();
}
class TestDialog : public views::DialogDelegateView {
public:
TestDialog() {}
// WidgetDelegate:
ui::ModalType GetModalType() const override { return ui::MODAL_TYPE_CHILD; }
private:
DISALLOW_COPY_AND_ASSIGN(TestDialog);
};
protected:
int destroy_count_ = 0;
Widget* last_destroyed_ = nullptr;
base::RunLoop* run_loop_ = nullptr;
private:
test::ScopedMacViewsBrowserMode cocoa_browser_mode_{false};
DISALLOW_COPY_AND_ASSIGN(WebContentsModalDialogManagerViewsMacTest);
};
} // namespace
// Basic test for showing and dismissing the dialog in a single tab.
IN_PROC_BROWSER_TEST_F(WebContentsModalDialogManagerViewsMacTest, Basic) {
NSArray* children = [browser()->window()->GetNativeWindow() childWindows];
EXPECT_EQ(0u, [children count]);
Widget* dialog = ShowViewsDialogOn(0, true);
EXPECT_TRUE(dialog->IsVisible());
// The browser window will get two child windows: a transparent overlay, and
// the views::Widget dialog. Ensure the dialog is on top.
children = [browser()->window()->GetNativeWindow() childWindows];
EXPECT_EQ(2u, [children count]);
EXPECT_EQ(dialog->GetNativeWindow(), [children objectAtIndex:1]);
// Toolkit-views dialogs use GetWidget()->Close() to dismiss themselves.
dialog->Close();
EXPECT_EQ(0, destroy_count_);
WaitForClose();
EXPECT_EQ(dialog, last_destroyed_);
EXPECT_EQ(1, destroy_count_);
}
// Test for some code injection or third party tool invoking -[NSWindow close].
IN_PROC_BROWSER_TEST_F(WebContentsModalDialogManagerViewsMacTest, NativeClose) {
NSArray* children = [browser()->window()->GetNativeWindow() childWindows];
EXPECT_EQ(0u, [children count]);
Widget* dialog = ShowViewsDialogOn(0, true);
EXPECT_TRUE(dialog->IsVisible());
// Chrome code never does this, but ensure the codepaths are robust to it.
// Invoking this will jump to SingleWebContentsDialogManagerViewsMac::
// OnWidgetDestroying() without first encountering OnWidgetClosing().
[dialog->GetNativeWindow() close];
EXPECT_EQ(1, destroy_count_);
EXPECT_EQ(dialog, last_destroyed_);
}
// Test showing dialogs in two tabs, switch tabs, then close the background tab,
// then close the browser window.
IN_PROC_BROWSER_TEST_F(WebContentsModalDialogManagerViewsMacTest,
TwoDialogsThenCloseTabs) {
// Add a second tab, then show a dialog on each while the tab is active.
AddTabAtIndex(1, GURL("about:blank"), ui::PAGE_TRANSITION_LINK);
Widget* dialog1 = ShowViewsDialogOn(1, true);
Widget* dialog0 = ShowViewsDialogOn(0, true);
// Tab 0 is active, so only |dialog0| should become visible.
EXPECT_TRUE(dialog0->IsVisible());
EXPECT_FALSE(dialog1->IsVisible());
// The inactive, second dialog will also be fully transparent.
EXPECT_EQ(0.0, [dialog1->GetNativeWindow() alphaValue]);
EXPECT_EQ(1.0, [dialog0->GetNativeWindow() alphaValue]);
// Activate the second tab, visibility should switch.
browser()->tab_strip_model()->ActivateTabAt(1, true);
EXPECT_EQ(1.0, [dialog1->GetNativeWindow() alphaValue]);
EXPECT_EQ(0.0, [dialog0->GetNativeWindow() alphaValue]);
EXPECT_FALSE(dialog0->IsVisible());
EXPECT_TRUE(dialog1->IsVisible());
// Close the background tab.
browser()->tab_strip_model()->CloseWebContentsAt(
0, TabStripModel::CLOSE_USER_GESTURE);
// Close is asynchronous, so nothing happens until a run loop is spun up.
EXPECT_EQ(0, destroy_count_);
WaitForClose();
EXPECT_EQ(dialog0, last_destroyed_);
EXPECT_EQ(1, destroy_count_);
// Close the window. But first close all tabs. Otherwise before-unload
// handlers have an opportunity to run, which is asynchronous.
browser()->tab_strip_model()->CloseAllTabs();
// Since the parent NSWindow goes away, close happens immediately.
browser()->window()->Close();
EXPECT_EQ(dialog1, last_destroyed_);
EXPECT_EQ(2, destroy_count_);
}
// Test showing in a dialog in an inactive tab. For these, the WebContentsView
// will be detached from the view hierarchy.
IN_PROC_BROWSER_TEST_F(WebContentsModalDialogManagerViewsMacTest,
DialogInBackgroundTab) {
// Add a second tab. It should start active.
AddTabAtIndex(1, GURL("about:blank"), ui::PAGE_TRANSITION_LINK);
EXPECT_TRUE(browser()->tab_strip_model()->IsTabSelected(1));
// Showing shouldn't change the active tab, and the dialog shouldn't be shown
// at all yet.
Widget* dialog0 = ShowViewsDialogOn(0, false);
EXPECT_TRUE(browser()->tab_strip_model()->IsTabSelected(1));
EXPECT_FALSE(dialog0->IsVisible());
// But switching to the tab should show (and animate) it.
browser()->tab_strip_model()->ActivateTabAt(0, true);
EXPECT_TRUE(browser()->tab_strip_model()->IsTabSelected(0));
EXPECT_TRUE(dialog0->IsVisible());
// Leave the dialog open for a change to ensure shutdown is clean.
}