blob: 2b51a6aef0942e16eca7066fc2e2767f9bb175a1 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/views/controls/webview/web_dialog_view.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/profile.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/browser/ui/views/content_test_utils.h"
#include "chrome/browser/ui/webui/chrome_web_contents_handler.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_utils.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/views/view_tracker.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"
#include "ui/web_dialogs/test/test_web_dialog_delegate.h"
#if BUILDFLAG(IS_MAC)
#include "base/mac/mac_util.h"
#endif
namespace {
// Initial size of WebDialog for SizeWindow test case. Note the height must be
// at least 59 on Windows.
const int kInitialWidth = 60;
const int kInitialHeight = 60;
class WidgetResizeWaiter : public views::WidgetObserver {
public:
explicit WidgetResizeWaiter(views::Widget* widget) {
old_size_ = widget->GetWindowBoundsInScreen().size();
observation_.Observe(widget);
}
void Wait() { run_loop_.Run(); }
void OnWidgetBoundsChanged(views::Widget* widget,
const gfx::Rect& bounds) override {
if (bounds.size() != old_size_) {
run_loop_.Quit();
}
}
private:
base::ScopedObservation<views::Widget, views::WidgetObserver> observation_{
this};
gfx::Size old_size_;
base::RunLoop run_loop_;
};
} // namespace
class WebDialogBrowserTest : public InProcessBrowserTest {
public:
WebDialogBrowserTest() = default;
WebDialogBrowserTest(const WebDialogBrowserTest&) = delete;
WebDialogBrowserTest& operator=(const WebDialogBrowserTest&) = delete;
// content::BrowserTestBase:
void SetUpOnMainThread() override;
protected:
void SimulateEscapeKey();
bool was_view_deleted() const { return !view_tracker_.view(); }
raw_ptr<views::WebDialogView, DanglingUntriaged> view_ = nullptr;
bool web_dialog_delegate_destroyed_ = false;
raw_ptr<ui::test::TestWebDialogDelegate, DanglingUntriaged> delegate_ =
nullptr;
private:
views::ViewTracker view_tracker_;
};
void WebDialogBrowserTest::SetUpOnMainThread() {
ui::test::TestWebDialogDelegate* delegate =
new ui::test::TestWebDialogDelegate(GURL(chrome::kChromeUIChromeURLsURL));
delegate->set_size(kInitialWidth, kInitialHeight);
delegate->SetDeleteOnClosedAndObserve(&web_dialog_delegate_destroyed_);
// Store the delegate so that we can update ShouldCloseDialogOnEscape().
delegate_ = delegate;
auto view = std::make_unique<views::WebDialogView>(
browser()->profile(), delegate,
std::make_unique<ChromeWebContentsHandler>());
view->SetOwnedByWidget(views::WidgetDelegate::OwnedByWidgetPassKey());
gfx::NativeView parent_view =
browser()->tab_strip_model()->GetActiveWebContents()->GetNativeView();
view_ = view.get();
view_tracker_.SetView(view_);
auto* widget =
views::Widget::CreateWindowWithParent(std::move(view), parent_view);
widget->Show();
}
void WebDialogBrowserTest::SimulateEscapeKey() {
ui::KeyEvent escape_event(ui::EventType::kKeyPressed, ui::VKEY_ESCAPE,
ui::EF_NONE);
if (view_->GetFocusManager()->OnKeyEvent(escape_event)) {
ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
view_->GetWidget()->GetNativeWindow(), ui::VKEY_ESCAPE, false, false,
false, false));
}
}
// Windows has some issues resizing windows. An off by one problem, and a
// minimum size that seems too big. See http://crbug.com/52602.
#if BUILDFLAG(IS_WIN)
#define MAYBE_SizeWindow DISABLED_SizeWindow
#else
#define MAYBE_SizeWindow SizeWindow
#endif
IN_PROC_BROWSER_TEST_F(WebDialogBrowserTest, MAYBE_SizeWindow) {
#if BUILDFLAG(IS_MAC)
// On macOS, the new mechanism for sheets, which are used for window modals
// like this dialog, always centers them within the parent window regardless
// of the requested origin. The size is still honored.
bool centered_in_window = true;
#else
bool centered_in_window = false;
#endif
gfx::Rect set_bounds = view_->GetWidget()->GetClientAreaBoundsInScreen();
gfx::Rect actual_bounds, rwhv_bounds;
// Bigger than the default in both dimensions.
set_bounds.set_width(400);
set_bounds.set_height(300);
auto check_bounds = [&](const gfx::Rect& set, const gfx::Rect& actual) {
if (centered_in_window) {
gfx::Rect expected = browser()->window()->GetBounds();
expected.ClampToCenteredSize(set.size());
EXPECT_EQ(expected, actual);
} else {
EXPECT_EQ(set, actual);
}
};
// WebDialogView ignores the WebContents* |source| argument to
// SetContentsBounds. We could pass view_->web_contents(), but it's not
// relevant for the test.
{
WidgetResizeWaiter waiter(view_->GetWidget());
view_->SetContentsBounds(nullptr, set_bounds);
waiter.Wait();
}
actual_bounds = view_->GetWidget()->GetClientAreaBoundsInScreen();
check_bounds(set_bounds, actual_bounds);
rwhv_bounds =
view_->web_contents()->GetRenderWidgetHostView()->GetViewBounds();
EXPECT_LT(0, rwhv_bounds.width());
EXPECT_LT(0, rwhv_bounds.height());
EXPECT_GE(set_bounds.width(), rwhv_bounds.width());
EXPECT_GE(set_bounds.height(), rwhv_bounds.height());
// Larger in one dimension and smaller in the other.
set_bounds.set_width(550);
set_bounds.set_height(250);
{
WidgetResizeWaiter waiter(view_->GetWidget());
view_->SetContentsBounds(nullptr, set_bounds);
waiter.Wait();
}
actual_bounds = view_->GetWidget()->GetClientAreaBoundsInScreen();
check_bounds(set_bounds, actual_bounds);
rwhv_bounds =
view_->web_contents()->GetRenderWidgetHostView()->GetViewBounds();
EXPECT_LT(0, rwhv_bounds.width());
EXPECT_LT(0, rwhv_bounds.height());
EXPECT_GE(set_bounds.width(), rwhv_bounds.width());
EXPECT_GE(set_bounds.height(), rwhv_bounds.height());
// Get very small.
const gfx::Size min_size = view_->GetWidget()->GetMinimumSize();
EXPECT_LT(0, min_size.width());
EXPECT_LT(0, min_size.height());
set_bounds.set_size(min_size);
{
WidgetResizeWaiter waiter(view_->GetWidget());
view_->SetContentsBounds(nullptr, set_bounds);
waiter.Wait();
}
actual_bounds = view_->GetWidget()->GetClientAreaBoundsInScreen();
check_bounds(set_bounds, actual_bounds);
rwhv_bounds =
view_->web_contents()->GetRenderWidgetHostView()->GetViewBounds();
EXPECT_LT(0, rwhv_bounds.width());
EXPECT_LT(0, rwhv_bounds.height());
EXPECT_GE(set_bounds.width(), rwhv_bounds.width());
EXPECT_GE(set_bounds.height(), rwhv_bounds.height());
// Check to make sure we can't get to 0x0. First expand beyond the minimum
// size that was set above so that TestWebDialogView has a change to pick up.
set_bounds.set_height(250);
{
WidgetResizeWaiter waiter(view_->GetWidget());
view_->SetContentsBounds(nullptr, set_bounds);
waiter.Wait();
}
actual_bounds = view_->GetWidget()->GetClientAreaBoundsInScreen();
check_bounds(set_bounds, actual_bounds);
// Now verify that attempts to re-size to 0x0 enforces the minimum size.
set_bounds.set_width(0);
set_bounds.set_height(0);
{
WidgetResizeWaiter waiter(view_->GetWidget());
view_->SetContentsBounds(nullptr, set_bounds);
waiter.Wait();
}
actual_bounds = view_->GetWidget()->GetClientAreaBoundsInScreen();
EXPECT_EQ(min_size, actual_bounds.size());
// And that the render view is also non-zero.
rwhv_bounds =
view_->web_contents()->GetRenderWidgetHostView()->GetViewBounds();
EXPECT_LT(0, rwhv_bounds.width());
EXPECT_LT(0, rwhv_bounds.height());
// WebDialogView::CanClose() returns true only after before-unload handlers
// have run (or the dialog has none and gets fast-closed via
// RenderViewHostImpl::ClosePageIgnoringUnloadEvents which is the case here).
// Close via WebContents for more authentic coverage (vs Widget::CloseNow()).
EXPECT_FALSE(web_dialog_delegate_destroyed_);
view_->web_contents()->Close();
EXPECT_TRUE(web_dialog_delegate_destroyed_);
// The close of the actual widget should happen asynchronously.
EXPECT_FALSE(was_view_deleted());
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(was_view_deleted());
}
// Test that closing the parent of a window-modal web dialog properly destroys
// the dialog and delegate.
IN_PROC_BROWSER_TEST_F(WebDialogBrowserTest, CloseParentWindow) {
// Open a second browser window so we don't trigger shutdown.
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(url::kAboutBlankURL), WindowOpenDisposition::NEW_WINDOW,
ui_test_utils::BROWSER_TEST_NO_WAIT);
// TestWebDialogDelegate defaults to window-modal, so closing the browser
// Window (as opposed to closing merely the tab) should close the dialog.
EXPECT_EQ(ui::mojom::ModalType::kWindow,
view_->GetWidget()->widget_delegate()->GetModalType());
// Close the parent window. Tear down may happen asynchronously.
EXPECT_FALSE(web_dialog_delegate_destroyed_);
EXPECT_FALSE(was_view_deleted());
browser()->window()->Close();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(web_dialog_delegate_destroyed_);
EXPECT_TRUE(was_view_deleted());
}
// Tests the Escape key behavior when ShouldCloseDialogOnEscape() is enabled.
IN_PROC_BROWSER_TEST_F(WebDialogBrowserTest, CloseDialogOnEscapeEnabled) {
// Open a second browser window so we don't trigger shutdown.
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(url::kAboutBlankURL), WindowOpenDisposition::NEW_WINDOW,
ui_test_utils::BROWSER_TEST_NO_WAIT);
// If ShouldCloseDialogOnEscape() is true, pressing Escape should close the
// dialog.
delegate_->SetCloseOnEscape(true);
SimulateEscapeKey();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(web_dialog_delegate_destroyed_);
EXPECT_TRUE(was_view_deleted());
}
// Tests the Escape key behavior when ShouldCloseDialogOnEscape() is disabled.
IN_PROC_BROWSER_TEST_F(WebDialogBrowserTest, CloseDialogOnEscapeDisabled) {
// Open a second browser window so we don't trigger shutdown.
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(url::kAboutBlankURL), WindowOpenDisposition::NEW_WINDOW,
ui_test_utils::BROWSER_TEST_NO_WAIT);
// If ShouldCloseDialogOnEscape() is false, pressing Escape does nothing.
delegate_->SetCloseOnEscape(false);
SimulateEscapeKey();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(web_dialog_delegate_destroyed_);
EXPECT_FALSE(was_view_deleted());
}
// Test that key event is translated to a text input properly.
IN_PROC_BROWSER_TEST_F(WebDialogBrowserTest, TextInputViaKeyEvent) {
TestTextInputViaKeyEvent(view_->web_contents());
}