blob: 0887c8989b6db67f0de2582574eec7ea50b94f65 [file] [log] [blame]
// Copyright 2016 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.
#include "ui/views/bubble/bubble_dialog_delegate.h"
#include <stddef.h>
#include "base/i18n/rtl.h"
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/base/hit_test.h"
#include "ui/events/event_utils.h"
#include "ui/views/bubble/bubble_frame_view.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/test/test_views.h"
#include "ui/views/test/test_widget_observer.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"
namespace views {
namespace {
class TestBubbleDialogDelegateView : public BubbleDialogDelegateView {
public:
TestBubbleDialogDelegateView(View* anchor_view)
: BubbleDialogDelegateView(anchor_view, BubbleBorder::TOP_LEFT),
view_(new View()),
title_view_(nullptr),
should_show_close_button_(false) {
view_->SetFocusBehavior(FocusBehavior::ALWAYS);
AddChildView(view_);
}
~TestBubbleDialogDelegateView() override {}
// BubbleDialogDelegateView overrides:
View* GetInitiallyFocusedView() override { return view_; }
gfx::Size CalculatePreferredSize() const override {
return gfx::Size(200, 200);
}
void AddedToWidget() override {
if (title_view_)
GetBubbleFrameView()->SetTitleView(std::move(title_view_));
}
base::string16 GetWindowTitle() const override {
return base::ASCIIToUTF16("TITLE TITLE TITLE");
}
bool ShouldShowCloseButton() const override {
return should_show_close_button_;
}
void set_title_view(View* title_view) { title_view_.reset(title_view); }
void show_close_button() { should_show_close_button_ = true; }
using BubbleDialogDelegateView::SetAnchorRect;
using BubbleDialogDelegateView::GetBubbleFrameView;
private:
View* view_;
std::unique_ptr<View> title_view_;
bool should_show_close_button_;
DISALLOW_COPY_AND_ASSIGN(TestBubbleDialogDelegateView);
};
class BubbleDialogDelegateTest : public ViewsTestBase {
public:
BubbleDialogDelegateTest() {}
~BubbleDialogDelegateTest() override {}
// Creates and shows a test widget that owns its native widget.
Widget* CreateTestWidget() {
Widget* widget = new Widget();
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget->Init(params);
widget->Show();
return widget;
}
private:
DISALLOW_COPY_AND_ASSIGN(BubbleDialogDelegateTest);
};
} // namespace
TEST_F(BubbleDialogDelegateTest, CreateDelegate) {
std::unique_ptr<Widget> anchor_widget(CreateTestWidget());
TestBubbleDialogDelegateView* bubble_delegate =
new TestBubbleDialogDelegateView(anchor_widget->GetContentsView());
bubble_delegate->set_color(SK_ColorGREEN);
Widget* bubble_widget =
BubbleDialogDelegateView::CreateBubble(bubble_delegate);
EXPECT_EQ(bubble_delegate, bubble_widget->widget_delegate());
EXPECT_EQ(bubble_widget, bubble_delegate->GetWidget());
test::TestWidgetObserver bubble_observer(bubble_widget);
bubble_widget->Show();
BubbleBorder* border = bubble_delegate->GetBubbleFrameView()->bubble_border();
EXPECT_EQ(bubble_delegate->color(), border->background_color());
EXPECT_FALSE(bubble_observer.widget_closed());
bubble_widget->CloseNow();
EXPECT_TRUE(bubble_observer.widget_closed());
}
TEST_F(BubbleDialogDelegateTest, MirrorArrowInRtl) {
std::string default_locale = base::i18n::GetConfiguredLocale();
std::unique_ptr<Widget> anchor_widget(CreateTestWidget());
for (bool rtl : {false, true}) {
base::i18n::SetICUDefaultLocale(rtl ? "he" : "en");
EXPECT_EQ(rtl, base::i18n::IsRTL());
for (bool mirror : {false, true}) {
TestBubbleDialogDelegateView* bubble =
new TestBubbleDialogDelegateView(anchor_widget->GetContentsView());
bubble->set_mirror_arrow_in_rtl(mirror);
BubbleDialogDelegateView::CreateBubble(bubble);
EXPECT_EQ(rtl && mirror ? BubbleBorder::horizontal_mirror(bubble->arrow())
: bubble->arrow(),
bubble->GetBubbleFrameView()->bubble_border()->arrow());
}
}
base::i18n::SetICUDefaultLocale(default_locale);
}
TEST_F(BubbleDialogDelegateTest, CloseAnchorWidget) {
std::unique_ptr<Widget> anchor_widget(CreateTestWidget());
BubbleDialogDelegateView* bubble_delegate =
new TestBubbleDialogDelegateView(anchor_widget->GetContentsView());
// Preventing close on deactivate should not prevent closing with the anchor.
bubble_delegate->set_close_on_deactivate(false);
Widget* bubble_widget =
BubbleDialogDelegateView::CreateBubble(bubble_delegate);
EXPECT_EQ(bubble_delegate, bubble_widget->widget_delegate());
EXPECT_EQ(bubble_widget, bubble_delegate->GetWidget());
EXPECT_EQ(anchor_widget.get(), bubble_delegate->anchor_widget());
test::TestWidgetObserver bubble_observer(bubble_widget);
EXPECT_FALSE(bubble_observer.widget_closed());
bubble_widget->Show();
EXPECT_EQ(anchor_widget.get(), bubble_delegate->anchor_widget());
EXPECT_FALSE(bubble_observer.widget_closed());
// TODO(msw): Remove activation hack to prevent bookkeeping errors in:
// aura::test::TestActivationClient::OnWindowDestroyed().
std::unique_ptr<Widget> smoke_and_mirrors_widget(CreateTestWidget());
EXPECT_FALSE(bubble_observer.widget_closed());
// Ensure that closing the anchor widget also closes the bubble itself.
anchor_widget->CloseNow();
EXPECT_TRUE(bubble_observer.widget_closed());
}
// This test checks that the bubble delegate is capable to handle an early
// destruction of the used anchor view. (Animations and delayed closure of the
// bubble will call upon the anchor view to get its location).
TEST_F(BubbleDialogDelegateTest, CloseAnchorViewTest) {
// Create an anchor widget and add a view to be used as an anchor view.
std::unique_ptr<Widget> anchor_widget(CreateTestWidget());
std::unique_ptr<View> anchor_view(new View());
anchor_widget->GetContentsView()->AddChildView(anchor_view.get());
TestBubbleDialogDelegateView* bubble_delegate =
new TestBubbleDialogDelegateView(anchor_view.get());
// Prevent flakes by avoiding closing on activation changes.
bubble_delegate->set_close_on_deactivate(false);
Widget* bubble_widget =
BubbleDialogDelegateView::CreateBubble(bubble_delegate);
// Check that the anchor view is correct and set up an anchor view rect.
// Make sure that this rect will get ignored (as long as the anchor view is
// attached).
EXPECT_EQ(anchor_view.get(), bubble_delegate->GetAnchorView());
const gfx::Rect set_anchor_rect = gfx::Rect(10, 10, 100, 100);
bubble_delegate->SetAnchorRect(set_anchor_rect);
const gfx::Rect view_rect = bubble_delegate->GetAnchorRect();
EXPECT_NE(view_rect.ToString(), set_anchor_rect.ToString());
// Create the bubble.
bubble_widget->Show();
EXPECT_EQ(anchor_widget.get(), bubble_delegate->anchor_widget());
// Remove now the anchor view and make sure that the original found rect
// is still kept, so that the bubble does not jump when the view gets deleted.
anchor_widget->GetContentsView()->RemoveChildView(anchor_view.get());
anchor_view.reset();
EXPECT_EQ(NULL, bubble_delegate->GetAnchorView());
EXPECT_EQ(view_rect.ToString(), bubble_delegate->GetAnchorRect().ToString());
}
// Testing that a move of the anchor view will lead to new bubble locations.
TEST_F(BubbleDialogDelegateTest, TestAnchorRectMovesWithViewTest) {
// Create an anchor widget and add a view to be used as anchor view.
std::unique_ptr<Widget> anchor_widget(CreateTestWidget());
TestBubbleDialogDelegateView* bubble_delegate =
new TestBubbleDialogDelegateView(anchor_widget->GetContentsView());
BubbleDialogDelegateView::CreateBubble(bubble_delegate);
anchor_widget->GetContentsView()->SetBounds(10, 10, 100, 100);
const gfx::Rect view_rect = bubble_delegate->GetAnchorRect();
anchor_widget->GetContentsView()->SetBounds(20, 10, 100, 100);
const gfx::Rect view_rect_2 = bubble_delegate->GetAnchorRect();
EXPECT_NE(view_rect.ToString(), view_rect_2.ToString());
}
TEST_F(BubbleDialogDelegateTest, ResetAnchorWidget) {
std::unique_ptr<Widget> anchor_widget(CreateTestWidget());
BubbleDialogDelegateView* bubble_delegate =
new TestBubbleDialogDelegateView(anchor_widget->GetContentsView());
// Make sure the bubble widget is parented to a widget other than the anchor
// widget so that closing the anchor widget does not close the bubble widget.
std::unique_ptr<Widget> parent_widget(CreateTestWidget());
bubble_delegate->set_parent_window(parent_widget->GetNativeView());
// Preventing close on deactivate should not prevent closing with the parent.
bubble_delegate->set_close_on_deactivate(false);
Widget* bubble_widget =
BubbleDialogDelegateView::CreateBubble(bubble_delegate);
EXPECT_EQ(bubble_delegate, bubble_widget->widget_delegate());
EXPECT_EQ(bubble_widget, bubble_delegate->GetWidget());
EXPECT_EQ(anchor_widget.get(), bubble_delegate->anchor_widget());
test::TestWidgetObserver bubble_observer(bubble_widget);
EXPECT_FALSE(bubble_observer.widget_closed());
// Showing and hiding the bubble widget should have no effect on its anchor.
bubble_widget->Show();
EXPECT_EQ(anchor_widget.get(), bubble_delegate->anchor_widget());
bubble_widget->Hide();
EXPECT_EQ(anchor_widget.get(), bubble_delegate->anchor_widget());
// Ensure that closing the anchor widget clears the bubble's reference to that
// anchor widget, but the bubble itself does not close.
anchor_widget->CloseNow();
EXPECT_NE(anchor_widget.get(), bubble_delegate->anchor_widget());
EXPECT_FALSE(bubble_observer.widget_closed());
// TODO(msw): Remove activation hack to prevent bookkeeping errors in:
// aura::test::TestActivationClient::OnWindowDestroyed().
std::unique_ptr<Widget> smoke_and_mirrors_widget(CreateTestWidget());
EXPECT_FALSE(bubble_observer.widget_closed());
// Ensure that closing the parent widget also closes the bubble itself.
parent_widget->CloseNow();
EXPECT_TRUE(bubble_observer.widget_closed());
}
TEST_F(BubbleDialogDelegateTest, InitiallyFocusedView) {
std::unique_ptr<Widget> anchor_widget(CreateTestWidget());
BubbleDialogDelegateView* bubble_delegate =
new TestBubbleDialogDelegateView(anchor_widget->GetContentsView());
Widget* bubble_widget =
BubbleDialogDelegateView::CreateBubble(bubble_delegate);
bubble_widget->Show();
EXPECT_EQ(bubble_delegate->GetInitiallyFocusedView(),
bubble_widget->GetFocusManager()->GetFocusedView());
bubble_widget->CloseNow();
}
TEST_F(BubbleDialogDelegateTest, NonClientHitTest) {
std::unique_ptr<Widget> anchor_widget(CreateTestWidget());
TestBubbleDialogDelegateView* bubble_delegate =
new TestBubbleDialogDelegateView(anchor_widget->GetContentsView());
BubbleDialogDelegateView::CreateBubble(bubble_delegate);
BubbleFrameView* frame = bubble_delegate->GetBubbleFrameView();
const int border = frame->bubble_border()->GetBorderThickness();
struct {
const int point;
const int hit;
} cases[] = {
{border, HTNOWHERE}, {border + 60, HTCLIENT}, {1000, HTNOWHERE},
};
for (size_t i = 0; i < arraysize(cases); ++i) {
gfx::Point point(cases[i].point, cases[i].point);
EXPECT_EQ(cases[i].hit, frame->NonClientHitTest(point))
<< " with border: " << border << ", at point " << cases[i].point;
}
}
TEST_F(BubbleDialogDelegateTest, VisibleWhenAnchorWidgetBoundsChanged) {
std::unique_ptr<Widget> anchor_widget(CreateTestWidget());
BubbleDialogDelegateView* bubble_delegate =
new TestBubbleDialogDelegateView(anchor_widget->GetContentsView());
Widget* bubble_widget =
BubbleDialogDelegateView::CreateBubble(bubble_delegate);
test::TestWidgetObserver bubble_observer(bubble_widget);
EXPECT_FALSE(bubble_observer.widget_closed());
bubble_widget->Show();
EXPECT_TRUE(bubble_widget->IsVisible());
anchor_widget->SetBounds(gfx::Rect(10, 10, 100, 100));
EXPECT_TRUE(bubble_widget->IsVisible());
}
// Test that setting WidgetDelegate::set_can_activate() to false makes the
// widget created via BubbleDialogDelegateView::CreateBubble() not activatable.
TEST_F(BubbleDialogDelegateTest, NotActivatable) {
std::unique_ptr<Widget> anchor_widget(CreateTestWidget());
BubbleDialogDelegateView* bubble_delegate =
new TestBubbleDialogDelegateView(anchor_widget->GetContentsView());
bubble_delegate->set_can_activate(false);
Widget* bubble_widget =
BubbleDialogDelegateView::CreateBubble(bubble_delegate);
bubble_widget->Show();
EXPECT_FALSE(bubble_widget->CanActivate());
}
TEST_F(BubbleDialogDelegateTest, CloseMethods) {
{
std::unique_ptr<Widget> anchor_widget(CreateTestWidget());
BubbleDialogDelegateView* bubble_delegate =
new TestBubbleDialogDelegateView(anchor_widget->GetContentsView());
bubble_delegate->set_close_on_deactivate(true);
Widget* bubble_widget =
BubbleDialogDelegateView::CreateBubble(bubble_delegate);
bubble_widget->Show();
anchor_widget->Activate();
EXPECT_TRUE(bubble_widget->IsClosed());
}
{
std::unique_ptr<Widget> anchor_widget(CreateTestWidget());
BubbleDialogDelegateView* bubble_delegate =
new TestBubbleDialogDelegateView(anchor_widget->GetContentsView());
Widget* bubble_widget =
BubbleDialogDelegateView::CreateBubble(bubble_delegate);
bubble_widget->Show();
ui::KeyEvent escape_event(ui::ET_KEY_PRESSED, ui::VKEY_ESCAPE, ui::EF_NONE);
bubble_widget->OnKeyEvent(&escape_event);
EXPECT_TRUE(bubble_widget->IsClosed());
}
{
std::unique_ptr<Widget> anchor_widget(CreateTestWidget());
TestBubbleDialogDelegateView* bubble_delegate =
new TestBubbleDialogDelegateView(anchor_widget->GetContentsView());
Widget* bubble_widget =
BubbleDialogDelegateView::CreateBubble(bubble_delegate);
bubble_widget->Show();
BubbleFrameView* frame_view = bubble_delegate->GetBubbleFrameView();
Button* close_button = frame_view->close_;
ASSERT_TRUE(close_button);
frame_view->ButtonPressed(
close_button,
ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE));
EXPECT_TRUE(bubble_widget->IsClosed());
}
}
TEST_F(BubbleDialogDelegateTest, CustomTitle) {
std::unique_ptr<Widget> anchor_widget(CreateTestWidget());
TestBubbleDialogDelegateView* bubble_delegate =
new TestBubbleDialogDelegateView(anchor_widget->GetContentsView());
constexpr int kTitlePreferredHeight = 20;
View* title_view = new StaticSizedView(gfx::Size(10, kTitlePreferredHeight));
bubble_delegate->set_title_view(title_view);
Widget* bubble_widget =
BubbleDialogDelegateView::CreateBubble(bubble_delegate);
bubble_widget->Show();
BubbleFrameView* bubble_frame = static_cast<BubbleFrameView*>(
bubble_widget->non_client_view()->frame_view());
EXPECT_EQ(title_view, bubble_frame->title());
EXPECT_EQ(bubble_frame, title_view->parent());
// Title takes up the whole bubble width when there's no icon or close button.
EXPECT_EQ(bubble_delegate->width(), title_view->size().width());
EXPECT_EQ(kTitlePreferredHeight, title_view->size().height());
bubble_delegate->show_close_button();
bubble_frame->ResetWindowControls();
bubble_frame->Layout();
Button* close_button = bubble_frame->GetCloseButtonForTest();
// Title moves over for the close button.
EXPECT_EQ(close_button->x() - LayoutProvider::Get()->GetDistanceMetric(
DISTANCE_CLOSE_BUTTON_MARGIN),
title_view->bounds().right());
}
} // namespace views