blob: 1d8dd95ab2e7e0fac908b2e619f3a1e0c103aee9 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <memory>
#include <set>
#include "base/bind.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/hit_test.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/events/event_observer.h"
#include "ui/events/event_utils.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/event_monitor.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/widget_test.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/widget_deletion_observer.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(OS_WIN)
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/view_prop.h"
#include "ui/base/win/window_event_target.h"
#include "ui/views/win/hwnd_util.h"
#endif
#if defined(OS_MACOSX)
#include "base/mac/mac_util.h"
#endif
#if defined(OS_CHROMEOS)
#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
namespace views {
namespace test {
namespace {
// 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;
}
class TestBubbleDialogDelegateView : public BubbleDialogDelegateView {
public:
TestBubbleDialogDelegateView(View* anchor)
: BubbleDialogDelegateView(anchor, BubbleBorder::NONE),
reset_controls_called_(false) {}
~TestBubbleDialogDelegateView() override {}
bool ShouldShowCloseButton() const override {
reset_controls_called_ = true;
return true;
}
mutable bool reset_controls_called_;
};
} // namespace
// A view that keeps track of the events it receives, and consumes all scroll
// gesture events and ui::ET_SCROLL events.
class ScrollableEventCountView : public EventCountView {
public:
ScrollableEventCountView() {}
~ScrollableEventCountView() override {}
private:
// Overridden from ui::EventHandler:
void OnGestureEvent(ui::GestureEvent* event) override {
EventCountView::OnGestureEvent(event);
switch (event->type()) {
case ui::ET_GESTURE_SCROLL_BEGIN:
case ui::ET_GESTURE_SCROLL_UPDATE:
case ui::ET_GESTURE_SCROLL_END:
case ui::ET_SCROLL_FLING_START:
event->SetHandled();
break;
default:
break;
}
}
void OnScrollEvent(ui::ScrollEvent* event) override {
EventCountView::OnScrollEvent(event);
if (event->type() == ui::ET_SCROLL)
event->SetHandled();
}
DISALLOW_COPY_AND_ASSIGN(ScrollableEventCountView);
};
// A view that implements GetMinimumSize.
class MinimumSizeFrameView : public NativeFrameView {
public:
explicit MinimumSizeFrameView(Widget* frame): NativeFrameView(frame) {}
~MinimumSizeFrameView() override {}
private:
// Overridden from View:
gfx::Size GetMinimumSize() const override { return gfx::Size(300, 400); }
DISALLOW_COPY_AND_ASSIGN(MinimumSizeFrameView);
};
// An event handler that simply keeps a count of the different types of events
// it receives.
class EventCountHandler : public ui::EventHandler {
public:
EventCountHandler() {}
~EventCountHandler() override {}
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_;
DISALLOW_COPY_AND_ASSIGN(EventCountHandler);
};
TEST_F(WidgetTest, WidgetInitParams) {
// Widgets are not transparent by default.
Widget::InitParams init1;
EXPECT_EQ(Widget::InitParams::INFER_OPACITY, init1.opacity);
}
// Tests that the internal name is propagated through widget initialization to
// the native widget and back.
TEST_F(WidgetTest, GetName) {
Widget widget;
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.name = "MyWidget";
widget.Init(params);
EXPECT_EQ("MyWidget", widget.native_widget_private()->GetName());
EXPECT_EQ("MyWidget", widget.GetName());
}
TEST_F(WidgetTest, NativeWindowProperty) {
const char* key = "foo";
int value = 3;
WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
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));
}
////////////////////////////////////////////////////////////////////////////////
// Widget::GetTopLevelWidget tests.
TEST_F(WidgetTest, GetTopLevelWidget_Native) {
// Create a hierarchy of native widgets.
WidgetAutoclosePtr toplevel(CreateTopLevelPlatformWidget());
gfx::NativeView parent = toplevel->GetNativeView();
Widget* child = CreateChildPlatformWidget(parent);
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) {
WidgetAutoclosePtr top1(CreateTopLevelPlatformWidget());
top1->Show();
RunPendingMessages();
WidgetAutoclosePtr top2(CreateTopLevelPlatformWidget());
top2->Show();
RunPendingMessages();
top1->Activate();
RunPendingMessages();
top2->Activate();
RunPendingMessages();
top1->Activate();
RunPendingMessages();
}
// Tests visibility of child widgets.
TEST_F(WidgetTest, Visibility) {
WidgetAutoclosePtr toplevel(CreateTopLevelPlatformWidget());
gfx::NativeView parent = toplevel->GetNativeView();
Widget* child = CreateChildPlatformWidget(parent);
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) {
WidgetAutoclosePtr toplevel(CreateTopLevelPlatformWidget());
Widget* child = CreateChildPlatformWidget(toplevel->GetNativeView());
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.
// A WidgetTest that supplies a toplevel widget for NativeWidget to parent to.
class WidgetOwnershipTest : public WidgetTest {
public:
WidgetOwnershipTest() {}
~WidgetOwnershipTest() override {}
void SetUp() override {
WidgetTest::SetUp();
desktop_widget_ = CreateTopLevelPlatformWidget();
}
void TearDown() override {
desktop_widget_->CloseNow();
WidgetTest::TearDown();
}
private:
Widget* desktop_widget_;
DISALLOW_COPY_AND_ASSIGN(WidgetOwnershipTest);
};
// A bag of state to monitor destructions.
struct OwnershipTestState {
OwnershipTestState() : widget_deleted(false), native_widget_deleted(false) {}
bool widget_deleted;
bool native_widget_deleted;
};
// 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() override { state_->widget_deleted = true; }
private:
OwnershipTestState* state_;
DISALLOW_COPY_AND_ASSIGN(OwnershipTestWidget);
};
// TODO(sky): add coverage of ownership for the desktop variants.
// Widget owns its NativeWidget, part 1: NativeWidget is a platform-native
// widget.
TEST_F(WidgetOwnershipTest, Ownership_WidgetOwnsPlatformNativeWidget) {
OwnershipTestState state;
std::unique_ptr<Widget> widget(new OwnershipTestWidget(&state));
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
params.native_widget = CreatePlatformNativeWidgetImpl(
params, widget.get(), kStubCapture, &state.native_widget_deleted);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget->Init(params);
// Now delete the Widget, which should delete the NativeWidget.
widget.reset();
EXPECT_TRUE(state.widget_deleted);
EXPECT_TRUE(state.native_widget_deleted);
// TODO(beng): write test for this ownership scenario and the NativeWidget
// being deleted out from under the Widget.
}
// Widget owns its NativeWidget, part 2: NativeWidget is a NativeWidget.
TEST_F(WidgetOwnershipTest, Ownership_WidgetOwnsViewsNativeWidget) {
OwnershipTestState state;
std::unique_ptr<Widget> widget(new OwnershipTestWidget(&state));
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
params.native_widget = CreatePlatformNativeWidgetImpl(
params, widget.get(), kStubCapture, &state.native_widget_deleted);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget->Init(params);
// Now delete the Widget, which should delete the NativeWidget.
widget.reset();
EXPECT_TRUE(state.widget_deleted);
EXPECT_TRUE(state.native_widget_deleted);
// TODO(beng): write test for this ownership scenario and the NativeWidget
// being deleted out from under the Widget.
}
// Widget owns its NativeWidget, part 3: NativeWidget is a NativeWidget,
// destroy the parent view.
TEST_F(WidgetOwnershipTest,
Ownership_WidgetOwnsViewsNativeWidget_DestroyParentView) {
OwnershipTestState state;
Widget* toplevel = CreateTopLevelPlatformWidget();
std::unique_ptr<Widget> widget(new OwnershipTestWidget(&state));
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
params.parent = toplevel->GetNativeView();
params.native_widget = CreatePlatformNativeWidgetImpl(
params, widget.get(), kStubCapture, &state.native_widget_deleted);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget->Init(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);
// Now delete it explicitly.
widget.reset();
EXPECT_TRUE(state.widget_deleted);
EXPECT_TRUE(state.native_widget_deleted);
}
// NativeWidget owns its Widget, part 1: NativeWidget is a platform-native
// widget.
TEST_F(WidgetOwnershipTest, Ownership_PlatformNativeWidgetOwnsWidget) {
OwnershipTestState state;
Widget* widget = new OwnershipTestWidget(&state);
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
params.native_widget = CreatePlatformNativeWidgetImpl(
params, widget, kStubCapture, &state.native_widget_deleted);
widget->Init(params);
// Now destroy the native widget.
widget->CloseNow();
EXPECT_TRUE(state.widget_deleted);
EXPECT_TRUE(state.native_widget_deleted);
}
// NativeWidget owns its Widget, part 2: NativeWidget is a NativeWidget.
TEST_F(WidgetOwnershipTest, Ownership_ViewsNativeWidgetOwnsWidget) {
OwnershipTestState state;
Widget* toplevel = CreateTopLevelPlatformWidget();
Widget* widget = new OwnershipTestWidget(&state);
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
params.parent = toplevel->GetNativeView();
params.native_widget = CreatePlatformNativeWidgetImpl(
params, widget, kStubCapture, &state.native_widget_deleted);
widget->Init(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.
RunPendingMessages();
EXPECT_TRUE(state.widget_deleted);
EXPECT_TRUE(state.native_widget_deleted);
}
// NativeWidget owns its Widget, part 3: NativeWidget is a platform-native
// widget, destroyed out from under it by the OS.
TEST_F(WidgetOwnershipTest,
Ownership_PlatformNativeWidgetOwnsWidget_NativeDestroy) {
OwnershipTestState state;
Widget* widget = new OwnershipTestWidget(&state);
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
params.native_widget = CreatePlatformNativeWidgetImpl(
params, widget, kStubCapture, &state.native_widget_deleted);
widget->Init(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);
}
// NativeWidget owns its Widget, part 4: NativeWidget is a NativeWidget,
// destroyed by the view hierarchy that contains it.
TEST_F(WidgetOwnershipTest,
Ownership_ViewsNativeWidgetOwnsWidget_NativeDestroy) {
OwnershipTestState state;
Widget* toplevel = CreateTopLevelPlatformWidget();
Widget* widget = new OwnershipTestWidget(&state);
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
params.parent = toplevel->GetNativeView();
params.native_widget = CreatePlatformNativeWidgetImpl(
params, widget, kStubCapture, &state.native_widget_deleted);
widget->Init(params);
// Destroy the widget (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.
RunPendingMessages();
EXPECT_TRUE(state.widget_deleted);
EXPECT_TRUE(state.native_widget_deleted);
}
// NativeWidget owns its Widget, part 5: NativeWidget is a NativeWidget,
// we close it directly.
TEST_F(WidgetOwnershipTest,
Ownership_ViewsNativeWidgetOwnsWidget_Close) {
OwnershipTestState state;
Widget* toplevel = CreateTopLevelPlatformWidget();
Widget* widget = new OwnershipTestWidget(&state);
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
params.parent = toplevel->GetNativeView();
params.native_widget = CreatePlatformNativeWidgetImpl(
params, widget, kStubCapture, &state.native_widget_deleted);
widget->Init(params);
// Destroy the widget.
widget->Close();
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.
RunPendingMessages();
EXPECT_TRUE(state.widget_deleted);
EXPECT_TRUE(state.native_widget_deleted);
}
// Widget owns its NativeWidget and has a WidgetDelegateView as its contents.
TEST_F(WidgetOwnershipTest,
Ownership_WidgetOwnsNativeWidgetWithWithWidgetDelegateView) {
OwnershipTestState state;
WidgetDelegateView* delegate_view = new WidgetDelegateView;
std::unique_ptr<Widget> widget(new OwnershipTestWidget(&state));
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
params.native_widget = CreatePlatformNativeWidgetImpl(
params, widget.get(), kStubCapture, &state.native_widget_deleted);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.delegate = delegate_view;
widget->Init(params);
widget->SetContentsView(delegate_view);
// Now delete the Widget. There should be no crash or use-after-free.
widget.reset();
EXPECT_TRUE(state.widget_deleted);
EXPECT_TRUE(state.native_widget_deleted);
}
////////////////////////////////////////////////////////////////////////////////
// Test to verify using various Widget methods doesn't crash when the underlying
// NativeView is destroyed.
//
class WidgetWithDestroyedNativeViewTest : public ViewsTestBase {
public:
WidgetWithDestroyedNativeViewTest() {}
~WidgetWithDestroyedNativeViewTest() override {}
void InvokeWidgetMethods(Widget* widget) {
widget->GetNativeView();
widget->GetNativeWindow();
ui::Accelerator accelerator;
widget->GetAccelerator(0, &accelerator);
widget->GetTopLevelWidget();
widget->GetWindowBoundsInScreen();
widget->GetClientAreaBoundsInScreen();
widget->SetBounds(gfx::Rect(0, 0, 100, 80));
widget->SetSize(gfx::Size(10, 11));
widget->SetBoundsConstrained(gfx::Rect(0, 0, 120, 140));
widget->SetVisibilityChangedAnimationsEnabled(false);
widget->StackAtTop();
widget->IsClosed();
widget->Close();
widget->Hide();
widget->Activate();
widget->Deactivate();
widget->IsActive();
widget->SetAlwaysOnTop(true);
widget->IsAlwaysOnTop();
widget->Maximize();
widget->Minimize();
widget->Restore();
widget->IsMaximized();
widget->IsFullscreen();
widget->SetOpacity(0.f);
widget->FlashFrame(true);
widget->IsVisible();
widget->GetThemeProvider();
widget->GetNativeTheme();
widget->GetFocusManager();
widget->SchedulePaintInRect(gfx::Rect(0, 0, 1, 2));
widget->IsMouseEventsEnabled();
widget->SetNativeWindowProperty("xx", widget);
widget->GetNativeWindowProperty("xx");
widget->GetFocusTraversable();
widget->GetLayer();
widget->ReorderNativeViews();
widget->SetCapture(widget->GetRootView());
widget->ReleaseCapture();
widget->HasCapture();
widget->GetWorkAreaBoundsInScreen();
widget->IsTranslucentWindowOpacitySupported();
}
private:
DISALLOW_COPY_AND_ASSIGN(WidgetWithDestroyedNativeViewTest);
};
TEST_F(WidgetWithDestroyedNativeViewTest, Test) {
{
Widget widget;
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget.Init(params);
widget.Show();
widget.native_widget_private()->CloseNow();
InvokeWidgetMethods(&widget);
}
#if !defined(OS_CHROMEOS)
{
Widget widget;
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
params.native_widget =
CreatePlatformDesktopNativeWidgetImpl(params, &widget, nullptr);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget.Init(params);
widget.Show();
widget.native_widget_private()->CloseNow();
InvokeWidgetMethods(&widget);
}
#endif
}
////////////////////////////////////////////////////////////////////////////////
// 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;
widget_closed_ = widget;
}
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;
}
}
void OnWidgetVisibilityChanged(Widget* widget, bool visible) override {
if (visible) {
widget_shown_ = widget;
return;
}
widget_hidden_ = widget;
if (widget_to_close_on_hide_) {
widget_to_close_on_hide_->Close();
widget_to_close_on_hide_ = nullptr;
}
}
void OnWidgetBoundsChanged(Widget* widget,
const gfx::Rect& new_bounds) override {
widget_bounds_changed_ = widget;
}
void reset() {
active_ = nullptr;
widget_closed_ = nullptr;
widget_activated_ = nullptr;
widget_deactivated_ = nullptr;
widget_shown_ = nullptr;
widget_hidden_ = nullptr;
widget_bounds_changed_ = nullptr;
}
Widget* NewWidget() {
Widget* widget = CreateTopLevelNativeWidget();
widget->AddObserver(this);
return widget;
}
const Widget* active() const { return active_; }
const Widget* widget_closed() const { return widget_closed_; }
const Widget* widget_activated() const { return widget_activated_; }
const Widget* widget_deactivated() const { return widget_deactivated_; }
const Widget* widget_shown() const { return widget_shown_; }
const Widget* widget_hidden() const { return widget_hidden_; }
const Widget* widget_bounds_changed() const { return widget_bounds_changed_; }
private:
Widget* active_ = nullptr;
Widget* widget_closed_ = nullptr;
Widget* widget_activated_ = nullptr;
Widget* widget_deactivated_ = nullptr;
Widget* widget_shown_ = nullptr;
Widget* widget_hidden_ = nullptr;
Widget* widget_bounds_changed_ = nullptr;
Widget* widget_to_close_on_hide_ = nullptr;
};
// This test appears to be flaky on Mac.
#if defined(OS_MACOSX)
#define MAYBE_ActivationChange DISABLED_ActivationChange
#else
#define MAYBE_ActivationChange ActivationChange
#endif
TEST_F(WidgetObserverTest, MAYBE_ActivationChange) {
WidgetAutoclosePtr toplevel1(NewWidget());
WidgetAutoclosePtr toplevel2(NewWidget());
toplevel1->Show();
toplevel2->Show();
reset();
toplevel1->Activate();
RunPendingMessages();
EXPECT_EQ(toplevel1.get(), widget_activated());
toplevel2->Activate();
RunPendingMessages();
EXPECT_EQ(toplevel1.get(), 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() override {}
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();
}
Widget* widget_to_activate_;
DISALLOW_COPY_AND_ASSIGN(WidgetActivationForwarder);
};
// 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() override {}
int close_count() const { return close_count_; }
private:
// WidgetObserver overrides:
void OnWidgetClosing(Widget* widget) override { close_count_++; }
int close_count_ = 0;
DISALLOW_COPY_AND_ASSIGN(WidgetCloseCounter);
};
} // 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());
WidgetAutoclosePtr child2(NewWidget());
toplevel->Show();
child1->Show();
child2->Show();
reset();
child1->Hide();
EXPECT_EQ(child1.get(), widget_hidden());
child2->Hide();
EXPECT_EQ(child2.get(), widget_hidden());
child1->Show();
EXPECT_EQ(child1.get(), widget_shown());
child2->Show();
EXPECT_EQ(child2.get(), widget_shown());
}
TEST_F(WidgetObserverTest, DestroyBubble) {
// This test expect NativeWidgetAura, force its creation.
ViewsDelegate::GetInstance()->set_native_widget_factory(
ViewsDelegate::NativeWidgetFactory());
WidgetAutoclosePtr anchor(CreateTopLevelPlatformWidget());
anchor->Show();
BubbleDialogDelegateView* bubble_delegate =
new TestBubbleDialogDelegateView(anchor->client_view());
{
WidgetAutoclosePtr bubble_widget(
BubbleDialogDelegateView::CreateBubble(bubble_delegate));
bubble_widget->Show();
}
anchor->Hide();
}
TEST_F(WidgetObserverTest, WidgetBoundsChanged) {
WidgetAutoclosePtr child1(NewWidget());
WidgetAutoclosePtr child2(NewWidget());
child1->OnNativeWidgetMove();
EXPECT_EQ(child1.get(), widget_bounds_changed());
child2->OnNativeWidgetMove();
EXPECT_EQ(child2.get(), widget_bounds_changed());
child1->OnNativeWidgetSizeChanged(gfx::Size());
EXPECT_EQ(child1.get(), widget_bounds_changed());
child2->OnNativeWidgetSizeChanged(gfx::Size());
EXPECT_EQ(child2.get(), 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.
Widget* widget = new Widget(); // Note: owned by NativeWidget.
widget->AddObserver(this);
EXPECT_FALSE(widget_bounds_changed());
Widget::InitParams params = CreateParams(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);
// 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(params);
EXPECT_TRUE(widget_bounds_changed());
reset();
// Resizing while hidden, triggers a change.
widget->SetSize(gfx::Size(160, 100));
EXPECT_FALSE(widget->IsVisible());
EXPECT_TRUE(widget_bounds_changed());
reset();
// Setting the same size does nothing.
widget->SetSize(gfx::Size(160, 100));
EXPECT_FALSE(widget_bounds_changed());
reset();
// Showing does nothing to the bounds.
widget->Show();
EXPECT_TRUE(widget->IsVisible());
EXPECT_FALSE(widget_bounds_changed());
reset();
// Resizing while shown.
widget->SetSize(gfx::Size(170, 100));
EXPECT_TRUE(widget_bounds_changed());
reset();
// Resize to the same thing while shown does nothing.
widget->SetSize(gfx::Size(170, 100));
EXPECT_FALSE(widget_bounds_changed());
reset();
// Move, but don't change the size.
widget->SetBounds(gfx::Rect(110, 110, 170, 100));
EXPECT_TRUE(widget_bounds_changed());
reset();
// Moving to the same place does nothing.
widget->SetBounds(gfx::Rect(110, 110, 170, 100));
EXPECT_FALSE(widget_bounds_changed());
reset();
// No bounds change when closing.
widget->CloseNow();
EXPECT_FALSE(widget_bounds_changed());
}
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
// An extension to the WidgetBoundsChangedNative test above to ensure move
// notifications propagate to the WidgetDelegate.
TEST_F(WidgetObserverTest, OnWidgetMovedWhenOriginChangesNative) {
MoveTrackingTestDesktopWidgetDelegate delegate;
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
delegate.InitWidget(params);
Widget* widget = delegate.GetWidget();
widget->Show();
widget->SetBounds(gfx::Rect(100, 100, 300, 200));
const int moves_during_init = delegate.move_count();
#if defined(OS_WIN)
// Windows reliably notifies twice per origin change. https://crbug.com/864938
constexpr int kDeltaPerMove = 2;
#else
constexpr int kDeltaPerMove = 1;
#endif
// 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 + kDeltaPerMove, delegate.move_count());
// Changing both moves.
widget->SetBounds(gfx::Rect(90, 90, 330, 230));
EXPECT_EQ(moves_during_init + 2 * kDeltaPerMove, 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 * kDeltaPerMove, 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 * kDeltaPerMove, delegate.move_count());
}
// Test correct behavior when widgets close themselves in response to visibility
// changes.
TEST_F(WidgetObserverTest, ClosingOnHiddenParent) {
WidgetAutoclosePtr parent(NewWidget());
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.
TEST_F(WidgetTest, GetWindowPlacement) {
#if defined(OS_MACOSX)
if (base::mac::IsOS10_10())
return; // Fails when swarmed. http://crbug.com/660582
#endif
WidgetAutoclosePtr widget;
#if defined(USE_X11)
// 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::SHOW_STATE_INACTIVE throughout.
// TODO(tapted): Find a nice way to run this with desktop widgets on Linux.
widget.reset(CreateTopLevelPlatformWidget());
#else
widget.reset(CreateNativeDesktopWidget());
#endif
gfx::Rect expected_bounds(100, 110, 200, 220);
widget->SetBounds(expected_bounds);
widget->Show();
// Start with something invalid to ensure it changes.
ui::WindowShowState show_state = ui::SHOW_STATE_END;
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 defined(OS_LINUX)
// Non-desktop/Ash widgets start off in "default" until a Restore().
EXPECT_EQ(ui::SHOW_STATE_DEFAULT, show_state);
widget->Restore();
native_widget->GetWindowPlacement(&restored_bounds, &show_state);
#endif
EXPECT_EQ(ui::SHOW_STATE_NORMAL, show_state);
widget->Minimize();
native_widget->GetWindowPlacement(&restored_bounds, &show_state);
EXPECT_EQ(ui::SHOW_STATE_MINIMIZED, show_state);
EXPECT_EQ(expected_bounds, restored_bounds);
widget->Restore();
native_widget->GetWindowPlacement(&restored_bounds, &show_state);
EXPECT_EQ(ui::SHOW_STATE_NORMAL, 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::SHOW_STATE_NORMAL, show_state);
EXPECT_EQ(expected_bounds, restored_bounds);
widget->SetFullscreen(true);
native_widget->GetWindowPlacement(&restored_bounds, &show_state);
#if defined(OS_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::SHOW_STATE_NORMAL, show_state);
#else
EXPECT_EQ(ui::SHOW_STATE_FULLSCREEN, show_state);
EXPECT_EQ(expected_bounds, restored_bounds);
#endif
widget->SetFullscreen(false);
native_widget->GetWindowPlacement(&restored_bounds, &show_state);
EXPECT_EQ(ui::SHOW_STATE_NORMAL, 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(WidgetTest, 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::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,
// CustomFrameView 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);
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
// TODO(tapted): Desktop Linux ignores size constraints for SetSize. Fix it.
const bool use_small_size = true;
#else
const bool use_small_size = false;
#endif
EXPECT_EQ(use_small_size ? smaller_size : minimum_size,
widget->GetClientAreaBoundsInScreen().size());
}
// 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());
}
// Non-Desktop widgets need the shell to maximize/fullscreen window.
// Disable on Linux because windows restore to the wrong bounds.
// See http://crbug.com/515369.
#if defined(OS_CHROMEOS) || defined(OS_LINUX)
#define MAYBE_GetRestoredBounds DISABLED_GetRestoredBounds
#else
#define MAYBE_GetRestoredBounds GetRestoredBounds
#endif
// Test that GetRestoredBounds() returns the original bounds of the window.
TEST_F(WidgetTest, MAYBE_GetRestoredBounds) {
WidgetAutoclosePtr toplevel(CreateNativeDesktopWidget());
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 defined(OS_MACOSX)
// 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(base::ASCIIToUTF16("some text"));
container->AddChildView(textfield);
toplevel->Show();
textfield->RequestFocus();
// The press gets handled. The release doesn't have an effect.
ui::KeyEvent backspace_p(ui::ET_KEY_PRESSED, ui::VKEY_DELETE, ui::EF_NONE);
toplevel->OnKeyEvent(&backspace_p);
EXPECT_TRUE(backspace_p.stopped_propagation());
ui::KeyEvent backspace_r(ui::ET_KEY_RELEASED, ui::VKEY_DELETE, ui::EF_NONE);
toplevel->OnKeyEvent(&backspace_r);
EXPECT_FALSE(backspace_r.handled());
}
// Verifies bubbles result in a focus lost when shown.
// TODO(msw): this tests relies on focus, it needs to be in
// interactive_ui_tests.
TEST_F(WidgetTest, DISABLED_FocusChangesOnBubble) {
// Create a widget, show and activate it and focus the contents view.
View* contents_view = new View;
contents_view->SetFocusBehavior(View::FocusBehavior::ALWAYS);
Widget widget;
Widget::InitParams init_params =
CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
init_params.bounds = gfx::Rect(0, 0, 200, 200);
init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
#if !defined(OS_CHROMEOS)
init_params.native_widget =
CreatePlatformDesktopNativeWidgetImpl(init_params, &widget, nullptr);
#endif
widget.Init(init_params);
widget.SetContentsView(contents_view);
widget.Show();
widget.Activate();
contents_view->RequestFocus();
EXPECT_TRUE(contents_view->HasFocus());
// Show a bubble.
BubbleDialogDelegateView* bubble_delegate_view =
new TestBubbleDialogDelegateView(contents_view);
bubble_delegate_view->SetFocusBehavior(View::FocusBehavior::ALWAYS);
BubbleDialogDelegateView::CreateBubble(bubble_delegate_view)->Show();
bubble_delegate_view->RequestFocus();
// |contents_view_| should no longer have focus.
EXPECT_FALSE(contents_view->HasFocus());
EXPECT_TRUE(bubble_delegate_view->HasFocus());
bubble_delegate_view->GetWidget()->CloseNow();
// Closing the bubble should result in focus going back to the contents view.
EXPECT_TRUE(contents_view->HasFocus());
}
TEST_F(WidgetTest, BubbleControlsResetOnInit) {
WidgetAutoclosePtr anchor(CreateTopLevelPlatformWidget());
anchor->Show();
{
TestBubbleDialogDelegateView* bubble_delegate =
new TestBubbleDialogDelegateView(anchor->client_view());
WidgetAutoclosePtr bubble_widget(
BubbleDialogDelegateView::CreateBubble(bubble_delegate));
EXPECT_TRUE(bubble_delegate->reset_controls_called_);
bubble_widget->Show();
}
anchor->Hide();
}
#if defined(OS_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(WidgetTest, TestViewWidthAfterMinimizingWidget) {
// Create a widget.
Widget widget;
Widget::InitParams init_params =
CreateParams(Widget::InitParams::TYPE_WINDOW);
init_params.show_state = ui::SHOW_STATE_NORMAL;
gfx::Rect initial_bounds(0, 0, 300, 400);
init_params.bounds = initial_bounds;
init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
init_params.native_widget =
CreatePlatformDesktopNativeWidgetImpl(init_params, &widget, nullptr);
widget.Init(init_params);
NonClientView* non_client_view = widget.non_client_view();
NonClientFrameView* frame_view = new MinimumSizeFrameView(&widget);
non_client_view->SetFrameView(frame_view);
// Setting the frame view doesn't do a layout, so force one.
non_client_view->Layout();
widget.Show();
EXPECT_NE(0, frame_view->width());
widget.Minimize();
EXPECT_EQ(0, frame_view->width());
}
#endif
// Desktop native widget Aura tests are for non Chrome OS platforms.
#if !defined(OS_CHROMEOS)
// 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:
DesktopAuraTestValidPaintWidget()
: received_paint_(false),
expect_paint_(true),
received_paint_while_hidden_(false) {
AddObserver(this);
}
~DesktopAuraTestValidPaintWidget() override { RemoveObserver(this); }
void InitForTest(Widget::InitParams create_params);
bool ReadReceivedPaintAndReset() {
bool result = received_paint_;
received_paint_ = false;
return result;
}
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_ = base::Closure();
}
void OnWidgetClosing(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;
views::Widget::OnNativeWidgetPaint(context);
if (!quit_closure_.is_null())
quit_closure_.Run();
}
// WidgetObserver:
void OnWidgetVisibilityChanged(Widget* widget, bool visible) override {
expect_paint_ = visible;
}
private:
bool received_paint_;
bool expect_paint_;
bool received_paint_while_hidden_;
base::Closure quit_closure_;
DISALLOW_COPY_AND_ASSIGN(DesktopAuraTestValidPaintWidget);
};
void DesktopAuraTestValidPaintWidget::InitForTest(InitParams init_params) {
init_params.bounds = gfx::Rect(0, 0, 200, 200);
init_params.ownership = InitParams::WIDGET_OWNS_NATIVE_WIDGET;
init_params.native_widget =
CreatePlatformDesktopNativeWidgetImpl(init_params, this, nullptr);
Init(init_params);
View* contents_view = new View;
contents_view->SetFocusBehavior(View::FocusBehavior::ALWAYS);
SetContentsView(contents_view);
Show();
Activate();
}
TEST_F(WidgetTest, DesktopNativeWidgetNoPaintAfterCloseTest) {
DesktopAuraTestValidPaintWidget widget;
widget.InitForTest(CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS));
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(WidgetTest, DesktopNativeWidgetNoPaintAfterHideTest) {
DesktopAuraTestValidPaintWidget widget;
widget.InitForTest(CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS));
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 visiblity state is set to visible if
// the underlying widget is hidden and then shown.
TEST_F(WidgetTest, TestWindowVisibilityAfterHide) {
// Create a widget.
Widget widget;
Widget::InitParams init_params =
CreateParams(Widget::InitParams::TYPE_WINDOW);
init_params.show_state = ui::SHOW_STATE_NORMAL;
gfx::Rect initial_bounds(0, 0, 300, 400);
init_params.bounds = initial_bounds;
init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
init_params.native_widget =
CreatePlatformDesktopNativeWidgetImpl(init_params, &widget, nullptr);
widget.Init(init_params);
NonClientView* non_client_view = widget.non_client_view();
NonClientFrameView* frame_view = new MinimumSizeFrameView(&widget);
non_client_view->SetFrameView(frame_view);
widget.Show();
EXPECT_TRUE(IsNativeWindowVisible(widget.GetNativeWindow()));
widget.Hide();
EXPECT_FALSE(IsNativeWindowVisible(widget.GetNativeWindow()));
widget.Show();
EXPECT_TRUE(IsNativeWindowVisible(widget.GetNativeWindow()));
}
#endif // !defined(OS_CHROMEOS)
// Tests that wheel events generated from scroll events are targetted 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()->AddChildView(cursor_view);
// Generate a scroll event on the cursor view.
ui::ScrollEvent scroll(ui::ET_SCROLL,
gfx::Point(65, 5),
ui::EventTimeForNow(),
0,
0, 20,
0, 20,
2);
widget->OnScrollEvent(&scroll);
EXPECT_EQ(1, cursor_view->GetEventCount(ui::ET_SCROLL));
EXPECT_EQ(1, cursor_view->GetEventCount(ui::ET_MOUSEWHEEL));
cursor_view->ResetCounts();
ui::ScrollEvent scroll2(ui::ET_SCROLL,
gfx::Point(5, 5),
ui::EventTimeForNow(),
0,
0, 20,
0, 20,
2);
widget->OnScrollEvent(&scroll2);
EXPECT_EQ(0, cursor_view->GetEventCount(ui::ET_SCROLL));
EXPECT_EQ(0, cursor_view->GetEventCount(ui::ET_MOUSEWHEEL));
}
// 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()->AddChildView(noscroll_view);
widget->GetRootView()->AddChildView(scroll_view);
{
ui::GestureEvent begin(
5, 5, 0, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN));
widget->OnGestureEvent(&begin);
ui::GestureEvent update(
25, 15, 0, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE, 20, 10));
widget->OnGestureEvent(&update);
ui::GestureEvent end(25, 15, 0, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END));
widget->OnGestureEvent(&end);
EXPECT_EQ(1, noscroll_view->GetEventCount(ui::ET_GESTURE_SCROLL_BEGIN));
EXPECT_EQ(0, noscroll_view->GetEventCount(ui::ET_GESTURE_SCROLL_UPDATE));
EXPECT_EQ(0, noscroll_view->GetEventCount(ui::ET_GESTURE_SCROLL_END));
}
{
ui::GestureEvent begin(
65, 5, 0, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN));
widget->OnGestureEvent(&begin);
ui::GestureEvent update(
85, 15, 0, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_UPDATE, 20, 10));
widget->OnGestureEvent(&update);
ui::GestureEvent end(85, 15, 0, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_END));
widget->OnGestureEvent(&end);
EXPECT_EQ(1, scroll_view->GetEventCount(ui::ET_GESTURE_SCROLL_BEGIN));
EXPECT_EQ(1, scroll_view->GetEventCount(ui::ET_GESTURE_SCROLL_UPDATE));
EXPECT_EQ(1, scroll_view->GetEventCount(ui::ET_GESTURE_SCROLL_END));
}
}
// 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();
std::unique_ptr<EventCountView> view(new EventCountView());
view->set_owned_by_client();
view->SetBounds(0, 0, 20, 20);
root_view->AddChildView(view.get());
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::ET_SCROLL event. The event remains unhandled and should
// bubble up the views hierarchy to be re-dispatched on the root view.
ui::ScrollEvent scroll(ui::ET_SCROLL,
gfx::Point(5, 5),
ui::EventTimeForNow(),
0,
0, 20,
0, 20,
2);
widget->OnScrollEvent(&scroll);
EXPECT_EQ(2, h1.GetEventCount(ui::ET_SCROLL));
EXPECT_EQ(1, view->GetEventCount(ui::ET_SCROLL));
EXPECT_EQ(2, h2.GetEventCount(ui::ET_SCROLL));
// Unhandled scroll events are turned into wheel events and re-dispatched.
EXPECT_EQ(1, h1.GetEventCount(ui::ET_MOUSEWHEEL));
EXPECT_EQ(1, view->GetEventCount(ui::ET_MOUSEWHEEL));
EXPECT_EQ(1, h2.GetEventCount(ui::ET_MOUSEWHEEL));
h1.ResetCounts();
view->ResetCounts();
h2.ResetCounts();
// Dispatch a ui::ET_SCROLL_FLING_START event. The event remains unhandled and
// should bubble up the views hierarchy to be re-dispatched on the root view.
ui::ScrollEvent fling(ui::ET_SCROLL_FLING_START,
gfx::Point(5, 5),
ui::EventTimeForNow(),
0,
0, 20,
0, 20,
2);
widget->OnScrollEvent(&fling);
EXPECT_EQ(2, h1.GetEventCount(ui::ET_SCROLL_FLING_START));
EXPECT_EQ(1, view->GetEventCount(ui::ET_SCROLL_FLING_START));
EXPECT_EQ(2, h2.GetEventCount(ui::ET_SCROLL_FLING_START));
// Unhandled scroll events which are not of type ui::ET_SCROLL should not
// be turned into wheel events and re-dispatched.
EXPECT_EQ(0, h1.GetEventCount(ui::ET_MOUSEWHEEL));
EXPECT_EQ(0, view->GetEventCount(ui::ET_MOUSEWHEEL));
EXPECT_EQ(0, h2.GetEventCount(ui::ET_MOUSEWHEEL));
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::CONSUME_EVENTS);
// Dispatch a ui::ET_GESTURE_TAP_DOWN and a ui::ET_GESTURE_TAP_CANCEL event.
// The events are handled at the target phase and should not reach the
// post-target handler.
ui::GestureEvent tap_down(5,
5,
0,
ui::EventTimeForNow(),
ui::GestureEventDetails(ui::ET_GESTURE_TAP_DOWN));
widget->OnGestureEvent(&tap_down);
EXPECT_EQ(1, h1.GetEventCount(ui::ET_GESTURE_TAP_DOWN));
EXPECT_EQ(1, view->GetEventCount(ui::ET_GESTURE_TAP_DOWN));
EXPECT_EQ(0, h2.GetEventCount(ui::ET_GESTURE_TAP_DOWN));
ui::GestureEvent tap_cancel(
5,
5,
0,
ui::EventTimeForNow(),
ui::GestureEventDetails(ui::ET_GESTURE_TAP_CANCEL));
widget->OnGestureEvent(&tap_cancel);
EXPECT_EQ(1, h1.GetEventCount(ui::ET_GESTURE_TAP_CANCEL));
EXPECT_EQ(1, view->GetEventCount(ui::ET_GESTURE_TAP_CANCEL));
EXPECT_EQ(0, h2.GetEventCount(ui::ET_GESTURE_TAP_CANCEL));
h1.ResetCounts();
view->ResetCounts();
h2.ResetCounts();
// Dispatch a ui::ET_SCROLL event. The event is handled at the target phase
// and should not reach the post-target handler.
ui::ScrollEvent consumed_scroll(ui::ET_SCROLL,
gfx::Point(5, 5),
ui::EventTimeForNow(),
0,
0, 20,
0, 20,
2);
widget->OnScrollEvent(&consumed_scroll);
EXPECT_EQ(1, h1.GetEventCount(ui::ET_SCROLL));
EXPECT_EQ(1, view->GetEventCount(ui::ET_SCROLL));
EXPECT_EQ(0, h2.GetEventCount(ui::ET_SCROLL));
// Handled scroll events are not turned into wheel events and re-dispatched.
EXPECT_EQ(0, h1.GetEventCount(ui::ET_MOUSEWHEEL));
EXPECT_EQ(0, view->GetEventCount(ui::ET_MOUSEWHEEL));
EXPECT_EQ(0, h2.GetEventCount(ui::ET_MOUSEWHEEL));
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 = new EventCountView();
v1->SetBounds(5, 5, 10, 10);
root_view->AddChildView(v1);
EventCountView* v2 = new EventCountView();
v2->SetBounds(5, 15, 10, 10);
root_view->AddChildView(v2);
widget->Show();
// SynthesizeMouseMoveEvent does nothing until the mouse is entered.
widget->SynthesizeMouseMoveEvent();
EXPECT_EQ(0, v1->GetEventCount(ui::ET_MOUSE_MOVED));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_MOUSE_MOVED));
gfx::Point cursor_location(5, 5);
ui::test::EventGenerator generator(
IsMus() ? GetRootWindow(widget.get()) : GetContext(),
widget->GetNativeWindow());
generator.MoveMouseTo(cursor_location);
EXPECT_EQ(1, v1->GetEventCount(ui::ET_MOUSE_MOVED));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_MOUSE_MOVED));
// SynthesizeMouseMoveEvent dispatches an mousemove event.
widget->SynthesizeMouseMoveEvent();
EXPECT_EQ(2, v1->GetEventCount(ui::ET_MOUSE_MOVED));
delete v1;
EXPECT_EQ(0, v2->GetEventCount(ui::ET_MOUSE_MOVED));
v2->SetBounds(5, 5, 10, 10);
EXPECT_EQ(0, v2->GetEventCount(ui::ET_MOUSE_MOVED));
widget->SynthesizeMouseMoveEvent();
EXPECT_EQ(1, v2->GetEventCount(ui::ET_MOUSE_MOVED));
}
namespace {
// ui::EventHandler which handles all mouse press events.
class MousePressEventConsumer : public ui::EventHandler {
public:
MousePressEventConsumer() {}
private:
// ui::EventHandler:
void OnMouseEvent(ui::MouseEvent* event) override {
if (event->type() == ui::ET_MOUSE_PRESSED)
event->SetHandled();
}
DISALLOW_COPY_AND_ASSIGN(MousePressEventConsumer);
};
} // namespace
// No touch on desktop Mac. Tracked in http://crbug.com/445520.
#if !defined(OS_MACOSX) || 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 = new EventCountView();
event_count_view->SetBounds(0, 0, 300, 300);
widget->GetRootView()->AddChildView(event_count_view);
MousePressEventConsumer consumer;
event_count_view->AddPostTargetHandler(&consumer);
ui::test::EventGenerator generator(
IsMus() ? GetRootWindow(widget) : GetContext(),
widget->GetNativeWindow());
generator.PressTouch();
generator.ClickLeftButton();
EXPECT_EQ(1, event_count_view->GetEventCount(ui::ET_MOUSE_PRESSED));
EXPECT_EQ(1, event_count_view->GetEventCount(ui::ET_MOUSE_RELEASED));
// For mus it's important we destroy the widget before the EventGenerator.
widget->CloseNow();
}
#endif // !defined(OS_MACOSX) || 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 = new EventCountView();
event_count_view->SetBounds(0, 0, 300, 300);
widget->GetRootView()->AddChildView(event_count_view);
// No capture has been set.
EXPECT_EQ(
gfx::kNullNativeView,
internal::NativeWidgetPrivate::GetGlobalCapture(widget->GetNativeView()));
MousePressEventConsumer consumer;
event_count_view->AddPostTargetHandler(&consumer);
ui::test::EventGenerator generator(
IsMus() ? GetRootWindow(widget) : GetContext(),
widget->GetNativeWindow());
generator.PressLeftButton();
EXPECT_EQ(1, event_count_view->GetEventCount(ui::ET_MOUSE_PRESSED));
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 Wiget upon receiving a mouse event. The Widget
// proceeds to take capture.
class CaptureEventConsumer : public ui::EventHandler {
public:
CaptureEventConsumer(Widget* widget)
: event_count_view_(new EventCountView()), widget_(widget) {}
~CaptureEventConsumer() override { widget_->CloseNow(); }
private:
// ui::EventHandler:
void OnMouseEvent(ui::MouseEvent* event) override {
if (event->type() == ui::ET_MOUSE_PRESSED) {
event->SetHandled();
widget_->Show();
widget_->SetSize(gfx::Size(200, 200));
event_count_view_->SetBounds(0, 0, 200, 200);
widget_->GetRootView()->AddChildView(event_count_view_);
widget_->SetCapture(event_count_view_);
}
}
EventCountView* event_count_view_;
Widget* widget_;
DISALLOW_COPY_AND_ASSIGN(CaptureEventConsumer);
};
} // 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 = new EventCountView();
event_count_view->SetBounds(0, 0, 300, 300);
widget->GetRootView()->AddChildView(event_count_view);
EXPECT_EQ(
gfx::kNullNativeView,
internal::NativeWidgetPrivate::GetGlobalCapture(widget->GetNativeView()));
Widget* widget2 = CreateTopLevelNativeWidget();
// Gives explicit capture to |widget2|
CaptureEventConsumer consumer(widget2);
event_count_view->AddPostTargetHandler(&consumer);
ui::test::EventGenerator generator(
IsMus() ? GetRootWindow(widget) : GetContext(),
widget->GetNativeWindow());
// 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::ET_MOUSE_PRESSED));
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) {}
// ui::EventObserver:
void OnEvent(const ui::Event& event) override {
// Guard against attempting to close the widget twice.
if (widget_)
widget_->CloseNow();
widget_ = nullptr;
}
private:
Widget* widget_;
DISALLOW_COPY_AND_ASSIGN(ClosingEventObserver);
};
class ClosingView : public View {
public:
explicit ClosingView(Widget* widget) : widget_(widget) {}
// View:
void OnEvent(ui::Event* event) override {
// Guard against closing twice and writing to freed memory.
if (widget_ && event->type() == ui::ET_MOUSE_PRESSED) {
Widget* widget = widget_;
widget_ = nullptr;
widget->CloseNow();
}
}
private:
Widget* widget_;
DISALLOW_COPY_AND_ASSIGN(ClosingView);
};
// 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 = new ClosingView(widget);
widget->GetContentsView()->AddChildView(closing_view);
widget->SetCapture(closing_view);
ClosingEventObserver closing_event_observer(widget);
auto monitor = EventMonitor::CreateApplicationMonitor(
&closing_event_observer, widget->GetNativeWindow(),
{ui::ET_MOUSE_PRESSED});
ui::test::EventGenerator generator(
IsMus() ? GetRootWindow(widget) : GetContext(),
widget->GetNativeWindow());
generator.set_target(ui::test::EventGenerator::Target::APPLICATION);
EXPECT_FALSE(observer.widget_closed());
generator.PressLeftButton();
EXPECT_TRUE(observer.widget_closed());
}
// 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(WidgetTest, WidgetDestroyedItselfDoesNotCrash) {
TestDesktopWidgetDelegate delegate(new TestNativeWidgetDestroyedWidget);
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
delegate.InitWidget(params);
delegate.GetWidget()->Show();
delegate.GetWidget()->CloseNow();
}
// Verifies WindowClosing() is invoked correctly on the delegate when a Widget
// is closed.
TEST_F(WidgetTest, SingleWindowClosing) {
TestDesktopWidgetDelegate delegate;
delegate.InitWidget(CreateParams(Widget::InitParams::TYPE_WINDOW));
EXPECT_EQ(0, delegate.window_closing_count());
delegate.GetWidget()->CloseNow();
EXPECT_EQ(1, delegate.window_closing_count());
}
TEST_F(WidgetTest, CloseRequested_AllowsClose) {
constexpr Widget::ClosedReason kReason = Widget::ClosedReason::kLostFocus;
TestDesktopWidgetDelegate delegate;
delegate.set_can_close(true);
delegate.InitWidget(CreateParams(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(WidgetTest, CloseRequested_DisallowClose) {
constexpr Widget::ClosedReason kReason = Widget::ClosedReason::kLostFocus;
TestDesktopWidgetDelegate delegate;
delegate.set_can_close(false);
delegate.InitWidget(CreateParams(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(WidgetTest, 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::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 WidgetTest {
protected:
void RunTest(bool desktop_native_widget) {
WidgetAutoclosePtr widget(new Widget()); // Destroyed by CloseNow().
Widget::InitParams init_params =
CreateParams(Widget::InitParams::TYPE_WINDOW);
#if !defined(OS_CHROMEOS)
if (desktop_native_widget)
init_params.native_widget = CreatePlatformDesktopNativeWidgetImpl(
init_params, widget.get(), nullptr);
#else
DCHECK(!desktop_native_widget)
<< "DesktopNativeWidget does not exist on non-Aura or on ChromeOS.";
#endif
widget->Init(init_params);
internal::NativeWidgetPrivate* native_widget =
widget->native_widget_private();
base::string16 empty;
base::string16 s1(base::UTF8ToUTF16("Title1"));
base::string16 s2(base::UTF8ToUTF16("Title2"));
base::string16 s3(base::UTF8ToUTF16("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);
}
// DesktopNativeWidget does not exist on non-Aura or on ChromeOS.
#if !defined(OS_CHROMEOS)
TEST_F(WidgetWindowTitleTest, SetWindowTitleChanged_DesktopNativeWidget) {
// Override to use a DesktopNativeWidget.
bool desktop_native_widget = true;
RunTest(desktop_native_widget);
}
#endif // !OS_CHROMEOS
TEST_F(WidgetTest, WidgetDeleted_InOnMousePressed) {
Widget* widget = new Widget;
Widget::InitParams params =
CreateParams(views::Widget::InitParams::TYPE_POPUP);
widget->Init(params);
widget->SetContentsView(new CloseWidgetView(ui::ET_MOUSE_PRESSED));
widget->SetSize(gfx::Size(100, 100));
widget->Show();
ui::test::EventGenerator generator(
IsMus() ? GetRootWindow(widget) : 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 !defined(OS_MACOSX) || defined(USE_AURA)
TEST_F(WidgetTest, WidgetDeleted_InDispatchGestureEvent) {
// TODO: test uses GetContext(), which is not applicable to aura-mus.
// http://crbug.com/663809.
if (IsMus())
return;
Widget* widget = new Widget;
Widget::InitParams params =
CreateParams(views::Widget::InitParams::TYPE_POPUP);
widget->Init(params);
widget->SetContentsView(new CloseWidgetView(ui::ET_GESTURE_TAP_DOWN));
widget->SetSize(gfx::Size(100, 100));
widget->Show();
ui::test::EventGenerator generator(GetContext(), widget->GetNativeWindow());
WidgetDeletionObserver deletion_observer(widget);
generator.GestureTapAt(widget->GetWindowBoundsInScreen().CenterPoint());
EXPECT_FALSE(deletion_observer.IsWidgetAlive());
// Yay we did not crash!
}
#endif // !defined(OS_MACOSX) || defined(USE_AURA)
// See description of RunGetNativeThemeFromDestructor() for details.
class GetNativeThemeFromDestructorView : public WidgetDelegateView {
public:
GetNativeThemeFromDestructorView() {}
~GetNativeThemeFromDestructorView() override { VerifyNativeTheme(); }
private:
void VerifyNativeTheme() {
ASSERT_TRUE(GetNativeTheme() != NULL);
}
DISALLOW_COPY_AND_ASSIGN(GetNativeThemeFromDestructorView);
};
// 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(const Widget::InitParams& in_params,
bool is_first_run) {
bool needs_second_run = false;
// Destroyed by CloseNow() below.
WidgetTest::WidgetAutoclosePtr widget(new Widget);
Widget::InitParams params(in_params);
// Deletes itself when the Widget is destroyed.
params.delegate = new GetNativeThemeFromDestructorView;
#if !defined(OS_CHROMEOS)
if (is_first_run) {
params.native_widget =
CreatePlatformDesktopNativeWidgetImpl(params, widget.get(), nullptr);
needs_second_run = true;
}
#endif
widget->Init(params);
return needs_second_run;
}
// See description of RunGetNativeThemeFromDestructor() for details.
TEST_F(WidgetTest, GetNativeThemeFromDestructor) {
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
if (RunGetNativeThemeFromDestructor(params, true))
RunGetNativeThemeFromDestructor(params, false);
}
// Used by HideCloseDestroy. Allows setting a boolean when the widget is
// destroyed.
class CloseDestroysWidget : public Widget {
public:
explicit CloseDestroysWidget(bool* destroyed)
: destroyed_(destroyed) {
}
~CloseDestroysWidget() override {
if (destroyed_) {
*destroyed_ = true;
base::RunLoop::QuitCurrentDeprecated();
}
}
void Detach() { destroyed_ = NULL; }
private:
// If non-null set to true from destructor.
bool* destroyed_;
DISALLOW_COPY_AND_ASSIGN(CloseDestroysWidget);
};
// An observer that registers that an animation has ended.
class AnimationEndObserver : public ui::ImplicitAnimationObserver {
public:
AnimationEndObserver() : animation_completed_(false) {}
~AnimationEndObserver() override {}
bool animation_completed() const { return animation_completed_; }
// ui::ImplicitAnimationObserver:
void OnImplicitAnimationsCompleted() override { animation_completed_ = true; }
private:
bool animation_completed_;
DISALLOW_COPY_AND_ASSIGN(AnimationEndObserver);
};
// An observer that registers the bounds of a widget on destruction.
class WidgetBoundsObserver : public WidgetObserver {
public:
WidgetBoundsObserver() {}
~WidgetBoundsObserver() override {}
gfx::Rect bounds() { return bounds_; }
// WidgetObserver:
void OnWidgetDestroying(Widget* widget) override {
EXPECT_TRUE(widget->GetNativeWindow());
EXPECT_TRUE(Widget::GetWidgetForNativeWindow(widget->GetNativeWindow()));
bounds_ = widget->GetWindowBoundsInScreen();
}
private:
gfx::Rect bounds_;
DISALLOW_COPY_AND_ASSIGN(WidgetBoundsObserver);
};
// Verifies Close() results in destroying.
TEST_F(WidgetTest, CloseDestroys) {
bool destroyed = false;
CloseDestroysWidget* widget = new CloseDestroysWidget(&destroyed);
Widget::InitParams params =
CreateParams(views::Widget::InitParams::TYPE_MENU);
params.opacity = Widget::InitParams::OPAQUE_WINDOW;
#if !defined(OS_CHROMEOS)
params.native_widget =
CreatePlatformDesktopNativeWidgetImpl(params, widget, nullptr);
#endif
widget->Init(params);
widget->Show();
widget->Hide();
widget->Close();
EXPECT_FALSE(destroyed);
// Run the message loop as Close() asynchronously deletes.
base::RunLoop().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(new Widget);
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.bounds = gfx::Rect(50, 50, 250, 250);
widget->Init(params);
AnimationEndObserver animation_observer;
WidgetBoundsObserver widget_observer;
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->AddObserver(&widget_observer);
widget->Show();
// Animate the bounds change.
widget->SetBounds(bounds);
widget.reset();
EXPECT_FALSE(animation_observer.animation_completed());
}
EXPECT_TRUE(animation_observer.animation_completed());
EXPECT_EQ(widget_observer.bounds(), bounds);
}
// Test Widget::CloseAllSecondaryWidgets works as expected across platforms.
// ChromeOS doesn't implement or need CloseAllSecondaryWidgets() since
// everything is under a single root window.
#if !defined(OS_CHROMEOS)
TEST_F(WidgetTest, CloseAllSecondaryWidgets) {
Widget* widget1 = CreateNativeDesktopWidget();
Widget* widget2 = CreateNativeDesktopWidget();
TestWidgetObserver observer1(widget1);
TestWidgetObserver observer2(widget2);
widget1->Show(); // Just show the first one.
Widget::CloseAllSecondaryWidgets();
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(WidgetTest, ValidDuringOnNativeWidgetDestroyingFromCloseNow) {
Widget* widget = CreateNativeDesktopWidget();
widget->Show();
gfx::Rect screen_rect(50, 50, 100, 100);
widget->SetBounds(screen_rect);
WidgetBoundsObserver observer;
widget->AddObserver(&observer);
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(WidgetTest, ValidDuringOnNativeWidgetDestroyingFromClose) {
Widget* widget = CreateNativeDesktopWidget();
widget->Show();
gfx::Rect screen_rect(50, 50, 100, 100);
widget->SetBounds(screen_rect);
WidgetBoundsObserver observer;
widget->AddObserver(&observer);
widget->Close();
EXPECT_EQ(gfx::Rect(), observer.bounds());
base::RunLoop().RunUntilIdle();
// Broken on Linux. See http://crbug.com/515379.
#if !defined(OS_LINUX) || defined(OS_CHROMEOS)
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) {
std::unique_ptr<Widget> widget(new Widget);
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget->Init(params);
}
TEST_F(WidgetTest, NoCrashOnResizeConstraintsWindowTitleOnPopup) {
std::unique_ptr<Widget> widget(new Widget);
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget->Init(params);
widget->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) {
// TODO: test uses GetContext(), which is not applicable to aura-mus.
// http://crbug.com/663809.
if (IsMus())
return;
std::unique_ptr<Widget> widget(new Widget);
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
params.bounds = gfx::Rect(0, 0, 200, 200);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget->Init(params);
widget->Show();
ui::test::EventGenerator generator(GetContext(), widget->GetNativeWindow());
generator.MoveMouseTo(10, 10);
// No touch on desktop Mac. Tracked in http://crbug.com/445520.
#if defined(OS_MACOSX) && !defined(USE_AURA)
generator.ClickLeftButton();
#else
generator.PressTouch();
#endif
widget.reset();
}
// A view that consumes mouse-pressed event and gesture-tap-down events.
class RootViewTestView : public View {
public:
RootViewTestView(): View() {}
private:
bool OnMousePressed(const ui::MouseEvent& event) override { return true; }
void OnGestureEvent(ui::GestureEvent* event) override {
if (event->type() == ui::ET_GESTURE_TAP_DOWN)
event->SetHandled();
}
};
// Checks if RootView::*_handler_ fields are unset when widget is hidden.
// Fails on chromium.webkit Windows bot, see crbug.com/264872.
#if defined(OS_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->AddChildView(view);
// Check RootView::mouse_pressed_handler_.
widget->Show();
EXPECT_EQ(NULL, GetMousePressedHandler(root_view));
gfx::Point click_location(45, 15);
ui::MouseEvent press(ui::ET_MOUSE_PRESSED, 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(NULL, GetMousePressedHandler(root_view));
// Check RootView::mouse_move_handler_.
widget->Show();
EXPECT_EQ(NULL, GetMouseMoveHandler(root_view));
gfx::Point move_location(45, 15);
ui::MouseEvent move(ui::ET_MOUSE_MOVED, move_location, move_location,
ui::EventTimeForNow(), 0, 0);
widget->OnMouseEvent(&move);
EXPECT_EQ(view, GetMouseMoveHandler(root_view));
widget->Hide();
EXPECT_EQ(NULL, GetMouseMoveHandler(root_view));
// Check RootView::gesture_handler_.
widget->Show();
EXPECT_EQ(NULL, GetGestureHandler(root_view));
ui::GestureEvent tap_down(15, 15, 0, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_TAP_DOWN));
widget->OnGestureEvent(&tap_down);
EXPECT_EQ(view, GetGestureHandler(root_view));
widget->Hide();
EXPECT_EQ(NULL, GetGestureHandler(root_view));
widget->Close();
}
// Convenience to make constructing a GestureEvent simpler.
class GestureEventForTest : public ui::GestureEvent {
public:
GestureEventForTest(ui::EventType type, int x, int y)
: GestureEvent(x,
y,
0,
base::TimeTicks(),
ui::GestureEventDetails(type)) {}
GestureEventForTest(ui::GestureEventDetails details, int x, int y)
: GestureEvent(x, y, 0, base::TimeTicks(), details) {}
};
// Tests that the |gesture_handler_| member in RootView is always NULL
// after the dispatch of a ui::ET_GESTURE_END event corresponding to
// the release of the final touch point on the screen, but that
// ui::ET_GESTURE_END 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->AddChildView(view);
widget->Show();
// If no gesture handler is set, a ui::ET_GESTURE_END event should not set
// the gesture handler.
EXPECT_EQ(NULL, GetGestureHandler(root_view));
GestureEventForTest end(ui::ET_GESTURE_END, 15, 15);
widget->OnGestureEvent(&end);
EXPECT_EQ(NULL, 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::CONSUME_EVENTS);
GestureEventForTest tap(ui::ET_GESTURE_TAP, 15, 15);
widget->OnGestureEvent(&tap);
EXPECT_TRUE(tap.handled());
EXPECT_EQ(view, GetGestureHandler(root_view));
// The gesture handler should remain unchanged on a ui::ET_GESTURE_END
// corresponding to a second touch point, but should be reset to NULL by a
// ui::ET_GESTURE_END corresponding to the final touch point.
ui::GestureEventDetails details(ui::ET_GESTURE_END);
details.set_touch_points(2);
GestureEventForTest end_second_touch_point(details, 15, 15);
widget->OnGestureEvent(&end_second_touch_point);
EXPECT_EQ(view, GetGestureHandler(root_view));
end = GestureEventForTest(ui::ET_GESTURE_END, 15, 15);
widget->OnGestureEvent(&end);
EXPECT_TRUE(end.handled());
EXPECT_EQ(NULL, 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 = GestureEventForTest(ui::ET_GESTURE_TAP, 15, 15);
widget->OnGestureEvent(&tap);
EXPECT_TRUE(tap.handled());
EXPECT_EQ(view, GetGestureHandler(root_view));
view->set_handle_mode(EventCountView::PROPAGATE_EVENTS);
// The gesture handler should remain unchanged on a ui::ET_GESTURE_END
// corresponding to a second touch point, but should be reset to NULL by a
// ui::ET_GESTURE_END corresponding to the final touch point.
end_second_touch_point = GestureEventForTest(details, 15, 15);
widget->OnGestureEvent(&end_second_touch_point);
EXPECT_EQ(view, GetGestureHandler(root_view));
end = GestureEventForTest(ui::ET_GESTURE_END, 15, 15);
widget->OnGestureEvent(&end);
EXPECT_FALSE(end.handled());
EXPECT_EQ(NULL, 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->AddChildView(v1);
v1->AddChildView(v2);
v2->AddChildView(v3);
v3->AddChildView(v4);
widget->Show();
// ui::ET_GESTURE_BEGIN events should never be seen by any view, but
// they should be marked as handled by OnEventProcessingStarted().
GestureEventForTest begin(ui::ET_GESTURE_BEGIN, 5, 5);
widget->OnGestureEvent(&begin);
EXPECT_EQ(0, v1->GetEventCount(ui::ET_GESTURE_BEGIN));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_GESTURE_BEGIN));
EXPECT_EQ(0, v3->GetEventCount(ui::ET_GESTURE_BEGIN));
EXPECT_EQ(0, v4->GetEventCount(ui::ET_GESTURE_BEGIN));
EXPECT_EQ(NULL, GetGestureHandler(root_view));
EXPECT_TRUE(begin.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// ui::ET_GESTURE_END 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().
GestureEventForTest end(ui::ET_GESTURE_END, 5, 5);
widget->OnGestureEvent(&end);
EXPECT_EQ(0, v1->GetEventCount(ui::ET_GESTURE_END));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_GESTURE_END));
EXPECT_EQ(0, v3->GetEventCount(ui::ET_GESTURE_END));
EXPECT_EQ(0, v4->GetEventCount(ui::ET_GESTURE_END));
EXPECT_EQ(NULL, GetGestureHandler(root_view));
EXPECT_TRUE(end.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// ui::ET_GESTURE_END 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::ET_GESTURE_END);
details.set_touch_points(2);
GestureEventForTest end_second_touch_point(details, 5, 5);
widget->OnGestureEvent(&end_second_touch_point);
EXPECT_EQ(0, v1->GetEventCount(ui::ET_GESTURE_END));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_GESTURE_END));
EXPECT_EQ(0, v3->GetEventCount(ui::ET_GESTURE_END));
EXPECT_EQ(0, v4->GetEventCount(ui::ET_GESTURE_END));
EXPECT_EQ(NULL, GetGestureHandler(root_view));
EXPECT_TRUE(end_second_touch_point.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// ui::ET_GESTURE_SCROLL_UPDATE 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().
GestureEventForTest scroll_update(ui::ET_GESTURE_SCROLL_UPDATE, 5, 5);
widget->OnGestureEvent(&scroll_update);
EXPECT_EQ(0, v1->GetEventCount(ui::ET_GESTURE_SCROLL_UPDATE));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_GESTURE_SCROLL_UPDATE));
EXPECT_EQ(0, v3->GetEventCount(ui::ET_GESTURE_SCROLL_UPDATE));
EXPECT_EQ(0, v4->GetEventCount(ui::ET_GESTURE_SCROLL_UPDATE));
EXPECT_EQ(NULL, GetGestureHandler(root_view));
EXPECT_TRUE(scroll_update.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// ui::ET_GESTURE_SCROLL_END 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().
GestureEventForTest scroll_end(ui::ET_GESTURE_SCROLL_END, 5, 5);
widget->OnGestureEvent(&scroll_end);
EXPECT_EQ(0, v1->GetEventCount(ui::ET_GESTURE_SCROLL_END));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_GESTURE_SCROLL_END));
EXPECT_EQ(0, v3->GetEventCount(ui::ET_GESTURE_SCROLL_END));
EXPECT_EQ(0, v4->GetEventCount(ui::ET_GESTURE_SCROLL_END));
EXPECT_EQ(NULL, GetGestureHandler(root_view));
EXPECT_TRUE(scroll_end.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// ui::ET_SCROLL_FLING_START 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().
GestureEventForTest scroll_fling_start(ui::ET_SCROLL_FLING_START, 5, 5);
widget->OnGestureEvent(&scroll_fling_start);
EXPECT_EQ(0, v1->GetEventCount(ui::ET_SCROLL_FLING_START));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_SCROLL_FLING_START));
EXPECT_EQ(0, v3->GetEventCount(ui::ET_SCROLL_FLING_START));
EXPECT_EQ(0, v4->GetEventCount(ui::ET_SCROLL_FLING_START));
EXPECT_EQ(NULL, 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->AddChildView(v1);
v1->AddChildView(v2);
v2->AddChildView(v3);
v3->AddChildView(v4);
widget->Show();
// No gesture handler is set in the root view and none of the views in the
// view hierarchy handle a ui::ET_GESTURE_TAP 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.
GestureEventForTest tap(ui::ET_GESTURE_TAP, 5, 5);
EXPECT_EQ(NULL, GetGestureHandler(root_view));
widget->OnGestureEvent(&tap);
EXPECT_EQ(1, v1->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(1, v2->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(1, v3->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(1, v4->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(NULL, 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::ET_GESTURE_TAP 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::CONSUME_EVENTS);
v2->set_handle_mode(EventCountView::CONSUME_EVENTS);
v3->set_handle_mode(EventCountView::CONSUME_EVENTS);
tap = GestureEventForTest(ui::ET_GESTURE_TAP, 5, 5);
widget->OnGestureEvent(&tap);
EXPECT_EQ(0, v1->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(1, v3->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(1, v4->GetEventCount(ui::ET_GESTURE_TAP));
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::CONSUME_EVENTS);
tap = GestureEventForTest(ui::ET_GESTURE_TAP, 5, 5);
widget->OnGestureEvent(&tap);
EXPECT_TRUE(tap.handled());
GestureEventForTest show_press(ui::ET_GESTURE_SHOW_PRESS, 5, 5);
widget->OnGestureEvent(&show_press);
tap = GestureEventForTest(ui::ET_GESTURE_TAP, 5, 5);
widget->OnGestureEvent(&tap);
EXPECT_EQ(0, v1->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(2, v3->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(0, v4->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(0, v1->GetEventCount(ui::ET_GESTURE_SHOW_PRESS));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_GESTURE_SHOW_PRESS));
EXPECT_EQ(1, v3->GetEventCount(ui::ET_GESTURE_SHOW_PRESS));
EXPECT_EQ(0, v4->GetEventCount(ui::ET_GESTURE_SHOW_PRESS));
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::ET_GESTURE_TAP 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::PROPAGATE_EVENTS);
tap = GestureEventForTest(ui::ET_GESTURE_TAP, 5, 5);
widget->OnGestureEvent(&tap);
EXPECT_EQ(0, v1->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(1, v3->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(0, v4->GetEventCount(ui::ET_GESTURE_TAP));
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->AddChildView(v1);
v1->AddChildView(v2);
v2->AddChildView(v3);
v3->AddChildView(v4);
widget->Show();
// Change the handle mode of |v3| to indicate that it would like to handle
// gesture events.
v3->set_handle_mode(EventCountView::CONSUME_EVENTS);
// When no gesture handler is set, dispatching a ui::ET_GESTURE_TAP_DOWN
// 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(NULL, GetGestureHandler(root_view));
GestureEventForTest tap_down(ui::ET_GESTURE_TAP_DOWN, 5, 5);
widget->OnGestureEvent(&tap_down);
EXPECT_EQ(0, v1->GetEventCount(ui::ET_GESTURE_TAP_DOWN));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_GESTURE_TAP_DOWN));
EXPECT_EQ(1, v3->GetEventCount(ui::ET_GESTURE_TAP_DOWN));
EXPECT_EQ(1, v4->GetEventCount(ui::ET_GESTURE_TAP_DOWN));
EXPECT_EQ(v3, GetGestureHandler(root_view));
EXPECT_TRUE(tap_down.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// A ui::ET_GESTURE_TAP_CANCEL event should be dispatched to |v3| directly.
GestureEventForTest tap_cancel(ui::ET_GESTURE_TAP_CANCEL, 5, 5);
widget->OnGestureEvent(&tap_cancel);
EXPECT_EQ(0, v1->GetEventCount(ui::ET_GESTURE_TAP_CANCEL));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_GESTURE_TAP_CANCEL));
EXPECT_EQ(1, v3->GetEventCount(ui::ET_GESTURE_TAP_CANCEL));
EXPECT_EQ(0, v4->GetEventCount(ui::ET_GESTURE_TAP_CANCEL));
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::PROPAGATE_EVENTS);
v1->set_handle_mode(EventCountView::CONSUME_EVENTS);
// Dispatch a ui::ET_GESTURE_SCROLL_BEGIN 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|.
GestureEventForTest scroll_begin(ui::ET_GESTURE_SCROLL_BEGIN, 5, 5);
widget->OnGestureEvent(&scroll_begin);
EXPECT_EQ(1, v1->GetEventCount(ui::ET_GESTURE_SCROLL_BEGIN));
EXPECT_EQ(1, v2->GetEventCount(ui::ET_GESTURE_SCROLL_BEGIN));
EXPECT_EQ(1, v3->GetEventCount(ui::ET_GESTURE_SCROLL_BEGIN));
EXPECT_EQ(0, v4->GetEventCount(ui::ET_GESTURE_SCROLL_BEGIN));
EXPECT_EQ(v1, GetGestureHandler(root_view));
EXPECT_TRUE(scroll_begin.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// A ui::ET_GESTURE_SCROLL_UPDATE event should be dispatched to |v1|
// directly.
GestureEventForTest scroll_update(ui::ET_GESTURE_SCROLL_UPDATE, 5, 5);
widget->OnGestureEvent(&scroll_update);
EXPECT_EQ(1, v1->GetEventCount(ui::ET_GESTURE_SCROLL_UPDATE));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_GESTURE_SCROLL_UPDATE));
EXPECT_EQ(0, v3->GetEventCount(ui::ET_GESTURE_SCROLL_UPDATE));
EXPECT_EQ(0, v4->GetEventCount(ui::ET_GESTURE_SCROLL_UPDATE));
EXPECT_EQ(v1, GetGestureHandler(root_view));
EXPECT_TRUE(scroll_update.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// A ui::ET_GESTURE_SCROLL_END event should be dispatched to |v1|
// directly and should not reset the gesture handler.
GestureEventForTest scroll_end(ui::ET_GESTURE_SCROLL_END, 5, 5);
widget->OnGestureEvent(&scroll_end);
EXPECT_EQ(1, v1->GetEventCount(ui::ET_GESTURE_SCROLL_END));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_GESTURE_SCROLL_END));
EXPECT_EQ(0, v3->GetEventCount(ui::ET_GESTURE_SCROLL_END));
EXPECT_EQ(0, v4->GetEventCount(ui::ET_GESTURE_SCROLL_END));
EXPECT_EQ(v1, GetGestureHandler(root_view));
EXPECT_TRUE(scroll_end.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// A ui::ET_GESTURE_PINCH_BEGIN event (which is a non-scroll event) should
// still be dispatched to |v1| directly.
GestureEventForTest pinch_begin(ui::ET_GESTURE_PINCH_BEGIN, 5, 5);
widget->OnGestureEvent(&pinch_begin);
EXPECT_EQ(1, v1->GetEventCount(ui::ET_GESTURE_PINCH_BEGIN));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_GESTURE_PINCH_BEGIN));
EXPECT_EQ(0, v3->GetEventCount(ui::ET_GESTURE_PINCH_BEGIN));
EXPECT_EQ(0, v4->GetEventCount(ui::ET_GESTURE_PINCH_BEGIN));
EXPECT_EQ(v1, GetGestureHandler(root_view));
EXPECT_TRUE(pinch_begin.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// A ui::ET_GESTURE_END event should be dispatched to |v1| and should
// set the gesture handler to NULL.
GestureEventForTest end(ui::ET_GESTURE_END, 5, 5);
widget->OnGestureEvent(&end);
EXPECT_EQ(1, v1->GetEventCount(ui::ET_GESTURE_END));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_GESTURE_END));
EXPECT_EQ(0, v3->GetEventCount(ui::ET_GESTURE_END));
EXPECT_EQ(0, v4->GetEventCount(ui::ET_GESTURE_END));
EXPECT_EQ(NULL, GetGestureHandler(root_view));
EXPECT_TRUE(end.handled());
widget->Close();
}
// 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 {
public:
GestureLocationView() {}
~GestureLocationView() override {}
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_;
DISALLOW_COPY_AND_ASSIGN(GestureLocationView);
};
// 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->AddChildView(v1);
v1->AddChildView(v2);
v2->AddChildView(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);
GestureEventForTest tap(
ui::ET_GESTURE_TAP, 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::ET_GESTURE_TAP));
EXPECT_EQ(1, view2->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(1, view3->GetEventCount(ui::ET_GESTURE_TAP));
widget->Close();
}
// Verifies that disabled views are permitted to be set as the default gesture
// handler in RootView. Also verifies that gesture events targeted to a disabled
// view are not actually dispatched to the view, but are still marked as
// handled.
TEST_F(WidgetTest, DisabledGestureEventTarget) {
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->AddChildView(v1);
v1->AddChildView(v2);
v2->AddChildView(v3);
v3->AddChildView(v4);
widget->Show();
// |v1|, |v2|, and |v3| all handle gesture events but |v3| is marked as
// disabled.
v1->set_handle_mode(EventCountView::CONSUME_EVENTS);
v2->set_handle_mode(EventCountView::CONSUME_EVENTS);
v3->set_handle_mode(EventCountView::CONSUME_EVENTS);
v3->SetEnabled(false);
// No gesture handler is set in the root view. In this case the tap event
// should be dispatched only to |v4|, the gesture handler should be set to
// |v3|, and the event should be marked as handled.
GestureEventForTest tap(ui::ET_GESTURE_TAP, 5, 5);
widget->OnGestureEvent(&tap);
EXPECT_EQ(0, v1->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(0, v3->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(1, v4->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(v3, GetGestureHandler(root_view));
EXPECT_TRUE(tap.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// A subsequent gesture event should be marked as handled but not dispatched.
tap = GestureEventForTest(ui::ET_GESTURE_TAP, 5, 5);
widget->OnGestureEvent(&tap);
EXPECT_EQ(0, v1->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(0, v3->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(0, v4->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(v3, GetGestureHandler(root_view));
EXPECT_TRUE(tap.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// A GESTURE_END should reset the default gesture handler to NULL. It should
// also not be dispatched to |v3| but still marked as handled.
GestureEventForTest end(ui::ET_GESTURE_END, 5, 5);
widget->OnGestureEvent(&end);
EXPECT_EQ(0, v1->GetEventCount(ui::ET_GESTURE_END));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_GESTURE_END));
EXPECT_EQ(0, v3->GetEventCount(ui::ET_GESTURE_END));
EXPECT_EQ(0, v4->GetEventCount(ui::ET_GESTURE_END));
EXPECT_EQ(NULL, GetGestureHandler(root_view));
EXPECT_TRUE(end.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 which are dispatched to it.
v3->set_handle_mode(EventCountView::PROPAGATE_EVENTS);
// No gesture handler is set in the root view. In this case the tap event
// should be dispatched only to |v4| and the event should be marked as
// handled. Furthermore, the gesture handler should be set to
// |v3|; even though |v3| does not explicitly handle events, it is a
// valid target for the tap event because it is disabled.
tap = GestureEventForTest(ui::ET_GESTURE_TAP, 5, 5);
widget->OnGestureEvent(&tap);
EXPECT_EQ(0, v1->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(0, v3->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(1, v4->GetEventCount(ui::ET_GESTURE_TAP));
EXPECT_EQ(v3, GetGestureHandler(root_view));
EXPECT_TRUE(tap.handled());
v1->ResetCounts();
v2->ResetCounts();
v3->ResetCounts();
v4->ResetCounts();
// A GESTURE_END should reset the default gesture handler to NULL. It should
// also not be dispatched to |v3| but still marked as handled.
end = GestureEventForTest(ui::ET_GESTURE_END, 5, 5);
widget->OnGestureEvent(&end);
EXPECT_EQ(0, v1->GetEventCount(ui::ET_GESTURE_END));
EXPECT_EQ(0, v2->GetEventCount(ui::ET_GESTURE_END));
EXPECT_EQ(0, v3->GetEventCount(ui::ET_GESTURE_END));
EXPECT_EQ(0, v4->GetEventCount(ui::ET_GESTURE_END));
EXPECT_EQ(NULL, GetGestureHandler(root_view));
EXPECT_TRUE(end.handled());
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);
std::set<Widget*> child_widgets;
Widget::GetAllChildWidgets(toplevel->GetNativeView(), &child_widgets);
EXPECT_EQ(expected.size(), child_widgets.size());
EXPECT_TRUE(
std::equal(expected.begin(), expected.end(), child_widgets.begin()));
// 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()));
std::set<Widget*> owned_widgets;
Widget::GetAllOwnedWidgets(toplevel->GetNativeView(), &owned_widgets);
EXPECT_EQ(expected.size(), owned_widgets.size());
EXPECT_TRUE(
std::equal(expected.begin(), expected.end(), owned_widgets.begin()));
}
// Used by DestroyChildWidgetsInOrder. On destruction adds the supplied name to
// a vector.
class DestroyedTrackingView : public View {
public:
DestroyedTrackingView(const std::string& name,
std::vector<std::string>* add_to)
: name_(name),
add_to_(add_to) {
}
~DestroyedTrackingView() override { add_to_->push_back(name_); }
private:
const std::string name_;
std::vector<std::string>* add_to_;
DISALLOW_COPY_AND_ASSIGN(DestroyedTrackingView);
};
class WidgetChildDestructionTest : public WidgetTest {
public:
WidgetChildDestructionTest() {}
// 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(views::Widget::InitParams::TYPE_WINDOW);
#if !defined(OS_CHROMEOS)
if (top_level_has_desktop_native_widget_aura) {
params.native_widget =
CreatePlatformDesktopNativeWidgetImpl(params, top_level, nullptr);
}
#endif
top_level->Init(params);
top_level->GetRootView()->AddChildView(
new DestroyedTrackingView("parent", &destroyed));
top_level->Show();
Widget* child = new Widget;
Widget::InitParams child_params =
CreateParams(views::Widget::InitParams::TYPE_POPUP);
child_params.parent = top_level->GetNativeView();
#if !defined(OS_CHROMEOS)
if (child_has_desktop_native_widget_aura) {
child_params.native_widget =
CreatePlatformDesktopNativeWidgetImpl(child_params, child, nullptr);
}
#endif
child->Init(child_params);
child->GetRootView()->AddChildView(
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]);
}
private:
DISALLOW_COPY_AND_ASSIGN(WidgetChildDestructionTest);
};
#if !defined(OS_CHROMEOS)
// 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);
}
#endif // !defined(OS_CHROMEOS)
// 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) {
Widget::InitParams init_params =
CreateParams(Widget::InitParams::TYPE_WINDOW);
init_params.show_state = ui::SHOW_STATE_NORMAL;
init_params.bounds = gfx::Rect(0, 0, 500, 500);
init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
Widget top_level_widget;
top_level_widget.Init(init_params);
top_level_widget.SetFullscreen(true);
EXPECT_EQ(top_level_widget.IsVisible(),
IsNativeWindowVisible(top_level_widget.GetNativeWindow()));
top_level_widget.CloseNow();
}
// Verifies nativeview visbility matches that of Widget visibility when
// SetFullscreen is invoked, for a widget provided with a desktop widget.
#if !defined(OS_CHROMEOS)
TEST_F(WidgetTest, FullscreenStatePropagated_DesktopWidget) {
Widget::InitParams init_params =
CreateParams(Widget::InitParams::TYPE_WINDOW);
init_params.show_state = ui::SHOW_STATE_NORMAL;
init_params.bounds = gfx::Rect(0, 0, 500, 500);
init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
Widget top_level_widget;
init_params.native_widget = CreatePlatformDesktopNativeWidgetImpl(
init_params, &top_level_widget, nullptr);
top_level_widget.Init(init_params);
top_level_widget.SetFullscreen(true);
EXPECT_EQ(top_level_widget.IsVisible(),
IsNativeWindowVisible(top_level_widget.GetNativeWindow()));
top_level_widget.CloseNow();
}
#endif
namespace {
class FullscreenAwareFrame : public views::NonClientFrameView {
public:
explicit FullscreenAwareFrame(views::Widget* widget)
: widget_(widget), fullscreen_layout_called_(false) {}
~FullscreenAwareFrame() override {}
// views::NonClientFrameView overrides:
gfx::Rect GetBoundsForClientView() const override { return gfx::Rect(); }
gfx::Rect GetWindowBoundsForClientBounds(
const gfx::Rect& client_bounds) const override {
return gfx::Rect();
}
int NonClientHitTest(const gfx::Point& point) override { return HTNOWHERE; }
void GetWindowMask(const gfx::Size& size, SkPath* window_mask) override {}
void ResetWindowControls() override {}
void UpdateWindowIcon() override {}
void UpdateWindowTitle() override {}
void SizeConstraintsChanged() override {}
// views::View overrides:
void Layout() override {
if (widget_->IsFullscreen())
fullscreen_layout_called_ = true;
}
bool fullscreen_layout_called() const { return fullscreen_layout_called_; }
private:
views::Widget* widget_;
bool fullscreen_layout_called_;
DISALLOW_COPY_AND_ASSIGN(FullscreenAwareFrame);
};
} // namespace
// Tests that frame Layout is called when a widget goes fullscreen without
// changing its size or title.
TEST_F(WidgetTest, FullscreenFrameLayout) {
WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
FullscreenAwareFrame* frame = new FullscreenAwareFrame(widget.get());
widget->non_client_view()->SetFrameView(frame); // Owns |frame|.
widget->Maximize();
RunPendingMessages();
EXPECT_FALSE(frame->fullscreen_layout_called());
widget->SetFullscreen(true);
widget->Show();
RunPendingMessages();
EXPECT_TRUE(frame->fullscreen_layout_called());
}
#if !defined(OS_CHROMEOS)
namespace {
// Trivial WidgetObserverTest that invokes Widget::IsActive() from
// OnWindowDestroying.
class IsActiveFromDestroyObserver : public WidgetObserver {
public:
IsActiveFromDestroyObserver() {}
~IsActiveFromDestroyObserver() override {}
void OnWidgetDestroying(Widget* widget) override { widget->IsActive(); }
private:
DISALLOW_COPY_AND_ASSIGN(IsActiveFromDestroyObserver);
};
} // namespace
// Verifies Widget::IsActive() invoked from
// WidgetObserver::OnWidgetDestroying() in a child widget doesn't crash.
TEST_F(WidgetTest, IsActiveFromDestroy) {
// Create two widgets, one a child of the other.
IsActiveFromDestroyObserver observer;
Widget parent_widget;
Widget::InitParams parent_params =
CreateParams(Widget::InitParams::TYPE_POPUP);
parent_params.native_widget = CreatePlatformDesktopNativeWidgetImpl(
parent_params, &parent_widget, nullptr);
parent_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
parent_widget.Init(parent_params);
parent_widget.Show();
Widget child_widget;
Widget::InitParams child_params =
CreateParams(Widget::InitParams::TYPE_POPUP);
child_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
child_params.context = parent_widget.GetNativeWindow();
child_widget.Init(child_params);
child_widget.AddObserver(&observer);
child_widget.Show();
parent_widget.CloseNow();
}
#endif // !defined(OS_CHROMEOS)
// 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) {
// TODO: test uses GetContext(), which is not applicable to aura-mus.
// http://crbug.com/663809.
if (IsMus())
return;
EventCountView* view = new EventCountView;
view->set_handle_mode(EventCountView::CONSUME_EVENTS);
view->SetBounds(10, 10, 50, 40);
WidgetAutoclosePtr widget(CreateTopLevelFramelessPlatformWidget());
widget->GetRootView()->AddChildView(view);
widget->SetBounds(gfx::Rect(0, 0, 100, 80));
widget->Show();
ui::test::EventGenerator generator(GetContext(), widget->GetNativeWindow());
generator.set_current_screen_location(gfx::Point(20, 20));
generator.ClickLeftButton();
EXPECT_EQ(1, view->GetEventCount(ui::ET_MOUSE_PRESSED));
EXPECT_EQ(1, view->GetEventCount(ui::ET_MOUSE_RELEASED));
EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, view->last_flags());
generator.PressRightButton();
EXPECT_EQ(2, view->GetEventCount(ui::ET_MOUSE_PRESSED));
EXPECT_EQ(1, view->GetEventCount(ui::ET_MOUSE_RELEASED));
EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, view->last_flags());
generator.ReleaseRightButton();
EXPECT_EQ(2, view->GetEventCount(ui::ET_MOUSE_PRESSED));
EXPECT_EQ(2, view->GetEventCount(ui::ET_MOUSE_RELEASED));
EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, view->last_flags());
// Test mouse move events.
EXPECT_EQ(0, view->GetEventCount(ui::ET_MOUSE_MOVED));
EXPECT_EQ(0, view->GetEventCount(ui::ET_MOUSE_ENTERED));
// Move the mouse within the view (20, 20) -> (30, 30).
generator.MoveMouseTo(gfx::Point(30, 30));
EXPECT_EQ(1, view->GetEventCount(ui::ET_MOUSE_MOVED));
EXPECT_EQ(1, view->GetEventCount(ui::ET_MOUSE_ENTERED));
EXPECT_EQ(ui::EF_NONE, view->last_flags());
// Move it again - entered count shouldn't change.
generator.MoveMouseTo(gfx::Point(31, 31));
EXPECT_EQ(2, view->GetEventCount(ui::ET_MOUSE_MOVED));
EXPECT_EQ(1, view->GetEventCount(ui::ET_MOUSE_ENTERED));
EXPECT_EQ(0, view->GetEventCount(ui::ET_MOUSE_EXITED));
// Move it off the view.
generator.MoveMouseTo(gfx::Point(5, 5));
EXPECT_EQ(2, view->GetEventCount(ui::ET_MOUSE_MOVED));
EXPECT_EQ(1, view->GetEventCount(ui::ET_MOUSE_ENTERED));
EXPECT_EQ(1, view->GetEventCount(ui::ET_MOUSE_EXITED));
// Move it back on.
generator.MoveMouseTo(gfx::Point(20, 20));
EXPECT_EQ(3, view->GetEventCount(ui::ET_MOUSE_MOVED));
EXPECT_EQ(2, view->GetEventCount(ui::ET_MOUSE_ENTERED));
EXPECT_EQ(1, view->GetEventCount(ui::ET_MOUSE_EXITED));
// Drargging. Cover HasCapture() and NativeWidgetPrivate::IsMouseButtonDown().
generator.DragMouseTo(gfx::Point(40, 40));
EXPECT_EQ(3, view->GetEventCount(ui::ET_MOUSE_PRESSED));
EXPECT_EQ(3, view->GetEventCount(ui::ET_MOUSE_RELEASED));
EXPECT_EQ(1, view->GetEventCount(ui::ET_MOUSE_DRAGGED));
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 defined(OS_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() {
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_;
DISALLOW_COPY_AND_ASSIGN(SubclassWindowHelper);
};
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(WidgetTest, DISABLED_SysCommandMoveOnNCLButtonDownOnCaptionAndMoveTest) {
Widget widget;
Widget::InitParams params =
CreateParams(Widget::InitParams::TYPE_WINDOW);
params.native_widget =
CreatePlatformDesktopNativeWidgetImpl(params, &widget, nullptr);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget.Init(params);
widget.SetBounds(gfx::Rect(0, 0, 200, 200));
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));
widget.CloseNow();
}
// 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(WidgetTest, DISABLED_DestroyInSysCommandNCLButtonDownOnCaption) {
Widget widget;
Widget::InitParams params =
CreateParams(Widget::InitParams::TYPE_WINDOW);
params.native_widget =
CreatePlatformDesktopNativeWidgetImpl(params, &widget, nullptr);
params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
widget.Init(params);
widget.SetBounds(gfx::Rect(0, 0, 200, 200));
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));
widget.CloseNow();
}
#endif
// Test that SetAlwaysOnTop and IsAlwaysOnTop are consistent.
TEST_F(WidgetTest, AlwaysOnTop) {
WidgetAutoclosePtr widget(CreateTopLevelNativeWidget());
EXPECT_FALSE(widget->IsAlwaysOnTop());
widget->SetAlwaysOnTop(true);
EXPECT_TRUE(widget->IsAlwaysOnTop());
widget->SetAlwaysOnTop(false);
EXPECT_FALSE(widget->IsAlwaysOnTop());
}
namespace {
class ScaleFactorView : public View {
public:
ScaleFactorView() = default;
// 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;
DISALLOW_COPY_AND_ASSIGN(ScaleFactorView);
};
}
// Ensure scale factor changes are propagated from the native Widget.
TEST_F(WidgetTest, OnDeviceScaleFactorChanged) {
// This relies on the NativeWidget being the WindowDelegate, which is not the
// case for aura-mus-client.
if (IsMus())
return;
// Automatically close the widget, but not delete it.
WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
ScaleFactorView* view = new ScaleFactorView;
widget->GetRootView()->AddChildView(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());
}
namespace {
class TestWidgetRemovalsObserver : public WidgetRemovalsObserver {
public:
TestWidgetRemovalsObserver() {}
~TestWidgetRemovalsObserver() override {}
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<View*> removed_views_;
DISALLOW_COPY_AND_ASSIGN(TestWidgetRemovalsObserver);
};
}
// 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()->AddChildView(parent);
View* child = new View();
parent->AddChildView(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()->AddChildView(parent);
View* child = new View();
widget->client_view()->AddChildView(child);
// Reparenting the child shouldn't call the removals observer.
parent->AddChildView(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()->AddChildView(child);
EXPECT_TRUE(removals_observer.DidRemoveView(child));
widget->RemoveRemovalsObserver(&removals_observer);
}
// Test dispatch of ui::ET_MOUSEWHEEL.
TEST_F(WidgetTest, MouseWheelEvent) {
// TODO: test uses GetContext(), which is not applicable to aura-mus.
// http://crbug.com/663809.
if (IsMus())
return;
WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget());
widget->SetBounds(gfx::Rect(0, 0, 600, 600));
EventCountView* event_count_view = new EventCountView();
widget->GetContentsView()->AddChildView(event_count_view);
event_count_view->SetBounds(0, 0, 600, 600);
widget->Show();
ui::test::EventGenerator event_generator(GetContext(),
widget->GetNativeWindow());
event_generator.MoveMouseWheel(1, 1);
EXPECT_EQ(1, event_count_view->GetEventCount(ui::ET_MOUSEWHEEL));
}
class WidgetShadowTest : public WidgetTest {
public:
WidgetShadowTest() { InitControllers(); }
// WidgetTest:
Widget::InitParams CreateParams(Widget::InitParams::Type type) override {
Widget::InitParams params =
WidgetTest::CreateParams(override_type_.value_or(type));
params.shadow_type = Widget::InitParams::SHADOW_TYPE_DROP;
params.shadow_elevation = 10;
params.name = name_;
params.child = force_child_;
return params;
}
protected:
base::Optional<Widget::InitParams::Type> override_type_;
std::string name_;
bool force_child_ = false;
private:
#if !defined(OS_CHROMEOS)
void InitControllers() {}
#else
class TestFocusRules : public wm::BaseFocusRules {
public:
TestFocusRules() = default;
bool SupportsChildActivation(aura::Window* window) const override {
return true;
}
private:
DISALLOW_COPY_AND_ASSIGN(TestFocusRules);
};
void InitControllers() {
// Add bits usually managed by the ash::Shell. Under Mus,
// DesktopNativeWidgetAura provides these in-process instead.
if (IsMus())
return;
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 // OS_CHROMEOS
DISALLOW_COPY_AND_ASSIGN(WidgetShadowTest);
};
// Disabled on Mac: All drop shadows are managed out of process for now.
#if defined(OS_MACOSX) && !defined(USE_AURA)
#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) {
// A desktop window clips to its bounds, so it shouldn't have a shadow.
bool top_level_window_should_have_shadow = false;
#if defined(OS_CHROMEOS)
// In Mus, the shadow should be in the WindowServer process only. In non-mus
// CreateNativeDesktopWidget() creates a non-root window, so it should have
// a shadow.
top_level_window_should_have_shadow = !IsMus();
#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 = CreateNativeDesktopWidget();
name_ = "top_level";
Widget* top_level = CreateNativeDesktopWidget();
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 defined(OS_WIN)
namespace {
// Provides functionality to create a window modal dialog.
class ModalDialogDelegate : public DialogDelegateView {
public:
explicit ModalDialogDelegate(ui::ModalType type) : type_(type) {}
~ModalDialogDelegate() override {}
// WidgetDelegate overrides.
ui::ModalType GetModalType() const override { return type_; }
private:
const ui::ModalType type_;
DISALLOW_COPY_AND_ASSIGN(ModalDialogDelegate);
};
} // namespace
// 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(WidgetTest, WindowModalOwnerDestroyedEnabledTest) {
// top_level_widget owns owner_dialog_widget which owns owned_dialog_widget.
Widget top_level_widget;
Widget owner_dialog_widget;
Widget owned_dialog_widget;
// Create the top level widget.
Widget::InitParams init_params =
CreateParams(Widget::InitParams::TYPE_WINDOW);
init_params.show_state = ui::SHOW_STATE_NORMAL;
gfx::Rect initial_bounds(0, 0, 500, 500);
init_params.bounds = initial_bounds;
init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
init_params.native_widget = CreatePlatformDesktopNativeWidgetImpl(
init_params, &top_level_widget, nullptr);
top_level_widget.Init(init_params);
top_level_widget.Show();
// Create the owner modal dialog.
// owner_dialog_delegate instance will be destroyed when the dialog
// is destroyed.
ModalDialogDelegate* owner_dialog_delegate =
new ModalDialogDelegate(ui::MODAL_TYPE_WINDOW);
init_params = CreateParams(Widget::InitParams::TYPE_WINDOW);
init_params.show_state = ui::SHOW_STATE_NORMAL;
init_params.bounds = gfx::Rect(100, 100, 200, 200);
init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
init_params.delegate = owner_dialog_delegate;
init_params.parent = top_level_widget.GetNativeView();
init_params.native_widget = CreatePlatformDesktopNativeWidgetImpl(
init_params, &owner_dialog_widget, nullptr);
owner_dialog_widget.Init(init_params);
HWND owner_hwnd = HWNDForWidget(&owner_dialog_widget);
owner_dialog_widget.Show();
// Create the owned modal dialog.
// As above, the owned_dialog_instance instance will be destroyed
// when the dialog is destroyed.
ModalDialogDelegate* owned_dialog_delegate =
new ModalDialogDelegate(ui::MODAL_TYPE_WINDOW);
init_params = CreateParams(Widget::InitParams::TYPE_WINDOW);
init_params.show_state = ui::SHOW_STATE_NORMAL;
init_params.bounds = gfx::Rect(150, 150, 250, 250);
init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
init_params.delegate = owned_dialog_delegate;
init_params.parent = owner_dialog_widget.GetNativeView();
init_params.native_widget = CreatePlatformDesktopNativeWidgetImpl(
init_params, &owned_dialog_widget, nullptr);
owned_dialog_widget.Init(init_params);
HWND owned_hwnd = HWNDForWidget(&owned_dialog_widget);
owned_dialog_widget.Show();
HWND top_hwnd = HWNDForWidget(&top_level_widget);
EXPECT_FALSE(!!IsWindowEnabled(owner_hwnd));
EXPECT_FALSE(!!IsWindowEnabled(top_hwnd));
EXPECT_TRUE(!!IsWindowEnabled(owned_hwnd));
owner_dialog_widget.CloseNow();
EXPECT_FALSE(!!IsWindow(owner_hwnd));
EXPECT_FALSE(!!IsWindow(owned_hwnd));
EXPECT_TRUE(!!IsWindowEnabled(top_hwnd));
top_level_widget.CloseNow();
}
#endif // defined(OS_WIN)
#if !defined(OS_CHROMEOS)
namespace {
void InitializeWidgetForOpacity(
Widget& widget,
Widget::InitParams init_params,
const Widget::InitParams::WindowOpacity opacity) {
init_params.opacity = opacity;
init_params.show_state = ui::SHOW_STATE_NORMAL;
init_params.bounds = gfx::Rect(0, 0, 500, 500);
init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
init_params.native_widget =
CreatePlatformDesktopNativeWidgetImpl(init_params, &widget, nullptr);
widget.Init(init_params);
}
class CompositingWidgetTest : public views::test::WidgetTest {
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() override {}
void CheckAllWidgetsForOpacity(
const Widget::InitParams::WindowOpacity opacity) {
for (const auto& widget_type : widget_types_) {
#if defined(OS_MACOSX)
// Tooltips are native on Mac. See BridgedNativeWidgetImpl::Init.
if (widget_type == Widget::InitParams::TYPE_TOOLTIP)
continue;
#elif defined(OS_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
Widget widget;
InitializeWidgetForOpacity(widget, CreateParams(widget_type), opacity);
// Use NativeWidgetAura directly.
if (widget_type == Widget::InitParams::TYPE_WINDOW_FRAMELESS ||
widget_type == Widget::InitParams::TYPE_CONTROL)
continue;
#if defined(OS_MACOSX)
// 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
// INFER_OPACITY: assume it is false.
bool should_be_transparent =
opacity == Widget::InitParams::TRANSLUCENT_WINDOW;
#else
bool should_be_transparent = widget.ShouldWindowContentsBeTransparent();
#endif
EXPECT_EQ(IsNativeWindowTransparent(widget.GetNativeWindow()),
should_be_transparent);
#if defined(USE_X11)
if (HasCompositingManager() &&
(widget_type == Widget::InitParams::TYPE_DRAG ||
widget_type == Widget::InitParams::TYPE_WINDOW)) {
EXPECT_TRUE(widget.IsTranslucentWindowOpacitySupported());
} else {
EXPECT_FALSE(widget.IsTranslucentWindowOpacitySupported());
}
#endif
}
}
protected:
const std::vector<Widget::InitParams::Type> widget_types_;
DISALLOW_COPY_AND_ASSIGN(CompositingWidgetTest);
};
} // namespace
// Test opacity when compositing is enabled.
TEST_F(CompositingWidgetTest, Transparency_DesktopWidgetInferOpacity) {
CheckAllWidgetsForOpacity(Widget::InitParams::INFER_OPACITY);
}
TEST_F(CompositingWidgetTest, Transparency_DesktopWidgetOpaque) {
CheckAllWidgetsForOpacity(Widget::InitParams::OPAQUE_WINDOW);
}
TEST_F(CompositingWidgetTest, Transparency_DesktopWidgetTranslucent) {
CheckAllWidgetsForOpacity(Widget::InitParams::TRANSLUCENT_WINDOW);
}
#endif // !defined(OS_CHROMEOS)
} // namespace test
} // namespace views