blob: b1b5cf6299f9224a3e0bf67a1f5b3ad55ed9fda5 [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 <algorithm>
#include <memory>
#include <optional>
#include <set>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/test/gtest_util.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/accessibility/accessibility_features.h"
#include "ui/accessibility/ax_node_data.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/base/hit_test.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/base/mojom/window_show_state.mojom.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/color/color_provider_key.h"
#include "ui/color/color_provider_manager.h"
#include "ui/color/color_recipe.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/compositor/test/draw_waiter_for_test.h"
#include "ui/events/event_observer.h"
#include "ui/events/event_utils.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/native_theme/native_theme.h"
#include "ui/native_theme/test_native_theme.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/buildflags.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/event_monitor.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/test/configurable_test_frame_view.h"
#include "ui/views/test/mock_drag_controller.h"
#include "ui/views/test/mock_native_widget.h"
#include "ui/views/test/native_widget_factory.h"
#include "ui/views/test/test_views.h"
#include "ui/views/test/test_widget_observer.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/view_test_api.h"
#include "ui/views/views_test_suite.h"
#include "ui/views/widget/native_widget_delegate.h"
#include "ui/views/widget/native_widget_private.h"
#include "ui/views/widget/root_view.h"
#include "ui/views/widget/unique_widget_ptr.h"
#include "ui/views/widget/widget_delegate.h"
#include "ui/views/widget/widget_deletion_observer.h"
#include "ui/views/widget/widget_interactive_uitest_utils.h"
#include "ui/views/widget/widget_removals_observer.h"
#include "ui/views/widget/widget_utils.h"
#include "ui/views/window/dialog_delegate.h"
#include "ui/views/window/native_frame_view.h"
#if defined(USE_AURA)
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/view_prop.h"
#include "ui/views/test/test_platform_native_widget.h"
#include "ui/views/widget/native_widget_aura.h"
#include "ui/wm/core/base_focus_rules.h"
#include "ui/wm/core/focus_controller.h"
#include "ui/wm/core/shadow_controller.h"
#include "ui/wm/core/shadow_controller_delegate.h"
#endif
#if BUILDFLAG(IS_WIN)
#include "ui/base/win/window_event_target.h"
#include "ui/views/win/hwnd_util.h"
#endif
#if BUILDFLAG(IS_MAC)
#include "base/mac/mac_util.h"
#endif
#if BUILDFLAG(ENABLE_DESKTOP_AURA)
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#endif
namespace views::test {
namespace {
using ::testing::_;
using ::testing::IsEmpty;
using ::testing::Not;
// TODO(tdanderson): This utility function is used in different unittest
// files. Move to a common location to avoid
// repeated code.
gfx::Point ConvertPointFromWidgetToView(View* view, const gfx::Point& p) {
gfx::Point tmp(p);
View::ConvertPointToTarget(view->GetWidget()->GetRootView(), view, &tmp);
return tmp;
}
std::unique_ptr<ui::test::EventGenerator> CreateEventGenerator(
gfx::NativeWindow root_window,
gfx::NativeWindow target_window) {
auto generator =
std::make_unique<ui::test::EventGenerator>(root_window, target_window);
return generator;
}
// Convenience to make constructing a GestureEvent simpler.
ui::GestureEvent CreateTestGestureEvent(ui::EventType type, int x, int y) {
return ui::GestureEvent(x, y, 0, base::TimeTicks(),
ui::GestureEventDetails(type));
}
ui::GestureEvent CreateTestGestureEvent(const ui::GestureEventDetails& details,
int x,
int y) {
return ui::GestureEvent(x, y, 0, base::TimeTicks(), details);
}
std::unique_ptr<NativeFrameView> CreateMinimumSizeFrameView(Widget* frame) {
auto frame_view = std::make_unique<ConfigurableTestFrameView>(frame);
frame_view->set_minimum_size(gfx::Size(300, 400));
return std::move(frame_view);
}
class TestWidgetRemovalsObserver : public WidgetRemovalsObserver {
public:
TestWidgetRemovalsObserver() = default;
TestWidgetRemovalsObserver(const TestWidgetRemovalsObserver&) = delete;
TestWidgetRemovalsObserver& operator=(const TestWidgetRemovalsObserver&) =
delete;
~TestWidgetRemovalsObserver() override = default;
void OnWillRemoveView(Widget* widget, View* view) override {
removed_views_.insert(view);
}
bool DidRemoveView(View* view) {
return removed_views_.find(view) != removed_views_.end();
}
private:
std::set<raw_ptr<View, SetExperimental>> removed_views_;
};
} // namespace
class WidgetTestBubbleDialogDelegateView : public BubbleDialogDelegateView {
METADATA_HEADER(WidgetTestBubbleDialogDelegateView, BubbleDialogDelegateView)
public:
explicit WidgetTestBubbleDialogDelegateView(View* anchor)
: BubbleDialogDelegateView(anchor, BubbleBorder::NONE) {}
~WidgetTestBubbleDialogDelegateView() override = default;
bool ShouldShowCloseButton() const override {
reset_controls_called_ = true;
return true;
}
mutable bool reset_controls_called_ = false;
};
BEGIN_METADATA(WidgetTestBubbleDialogDelegateView)
END_METADATA
// A view that keeps track of the events it receives, and consumes all scroll
// gesture events and ui::EventType::kScroll events.
class ScrollableEventCountView : public EventCountView {
METADATA_HEADER(ScrollableEventCountView, EventCountView)
public:
ScrollableEventCountView() = default;
ScrollableEventCountView(const ScrollableEventCountView&) = delete;
ScrollableEventCountView& operator=(const ScrollableEventCountView&) = delete;
~ScrollableEventCountView() override = default;
private:
// Overridden from ui::EventHandler:
void OnGestureEvent(ui::GestureEvent* event) override {
EventCountView::OnGestureEvent(event);
switch (event->type()) {
case ui::EventType::kGestureScrollBegin:
case ui::EventType::kGestureScrollUpdate:
case ui::EventType::kGestureScrollEnd:
case ui::EventType::kScrollFlingStart:
event->SetHandled();
break;
default:
break;
}
}
void OnScrollEvent(ui::ScrollEvent* event) override {
EventCountView::OnScrollEvent(event);
if (event->type() == ui::EventType::kScroll) {
event->SetHandled();
}
}
};
BEGIN_METADATA(ScrollableEventCountView)
END_METADATA
// An event handler that simply keeps a count of the different types of events
// it receives.
class EventCountHandler : public ui::EventHandler {
public:
EventCountHandler() = default;
EventCountHandler(const EventCountHandler&) = delete;
EventCountHandler& operator=(const EventCountHandler&) = delete;
~EventCountHandler() override = default;
int GetEventCount(ui::EventType type) { return event_count_[type]; }
void ResetCounts() { event_count_.clear(); }
protected:
// Overridden from ui::EventHandler:
void OnEvent(ui::Event* event) override {
RecordEvent(*event);
ui::EventHandler::OnEvent(event);
}
private:
void RecordEvent(const ui::Event& event) { ++event_count_[event.type()]; }
std::map<ui::EventType, int> event_count_;
};
TEST_F(WidgetTest, WidgetInitParams) {
// Widgets are not transparent by default.
Widget::InitParams init1(Widget::InitParams::CLIENT_OWNS_WIDGET);
EXPECT_EQ(Widget::InitParams::WindowOpacity::kInferred, init1.opacity);
}
// Tests that the internal name is propagated through widget initialization to
// the native widget and back.
class WidgetWithCustomParamsTest : public WidgetTest {
public:
using InitFunction = base::RepeatingCallback<void(Widget::InitParams*)>;
void SetInitFunction(const InitFunction& init) { init_ = std::move(init); }
Widget::InitParams CreateParams(Widget::InitParams::Ownership ownership,
Widget::InitParams::Type type) override {
Widget::InitParams params = WidgetTest::CreateParams(ownership, type);
DCHECK(init_) << "If you don't need an init function, use WidgetTest";
init_.Run(&params);
return params;
}
private:
InitFunction init_;
};
TEST_F(WidgetWithCustomParamsTest, NamePropagatedFromParams) {
SetInitFunction(base::BindLambdaForTesting(
[](Widget::InitParams* params) { params->name = "MyWidget"; }));
std::unique_ptr<Widget> widget =
CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET);
EXPECT_EQ("MyWidget", widget->native_widget_private()->GetName());
EXPECT_EQ("MyWidget", widget->GetName());
}
TEST_F(WidgetWithCustomParamsTest, NamePropagatedFromDelegate) {
WidgetDelegate delegate;
delegate.set_internal_name("Foobar");
SetInitFunction(base::BindLambdaForTesting(
[&](Widget::InitParams* params) { params->delegate = &delegate; }));
std::unique_ptr<Widget> widget =
CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET);
EXPECT_EQ(delegate.internal_name(),
widget->native_widget_private()->GetName());
EXPECT_EQ(delegate.internal_name(), widget->GetName());
}
// Test that Widget::InitParams::autosize allows widget to
// automatically resize when content view size changes.
TEST_F(WidgetWithCustomParamsTest, Autosize) {
SetInitFunction(base::BindLambdaForTesting(
[](Widget::InitParams* params) { params->autosize = true; }));
std::unique_ptr<Widget> widget =
CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET);
auto* view = widget->SetContentsView(std::make_unique<views::View>());
widget->Show();
constexpr gfx::Size kInitialSize(100, 100);
view->SetPreferredSize(kInitialSize);
// RunScheduledLayout() is needed due to widget auto-resize.
views::test::RunScheduledLayout(widget.get());
const gfx::Size starting_size = widget->GetWindowBoundsInScreen().size();
constexpr gfx::Size kDelta(50, 50);
view->SetPreferredSize(kInitialSize + kDelta);
// RunScheduledLayout() is needed due to widget auto-resize.
views::test::RunScheduledLayout(widget.get());
const gfx::Size ending_size = widget->GetWindowBoundsInScreen().size();
EXPECT_EQ(ending_size, starting_size + kDelta);
}
namespace {
class ViewWithClassName : public View {
METADATA_HEADER(ViewWithClassName, View)
};
BEGIN_METADATA(ViewWithClassName)
END_METADATA
} // namespace
TEST_F(WidgetWithCustomParamsTest, NamePropagatedFromContentsViewClassName) {
WidgetDelegate delegate;
auto view = std::make_unique<ViewWithClassName>();
auto* contents = delegate.SetContentsView(std::move(view));
SetInitFunction(base::BindLambdaForTesting(
[&](Widget::InitParams* params) { params->delegate = &delegate; }));
std::unique_ptr<Widget> widget =
CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET);
EXPECT_EQ(contents->GetClassName(),
widget->native_widget_private()->GetName());
EXPECT_EQ(contents->GetClassName(), widget->GetName());
}
namespace {
class TestView : public View {
METADATA_HEADER(TestView, View)
public:
~TestView() override = default;
void OnThemeChanged() override {
View::OnThemeChanged();
auto* native_theme = GetNativeTheme();
if (native_theme && native_theme->user_color()) {
user_color_ = *native_theme->user_color();
}
}
SkColor user_color() const { return user_color_; }
private:
SkColor user_color_ = SK_ColorWHITE;
};
BEGIN_METADATA(TestView)
END_METADATA
} // namespace
class WidgetColorModeTest : public WidgetTest {
public:
static constexpr SkColor kLightColor = SK_ColorWHITE;
static constexpr SkColor kDarkColor = SK_ColorBLACK;
WidgetColorModeTest() = default;
~WidgetColorModeTest() override = default;
void SetUp() override {
WidgetTest::SetUp();
// Setup color provider for the ui::kColorSysPrimary color.
ui::ColorProviderManager& manager =
ui::ColorProviderManager::GetForTesting();
manager.AppendColorProviderInitializer(base::BindRepeating(&AddColor));
}
void TearDown() override {
ui::ColorProviderManager::ResetForTesting();
WidgetTest::TearDown();
}
private:
static void AddColor(ui::ColorProvider* provider,
const ui::ColorProviderKey& key) {
ui::ColorMixer& mixer = provider->AddMixer();
mixer[ui::kColorSysPrimary] = {
key.color_mode == ui::ColorProviderKey::ColorMode::kDark ? kDarkColor
: kLightColor};
}
};
TEST_F(WidgetColorModeTest, ColorModeOverride_NoOverride) {
ui::TestNativeTheme test_theme;
std::unique_ptr<Widget> widget = base::WrapUnique(
CreateTopLevelPlatformWidget(Widget::InitParams::CLIENT_OWNS_WIDGET));
test_theme.SetDarkMode(true);
widget->SetNativeThemeForTest(&test_theme);
widget->SetColorModeOverride(std::nullopt, std::nullopt);
// Verify that we resolve the dark color when we don't override color mode.
EXPECT_EQ(kDarkColor,
widget->GetColorProvider()->GetColor(ui::kColorSysPrimary));
}
TEST_F(WidgetColorModeTest, ColorModeOverride_DarkOverride) {
ui::TestNativeTheme test_theme;
std::unique_ptr<Widget> widget = base::WrapUnique(
CreateTopLevelPlatformWidget(Widget::InitParams::CLIENT_OWNS_WIDGET));
test_theme.SetDarkMode(false);
widget->SetNativeThemeForTest(&test_theme);
widget->SetColorModeOverride(ui::ColorProviderKey::ColorMode::kDark,
std::nullopt);
// Verify that we resolve the light color even though the theme is dark.
EXPECT_EQ(kDarkColor,
widget->GetColorProvider()->GetColor(ui::kColorSysPrimary));
}
TEST_F(WidgetColorModeTest, ColorModeOverride_LightOverride) {
ui::TestNativeTheme test_theme;
std::unique_ptr<Widget> widget = base::WrapUnique(
CreateTopLevelPlatformWidget(Widget::InitParams::CLIENT_OWNS_WIDGET));
test_theme.SetDarkMode(true);
widget->SetNativeThemeForTest(&test_theme);
widget->SetColorModeOverride(ui::ColorProviderKey::ColorMode::kLight,
std::nullopt);
// Verify that we resolve the light color even though the theme is dark.
EXPECT_EQ(kLightColor,
widget->GetColorProvider()->GetColor(ui::kColorSysPrimary));
}
TEST_F(WidgetColorModeTest, ChildInheritsColorMode_NoOverrides) {
// Create the parent widget and set the native theme to dark.
ui::TestNativeTheme test_theme;
std::unique_ptr<Widget> widget = base::WrapUnique(
CreateTopLevelPlatformWidget(Widget::InitParams::CLIENT_OWNS_WIDGET));
test_theme.SetDarkMode(true);
widget->SetNativeThemeForTest(&test_theme);
// Create the child widget.
std::unique_ptr<Widget> widget_child =
base::WrapUnique(CreateChildPlatformWidget(
widget->GetNativeView(), Widget::InitParams::CLIENT_OWNS_WIDGET));
// Ensure neither has an override set. The child should inherit the color mode
// of the parent.
widget->SetColorModeOverride(std::nullopt, std::nullopt);
widget_child->SetColorModeOverride(std::nullopt, std::nullopt);
EXPECT_EQ(kDarkColor,
widget->GetColorProvider()->GetColor(ui::kColorSysPrimary));
EXPECT_EQ(kDarkColor,
widget_child->GetColorProvider()->GetColor(ui::kColorSysPrimary));
// Set the parent's native theme to light. The child should inherit the color
// mode of the parent.
test_theme.SetDarkMode(false);
EXPECT_EQ(kLightColor,
widget->GetColorProvider()->GetColor(ui::kColorSysPrimary));
EXPECT_EQ(kLightColor,
widget_child->GetColorProvider()->GetColor(ui::kColorSysPrimary));
}
TEST_F(WidgetColorModeTest, ChildInheritsColorMode_Overrides) {
// Create the parent widget and set the native theme to dark.
ui::TestNativeTheme test_theme;
std::unique_ptr<Widget> widget = base::WrapUnique(
CreateTopLevelPlatformWidget(Widget::InitParams::CLIENT_OWNS_WIDGET));
test_theme.SetDarkMode(true);
widget->SetNativeThemeForTest(&test_theme);
// Create the child widget.
std::unique_ptr<Widget> widget_child =
base::WrapUnique(CreateChildPlatformWidget(
widget->GetNativeView(), Widget::InitParams::CLIENT_OWNS_WIDGET));
// Ensure neither has an override set. The child should inherit the color mode
// of the parent.
widget->SetColorModeOverride(std::nullopt, std::nullopt);
widget_child->SetColorModeOverride(std::nullopt, std::nullopt);
EXPECT_EQ(kDarkColor,
widget->GetColorProvider()->GetColor(ui::kColorSysPrimary));
EXPECT_EQ(kDarkColor,
widget_child->GetColorProvider()->GetColor(ui::kColorSysPrimary));
// Set the parent's override to light, then back to dark. the child should
// follow the parent's overridden color mode.
widget->SetColorModeOverride(ui::ColorProviderKey::ColorMode::kLight,
std::nullopt);
EXPECT_EQ(kLightColor,
widget->GetColorProvider()->GetColor(ui::kColorSysPrimary));
EXPECT_EQ(kLightColor,
widget_child->GetColorProvider()->GetColor(ui::kColorSysPrimary));
widget->SetColorModeOverride(ui::ColorProviderKey::ColorMode::kDark,
std::nullopt);
EXPECT_EQ(kDarkColor,
widget->GetColorProvider()->GetColor(ui::kColorSysPrimary));
EXPECT_EQ(kDarkColor,
widget_child->GetColorProvider()->GetColor(ui::kColorSysPrimary));
// Override the child's color mode to light. The parent should continue to
// report a dark color mode.
widget_child->SetColorModeOverride(ui::ColorProviderKey::ColorMode::kLight,
std::nullopt);
EXPECT_EQ(kDarkColor,
widget->GetColorProvider()->GetColor(ui::kColorSysPrimary));
EXPECT_EQ(kLightColor,
widget_child->GetColorProvider()->GetColor(ui::kColorSysPrimary));
}
TEST_F(WidgetTest, NativeWindowProperty) {
const char* key = "foo";
int value = 3;
std::unique_ptr<Widget> widget = base::WrapUnique(
CreateTopLevelPlatformWidget(Widget::InitParams::CLIENT_OWNS_WIDGET));
EXPECT_EQ(nullptr, widget->GetNativeWindowProperty(key));
widget->SetNativeWindowProperty(key, &value);
EXPECT_EQ(&value, widget->GetNativeWindowProperty(key));
widget->SetNativeWindowProperty(key, nullptr);
EXPECT_EQ(nullptr, widget->GetNativeWindowProperty(key));
}
TEST_F(WidgetTest, GetParent) {
// Create a hierarchy of native widgets.
std::unique_ptr<Widget> toplevel = base::WrapUnique(
CreateTopLevelPlatformWidget(Widget::InitParams::CLIENT_OWNS_WIDGET));
std::unique_ptr<Widget> child = base::WrapUnique(CreateChildPlatformWidget(
toplevel->GetNativeView(), Widget::InitParams::CLIENT_OWNS_WIDGET));
std::unique_ptr<Widget> grandchild =
base::WrapUnique(CreateChildPlatformWidget(
child->GetNativeView(), Widget::InitParams::CLIENT_OWNS_WIDGET));
EXPECT_EQ(nullptr, toplevel->parent());
EXPECT_EQ(child.get(), grandchild->parent());
EXPECT_EQ(toplevel.get(), child->parent());
// children should be automatically destroyed with |toplevel|.
}
// Verify that there is no change in focus if |enable_arrow_key_traversal| is
// false (the default).
TEST_F(WidgetTest, ArrowKeyFocusTraversalOffByDefault) {
std::unique_ptr<Widget> toplevel = base::WrapUnique(
CreateTopLevelPlatformWidget(Widget::InitParams::CLIENT_OWNS_WIDGET));
// Establish default value.
DCHECK(!toplevel->widget_delegate()->enable_arrow_key_traversal());
View* container = toplevel->client_view();
container->SetLayoutManager(std::make_unique<FillLayout>());
auto* const button1 =
container->AddChildView(std::make_unique<LabelButton>());
auto* const button2 =
container->AddChildView(std::make_unique<LabelButton>());
toplevel->Show();
button1->RequestFocus();
ui::KeyEvent right_arrow(ui::EventType::kKeyPressed, ui::VKEY_RIGHT,
ui::EF_NONE);
toplevel->OnKeyEvent(&right_arrow);
EXPECT_TRUE(button1->HasFocus());
EXPECT_FALSE(button2->HasFocus());
ui::KeyEvent left_arrow(ui::EventType::kKeyPressed, ui::VKEY_LEFT,
ui::EF_NONE);
toplevel->OnKeyEvent(&left_arrow);
EXPECT_TRUE(button1->HasFocus());
EXPECT_FALSE(button2->HasFocus());
ui::KeyEvent up_arrow(ui::EventType::kKeyPressed, ui::VKEY_UP, ui::EF_NONE);
toplevel->OnKeyEvent(&up_arrow);
EXPECT_TRUE(button1->HasFocus());
EXPECT_FALSE(button2->HasFocus());
ui::KeyEvent down_arrow(ui::EventType::kKeyPressed, ui::VKEY_DOWN,
ui::EF_NONE);
toplevel->OnKeyEvent(&down_arrow);
EXPECT_TRUE(button1->HasFocus());
EXPECT_FALSE(button2->HasFocus());
}
// Verify that arrow keys can change focus if |enable_arrow_key_traversal| is
// set to true.
TEST_F(WidgetTest, ArrowKeyTraversalMovesFocusBetweenViews) {
std::unique_ptr<Widget> toplevel = base::WrapUnique(
CreateTopLevelPlatformWidget(Widget::InitParams::CLIENT_OWNS_WIDGET));
toplevel->widget_delegate()->SetEnableArrowKeyTraversal(true);
View* container = toplevel->client_view();
container->SetLayoutManager(std::make_unique<FillLayout>());
auto* const button1 =
container->AddChildView(std::make_unique<LabelButton>());
auto* const button2 =
container->AddChildView(std::make_unique<LabelButton>());
auto* const button3 =
container->AddChildView(std::make_unique<LabelButton>());
toplevel->Show();
button1->RequestFocus();
// Right should advance focus (similar to TAB).
ui::KeyEvent right_arrow(ui::EventType::kKeyPressed, ui::VKEY_RIGHT,
ui::EF_NONE);
toplevel->OnKeyEvent(&right_arrow);
EXPECT_FALSE(button1->HasFocus());
EXPECT_TRUE(button2->HasFocus());
EXPECT_FALSE(button3->HasFocus());
// Down should also advance focus.
ui::KeyEvent down_arrow(ui::EventType::kKeyPressed, ui::VKEY_DOWN,
ui::EF_NONE);
toplevel->OnKeyEvent(&down_arrow);
EXPECT_FALSE(button1->HasFocus());
EXPECT_FALSE(button2->HasFocus());
EXPECT_TRUE(button3->HasFocus());
// Left should reverse focus (similar to SHIFT+TAB).
ui::KeyEvent left_arrow(ui::EventType::kKeyPressed, ui::VKEY_LEFT,
ui::EF_NONE);
toplevel->OnKeyEvent(&left_arrow);
EXPECT_FALSE(button1->HasFocus());
EXPECT_TRUE(button2->HasFocus());
EXPECT_FALSE(button3->HasFocus());
// Up should also reverse focus.
ui::KeyEvent up_arrow(ui::EventType::kKeyPressed, ui::VKEY_UP, ui::EF_NONE);
toplevel->OnKeyEvent(&up_arrow);
EXPECT_TRUE(button1->HasFocus());
EXPECT_FALSE(button2->HasFocus());
EXPECT_FALSE(button3->HasFocus());
// Test backwards wrap-around.
ui::KeyEvent up_arrow2(ui::EventType::kKeyPressed, ui::VKEY_UP, ui::EF_NONE);
toplevel->OnKeyEvent(&up_arrow2);
EXPECT_FALSE(button1->HasFocus());
EXPECT_FALSE(button2->HasFocus());
EXPECT_TRUE(button3->HasFocus());
// Test forward wrap-around.
ui::KeyEvent down_arrow2(ui::EventType::kKeyPressed, ui::VKEY_DOWN,
ui::EF_NONE);
toplevel->OnKeyEvent(&down_arrow2);
EXPECT_TRUE(button1->HasFocus());
EXPECT_FALSE(button2->HasFocus());
EXPECT_FALSE(button3->HasFocus());
}
TEST_F(WidgetTest, ArrowKeyTraversalNotInheritedByChildWidgets) {
std::unique_ptr<Widget> parent = base::WrapUnique(
CreateTopLevelPlatformWidget(Widget::InitParams::CLIENT_OWNS_WIDGET));
std::unique_ptr<Widget> child = base::WrapUnique(CreateChildPlatformWidget(
parent->GetNativeView(), Widget::InitParams::CLIENT_OWNS_WIDGET));
parent->widget_delegate()->SetEnableArrowKeyTraversal(true);
View* container = child->GetContentsView();
DCHECK(container);
container->SetLayoutManager(std::make_unique<FillLayout>());
auto* const button1 =
container->AddChildView(std::make_unique<LabelButton>());
auto* const button2 =
container->AddChildView(std::make_unique<LabelButton>());
parent->Show();
child->Show();
button1->RequestFocus();
// Arrow key should not cause focus change on child since only the parent
// Widget has |enable_arrow_key_traversal| set.
ui::KeyEvent right_arrow(ui::EventType::kKeyPressed, ui::VKEY_RIGHT,
ui::EF_NONE);
child->OnKeyEvent(&right_arrow);
EXPECT_TRUE(button1->HasFocus());
EXPECT_FALSE(button2->HasFocus());
}
TEST_F(WidgetTest, ArrowKeyTraversalMayBeExplicitlyEnabledByChildWidgets) {
std::unique_ptr<Widget> parent = base::WrapUnique(
CreateTopLevelPlatformWidget(Widget::InitParams::CLIENT_OWNS_WIDGET));
std::unique_ptr<Widget> child = base::WrapUnique(CreateChildPlatformWidget(
parent->GetNativeView(), Widget::InitParams::CLIENT_OWNS_WIDGET));
child->widget_delegate()->SetEnableArrowKeyTraversal(true);
View* container = child->GetContentsView();
container->SetLayoutManager(std::make_unique<FillLayout>());
auto* const button1 =
container->AddChildView(std::make_unique<LabelButton>());
auto* const button2 =
container->AddChildView(std::make_unique<LabelButton>());
parent->Show();
child->Show();
button1->RequestFocus();
// Arrow key should cause focus key on child since child has flag set, even
// if the parent Widget does not.
ui::KeyEvent right_arrow(ui::EventType::kKeyPressed, ui::VKEY_RIGHT,
ui::EF_NONE);
child->OnKeyEvent(&right_arrow);
EXPECT_FALSE(button1->HasFocus());
EXPECT_TRUE(button2->HasFocus());
}
////////////////////////////////////////////////////////////////////////////////
// Widget::GetTopLevelWidget tests.
TEST_F(WidgetTest, GetTopLevelWidget_Native) {
// Create a hierarchy of native widgets.
std::unique_ptr<Widget> toplevel = base::WrapUnique(
CreateTopLevelPlatformWidget(Widget::InitParams::CLIENT_OWNS_WIDGET));
gfx::NativeView parent = toplevel->GetNativeView();
std::unique_ptr<Widget> child = base::WrapUnique(CreateChildPlatformWidget(
parent, Widget::InitParams::CLIENT_OWNS_WIDGET));
EXPECT_EQ(toplevel.get(), toplevel->GetTopLevelWidget());
EXPECT_EQ(toplevel.get(), child->GetTopLevelWidget());
// |child| should be automatically destroyed with |toplevel|.
}
// Test if a focus manager and an inputmethod work without CHECK failure
// when window activation changes.
TEST_F(WidgetTest, ChangeActivation) {
std::unique_ptr<Widget> top1 = base::WrapUnique(
CreateTopLevelPlatformWidget(Widget::InitParams::CLIENT_OWNS_WIDGET));
top1->Show();
RunPendingMessages();
std::unique_ptr<Widget> top2 = base::WrapUnique(
CreateTopLevelPlatformWidget(Widget::InitParams::CLIENT_OWNS_WIDGET));
top2->Show();
RunPendingMessages();
top1->Activate();
RunPendingMessages();
top2->Activate();
RunPendingMessages();
top1->Activate();
RunPendingMessages();
}
// Tests visibility of child widgets.
TEST_F(WidgetTest, Visibility) {
std::unique_ptr<Widget> toplevel = base::WrapUnique(
CreateTopLevelPlatformWidget(Widget::InitParams::CLIENT_OWNS_WIDGET));
gfx::NativeView parent = toplevel->GetNativeView();
std::unique_ptr<Widget> child = base::WrapUnique(CreateChildPlatformWidget(
parent, Widget::InitParams::CLIENT_OWNS_WIDGET));
EXPECT_FALSE(toplevel->IsVisible());
EXPECT_FALSE(child->IsVisible());
// Showing a child with a hidden parent keeps the child hidden.
child->Show();
EXPECT_FALSE(toplevel->IsVisible());
EXPECT_FALSE(child->IsVisible());
// Showing a hidden parent with a visible child shows both.
toplevel->Show();
EXPECT_TRUE(toplevel->IsVisible());
EXPECT_TRUE(child->IsVisible());
// Hiding a parent hides both parent and child.
toplevel->Hide();
EXPECT_FALSE(toplevel->IsVisible());
EXPECT_FALSE(child->IsVisible());
// Hiding a child while the parent is hidden keeps the child hidden when the
// parent is shown.
child->Hide();
toplevel->Show();
EXPECT_TRUE(toplevel->IsVisible());
EXPECT_FALSE(child->IsVisible());
// |child| should be automatically destroyed with |toplevel|.
}
// Test that child widgets are positioned relative to their parent.
TEST_F(WidgetTest, ChildBoundsRelativeToParent) {
std::unique_ptr<Widget> toplevel = base::WrapUnique(
CreateTopLevelPlatformWidget(Widget::InitParams::CLIENT_OWNS_WIDGET));
std::unique_ptr<Widget> child = base::WrapUnique(CreateChildPlatformWidget(
toplevel->GetNativeView(), Widget::InitParams::CLIENT_OWNS_WIDGET));
toplevel->SetBounds(gfx::Rect(160, 100, 320, 200));
child->SetBounds(gfx::Rect(0, 0, 320, 200));
child->Show();
toplevel->Show();
gfx::Rect toplevel_bounds = toplevel->GetWindowBoundsInScreen();
// Check the parent origin. If it was (0, 0) the test wouldn't be interesting.
EXPECT_NE(gfx::Vector2d(0, 0), toplevel_bounds.OffsetFromOrigin());
// The child's origin is at (0, 0), but the same size, so bounds should match.
EXPECT_EQ(toplevel_bounds, child->GetWindowBoundsInScreen());
}
////////////////////////////////////////////////////////////////////////////////
// Widget ownership tests.
//
// Tests various permutations of Widget ownership specified in the
// InitParams::Ownership param. Make sure that they are properly destructed
// during shutdown.
// A bag of state to monitor destructions.
struct OwnershipTestState {
OwnershipTestState() = default;
bool widget_deleted = false;
bool native_widget_deleted = false;
};
class WidgetOwnershipTest : public WidgetTest {
public:
WidgetOwnershipTest() = default;
WidgetOwnershipTest(const WidgetOwnershipTest&) = delete;
WidgetOwnershipTest& operator=(const WidgetOwnershipTest&) = delete;
~WidgetOwnershipTest() override = default;
void TearDown() override {
EXPECT_TRUE(state()->widget_deleted);
EXPECT_TRUE(state()->native_widget_deleted);
WidgetTest::TearDown();
}
OwnershipTestState* state() { return &state_; }
private:
OwnershipTestState state_;
};
// A Widget subclass that updates a bag of state when it is destroyed.
class OwnershipTestWidget : public Widget {
public:
explicit OwnershipTestWidget(OwnershipTestState* state) : state_(state) {}
OwnershipTestWidget(const OwnershipTestWidget&) = delete;
OwnershipTestWidget& operator=(const OwnershipTestWidget&) = delete;
~OwnershipTestWidget() override { state_->widget_deleted = true; }
private:
raw_ptr<OwnershipTestState> state_;
};
class NativeWidgetDestroyedWaiter {
public:
explicit NativeWidgetDestroyedWaiter(OwnershipTestState* state)
: state_(state) {}
base::OnceClosure GetNativeWidgetDestroyedCallback() {
return base::BindOnce(
[](OwnershipTestState* state, base::RunLoop* run_loop) {
state->native_widget_deleted = true;
run_loop->Quit();
},
state_.get(), &run_loop_);
}
void Wait() {
if (!state_->native_widget_deleted) {
run_loop_.Run();
}
}
private:
base::RunLoop run_loop_;
raw_ptr<OwnershipTestState> state_;
};
using NativeWidgetOwnsWidgetTest = WidgetOwnershipTest;
// NativeWidget owns its Widget, part 1.1: NativeWidget is a non-desktop
// widget, CloseNow() destroys Widget and NativeWidget synchronously.
TEST_F(NativeWidgetOwnsWidgetTest, NonDesktopWidget_CloseNow) {
Widget* widget = new OwnershipTestWidget(state());
Widget::InitParams params =
CreateParams(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
Widget::InitParams::TYPE_POPUP);
params.native_widget = CreatePlatformNativeWidgetImpl(
widget, kStubCapture, &state()->native_widget_deleted);
widget->Init(std::move(params));
widget->CloseNow();
// Both widget and native widget should be deleted synchronously.
EXPECT_TRUE(state()->widget_deleted);
EXPECT_TRUE(state()->native_widget_deleted);
}
// NativeWidget owns its Widget, part 1.2: NativeWidget is a non-desktop
// widget, Close() destroys Widget and NativeWidget asynchronously.
TEST_F(NativeWidgetOwnsWidgetTest, NonDesktopWidget_Close) {
NativeWidgetDestroyedWaiter waiter(state());
Widget* widget = new OwnershipTestWidget(state());
Widget::InitParams params =
CreateParams(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
Widget::InitParams::TYPE_POPUP);
params.native_widget = CreatePlatformNativeWidgetImpl(
widget, kStubCapture, waiter.GetNativeWidgetDestroyedCallback());
widget->Init(std::move(params));
widget->Close();
waiter.Wait();
EXPECT_TRUE(state()->widget_deleted);
EXPECT_TRUE(state()->native_widget_deleted);
}
// NativeWidget owns its Widget, part 1.3: NativeWidget is a desktop
// widget, Close() destroys Widget and NativeWidget asynchronously.
#if BUILDFLAG(ENABLE_DESKTOP_AURA)
TEST_F(NativeWidgetOwnsWidgetTest, DesktopWidget_Close) {
NativeWidgetDestroyedWaiter waiter(state());
Widget* widget = new OwnershipTestWidget(state());
Widget::InitParams params =
CreateParams(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
Widget::InitParams::TYPE_POPUP);
params.native_widget = CreatePlatformDesktopNativeWidgetImpl(
widget, kStubCapture, waiter.GetNativeWidgetDestroyedCallback());
widget->Init(std::move(params));
widget->Close();
waiter.Wait();
EXPECT_TRUE(state()->widget_deleted);
EXPECT_TRUE(state()->native_widget_deleted);
}
#endif
// NativeWidget owns its Widget, part 1.4: NativeWidget is a desktop
// widget. Unlike desktop widget, CloseNow() might destroy Widget and
// NativeWidget asynchronously.
#if BUILDFLAG(ENABLE_DESKTOP_AURA)
TEST_F(NativeWidgetOwnsWidgetTest, DesktopWidget_CloseNow) {
NativeWidgetDestroyedWaiter waiter(state());
Widget* widget = new OwnershipTestWidget(state());
Widget::InitParams params =
CreateParams(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
Widget::InitParams::TYPE_POPUP);
params.native_widget = CreatePlatformDesktopNativeWidgetImpl(
widget, kStubCapture, waiter.GetNativeWidgetDestroyedCallback());
widget->Init(std::move(params));
widget->CloseNow();
waiter.Wait();
EXPECT_TRUE(state()->widget_deleted);
EXPECT_TRUE(state()->native_widget_deleted);
}
#endif
// NativeWidget owns its Widget, part 2.1: NativeWidget is a non-desktop
// widget. CloseNow() the parent should destroy the child.
TEST_F(NativeWidgetOwnsWidgetTest, NonDestkopWidget_CloseNowParent) {
NativeWidgetDestroyedWaiter waiter(state());
Widget* toplevel = CreateTopLevelPlatformWidget();
Widget* widget = new OwnershipTestWidget(state());
Widget::InitParams params =
CreateParams(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
Widget::InitParams::TYPE_POPUP);
params.parent = toplevel->GetNativeView();
params.native_widget = CreatePlatformNativeWidgetImpl(
widget, kStubCapture, waiter.GetNativeWidgetDestroyedCallback());
widget->Init(std::move(params));
// Now destroy the native widget. This is achieved by closing the toplevel.
toplevel->CloseNow();
// The NativeWidget won't be deleted until after a return to the message loop
// so we have to run pending messages before testing the destruction status.
waiter.Wait();
EXPECT_TRUE(state()->widget_deleted);
EXPECT_TRUE(state()->native_widget_deleted);
}
// NativeWidget owns its Widget, part 2.2: NativeWidget is a desktop
// widget. CloseNow() the parent should destroy the child.
#if BUILDFLAG(ENABLE_DESKTOP_AURA)
TEST_F(NativeWidgetOwnsWidgetTest, DestkopWidget_CloseNowParent) {
NativeWidgetDestroyedWaiter waiter(state());
Widget* toplevel = CreateTopLevelPlatformDesktopWidget();
Widget* widget = new OwnershipTestWidget(state());
Widget::InitParams params =
CreateParams(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
Widget::InitParams::TYPE_POPUP);
params.parent = toplevel->GetNativeView();
params.native_widget = CreatePlatformDesktopNativeWidgetImpl(
widget, kStubCapture, waiter.GetNativeWidgetDestroyedCallback());
widget->Init(std::move(params));
// Now destroy the native widget. This is achieved by closing the toplevel.
toplevel->CloseNow();
// The NativeWidget won't be deleted until after a return to the message loop
// so we have to run pending messages before testing the destruction status.
waiter.Wait();
EXPECT_TRUE(state()->widget_deleted);
EXPECT_TRUE(state()->native_widget_deleted);
}
#endif
// NativeWidget owns its Widget, part 3.1: NativeWidget is a non-desktop
// widget, destroyed out from under it by the OS.
TEST_F(NativeWidgetOwnsWidgetTest, NonDesktopWidget_NativeDestroy) {
Widget* widget = new OwnershipTestWidget(state());
Widget::InitParams params =
CreateParams(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
Widget::InitParams::TYPE_POPUP);
params.native_widget = CreatePlatformNativeWidgetImpl(
widget, kStubCapture, &state()->native_widget_deleted);
widget->Init(std::move(params));
// Now simulate a destroy of the platform native widget from the OS:
SimulateNativeDestroy(widget);
EXPECT_TRUE(state()->widget_deleted);
EXPECT_TRUE(state()->native_widget_deleted);
}
#if BUILDFLAG(ENABLE_DESKTOP_AURA)
// NativeWidget owns its Widget, part 3.2: NativeWidget is a desktop
// widget, destroyed out from under it by the OS.
TEST_F(NativeWidgetOwnsWidgetTest, DesktopWidget_NativeDestroy) {
NativeWidgetDestroyedWaiter waiter(state());
Widget* widget = new OwnershipTestWidget(state());
Widget::InitParams params =
CreateParams(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
Widget::InitParams::TYPE_POPUP);
params.native_widget = CreatePlatformDesktopNativeWidgetImpl(
widget, kStubCapture, waiter.GetNativeWidgetDestroyedCallback());
widget->Init(std::move(params));
// Now simulate a destroy of the platform native widget from the OS:
SimulateDesktopNativeDestroy(widget);
waiter.Wait();
EXPECT_TRUE(state()->widget_deleted);
EXPECT_TRUE(state()->native_widget_deleted);
}
#endif
using WidgetOwnsNativeWidgetTest = WidgetOwnershipTest;
// Widget owns its NativeWidget, part 1.
TEST_F(WidgetOwnsNativeWidgetTest, Ownership) {
auto widget = std::make_unique<OwnershipTestWidget>(state());
Widget::InitParams params =
CreateParamsForTestWidget(Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.native_widget = CreatePlatformNativeWidgetImpl(
widget.get(), kStubCapture, &state()->native_widget_deleted);
widget->Init(std::move(params));
// Now delete the Widget, which should delete the NativeWidget.
widget.reset();
// TODO(beng): write test for this ownership scenario and the NativeWidget
// being deleted out from under the Widget.
}
// Widget owns its NativeWidget, part 2: destroy the parent view.
TEST_F(WidgetOwnsNativeWidgetTest, DestroyParentView) {
Widget* toplevel = CreateTopLevelPlatformWidget();
auto widget = std::make_unique<OwnershipTestWidget>(state());
Widget::InitParams params =
CreateParamsForTestWidget(Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.parent = toplevel->GetNativeView();
params.native_widget = CreatePlatformNativeWidgetImpl(
widget.get(), kStubCapture, &state()->native_widget_deleted);
widget->Init(std::move(params));
// Now close the toplevel, which deletes the view hierarchy.
toplevel->CloseNow();
RunPendingMessages();
// This shouldn't delete the widget because it shouldn't be deleted
// from the native side.
EXPECT_FALSE(state()->widget_deleted);
EXPECT_FALSE(state()->native_widget_deleted);
}
// Widget owns its NativeWidget, part 3: has a WidgetDelegateView as contents.
TEST_F(WidgetOwnsNativeWidgetTest, WidgetDelegateView) {
auto widget = std::make_unique<OwnershipTestWidget>(state());
Widget::InitParams params =
CreateParamsForTestWidget(Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.native_widget = CreatePlatformNativeWidgetImpl(
widget.get(), kStubCapture, &state()->native_widget_deleted);
params.delegate = new WidgetDelegateView(WidgetDelegateView::CreatePassKey());
widget->Init(std::move(params));
// Allow the Widget to go out of scope. There should be no crash or
// use-after-free.
}
// Widget owns its NativeWidget, part 4: Widget::CloseNow should be idempotent.
TEST_F(WidgetOwnsNativeWidgetTest, IdempotentCloseNow) {
auto widget = std::make_unique<OwnershipTestWidget>(state());
Widget::InitParams params =
CreateParamsForTestWidget(Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.native_widget = CreatePlatformNativeWidgetImpl(
widget.get(), kStubCapture, &state()->native_widget_deleted);
widget->Init(std::move(params));
// Now close the Widget, which should delete the NativeWidget.
widget->CloseNow();
RunPendingMessages();
// Close the widget again should not crash.
widget->CloseNow();
RunPendingMessages();
}
// Widget owns its NativeWidget, part 5: Widget::Close should be idempotent.
TEST_F(WidgetOwnsNativeWidgetTest, IdempotentClose) {
auto widget = std::make_unique<OwnershipTestWidget>(state());
Widget::InitParams params =
CreateParamsForTestWidget(Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.native_widget = CreatePlatformNativeWidgetImpl(
widget.get(), kStubCapture, &state()->native_widget_deleted);
widget->Init(std::move(params));
// Now close the Widget, which should delete the NativeWidget.
widget->Close();
RunPendingMessages();
// Close the widget again should not crash.
widget->Close();
RunPendingMessages();
}
// Tests for CLIENT_OWNS_WIDGET. The client holds a unique_ptr<Widget>.
// The NativeWidget will be destroyed when the platform window is closed.
using ClientOwnsWidgetTest = WidgetOwnershipTest;
TEST_F(ClientOwnsWidgetTest, Ownership) {
auto widget = std::make_unique<OwnershipTestWidget>(state());
Widget::InitParams params =
CreateParamsForTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET,
Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.native_widget = CreatePlatformNativeWidgetImpl(
widget.get(), kStubCapture, &state()->native_widget_deleted);
widget->Init(std::move(params));
widget->CloseNow();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(state()->native_widget_deleted);
}
TEST_F(ClientOwnsWidgetTest, DestructWithAsyncCloseFirst) {
auto widget = std::make_unique<OwnershipTestWidget>(state());
Widget::InitParams params =
CreateParamsForTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET,
Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.native_widget = CreatePlatformNativeWidgetImpl(
widget.get(), kStubCapture, &state()->native_widget_deleted);
widget->Init(std::move(params));
widget->Close();
widget.reset();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(state()->native_widget_deleted);
}
TEST_F(ClientOwnsWidgetTest, DestructWithoutExplicitClose) {
auto widget = std::make_unique<OwnershipTestWidget>(state());
Widget::InitParams params =
CreateParamsForTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET,
Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.native_widget = CreatePlatformNativeWidgetImpl(
widget.get(), kStubCapture, &state()->native_widget_deleted);
widget->Init(std::move(params));
widget.reset();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(state()->native_widget_deleted);
}
class WidgetDestroyCounter : public WidgetObserver {
public:
explicit WidgetDestroyCounter(Widget* widget)
: widget_(widget->GetWeakPtr()) {
widget_->AddObserver(this);
}
~WidgetDestroyCounter() override {
if (widget_) {
widget_->RemoveObserver(this);
}
}
int widget_destroying_count() const { return widget_destroying_count_; }
int widget_destroyed_count() const { return widget_destroyed_count_; }
private:
// WidgetObserver:
void OnWidgetDestroying(Widget* widget) override {
++widget_destroying_count_;
}
void OnWidgetDestroyed(Widget* widget) override { ++widget_destroyed_count_; }
base::WeakPtr<Widget> widget_;
int widget_destroying_count_ = 0;
int widget_destroyed_count_ = 0;
};
TEST_F(ClientOwnsWidgetTest, NotificationsTest) {
auto widget = std::make_unique<OwnershipTestWidget>(state());
Widget::InitParams params =
CreateParamsForTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET,
Widget::InitParams::TYPE_WINDOW_FRAMELESS);
params.native_widget = CreatePlatformNativeWidgetImpl(
widget.get(), kStubCapture, &state()->native_widget_deleted);
widget->Init(std::move(params));
auto observer = std::make_unique<WidgetDestroyCounter>(widget.get());
widget->Close();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(observer->widget_destroying_count(), 1);
EXPECT_EQ(observer->widget_destroyed_count(), 1);
widget.reset();
EXPECT_TRUE(state()->widget_deleted);
// The destroying & destroyed notifications should only happen once.
EXPECT_EQ(observer->widget_destroying_count(), 1);
EXPECT_EQ(observer->widget_destroyed_count(), 1);
}
////////////////////////////////////////////////////////////////////////////////
// Test to verify using various Widget methods doesn't crash when the underlying
// NativeView and NativeWidget is destroyed. Currently, for
// the WIDGET_OWNS_NATIVE_WIDGET ownership pattern, the NativeWidget will not be
// destroyed, but |native_widget_| will still be set to nullptr.
class WidgetWithDestroyedNativeViewOrNativeWidgetTest
: public ViewsTestBase,
public testing::WithParamInterface<
std::tuple<ViewsTestBase::NativeWidgetType,
Widget::InitParams::Ownership>> {
public:
WidgetWithDestroyedNativeViewOrNativeWidgetTest() = default;
WidgetWithDestroyedNativeViewOrNativeWidgetTest(
const WidgetWithDestroyedNativeViewOrNativeWidgetTest&) = delete;
WidgetWithDestroyedNativeViewOrNativeWidgetTest& operator=(
const WidgetWithDestroyedNativeViewOrNativeWidgetTest&) = delete;
~WidgetWithDestroyedNativeViewOrNativeWidgetTest() override = default;
// ViewsTestBase:
void SetUp() override {
set_native_widget_type(
std::get<ViewsTestBase::NativeWidgetType>(GetParam()));
ViewsTestBase::SetUp();
if (std::get<Widget::InitParams::Ownership>(GetParam()) ==
Widget::InitParams::CLIENT_OWNS_WIDGET) {
widget_ = std::make_unique<Widget>();
Widget::InitParams params =
CreateParamsForTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET,
Widget::InitParams::TYPE_WINDOW_FRAMELESS);
widget_->Init(std::move(params));
} else {
widget_ = CreateTestWidget(Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);
}
widget()->Show();
widget()->native_widget_private()->CloseNow();
task_environment()->RunUntilIdle();
}
Widget* widget() { return widget_.get(); }
static std::string PrintTestName(
const ::testing::TestParamInfo<
WidgetWithDestroyedNativeViewOrNativeWidgetTest::ParamType>& info) {
std::string test_name;
switch (std::get<ViewsTestBase::NativeWidgetType>(info.param)) {
case ViewsTestBase::NativeWidgetType::kDefault:
test_name += "DefaultNativeWidget";
break;
case ViewsTestBase::NativeWidgetType::kDesktop:
test_name += "DesktopNativeWidget";
break;
}
test_name += "_";
switch (std::get<Widget::InitParams::Ownership>(info.param)) {
case Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET:
test_name += "WidgetOwnsNativeWidget";
break;
case Widget::InitParams::CLIENT_OWNS_WIDGET:
test_name += "ClientOwnsWidget";
break;
case Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET:
// Note: We don't test for this case in
// WidgetWithDestroyedNativeViewOrNativeWidgetTest.
test_name += "NativeWidgetOwnsWidget";
break;
}
return test_name;
}
private:
std::unique_ptr<Widget> widget_;
};
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, Activate) {
widget()->Activate();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, AddAndRemoveObserver) {
// Constructor calls |AddObserver()|
TestWidgetObserver observer(widget());
widget()->RemoveObserver(&observer);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
AddAndRemoveRemovalsObserver) {
TestWidgetRemovalsObserver removals_observer;
widget()->AddRemovalsObserver(&removals_observer);
widget()->RemoveRemovalsObserver(&removals_observer);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, AsWidget) {
widget()->AsWidget();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, CanActivate) {
widget()->CanActivate();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, CenterWindow) {
widget()->CenterWindow(gfx::Size());
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, ClearNativeFocus) {
widget()->ClearNativeFocus();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, ClientView) {
widget()->client_view();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, Close) {
widget()->Close();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, CloseAllWidgets) {
widget()->CloseAllWidgets();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, CloseNow) {
widget()->CloseNow();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, ClosedReason) {
widget()->closed_reason();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, CloseWithReason) {
widget()->CloseWithReason(Widget::ClosedReason::kUnspecified);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
CreateNonClientFrameView) {
widget()->CreateNonClientFrameView();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, Deactivate) {
widget()->Deactivate();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, DraggedView) {
widget()->dragged_view();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, EndMoveLoop) {
widget()->EndMoveLoop();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, ExecuteCommand) {
widget()->ExecuteCommand(0);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, FlashFrame) {
widget()->FlashFrame(true);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, FrameType) {
widget()->frame_type();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, FrameTypeChanged) {
widget()->FrameTypeChanged();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetAccelerator) {
ui::Accelerator accelerator;
widget()->GetAccelerator(0, &accelerator);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetAllChildWidgets) {
Widget::Widgets widgets =
Widget::GetAllChildWidgets(widget()->GetNativeView());
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetAllOwnedWidgets) {
Widget::Widgets widgets =
Widget::GetAllOwnedWidgets(widget()->GetNativeView());
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetAndSetZOrderLevel) {
widget()->SetZOrderLevel(ui::ZOrderLevel::kNormal);
widget()->GetZOrderLevel();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
GetClientAreaBoundsInScreen) {
widget()->GetClientAreaBoundsInScreen();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetColorProvider) {
widget()->GetColorProvider();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetCompositor) {
widget()->GetCompositor();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetContentsView) {
widget()->GetContentsView();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetCustomTheme) {
widget()->GetCustomTheme();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetEventSink) {
widget()->GetEventSink();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetFocusSearch) {
widget()->GetFocusSearch();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetFocusManager) {
widget()->GetFocusManager();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetFocusTraversable) {
widget()->GetFocusTraversable();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetGestureConsumer) {
widget()->GetGestureConsumer();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetGestureRecognizer) {
widget()->GetGestureRecognizer();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetHitTestMask) {
SkPath mask;
widget()->GetHitTestMask(&mask);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetInputMethod) {
widget()->GetInputMethod();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetLayer) {
widget()->GetLayer();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetMinimumSize) {
widget()->GetMinimumSize();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetMaximumSize) {
widget()->GetMaximumSize();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetName) {
widget()->GetName();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetNativeTheme) {
widget()->GetNativeTheme();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetNativeView) {
widget()->GetNativeView();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetNativeWindow) {
widget()->GetNativeWindow();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
GetNativeWindowProperty) {
widget()->GetNativeWindowProperty("xx");
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetNonClientComponent) {
gfx::Point point;
widget()->GetNonClientComponent(point);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
GetPrimaryWindowWidget) {
widget()->GetPrimaryWindowWidget();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetRestoredBounds) {
widget()->GetRestoredBounds();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetRootView) {
widget()->GetRootView();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetSublevelManager) {
widget()->GetSublevelManager();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetThemeProvider) {
widget()->GetThemeProvider();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetTooltipManager) {
widget()->GetTooltipManager();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetTopLevelWidget) {
widget()->GetTopLevelWidget();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
GetTopLevelWidgetForNativeView) {
Widget::GetTopLevelWidgetForNativeView(widget()->GetNativeView());
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetWeakPtr) {
widget()->GetWeakPtr();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
GetWidgetForNativeView) {
Widget::GetWidgetForNativeView(widget()->GetNativeView());
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
GetWidgetForNativeWindow) {
Widget::GetWidgetForNativeWindow(widget()->GetNativeWindow());
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
GetWindowBoundsInScreen) {
widget()->GetWindowBoundsInScreen();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
GetWorkAreaBoundsInScreen) {
widget()->GetWorkAreaBoundsInScreen();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetWorkspace) {
widget()->GetWorkspace();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, GetZOrderSublevel) {
widget()->GetZOrderSublevel();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, HasCapture) {
widget()->HasCapture();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, HasFocusManager) {
widget()->HasFocusManager();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, HasHitTestMask) {
widget()->HasHitTestMask();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, HasObserver) {
TestWidgetObserver observer(widget());
widget()->HasObserver(&observer);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, HasRemovalsObserver) {
TestWidgetRemovalsObserver observer;
widget()->HasRemovalsObserver(&observer);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, Hide) {
widget()->Hide();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, Init) {
Widget::InitParams params(
std::get<Widget::InitParams::Ownership>(GetParam()));
EXPECT_DCHECK_DEATH(widget()->Init(std::move(params)));
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, is_secondary_widget) {
widget()->is_secondary_widget();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, IsActive) {
widget()->IsActive();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, IsClosed) {
widget()->IsClosed();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, IsDialogBox) {
widget()->IsDialogBox();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, IsFullscreen) {
widget()->IsFullscreen();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, IsMaximized) {
widget()->IsMaximized();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, IsMinimized) {
widget()->IsMinimized();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, IsModal) {
widget()->IsModal();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, IsMouseEventsEnabled) {
widget()->IsMouseEventsEnabled();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, IsMoveLoopSupported) {
widget()->IsMoveLoopSupported();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
IsNativeWidgetInitialized) {
widget()->IsNativeWidgetInitialized();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, IsStackedAbove) {
std::unique_ptr<Widget> other_widget =
CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET);
widget()->IsStackedAbove(other_widget->GetNativeView());
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, IsVisible) {
widget()->IsVisible();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
IsVisibleOnAllWorkspaces) {
widget()->IsVisibleOnAllWorkspaces();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, OnGestureEvent) {
ui::GestureEvent event =
CreateTestGestureEvent(ui::EventType::kGestureScrollBegin, 5, 5);
widget()->OnGestureEvent(&event);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, OnKeyEvent) {
ui::KeyEvent event(ui::EventType::kKeyPressed, ui::VKEY_RIGHT, ui::EF_NONE);
widget()->OnKeyEvent(&event);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, OnMouseCaptureLost) {
widget()->OnMouseCaptureLost();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, OnMouseEvent) {
gfx::Point p(200, 200);
ui::MouseEvent event(ui::EventType::kMouseMoved, p, p, ui::EventTimeForNow(),
ui::EF_NONE, ui::EF_NONE);
widget()->OnMouseEvent(&event);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, OnNativeBlur) {
widget()->OnNativeBlur();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, OnNativeFocus) {
widget()->OnNativeFocus();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, OnNativeThemeUpdated) {
ui::TestNativeTheme theme;
widget()->OnNativeThemeUpdated(&theme);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
OnNativeWidgetActivationChanged) {
widget()->OnNativeWidgetActivationChanged(false);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
OnNativeWidgetAddedToCompositor) {
widget()->OnNativeWidgetAddedToCompositor();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
OnNativeWidgetBeginUserBoundsChange) {
widget()->OnNativeWidgetBeginUserBoundsChange();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, OnNativeWidgetCreated) {
EXPECT_DCHECK_DEATH(widget()->OnNativeWidgetCreated());
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
OnNativeWidgetDestroyed) {
widget()->OnNativeWidgetDestroyed();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
OnNativeWidgetDestroying) {
EXPECT_DCHECK_DEATH(widget()->OnNativeWidgetDestroying());
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
OnNativeWidgetEndUserBoundsChange) {
widget()->OnNativeWidgetEndUserBoundsChange();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, OnNativeWidgetMove) {
widget()->OnNativeWidgetMove();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, OnNativeWidgetPaint) {
auto display_list = base::MakeRefCounted<cc::DisplayItemList>();
widget()->OnNativeWidgetPaint(
ui::PaintContext(display_list.get(), 1, gfx::Rect(), false));
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
OnNativeWidgetParentChanged) {
widget()->OnNativeWidgetParentChanged(gfx::NativeView());
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
OnNativeWidgetRemovingFromCompositor) {
widget()->OnNativeWidgetRemovingFromCompositor();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
OnNativeWidgetSizeChanged) {
widget()->OnNativeWidgetSizeChanged(gfx::Size());
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
OnNativeWidgetVisibilityChanged) {
widget()->OnNativeWidgetVisibilityChanged(false);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
OnNativeWidgetWindowShowStateChanged) {
widget()->OnNativeWidgetWindowShowStateChanged();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
OnNativeWidgetWorkspaceChanged) {
widget()->OnNativeWidgetWorkspaceChanged();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, OnOwnerClosing) {
widget()->OnOwnerClosing();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
OnParentShouldPaintAsActiveChanged) {
widget()->OnParentShouldPaintAsActiveChanged();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, OnScrollEvent) {
ui::ScrollEvent scroll(ui::EventType::kScroll, gfx::Point(65, 5),
ui::EventTimeForNow(), 0, 0, 20, 0, 20, 2);
widget()->OnScrollEvent(&scroll);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
OnSizeConstraintsChanged) {
widget()->OnSizeConstraintsChanged();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, LayerTreeChanged) {
widget()->LayerTreeChanged();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
LayoutRootViewIfNecessary) {
widget()->LayoutRootViewIfNecessary();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, LockPaintAsActive) {
widget()->LockPaintAsActive();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, Maximize) {
widget()->Maximize();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, Minimize) {
widget()->Minimize();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, movement_disabled) {
widget()->movement_disabled();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, native_widget_private) {
widget()->native_widget_private();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, native_widget) {
widget()->native_widget();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, non_client_view) {
widget()->non_client_view();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
NotifyNativeViewHierarchyChanged) {
widget()->NotifyNativeViewHierarchyChanged();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
NotifyNativeViewHierarchyWillChange) {
widget()->NotifyNativeViewHierarchyWillChange();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, NotifyWillRemoveView) {
widget()->NotifyWillRemoveView(widget()->non_client_view());
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, parent) {
widget()->parent();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
RegisterPaintAsActiveChangedCallback) {
auto subscription =
widget()->RegisterPaintAsActiveChangedCallback(base::DoNothing());
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, ReleaseCapture) {
widget()->ReleaseCapture();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, ReorderNativeViews) {
widget()->ReorderNativeViews();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, ReparentNativeView) {
EXPECT_DCHECK_DEATH(
Widget::ReparentNativeView(widget()->GetNativeView(), gfx::NativeView()));
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, Restore) {
widget()->Restore();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, RunMoveLoop) {
widget()->RunMoveLoop(gfx::Vector2d(), Widget::MoveLoopSource::kMouse,
Widget::MoveLoopEscapeBehavior::kHide);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, RunShellDrag) {
std::unique_ptr<OSExchangeData> data(std::make_unique<OSExchangeData>());
widget()->RunShellDrag(nullptr, std::move(data), gfx::Point(), 0,
ui::mojom::DragEventSource::kMouse);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, ScheduleLayout) {
widget()->ScheduleLayout();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, SchedulePaintInRect) {
widget()->SchedulePaintInRect(gfx::Rect(0, 0, 1, 2));
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, SetAspectRatio) {
widget()->SetAspectRatio(gfx::SizeF(1.0, 1.0));
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, SetBounds) {
widget()->SetBounds(gfx::Rect(0, 0, 100, 80));
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, SetBoundsConstrained) {
widget()->SetBoundsConstrained(gfx::Rect(0, 0, 120, 140));
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
SetCanAppearInExistingFullscreenSpaces) {
widget()->SetCanAppearInExistingFullscreenSpaces(false);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, SetCapture) {
widget()->SetCapture(widget()->GetRootView());
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, SetContentsView) {
View view;
EXPECT_DCHECK_DEATH(widget()->SetContentsView(std::make_unique<View>()));
EXPECT_DCHECK_DEATH(widget()->SetContentsView(&view));
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, SetCursor) {
widget()->SetCursor(ui::Cursor());
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
SetFocusTraversableParent) {
std::unique_ptr<Widget> another_widget =
CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET);
widget()->SetFocusTraversableParent(another_widget->GetFocusTraversable());
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
SetFocusTraversableParentView) {
std::unique_ptr<Widget> another_widget =
CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET);
widget()->SetFocusTraversableParentView(another_widget->GetContentsView());
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, SetFullscreen) {
widget()->SetFullscreen(true);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, SetInitialFocus) {
widget()->SetInitialFocus(ui::mojom::WindowShowState::kInactive);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
SetNativeWindowProperty) {
widget()->SetNativeWindowProperty("xx", widget());
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, SetOpacity) {
widget()->SetOpacity(0.f);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, SetShape) {
auto rects = std::make_unique<Widget::ShapeRects>();
rects->emplace_back(40, 0, 20, 100);
rects->emplace_back(0, 40, 100, 20);
widget()->SetShape(std::move(rects));
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, SetSize) {
widget()->SetSize(gfx::Size(10, 11));
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
SetVisibilityChangedAnimationsEnabled) {
widget()->SetVisibilityChangedAnimationsEnabled(false);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
SetVisibilityAnimationDuration) {
widget()->SetVisibilityAnimationDuration(base::Seconds(1));
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
SetVisibilityAnimationTransition) {
widget()->SetVisibilityAnimationTransition(Widget::ANIMATE_BOTH);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, SetVisible) {
widget()->SetVisibilityAnimationTransition(Widget::ANIMATE_BOTH);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
SetVisibleOnAllWorkspaces) {
widget()->SetVisibleOnAllWorkspaces(true);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
ShouldDescendIntoChildForEventHandling) {
widget()->ShouldDescendIntoChildForEventHandling(nullptr, gfx::NativeView(),
nullptr, gfx::Point(0, 0));
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
ShouldHandleNativeWidgetActivationChanged) {
widget()->ShouldHandleNativeWidgetActivationChanged(true);
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, ShouldPaintAsActive) {
widget()->ShouldPaintAsActive();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, ShouldUseNativeFrame) {
widget()->ShouldUseNativeFrame();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
ShouldWindowContentsBeTransparent) {
widget()->ShouldWindowContentsBeTransparent();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, Show) {
widget()->Show();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, ShowEmojiPanel) {
widget()->ShowEmojiPanel();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, ShowInactive) {
widget()->ShowInactive();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, StackAbove) {
std::unique_ptr<Widget> another_widget =
CreateTestWidget(std::get<Widget::InitParams::Ownership>(GetParam()));
widget()->StackAbove(another_widget->GetNativeView());
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, StackAboveWidget) {
std::unique_ptr<Widget> another_widget =
CreateTestWidget(std::get<Widget::InitParams::Ownership>(GetParam()));
widget()->StackAboveWidget(another_widget.get());
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, StackAtTop) {
widget()->StackAtTop();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest,
SynthesizeMouseMoveEvent) {
widget()->SynthesizeMouseMoveEvent();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, ThemeChanged) {
widget()->ThemeChanged();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, UnlockPaintAsActive) {
// UnlockPaintAsActive() is called in the destructor of PaintAsActiveLock.
// External invocation is not allowed.
EXPECT_DCHECK_DEATH(widget()->UnlockPaintAsActive());
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, UpdateWindowIcon) {
widget()->UpdateWindowIcon();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, UpdateWindowTitle) {
widget()->UpdateWindowTitle();
}
TEST_P(WidgetWithDestroyedNativeViewOrNativeWidgetTest, ViewHierarchyChanged) {
widget()->ViewHierarchyChanged(
ViewHierarchyChangedDetails(true, nullptr, nullptr, nullptr));
}
INSTANTIATE_TEST_SUITE_P(
PlatformWidgetWithDestroyedNativeViewOrNativeWidgetTest,
WidgetWithDestroyedNativeViewOrNativeWidgetTest,
::testing::Combine(
::testing::Values(ViewsTestBase::NativeWidgetType::kDefault,
ViewsTestBase::NativeWidgetType::kDesktop),
::testing::Values(Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
Widget::InitParams::CLIENT_OWNS_WIDGET)),
WidgetWithDestroyedNativeViewOrNativeWidgetTest::PrintTestName);
////////////////////////////////////////////////////////////////////////////////
// Widget observer tests.
//
class WidgetObserverTest : public WidgetTest, public WidgetObserver {
public:
WidgetObserverTest() = default;
~WidgetObserverTest() override = default;
// Set a widget to Close() the next time the Widget being observed is hidden.
void CloseOnNextHide(Widget* widget) { widget_to_close_on_hide_ = widget; }
// Overridden from WidgetObserver:
void OnWidgetDestroying(Widget* widget) override {
if (active_ == widget) {
active_ = nullptr;
}
if (widget_activated_ == widget) {
widget_activated_ = nullptr;
}
}
void OnWidgetActivationChanged(Widget* widget, bool active) override {
if (active) {
if (widget_activated_) {
widget_activated_->Deactivate();
}
widget_activated_ = widget;
active_ = widget;
} else {
if (widget_activated_ == widget) {
widget_activated_ = nullptr;
}
widget_deactivated_ = widget->GetName();
}
}
void OnWidgetVisibilityChanged(Widget* widget, bool visible) override {
if (visible) {
widget_shown_ = widget->GetName();
return;
}
widget_hidden_ = widget->GetName();
if (widget_to_close_on_hide_) {
std::exchange(widget_to_close_on_hide_, nullptr)->Close();
}
}
void OnWidgetBoundsChanged(Widget* widget,
const gfx::Rect& new_bounds) override {
widget_bounds_changed_ = widget->GetName();
}
void reset() {
active_ = nullptr;
widget_activated_ = nullptr;
widget_deactivated_.clear();
widget_shown_.clear();
widget_hidden_.clear();
widget_bounds_changed_.clear();
}
Widget* NewWidget(std::string name) {
Widget* widget = new Widget();
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
params.name = std::move(name);
widget->Init(std::move(params));
widget->AddObserver(this);
return widget;
}
const Widget* active() const { return active_; }
const Widget* widget_activated() const { return widget_activated_; }
const std::string& widget_deactivated() const { return widget_deactivated_; }
const std::string& widget_shown() const { return widget_shown_; }
const std::string& widget_hidden() const { return widget_hidden_; }
const std::string& widget_bounds_changed() const {
return widget_bounds_changed_;
}
private:
raw_ptr<Widget> active_ = nullptr;
raw_ptr<Widget> widget_activated_ = nullptr;
std::string widget_deactivated_;
std::string widget_shown_;
std::string widget_hidden_;
std::string widget_bounds_changed_;
raw_ptr<Widget> widget_to_close_on_hide_ = nullptr;
};
// This test appears to be flaky on Mac.
#if BUILDFLAG(IS_MAC)
#define MAYBE_ActivationChange DISABLED_ActivationChange
#else
#define MAYBE_ActivationChange ActivationChange
#endif
TEST_F(WidgetObserverTest, MAYBE_ActivationChange) {
WidgetAutoclosePtr toplevel1(NewWidget("top1"));
WidgetAutoclosePtr toplevel2(NewWidget("top2"));
toplevel1->Show();
toplevel2->Show();
reset();
toplevel1->Activate();
RunPendingMessages();
EXPECT_EQ(toplevel1.get(), widget_activated());
toplevel2->Activate();
RunPendingMessages();
EXPECT_EQ(toplevel1->GetName(), widget_deactivated());
EXPECT_EQ(toplevel2.get(), widget_activated());
EXPECT_EQ(toplevel2.get(), active());
}
namespace {
// This class simulates a focus manager that moves focus to a second widget when
// the first one is closed. It simulates a situation where a sequence of widget
// observers might try to call Widget::Close in response to a OnWidgetClosing().
class WidgetActivationForwarder : public TestWidgetObserver {
public:
WidgetActivationForwarder(Widget* current_active_widget,
Widget* widget_to_activate)
: TestWidgetObserver(current_active_widget),
widget_to_activate_(widget_to_activate) {}
WidgetActivationForwarder(const WidgetActivationForwarder&) = delete;
WidgetActivationForwarder& operator=(const WidgetActivationForwarder&) =
delete;
~WidgetActivationForwarder() override = default;
private:
// WidgetObserver overrides:
void OnWidgetClosing(Widget* widget) override {
widget->OnNativeWidgetActivationChanged(false);
widget_to_activate_->Activate();
}
void OnWidgetActivationChanged(Widget* widget, bool active) override {
if (!active) {
widget->Close();
}
}
raw_ptr<Widget> widget_to_activate_;
};
// This class observes a widget and counts the number of times OnWidgetClosing
// is called.
class WidgetCloseCounter : public TestWidgetObserver {
public:
explicit WidgetCloseCounter(Widget* widget) : TestWidgetObserver(widget) {}
WidgetCloseCounter(const WidgetCloseCounter&) = delete;
WidgetCloseCounter& operator=(const WidgetCloseCounter&) = delete;
~WidgetCloseCounter() override = default;
int close_count() const { return close_count_; }
private:
// WidgetObserver overrides:
void OnWidgetClosing(Widget* widget) override { close_count_++; }
int close_count_ = 0;
};
} // namespace
// Makes sure close notifications aren't sent more than once when a Widget is
// shutting down. Test for crbug.com/714334
TEST_F(WidgetObserverTest, CloseReentrancy) {
Widget* widget1 = CreateTopLevelPlatformWidget();
Widget* widget2 = CreateTopLevelPlatformWidget();
WidgetCloseCounter counter(widget1);
WidgetActivationForwarder focus_manager(widget1, widget2);
widget1->Close();
EXPECT_EQ(1, counter.close_count());
widget2->Close();
}
TEST_F(WidgetObserverTest, VisibilityChange) {
WidgetAutoclosePtr toplevel(CreateTopLevelPlatformWidget());
WidgetAutoclosePtr child1(NewWidget("child1"));
WidgetAutoclosePtr child2(NewWidget("child2"));
toplevel->Show();
child1->Show();
child2->Show();
reset();
child1->Hide();
EXPECT_EQ(child1->GetName(), widget_hidden());
child2->Hide();
EXPECT_EQ(child2->GetName(), widget_hidden());
child1->Show();
EXPECT_EQ(child1->GetName(), widget_shown());
child2->Show();
EXPECT_EQ(child2->GetName(), widget_shown());
}
TEST_F(WidgetObserverTest, DestroyBubble) {
// This test expect NativeWidgetAura, force its creation.
ViewsDelegate::GetInstance()->set_native_widget_factory(
ViewsDelegate::NativeWidgetFactory());
std::unique_ptr<Widget> anchor = base::WrapUnique(
CreateTopLevelPlatformWidget(Widget::InitParams::CLIENT_OWNS_WIDGET));
anchor->Show();
auto bubble_delegate = std::make_unique<WidgetTestBubbleDialogDelegateView>(
anchor->client_view());
{
std::unique_ptr<Widget> bubble_widget =
base::WrapUnique(BubbleDialogDelegateView::CreateBubble(
bubble_delegate.release(), Widget::InitParams::CLIENT_OWNS_WIDGET));
bubble_widget->Show();
}
anchor->Hide();
}
TEST_F(WidgetObserverTest, WidgetBoundsChanged) {
WidgetAutoclosePtr child1(NewWidget("child1"));
WidgetAutoclosePtr child2(NewWidget("child2"));
child1->OnNativeWidgetMove();
EXPECT_EQ(child1->GetName(), widget_bounds_changed());
child2->OnNativeWidgetMove();
EXPECT_EQ(child2->GetName(), widget_bounds_changed());
child1->OnNativeWidgetSizeChanged(gfx::Size());
EXPECT_EQ(child1->GetName(), widget_bounds_changed());
child2->OnNativeWidgetSizeChanged(gfx::Size());
EXPECT_EQ(child2->GetName(), widget_bounds_changed());
}
// An extension to WidgetBoundsChanged to ensure notifications are forwarded
// by the NativeWidget implementation.
TEST_F(WidgetObserverTest, WidgetBoundsChangedNative) {
// Don't use NewWidget(), so that the Init() flow can be observed to ensure
// consistency across platforms.
auto widget = std::make_unique<Widget>();
widget->AddObserver(this);
EXPECT_THAT(widget_bounds_changed(), IsEmpty());
Widget::InitParams params = CreateParams(
Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_WINDOW);
// Use an origin within the work area since platforms (e.g. Mac) may move a
// window into the work area when showing, triggering a bounds change.
params.bounds = gfx::Rect(50, 50, 100, 100);
params.name = "widget";
// Init causes a bounds change, even while not showing. Note some platforms
// cause a bounds change even when the bounds are empty. Mac does not.
widget->Init(std::move(params));
EXPECT_THAT(widget_bounds_changed(), Not(IsEmpty()));
reset();
// Resizing while hidden, triggers a change.
widget->SetSize(gfx::Size(160, 100));
EXPECT_FALSE(widget->IsVisible());
EXPECT_THAT(widget_bounds_changed(), Not(IsEmpty()));
reset();
// Setting the same size does nothing.
widget->SetSize(gfx::Size(160, 100));
EXPECT_THAT(widget_bounds_changed(), IsEmpty());
reset();
// Showing does nothing to the bounds.
widget->Show();
EXPECT_TRUE(widget->IsVisible());
EXPECT_THAT(widget_bounds_changed(), IsEmpty());
reset();
// Resizing while shown.
widget->SetSize(gfx::Size(170, 100));
EXPECT_THAT(widget_bounds_changed(), Not(IsEmpty()));
reset();
// Resize to the same thing while shown does nothing.
widget->SetSize(gfx::Size(170, 100));
EXPECT_THAT(widget_bounds_changed(), IsEmpty());
reset();
// Move, but don't change the size.
widget->SetBounds(gfx::Rect(110, 110, 170, 100));
EXPECT_THAT(widget_bounds_changed(), Not(IsEmpty()));
reset();
// Moving to the same place does nothing.
widget->SetBounds(gfx::Rect(110, 110, 170, 100));
EXPECT_THAT(widget_bounds_changed(), IsEmpty());
reset();
// No bounds change when closing.
std::exchange(widget, nullptr)->CloseNow();
EXPECT_THAT(widget_bounds_changed(), IsEmpty());
}
namespace {
class MoveTrackingTestDesktopWidgetDelegate : public TestDesktopWidgetDelegate {
public:
int move_count() const { return move_count_; }
// WidgetDelegate:
void OnWidgetMove() override { ++move_count_; }
private:
int move_count_ = 0;
};
} // namespace
class DesktopWidgetObserverTest : public WidgetObserverTest {
public:
DesktopWidgetObserverTest() = default;
DesktopWidgetObserverTest(const DesktopWidgetObserverTest&) = delete;
DesktopWidgetObserverTest& operator=(const DesktopWidgetObserverTest&) =
delete;
~DesktopWidgetObserverTest() override = default;
// WidgetObserverTest:
void SetUp() override {
set_native_widget_type(NativeWidgetType::kDesktop);
WidgetObserverTest::SetUp();
}
};
// An extension to the WidgetBoundsChangedNative test above to ensure move
// notifications propagate to the WidgetDelegate.
TEST_F(DesktopWidgetObserverTest, OnWidgetMovedWhenOriginChangesNative) {
MoveTrackingTestDesktopWidgetDelegate delegate;
Widget::InitParams params = CreateParams(
Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_WINDOW);
delegate.InitWidget(std::move(params));
Widget* widget = delegate.GetWidget();
widget->Show();
widget->SetBounds(gfx::Rect(100, 100, 300, 200));
const int moves_during_init = delegate.move_count();
// Resize without changing origin. No move.
widget->SetBounds(gfx::Rect(100, 100, 310, 210));
EXPECT_EQ(moves_during_init, delegate.move_count());
// Move without changing size. Moves.
widget->SetBounds(gfx::Rect(110, 110, 310, 210));
EXPECT_EQ(moves_during_init + 1, delegate.move_count());
// Changing both moves.
widget->SetBounds(gfx::Rect(90, 90, 330, 230));
EXPECT_EQ(moves_during_init + 2, delegate.move_count());
// Just grow vertically. On Mac, this changes the AppKit origin since it is
// from the bottom left of the screen, but there is no move as far as views is
// concerned.
widget->SetBounds(gfx::Rect(90, 90, 330, 240));
// No change.
EXPECT_EQ(moves_during_init + 2, delegate.move_count());
// For a similar reason, move the widget down by the same amount that it grows
// vertically. The AppKit origin does not change, but it is a move.
widget->SetBounds(gfx::Rect(90, 100, 330, 250));
EXPECT_EQ(moves_during_init + 3, delegate.move_count());
}
// Test correct behavior when widgets close themselves in response to visibility
// changes.
TEST_F(WidgetObserverTest, ClosingOnHiddenParent) {
WidgetAutoclosePtr parent(NewWidget("parent"));
Widget* child = CreateChildPlatformWidget(parent->GetNativeView());
TestWidgetObserver child_observer(child);
EXPECT_FALSE(parent->IsVisible());
EXPECT_FALSE(child->IsVisible());
// Note |child| is TYPE_CONTROL, which start shown. So no need to show the
// child separately.
parent->Show();
EXPECT_TRUE(parent->IsVisible());
EXPECT_TRUE(child->IsVisible());
// Simulate a child widget that closes itself when the parent is hidden.
CloseOnNextHide(child);
EXPECT_FALSE(child_observer.widget_closed());
parent->Hide();
RunPendingMessages();
EXPECT_TRUE(child_observer.widget_closed());
}
// Test behavior of NativeWidget*::GetWindowPlacement on the native desktop.
#if BUILDFLAG(IS_LINUX)
// On desktop-Linux cheat and use non-desktop widgets. On X11, minimize is
// asynchronous. Also (harder) showing a window doesn't activate it without
// user interaction (or extra steps only done for interactive ui tests).
// Without that, show_state remains in ui::mojom::WindowShowState::kInactive
// throughout.
// TODO(tapted): Find a nice way to run this with desktop widgets on Linux.
TEST_F(WidgetTest, GetWindowPlacement) {
#else
TEST_F(DesktopWidgetTest, GetWindowPlacement) {
#endif
WidgetAutoclosePtr widget;
widget.reset(CreateTopLevelNativeWidget());
gfx::Rect expected_bounds(100, 110, 200, 220);
widget->SetBounds(expected_bounds);
widget->Show();
// Start with something invalid to ensure it changes.
ui::mojom::WindowShowState show_state = ui::mojom::WindowShowState::kEnd;
gfx::Rect restored_bounds;
internal::NativeWidgetPrivate* native_widget =
widget->native_widget_private();
native_widget->GetWindowPlacement(&restored_bounds, &show_state);
EXPECT_EQ(expected_bounds, restored_bounds);
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
// Non-desktop/Ash widgets start off in "default" until a Restore().
EXPECT_EQ(ui::mojom::WindowShowState::kDefault, show_state);
widget->Restore();
native_widget->GetWindowPlacement(&restored_bounds, &show_state);
#endif
EXPECT_EQ(ui::mojom::WindowShowState::kNormal, show_state);
views::test::PropertyWaiter minimize_waiter(
base::BindRepeating(&Widget::IsMinimized, base::Unretained(widget.get())),
true);
widget->Minimize();
EXPECT_TRUE(minimize_waiter.Wait());
native_widget->GetWindowPlacement(&restored_bounds, &show_state);
EXPECT_EQ(ui::mojom::WindowShowState::kMinimized, show_state);
EXPECT_EQ(expected_bounds, restored_bounds);
views::test::PropertyWaiter restore_waiter(
base::BindRepeating(&Widget::IsMinimized, base::Unretained(widget.get())),
false);
widget->Restore();
EXPECT_TRUE(restore_waiter.Wait());
native_widget->GetWindowPlacement(&restored_bounds, &show_state);
EXPECT_EQ(ui::mojom::WindowShowState::kNormal, show_state);
EXPECT_EQ(expected_bounds, restored_bounds);
expected_bounds = gfx::Rect(130, 140, 230, 250);
widget->SetBounds(expected_bounds);
native_widget->GetWindowPlacement(&restored_bounds, &show_state);
EXPECT_EQ(ui::mojom::WindowShowState::kNormal, show_state);
EXPECT_EQ(expected_bounds, restored_bounds);
widget->SetFullscreen(true);
native_widget->GetWindowPlacement(&restored_bounds, &show_state);
#if BUILDFLAG(IS_WIN)
// Desktop Aura widgets on Windows currently don't update show_state when
// going fullscreen, and report restored_bounds as the full screen size.
// See http://crbug.com/475813.
EXPECT_EQ(ui::mojom::WindowShowState::kNormal, show_state);
#else
EXPECT_EQ(ui::mojom::WindowShowState::kFullscreen, show_state);
EXPECT_EQ(expected_bounds, restored_bounds);
#endif
widget->SetFullscreen(false);
native_widget->GetWindowPlacement(&restored_bounds, &show_state);
EXPECT_EQ(ui::mojom::WindowShowState::kNormal, show_state);
EXPECT_EQ(expected_bounds, restored_bounds);
}
// Test that widget size constraints are properly applied immediately after
// Init(), and that SetBounds() calls are appropriately clamped.
TEST_F(DesktopWidgetTest, MinimumSizeConstraints) {
TestDesktopWidgetDelegate delegate;
gfx::Size minimum_size(100, 100);
const gfx::Size smaller_size(90, 90);
delegate.set_contents_view(new StaticSizedView(minimum_size));
delegate.InitWidget(CreateParams(Widget::InitParams::CLIENT_OWNS_WIDGET,
Widget::InitParams::TYPE_WINDOW));
Widget* widget = delegate.GetWidget();
// On desktop Linux, the Widget must be shown to ensure the window is mapped.
// On other platforms this line is optional.
widget->Show();
// Sanity checks.
EXPECT_GT(delegate.initial_bounds().width(), minimum_size.width());
EXPECT_GT(delegate.initial_bounds().height(), minimum_size.height());
EXPECT_EQ(delegate.initial_bounds().size(),
widget->GetWindowBoundsInScreen().size());
// Note: StaticSizedView doesn't currently provide a maximum size.
EXPECT_EQ(gfx::Size(), widget->GetMaximumSize());
if (!widget->ShouldUseNativeFrame()) {
// The test environment may have dwm disabled on Windows. In this case,
// DefaultFrameView is used instead of the NativeFrameView, which will
// provide a minimum size that includes frame decorations.
minimum_size = widget->non_client_view()
->GetWindowBoundsForClientBounds(gfx::Rect(minimum_size))
.size();
}
EXPECT_EQ(minimum_size, widget->GetMinimumSize());
EXPECT_EQ(minimum_size, GetNativeWidgetMinimumContentSize(widget));
// Trying to resize smaller than the minimum size should restrict the content
// size to the minimum size.
widget->SetBounds(gfx::Rect(smaller_size));
EXPECT_EQ(minimum_size, widget->GetClientAreaBoundsInScreen().size());
widget->SetSize(smaller_size);
EXPECT_EQ(minimum_size, widget->GetClientAreaBoundsInScreen().size());
}
// When a non-desktop widget has a desktop child widget, due to the
// async nature of desktop widget shutdown, the parent can be destroyed before
// its child. Make sure that parent() returns nullptr at this time.
#if BUILDFLAG(ENABLE_DESKTOP_AURA)
TEST_F(DesktopWidgetTest, GetPossiblyDestroyedParent) {
WidgetAutoclosePtr root(CreateTopLevelNativeWidget());
const auto create_widget = [](Widget* parent, bool is_desktop) {
Widget* widget = new Widget;
Widget::InitParams init_params(
Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
Widget::InitParams::TYPE_WINDOW);
init_params.parent = parent->GetNativeView();
init_params.context = parent->GetNativeView();
if (is_desktop) {
init_params.native_widget =
new test::TestPlatformNativeWidget<DesktopNativeWidgetAura>(
widget, false, nullptr);
} else {
init_params.native_widget =
new test::TestPlatformNativeWidget<NativeWidgetAura>(widget, false,
nullptr);
}
widget->Init(std::move(init_params));
return widget;
};
WidgetAutoclosePtr child(create_widget(root.get(), /* non-desktop */ false));
WidgetAutoclosePtr grandchild(create_widget(child.get(), /* desktop */ true));
child.reset();
EXPECT_EQ(grandchild->parent(), nullptr);
}
#endif // BUILDFLAG(ENABLE_DESKTOP_AURA)
// Tests that SetBounds() and GetWindowBoundsInScreen() is symmetric when the
// widget is visible and not maximized or fullscreen.
TEST_F(WidgetTest, GetWindowBoundsInScreen) {
// Choose test coordinates away from edges and dimensions that are "small"
// (but not too small) to ensure the OS doesn't try to adjust them.
const gfx::Rect kTestBounds(150, 150, 400, 300);
const gfx::Size kTestSize(200, 180);
{
// First test a toplevel widget.
WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
widget->Show();
EXPECT_NE(kTestSize.ToString(),
widget->GetWindowBoundsInScreen().size().ToString());
widget->SetSize(kTestSize);
EXPECT_EQ(kTestSize.ToString(),
widget->GetWindowBoundsInScreen().size().ToString());
EXPECT_NE(kTestBounds.ToString(),
widget->GetWindowBoundsInScreen().ToString());
widget->SetBounds(kTestBounds);
EXPECT_EQ(kTestBounds.ToString(),
widget->GetWindowBoundsInScreen().ToString());
// Changing just the size should not change the origin.
widget->SetSize(kTestSize);
EXPECT_EQ(kTestBounds.origin().ToString(),
widget->GetWindowBoundsInScreen().origin().ToString());
}
// Same tests with a frameless window.
WidgetAutoclosePtr widget(CreateTopLevelFramelessPlatformWidget());
widget->Show();
EXPECT_NE(kTestSize.ToString(),
widget->GetWindowBoundsInScreen().size().ToString());
widget->SetSize(kTestSize);
EXPECT_EQ(kTestSize.ToString(),
widget->GetWindowBoundsInScreen().size().ToString());
EXPECT_NE(kTestBounds.ToString(),
widget->GetWindowBoundsInScreen().ToString());
widget->SetBounds(kTestBounds);
EXPECT_EQ(kTestBounds.ToString(),
widget->GetWindowBoundsInScreen().ToString());
// For a frameless widget, the client bounds should also match.
EXPECT_EQ(kTestBounds.ToString(),
widget->GetClientAreaBoundsInScreen().ToString());
// Verify origin is stable for a frameless window as well.
widget->SetSize(kTestSize);
EXPECT_EQ(kTestBounds.origin().ToString(),
widget->GetWindowBoundsInScreen().origin().ToString());
}
// Chrome OS widgets need the shell to maximize/fullscreen window.
// Disable on desktop Linux because windows restore to the wrong bounds.
// See http://crbug.com/515369.
#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
#define MAYBE_GetRestoredBounds DISABLED_GetRestoredBounds
#else
#define MAYBE_GetRestoredBounds GetRestoredBounds
#endif
// Test that GetRestoredBounds() returns the original bounds of the window.
TEST_F(DesktopWidgetTest, MAYBE_GetRestoredBounds) {
WidgetAutoclosePtr toplevel(CreateTopLevelNativeWidget());
toplevel->Show();
// Initial restored bounds have non-zero size.
EXPECT_FALSE(toplevel->GetRestoredBounds().IsEmpty());
const gfx::Rect bounds(100, 100, 200, 200);
toplevel->SetBounds(bounds);
EXPECT_EQ(bounds, toplevel->GetWindowBoundsInScreen());
EXPECT_EQ(bounds, toplevel->GetRestoredBounds());
toplevel->Maximize();
RunPendingMessages();
#if BUILDFLAG(IS_MAC)
// Current expectation on Mac is to do nothing on Maximize.
EXPECT_EQ(toplevel->GetWindowBoundsInScreen(), toplevel->GetRestoredBounds());
#else
EXPECT_NE(toplevel->GetWindowBoundsInScreen(), toplevel->GetRestoredBounds());
#endif
EXPECT_EQ(bounds, toplevel->GetRestoredBounds());
toplevel->Restore();
RunPendingMessages();
EXPECT_EQ(bounds, toplevel->GetWindowBoundsInScreen());
EXPECT_EQ(bounds, toplevel->GetRestoredBounds());
toplevel->SetFullscreen(true);
RunPendingMessages();
EXPECT_NE(toplevel->GetWindowBoundsInScreen(), toplevel->GetRestoredBounds());
EXPECT_EQ(bounds, toplevel->GetRestoredBounds());
toplevel->SetFullscreen(false);
RunPendingMessages();
EXPECT_EQ(bounds, toplevel->GetWindowBoundsInScreen());
EXPECT_EQ(bounds, toplevel->GetRestoredBounds());
}
// The key-event propagation from Widget happens differently on aura and
// non-aura systems because of the difference in IME. So this test works only on
// aura.
TEST_F(WidgetTest, KeyboardInputEvent) {
WidgetAutoclosePtr toplevel(CreateTopLevelPlatformWidget());
View* container = toplevel->client_view();
Textfield* textfield = new Textfield();
textfield->SetText(u"some text");
container->AddChildViewRaw(textfield);
toplevel->Show();
textfield->RequestFocus();
// The press gets handled. The release doesn't have an effect.
ui::KeyEvent backspace_p(ui::EventType::kKeyPressed, ui::VKEY_DELETE,
ui::EF_NONE);
toplevel->OnKeyEvent(&backspace_p);
EXPECT_TRUE(backspace_p.stopped_propagation());
ui::KeyEvent backspace_r(ui::EventType::kKeyReleased, ui::VKEY_DELETE,
ui::EF_NONE);
toplevel->OnKeyEvent(&backspace_r);
EXPECT_FALSE(backspace_r.handled());
}
TEST_F(WidgetTest, BubbleControlsResetOnInit) {
std::unique_ptr<Widget> anchor = base::WrapUnique(
CreateTopLevelPlatformWidget(Widget::InitParams::CLIENT_OWNS_WIDGET));
anchor->Show();
{
auto bubble_delegate = std::make_unique<WidgetTestBubbleDialogDelegateView>(
anchor->client_view());
auto* bubble_delegate_ptr = bubble_delegate.get();
std::unique_ptr<Widget> bubble_widget =
base::WrapUnique(BubbleDialogDelegateView::CreateBubble(
bubble_delegate.release(), Widget::InitParams::CLIENT_OWNS_WIDGET));
EXPECT_TRUE(bubble_delegate_ptr->reset_controls_called_);
bubble_widget->Show();
}
anchor->Hide();
}
#if BUILDFLAG(IS_WIN)
// Test to ensure that after minimize, view width is set to zero. This is only
// the case for desktop widgets on Windows. Other platforms retain the window
// size while minimized.
TEST_F(DesktopWidgetTest, TestViewWidthAfterMinimizingWidget) {
// Create a widget.
std::unique_ptr<Widget> widget = CreateTestWidget(
Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_WINDOW);
NonClientView* non_client_view = widget->non_client_view();
non_client_view->SetFrameView(CreateMinimumSizeFrameView(widget.get()));
// Setting the frame view doesn't do a layout, so force one.
non_client_view->InvalidateLayout();
views::test::RunScheduledLayout(non_client_view);
widget->Show();
EXPECT_NE(0, non_client_view->frame_view()->width());
widget->Minimize();
EXPECT_EQ(0, non_client_view->frame_view()->width());
}
#endif
// Desktop native widget Aura tests are for non Chrome OS platforms.
// This class validates whether paints are received for a visible Widget.
// It observes Widget visibility and Close() and tracks whether subsequent
// paints are expected.
class DesktopAuraTestValidPaintWidget : public Widget, public WidgetObserver {
public:
explicit DesktopAuraTestValidPaintWidget(Widget::InitParams init_params)
: Widget(std::move(init_params)) {
observation_.Observe(this);
}
DesktopAuraTestValidPaintWidget(const DesktopAuraTestValidPaintWidget&) =
delete;
DesktopAuraTestValidPaintWidget& operator=(
const DesktopAuraTestValidPaintWidget&) = delete;
~DesktopAuraTestValidPaintWidget() override = default;
bool ReadReceivedPaintAndReset() {
return std::exchange(received_paint_, false);
}
bool received_paint_while_hidden() const {
return received_paint_while_hidden_;
}
void WaitUntilPaint() {
if (received_paint_) {
return;
}
base::RunLoop runloop;
quit_closure_ = runloop.QuitClosure();
runloop.Run();
quit_closure_.Reset();
}
// WidgetObserver:
void OnWidgetDestroying(Widget* widget) override { expect_paint_ = false; }
void OnNativeWidgetPaint(const ui::PaintContext& context) override {
received_paint_ = true;
EXPECT_TRUE(expect_paint_);
if (!expect_paint_) {
received_paint_while_hidden_ = true;
}
Widget::OnNativeWidgetPaint(context);
if (!quit_closure_.is_null()) {
std::move(quit_closure_).Run();
}
}
void OnWidgetVisibilityChanged(Widget* widget, bool visible) override {
expect_paint_ = visible;
}
private:
bool received_paint_ = false;
bool expect_paint_ = true;
bool received_paint_while_hidden_ = false;
base::OnceClosure quit_closure_;
base::ScopedObservation<Widget, WidgetObserver> observation_{this};
};
namespace {
class ContentsView : public View {
METADATA_HEADER(ContentsView, View)
public:
ContentsView() {
GetViewAccessibility().SetRole(ax::mojom::Role::kDialog);
GetViewAccessibility().SetName(
std::string(), ax::mojom::NameFrom::kAttributeExplicitlyEmpty);
}
};
BEGIN_METADATA(ContentsView)
END_METADATA
} // namespace
class DesktopAuraPaintWidgetTest : public DesktopWidgetTest {
public:
std::unique_ptr<DesktopAuraTestValidPaintWidget>
CreateDesktopAuraTestValidPaintWidget(
Widget::InitParams::Type type =
Widget::InitParams::TYPE_WINDOW_FRAMELESS) {
auto widget = std::make_unique<DesktopAuraTestValidPaintWidget>(
CreateParamsForTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET,
type));
View* contents_view =
widget->SetContentsView(std::make_unique<ContentsView>());
contents_view->SetFocusBehavior(View::FocusBehavior::ALWAYS);
widget->Show();
widget->Activate();
return widget;
}
};
TEST_F(DesktopAuraPaintWidgetTest, DesktopNativeWidgetNoPaintAfterCloseTest) {
auto widget = CreateDesktopAuraTestValidPaintWidget();
widget->WaitUntilPaint();
EXPECT_TRUE(widget->ReadReceivedPaintAndReset());
widget->SchedulePaintInRect(widget->GetRestoredBounds());
widget->Close();
RunPendingMessages();
EXPECT_FALSE(widget->ReadReceivedPaintAndReset());
EXPECT_FALSE(widget->received_paint_while_hidden());
}
TEST_F(DesktopAuraPaintWidgetTest, DesktopNativeWidgetNoPaintAfterHideTest) {
auto widget = CreateDesktopAuraTestValidPaintWidget();
widget->WaitUntilPaint();
EXPECT_TRUE(widget->ReadReceivedPaintAndReset());
widget->SchedulePaintInRect(widget->GetRestoredBounds());
widget->Hide();
RunPendingMessages();
EXPECT_FALSE(widget->ReadReceivedPaintAndReset());
EXPECT_FALSE(widget->received_paint_while_hidden());
widget->Close();
}
// Test to ensure that the aura Window's visibility state is set to visible if
// the underlying widget is hidden and then shown.
TEST_F(DesktopWidgetTest, TestWindowVisibilityAfterHide) {
// Create a widget.
std::unique_ptr<Widget> widget = CreateTestWidget(
Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_WINDOW);
NonClientView* non_client_view = widget->non_client_view();
non_client_view->SetFrameView(CreateMinimumSizeFrameView(widget.get()));
widget->Show();
EXPECT_TRUE(IsNativeWindowVisible(widget->GetNativeWindow()));
widget->Hide();
EXPECT_FALSE(IsNativeWindowVisible(widget->GetNativeWindow()));
widget->Show();
EXPECT_TRUE(IsNativeWindowVisible(widget->GetNativeWindow()));
}
// Tests that wheel events generated from scroll events are targeted to the
// views under the cursor when the focused view does not processed them.
TEST_F(WidgetTest, WheelEventsFromScrollEventTarget) {
EventCountView* cursor_view = new EventCountView;
cursor_view->SetBounds(60, 0, 50, 40);
WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
widget->GetRootView()->AddChildViewRaw(cursor_view);
// Generate a scroll event on the cursor view.
ui::ScrollEvent scroll(ui::EventType::kScroll, gfx::Point(65, 5),
ui::EventTimeForNow(), 0, 0, 20, 0, 20, 2);
widget->OnScrollEvent(&scroll);
EXPECT_EQ(1, cursor_view->GetEventCount(ui::EventType::kScroll));
EXPECT_EQ(1, cursor_view->GetEventCount(ui::EventType::kMousewheel));
cursor_view->ResetCounts();
ui::ScrollEvent scroll2(ui::EventType::kScroll, gfx::Point(5, 5),
ui::EventTimeForNow(), 0, 0, 20, 0, 20, 2);
widget->OnScrollEvent(&scroll2);
EXPECT_EQ(0, cursor_view->GetEventCount(ui::EventType::kScroll));
EXPECT_EQ(0, cursor_view->GetEventCount(ui::EventType::kMousewheel));
}
// Tests that if a scroll-begin gesture is not handled, then subsequent scroll
// events are not dispatched to any view.
TEST_F(WidgetTest, GestureScrollEventDispatching) {
EventCountView* noscroll_view = new EventCountView;
EventCountView* scroll_view = new ScrollableEventCountView;
noscroll_view->SetBounds(0, 0, 50, 40);
scroll_view->SetBounds(60, 0, 40, 40);
WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
widget->GetRootView()->AddChildViewRaw(noscroll_view);
widget->GetRootView()->AddChildViewRaw(scroll_view);
{
ui::GestureEvent begin =
CreateTestGestureEvent(ui::EventType::kGestureScrollBegin, 5, 5);
widget->OnGestureEvent(&begin);
ui::GestureEvent update = CreateTestGestureEvent(
ui::GestureEventDetails(ui::EventType::kGestureScrollUpdate, 20, 10),
25, 15);
widget->OnGestureEvent(&update);
ui::GestureEvent end =
CreateTestGestureEvent(ui::EventType::kGestureScrollEnd, 25, 15);
widget->OnGestureEvent(&end);
EXPECT_EQ(1,
noscroll_view->GetEventCount(ui::EventType::kGestureScrollBegin));
EXPECT_EQ(
0, noscroll_view->GetEventCount(ui::EventType::kGestureScrollUpdate));
EXPECT_EQ(0,
noscroll_view->GetEventCount(ui::EventType::kGestureScrollEnd));
}
{
ui::GestureEvent begin =
CreateTestGestureEvent(ui::EventType::kGestureScrollBegin, 65, 5);
widget->OnGestureEvent(&begin);
ui::GestureEvent update = CreateTestGestureEvent(
ui::GestureEventDetails(ui::EventType::kGestureScrollUpdate, 20, 10),
85, 15);
widget->OnGestureEvent(&update);
ui::GestureEvent end =
CreateTestGestureEvent(ui::EventType::kGestureScrollEnd, 85, 15);
widget->OnGestureEvent(&end);
EXPECT_EQ(1,
scroll_view->GetEventCount(ui::EventType::kGestureScrollBegin));
EXPECT_EQ(1,
scroll_view->GetEventCount(ui::EventType::kGestureScrollUpdate));
EXPECT_EQ(1, scroll_view->GetEventCount(ui::EventType::kGestureScrollEnd));
}
}
// Tests that event-handlers installed on the RootView get triggered correctly.
// TODO(tdanderson): Clean up this test as part of crbug.com/355680.
TEST_F(WidgetTest, EventHandlersOnRootView) {
WidgetAutoclosePtr widget(CreateTopLevelNativeWidget());
View* root_view = widget->GetRootView();
EventCountView* view =
root_view->AddChildView(std::make_unique<EventCountView>());
view->SetBounds(0, 0, 20, 20);
EventCountHandler h1;
root_view->AddPreTargetHandler(&h1);
EventCountHandler h2;
root_view->AddPostTargetHandler(&h2);
widget->SetBounds(gfx::Rect(0, 0, 100, 100));
widget->Show();
// Dispatch a ui::EventType::kScroll event. The event remains unhandled and
// should bubble up the views hierarchy to be re-dispatched on the root view.
ui::ScrollEvent scroll(ui::EventType::kScroll, gfx::Point(5, 5),
ui::EventTimeForNow(), 0, 0, 20, 0, 20, 2);
widget->OnScrollEvent(&scroll);
EXPECT_EQ(2, h1.GetEventCount(ui::EventType::kScroll));
EXPECT_EQ(1, view->GetEventCount(ui::EventType::kScroll));
EXPECT_EQ(2, h2.GetEventCount(ui::EventType::kScroll));
// Unhandled scroll events are turned into wheel events and re-dispatched.
EXPECT_EQ(1, h1.GetEventCount(ui::EventType::kMousewheel));
EXPECT_EQ(1, view->GetEventCount(ui::EventType::kMousewheel));
EXPECT_EQ(1, h2.GetEventCount(ui::EventType::kMousewheel));
h1.ResetCounts();
view->ResetCounts();
h2.ResetCounts();
// Dispatch a ui::EventType::kScrollFlingStart event. The event remains
// unhandled and should bubble up the views hierarchy to be re-dispatched on
// the root view.
ui::ScrollEvent fling(ui::EventType::kScrollFlingStart, gfx::Point(5, 5),
ui::EventTimeForNow(), 0, 0, 20, 0, 20, 2);
widget->OnScrollEvent(&fling);
EXPECT_EQ(2, h1.GetEventCount(ui::EventType::kScrollFlingStart));
EXPECT_EQ(1, view->GetEventCount(ui::EventType::kScrollFlingStart));
EXPECT_EQ(2, h2.GetEventCount(ui::EventType::kScrollFlingStart));
// Unhandled scroll events which are not of type ui::EventType::kScroll should
// not be turned into wheel events and re-dispatched.
EXPECT_EQ(0, h1.GetEventCount(ui::EventType::kMousewheel));
EXPECT_EQ(0, view->GetEventCount(ui::EventType::kMousewheel));
EXPECT_EQ(0, h2.GetEventCount(ui::EventType::kMousewheel));
h1.ResetCounts();
view->ResetCounts();
h2.ResetCounts();
// Change the handle mode of |view| so that events are marked as handled at
// the target phase.
view->set_handle_mode(EventCountView::HandleMode::kConsumeEvents);
// Dispatch a ui::EventType::kGestureTapDown and a
// ui::EventType::kGestureTapCancel event. The events are handled at the
// target phase and should not reach the post-target handler.
ui::GestureEvent tap_down =
CreateTestGestureEvent(ui::EventType::kGestureTapDown, 5, 5);
widget->OnGestureEvent(&tap_down);
EXPECT_EQ(1, h1.GetEventCount(ui::EventType::kGestureTapDown));
EXPECT_EQ(1, view->GetEventCount(ui::EventType::kGestureTapDown));
EXPECT_EQ(0, h2.GetEventCount(ui::EventType::kGestureTapDown));
ui::GestureEvent tap_cancel =
CreateTestGestureEvent(ui::EventType::kGestureTapCancel, 5, 5);
widget->OnGestureEvent(&tap_cancel);
EXPECT_EQ(1, h1.GetEventCount(ui::EventType::kGestureTapCancel));
EXPECT_EQ(1, view->GetEventCount(ui::EventType::kGestureTapCancel));
EXPECT_EQ(0, h2.GetEventCount(ui::EventType::kGestureTapCancel));
h1.ResetCounts();
view->ResetCounts();
h2.ResetCounts();
// Dispatch a ui::EventType::kScroll event. The event is handled at the target
// phase and should not reach the post-target handler.
ui::ScrollEvent consumed_scroll(ui::EventType::kScroll, gfx::Point(5, 5),
ui::EventTimeForNow(), 0, 0, 20, 0, 20, 2);
widget->OnScrollEvent(&consumed_scroll);
EXPECT_EQ(1, h1.GetEventCount(ui::EventType::kScroll));
EXPECT_EQ(1, view->GetEventCount(ui::EventType::kScroll));
EXPECT_EQ(0, h2.GetEventCount(ui::EventType::kScroll));
// Handled scroll events are not turned into wheel events and re-dispatched.
EXPECT_EQ(0, h1.GetEventCount(ui::EventType::kMousewheel));
EXPECT_EQ(0, view->GetEventCount(ui::EventType::kMousewheel));
EXPECT_EQ(0, h2.GetEventCount(ui::EventType::kMousewheel));
root_view->RemovePreTargetHandler(&h1);
}
TEST_F(WidgetTest, SynthesizeMouseMoveEvent) {
WidgetAutoclosePtr widget(CreateTopLevelNativeWidget());
View* root_view = widget->GetRootView();
widget->SetBounds(gfx::Rect(0, 0, 100, 100));
EventCountView* v1 =
root_view->AddChildView(std::make_unique<EventCountView>());
v1->SetBounds(5, 5, 10, 10);
EventCountView* v2 =
root_view->AddChildView(std::make_unique<EventCountView>());
v2->SetBounds(5, 15, 10, 10);
widget->Show();
// SynthesizeMouseMoveEvent does nothing until the mouse is entered.
widget->SynthesizeMouseMoveEvent();
EXPECT_EQ(0, v1->GetEventCount(ui::EventType::kMouseMoved));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kMouseMoved));
gfx::Point cursor_location(v1->GetBoundsInScreen().CenterPoint());
auto generator =
CreateEventGenerator(GetContext(), widget->GetNativeWindow());
generator->MoveMouseTo(cursor_location);
EXPECT_EQ(1, v1->GetEventCount(ui::EventType::kMouseMoved));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kMouseMoved));
// SynthesizeMouseMoveEvent dispatches an mousemove event.
widget->SynthesizeMouseMoveEvent();
EXPECT_EQ(2, v1->GetEventCount(ui::EventType::kMouseMoved));
root_view->RemoveChildViewT(v1);
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kMouseMoved));
v2->SetBounds(5, 5, 10, 10);
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kMouseMoved));
widget->SynthesizeMouseMoveEvent();
EXPECT_EQ(1, v2->GetEventCount(ui::EventType::kMouseMoved));
}
namespace {
// ui::EventHandler which handles all mouse press events.
class MousePressEventConsumer : public ui::EventHandler {
public:
MousePressEventConsumer() = default;
MousePressEventConsumer(const MousePressEventConsumer&) = delete;
MousePressEventConsumer& operator=(const MousePressEventConsumer&) = delete;
private:
// ui::EventHandler:
void OnMouseEvent(ui::MouseEvent* event) override {
if (event->type() == ui::EventType::kMousePressed) {
event->SetHandled();
}
}
};
} // namespace
// No touch on desktop Mac. Tracked in http://crbug.com/445520.
#if !BUILDFLAG(IS_MAC) || defined(USE_AURA)
// Test that mouse presses and mouse releases are dispatched normally when a
// touch is down.
TEST_F(WidgetTest, MouseEventDispatchWhileTouchIsDown) {
Widget* widget = CreateTopLevelNativeWidget();
widget->Show();
widget->SetSize(gfx::Size(300, 300));
EventCountView* event_count_view =
widget->GetRootView()->AddChildView(std::make_unique<EventCountView>());
event_count_view->SetBounds(0, 0, 300, 300);
MousePressEventConsumer consumer;
event_count_view->AddPostTargetHandler(&consumer);
auto generator =
CreateEventGenerator(GetContext(), widget->GetNativeWindow());
generator->PressTouch();
generator->ClickLeftButton();
EXPECT_EQ(1, event_count_view->GetEventCount(ui::EventType::kMousePressed));
EXPECT_EQ(1, event_count_view->GetEventCount(ui::EventType::kMouseReleased));
// For mus it's important we destroy the widget before the EventGenerator.
widget->CloseNow();
}
#endif // !BUILDFLAG(IS_MAC) || defined(USE_AURA)
// Tests that when there is no active capture, that a mouse press causes capture
// to be set.
TEST_F(WidgetTest, MousePressCausesCapture) {
Widget* widget = CreateTopLevelNativeWidget();
widget->Show();
widget->SetSize(gfx::Size(300, 300));
EventCountView* event_count_view =
widget->GetRootView()->AddChildView(std::make_unique<EventCountView>());
event_count_view->SetBounds(0, 0, 300, 300);
// No capture has been set.
EXPECT_EQ(gfx::NativeView(), internal::NativeWidgetPrivate::GetGlobalCapture(
widget->GetNativeView()));
MousePressEventConsumer consumer;
event_count_view->AddPostTargetHandler(&consumer);
auto generator =
CreateEventGenerator(GetContext(), widget->GetNativeWindow());
generator->MoveMouseTo(widget->GetClientAreaBoundsInScreen().CenterPoint());
generator->PressLeftButton();
EXPECT_EQ(1, event_count_view->GetEventCount(ui::EventType::kMousePressed));
EXPECT_EQ(
widget->GetNativeView(),
internal::NativeWidgetPrivate::GetGlobalCapture(widget->GetNativeView()));
// For mus it's important we destroy the widget before the EventGenerator.
widget->CloseNow();
}
namespace {
// An EventHandler which shows a Widget upon receiving a mouse event. The Widget
// proceeds to take capture.
class CaptureEventConsumer : public ui::EventHandler {
public:
explicit CaptureEventConsumer(Widget* widget) : widget_(widget) {}
CaptureEventConsumer(const CaptureEventConsumer&) = delete;
CaptureEventConsumer& operator=(const CaptureEventConsumer&) = delete;
~CaptureEventConsumer() override { widget_.ExtractAsDangling()->CloseNow(); }
private:
// ui::EventHandler:
void OnMouseEvent(ui::MouseEvent* event) override {
if (event->type() == ui::EventType::kMousePressed) {
event->SetHandled();
widget_->Show();
widget_->SetSize(gfx::Size(200, 200));
auto event_count_view = std::make_unique<EventCountView>();
event_count_view->SetBounds(0, 0, 200, 200);
widget_->SetCapture(
widget_->GetRootView()->AddChildView(std::move(event_count_view)));
}
}
raw_ptr<Widget> widget_;
};
} // namespace
// Tests that if explicit capture occurs during a mouse press, that implicit
// capture is not applied.
TEST_F(WidgetTest, CaptureDuringMousePressNotOverridden) {
Widget* widget = CreateTopLevelNativeWidget();
widget->Show();
widget->SetSize(gfx::Size(300, 300));
EventCountView* event_count_view =
widget->GetRootView()->AddChildView(std::make_unique<EventCountView>());
event_count_view->SetBounds(0, 0, 300, 300);
EXPECT_EQ(gfx::NativeView(), internal::NativeWidgetPrivate::GetGlobalCapture(
widget->GetNativeView()));
Widget* widget2 = CreateTopLevelNativeWidget();
// Gives explicit capture to |widget2|
CaptureEventConsumer consumer(widget2);
event_count_view->AddPostTargetHandler(&consumer);
auto generator =
CreateEventGenerator(GetRootWindow(widget), widget->GetNativeWindow());
generator->MoveMouseTo(widget->GetClientAreaBoundsInScreen().CenterPoint());
// This event should implicitly give capture to |widget|, except that
// |consumer| will explicitly set capture on |widget2|.
generator->PressLeftButton();
EXPECT_EQ(1, event_count_view->GetEventCount(ui::EventType::kMousePressed));
EXPECT_NE(
widget->GetNativeView(),
internal::NativeWidgetPrivate::GetGlobalCapture(widget->GetNativeView()));
EXPECT_EQ(
widget2->GetNativeView(),
internal::NativeWidgetPrivate::GetGlobalCapture(widget->GetNativeView()));
// For mus it's important we destroy the widget before the EventGenerator.
widget->CloseNow();
}
class ClosingEventObserver : public ui::EventObserver {
public:
explicit ClosingEventObserver(Widget* widget) : widget_(widget) {}
ClosingEventObserver(const ClosingEventObserver&) = delete;
ClosingEventObserver& operator=(const ClosingEventObserver&) = delete;
// ui::EventObserver:
void OnEvent(const ui::Event& event) override {
// Guard against attempting to close the widget twice.
if (widget_) {
widget_.ExtractAsDangling()->CloseNow();
}
}
private:
raw_ptr<Widget, DanglingUntriaged> widget_;
};
class ClosingView : public View {
METADATA_HEADER(ClosingView, View)
public:
explicit ClosingView(Widget* widget) : widget_(widget) {}
ClosingView(const ClosingView&) = delete;
ClosingView& operator=(const ClosingView&) = delete;
// View:
void OnEvent(ui::Event* event) override {
// Guard against closing twice and writing to freed memory.
if (widget_ && event->type() == ui::EventType::kMousePressed) {
Widget* widget = widget_;
widget_ = nullptr;
widget->CloseNow();
}
}
private:
raw_ptr<Widget> widget_;
};
BEGIN_METADATA(ClosingView)
END_METADATA
// Ensures that when multiple objects are intercepting OS-level events, that one
// can safely close a Widget that has capture.
TEST_F(WidgetTest, DestroyedWithCaptureViaEventMonitor) {
Widget* widget = CreateTopLevelNativeWidget();
TestWidgetObserver observer(widget);
widget->Show();
widget->SetSize(gfx::Size(300, 300));
// ClosingView and ClosingEventObserver both try to close the Widget. On Mac
// the order that EventMonitors receive OS events is not deterministic. If the
// one installed via SetCapture() sees it first, the event is swallowed (so
// both need to try). Note the regression test would only fail when the
// SetCapture() handler did _not_ swallow the event, but it still needs to try
// to close the Widget otherwise it will be left open, which fails elsewhere.
ClosingView* closing_view = widget->GetContentsView()->AddChildView(
std::make_unique<ClosingView>(widget));
widget->SetCapture(closing_view);
ClosingEventObserver closing_event_observer(widget);
auto monitor = EventMonitor::CreateApplicationMonitor(
&closing_event_observer, widget->GetNativeWindow(),
{ui::EventType::kMousePressed});
auto generator =
CreateEventGenerator(GetContext(), widget->GetNativeWindow());
generator->set_target(ui::test::EventGenerator::Target::APPLICATION);
EXPECT_FALSE(observer.widget_closed());
generator->PressLeftButton();
EXPECT_TRUE(observer.widget_closed());
}
TEST_F(WidgetTest, LockPaintAsActive) {
WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
widget->ShowInactive();
EXPECT_FALSE(widget->ShouldPaintAsActive());
// First lock causes widget to paint as active.
auto lock = widget->LockPaintAsActive();
EXPECT_TRUE(widget->ShouldPaintAsActive());
// Second lock has no effect.
auto lock2 = widget->LockPaintAsActive();
EXPECT_TRUE(widget->ShouldPaintAsActive());
// Have to release twice to get back to inactive state.
lock2.reset();
EXPECT_TRUE(widget->ShouldPaintAsActive());
lock.reset();
EXPECT_FALSE(widget->ShouldPaintAsActive());
}
TEST_F(WidgetTest, LockPaintAsActive_AlreadyActive) {
WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
widget->Show();
EXPECT_TRUE(widget->ShouldPaintAsActive());
// Lock has no effect.
auto lock = widget->LockPaintAsActive();
EXPECT_TRUE(widget->ShouldPaintAsActive());
// Remove lock has no effect.
lock.reset();
EXPECT_TRUE(widget->ShouldPaintAsActive());
}
TEST_F(WidgetTest, LockPaintAsActive_BecomesActive) {
WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
widget->ShowInactive();
EXPECT_FALSE(widget->ShouldPaintAsActive());
// Lock toggles render mode.
auto lock = widget->LockPaintAsActive();
EXPECT_TRUE(widget->ShouldPaintAsActive());
widget->Activate();
// Remove lock has no effect.
lock.reset();
EXPECT_TRUE(widget->ShouldPaintAsActive());
}
class PaintAsActiveCallbackCounter {
public:
explicit PaintAsActiveCallbackCounter(Widget* widget) {
// Subscribe to |widget|'s paint-as-active change.
paint_as_active_subscription_ =
widget->RegisterPaintAsActiveChangedCallback(base::BindRepeating(
&PaintAsActiveCallbackCounter::Call, base::Unretained(this)));
}
void Call() { count_++; }
int CallCount() { return count_; }
private:
int count_ = 0;
base::CallbackListSubscription paint_as_active_subscription_;
};
TEST_F(WidgetTest, LockParentPaintAsActive) {
if constexpr (!PlatformStyle::kInactiveWidgetControlsAppearDisabled) {
return;
}
WidgetAutoclosePtr parent(CreateTopLevelPlatformWidget());
WidgetAutoclosePtr child(CreateChildPlatformWidget(parent->GetNativeView()));
WidgetAutoclosePtr grandchild(
CreateChildPlatformWidget(child->GetNativeView()));
WidgetAutoclosePtr other(CreateTopLevelPlatformWidget());
child->widget_delegate()->SetCanActivate(true);
grandchild->widget_delegate()->SetCanActivate(true);
PaintAsActiveCallbackCounter parent_control(parent.get());
PaintAsActiveCallbackCounter child_control(child.get());
PaintAsActiveCallbackCounter grandchild_control(grandchild.get());
PaintAsActiveCallbackCounter other_control(other.get());
parent->Show();
EXPECT_TRUE(parent->ShouldPaintAsActive());
EXPECT_TRUE(child->ShouldPaintAsActive());
EXPECT_TRUE(grandchild->ShouldPaintAsActive());
EXPECT_FALSE(other->ShouldPaintAsActive());
EXPECT_EQ(parent_control.CallCount(), 1);
EXPECT_EQ(child_control.CallCount(), 1);
EXPECT_EQ(grandchild_control.CallCount(), 1);
EXPECT_EQ(other_control.CallCount(), 0);
other->Show();
EXPECT_FALSE(parent->ShouldPaintAsActive());
EXPECT_FALSE(child->ShouldPaintAsActive());
EXPECT_FALSE(grandchild->ShouldPaintAsActive());
EXPECT_TRUE(other->ShouldPaintAsActive());
EXPECT_EQ(parent_control.CallCount(), 2);
EXPECT_EQ(child_control.CallCount(), 2);
EXPECT_EQ(grandchild_control.CallCount(), 2);
EXPECT_EQ(other_control.CallCount(), 1);
child->Show();
EXPECT_TRUE(parent->ShouldPaintAsActive());
EXPECT_TRUE(child->ShouldPaintAsActive());
EXPECT_TRUE(grandchild->ShouldPaintAsActive());
EXPECT_FALSE(other->ShouldPaintAsActive());
EXPECT_EQ(parent_control.CallCount(), 3);
EXPECT_EQ(child_control.CallCount(), 3);
EXPECT_EQ(grandchild_control.CallCount(), 3);
EXPECT_EQ(other_control.CallCount(), 2);
other->Show();
EXPECT_FALSE(parent->ShouldPaintAsActive());
EXPECT_FALSE(child->ShouldPaintAsActive());
EXPECT_FALSE(grandchild->ShouldPaintAsActive());
EXPECT_TRUE(other->ShouldPaintAsActive());
EXPECT_EQ(parent_control.CallCount(), 4);
EXPECT_EQ(child_control.CallCount(), 4);
EXPECT_EQ(grandchild_control.CallCount(), 4);
EXPECT_EQ(other_control.CallCount(), 3);
grandchild->Show();
EXPECT_TRUE(parent->ShouldPaintAsActive());
EXPECT_TRUE(child->ShouldPaintAsActive());
EXPECT_TRUE(grandchild->ShouldPaintAsActive());
EXPECT_FALSE(other->ShouldPaintAsActive());
EXPECT_EQ(parent_control.CallCount(), 5);
EXPECT_EQ(child_control.CallCount(), 5);
EXPECT_EQ(grandchild_control.CallCount(), 5);
EXPECT_EQ(other_control.CallCount(), 4);
}
// Tests to make sure that child widgets do not cause their parent widget to
// paint inactive immediately when they are closed. This avoids having the
// parent paint as inactive in the time between when the bubble is closed and
// when it's eventually destroyed by its native widget (see crbug.com/1303549).
TEST_F(DesktopWidgetTest,
ClosingActiveChildDoesNotPrematurelyPaintParentInactive) {
// top_level_widget that owns the bubble widget.
auto top_level_widget =
CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET);
top_level_widget->Show();
// Create the child bubble widget.
auto bubble_widget = std::make_unique<Widget>();
Widget::InitParams init_params = CreateParamsForTestWidget(
Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_BUBBLE);
init_params.parent = top_level_widget->GetNativeView();
bubble_widget->Init(std::move(init_params));
bubble_widget->Show();
EXPECT_TRUE(bubble_widget->ShouldPaintAsActive());
EXPECT_TRUE(top_level_widget->ShouldPaintAsActive());
// Closing the bubble wiget should not immediately cause the top level widget
// to paint inactive.
PaintAsActiveCallbackCounter top_level_counter(top_level_widget.get());
PaintAsActiveCallbackCounter bubble_counter(bubble_widget.get());
bubble_widget->Close();
EXPECT_FALSE(bubble_widget->ShouldPaintAsActive());
EXPECT_TRUE(top_level_widget->ShouldPaintAsActive());
EXPECT_EQ(top_level_counter.CallCount(), 0);
EXPECT_EQ(bubble_counter.CallCount(), 0);
}
// Tests that there is no crash when paint as active lock is removed for child
// widget while its parent widget is being closed.
TEST_F(DesktopWidgetTest, LockPaintAsActiveAndCloseParent) {
// Make sure that DesktopNativeWidgetAura is used for widgets.
test_views_delegate()->set_use_desktop_native_widgets(true);
std::unique_ptr<Widget> parent =
CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET);
parent->Show();
auto delegate = std::make_unique<TestDesktopWidgetDelegate>();
Widget::InitParams params = CreateParams(
Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_WINDOW);
params.parent = parent->GetNativeView();
delegate->InitWidget(std::move(params));
delegate->RegisterDeleteDelegateCallback(
WidgetDelegate::RegisterDeleteCallbackPassKey(),
base::DoNothingWithBoundArgs(delegate->GetWidget()->LockPaintAsActive()));
base::WeakPtr<Widget> child = delegate->GetWidget()->GetWeakPtr();
child->ShowInactive();
// Child widget and its delegate are destroyed when the parent widget is being
// closed. PaintAsActiveTestDesktopWidgetDelegate::paint_as_active_lock_ is
// also deleted which should not cause a crash.
parent->CloseNow();
// Ensure that child widget has been destroyed.
ASSERT_TRUE(child && child->IsClosed());
}
// Widget used to destroy itself when OnNativeWidgetDestroyed is called.
class TestNativeWidgetDestroyedWidget : public Widget {
public:
// Overridden from NativeWidgetDelegate:
void OnNativeWidgetDestroyed() override;
};
void TestNativeWidgetDestroyedWidget::OnNativeWidgetDestroyed() {
Widget::OnNativeWidgetDestroyed();
delete this;
}
// Verifies that widget destroyed itself in OnNativeWidgetDestroyed does not
// crash in ASan.
TEST_F(DesktopWidgetTest, WidgetDestroyedItselfDoesNotCrash) {
TestDesktopWidgetDelegate delegate(new TestNativeWidgetDestroyedWidget);
delegate.InitWidget(
CreateParamsForTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET,
Widget::InitParams::TYPE_WINDOW_FRAMELESS));
delegate.GetWidget()->Show();
delegate.GetWidget()->CloseNow();
}
// Verifies WindowClosing() is invoked correctly on the delegate when a Widget
// is closed.
TEST_F(DesktopWidgetTest, SingleWindowClosing) {
TestDesktopWidgetDelegate delegate;
delegate.InitWidget(CreateParams(Widget::InitParams::CLIENT_OWNS_WIDGET,
Widget::InitParams::TYPE_WINDOW));
EXPECT_EQ(0, delegate.window_closing_count());
delegate.GetWidget()->CloseNow();
EXPECT_EQ(1, delegate.window_closing_count());
}
TEST_F(DesktopWidgetTest, CloseRequested_AllowsClose) {
constexpr Widget::ClosedReason kReason = Widget::ClosedReason::kLostFocus;
TestDesktopWidgetDelegate delegate;
delegate.set_can_close(true);
delegate.InitWidget(CreateParams(Widget::InitParams::CLIENT_OWNS_WIDGET,
Widget::InitParams::TYPE_WINDOW));
WidgetDestroyedWaiter waiter(delegate.GetWidget());
delegate.GetWidget()->CloseWithReason(kReason);
EXPECT_TRUE(delegate.GetWidget()->IsClosed());
EXPECT_EQ(kReason, delegate.GetWidget()->closed_reason());
EXPECT_EQ(kReason, delegate.last_closed_reason());
waiter.Wait();
}
TEST_F(DesktopWidgetTest, CloseRequested_DisallowClose) {
constexpr Widget::ClosedReason kReason = Widget::ClosedReason::kLostFocus;
TestDesktopWidgetDelegate delegate;
delegate.set_can_close(false);
delegate.InitWidget(CreateParams(Widget::InitParams::CLIENT_OWNS_WIDGET,
Widget::InitParams::TYPE_WINDOW));
delegate.GetWidget()->CloseWithReason(kReason);
EXPECT_FALSE(delegate.GetWidget()->IsClosed());
EXPECT_EQ(Widget::ClosedReason::kUnspecified,
delegate.GetWidget()->closed_reason());
EXPECT_EQ(kReason, delegate.last_closed_reason());
delegate.GetWidget()->CloseNow();
}
TEST_F(DesktopWidgetTest, CloseRequested_SecondCloseIgnored) {
constexpr Widget::ClosedReason kReason1 = Widget::ClosedReason::kLostFocus;
constexpr Widget::ClosedReason kReason2 = Widget::ClosedReason::kUnspecified;
TestDesktopWidgetDelegate delegate;
delegate.set_can_close(true);
delegate.InitWidget(CreateParams(Widget::InitParams::CLIENT_OWNS_WIDGET,
Widget::InitParams::TYPE_WINDOW));
WidgetDestroyedWaiter waiter(delegate.GetWidget());
// Close for the first time.
delegate.GetWidget()->CloseWithReason(kReason1);
EXPECT_TRUE(delegate.GetWidget()->IsClosed());
EXPECT_EQ(kReason1, delegate.last_closed_reason());
// Calling close again should have no effect.
delegate.GetWidget()->CloseWithReason(kReason2);
EXPECT_TRUE(delegate.GetWidget()->IsClosed());
EXPECT_EQ(kReason1, delegate.last_closed_reason());
waiter.Wait();
}
class WidgetWindowTitleTest : public DesktopWidgetTest {
protected:
void RunTest(bool desktop_native_widget) {
auto widget = std::make_unique<Widget>();
Widget::InitParams init_params =
CreateParams(Widget::InitParams::CLIENT_OWNS_WIDGET,
Widget::InitParams::TYPE_WINDOW);
if (!desktop_native_widget) {
init_params.native_widget =
CreatePlatformNativeWidgetImpl(widget.get(), kStubCapture, nullptr);
}
widget->Init(std::move(init_params));
internal::NativeWidgetPrivate* native_widget =
widget->native_widget_private();
std::u16string empty;
std::u16string s1(u"Title1");
std::u16string s2(u"Title2");
std::u16string s3(u"TitleLong");
// The widget starts with no title, setting empty should not change
// anything.
EXPECT_FALSE(native_widget->SetWindowTitle(empty));
// Setting the title to something non-empty should cause a change.
EXPECT_TRUE(native_widget->SetWindowTitle(s1));
// Setting the title to something else with the same length should cause a
// change.
EXPECT_TRUE(native_widget->SetWindowTitle(s2));
// Setting the title to something else with a different length should cause
// a change.
EXPECT_TRUE(native_widget->SetWindowTitle(s3));
// Setting the title to the same thing twice should not cause a change.
EXPECT_FALSE(native_widget->SetWindowTitle(s3));
}
};
TEST_F(WidgetWindowTitleTest, SetWindowTitleChanged_NativeWidget) {
// Use the default NativeWidget.
bool desktop_native_widget = false;
RunTest(desktop_native_widget);
}
TEST_F(WidgetWindowTitleTest, SetWindowTitleChanged_DesktopNativeWidget) {
// Override to use a DesktopNativeWidget.
bool desktop_native_widget = true;
RunTest(desktop_native_widget);
}
TEST_F(WidgetTest, WidgetDeleted_InOnMousePressed) {
Widget* widget = new Widget;
Widget::InitParams params =
CreateParams(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
Widget::InitParams::TYPE_POPUP);
widget->Init(std::move(params));
widget->SetContentsView(
std::make_unique<CloseWidgetView>(ui::EventType::kMousePressed));
widget->SetSize(gfx::Size(100, 100));
widget->Show();
auto generator =
CreateEventGenerator(GetContext(), widget->GetNativeWindow());
WidgetDeletionObserver deletion_observer(widget);
generator->PressLeftButton();
if (deletion_observer.IsWidgetAlive()) {
generator->ReleaseLeftButton();
}
EXPECT_FALSE(deletion_observer.IsWidgetAlive());
// Yay we did not crash!
}
// No touch on desktop Mac. Tracked in http://crbug.com/445520.
#if !BUILDFLAG(IS_MAC) || defined(USE_AURA)
TEST_F(WidgetTest, WidgetDeleted_InDispatchGestureEvent) {
Widget* widget = new Widget;
Widget::InitParams params =
CreateParams(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
Widget::InitParams::TYPE_POPUP);
widget->Init(std::move(params));
widget->SetContentsView(
std::make_unique<CloseWidgetView>(ui::EventType::kGestureTapDown));
widget->SetSize(gfx::Size(100, 100));
widget->Show();
auto generator =
CreateEventGenerator(GetContext(), widget->GetNativeWindow());
WidgetDeletionObserver deletion_observer(widget);
generator->GestureTapAt(widget->GetWindowBoundsInScreen().CenterPoint());
EXPECT_FALSE(deletion_observer.IsWidgetAlive());
// Yay we did not crash!
}
#endif // !BUILDFLAG(IS_MAC) || defined(USE_AURA)
// See description of RunGetNativeThemeFromDestructor() for details.
class GetNativeThemeFromDestructorView : public WidgetDelegateView {
public:
GetNativeThemeFromDestructorView() = default;
GetNativeThemeFromDestructorView(const GetNativeThemeFromDestructorView&) =
delete;
GetNativeThemeFromDestructorView& operator=(
const GetNativeThemeFromDestructorView&) = delete;
~GetNativeThemeFromDestructorView() override { VerifyNativeTheme(); }
private:
void VerifyNativeTheme() { ASSERT_TRUE(GetNativeTheme() != nullptr); }
};
// Verifies GetNativeTheme() from the destructor of a WidgetDelegateView doesn't
// crash. |is_first_run| is true if this is the first call. A return value of
// true indicates this should be run again with a value of false.
// First run uses DesktopNativeWidgetAura (if possible). Second run doesn't.
bool RunGetNativeThemeFromDestructor(Widget::InitParams params,
bool is_first_run) {
bool needs_second_run = false;
// Destroyed by CloseNow() below.
WidgetTest::WidgetAutoclosePtr widget(new Widget);
// Deletes itself when the Widget is destroyed.
params.delegate = new GetNativeThemeFromDestructorView;
if (!is_first_run) {
params.native_widget =
CreatePlatformNativeWidgetImpl(widget.get(), kStubCapture, nullptr);
needs_second_run = true;
}
widget->Init(std::move(params));
return needs_second_run;
}
// See description of RunGetNativeThemeFromDestructor() for details.
TEST_F(DesktopWidgetTest, DISABLED_GetNativeThemeFromDestructor) {
if (RunGetNativeThemeFromDestructor(
CreateParams(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
Widget::InitParams::TYPE_POPUP),
true)) {
RunGetNativeThemeFromDestructor(
CreateParams(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
Widget::InitParams::TYPE_POPUP),
false);
}
}
// Used by HideCloseDestroy. Allows setting a boolean when the widget is
// destroyed.
class CloseDestroysWidget : public Widget {
public:
CloseDestroysWidget(bool* destroyed, base::OnceClosure quit_closure)
: destroyed_(destroyed), quit_closure_(std::move(quit_closure)) {
DCHECK(destroyed_);
DCHECK(quit_closure_);
}
CloseDestroysWidget(const CloseDestroysWidget&) = delete;
CloseDestroysWidget& operator=(const CloseDestroysWidget&) = delete;
~CloseDestroysWidget() override {
*destroyed_ = true;
std::move(quit_closure_).Run();
}
void Detach() { destroyed_ = nullptr; }
private:
raw_ptr<bool> destroyed_;
base::OnceClosure quit_closure_;
};
// An observer that registers that an animation has ended.
class AnimationEndObserver : public ui::ImplicitAnimationObserver {
public:
AnimationEndObserver() = default;
AnimationEndObserver(const AnimationEndObserver&) = delete;
AnimationEndObserver& operator=(const AnimationEndObserver&) = delete;
~AnimationEndObserver() override = default;
bool animation_completed() const { return animation_completed_; }
// ui::ImplicitAnimationObserver:
void OnImplicitAnimationsCompleted() override { animation_completed_ = true; }
private:
bool animation_completed_ = false;
};
// An observer that registers the bounds of a widget on destruction.
class WidgetBoundsObserver : public WidgetObserver {
public:
explicit WidgetBoundsObserver(Widget* widget) {
widget_observation_.Observe(widget);
}
WidgetBoundsObserver(const WidgetBoundsObserver&) = delete;
WidgetBoundsObserver& operator=(const WidgetBoundsObserver&) = delete;
~WidgetBoundsObserver() override = default;
gfx::Rect bounds() { return bounds_; }
// WidgetObserver:
void OnWidgetDestroying(Widget* widget) override {
EXPECT_TRUE(widget->GetNativeWindow());
EXPECT_TRUE(Widget::GetWidgetForNativeWindow(widget->GetNativeWindow()));
bounds_ = widget->GetWindowBoundsInScreen();
widget_observation_.Reset();
}
private:
gfx::Rect bounds_;
base::ScopedObservation<Widget, WidgetObserver> widget_observation_{this};
};
// Verifies Close() results in destroying.
TEST_F(DesktopWidgetTest, CloseDestroys) {
bool destroyed = false;
base::RunLoop run_loop;
CloseDestroysWidget* widget =
new CloseDestroysWidget(&destroyed, run_loop.QuitClosure());
Widget::InitParams params =
CreateParams(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
Widget::InitParams::TYPE_MENU);
params.opacity = Widget::InitParams::WindowOpacity::kOpaque;
params.bounds = gfx::Rect(50, 50, 250, 250);
widget->Init(std::move(params));
widget->Show();
widget->Hide();
widget->Close();
EXPECT_FALSE(destroyed);
// Run the message loop as Close() asynchronously deletes.
run_loop.Run();
EXPECT_TRUE(destroyed);
// Close() should destroy the widget. If not we'll cleanup to avoid leaks.
if (!destroyed) {
widget->Detach();
widget->CloseNow();
}
}
// Tests that killing a widget while animating it does not crash.
TEST_F(WidgetTest, CloseWidgetWhileAnimating) {
std::unique_ptr<Widget> widget =
CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET);
AnimationEndObserver animation_observer;
WidgetBoundsObserver widget_observer(widget.get());
gfx::Rect bounds(100, 100, 50, 50);
{
// Normal animations for tests have ZERO_DURATION, make sure we are actually
// animating the movement.
ui::ScopedAnimationDurationScaleMode animation_scale_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
ui::ScopedLayerAnimationSettings animation_settings(
widget->GetLayer()->GetAnimator());
animation_settings.AddObserver(&animation_observer);
widget->Show();
// Animate the bounds change.
widget->SetBounds(bounds);
widget->CloseNow();
widget.reset();
EXPECT_FALSE(animation_observer.animation_completed());
}
EXPECT_TRUE(animation_observer.animation_completed());
EXPECT_EQ(widget_observer.bounds(), bounds);
}
// Test Widget::CloseAllWidgets works as expected across platforms. ChromeOS
// doesn't implement or need CloseAllWidgets() since everything is under a
// single root window.
#if BUILDFLAG(ENABLE_DESKTOP_AURA) || BUILDFLAG(IS_MAC)
TEST_F(DesktopWidgetTest, CloseAllWidgets) {
Widget* widget1 = CreateTopLevelNativeWidget();
Widget* widget2 = CreateTopLevelNativeWidget();
TestWidgetObserver observer1(widget1);
TestWidgetObserver observer2(widget2);
widget1->Show(); // Just show the first one.
Widget::CloseAllWidgets();
EXPECT_TRUE(observer1.widget_closed());
EXPECT_TRUE(observer2.widget_closed());
}
#endif
// Test that the NativeWidget is still valid during OnNativeWidgetDestroying(),
// and properties that depend on it are valid, when closed via CloseNow().
TEST_F(DesktopWidgetTest, ValidDuringOnNativeWidgetDestroyingFromCloseNow) {
Widget* widget = CreateTopLevelNativeWidget();
widget->Show();
gfx::Rect screen_rect(50, 50, 100, 100);
widget->SetBounds(screen_rect);
WidgetBoundsObserver observer(widget);
widget->CloseNow();
EXPECT_EQ(screen_rect, observer.bounds());
}
// Test that the NativeWidget is still valid during OnNativeWidgetDestroying(),
// and properties that depend on it are valid, when closed via Close().
TEST_F(DesktopWidgetTest, ValidDuringOnNativeWidgetDestroyingFromClose) {
Widget* widget = CreateTopLevelNativeWidget();
widget->Show();
gfx::Rect screen_rect(50, 50, 100, 100);
widget->SetBounds(screen_rect);
WidgetBoundsObserver observer(widget);
widget->Close();
EXPECT_EQ(gfx::Rect(), observer.bounds());
base::RunLoop().RunUntilIdle();
// Broken on Linux. See http://crbug.com/515379.
#if !BUILDFLAG(IS_LINUX)
EXPECT_EQ(screen_rect, observer.bounds());
#endif
}
// Tests that we do not crash when a Widget is destroyed by going out of
// scope (as opposed to being explicitly deleted by its NativeWidget).
TEST_F(WidgetTest, NoCrashOnWidgetDelete) {
CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET);
}
TEST_F(WidgetTest, NoCrashOnResizeConstraintsWindowTitleOnPopup) {
CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET,
Widget::InitParams::TYPE_POPUP)
->OnSizeConstraintsChanged();
}
// Tests that we do not crash when a Widget is destroyed before it finishes
// processing of pending input events in the message loop.
TEST_F(WidgetTest, NoCrashOnWidgetDeleteWithPendingEvents) {
std::unique_ptr<Widget> widget =
CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET);
widget->Show();
auto generator =
CreateEventGenerator(GetContext(), widget->GetNativeWindow());
generator->MoveMouseTo(10, 10);
// No touch on desktop Mac. Tracked in http://crbug.com/445520.
#if BUILDFLAG(IS_MAC)
generator->ClickLeftButton();
#else
generator->PressTouch();
#endif
widget.reset();
}
// A view that consumes mouse-pressed event and gesture-tap-down events.
class RootViewTestView : public View {
METADATA_HEADER(RootViewTestView, View)
public:
RootViewTestView() = default;
private:
bool OnMousePressed(const ui::MouseEvent& event) override { return true; }
void OnGestureEvent(ui::GestureEvent* event) override {
if (event->type() == ui::EventType::kGestureTapDown) {
event->SetHandled();
}
}
};
BEGIN_METADATA(RootViewTestView)
END_METADATA
// Checks if RootView::*_handler_ fields are unset when widget is hidden.
// Fails on chromium.webkit Windows bot, see crbug.com/264872.
#if BUILDFLAG(IS_WIN)
#define MAYBE_DisableTestRootViewHandlersWhenHidden \
DISABLED_TestRootViewHandlersWhenHidden
#else
#define MAYBE_DisableTestRootViewHandlersWhenHidden \
TestRootViewHandlersWhenHidden
#endif
TEST_F(WidgetTest, MAYBE_DisableTestRootViewHandlersWhenHidden) {
Widget* widget = CreateTopLevelNativeWidget();
widget->SetBounds(gfx::Rect(0, 0, 300, 300));
View* view = new RootViewTestView();
view->SetBounds(0, 0, 300, 300);
internal::RootView* root_view =
static_cast<internal::RootView*>(widget->GetRootView());
root_view->AddChildViewRaw(view);
// Check RootView::mouse_pressed_handler_.
widget->Show();
EXPECT_EQ(nullptr, GetMousePressedHandler(root_view));
gfx::Point click_location(45, 15);
ui::MouseEvent press(ui::EventType::kMousePressed, click_location,
click_location, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON);
widget->OnMouseEvent(&press);
EXPECT_EQ(view, GetMousePressedHandler(root_view));
widget->Hide();
EXPECT_EQ(nullptr, GetMousePressedHandler(root_view));
// Check RootView::mouse_move_handler_.
widget->Show();
EXPECT_EQ(nullptr, GetMouseMoveHandler(root_view));
gfx::Point move_location(45, 15);
ui::MouseEvent move(ui::EventType::kMouseMoved, move_location, move_location,
ui::EventTimeForNow(), 0, 0);
widget->OnMouseEvent(&move);
EXPECT_EQ(view, GetMouseMoveHandler(root_view));
widget->Hide();
EXPECT_EQ(nullptr, GetMouseMoveHandler(root_view));
// Check RootView::gesture_handler_.
widget->Show();
EXPECT_EQ(nullptr, GetGestureHandler(root_view));
ui::GestureEvent tap_down =
CreateTestGestureEvent(ui::EventType::kGestureTapDown, 15, 15);
widget->OnGestureEvent(&tap_down);
EXPECT_EQ(view, GetGestureHandler(root_view));
widget->Hide();
EXPECT_EQ(nullptr, GetGestureHandler(root_view));
widget->Close();
}
// Tests that the |gesture_handler_| member in RootView is always NULL
// after the dispatch of a ui::EventType::kGestureEnd event corresponding to
// the release of the final touch point on the screen, but that
// ui::EventType::kGestureEnd events corresponding to the removal of any other
// touch point do not modify |gesture_handler_|.
TEST_F(WidgetTest, GestureEndEvents) {
Widget* widget = CreateTopLevelNativeWidget();
widget->SetBounds(gfx::Rect(0, 0, 300, 300));
EventCountView* view = new EventCountView();
view->SetBounds(0, 0, 300, 300);
internal::RootView* root_view =
static_cast<internal::RootView*>(widget->GetRootView());
root_view->AddChildViewRaw(view);
widget->Show();
// If no gesture handler is set, a ui::EventType::kGestureEnd event should not
// set the gesture handler.
EXPECT_EQ(nullptr, GetGestureHandler(root_view));
ui::GestureEvent end =
CreateTestGestureEvent(ui::EventType::kGestureEnd, 15, 15);
widget->OnGestureEvent(&end);
EXPECT_EQ(nullptr, GetGestureHandler(root_view));
// Change the handle mode of |view| to indicate that it would like
// to handle all events, then send a GESTURE_TAP to set the gesture handler.
view->set_handle_mode(EventCountView::HandleMode::kConsumeEvents);
ui::GestureEvent tap =
CreateTestGestureEvent(ui::EventType::kGestureTap, 15, 15);
widget->OnGestureEvent(&tap);
EXPECT_TRUE(tap.handled());
EXPECT_EQ(view, GetGestureHandler(root_view));
// The gesture handler should remain unchanged on a ui::EventType::kGestureEnd
// corresponding to a second touch point, but should be reset to NULL by a
// ui::EventType::kGestureEnd corresponding to the final touch point.
ui::GestureEventDetails details(ui::EventType::kGestureEnd);
details.set_touch_points(2);
ui::GestureEvent end_second_touch_point =
CreateTestGestureEvent(details, 15, 15);
widget->OnGestureEvent(&end_second_touch_point);
EXPECT_EQ(view, GetGestureHandler(root_view));
end = CreateTestGestureEvent(ui::EventType::kGestureEnd, 15, 15);
widget->OnGestureEvent(&end);
EXPECT_TRUE(end.handled());
EXPECT_EQ(nullptr, GetGestureHandler(root_view));
// Send a GESTURE_TAP to set the gesture handler, then change the handle
// mode of |view| to indicate that it does not want to handle any
// further events.
tap = CreateTestGestureEvent(ui::EventType::kGestureTap, 15, 15);
widget->OnGestureEvent(&tap);
EXPECT_TRUE(tap.handled());
EXPECT_EQ(view, GetGestureHandler(root_view));
view->set_handle_mode(EventCountView::HandleMode::kPropagateEvents);
// The gesture handler should remain unchanged on a ui::EventType::kGestureEnd
// corresponding to a second touch point, but should be reset to NULL by a
// ui::EventType::kGestureEnd corresponding to the final touch point.
end_second_touch_point = CreateTestGestureEvent(details, 15, 15);
widget->OnGestureEvent(&end_second_touch_point);
EXPECT_EQ(view, GetGestureHandler(root_view));
end = CreateTestGestureEvent(ui::EventType::kGestureEnd, 15, 15);
widget->OnGestureEvent(&end);
EXPECT_FALSE(end.handled());
EXPECT_EQ(nullptr, GetGestureHandler(root_view));
widget->Close();
}
// Tests that gesture events which should not be processed (because
// RootView::OnEventProcessingStarted() has marked them as handled) are not
// dispatched to any views.
TEST_F(WidgetTest, GestureEventsNotProcessed) {
Widget* widget = CreateTopLevelNativeWidget();
widget->SetBounds(gfx::Rect(0, 0, 300, 300));
// Define a hierarchy of four views (coordinates are in
// their parent coordinate space).
// v1 (0, 0, 300, 300)
// v2 (0, 0, 100, 100)
// v3 (0, 0, 50, 50)
// v4(0, 0, 10, 10)
EventCountView* v1 = new EventCountView();
v1->SetBounds(0, 0, 300, 300);
EventCountView* v2 = new EventCountView();
v2->SetBounds(0, 0, 100, 100);
EventCountView* v3 = new EventCountView();
v3->SetBounds(0, 0, 50, 50);
EventCountView* v4 = new EventCountView();
v4->SetBounds(0, 0, 10, 10);
internal::RootView* root_view =
static_cast<internal::RootView*>(widget->GetRootView());
root_view->AddChildViewRaw(v1);
v1->AddChildViewRaw(v2);
v2->AddChildViewRaw(v3);
v3->AddChildViewRaw(v4);
widget->Show();
// ui::EventType::kGestureBegin events should never be seen by any view, but
// they should be marked as handled by OnEventProcessingStarted().
ui::GestureEvent begin =
CreateTestGestureEvent(ui::EventType::kGestureBegin, 5, 5);
widget->OnGestureEvent(&begin);
EXPECT_EQ(0, v1->GetEventCount(ui::EventType::kGestureBegin));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kGestureBegin));
EXPECT_EQ(0, v3->GetEventCount(ui::EventType::kGestureBegin));
EXPECT_EQ(0, v4->GetEventCount(ui::EventType::kGestureBegin));
EXPECT_EQ(nullptr, GetGestureHandler(root_view));
EXPECT_TRUE(begin.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// ui::EventType::kGestureEnd events should not be seen by any view when there
// is no default gesture handler set, but they should be marked as handled by
// OnEventProcessingStarted().
ui::GestureEvent end =
CreateTestGestureEvent(ui::EventType::kGestureEnd, 5, 5);
widget->OnGestureEvent(&end);
EXPECT_EQ(0, v1->GetEventCount(ui::EventType::kGestureEnd));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kGestureEnd));
EXPECT_EQ(0, v3->GetEventCount(ui::EventType::kGestureEnd));
EXPECT_EQ(0, v4->GetEventCount(ui::EventType::kGestureEnd));
EXPECT_EQ(nullptr, GetGestureHandler(root_view));
EXPECT_TRUE(end.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// ui::EventType::kGestureEnd events not corresponding to the release of the
// final touch point should never be seen by any view, but they should
// be marked as handled by OnEventProcessingStarted().
ui::GestureEventDetails details(ui::EventType::kGestureEnd);
details.set_touch_points(2);
ui::GestureEvent end_second_touch_point =
CreateTestGestureEvent(details, 5, 5);
widget->OnGestureEvent(&end_second_touch_point);
EXPECT_EQ(0, v1->GetEventCount(ui::EventType::kGestureEnd));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kGestureEnd));
EXPECT_EQ(0, v3->GetEventCount(ui::EventType::kGestureEnd));
EXPECT_EQ(0, v4->GetEventCount(ui::EventType::kGestureEnd));
EXPECT_EQ(nullptr, GetGestureHandler(root_view));
EXPECT_TRUE(end_second_touch_point.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// ui::EventType::kGestureScrollUpdate events should never be seen by any view
// when there is no default gesture handler set, but they should be marked as
// handled by OnEventProcessingStarted().
ui::GestureEvent scroll_update =
CreateTestGestureEvent(ui::EventType::kGestureScrollUpdate, 5, 5);
widget->OnGestureEvent(&scroll_update);
EXPECT_EQ(0, v1->GetEventCount(ui::EventType::kGestureScrollUpdate));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kGestureScrollUpdate));
EXPECT_EQ(0, v3->GetEventCount(ui::EventType::kGestureScrollUpdate));
EXPECT_EQ(0, v4->GetEventCount(ui::EventType::kGestureScrollUpdate));
EXPECT_EQ(nullptr, GetGestureHandler(root_view));
EXPECT_TRUE(scroll_update.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// ui::EventType::kGestureScrollEnd events should never be seen by any view
// when there is no default gesture handler set, but they should be marked as
// handled by OnEventProcessingStarted().
ui::GestureEvent scroll_end =
CreateTestGestureEvent(ui::EventType::kGestureScrollEnd, 5, 5);
widget->OnGestureEvent(&scroll_end);
EXPECT_EQ(0, v1->GetEventCount(ui::EventType::kGestureScrollEnd));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kGestureScrollEnd));
EXPECT_EQ(0, v3->GetEventCount(ui::EventType::kGestureScrollEnd));
EXPECT_EQ(0, v4->GetEventCount(ui::EventType::kGestureScrollEnd));
EXPECT_EQ(nullptr, GetGestureHandler(root_view));
EXPECT_TRUE(scroll_end.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// ui::EventType::kScrollFlingStart events should never be seen by any view
// when there is no default gesture handler set, but they should be marked as
// handled by OnEventProcessingStarted().
ui::GestureEvent scroll_fling_start =
CreateTestGestureEvent(ui::EventType::kScrollFlingStart, 5, 5);
widget->OnGestureEvent(&scroll_fling_start);
EXPECT_EQ(0, v1->GetEventCount(ui::EventType::kScrollFlingStart));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kScrollFlingStart));
EXPECT_EQ(0, v3->GetEventCount(ui::EventType::kScrollFlingStart));
EXPECT_EQ(0, v4->GetEventCount(ui::EventType::kScrollFlingStart));
EXPECT_EQ(nullptr, GetGestureHandler(root_view));
EXPECT_TRUE(scroll_fling_start.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
widget->Close();
}
// Tests that a (non-scroll) gesture event is dispatched to the correct views
// in a view hierarchy and that the default gesture handler in RootView is set
// correctly.
TEST_F(WidgetTest, GestureEventDispatch) {
Widget* widget = CreateTopLevelNativeWidget();
widget->SetBounds(gfx::Rect(0, 0, 300, 300));
// Define a hierarchy of four views (coordinates are in
// their parent coordinate space).
// v1 (0, 0, 300, 300)
// v2 (0, 0, 100, 100)
// v3 (0, 0, 50, 50)
// v4(0, 0, 10, 10)
EventCountView* v1 = new EventCountView();
v1->SetBounds(0, 0, 300, 300);
EventCountView* v2 = new EventCountView();
v2->SetBounds(0, 0, 100, 100);
EventCountView* v3 = new EventCountView();
v3->SetBounds(0, 0, 50, 50);
EventCountView* v4 = new EventCountView();
v4->SetBounds(0, 0, 10, 10);
internal::RootView* root_view =
static_cast<internal::RootView*>(widget->GetRootView());
root_view->AddChildViewRaw(v1);
v1->AddChildViewRaw(v2);
v2->AddChildViewRaw(v3);
v3->AddChildViewRaw(v4);
widget->Show();
// No gesture handler is set in the root view and none of the views in the
// view hierarchy handle a ui::EventType::kGestureTap event. In this case the
// tap event should be dispatched to all views in the hierarchy, the gesture
// handler should remain unset, and the event should remain unhandled.
ui::GestureEvent tap =
CreateTestGestureEvent(ui::EventType::kGestureTap, 5, 5);
EXPECT_EQ(nullptr, GetGestureHandler(root_view));
widget->OnGestureEvent(&tap);
EXPECT_EQ(1, v1->GetEventCount(ui::EventType::kGestureTap));
EXPECT_EQ(1, v2->GetEventCount(ui::EventType::kGestureTap));
EXPECT_EQ(1, v3->GetEventCount(ui::EventType::kGestureTap));
EXPECT_EQ(1, v4->GetEventCount(ui::EventType::kGestureTap));
EXPECT_EQ(nullptr, GetGestureHandler(root_view));
EXPECT_FALSE(tap.handled());
// No gesture handler is set in the root view and |v1|, |v2|, and |v3| all
// handle a ui::EventType::kGestureTap event. In this case the tap event
// should be dispatched to |v4| and |v3|, the gesture handler should be set to
// |v3|, and the event should be marked as handled.
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
v1->set_handle_mode(EventCountView::HandleMode::kConsumeEvents);
v2->set_handle_mode(EventCountView::HandleMode::kConsumeEvents);
v3->set_handle_mode(EventCountView::HandleMode::kConsumeEvents);
tap = CreateTestGestureEvent(ui::EventType::kGestureTap, 5, 5);
widget->OnGestureEvent(&tap);
EXPECT_EQ(0, v1->GetEventCount(ui::EventType::kGestureTap));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kGestureTap));
EXPECT_EQ(1, v3->GetEventCount(ui::EventType::kGestureTap));
EXPECT_EQ(1, v4->GetEventCount(ui::EventType::kGestureTap));
EXPECT_EQ(v3, GetGestureHandler(root_view));
EXPECT_TRUE(tap.handled());
// The gesture handler is set to |v3| and all views handle all gesture event
// types. In this case subsequent gesture events should only be dispatched to
// |v3| and marked as handled. The gesture handler should remain as |v3|.
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
v4->set_handle_mode(EventCountView::HandleMode::kConsumeEvents);
tap = CreateTestGestureEvent(ui::EventType::kGestureTap, 5, 5);
widget->OnGestureEvent(&tap);
EXPECT_TRUE(tap.handled());
ui::GestureEvent show_press =
CreateTestGestureEvent(ui::EventType::kGestureShowPress, 5, 5);
widget->OnGestureEvent(&show_press);
tap = CreateTestGestureEvent(ui::EventType::kGestureTap, 5, 5);
widget->OnGestureEvent(&tap);
EXPECT_EQ(0, v1->GetEventCount(ui::EventType::kGestureTap));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kGestureTap));
EXPECT_EQ(2, v3->GetEventCount(ui::EventType::kGestureTap));
EXPECT_EQ(0, v4->GetEventCount(ui::EventType::kGestureTap));
EXPECT_EQ(0, v1->GetEventCount(ui::EventType::kGestureShowPress));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kGestureShowPress));
EXPECT_EQ(1, v3->GetEventCount(ui::EventType::kGestureShowPress));
EXPECT_EQ(0, v4->GetEventCount(ui::EventType::kGestureShowPress));
EXPECT_TRUE(tap.handled());
EXPECT_TRUE(show_press.handled());
EXPECT_EQ(v3, GetGestureHandler(root_view));
// The gesture handler is set to |v3|, but |v3| does not handle
// ui::EventType::kGestureTap events. In this case a tap gesture should be
// dispatched only to |v3|, but the event should remain unhandled. The gesture
// handler should remain as |v3|.
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
v3->set_handle_mode(EventCountView::HandleMode::kPropagateEvents);
tap = CreateTestGestureEvent(ui::EventType::kGestureTap, 5, 5);
widget->OnGestureEvent(&tap);
EXPECT_EQ(0, v1->GetEventCount(ui::EventType::kGestureTap));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kGestureTap));
EXPECT_EQ(1, v3->GetEventCount(ui::EventType::kGestureTap));
EXPECT_EQ(0, v4->GetEventCount(ui::EventType::kGestureTap));
EXPECT_FALSE(tap.handled());
EXPECT_EQ(v3, GetGestureHandler(root_view));
widget->Close();
}
// Tests that gesture scroll events will change the default gesture handler in
// RootView if the current handler to which they are dispatched does not handle
// gesture scroll events.
TEST_F(WidgetTest, ScrollGestureEventDispatch) {
Widget* widget = CreateTopLevelNativeWidget();
widget->SetBounds(gfx::Rect(0, 0, 300, 300));
// Define a hierarchy of four views (coordinates are in
// their parent coordinate space).
// v1 (0, 0, 300, 300)
// v2 (0, 0, 100, 100)
// v3 (0, 0, 50, 50)
// v4(0, 0, 10, 10)
EventCountView* v1 = new EventCountView();
v1->SetBounds(0, 0, 300, 300);
EventCountView* v2 = new EventCountView();
v2->SetBounds(0, 0, 100, 100);
EventCountView* v3 = new EventCountView();
v3->SetBounds(0, 0, 50, 50);
EventCountView* v4 = new EventCountView();
v4->SetBounds(0, 0, 10, 10);
internal::RootView* root_view =
static_cast<internal::RootView*>(widget->GetRootView());
root_view->AddChildViewRaw(v1);
v1->AddChildViewRaw(v2);
v2->AddChildViewRaw(v3);
v3->AddChildViewRaw(v4);
widget->Show();
// Change the handle mode of |v3| to indicate that it would like to handle
// gesture events.
v3->set_handle_mode(EventCountView::HandleMode::kConsumeEvents);
// When no gesture handler is set, dispatching a
// ui::EventType::kGestureTapDown should bubble up the views hierarchy until
// it reaches the first view that will handle it (|v3|) and then sets the
// handler to |v3|.
EXPECT_EQ(nullptr, GetGestureHandler(root_view));
ui::GestureEvent tap_down =
CreateTestGestureEvent(ui::EventType::kGestureTapDown, 5, 5);
widget->OnGestureEvent(&tap_down);
EXPECT_EQ(0, v1->GetEventCount(ui::EventType::kGestureTapDown));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kGestureTapDown));
EXPECT_EQ(1, v3->GetEventCount(ui::EventType::kGestureTapDown));
EXPECT_EQ(1, v4->GetEventCount(ui::EventType::kGestureTapDown));
EXPECT_EQ(v3, GetGestureHandler(root_view));
EXPECT_TRUE(tap_down.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// A ui::EventType::kGestureTapCancel event should be dispatched to |v3|
// directly.
ui::GestureEvent tap_cancel =
CreateTestGestureEvent(ui::EventType::kGestureTapCancel, 5, 5);
widget->OnGestureEvent(&tap_cancel);
EXPECT_EQ(0, v1->GetEventCount(ui::EventType::kGestureTapCancel));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kGestureTapCancel));
EXPECT_EQ(1, v3->GetEventCount(ui::EventType::kGestureTapCancel));
EXPECT_EQ(0, v4->GetEventCount(ui::EventType::kGestureTapCancel));
EXPECT_EQ(v3, GetGestureHandler(root_view));
EXPECT_TRUE(tap_cancel.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// Change the handle mode of |v3| to indicate that it would no longer like
// to handle events, and change the mode of |v1| to indicate that it would
// like to handle events.
v3->set_handle_mode(EventCountView::HandleMode::kPropagateEvents);
v1->set_handle_mode(EventCountView::HandleMode::kConsumeEvents);
// Dispatch a ui::EventType::kGestureScrollBegin event. Because the current
// gesture handler (|v3|) does not handle scroll events, the event should
// bubble up the views hierarchy until it reaches the first view that will
// handle it (|v1|) and then sets the handler to |v1|.
ui::GestureEvent scroll_begin =
CreateTestGestureEvent(ui::EventType::kGestureScrollBegin, 5, 5);
widget->OnGestureEvent(&scroll_begin);
EXPECT_EQ(1, v1->GetEventCount(ui::EventType::kGestureScrollBegin));
EXPECT_EQ(1, v2->GetEventCount(ui::EventType::kGestureScrollBegin));
EXPECT_EQ(1, v3->GetEventCount(ui::EventType::kGestureScrollBegin));
EXPECT_EQ(0, v4->GetEventCount(ui::EventType::kGestureScrollBegin));
EXPECT_EQ(v1, GetGestureHandler(root_view));
EXPECT_TRUE(scroll_begin.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// A ui::EventType::kGestureScrollUpdate event should be dispatched to |v1|
// directly.
ui::GestureEvent scroll_update =
CreateTestGestureEvent(ui::EventType::kGestureScrollUpdate, 5, 5);
widget->OnGestureEvent(&scroll_update);
EXPECT_EQ(1, v1->GetEventCount(ui::EventType::kGestureScrollUpdate));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kGestureScrollUpdate));
EXPECT_EQ(0, v3->GetEventCount(ui::EventType::kGestureScrollUpdate));
EXPECT_EQ(0, v4->GetEventCount(ui::EventType::kGestureScrollUpdate));
EXPECT_EQ(v1, GetGestureHandler(root_view));
EXPECT_TRUE(scroll_update.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// A ui::EventType::kGestureScrollEnd event should be dispatched to |v1|
// directly and should not reset the gesture handler.
ui::GestureEvent scroll_end =
CreateTestGestureEvent(ui::EventType::kGestureScrollEnd, 5, 5);
widget->OnGestureEvent(&scroll_end);
EXPECT_EQ(1, v1->GetEventCount(ui::EventType::kGestureScrollEnd));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kGestureScrollEnd));
EXPECT_EQ(0, v3->GetEventCount(ui::EventType::kGestureScrollEnd));
EXPECT_EQ(0, v4->GetEventCount(ui::EventType::kGestureScrollEnd));
EXPECT_EQ(v1, GetGestureHandler(root_view));
EXPECT_TRUE(scroll_end.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// A ui::EventType::kGesturePinchBegin event (which is a non-scroll event)
// should still be dispatched to |v1| directly.
ui::GestureEvent pinch_begin =
CreateTestGestureEvent(ui::EventType::kGesturePinchBegin, 5, 5);
widget->OnGestureEvent(&pinch_begin);
EXPECT_EQ(1, v1->GetEventCount(ui::EventType::kGesturePinchBegin));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kGesturePinchBegin));
EXPECT_EQ(0, v3->GetEventCount(ui::EventType::kGesturePinchBegin));
EXPECT_EQ(0, v4->GetEventCount(ui::EventType::kGesturePinchBegin));
EXPECT_EQ(v1, GetGestureHandler(root_view));
EXPECT_TRUE(pinch_begin.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// A ui::EventType::kGestureEnd event should be dispatched to |v1| and should
// set the gesture handler to NULL.
ui::GestureEvent end =
CreateTestGestureEvent(ui::EventType::kGestureEnd, 5, 5);
widget->OnGestureEvent(&end);
EXPECT_EQ(1, v1->GetEventCount(ui::EventType::kGestureEnd));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kGestureEnd));
EXPECT_EQ(0, v3->GetEventCount(ui::EventType::kGestureEnd));
EXPECT_EQ(0, v4->GetEventCount(ui::EventType::kGestureEnd));
EXPECT_EQ(nullptr, GetGestureHandler(root_view));
EXPECT_TRUE(end.handled());
widget->Close();
}
// TODO(b/271490637): on Mac a drag controller should still be notified when
// drag will start. Figure out how to write a unit test for Mac. Then remove
// this build flag check.
#if !BUILDFLAG(IS_MAC)
// Verifies that the drag controller is notified when the view drag will start.
TEST_F(WidgetTest, NotifyDragControllerWhenDragWillStart) {
// Create a widget whose contents view is draggable.
UniqueWidgetPtr widget(std::make_unique<Widget>());
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
params.bounds = gfx::Rect(/*width=*/650, /*height=*/650);
widget->Init(std::move(params));
widget->Show();
MockDragController mock_drag_controller;
views::View contents_view;
contents_view.set_drag_controller(&mock_drag_controller);
widget->SetContentsView(&contents_view);
// Expect the drag controller is notified of the drag start.
EXPECT_CALL(mock_drag_controller, OnWillStartDragForView(&contents_view));
// Drag-and-drop `contents_view` by mouse.
ui::test::EventGenerator generator(GetContext(), widget->GetNativeWindow());
generator.MoveMouseTo(contents_view.GetBoundsInScreen().CenterPoint());
generator.PressLeftButton();
generator.MoveMouseBy(/*x=*/200, /*y=*/0);
generator.ReleaseLeftButton();
}
#endif // !BUILDFLAG(IS_MAC)
// A class used in WidgetTest.GestureEventLocationWhileBubbling to verify
// that when a gesture event bubbles up a View hierarchy, the location
// of a gesture event seen by each View is in the local coordinate space
// of that View.
class GestureLocationView : public EventCountView {
METADATA_HEADER(GestureLocationView, EventCountView)
public:
GestureLocationView() = default;
GestureLocationView(const GestureLocationView&) = delete;
GestureLocationView& operator=(const GestureLocationView&) = delete;
~GestureLocationView() override = default;
void set_expected_location(gfx::Point expected_location) {
expected_location_ = expected_location;
}
// EventCountView:
void OnGestureEvent(ui::GestureEvent* event) override {
EventCountView::OnGestureEvent(event);
// Verify that the location of |event| is in the local coordinate
// space of |this|.
EXPECT_EQ(expected_location_, event->location());
}
private:
// The expected location of a gesture event dispatched to |this|.
gfx::Point expected_location_;
};
BEGIN_METADATA(GestureLocationView)
END_METADATA
// Verifies that the location of a gesture event is always in the local
// coordinate space of the View receiving the event while bubbling.
TEST_F(WidgetTest, GestureEventLocationWhileBubbling) {
Widget* widget = CreateTopLevelNativeWidget();
widget->SetBounds(gfx::Rect(0, 0, 300, 300));
// Define a hierarchy of three views (coordinates shown below are in the
// coordinate space of the root view, but the coordinates used for
// SetBounds() are in their parent coordinate space).
// v1 (50, 50, 150, 150)
// v2 (100, 70, 50, 80)
// v3 (120, 100, 10, 10)
GestureLocationView* v1 = new GestureLocationView();
v1->SetBounds(50, 50, 150, 150);
GestureLocationView* v2 = new GestureLocationView();
v2->SetBounds(50, 20, 50, 80);
GestureLocationView* v3 = new GestureLocationView();
v3->SetBounds(20, 30, 10, 10);
internal::RootView* root_view =
static_cast<internal::RootView*>(widget->GetRootView());
root_view->AddChildViewRaw(v1);
v1->AddChildViewRaw(v2);
v2->AddChildViewRaw(v3);
widget->Show();
// Define a GESTURE_TAP event located at (125, 105) in root view coordinates.
// This event is contained within all of |v1|, |v2|, and |v3|.
gfx::Point location_in_root(125, 105);
ui::GestureEvent tap = CreateTestGestureEvent(
ui::EventType::kGestureTap, location_in_root.x(), location_in_root.y());
// Calculate the location of the event in the local coordinate spaces
// of each of the views.
gfx::Point location_in_v1(ConvertPointFromWidgetToView(v1, location_in_root));
EXPECT_EQ(gfx::Point(75, 55), location_in_v1);
gfx::Point location_in_v2(ConvertPointFromWidgetToView(v2, location_in_root));
EXPECT_EQ(gfx::Point(25, 35), location_in_v2);
gfx::Point location_in_v3(ConvertPointFromWidgetToView(v3, location_in_root));
EXPECT_EQ(gfx::Point(5, 5), location_in_v3);
// Dispatch the event. When each view receives the event, its location should
// be in the local coordinate space of that view (see the check made by
// GestureLocationView). After dispatch is complete the event's location
// should be in the root coordinate space.
v1->set_expected_location(location_in_v1);
v2->set_expected_location(location_in_v2);
v3->set_expected_location(location_in_v3);
widget->OnGestureEvent(&tap);
EXPECT_EQ(location_in_root, tap.location());
// Verify that each view did in fact see the event.
EventCountView* view1 = v1;
EventCountView* view2 = v2;
EventCountView* view3 = v3;
EXPECT_EQ(1, view1->GetEventCount(ui::EventType::kGestureTap));
EXPECT_EQ(1, view2->GetEventCount(ui::EventType::kGestureTap));
EXPECT_EQ(1, view3->GetEventCount(ui::EventType::kGestureTap));
widget->Close();
}
// Test the result of Widget::GetAllChildWidgets().
TEST_F(WidgetTest, GetAllChildWidgets) {
// Create the following widget hierarchy:
//
// toplevel
// +-- w1
// +-- w11
// +-- w2
// +-- w21
// +-- w22
WidgetAutoclosePtr toplevel(CreateTopLevelPlatformWidget());
Widget* w1 = CreateChildPlatformWidget(toplevel->GetNativeView());
Widget* w11 = CreateChildPlatformWidget(w1->GetNativeView());
Widget* w2 = CreateChildPlatformWidget(toplevel->GetNativeView());
Widget* w21 = CreateChildPlatformWidget(w2->GetNativeView());
Widget* w22 = CreateChildPlatformWidget(w2->GetNativeView());
std::set<Widget*> expected;
expected.insert(toplevel.get());
expected.insert(w1);
expected.insert(w11);
expected.insert(w2);
expected.insert(w21);
expected.insert(w22);
Widget::Widgets child_widgets =
Widget::GetAllChildWidgets(toplevel->GetNativeView());
EXPECT_TRUE(std::ranges::equal(expected, child_widgets));
// Check GetAllOwnedWidgets(). On Aura, this includes "transient" children.
// Otherwise (on all platforms), it should be the same as GetAllChildWidgets()
// except the root Widget is not included.
EXPECT_EQ(1u, expected.erase(toplevel.get()));
Widget::Widgets owned_widgets =
Widget::GetAllOwnedWidgets(toplevel->GetNativeView());
EXPECT_TRUE(std::ranges::equal(expected, owned_widgets));
}
// Test the result of Widget::ForEachOwnedWidget().
TEST_F(WidgetTest, ForEachOwnedWidget) {
// Create the following widget hierarchy:
//
// toplevel
// +-- w1
// +-- w11
// +-- w2
// +-- w21
// +-- w22
WidgetAutoclosePtr toplevel(CreateTopLevelPlatformWidget());
Widget* w1 = CreateChildPlatformWidget(toplevel->GetNativeView());
Widget* w11 = CreateChildPlatformWidget(w1->GetNativeView());
Widget* w2 = CreateChildPlatformWidget(toplevel->GetNativeView());
Widget* w21 = CreateChildPlatformWidget(w2->GetNativeView());
Widget* w22 = CreateChildPlatformWidget(w2->GetNativeView());
std::set<Widget*> expected;
expected.insert(w1);
expected.insert(w11);
expected.insert(w2);
expected.insert(w21);
expected.insert(w22);
Widget::Widgets widgets;
Widget::ForEachOwnedWidget(
toplevel->GetNativeView(),
[&widgets](Widget* widget) { widgets.insert(widget); });
EXPECT_TRUE(std::ranges::equal(expected, widgets));
}
// Test that ForEachOwnedWidget is robust to deletion.
TEST_F(WidgetTest, ForEachOwnedWidget_WithDeletion) {
// Create the following widget hierarchy:
//
// toplevel
// +-- w1
// +-- w2
// +-- w3
WidgetAutoclosePtr toplevel(CreateTopLevelPlatformWidget());
Widget* w1 = CreateChildPlatformWidget(toplevel->GetNativeView());
Widget* w2 = CreateChildPlatformWidget(toplevel->GetNativeView());
Widget* w3 = CreateChildPlatformWidget(toplevel->GetNativeView());
// We need to delete a widget from another widget's callback.
// The iteration order is pointer-based, so we sort them to find
// two widgets where we can guarantee the order.
std::vector<Widget*> children = {w1, w2, w3};
std::sort(children.begin(), children.end());
Widget* first_widget = children[0];
Widget* second_widget = children[1];
std::set<Widget*> visited_widgets;
Widget::ForEachOwnedWidget(toplevel->GetNativeView(), [&](Widget* widget) {
if (widget == first_widget) {
second_widget->CloseNow();
}
visited_widgets.insert(widget);
});
EXPECT_EQ(2u, visited_widgets.size());
EXPECT_TRUE(visited_widgets.count(first_widget));
EXPECT_FALSE(visited_widgets.count(second_widget));
EXPECT_TRUE(visited_widgets.count(children[2]));
}
// Used by DestroyChildWidgetsInOrder. On destruction adds the supplied name to
// a vector.
class DestroyedTrackingView : public View {
METADATA_HEADER(DestroyedTrackingView, View)
public:
DestroyedTrackingView(const std::string& name,
std::vector<std::string>* add_to)
: name_(name), add_to_(add_to) {}
DestroyedTrackingView(const DestroyedTrackingView&) = delete;
DestroyedTrackingView& operator=(const DestroyedTrackingView&) = delete;
~DestroyedTrackingView() override { add_to_->push_back(name_); }
private:
const std::string name_;
raw_ptr<std::vector<std::string>> add_to_;
};
BEGIN_METADATA(DestroyedTrackingView)
END_METADATA
class WidgetChildDestructionTest : public DesktopWidgetTest {
public:
WidgetChildDestructionTest() = default;
WidgetChildDestructionTest(const WidgetChildDestructionTest&) = delete;
WidgetChildDestructionTest& operator=(const WidgetChildDestructionTest&) =
delete;
// Creates a top level and a child, destroys the child and verifies the views
// of the child are destroyed before the views of the parent.
void RunDestroyChildWidgetsTest(bool top_level_has_desktop_native_widget_aura,
bool child_has_desktop_native_widget_aura) {
// When a View is destroyed its name is added here.
std::vector<std::string> destroyed;
Widget* top_level = new Widget;
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
if (!top_level_has_desktop_native_widget_aura) {
params.native_widget =
CreatePlatformNativeWidgetImpl(top_level, kStubCapture, nullptr);
}
top_level->Init(std::move(params));
top_level->GetRootView()->AddChildViewRaw(
new DestroyedTrackingView("parent", &destroyed));
top_level->Show();
Widget* child = new Widget;
Widget::InitParams child_params =
CreateParams(Widget::InitParams::TYPE_POPUP);
child_params.parent = top_level->GetNativeView();
if (!child_has_desktop_native_widget_aura) {
child_params.native_widget =
CreatePlatformNativeWidgetImpl(child, kStubCapture, nullptr);
}
child->Init(std::move(child_params));
child->GetRootView()->AddChildViewRaw(
new DestroyedTrackingView("child", &destroyed));
child->Show();
// Should trigger destruction of the child too.
top_level->native_widget_private()->CloseNow();
// Child should be destroyed first.
ASSERT_EQ(2u, destroyed.size());
EXPECT_EQ("child", destroyed[0]);
EXPECT_EQ("parent", destroyed[1]);
}
};
// See description of RunDestroyChildWidgetsTest(). Parent uses
// DesktopNativeWidgetAura.
TEST_F(WidgetChildDestructionTest,
DestroyChildWidgetsInOrderWithDesktopNativeWidget) {
RunDestroyChildWidgetsTest(true, false);
}
// See description of RunDestroyChildWidgetsTest(). Both parent and child use
// DesktopNativeWidgetAura.
TEST_F(WidgetChildDestructionTest,
DestroyChildWidgetsInOrderWithDesktopNativeWidgetForBoth) {
RunDestroyChildWidgetsTest(true, true);
}
// See description of RunDestroyChildWidgetsTest().
TEST_F(WidgetChildDestructionTest, DestroyChildWidgetsInOrder) {
RunDestroyChildWidgetsTest(false, false);
}
// Verifies nativeview visbility matches that of Widget visibility when
// SetFullscreen is invoked.
TEST_F(WidgetTest, FullscreenStatePropagated) {
std::unique_ptr<Widget> top_level_widget =
CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET);
top_level_widget->SetFullscreen(true);
EXPECT_EQ(top_level_widget->IsVisible(),
IsNativeWindowVisible(top_level_widget->GetNativeWindow()));
}
// Verifies nativeview visbility matches that of Widget visibility when
// SetFullscreen is invoked, for a widget provided with a desktop widget.
TEST_F(DesktopWidgetTest, FullscreenStatePropagated_DesktopWidget) {
std::unique_ptr<Widget> top_level_widget =
CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET);
top_level_widget->SetFullscreen(true);
EXPECT_EQ(top_level_widget->IsVisible(),
IsNativeWindowVisible(top_level_widget->GetNativeWindow()));
}
// Used to delete the widget when the supplied bounds changes.
class DestroyingWidgetBoundsObserver : public WidgetObserver {
public:
explicit DestroyingWidgetBoundsObserver(Widget* widget) {
widget_observation_.Observe(widget);
}
// There are no assertions here as not all platforms call
// OnWidgetBoundsChanged() when going fullscreen.
~DestroyingWidgetBoundsObserver() override = default;
// WidgetObserver:
void OnWidgetBoundsChanged(Widget* widget,
const gfx::Rect& new_bounds) override {
widget_observation_.Reset();
}
private:
base::ScopedObservation<Widget, WidgetObserver> widget_observation_{this};
};
// Deletes a Widget when the bounds change as part of toggling fullscreen.
// This is a regression test for https://crbug.com/1197436.
// Disabled on Mac: This test has historically deleted the Widget not during
// SetFullscreen, but at the end of the test. When the Widget is deleted inside
// SetFullscreen, the test crashes.
// https://crbug.com/1307486
#if BUILDFLAG(IS_MAC)
#define MAYBE_DeleteInSetFullscreen DISABLED_DeleteInSetFullscreen
#else
#define MAYBE_DeleteInSetFullscreen DeleteInSetFullscreen
#endif
TEST_F(DesktopWidgetTest, MAYBE_DeleteInSetFullscreen) {
std::unique_ptr<Widget> widget = std::make_unique<Widget>();
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
params.ownership = Widget::InitParams::CLIENT_OWNS_WIDGET;
widget->Init(std::move(params));
Widget* w = widget.get();
DestroyingWidgetBoundsObserver destroyer(w);
w->SetFullscreen(true);
}
// Tests that frame Layout is called when a widget goes fullscreen without
// changing its size or title.
TEST_F(WidgetTest, FullscreenFrameLayout) {
WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
auto frame_view = std::make_unique<ConfigurableTestFrameView>(widget.get());
ConfigurableTestFrameView* frame = frame_view.get();
widget->non_client_view()->SetFrameView(std::move(frame_view));
widget->Maximize();
RunPendingMessages();
EXPECT_FALSE(frame->fullscreen_layout_called());
widget->SetFullscreen(true);
widget->Show();
#if BUILDFLAG(IS_MAC)
// On macOS, a fullscreen layout is triggered from within SetFullscreen.
// https://crbug.com/1307496
EXPECT_TRUE(frame->fullscreen_layout_called());
#else
EXPECT_TRUE(ViewTestApi(frame).needs_layout());
#endif
widget->LayoutRootViewIfNecessary();
RunPendingMessages();
EXPECT_TRUE(frame->fullscreen_layout_called());
}
namespace {
// Trivial WidgetObserverTest that invokes Widget::IsActive() from
// OnWindowDestroying.
class IsActiveFromDestroyObserver : public WidgetObserver {
public:
explicit IsActiveFromDestroyObserver(Widget* widget) {
widget_observation_.Observe(widget);
}
IsActiveFromDestroyObserver(const IsActiveFromDestroyObserver&) = delete;
IsActiveFromDestroyObserver& operator=(const IsActiveFromDestroyObserver&) =
delete;
~IsActiveFromDestroyObserver() override = default;
void OnWidgetDestroying(Widget* widget) override {
widget->IsActive();
widget_observation_.Reset();
}
private:
base::ScopedObservation<Widget, WidgetObserver> widget_observation_{this};
};
} // namespace
class ChildDesktopWidgetTest : public DesktopWidgetTest {
public:
Widget::InitParams CreateParams(Widget::InitParams::Ownership ownership,
Widget::InitParams::Type type) override {
Widget::InitParams params =
DesktopWidgetTest::CreateParams(ownership, type);
if (context_) {
params.context = context_;
}
return params;
}
std::unique_ptr<Widget> CreateChildWidget(gfx::NativeWindow context) {
context_ = context;
return CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET);
}
private:
gfx::NativeWindow context_ = gfx::NativeWindow();
};
// Verifies Widget::IsActive() invoked from
// WidgetObserver::OnWidgetDestroying() in a child widget doesn't crash.
TEST_F(ChildDesktopWidgetTest, IsActiveFromDestroy) {
// Create two widgets, one a child of the other.
std::unique_ptr<Widget> parent_widget =
CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET);
parent_widget->Show();
std::unique_ptr<Widget> child_widget =
CreateChildWidget(parent_widget->GetNativeWindow());
IsActiveFromDestroyObserver observer(child_widget.get());
child_widget->Show();
parent_widget->CloseNow();
}
// Tests that events propagate through from the dispatcher with the correct
// event type, and that the different platforms behave the same.
TEST_F(WidgetTest, MouseEventTypesViaGenerator) {
WidgetAutoclosePtr widget(CreateTopLevelFramelessPlatformWidget());
EventCountView* view =
widget->GetRootView()->AddChildView(std::make_unique<EventCountView>());
view->set_handle_mode(EventCountView::HandleMode::kConsumeEvents);
view->SetBounds(10, 10, 50, 40);
widget->SetBounds(gfx::Rect(0, 0, 100, 80));
widget->Show();
auto generator =
CreateEventGenerator(GetContext(), widget->GetNativeWindow());
const gfx::Point view_center_point = view->GetBoundsInScreen().CenterPoint();
generator->set_current_screen_location(view_center_point);
generator->ClickLeftButton();
EXPECT_EQ(1, view->GetEventCount(ui::EventType::kMousePressed));
EXPECT_EQ(1, view->GetEventCount(ui::EventType::kMouseReleased));
EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, view->last_flags());
generator->PressRightButton();
EXPECT_EQ(2, view->GetEventCount(ui::EventType::kMousePressed));
EXPECT_EQ(1, view->GetEventCount(ui::EventType::kMouseReleased));
EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, view->last_flags());
generator->ReleaseRightButton();
EXPECT_EQ(2, view->GetEventCount(ui::EventType::kMousePressed));
EXPECT_EQ(2, view->GetEventCount(ui::EventType::kMouseReleased));
EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, view->last_flags());
// Test mouse move events.
EXPECT_EQ(0, view->GetEventCount(ui::EventType::kMouseMoved));
EXPECT_EQ(0, view->GetEventCount(ui::EventType::kMouseEntered));
// Move the mouse a displacement of (10, 10).
generator->MoveMouseTo(view_center_point + gfx::Vector2d(10, 10));
EXPECT_EQ(1, view->GetEventCount(ui::EventType::kMouseMoved));
EXPECT_EQ(1, view->GetEventCount(ui::EventType::kMouseEntered));
EXPECT_EQ(ui::EF_NONE, view->last_flags());
// Move it again - entered count shouldn't change.
generator->MoveMouseTo(view_center_point + gfx::Vector2d(11, 11));
EXPECT_EQ(2, view->GetEventCount(ui::EventType::kMouseMoved));
EXPECT_EQ(1, view->GetEventCount(ui::EventType::kMouseEntered));
EXPECT_EQ(0, view->GetEventCount(ui::EventType::kMouseExited));
// Move it off the view.
const gfx::Point out_of_bounds_point =
view->GetBoundsInScreen().bottom_right() + gfx::Vector2d(10, 10);
generator->MoveMouseTo(out_of_bounds_point);
EXPECT_EQ(2, view->GetEventCount(ui::EventType::kMouseMoved));
EXPECT_EQ(1, view->GetEventCount(ui::EventType::kMouseEntered));
EXPECT_EQ(1, view->GetEventCount(ui::EventType::kMouseExited));
// Move it back on.
generator->MoveMouseTo(view_center_point);
EXPECT_EQ(3, view->GetEventCount(ui::EventType::kMouseMoved));
EXPECT_EQ(2, view->GetEventCount(ui::EventType::kMouseEntered));
EXPECT_EQ(1, view->GetEventCount(ui::EventType::kMouseExited));
// Drargging. Cover HasCapture() and NativeWidgetPrivate::IsMouseButtonDown().
generator->DragMouseTo(out_of_bounds_point);
EXPECT_EQ(3, view->GetEventCount(ui::EventType::kMousePressed));
EXPECT_EQ(3, view->GetEventCount(ui::EventType::kMouseReleased));
EXPECT_EQ(1, view->GetEventCount(ui::EventType::kMouseDragged));
EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, view->last_flags());
}
// Tests that the root view is correctly set up for Widget types that do not
// require a non-client view, before any other views are added to the widget.
// That is, before Widget::ReorderNativeViews() is called which, if called with
// a root view not set, could cause the root view to get resized to the widget.
TEST_F(WidgetTest, NonClientWindowValidAfterInit) {
WidgetAutoclosePtr widget(CreateTopLevelFramelessPlatformWidget());
View* root_view = widget->GetRootView();
// Size the root view to exceed the widget bounds.
const gfx::Rect test_rect(0, 0, 500, 500);
root_view->SetBoundsRect(test_rect);
EXPECT_NE(test_rect.size(), widget->GetWindowBoundsInScreen().size());
EXPECT_EQ(test_rect, root_view->bounds());
widget->ReorderNativeViews();
EXPECT_EQ(test_rect, root_view->bounds());
}
#if BUILDFLAG(IS_WIN)
// Provides functionality to subclass a window and keep track of messages
// received.
class SubclassWindowHelper {
public:
explicit SubclassWindowHelper(HWND window)
: window_(window), message_to_destroy_on_(0) {
EXPECT_EQ(instance_, nullptr);
instance_ = this;
EXPECT_TRUE(Subclass());
}
SubclassWindowHelper(const SubclassWindowHelper&) = delete;
SubclassWindowHelper& operator=(const SubclassWindowHelper&) = delete;
~SubclassWindowHelper() {
Unsubclass();
instance_ = nullptr;
}
// Returns true if the |message| passed in was received.
bool received_message(unsigned int message) {
return (messages_.find(message) != messages_.end());
}
void Clear() { messages_.clear(); }
void set_message_to_destroy_on(unsigned int message) {
message_to_destroy_on_ = message;
}
private:
bool Subclass() {
old_proc_ = reinterpret_cast<WNDPROC>(::SetWindowLongPtr(
window_, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(WndProc)));
return old_proc_ != nullptr;
}
void Unsubclass() {
::SetWindowLongPtr(window_, GWLP_WNDPROC,
reinterpret_cast<LONG_PTR>(old_proc_));
}
static LRESULT CALLBACK WndProc(HWND window,
unsigned int message,
WPARAM w_param,
LPARAM l_param) {
EXPECT_NE(instance_, nullptr);
EXPECT_EQ(window, instance_->window_);
// Keep track of messags received for this window.
instance_->messages_.insert(message);
LRESULT ret = ::CallWindowProc(instance_->old_proc_, window, message,
w_param, l_param);
if (message == instance_->message_to_destroy_on_) {
instance_->Unsubclass();
::DestroyWindow(window);
}
return ret;
}
WNDPROC old_proc_;
HWND window_;
static SubclassWindowHelper* instance_;
std::set<unsigned int> messages_;
unsigned int message_to_destroy_on_;
};
SubclassWindowHelper* SubclassWindowHelper::instance_ = nullptr;
// This test validates whether the WM_SYSCOMMAND message for SC_MOVE is
// received when we post a WM_NCLBUTTONDOWN message for the caption in the
// following scenarios:-
// 1. Posting a WM_NCMOUSEMOVE message for a different location.
// 2. Posting a WM_NCMOUSEMOVE message with a different hittest code.
// 3. Posting a WM_MOUSEMOVE message.
// Disabled because of flaky timeouts: http://crbug.com/592742
TEST_F(DesktopWidgetTest,
DISABLED_SysCommandMoveOnNCLButtonDownOnCaptionAndMoveTest) {
std::unique_ptr<Widget> widget = CreateTestWidget(
Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_WINDOW);
widget->Show();
::SetCursorPos(500, 500);
HWND window = widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
SubclassWindowHelper subclass_helper(window);
// Posting just a WM_NCLBUTTONDOWN message should not result in a
// WM_SYSCOMMAND
::PostMessage(window, WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(100, 100));
RunPendingMessages();
EXPECT_TRUE(subclass_helper.received_message(WM_NCLBUTTONDOWN));
EXPECT_FALSE(subclass_helper.received_message(WM_SYSCOMMAND));
subclass_helper.Clear();
// Posting a WM_NCLBUTTONDOWN message followed by a WM_NCMOUSEMOVE at the
// same location should not result in a WM_SYSCOMMAND message.
::PostMessage(window, WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(100, 100));
::PostMessage(window, WM_NCMOUSEMOVE, HTCAPTION, MAKELPARAM(100, 100));
RunPendingMessages();
EXPECT_TRUE(subclass_helper.received_message(WM_NCLBUTTONDOWN));
EXPECT_TRUE(subclass_helper.received_message(WM_NCMOUSEMOVE));
EXPECT_FALSE(subclass_helper.received_message(WM_SYSCOMMAND));
subclass_helper.Clear();
// Posting a WM_NCLBUTTONDOWN message followed by a WM_NCMOUSEMOVE at a
// different location should result in a WM_SYSCOMMAND message.
::PostMessage(window, WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(100, 100));
::PostMessage(window, WM_NCMOUSEMOVE, HTCAPTION, MAKELPARAM(110, 110));
RunPendingMessages();
EXPECT_TRUE(subclass_helper.received_message(WM_NCLBUTTONDOWN));
EXPECT_TRUE(subclass_helper.received_message(WM_NCMOUSEMOVE));
EXPECT_TRUE(subclass_helper.received_message(WM_SYSCOMMAND));
subclass_helper.Clear();
// Posting a WM_NCLBUTTONDOWN message followed by a WM_NCMOUSEMOVE at a
// different location with a different hittest code should result in a
// WM_SYSCOMMAND message.
::PostMessage(window, WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(100, 100));
::PostMessage(window, WM_NCMOUSEMOVE, HTTOP, MAKELPARAM(110, 102));
RunPendingMessages();
EXPECT_TRUE(subclass_helper.received_message(WM_NCLBUTTONDOWN));
EXPECT_TRUE(subclass_helper.received_message(WM_NCMOUSEMOVE));
EXPECT_TRUE(subclass_helper.received_message(WM_SYSCOMMAND));
subclass_helper.Clear();
// Posting a WM_NCLBUTTONDOWN message followed by a WM_MOUSEMOVE should
// result in a WM_SYSCOMMAND message.
::PostMessage(window, WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(100, 100));
::PostMessage(window, WM_MOUSEMOVE, HTCLIENT, MAKELPARAM(110, 110));
RunPendingMessages();
EXPECT_TRUE(subclass_helper.received_message(WM_NCLBUTTONDOWN));
EXPECT_TRUE(subclass_helper.received_message(WM_MOUSEMOVE));
EXPECT_TRUE(subclass_helper.received_message(WM_SYSCOMMAND));
}
// This test validates that destroying the window in the context of the
// WM_SYSCOMMAND message with SC_MOVE does not crash.
// Disabled because of flaky timeouts: http://crbug.com/592742
TEST_F(DesktopWidgetTest, DISABLED_DestroyInSysCommandNCLButtonDownOnCaption) {
std::unique_ptr<Widget> widget = CreateTestWidget(
Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_WINDOW);
widget->Show();
::SetCursorPos(500, 500);
HWND window = widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget();
SubclassWindowHelper subclass_helper(window);
// Destroying the window in the context of the WM_SYSCOMMAND message
// should not crash.
subclass_helper.set_message_to_destroy_on(WM_SYSCOMMAND);
::PostMessage(window, WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(100, 100));
::PostMessage(window, WM_NCMOUSEMOVE, HTCAPTION, MAKELPARAM(110, 110));
RunPendingMessages();
EXPECT_TRUE(subclass_helper.received_message(WM_NCLBUTTONDOWN));
EXPECT_TRUE(subclass_helper.received_message(WM_SYSCOMMAND));
}
#endif
// Test that the z-order levels round-trip.
TEST_F(WidgetTest, ZOrderLevel) {
WidgetAutoclosePtr widget(CreateTopLevelNativeWidget());
EXPECT_EQ(ui::ZOrderLevel::kNormal, widget->GetZOrderLevel());
widget->SetZOrderLevel(ui::ZOrderLevel::kFloatingWindow);
EXPECT_EQ(ui::ZOrderLevel::kFloatingWindow, widget->GetZOrderLevel());
widget->SetZOrderLevel(ui::ZOrderLevel::kNormal);
EXPECT_EQ(ui::ZOrderLevel::kNormal, widget->GetZOrderLevel());
}
namespace {
class ScaleFactorView : public View {
METADATA_HEADER(ScaleFactorView, View)
public:
ScaleFactorView() = default;
ScaleFactorView(const ScaleFactorView&) = delete;
ScaleFactorView& operator=(const ScaleFactorView&) = delete;
// Overridden from ui::LayerDelegate:
void OnDeviceScaleFactorChanged(float old_device_scale_factor,
float new_device_scale_factor) override {
last_scale_factor_ = new_device_scale_factor;
View::OnDeviceScaleFactorChanged(old_device_scale_factor,
new_device_scale_factor);
}
float last_scale_factor() const { return last_scale_factor_; }
private:
float last_scale_factor_ = 0.f;
};
BEGIN_METADATA(ScaleFactorView)
END_METADATA
} // namespace
// Ensure scale factor changes are propagated from the native Widget.
TEST_F(WidgetTest, OnDeviceScaleFactorChanged) {
// Automatically close the widget, but not delete it.
WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
ScaleFactorView* view = new ScaleFactorView;
widget->GetRootView()->AddChildViewRaw(view);
float scale_factor = widget->GetLayer()->device_scale_factor();
EXPECT_NE(scale_factor, 0.f);
// For views that are not layer-backed, adding the view won't notify the view
// about the initial scale factor. Fake it.
view->OnDeviceScaleFactorChanged(0.f, scale_factor);
EXPECT_EQ(scale_factor, view->last_scale_factor());
// Changes should be propagated.
scale_factor *= 2.0f;
widget->GetLayer()->OnDeviceScaleFactorChanged(scale_factor);
EXPECT_EQ(scale_factor, view->last_scale_factor());
}
// Test that WidgetRemovalsObserver::OnWillRemoveView is called when deleting
// a view.
TEST_F(WidgetTest, WidgetRemovalsObserverCalled) {
WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
TestWidgetRemovalsObserver removals_observer;
widget->AddRemovalsObserver(&removals_observer);
View* parent = new View();
widget->client_view()->AddChildViewRaw(parent);
View* child = new View();
parent->AddChildViewRaw(child);
widget->client_view()->RemoveChildView(parent);
EXPECT_TRUE(removals_observer.DidRemoveView(parent));
EXPECT_FALSE(removals_observer.DidRemoveView(child));
// Calling RemoveChildView() doesn't delete the view, but deleting
// |parent| will automatically delete |child|.
delete parent;
widget->RemoveRemovalsObserver(&removals_observer);
}
// Test that WidgetRemovalsObserver::OnWillRemoveView is called when deleting
// the root view.
TEST_F(WidgetTest, WidgetRemovalsObserverCalledWhenRemovingRootView) {
WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
TestWidgetRemovalsObserver removals_observer;
widget->AddRemovalsObserver(&removals_observer);
views::View* root_view = widget->GetRootView();
widget.reset();
EXPECT_TRUE(removals_observer.DidRemoveView(root_view));
}
// Test that WidgetRemovalsObserver::OnWillRemoveView is called when moving
// a view from one widget to another, but not when moving a view within
// the same widget.
TEST_F(WidgetTest, WidgetRemovalsObserverCalledWhenMovingBetweenWidgets) {
WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
TestWidgetRemovalsObserver removals_observer;
widget->AddRemovalsObserver(&removals_observer);
View* parent = new View();
widget->client_view()->AddChildViewRaw(parent);
View* child = new View();
widget->client_view()->AddChildViewRaw(child);
// Reparenting the child shouldn't call the removals observer.
parent->AddChildViewRaw(child);
EXPECT_FALSE(removals_observer.DidRemoveView(child));
// Moving the child to a different widget should call the removals observer.
WidgetAutoclosePtr widget2(CreateTopLevelPlatformWidget());
widget2->client_view()->AddChildViewRaw(child);
EXPECT_TRUE(removals_observer.DidRemoveView(child));
widget->RemoveRemovalsObserver(&removals_observer);
}
// Test dispatch of ui::EventType::kMousewheel.
TEST_F(WidgetTest, MouseWheelEvent) {
WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
widget->SetBounds(gfx::Rect(0, 0, 600, 600));
EventCountView* event_count_view =
widget->client_view()->AddChildView(std::make_unique<EventCountView>());
event_count_view->SetBounds(0, 0, 600, 600);
widget->Show();
auto event_generator =
CreateEventGenerator(GetContext(), widget->GetNativeWindow());
event_generator->MoveMouseWheel(1, 1);
EXPECT_EQ(1, event_count_view->GetEventCount(ui::EventType::kMousewheel));
}
// Test that ui::EventType::kMouseEntered is dispatched even when not followed
// by ui::EventType::kMouseMoved.
TEST_F(WidgetTest, MouseEnteredWithoutMoved) {
WidgetAutoclosePtr widget(CreateTopLevelFramelessPlatformWidget());
widget->SetBounds(gfx::Rect(0, 0, 100, 100));
auto* root_view = static_cast<internal::RootView*>(widget->GetRootView());
auto* v1 = root_view->AddChildView(std::make_unique<EventCountView>());
v1->SetBounds(10, 10, 10, 10);
auto* v2 = root_view->AddChildView(std::make_unique<EventCountView>());
v2->SetBounds(20, 10, 10, 10);
widget->Show();
auto generator =
CreateEventGenerator(GetContext(), widget->GetNativeWindow());
// Enter |v1| and check that it received ui::EventType::kMouseEntered.
auto enter_location = v1->GetBoundsInScreen().CenterPoint();
generator->set_current_screen_location(enter_location);
generator->SendMouseEnter();
EXPECT_EQ(v1, GetMouseMoveHandler(root_view));
EXPECT_EQ(1, v1->GetEventCount(ui::EventType::kMouseEntered));
EXPECT_EQ(0, v1->GetEventCount(ui::EventType::kMouseMoved));
EXPECT_EQ(0, v1->GetEventCount(ui::EventType::kMouseExited));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kMouseEntered));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kMouseMoved));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kMouseExited));
// Enter |v2| and check that |v1| received ui::EventType::kMouseExited and
// |v2| received ui::EventType::kMouseEntered.
enter_location = v2->GetBoundsInScreen().CenterPoint();
generator->set_current_screen_location(enter_location);
generator->SendMouseEnter();
EXPECT_EQ(v2, GetMouseMoveHandler(root_view));
EXPECT_EQ(1, v1->GetEventCount(ui::EventType::kMouseEntered));
EXPECT_EQ(0, v1->GetEventCount(ui::EventType::kMouseMoved));
EXPECT_EQ(1, v1->GetEventCount(ui::EventType::kMouseExited));
EXPECT_EQ(1, v2->GetEventCount(ui::EventType::kMouseEntered));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kMouseMoved));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kMouseExited));
// Enter |root_view| and check that |v2| received ui::EventType::kMouseExited.
enter_location = gfx::Point(0, 0);
generator->set_current_screen_location(enter_location);
generator->SendMouseEnter();
EXPECT_EQ(nullptr, GetMouseMoveHandler(root_view));
EXPECT_EQ(1, v1->GetEventCount(ui::EventType::kMouseEntered));
EXPECT_EQ(0, v1->GetEventCount(ui::EventType::kMouseMoved));
EXPECT_EQ(1, v1->GetEventCount(ui::EventType::kMouseExited));
EXPECT_EQ(1, v2->GetEventCount(ui::EventType::kMouseEntered));
EXPECT_EQ(0, v2->GetEventCount(ui::EventType::kMouseMoved));
EXPECT_EQ(1, v2->GetEventCount(ui::EventType::kMouseExited));
}
// Test that ui::EventType::kMouseMoved after ui::EventType::kMouseEntered
// doesn't cause an extra ui::EventType::kMouseEntered.
TEST_F(WidgetTest, MouseMovedAfterEnteredDoesntCauseEntered) {
WidgetAutoclosePtr widget(CreateTopLevelFramelessPlatformWidget());
widget->SetBounds(gfx::Rect(0, 0, 100, 100));
auto* root_view = widget->GetRootView();
auto* v = root_view->AddChildView(std::make_unique<EventCountView>());
v->SetBounds(10, 10, 10, 10);
widget->Show();
auto generator =
CreateEventGenerator(GetContext(), widget->GetNativeWindow());
// Enter |v|.
auto enter_location = v->GetBoundsInScreen().CenterPoint();
generator->set_current_screen_location(enter_location);
generator->SendMouseEnter();
// Send ui::EventType::kMouseMoved at the same location and check that it
// didn't generate ui::EventType::kMouseEntered again.
generator->MoveMouseBy(0, 0);
EXPECT_EQ(1, v->GetEventCount(ui::EventType::kMouseEntered));
EXPECT_EQ(1, v->GetEventCount(ui::EventType::kMouseMoved));
// Reset state by entering |root_view|.
generator->MoveMouseTo(0, 0);
// Enter |v| again.
generator->set_current_screen_location(enter_location);
generator->SendMouseEnter();
// Send ui::EventType::kMouseMoved at a slightly offset location and check
// that it didn't generate ui::EventType::kMouseEntered again.
generator->MoveMouseBy(1, 1);
EXPECT_EQ(2, v->GetEventCount(ui::EventType::kMouseEntered));
EXPECT_EQ(2, v->GetEventCount(ui::EventType::kMouseMoved));
}
class CloseFromClosingObserver : public WidgetObserver {
public:
~CloseFromClosingObserver() override {
EXPECT_TRUE(was_on_widget_closing_called_);
}
// WidgetObserver:
void OnWidgetClosing(Widget* widget) override {
// OnWidgetClosing() should only be called once, even if Close() is called
// after CloseNow().
ASSERT_FALSE(was_on_widget_closing_called_);
was_on_widget_closing_called_ = true;
widget->Close();
}
private:
bool was_on_widget_closing_called_ = false;
};
TEST_F(WidgetTest, CloseNowFollowedByCloseDoesntCallOnWidgetClosingTwice) {
CloseFromClosingObserver observer;
std::unique_ptr<Widget> widget = std::make_unique<Widget>();
Widget::InitParams params = CreateParams(
Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_WINDOW);
widget->Init(std::move(params));
widget->AddObserver(&observer);
widget->CloseNow();
widget->RemoveObserver(&observer);
widget.reset();
// Assertions are in CloseFromClosingObserver.
}
namespace {
class TestSaveWindowPlacementWidgetDelegate : public TestDesktopWidgetDelegate {
public:
TestSaveWindowPlacementWidgetDelegate() = default;
TestSaveWindowPlacementWidgetDelegate(
const TestSaveWindowPlacementWidgetDelegate&) = delete;
TestSaveWindowPlacementWidgetDelegate operator=(
const TestSaveWindowPlacementWidgetDelegate&) = delete;
~TestSaveWindowPlacementWidgetDelegate() override = default;
void set_should_save_window_placement(bool should_save) {
should_save_window_placement_ = should_save;
}
int save_window_placement_count() const {
return save_window_placement_count_;
}
// ViewsDelegate:
std::string GetWindowName() const final { return GetWidget()->GetName(); }
bool ShouldSaveWindowPlacement() const final {
return should_save_window_placement_;
}
void SaveWindowPlacement(const gfx::Rect& bounds,
ui::mojom::WindowShowState show_state) override {
save_window_placement_count_++;
}
private:
bool should_save_window_placement_ = true;
int save_window_placement_count_ = 0;
};
} // namespace
TEST_F(WidgetTest, ShouldSaveWindowPlacement) {
for (bool save : {false, true}) {
SCOPED_TRACE(save ? "ShouldSave" : "ShouldNotSave");
TestSaveWindowPlacementWidgetDelegate widget_delegate;
widget_delegate.set_should_save_window_placement(save);
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
params.ownership = Widget::InitParams::CLIENT_OWNS_WIDGET;
params.name = "TestWidget";
widget_delegate.InitWidget(std::move(params));
auto* widget = widget_delegate.GetWidget();
widget->Close();
EXPECT_EQ(save ? 1 : 0, widget_delegate.save_window_placement_count());
}
}
TEST_F(WidgetTest, WidgetAXManagerNotInitializedWhenFlagIsOff) {
WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
widget->Show();
EXPECT_EQ(widget->ax_manager(), nullptr);
}
class WidgetWithAXTree : public WidgetTest {
public:
WidgetWithAXTree() = default;
WidgetWithAXTree(const WidgetWithAXTree&) = delete;
WidgetWithAXTree& operator=(const WidgetWithAXTree&) = delete;
~WidgetWithAXTree() override = default;
private:
base::test::ScopedFeatureList scoped_feature_list_{
features::kAccessibilityTreeForViews};
};
TEST_F(WidgetWithAXTree, WidgetAXManagerInitializedWhenFlagIsOn) {
WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
widget->Show();
EXPECT_NE(widget->ax_manager(), nullptr);
}
TEST_F(WidgetTest, RootViewAccessibilityCacheInitialized) {
std::unique_ptr<Widget> widget =
CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET);
widget->Show();
EXPECT_TRUE(widget->GetRootView()->GetViewAccessibility().is_initialized());
}
TEST_F(WidgetTest, ClientViewAccessibilityProperties) {
std::unique_ptr<Widget> widget = CreateTestWidget(
Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_WINDOW);
widget->Show();
ui::AXNodeData node_data;
widget->client_view()->GetViewAccessibility().GetAccessibleNodeData(
&node_data);
EXPECT_EQ(node_data.role, ax::mojom::Role::kClient);
}
TEST_F(WidgetTest, NonClientViewAccessibilityProperties) {
std::unique_ptr<Widget> widget = CreateTestWidget(
Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_WINDOW);
NonClientView* non_client_view = widget->non_client_view();
non_client_view->SetFrameView(CreateMinimumSizeFrameView(widget.get()));
widget->Show();
ui::AXNodeData node_data;
non_client_view->GetViewAccessibility().GetAccessibleNodeData(&node_data);
EXPECT_EQ(node_data.role, ax::mojom::Role::kClient);
node_data = ui::AXNodeData();
non_client_view->frame_view()->GetViewAccessibility().GetAccessibleNodeData(
&node_data);
EXPECT_EQ(node_data.role, ax::mojom::Role::kClient);
}
TEST_F(WidgetTest, UpdateAccessibleURLForRootView) {
std::unique_ptr<Widget> widget = CreateTestWidget(
Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_WINDOW);
widget->Show();
const GURL test_url("https://example.com");
widget->UpdateAccessibleURLForRootView(test_url);
ui::AXNodeData node_data;
widget->GetRootView()->GetViewAccessibility().GetAccessibleNodeData(
&node_data);
EXPECT_EQ(node_data.GetStringAttribute(ax::mojom::StringAttribute::kUrl),
test_url);
}
class WidgetChildObserver : public WidgetObserver {
public:
explicit WidgetChildObserver(Widget* widget) { observation_.Observe(widget); }
const Widget* child_widget() const { return child_widget_; }
private:
// WidgetObserver:
void OnWidgetChildAdded(Widget* widget, Widget* child_widget) override {
child_widget_ = child_widget;
WidgetObserver::OnWidgetChildAdded(widget, child_widget);
}
void OnWidgetChildRemoved(Widget* widget, Widget* child_widget) override {
EXPECT_EQ(widget, observation_.GetSource());
EXPECT_EQ(child_widget, child_widget_);
child_widget_ = nullptr;
WidgetObserver::OnWidgetChildRemoved(widget, child_widget);
}
base::ScopedObservation<Widget, WidgetObserver> observation_{this};
raw_ptr<Widget> child_widget_ = nullptr;
};
TEST_F(WidgetTest, ChildWidgetNotifiesObserverWhenInitializedAndDestroyed) {
// Adding a child widget should call back the observer.
std::unique_ptr<Widget> widget = base::WrapUnique(
CreateTopLevelPlatformWidget(Widget::InitParams::CLIENT_OWNS_WIDGET));
WidgetChildObserver observer(widget.get());
std::unique_ptr<Widget> child_widget =
base::WrapUnique(CreateChildPlatformWidget(
widget->GetNativeView(), Widget::InitParams::CLIENT_OWNS_WIDGET));
EXPECT_EQ(observer.child_widget(), child_widget.get());
// Destroy the child and verify that the observer was notified.
child_widget.reset();
EXPECT_EQ(observer.child_widget(), nullptr);
}
TEST_F(WidgetTest, ChildWidgetNotifiesObserverWhenReparented) {
// Verify that reparenting a child widget notifies both the outgoing and
// incoming parent widgets.
std::unique_ptr<Widget> widget_1 = base::WrapUnique(
CreateTopLevelPlatformWidget(Widget::InitParams::CLIENT_OWNS_WIDGET));
WidgetChildObserver observer_1(widget_1.get());
std::unique_ptr<Widget> widget_2 = base::WrapUnique(
CreateTopLevelPlatformWidget(Widget::InitParams::CLIENT_OWNS_WIDGET));
WidgetChildObserver observer_2(widget_2.get());
// Create a child widget of `widget_1`.
std::unique_ptr<Widget> child_widget =
base::WrapUnique(CreateChildPlatformWidget(
widget_1->GetNativeView(), Widget::InitParams::CLIENT_OWNS_WIDGET));
EXPECT_EQ(observer_1.child_widget(), child_widget.get());
Widget::ReparentNativeView(child_widget->GetNativeView(),
widget_2->GetNativeView());
EXPECT_EQ(observer_1.child_widget(), nullptr);
EXPECT_EQ(observer_2.child_widget(), child_widget.get());
child_widget.reset();
EXPECT_EQ(observer_2.child_widget(), nullptr);
}
TEST_F(WidgetTest, NativeWidgetNotifiedOfWidgetDestructionForClientOwnsWidget) {
auto widget = std::make_unique<Widget>();
Widget::InitParams params = CreateParams(
Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_WINDOW);
auto native_widget =
std::make_unique<testing::NiceMock<MockNativeWidget>>(widget.get());
ON_CALL(*native_widget, CreateNonClientFrameView).WillByDefault([]() {
return std::make_unique<NonClientFrameView>();
});
params.native_widget = native_widget.get();
widget->Init(std::move(params));
EXPECT_CALL(*native_widget, ClientDestroyedWidget());
widget.reset();
}
// Parameterized test that verifies the behavior of SetAspectRatio with respect
// to the excluded margin.
class WidgetSetAspectRatioTest
: public ViewsTestBase,
public testing::WithParamInterface<gfx::Size /* margin */> {
public:
WidgetSetAspectRatioTest() : margin_(GetParam()) {}
WidgetSetAspectRatioTest(const WidgetSetAspectRatioTest&) = delete;
WidgetSetAspectRatioTest& operator=(const WidgetSetAspectRatioTest&) = delete;
~WidgetSetAspectRatioTest() override = default;
// ViewsTestBase:
void SetUp() override {
ViewsTestBase::SetUp();
widget_ = std::make_unique<Widget>();
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
native_widget_ = std::make_unique<MockNativeWidget>(widget());
ON_CALL(*native_widget(), CreateNonClientFrameView).WillByDefault([this]() {
auto frame_view_with_fixed_margin =
std::make_unique<test::ConfigurableTestFrameView>(widget_.get());
frame_view_with_fixed_margin->set_client_view_margin(margin());
return frame_view_with_fixed_margin;
});
params.native_widget = native_widget();
widget()->Init(std::move(params));
task_environment()->RunUntilIdle();
}
void TearDown() override {
// `ViewAccessibility` objects have some references to the `widget` which
// must be updated when the widget is freed. The function that is in charge
// of clearing these lists however (`OnNativeWidgetDestroying`), is never
// called in this test suite because we use a `MockNativeWindow` rather than
// a `NativeWindow`. So we make sure this clean up happens manually.
widget()->OnNativeWidgetDestroying();
native_widget_.reset();
widget()->Close();
widget_.reset();
ViewsTestBase::TearDown();
}
const gfx::Size& margin() const { return margin_; }
Widget* widget() { return widget_.get(); }
MockNativeWidget* native_widget() { return native_widget_.get(); }
private:
// Margin around the client view that should be excluded.
const gfx::Size margin_;
std::unique_ptr<Widget> widget_;
std::unique_ptr<MockNativeWidget> native_widget_;
};
TEST_P(WidgetSetAspectRatioTest, SetAspectRatioIncludesMargin) {
// Provide a nonzero size. It doesn't particularly matter what, as long as
// it's larger than our margin.
const gfx::Rect root_view_bounds(0, 0, 100, 200);
ASSERT_GT(root_view_bounds.width(), margin().width());
ASSERT_GT(root_view_bounds.height(), margin().height());
widget()->non_client_view()->SetBoundsRect(root_view_bounds);
// Verify that the excluded margin matches the margin that our custom
// non-client frame provides.
const gfx::SizeF aspect_ratio(1.5f, 1.0f);
EXPECT_CALL(*native_widget(), SetAspectRatio(aspect_ratio, margin()));
widget()->SetAspectRatio(aspect_ratio);
}
INSTANTIATE_TEST_SUITE_P(WidgetSetAspectRatioTestInstantiation,
WidgetSetAspectRatioTest,
::testing::Values(gfx::Size(15, 20), gfx::Size(0, 0)));
class WidgetShadowTest : public WidgetTest {
public:
WidgetShadowTest() = default;
WidgetShadowTest(const WidgetShadowTest&) = delete;
WidgetShadowTest& operator=(const WidgetShadowTest&) = delete;
~WidgetShadowTest() override = default;
// WidgetTest:
void SetUp() override {
set_native_widget_type(NativeWidgetType::kDesktop);
WidgetTest::SetUp();
InitControllers();
}
void TearDown() override {
#if defined(USE_AURA) && !BUILDFLAG(ENABLE_DESKTOP_AURA)
shadow_controller_.reset();
focus_controller_.reset();
#endif
WidgetTest::TearDown();
}
Widget::InitParams CreateParams(Widget::InitParams::Ownership ownership,
Widget::InitParams::Type type) override {
Widget::InitParams params =
WidgetTest::CreateParams(ownership, override_type_.value_or(type));
params.shadow_type = Widget::InitParams::ShadowType::kDrop;
params.shadow_elevation = 10;
params.name = name_;
params.child = force_child_;
return params;
}
protected:
std::optional<Widget::InitParams::Type> override_type_;
std::string name_;
bool force_child_ = false;
private:
#if BUILDFLAG(ENABLE_DESKTOP_AURA) || BUILDFLAG(IS_MAC)
void InitControllers() {}
#else
class TestFocusRules : public wm::BaseFocusRules {
public:
TestFocusRules() = default;
TestFocusRules(const TestFocusRules&) = delete;
TestFocusRules& operator=(const TestFocusRules&) = delete;
bool SupportsChildActivation(const aura::Window* window) const override {
return true;
}
};
void InitControllers() {
focus_controller_ =
std::make_unique<wm::FocusController>(new TestFocusRules);
shadow_controller_ = std::make_unique<wm::ShadowController>(
focus_controller_.get(), nullptr);
}
std::unique_ptr<wm::FocusController> focus_controller_;
std::unique_ptr<wm::ShadowController> shadow_controller_;
#endif // !BUILDFLAG(ENABLE_DESKTOP_AURA) && !BUILDFLAG(IS_MAC)
};
// Disabled on Mac: All drop shadows are managed out of process for now.
#if BUILDFLAG(IS_MAC)
#define MAYBE_ShadowsInRootWindow DISABLED_ShadowsInRootWindow
#else
#define MAYBE_ShadowsInRootWindow ShadowsInRootWindow
#endif
// Test that shadows are not added to root windows when created or upon
// activation. Test that shadows are added to non-root windows even if not
// activated.
TEST_F(WidgetShadowTest, MAYBE_ShadowsInRootWindow) {
#if defined(USE_AURA) && !BUILDFLAG(ENABLE_DESKTOP_AURA)
// On ChromeOS, top-levels have shadows.
bool top_level_window_should_have_shadow = true;
#else
// On non-chromeos platforms, the hosting OS is responsible for the shadow.
bool top_level_window_should_have_shadow = false;
#endif
// To start, just create a Widget. This constructs the first ShadowController
// which will start observing the environment for additional aura::Window
// initialization. The very first ShadowController in DesktopNativeWidgetAura
// is created after the call to aura::Window::Init(), so the ShadowController
// Impl class won't ever see this first Window being initialized.
name_ = "other_top_level";
Widget* other_top_level = CreateTopLevelNativeWidget();
name_ = "top_level";
Widget* top_level = CreateTopLevelNativeWidget();
top_level->SetBounds(gfx::Rect(100, 100, 320, 200));
EXPECT_FALSE(WidgetHasInProcessShadow(top_level));
EXPECT_FALSE(top_level->IsVisible());
top_level->ShowInactive();
EXPECT_EQ(top_level_window_should_have_shadow,
WidgetHasInProcessShadow(top_level));
top_level->Show();
EXPECT_EQ(top_level_window_should_have_shadow,
WidgetHasInProcessShadow(top_level));
name_ = "control";
Widget* control = CreateChildNativeWidgetWithParent(top_level);
control->SetBounds(gfx::Rect(20, 20, 160, 100));
// Widgets of TYPE_CONTROL become visible during Init, so start with a shadow.
EXPECT_TRUE(WidgetHasInProcessShadow(control));
control->ShowInactive();
EXPECT_TRUE(WidgetHasInProcessShadow(control));
control->Show();
EXPECT_TRUE(WidgetHasInProcessShadow(control));
name_ = "child";
override_type_ = Widget::InitParams::TYPE_POPUP;
force_child_ = true;
Widget* child = CreateChildNativeWidgetWithParent(top_level);
child->SetBounds(gfx::Rect(20, 20, 160, 100));
// Now false: the Widget hasn't been shown yet.
EXPECT_FALSE(WidgetHasInProcessShadow(child));
child->ShowInactive();
EXPECT_TRUE(WidgetHasInProcessShadow(child));
child->Show();
EXPECT_TRUE(WidgetHasInProcessShadow(child));
other_top_level->Show();
// Re-activate the top level window. This handles a hypothetical case where
// a shadow is added via the ActivationChangeObserver rather than by the
// aura::WindowObserver. Activation changes only modify an existing shadow
// (if there is one), but should never install a Shadow, even if the Window
// properties otherwise say it should have one.
top_level->Show();
EXPECT_EQ(top_level_window_should_have_shadow,
WidgetHasInProcessShadow(top_level));
top_level->Close();
other_top_level->Close();
}
#if BUILDFLAG(IS_WIN)
// Tests the case where an intervening owner popup window is destroyed out from
// under the currently active modal top-level window. In this instance, the
// remaining top-level windows should be re-enabled.
TEST_F(DesktopWidgetTest, WindowModalOwnerDestroyedEnabledTest) {
// top_level_widget owns owner_dialog_widget which owns owned_dialog_widget.
std::unique_ptr<Widget> top_level_widget =
CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET);
top_level_widget->Show();
// Create the owner modal dialog.
const auto create_params = [this](Widget* widget, gfx::NativeView parent) {
Widget::InitParams init_params =
CreateParamsForTestWidget(Widget::InitParams::TYPE_WINDOW);
init_params.delegate = new WidgetDelegate();
init_params.delegate->SetModalType(ui::mojom::ModalType::kWindow);
init_params.parent = parent;
init_params.native_widget =
new test::TestPlatformNativeWidget<DesktopNativeWidgetAura>(
widget, false, nullptr);
return init_params;
};
Widget owner_dialog_widget;
owner_dialog_widget.Init(
create_params(&owner_dialog_widget, top_level_widget->GetNativeView()));
owner_dialog_widget.Show();
HWND owner_hwnd = HWNDForWidget(&owner_dialog_widget);
// Create the owned modal dialog.
Widget owned_dialog_widget;
owned_dialog_widget.Init(
create_params(&owned_dialog_widget, owner_dialog_widget.GetNativeView()));
owned_dialog_widget.Show();
HWND owned_hwnd = HWNDForWidget(&owned_dialog_widget);
RunPendingMessages();
HWND top_hwnd = HWNDForWidget(top_level_widget.get());
EXPECT_FALSE(!!IsWindowEnabled(owner_hwnd));
EXPECT_FALSE(!!IsWindowEnabled(top_hwnd));
EXPECT_TRUE(!!IsWindowEnabled(owned_hwnd));
owner_dialog_widget.CloseNow();
RunPendingMessages();
EXPECT_FALSE(!!IsWindow(owner_hwnd));
EXPECT_FALSE(!!IsWindow(owned_hwnd));
EXPECT_TRUE(!!IsWindowEnabled(top_hwnd));
top_level_widget->CloseNow();
}
TEST_F(DesktopWidgetTest, StackAboveTest) {
WidgetAutoclosePtr root_one(CreateTopLevelNativeWidget());
WidgetAutoclosePtr root_two(CreateTopLevelNativeWidget());
Widget* child_one = CreateChildNativeWidgetWithParent(root_one->AsWidget());
Widget* child_one_b = CreateChildNativeWidgetWithParent(root_one->AsWidget());
Widget* child_two = CreateChildNativeWidgetWithParent(root_two->AsWidget());
Widget* grandchild_one =
CreateChildNativeWidgetWithParent(child_one->AsWidget());
Widget* grandchild_two =
CreateChildNativeWidgetWithParent(child_two->AsWidget());
root_one->ShowInactive();
child_one->ShowInactive();
child_one_b->ShowInactive();
grandchild_one->ShowInactive();
root_two->ShowInactive();
child_two->ShowInactive();
grandchild_two->ShowInactive();
// Creates the following where Z-Order is from Left to Right.
// root_one root_two
// / \ /
// child_one_b child_one child_two
// / /
// grandchild_one grandchild_two
//
// Note: child_one and grandchild_one were brought to front
// when grandchild_one was shown.
// Child elements are stacked above parent.
EXPECT_TRUE(child_one->IsStackedAbove(root_one->GetNativeView()));
EXPECT_TRUE(child_one_b->IsStackedAbove(root_one->GetNativeView()));
EXPECT_TRUE(grandchild_one->IsStackedAbove(child_one->GetNativeView()));
EXPECT_TRUE(grandchild_two->IsStackedAbove(root_two->GetNativeView()));
// Siblings with higher z-order are stacked correctly.
EXPECT_TRUE(child_one->IsStackedAbove(child_one_b->GetNativeView()));
EXPECT_TRUE(grandchild_one->IsStackedAbove(child_one_b->GetNativeView()));
// Root elements are stacked above child of a root with lower z-order.
EXPECT_TRUE(root_two->IsStackedAbove(root_one->GetNativeView()));
EXPECT_TRUE(root_two->IsStackedAbove(child_one_b->GetNativeView()));
// Child elements are stacked above child of root with lower z-order.
EXPECT_TRUE(child_two->IsStackedAbove(child_one_b->GetNativeView()));
EXPECT_TRUE(child_two->IsStackedAbove(grandchild_one->GetNativeView()));
EXPECT_TRUE(grandchild_two->IsStackedAbove(child_one->GetNativeView()));
EXPECT_TRUE(grandchild_two->IsStackedAbove(root_one->GetNativeView()));
// False cases to verify function is not just returning true for all cases.
EXPECT_FALSE(root_one->IsStackedAbove(grandchild_two->GetNativeView()));
EXPECT_FALSE(root_one->IsStackedAbove(grandchild_one->GetNativeView()));
EXPECT_FALSE(child_two->IsStackedAbove(grandchild_two->GetNativeView()));
EXPECT_FALSE(child_one->IsStackedAbove(grandchild_two->GetNativeView()));
EXPECT_FALSE(child_one_b->IsStackedAbove(child_two->GetNativeView()));
EXPECT_FALSE(grandchild_one->IsStackedAbove(grandchild_two->GetNativeView()));
EXPECT_FALSE(grandchild_one->IsStackedAbove(root_two->GetNativeView()));
EXPECT_FALSE(child_one_b->IsStackedAbove(grandchild_one->GetNativeView()));
}
#endif // BUILDFLAG(IS_WIN)
#if BUILDFLAG(ENABLE_DESKTOP_AURA) || BUILDFLAG(IS_MAC)
namespace {
class CompositingWidgetTest : public DesktopWidgetTest {
public:
CompositingWidgetTest()
: widget_types_{Widget::InitParams::TYPE_WINDOW,
Widget::InitParams::TYPE_WINDOW_FRAMELESS,
Widget::InitParams::TYPE_CONTROL,
Widget::InitParams::TYPE_POPUP,
Widget::InitParams::TYPE_MENU,
Widget::InitParams::TYPE_TOOLTIP,
Widget::InitParams::TYPE_BUBBLE,
Widget::InitParams::TYPE_DRAG} {}
CompositingWidgetTest(const CompositingWidgetTest&) = delete;
CompositingWidgetTest& operator=(const CompositingWidgetTest&) = delete;
~CompositingWidgetTest() override = default;
Widget::InitParams CreateParams(Widget::InitParams::Ownership ownership,
Widget::InitParams::Type type) override {
Widget::InitParams params =
DesktopWidgetTest::CreateParams(ownership, type);
params.opacity = opacity_;
return params;
}
void CheckAllWidgetsForOpacity(
const Widget::InitParams::WindowOpacity opacity) {
opacity_ = opacity;
for (const auto& widget_type : widget_types_) {
#if BUILDFLAG(IS_MAC)
// Tooltips are native on Mac. See NativeWidgetNSWindowBridge::Init.
if (widget_type == Widget::InitParams::TYPE_TOOLTIP) {
continue;
}
#elif BUILDFLAG(IS_WIN)
// Other widget types would require to create a parent window and the
// the purpose of this test is mainly X11 in the first place.
if (widget_type != Widget::InitParams::TYPE_WINDOW) {
continue;
}
#endif
std::unique_ptr<Widget> widget =
CreateTestWidget(Widget::InitParams::CLIENT_OWNS_WIDGET, widget_type);
// Use NativeWidgetAura directly.
if (widget_type == Widget::InitParams::TYPE_WINDOW_FRAMELESS ||
widget_type == Widget::InitParams::TYPE_CONTROL) {
continue;
}
#if BUILDFLAG(IS_MAC)
// Mac always always has a compositing window manager, but doesn't have
// transparent titlebars which is what ShouldWindowContentsBeTransparent()
// is currently used for. Asking for transparency should get it. Note that
// TestViewsDelegate::use_transparent_windows_ determines the result of
// kInferOpacity: assume it is false.
bool should_be_transparent =
opacity_ == Widget::InitParams::WindowOpacity::kTranslucent;
#else
bool should_be_transparent = widget->ShouldWindowContentsBeTransparent();
#endif
EXPECT_EQ(IsNativeWindowTransparent(widget->GetNativeWindow()),
should_be_transparent);
}
}
protected:
const std::vector<Widget::InitParams::Type> widget_types_;
Widget::InitParams::WindowOpacity opacity_ =
Widget::InitParams::WindowOpacity::kInferred;
};
} // namespace
// Only test manually set opacity via kOpaque or kTranslucent. kInferred is
// unpredictable and depends on the platform and window type.
TEST_F(CompositingWidgetTest, Transparency_DesktopWidgetOpaque) {
CheckAllWidgetsForOpacity(Widget::InitParams::WindowOpacity::kOpaque);
}
TEST_F(CompositingWidgetTest, Transparency_DesktopWidgetTranslucent) {
CheckAllWidgetsForOpacity(Widget::InitParams::WindowOpacity::kTranslucent);
}
namespace {
class ScreenshotWidgetTest : public ViewsTestBase {
public:
ScreenshotWidgetTest() = default;
// ViewsTestBase:
void SetUp() override {
ViewsTestBase::SetUp();
widget_ = std::make_unique<Widget>();
Widget::InitParams params =
CreateParams(Widget::InitParams::CLIENT_OWNS_WIDGET,
Widget::InitParams::TYPE_WINDOW);
native_widget_ =
std::make_unique<testing::NiceMock<MockNativeWidget>>(widget());
EXPECT_CALL(*native_widget(), SetAllowScreenshots(_))
.WillOnce([this](bool allowed) {
screenshots_allowed_ = allowed;
return true;
});
params.native_widget = native_widget();
widget()->Init(std::move(params));
}
void TearDown() override {
// `ViewAccessibility` objects have some references to the `widget` which
// must be updated when the widget is freed. The function that is in charge
// of clearing these lists however (`OnNativeWidgetDestroying`), is never
// called in this test suite because we use a `MockNativeWindow` rather than
// a `NativeWindow`. So we make sure this clean up happens manually.
widget()->OnNativeWidgetDestroying();
native_widget_.reset();
ViewsTestBase::TearDown();
}
Widget* widget() { return widget_.get(); }
MockNativeWidget* native_widget() { return native_widget_.get(); }
const std::optional<bool>& screenshots_allowed() {
return screenshots_allowed_;
}
private:
std::unique_ptr<Widget> widget_;
std::unique_ptr<MockNativeWidget> native_widget_;
std::optional<bool> screenshots_allowed_;
};
} // namespace
TEST_F(ScreenshotWidgetTest, CallsNativeWidget) {
widget()->SetAllowScreenshots(false);
ASSERT_TRUE(screenshots_allowed().has_value());
EXPECT_FALSE(screenshots_allowed().value());
}
#endif // BUILDFLAG(ENABLE_DESKTOP_AURA) || BUILDFLAG(IS_MAC)
namespace {
class WidgetModalVisibilityObserver : public WidgetObserver {
public:
MOCK_METHOD(void,
OnWidgetWindowModalVisibilityChanged,
(Widget*, bool),
(override));
};
} // namespace
TEST_F(WidgetTest, ChildWidgetNotifiesModalVisibilityChanged) {
std::unique_ptr<Widget> widget = base::WrapUnique(
CreateTopLevelPlatformWidget(Widget::InitParams::CLIENT_OWNS_WIDGET));
testing::NiceMock<WidgetModalVisibilityObserver> observer;
base::ScopedObservation<Widget, WidgetObserver> observation(&observer);
observation.Observe(widget.get());
widget->Show();
auto child_widget_delegate = std::make_unique<WidgetDelegate>();
auto child_widget = std::make_unique<Widget>();
child_widget_delegate->SetModalType(ui::mojom::ModalType::kWindow);
Widget::InitParams params = CreateParams(
Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_WINDOW);
params.delegate = child_widget_delegate.get();
params.parent = widget->GetNativeView();
child_widget->Init(std::move(params));
// Sequence: show -> hide -> show -> destroy.
testing::InSequence seq;
EXPECT_CALL(observer,
OnWidgetWindowModalVisibilityChanged(widget.get(), true));
EXPECT_CALL(observer,
OnWidgetWindowModalVisibilityChanged(widget.get(), false));
EXPECT_CALL(observer,
OnWidgetWindowModalVisibilityChanged(widget.get(), true));
EXPECT_CALL(observer,
OnWidgetWindowModalVisibilityChanged(widget.get(), false));
WidgetVisibleWaiter waiter(child_widget.get());
child_widget->Show();
waiter.Wait();
child_widget->Hide();
waiter.WaitUntilInvisible();
child_widget->Show();
waiter.Wait();
// Destroy the child widget, the parent should be notified about child modal
// visibility change.
child_widget.reset();
// No need to wait for visibility change because the widget is already
// destroyed.
widget->RemoveObserver(&observer);
}
TEST_F(WidgetTest, RemoveClientContentsView) {
std::unique_ptr<Widget> widget = CreateTestWidget(
Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_WINDOW);
auto client_view = widget->RemoveClientContentsView<View>();
EXPECT_TRUE(client_view);
EXPECT_FALSE(widget->GetClientContentsView());
}
TEST_F(WidgetTest, SetClientContentsView) {
std::unique_ptr<Widget> widget = CreateTestWidget(
Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_WINDOW);
auto client_view = widget->RemoveClientContentsView<View>();
EXPECT_TRUE(client_view);
auto* client_view_ptr = widget->SetClientContentsView(std::move(client_view));
EXPECT_EQ(client_view_ptr, widget->GetClientContentsView());
}
TEST_F(WidgetTest, ReplaceClientContentsView) {
std::unique_ptr<Widget> widget = CreateTestWidget(
Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_WINDOW);
auto client_view = std::make_unique<ContentsView>();
EXPECT_NE(client_view.get(), widget->GetClientContentsView());
auto* client_view_ptr = widget->SetClientContentsView(std::move(client_view));
EXPECT_EQ(client_view_ptr, widget->GetClientContentsView());
}
} // namespace views::test