blob: e50afdb07d0ee4f79975b3770c1d682ce3804b1f [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/ui/ws/event_dispatcher.h"
#include <stddef.h>
#include <stdint.h>
#include <queue>
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "services/ui/common/event_matcher_util.h"
#include "services/ui/ws/accelerator.h"
#include "services/ui/ws/event_dispatcher_delegate.h"
#include "services/ui/ws/server_window.h"
#include "services/ui/ws/server_window_surface_manager_test_api.h"
#include "services/ui/ws/test_server_window_delegate.h"
#include "services/ui/ws/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/events/event.h"
namespace ui {
namespace ws {
namespace test {
namespace {
// Client ids used to indicate the client area and non-client area.
const ClientSpecificId kClientAreaId = 11;
const ClientSpecificId kNonclientAreaId = 111;
// Identifies a generated event.
struct DispatchedEventDetails {
DispatchedEventDetails()
: window(nullptr), client_id(kInvalidClientId), accelerator(nullptr) {}
bool IsNonclientArea() const { return client_id == kNonclientAreaId; }
bool IsClientArea() const { return client_id == kClientAreaId; }
ServerWindow* window;
ClientSpecificId client_id;
std::unique_ptr<ui::Event> event;
Accelerator* accelerator;
};
class TestEventDispatcherDelegate : public EventDispatcherDelegate {
public:
// Delegate interface used by this class to release capture on event
// dispatcher.
class Delegate {
public:
virtual void ReleaseCapture() = 0;
};
explicit TestEventDispatcherDelegate(Delegate* delegate)
: delegate_(delegate),
focused_window_(nullptr),
lost_capture_window_(nullptr),
last_accelerator_(0) {}
~TestEventDispatcherDelegate() override {}
ui::Event* last_event_target_not_found() {
return last_event_target_not_found_.get();
}
uint32_t GetAndClearLastAccelerator() {
uint32_t return_value = last_accelerator_;
last_accelerator_ = 0;
last_accelerator_phase_ = AcceleratorPhase::POST;
return return_value;
}
AcceleratorPhase last_accelerator_phase() const {
return last_accelerator_phase_;
}
void set_root(ServerWindow* root) { root_ = root; }
// Returns the last dispatched event, or null if there are no more.
std::unique_ptr<DispatchedEventDetails>
GetAndAdvanceDispatchedEventDetails() {
if (dispatched_event_queue_.empty())
return nullptr;
std::unique_ptr<DispatchedEventDetails> details =
std::move(dispatched_event_queue_.front());
dispatched_event_queue_.pop();
return details;
}
ServerWindow* GetAndClearLastFocusedWindow() {
ServerWindow* result = focused_window_;
focused_window_ = nullptr;
return result;
}
bool has_queued_events() const { return !dispatched_event_queue_.empty(); }
ServerWindow* lost_capture_window() { return lost_capture_window_; }
// EventDispatcherDelegate:
void SetFocusedWindowFromEventDispatcher(ServerWindow* window) override {
focused_window_ = window;
}
private:
// EventDispatcherDelegate:
void OnAccelerator(uint32_t accelerator,
const ui::Event& event,
AcceleratorPhase phase) override {
EXPECT_EQ(0u, last_accelerator_);
last_accelerator_ = accelerator;
last_accelerator_phase_ = phase;
}
ServerWindow* GetFocusedWindowForEventDispatcher() override {
return focused_window_;
}
void SetNativeCapture(ServerWindow* window) override {}
void ReleaseNativeCapture() override {
if (delegate_)
delegate_->ReleaseCapture();
}
void OnCaptureChanged(ServerWindow* new_capture_window,
ServerWindow* old_capture_window) override {
lost_capture_window_ = old_capture_window;
}
void OnMouseCursorLocationChanged(const gfx::Point& point) override {}
void DispatchInputEventToWindow(ServerWindow* target,
ClientSpecificId client_id,
const ui::Event& event,
Accelerator* accelerator) override {
std::unique_ptr<DispatchedEventDetails> details(new DispatchedEventDetails);
details->window = target;
details->client_id = client_id;
details->event = ui::Event::Clone(event);
details->accelerator = accelerator;
dispatched_event_queue_.push(std::move(details));
}
ClientSpecificId GetEventTargetClientId(const ServerWindow* window,
bool in_nonclient_area) override {
return in_nonclient_area ? kNonclientAreaId : kClientAreaId;
}
ServerWindow* GetRootWindowContaining(gfx::Point* location) override {
return root_;
}
void OnEventTargetNotFound(const ui::Event& event) override {
last_event_target_not_found_ = ui::Event::Clone(event);
}
Delegate* delegate_;
ServerWindow* focused_window_;
ServerWindow* lost_capture_window_;
uint32_t last_accelerator_;
AcceleratorPhase last_accelerator_phase_ = AcceleratorPhase::POST;
std::queue<std::unique_ptr<DispatchedEventDetails>> dispatched_event_queue_;
ServerWindow* root_ = nullptr;
std::unique_ptr<ui::Event> last_event_target_not_found_;
DISALLOW_COPY_AND_ASSIGN(TestEventDispatcherDelegate);
};
// Used by RunMouseEventTests(). Can identify up to two generated events. The
// first ServerWindow and two points identify the first event, the second
// ServerWindow and points identify the second event. If only one event is
// generated set the second window to null.
struct MouseEventTest {
ui::MouseEvent input_event;
ServerWindow* expected_target_window1;
gfx::Point expected_root_location1;
gfx::Point expected_location1;
ServerWindow* expected_target_window2;
gfx::Point expected_root_location2;
gfx::Point expected_location2;
};
// Verifies |details| matches the supplied ServerWindow and points.
void ExpectDispatchedEventDetailsMatches(const DispatchedEventDetails* details,
ServerWindow* target,
const gfx::Point& root_location,
const gfx::Point& location) {
if (!target) {
ASSERT_FALSE(details);
return;
}
ASSERT_EQ(target, details->window);
ASSERT_TRUE(details->event);
ASSERT_TRUE(details->event->IsLocatedEvent());
ASSERT_TRUE(details->IsClientArea());
ASSERT_EQ(root_location, details->event->AsLocatedEvent()->root_location());
ASSERT_EQ(location, details->event->AsLocatedEvent()->location());
}
void RunMouseEventTests(EventDispatcher* dispatcher,
TestEventDispatcherDelegate* dispatcher_delegate,
MouseEventTest* tests,
size_t test_count) {
for (size_t i = 0; i < test_count; ++i) {
const MouseEventTest& test = tests[i];
ASSERT_FALSE(dispatcher_delegate->has_queued_events())
<< " unexpected queued events before running " << i;
dispatcher->ProcessEvent(ui::PointerEvent(test.input_event),
EventDispatcher::AcceleratorMatchPhase::ANY);
std::unique_ptr<DispatchedEventDetails> details =
dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
ASSERT_NO_FATAL_FAILURE(ExpectDispatchedEventDetailsMatches(
details.get(), test.expected_target_window1,
test.expected_root_location1, test.expected_location1))
<< " details don't match " << i;
details = dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
ASSERT_NO_FATAL_FAILURE(ExpectDispatchedEventDetailsMatches(
details.get(), test.expected_target_window2,
test.expected_root_location2, test.expected_location2))
<< " details2 don't match " << i;
ASSERT_FALSE(dispatcher_delegate->has_queued_events())
<< " unexpected queued events after running " << i;
}
}
} // namespace
// Test fixture for EventDispatcher with friend access to verify the internal
// state. Setup creates a TestServerWindowDelegate, a visible root ServerWindow,
// a TestEventDispatcher and the EventDispatcher for testing.
class EventDispatcherTest : public testing::Test,
public TestEventDispatcherDelegate::Delegate {
public:
EventDispatcherTest() {}
~EventDispatcherTest() override {}
ServerWindow* root_window() { return root_window_.get(); }
TestEventDispatcherDelegate* test_event_dispatcher_delegate() {
return test_event_dispatcher_delegate_.get();
}
EventDispatcher* event_dispatcher() { return event_dispatcher_.get(); }
bool AreAnyPointersDown() const;
// Deletes everything created during SetUp()
void ClearSetup();
std::unique_ptr<ServerWindow> CreateChildWindowWithParent(
const WindowId& id,
ServerWindow* parent);
// Creates a window which is a child of |root_window_|.
std::unique_ptr<ServerWindow> CreateChildWindow(const WindowId& id);
bool IsMouseButtonDown() const;
bool IsWindowPointerTarget(const ServerWindow* window) const;
int NumberPointerTargetsForWindow(ServerWindow* window) const;
ServerWindow* GetActiveSystemModalWindow() const;
protected:
// testing::Test:
void SetUp() override;
private:
// TestEventDispatcherDelegate::Delegate:
void ReleaseCapture() override {
event_dispatcher_->SetCaptureWindow(nullptr, kInvalidClientId);
}
std::unique_ptr<TestServerWindowDelegate> window_delegate_;
std::unique_ptr<ServerWindow> root_window_;
std::unique_ptr<TestEventDispatcherDelegate> test_event_dispatcher_delegate_;
std::unique_ptr<EventDispatcher> event_dispatcher_;
DISALLOW_COPY_AND_ASSIGN(EventDispatcherTest);
};
bool EventDispatcherTest::AreAnyPointersDown() const {
return EventDispatcherTestApi(event_dispatcher_.get()).AreAnyPointersDown();
}
void EventDispatcherTest::ClearSetup() {
window_delegate_.reset();
root_window_.reset();
test_event_dispatcher_delegate_.reset();
event_dispatcher_.reset();
}
std::unique_ptr<ServerWindow> EventDispatcherTest::CreateChildWindowWithParent(
const WindowId& id,
ServerWindow* parent) {
std::unique_ptr<ServerWindow> child(
new ServerWindow(window_delegate_.get(), id));
parent->Add(child.get());
child->SetVisible(true);
EnableHitTest(child.get());
return child;
}
std::unique_ptr<ServerWindow> EventDispatcherTest::CreateChildWindow(
const WindowId& id) {
return CreateChildWindowWithParent(id, root_window_.get());
}
bool EventDispatcherTest::IsMouseButtonDown() const {
return EventDispatcherTestApi(event_dispatcher_.get()).is_mouse_button_down();
}
bool EventDispatcherTest::IsWindowPointerTarget(
const ServerWindow* window) const {
return EventDispatcherTestApi(event_dispatcher_.get())
.IsWindowPointerTarget(window);
}
int EventDispatcherTest::NumberPointerTargetsForWindow(
ServerWindow* window) const {
return EventDispatcherTestApi(event_dispatcher_.get())
.NumberPointerTargetsForWindow(window);
}
ServerWindow* EventDispatcherTest::GetActiveSystemModalWindow() const {
ModalWindowController* mwc =
EventDispatcherTestApi(event_dispatcher_.get()).modal_window_controller();
return ModalWindowControllerTestApi(mwc).GetActiveSystemModalWindow();
}
void EventDispatcherTest::SetUp() {
testing::Test::SetUp();
window_delegate_.reset(new TestServerWindowDelegate());
root_window_.reset(new ServerWindow(window_delegate_.get(), WindowId(1, 2)));
window_delegate_->set_root_window(root_window_.get());
root_window_->SetVisible(true);
test_event_dispatcher_delegate_.reset(new TestEventDispatcherDelegate(this));
event_dispatcher_.reset(
new EventDispatcher(test_event_dispatcher_delegate_.get()));
test_event_dispatcher_delegate_->set_root(root_window_.get());
}
TEST_F(EventDispatcherTest, ProcessEvent) {
std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3));
root_window()->SetBounds(gfx::Rect(0, 0, 100, 100));
child->SetBounds(gfx::Rect(10, 10, 20, 20));
// Send event that is over child.
const ui::PointerEvent ui_event(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, gfx::Point(20, 25), gfx::Point(20, 25),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
event_dispatcher()->ProcessEvent(ui_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
std::unique_ptr<DispatchedEventDetails> details =
test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails();
ASSERT_TRUE(details);
ASSERT_EQ(child.get(), details->window);
ASSERT_TRUE(details->event);
ASSERT_TRUE(details->event->IsPointerEvent());
ui::PointerEvent* dispatched_event = details->event->AsPointerEvent();
EXPECT_EQ(gfx::Point(20, 25), dispatched_event->root_location());
EXPECT_EQ(gfx::Point(10, 15), dispatched_event->location());
}
TEST_F(EventDispatcherTest, ProcessEventNoTarget) {
// Send event without a target.
ui::KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_A, ui::EF_NONE);
event_dispatcher()->ProcessEvent(key,
EventDispatcher::AcceleratorMatchPhase::ANY);
// Event wasn't dispatched to a target.
std::unique_ptr<DispatchedEventDetails> details =
test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails();
EXPECT_FALSE(details);
// Delegate was informed that there wasn't a target.
ui::Event* event_out =
test_event_dispatcher_delegate()->last_event_target_not_found();
ASSERT_TRUE(event_out);
EXPECT_TRUE(event_out->IsKeyEvent());
EXPECT_EQ(ui::VKEY_A, event_out->AsKeyEvent()->key_code());
}
TEST_F(EventDispatcherTest, AcceleratorBasic) {
ClearSetup();
TestEventDispatcherDelegate event_dispatcher_delegate(nullptr);
EventDispatcher dispatcher(&event_dispatcher_delegate);
uint32_t accelerator_1 = 1;
mojom::EventMatcherPtr matcher = ui::CreateKeyMatcher(
ui::mojom::KeyboardCode::W, ui::mojom::kEventFlagControlDown);
EXPECT_TRUE(dispatcher.AddAccelerator(accelerator_1, std::move(matcher)));
uint32_t accelerator_2 = 2;
matcher = ui::CreateKeyMatcher(ui::mojom::KeyboardCode::N,
ui::mojom::kEventFlagNone);
EXPECT_TRUE(dispatcher.AddAccelerator(accelerator_2, std::move(matcher)));
// Attempting to add a new accelerator with the same id should fail.
matcher = ui::CreateKeyMatcher(ui::mojom::KeyboardCode::T,
ui::mojom::kEventFlagNone);
EXPECT_FALSE(dispatcher.AddAccelerator(accelerator_2, std::move(matcher)));
// Adding the accelerator with the same id should succeed once the existing
// accelerator is removed.
dispatcher.RemoveAccelerator(accelerator_2);
matcher = ui::CreateKeyMatcher(ui::mojom::KeyboardCode::T,
ui::mojom::kEventFlagNone);
EXPECT_TRUE(dispatcher.AddAccelerator(accelerator_2, std::move(matcher)));
// Attempting to add an accelerator with the same matcher should fail.
uint32_t accelerator_3 = 3;
matcher = ui::CreateKeyMatcher(ui::mojom::KeyboardCode::T,
ui::mojom::kEventFlagNone);
EXPECT_FALSE(dispatcher.AddAccelerator(accelerator_3, std::move(matcher)));
matcher = ui::CreateKeyMatcher(ui::mojom::KeyboardCode::T,
ui::mojom::kEventFlagControlDown);
EXPECT_TRUE(dispatcher.AddAccelerator(accelerator_3, std::move(matcher)));
}
TEST_F(EventDispatcherTest, EventMatching) {
TestEventDispatcherDelegate* event_dispatcher_delegate =
test_event_dispatcher_delegate();
EventDispatcher* dispatcher = event_dispatcher();
mojom::EventMatcherPtr matcher = ui::CreateKeyMatcher(
ui::mojom::KeyboardCode::W, ui::mojom::kEventFlagControlDown);
uint32_t accelerator_1 = 1;
dispatcher->AddAccelerator(accelerator_1, std::move(matcher));
ui::KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_W, ui::EF_CONTROL_DOWN);
dispatcher->ProcessEvent(key, EventDispatcher::AcceleratorMatchPhase::ANY);
EXPECT_EQ(accelerator_1,
event_dispatcher_delegate->GetAndClearLastAccelerator());
// EF_NUM_LOCK_ON should be ignored since CreateKeyMatcher defaults to
// ignoring.
key = ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_W,
ui::EF_CONTROL_DOWN | ui::EF_NUM_LOCK_ON);
dispatcher->ProcessEvent(key, EventDispatcher::AcceleratorMatchPhase::ANY);
EXPECT_EQ(accelerator_1,
event_dispatcher_delegate->GetAndClearLastAccelerator());
key = ui::KeyEvent(ui::ET_KEY_PRESSED, ui::VKEY_W, ui::EF_NONE);
dispatcher->ProcessEvent(key, EventDispatcher::AcceleratorMatchPhase::ANY);
EXPECT_EQ(0u, event_dispatcher_delegate->GetAndClearLastAccelerator());
uint32_t accelerator_2 = 2;
matcher = ui::CreateKeyMatcher(ui::mojom::KeyboardCode::W,
ui::mojom::kEventFlagNone);
dispatcher->AddAccelerator(accelerator_2, std::move(matcher));
dispatcher->ProcessEvent(key, EventDispatcher::AcceleratorMatchPhase::ANY);
EXPECT_EQ(accelerator_2,
event_dispatcher_delegate->GetAndClearLastAccelerator());
dispatcher->RemoveAccelerator(accelerator_2);
dispatcher->ProcessEvent(key, EventDispatcher::AcceleratorMatchPhase::ANY);
EXPECT_EQ(0u, event_dispatcher_delegate->GetAndClearLastAccelerator());
}
// Tests that a post-target accelerator is not triggered by ProcessEvent.
TEST_F(EventDispatcherTest, PostTargetAccelerator) {
TestEventDispatcherDelegate* event_dispatcher_delegate =
test_event_dispatcher_delegate();
EventDispatcher* dispatcher = event_dispatcher();
mojom::EventMatcherPtr matcher = ui::CreateKeyMatcher(
ui::mojom::KeyboardCode::W, ui::mojom::kEventFlagControlDown);
matcher->accelerator_phase = ui::mojom::AcceleratorPhase::POST_TARGET;
uint32_t accelerator_1 = 1;
dispatcher->AddAccelerator(accelerator_1, std::move(matcher));
ui::KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_W, ui::EF_CONTROL_DOWN);
// The post-target accelerator should be fired if there is no focused window.
dispatcher->ProcessEvent(key, EventDispatcher::AcceleratorMatchPhase::ANY);
EXPECT_EQ(accelerator_1,
event_dispatcher_delegate->GetAndClearLastAccelerator());
std::unique_ptr<DispatchedEventDetails> details =
event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_FALSE(details);
// Set focused window for EventDispatcher dispatches key events.
std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3));
event_dispatcher_delegate->SetFocusedWindowFromEventDispatcher(child.get());
// With a focused window the event should be dispatched.
dispatcher->ProcessEvent(key, EventDispatcher::AcceleratorMatchPhase::ANY);
EXPECT_EQ(0u, event_dispatcher_delegate->GetAndClearLastAccelerator());
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_TRUE(details);
EXPECT_TRUE(details->accelerator);
base::WeakPtr<Accelerator> accelerator_weak_ptr =
details->accelerator->GetWeakPtr();
dispatcher->RemoveAccelerator(accelerator_1);
EXPECT_FALSE(accelerator_weak_ptr);
// Post deletion there should be no accelerator
dispatcher->ProcessEvent(key, EventDispatcher::AcceleratorMatchPhase::ANY);
EXPECT_EQ(0u, event_dispatcher_delegate->GetAndClearLastAccelerator());
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_TRUE(details);
EXPECT_FALSE(details->accelerator);
}
TEST_F(EventDispatcherTest, ProcessPost) {
TestEventDispatcherDelegate* event_dispatcher_delegate =
test_event_dispatcher_delegate();
EventDispatcher* dispatcher = event_dispatcher();
uint32_t pre_id = 1;
{
mojom::EventMatcherPtr matcher = ui::CreateKeyMatcher(
ui::mojom::KeyboardCode::W, ui::mojom::kEventFlagControlDown);
matcher->accelerator_phase = ui::mojom::AcceleratorPhase::PRE_TARGET;
dispatcher->AddAccelerator(pre_id, std::move(matcher));
}
uint32_t post_id = 2;
{
mojom::EventMatcherPtr matcher = ui::CreateKeyMatcher(
ui::mojom::KeyboardCode::W, ui::mojom::kEventFlagControlDown);
matcher->accelerator_phase = ui::mojom::AcceleratorPhase::POST_TARGET;
dispatcher->AddAccelerator(post_id, std::move(matcher));
}
// Set focused window for EventDispatcher dispatches key events.
std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3));
event_dispatcher_delegate->SetFocusedWindowFromEventDispatcher(child.get());
// Dispatch for ANY, which should trigger PRE and not call
// DispatchInputEventToWindow().
ui::KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_W, ui::EF_CONTROL_DOWN);
dispatcher->ProcessEvent(key, EventDispatcher::AcceleratorMatchPhase::ANY);
EXPECT_EQ(EventDispatcherDelegate::AcceleratorPhase::PRE,
event_dispatcher_delegate->last_accelerator_phase());
EXPECT_EQ(pre_id, event_dispatcher_delegate->GetAndClearLastAccelerator());
EXPECT_FALSE(event_dispatcher_delegate->has_queued_events());
// Dispatch for POST, which should trigger POST.
dispatcher->ProcessEvent(key,
EventDispatcher::AcceleratorMatchPhase::POST_ONLY);
std::unique_ptr<DispatchedEventDetails> details =
event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
ASSERT_TRUE(details);
ASSERT_TRUE(details->accelerator);
EXPECT_EQ(post_id, details->accelerator->id());
}
TEST_F(EventDispatcherTest, Capture) {
ServerWindow* root = root_window();
std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3));
root->SetBounds(gfx::Rect(0, 0, 100, 100));
child->SetBounds(gfx::Rect(10, 10, 20, 20));
MouseEventTest tests[] = {
// Send a mouse down event over child.
{ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(20, 25),
gfx::Point(20, 25), base::TimeTicks(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON),
child.get(), gfx::Point(20, 25), gfx::Point(10, 15), nullptr,
gfx::Point(), gfx::Point()},
// Capture should be activated. Let's send a mouse move outside the bounds
// of the child.
{ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(50, 50),
gfx::Point(50, 50), base::TimeTicks(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON),
child.get(), gfx::Point(50, 50), gfx::Point(40, 40), nullptr,
gfx::Point(), gfx::Point()},
// Release the mouse and verify that the mouse up event goes to the child.
{ui::MouseEvent(ui::ET_MOUSE_RELEASED, gfx::Point(50, 50),
gfx::Point(50, 50), base::TimeTicks(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON),
child.get(), gfx::Point(50, 50), gfx::Point(40, 40), nullptr,
gfx::Point(), gfx::Point()},
// A mouse move at (50, 50) should now go to the root window. As the
// move crosses between |child| and |root| |child| gets an exit, and
// |root| the move.
{ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(50, 50),
gfx::Point(50, 50), base::TimeTicks(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON),
child.get(), gfx::Point(50, 50), gfx::Point(40, 40), root,
gfx::Point(50, 50), gfx::Point(50, 50)},
};
RunMouseEventTests(event_dispatcher(), test_event_dispatcher_delegate(),
tests, arraysize(tests));
}
TEST_F(EventDispatcherTest, CaptureMultipleMouseButtons) {
std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3));
root_window()->SetBounds(gfx::Rect(0, 0, 100, 100));
child->SetBounds(gfx::Rect(10, 10, 20, 20));
MouseEventTest tests[] = {
// Send a mouse down event over child with a left mouse button
{ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(20, 25),
gfx::Point(20, 25), base::TimeTicks(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON),
child.get(), gfx::Point(20, 25), gfx::Point(10, 15), nullptr,
gfx::Point(), gfx::Point()},
// Capture should be activated. Let's send a mouse move outside the bounds
// of the child and press the right mouse button too.
{ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(50, 50),
gfx::Point(50, 50), base::TimeTicks(),
ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON, 0),
child.get(), gfx::Point(50, 50), gfx::Point(40, 40), nullptr,
gfx::Point(), gfx::Point()},
// Release the left mouse button and verify that the mouse up event goes
// to the child.
{ui::MouseEvent(ui::ET_MOUSE_RELEASED, gfx::Point(50, 50),
gfx::Point(50, 50), base::TimeTicks(),
ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON,
ui::EF_RIGHT_MOUSE_BUTTON),
child.get(), gfx::Point(50, 50), gfx::Point(40, 40), nullptr,
gfx::Point(), gfx::Point()},
// A mouse move at (50, 50) should still go to the child.
{ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(50, 50),
gfx::Point(50, 50), base::TimeTicks(),
ui::EF_LEFT_MOUSE_BUTTON, 0),
child.get(), gfx::Point(50, 50), gfx::Point(40, 40), nullptr,
gfx::Point(), gfx::Point()},
};
RunMouseEventTests(event_dispatcher(), test_event_dispatcher_delegate(),
tests, arraysize(tests));
}
TEST_F(EventDispatcherTest, ClientAreaGoesToOwner) {
std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3));
root_window()->SetBounds(gfx::Rect(0, 0, 100, 100));
child->SetBounds(gfx::Rect(10, 10, 20, 20));
child->SetClientArea(gfx::Insets(5, 5, 5, 5), std::vector<gfx::Rect>());
TestEventDispatcherDelegate* event_dispatcher_delegate =
test_event_dispatcher_delegate();
EventDispatcher* dispatcher = event_dispatcher();
// Start move loop by sending mouse event over non-client area.
const ui::PointerEvent press_event(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, gfx::Point(12, 12), gfx::Point(12, 12),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
dispatcher->ProcessEvent(press_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
// Events should target child and be in the non-client area.
std::unique_ptr<DispatchedEventDetails> details =
event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_FALSE(event_dispatcher_delegate->has_queued_events());
ASSERT_TRUE(details);
ASSERT_EQ(child.get(), details->window);
EXPECT_TRUE(details->IsNonclientArea());
// Move the mouse 5,6 pixels and target is the same.
const ui::PointerEvent move_event(
ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(17, 18), gfx::Point(17, 18),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, 0));
dispatcher->ProcessEvent(move_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
// Still same target.
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_FALSE(event_dispatcher_delegate->has_queued_events());
ASSERT_EQ(child.get(), details->window);
EXPECT_TRUE(details->IsNonclientArea());
// Release the mouse.
const ui::PointerEvent release_event(ui::MouseEvent(
ui::ET_MOUSE_RELEASED, gfx::Point(17, 18), gfx::Point(17, 18),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
dispatcher->ProcessEvent(release_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
// The event should not have been dispatched to the delegate.
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_FALSE(event_dispatcher_delegate->has_queued_events());
ASSERT_EQ(child.get(), details->window);
EXPECT_TRUE(details->IsNonclientArea());
// Press in the client area and verify target/client area. The non-client area
// should get an exit first.
const ui::PointerEvent press_event2(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, gfx::Point(21, 22), gfx::Point(21, 22),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
dispatcher->ProcessEvent(press_event2,
EventDispatcher::AcceleratorMatchPhase::ANY);
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_TRUE(event_dispatcher_delegate->has_queued_events());
ASSERT_EQ(child.get(), details->window);
EXPECT_TRUE(details->IsNonclientArea());
EXPECT_EQ(ui::ET_POINTER_EXITED, details->event->type());
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_FALSE(event_dispatcher_delegate->has_queued_events());
ASSERT_EQ(child.get(), details->window);
EXPECT_TRUE(details->IsClientArea());
EXPECT_EQ(ui::ET_POINTER_DOWN, details->event->type());
}
TEST_F(EventDispatcherTest, AdditionalClientArea) {
std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3));
root_window()->SetBounds(gfx::Rect(0, 0, 100, 100));
child->SetBounds(gfx::Rect(10, 10, 20, 20));
std::vector<gfx::Rect> additional_client_areas;
additional_client_areas.push_back(gfx::Rect(18, 0, 2, 2));
child->SetClientArea(gfx::Insets(5, 5, 5, 5), additional_client_areas);
TestEventDispatcherDelegate* event_dispatcher_delegate =
test_event_dispatcher_delegate();
// Press in the additional client area, it should go to the child.
const ui::PointerEvent press_event(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, gfx::Point(28, 11), gfx::Point(28, 11),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
event_dispatcher()->ProcessEvent(press_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
// Events should target child and be in the client area.
std::unique_ptr<DispatchedEventDetails> details =
event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_FALSE(event_dispatcher_delegate->has_queued_events());
ASSERT_EQ(child.get(), details->window);
EXPECT_TRUE(details->IsClientArea());
}
TEST_F(EventDispatcherTest, HitTestMask) {
std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3));
root_window()->SetBounds(gfx::Rect(0, 0, 100, 100));
child->SetBounds(gfx::Rect(10, 10, 20, 20));
child->SetHitTestMask(gfx::Rect(2, 2, 16, 16));
// Move in the masked area.
const ui::PointerEvent move1(ui::MouseEvent(
ui::ET_MOUSE_MOVED, gfx::Point(11, 11), gfx::Point(11, 11),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, 0));
event_dispatcher()->ProcessEvent(move1,
EventDispatcher::AcceleratorMatchPhase::ANY);
// Event went through the child window and hit the root.
std::unique_ptr<DispatchedEventDetails> details1 =
test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails();
EXPECT_EQ(root_window(), details1->window);
EXPECT_TRUE(details1->IsClientArea());
child->ClearHitTestMask();
// Move right in the same part of the window.
const ui::PointerEvent move2(ui::MouseEvent(
ui::ET_MOUSE_MOVED, gfx::Point(11, 12), gfx::Point(11, 12),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, 0));
event_dispatcher()->ProcessEvent(move2,
EventDispatcher::AcceleratorMatchPhase::ANY);
// Mouse exits the root.
std::unique_ptr<DispatchedEventDetails> details2 =
test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails();
EXPECT_EQ(ui::ET_POINTER_EXITED, details2->event->type());
// Mouse hits the child.
std::unique_ptr<DispatchedEventDetails> details3 =
test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails();
EXPECT_EQ(child.get(), details3->window);
EXPECT_TRUE(details3->IsClientArea());
}
TEST_F(EventDispatcherTest, DontFocusOnSecondDown) {
std::unique_ptr<ServerWindow> child1 = CreateChildWindow(WindowId(1, 3));
std::unique_ptr<ServerWindow> child2 = CreateChildWindow(WindowId(1, 4));
root_window()->SetBounds(gfx::Rect(0, 0, 100, 100));
child1->SetBounds(gfx::Rect(10, 10, 20, 20));
child2->SetBounds(gfx::Rect(50, 51, 11, 12));
TestEventDispatcherDelegate* event_dispatcher_delegate =
test_event_dispatcher_delegate();
EventDispatcher* dispatcher = event_dispatcher();
// Press on child1. First press event should change focus.
const ui::PointerEvent press_event(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, gfx::Point(12, 12), gfx::Point(12, 12),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
dispatcher->ProcessEvent(press_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
std::unique_ptr<DispatchedEventDetails> details =
event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_FALSE(event_dispatcher_delegate->has_queued_events());
EXPECT_EQ(child1.get(), details->window);
EXPECT_EQ(child1.get(),
event_dispatcher_delegate->GetAndClearLastFocusedWindow());
// Press (with a different pointer id) on child2. Event should go to child2,
// but focus should not change.
const ui::PointerEvent touch_event(ui::TouchEvent(
ui::ET_TOUCH_PRESSED, gfx::Point(53, 54), 2, base::TimeTicks()));
dispatcher->ProcessEvent(touch_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_FALSE(event_dispatcher_delegate->has_queued_events());
EXPECT_EQ(child2.get(), details->window);
EXPECT_EQ(nullptr, event_dispatcher_delegate->GetAndClearLastFocusedWindow());
}
TEST_F(EventDispatcherTest, TwoPointersActive) {
std::unique_ptr<ServerWindow> child1 = CreateChildWindow(WindowId(1, 3));
std::unique_ptr<ServerWindow> child2 = CreateChildWindow(WindowId(1, 4));
root_window()->SetBounds(gfx::Rect(0, 0, 100, 100));
child1->SetBounds(gfx::Rect(10, 10, 20, 20));
child2->SetBounds(gfx::Rect(50, 51, 11, 12));
TestEventDispatcherDelegate* event_dispatcher_delegate =
test_event_dispatcher_delegate();
EventDispatcher* dispatcher = event_dispatcher();
// Press on child1.
const ui::PointerEvent touch_event1(ui::TouchEvent(
ui::ET_TOUCH_PRESSED, gfx::Point(12, 13), 1, base::TimeTicks()));
dispatcher->ProcessEvent(touch_event1,
EventDispatcher::AcceleratorMatchPhase::ANY);
std::unique_ptr<DispatchedEventDetails> details =
event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_EQ(child1.get(), details->window);
// Drag over child2, child1 should get the drag.
const ui::PointerEvent drag_event1(ui::TouchEvent(
ui::ET_TOUCH_MOVED, gfx::Point(53, 54), 1, base::TimeTicks()));
dispatcher->ProcessEvent(drag_event1,
EventDispatcher::AcceleratorMatchPhase::ANY);
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_EQ(child1.get(), details->window);
// Press on child2 with a different touch id.
const ui::PointerEvent touch_event2(ui::TouchEvent(
ui::ET_TOUCH_PRESSED, gfx::Point(54, 55), 2, base::TimeTicks()));
dispatcher->ProcessEvent(touch_event2,
EventDispatcher::AcceleratorMatchPhase::ANY);
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_EQ(child2.get(), details->window);
// Drag over child1 with id 2, child2 should continue to get the drag.
const ui::PointerEvent drag_event2(ui::TouchEvent(
ui::ET_TOUCH_MOVED, gfx::Point(13, 14), 2, base::TimeTicks()));
dispatcher->ProcessEvent(drag_event2,
EventDispatcher::AcceleratorMatchPhase::ANY);
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_EQ(child2.get(), details->window);
// Drag again with id 1, child1 should continue to get it.
dispatcher->ProcessEvent(drag_event1,
EventDispatcher::AcceleratorMatchPhase::ANY);
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_EQ(child1.get(), details->window);
// Release touch id 1, and click on 2. 2 should get it.
const ui::PointerEvent touch_release(ui::TouchEvent(
ui::ET_TOUCH_RELEASED, gfx::Point(54, 55), 1, base::TimeTicks()));
dispatcher->ProcessEvent(touch_release,
EventDispatcher::AcceleratorMatchPhase::ANY);
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_EQ(child1.get(), details->window);
const ui::PointerEvent touch_event3(ui::TouchEvent(
ui::ET_TOUCH_PRESSED, gfx::Point(54, 55), 2, base::TimeTicks()));
dispatcher->ProcessEvent(touch_event3,
EventDispatcher::AcceleratorMatchPhase::ANY);
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_EQ(child2.get(), details->window);
}
TEST_F(EventDispatcherTest, DestroyWindowWhileGettingEvents) {
std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3));
root_window()->SetBounds(gfx::Rect(0, 0, 100, 100));
child->SetBounds(gfx::Rect(10, 10, 20, 20));
TestEventDispatcherDelegate* event_dispatcher_delegate =
test_event_dispatcher_delegate();
EventDispatcher* dispatcher = event_dispatcher();
// Press on child.
const ui::PointerEvent touch_event1(ui::TouchEvent(
ui::ET_TOUCH_PRESSED, gfx::Point(12, 13), 1, base::TimeTicks()));
dispatcher->ProcessEvent(touch_event1,
EventDispatcher::AcceleratorMatchPhase::ANY);
std::unique_ptr<DispatchedEventDetails> details =
event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_FALSE(event_dispatcher_delegate->has_queued_events());
EXPECT_EQ(child.get(), details->window);
// Delete child, and continue the drag. Event should not be dispatched.
child.reset();
const ui::PointerEvent drag_event1(ui::TouchEvent(
ui::ET_TOUCH_MOVED, gfx::Point(53, 54), 1, base::TimeTicks()));
dispatcher->ProcessEvent(drag_event1,
EventDispatcher::AcceleratorMatchPhase::ANY);
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_EQ(nullptr, details.get());
}
TEST_F(EventDispatcherTest, MouseInExtendedHitTestRegion) {
ServerWindow* root = root_window();
std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3));
root->SetBounds(gfx::Rect(0, 0, 100, 100));
child->SetBounds(gfx::Rect(10, 10, 20, 20));
TestEventDispatcherDelegate* event_dispatcher_delegate =
test_event_dispatcher_delegate();
EventDispatcher* dispatcher = event_dispatcher();
// Send event that is not over child.
const ui::PointerEvent ui_event(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, gfx::Point(8, 9), gfx::Point(8, 9),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
dispatcher->ProcessEvent(ui_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
std::unique_ptr<DispatchedEventDetails> details =
event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
ASSERT_EQ(root, details->window);
// Release the mouse.
const ui::PointerEvent release_event(ui::MouseEvent(
ui::ET_MOUSE_RELEASED, gfx::Point(8, 9), gfx::Point(8, 9),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
dispatcher->ProcessEvent(release_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_FALSE(event_dispatcher_delegate->has_queued_events());
ASSERT_EQ(root, details->window);
EXPECT_TRUE(details->IsClientArea());
// Change the extended hit test region and send event in extended hit test
// region. Should result in exit for root, followed by press for child.
child->set_extended_hit_test_region(gfx::Insets(5, 5, 5, 5));
dispatcher->ProcessEvent(ui_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_EQ(root, details->window);
EXPECT_EQ(ui::ET_POINTER_EXITED, details->event->type());
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
ASSERT_TRUE(details);
EXPECT_FALSE(event_dispatcher_delegate->has_queued_events());
EXPECT_TRUE(details->IsNonclientArea());
ASSERT_EQ(child.get(), details->window);
EXPECT_EQ(ui::ET_POINTER_DOWN, details->event->type());
ASSERT_TRUE(details->event.get());
ASSERT_TRUE(details->event->IsPointerEvent());
EXPECT_EQ(gfx::Point(-2, -1), details->event->AsPointerEvent()->location());
}
TEST_F(EventDispatcherTest, WheelWhileDown) {
std::unique_ptr<ServerWindow> child1 = CreateChildWindow(WindowId(1, 3));
std::unique_ptr<ServerWindow> child2 = CreateChildWindow(WindowId(1, 4));
root_window()->SetBounds(gfx::Rect(0, 0, 100, 100));
child1->SetBounds(gfx::Rect(10, 10, 20, 20));
child2->SetBounds(gfx::Rect(50, 51, 11, 12));
MouseEventTest tests[] = {
// Send a mouse down event over child1.
{ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(15, 15),
gfx::Point(15, 15), base::TimeTicks(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON),
child1.get(), gfx::Point(15, 15), gfx::Point(5, 5), nullptr,
gfx::Point(), gfx::Point()},
// Send mouse wheel over child2, should go to child1 as it has capture.
{ui::MouseWheelEvent(gfx::Vector2d(1, 0), gfx::Point(53, 54),
gfx::Point(53, 54), base::TimeTicks(), ui::EF_NONE,
ui::EF_NONE),
child1.get(), gfx::Point(53, 54), gfx::Point(43, 44), nullptr,
gfx::Point(), gfx::Point()},
};
RunMouseEventTests(event_dispatcher(), test_event_dispatcher_delegate(),
tests, arraysize(tests));
}
// Tests that when explicit capture has been set that all events go to the
// designated window, and that when capture is cleared, events find the
// appropriate target window.
TEST_F(EventDispatcherTest, SetExplicitCapture) {
ServerWindow* root = root_window();
std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3));
root->SetBounds(gfx::Rect(0, 0, 100, 100));
child->SetBounds(gfx::Rect(10, 10, 20, 20));
TestEventDispatcherDelegate* event_dispatcher_delegate =
test_event_dispatcher_delegate();
EventDispatcher* dispatcher = event_dispatcher();
{
// Send all pointer events to the child.
dispatcher->SetCaptureWindow(child.get(), kClientAreaId);
// The mouse press should go to the child even though its outside its
// bounds.
const ui::PointerEvent left_press_event(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, gfx::Point(5, 5), gfx::Point(5, 5),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
dispatcher->ProcessEvent(left_press_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
// Events should target child.
std::unique_ptr<DispatchedEventDetails> details =
event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
ASSERT_TRUE(details);
ASSERT_EQ(child.get(), details->window);
EXPECT_TRUE(details->IsClientArea());
EXPECT_TRUE(IsMouseButtonDown());
// The mouse down state should update while capture is set.
const ui::PointerEvent right_press_event(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, gfx::Point(5, 5), gfx::Point(5, 5),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON,
ui::EF_RIGHT_MOUSE_BUTTON));
dispatcher->ProcessEvent(right_press_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_TRUE(IsMouseButtonDown());
// One button released should not clear mouse down
const ui::PointerEvent left_release_event(ui::MouseEvent(
ui::ET_MOUSE_RELEASED, gfx::Point(5, 5), gfx::Point(5, 5),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON,
ui::EF_LEFT_MOUSE_BUTTON));
dispatcher->ProcessEvent(left_release_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_TRUE(IsMouseButtonDown());
// Touch Event while mouse is down should not affect state.
const ui::PointerEvent touch_event(ui::TouchEvent(
ui::ET_TOUCH_PRESSED, gfx::Point(15, 15), 2, base::TimeTicks()));
dispatcher->ProcessEvent(touch_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_TRUE(IsMouseButtonDown());
// Move event should not affect down
const ui::PointerEvent move_event(
ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(15, 5), gfx::Point(15, 5),
base::TimeTicks(), ui::EF_RIGHT_MOUSE_BUTTON,
ui::EF_RIGHT_MOUSE_BUTTON));
dispatcher->ProcessEvent(move_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_TRUE(IsMouseButtonDown());
// All mouse buttons up should clear mouse down.
const ui::PointerEvent right_release_event(
ui::MouseEvent(ui::ET_MOUSE_RELEASED, gfx::Point(5, 5),
gfx::Point(5, 5), base::TimeTicks(),
ui::EF_RIGHT_MOUSE_BUTTON, ui::EF_RIGHT_MOUSE_BUTTON));
dispatcher->ProcessEvent(right_release_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_FALSE(IsMouseButtonDown());
}
{
// Releasing capture and sending the same event will go to the root.
dispatcher->SetCaptureWindow(nullptr, kClientAreaId);
const ui::PointerEvent press_event(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, gfx::Point(5, 5), gfx::Point(5, 5),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
dispatcher->ProcessEvent(press_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
// Events should target the root.
std::unique_ptr<DispatchedEventDetails> details =
event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
ASSERT_TRUE(details);
ASSERT_EQ(root, details->window);
}
}
// This test verifies that explicit capture overrides and resets implicit
// capture.
TEST_F(EventDispatcherTest, ExplicitCaptureOverridesImplicitCapture) {
ServerWindow* root = root_window();
std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3));
root->SetBounds(gfx::Rect(0, 0, 100, 100));
child->SetBounds(gfx::Rect(10, 10, 20, 20));
TestEventDispatcherDelegate* event_dispatcher_delegate =
test_event_dispatcher_delegate();
EventDispatcher* dispatcher = event_dispatcher();
// Run some implicit capture tests.
MouseEventTest tests[] = {
// Send a mouse down event over child with a left mouse button
{ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(20, 25),
gfx::Point(20, 25), base::TimeTicks(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON),
child.get(), gfx::Point(20, 25), gfx::Point(10, 15)},
// Capture should be activated. Let's send a mouse move outside the bounds
// of the child and press the right mouse button too.
{ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(50, 50),
gfx::Point(50, 50), base::TimeTicks(),
ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON, 0),
child.get(), gfx::Point(50, 50), gfx::Point(40, 40)},
// Release the left mouse button and verify that the mouse up event goes
// to the child.
{ui::MouseEvent(ui::ET_MOUSE_RELEASED, gfx::Point(50, 50),
gfx::Point(50, 50), base::TimeTicks(),
ui::EF_LEFT_MOUSE_BUTTON | ui::EF_RIGHT_MOUSE_BUTTON,
ui::EF_RIGHT_MOUSE_BUTTON),
child.get(), gfx::Point(50, 50), gfx::Point(40, 40)},
// A mouse move at (50, 50) should still go to the child.
{ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(50, 50),
gfx::Point(50, 50), base::TimeTicks(),
ui::EF_LEFT_MOUSE_BUTTON, 0),
child.get(), gfx::Point(50, 50), gfx::Point(40, 40)},
};
RunMouseEventTests(dispatcher, event_dispatcher_delegate, tests,
arraysize(tests));
// Add a second pointer target to the child.
{
const ui::PointerEvent touch_event(ui::TouchEvent(
ui::ET_TOUCH_PRESSED, gfx::Point(12, 13), 1, base::TimeTicks()));
dispatcher->ProcessEvent(touch_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
}
std::unique_ptr<DispatchedEventDetails> details =
event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_FALSE(event_dispatcher_delegate->has_queued_events());
EXPECT_EQ(child.get(), details->window);
// Verify that no window has explicit capture and hence we did indeed do
// implicit capture.
ASSERT_EQ(nullptr, dispatcher->capture_window());
// Give the root window explicit capture and verify input events over the
// child go to the root instead.
dispatcher->SetCaptureWindow(root, kNonclientAreaId);
// The implicit target should receive a cancel event for each pointer target.
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
ASSERT_TRUE(details);
EXPECT_TRUE(event_dispatcher_delegate->has_queued_events());
EXPECT_EQ(child.get(), details->window);
EXPECT_EQ(ui::ET_POINTER_CANCELLED, details->event->type());
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
ASSERT_TRUE(details);
EXPECT_FALSE(event_dispatcher_delegate->has_queued_events());
EXPECT_EQ(child.get(), details->window);
EXPECT_EQ(ui::ET_POINTER_EXITED, details->event->type());
const ui::PointerEvent press_event(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, gfx::Point(15, 15), gfx::Point(15, 15),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
dispatcher->ProcessEvent(press_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
// Events should target the root.
details = event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
ASSERT_TRUE(details);
ASSERT_EQ(root, details->window);
ASSERT_TRUE(details->IsNonclientArea());
}
// Tests that setting capture does delete active pointer targets for the capture
// window.
TEST_F(EventDispatcherTest, CaptureUpdatesActivePointerTargets) {
ServerWindow* root = root_window();
root->SetBounds(gfx::Rect(0, 0, 100, 100));
EventDispatcher* dispatcher = event_dispatcher();
{
const ui::PointerEvent press_event(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, gfx::Point(5, 5), gfx::Point(5, 5),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
dispatcher->ProcessEvent(press_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
std::unique_ptr<DispatchedEventDetails> details =
test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails();
ASSERT_TRUE(details);
ASSERT_EQ(root, details->window);
}
{
const ui::PointerEvent touch_event(ui::TouchEvent(
ui::ET_TOUCH_PRESSED, gfx::Point(12, 13), 1, base::TimeTicks()));
dispatcher->ProcessEvent(touch_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
}
ASSERT_TRUE(AreAnyPointersDown());
ASSERT_TRUE(IsWindowPointerTarget(root));
EXPECT_EQ(2, NumberPointerTargetsForWindow(root));
// Setting the capture should clear the implicit pointers for the specified
// window.
dispatcher->SetCaptureWindow(root, kNonclientAreaId);
EXPECT_FALSE(AreAnyPointersDown());
EXPECT_FALSE(IsWindowPointerTarget(root));
}
// Tests that when explicit capture is changed, that the previous window with
// capture is no longer being observed.
TEST_F(EventDispatcherTest, UpdatingCaptureStopsObservingPreviousCapture) {
std::unique_ptr<ServerWindow> child1 = CreateChildWindow(WindowId(1, 3));
std::unique_ptr<ServerWindow> child2 = CreateChildWindow(WindowId(1, 4));
root_window()->SetBounds(gfx::Rect(0, 0, 100, 100));
child1->SetBounds(gfx::Rect(10, 10, 20, 20));
child2->SetBounds(gfx::Rect(50, 51, 11, 12));
EventDispatcher* dispatcher = event_dispatcher();
ASSERT_FALSE(AreAnyPointersDown());
ASSERT_FALSE(IsWindowPointerTarget(child1.get()));
ASSERT_FALSE(IsWindowPointerTarget(child2.get()));
dispatcher->SetCaptureWindow(child1.get(), kClientAreaId);
dispatcher->SetCaptureWindow(child2.get(), kClientAreaId);
EXPECT_EQ(child1.get(),
test_event_dispatcher_delegate()->lost_capture_window());
// If observing does not stop during the capture update this crashes.
child1->AddObserver(dispatcher);
}
// Tests that destroying a window with explicit capture clears the capture
// state.
TEST_F(EventDispatcherTest, DestroyingCaptureWindowRemovesExplicitCapture) {
std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3));
child->SetBounds(gfx::Rect(10, 10, 20, 20));
EventDispatcher* dispatcher = event_dispatcher();
dispatcher->SetCaptureWindow(child.get(), kClientAreaId);
EXPECT_EQ(child.get(), dispatcher->capture_window());
ServerWindow* lost_capture_window = child.get();
child.reset();
EXPECT_EQ(nullptr, dispatcher->capture_window());
EXPECT_EQ(lost_capture_window,
test_event_dispatcher_delegate()->lost_capture_window());
}
// Tests that when |client_id| is set for a window performing capture, that this
// preference is used regardless of whether an event targets the client region.
TEST_F(EventDispatcherTest, CaptureInNonClientAreaOverridesActualPoint) {
ServerWindow* root = root_window();
root->SetBounds(gfx::Rect(0, 0, 100, 100));
root->SetClientArea(gfx::Insets(5, 5, 5, 5), std::vector<gfx::Rect>());
EventDispatcher* dispatcher = event_dispatcher();
dispatcher->SetCaptureWindow(root, kNonclientAreaId);
TestEventDispatcherDelegate* event_dispatcher_delegate =
test_event_dispatcher_delegate();
// Press in the client area, it should be marked as non client.
const ui::PointerEvent press_event(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, gfx::Point(6, 6), gfx::Point(6, 6),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
event_dispatcher()->ProcessEvent(press_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
// Events should target child and be in the client area.
std::unique_ptr<DispatchedEventDetails> details =
event_dispatcher_delegate->GetAndAdvanceDispatchedEventDetails();
EXPECT_FALSE(event_dispatcher_delegate->has_queued_events());
ASSERT_EQ(root, details->window);
EXPECT_TRUE(details->IsNonclientArea());
}
TEST_F(EventDispatcherTest, ProcessPointerEvents) {
std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3));
root_window()->SetBounds(gfx::Rect(0, 0, 100, 100));
child->SetBounds(gfx::Rect(10, 10, 20, 20));
{
const ui::PointerEvent pointer_event(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, gfx::Point(20, 25), gfx::Point(20, 25),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
event_dispatcher()->ProcessEvent(
pointer_event, EventDispatcher::AcceleratorMatchPhase::ANY);
std::unique_ptr<DispatchedEventDetails> details =
test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails();
ASSERT_TRUE(details);
ASSERT_EQ(child.get(), details->window);
ASSERT_TRUE(details->event);
ASSERT_TRUE(details->event->IsPointerEvent());
ui::PointerEvent* dispatched_event = details->event->AsPointerEvent();
EXPECT_EQ(gfx::Point(20, 25), dispatched_event->root_location());
EXPECT_EQ(gfx::Point(10, 15), dispatched_event->location());
}
{
const int touch_id = 3;
const ui::PointerEvent pointer_event(
ui::TouchEvent(ui::ET_TOUCH_RELEASED, gfx::Point(25, 20), touch_id,
base::TimeTicks()));
event_dispatcher()->ProcessEvent(
pointer_event, EventDispatcher::AcceleratorMatchPhase::ANY);
std::unique_ptr<DispatchedEventDetails> details =
test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails();
ASSERT_TRUE(details);
ASSERT_EQ(child.get(), details->window);
ASSERT_TRUE(details->event);
ASSERT_TRUE(details->event->IsPointerEvent());
ui::PointerEvent* dispatched_event = details->event->AsPointerEvent();
EXPECT_EQ(gfx::Point(25, 20), dispatched_event->root_location());
EXPECT_EQ(gfx::Point(15, 10), dispatched_event->location());
EXPECT_EQ(touch_id, dispatched_event->pointer_id());
}
}
TEST_F(EventDispatcherTest, ResetClearsPointerDown) {
std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3));
root_window()->SetBounds(gfx::Rect(0, 0, 100, 100));
child->SetBounds(gfx::Rect(10, 10, 20, 20));
// Send event that is over child.
const ui::PointerEvent ui_event(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, gfx::Point(20, 25), gfx::Point(20, 25),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
event_dispatcher()->ProcessEvent(ui_event,
EventDispatcher::AcceleratorMatchPhase::ANY);
std::unique_ptr<DispatchedEventDetails> details =
test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails();
ASSERT_TRUE(details);
ASSERT_EQ(child.get(), details->window);
EXPECT_TRUE(AreAnyPointersDown());
event_dispatcher()->Reset();
EXPECT_FALSE(test_event_dispatcher_delegate()->has_queued_events());
EXPECT_FALSE(AreAnyPointersDown());
}
TEST_F(EventDispatcherTest, ResetClearsCapture) {
ServerWindow* root = root_window();
root->SetBounds(gfx::Rect(0, 0, 100, 100));
root->SetClientArea(gfx::Insets(5, 5, 5, 5), std::vector<gfx::Rect>());
EventDispatcher* dispatcher = event_dispatcher();
dispatcher->SetCaptureWindow(root, kNonclientAreaId);
event_dispatcher()->Reset();
EXPECT_FALSE(test_event_dispatcher_delegate()->has_queued_events());
EXPECT_EQ(nullptr, event_dispatcher()->capture_window());
}
// Tests that events on a modal parent target the modal child.
TEST_F(EventDispatcherTest, ModalWindowEventOnModalParent) {
std::unique_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3));
std::unique_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 5));
root_window()->SetBounds(gfx::Rect(0, 0, 100, 100));
w1->SetBounds(gfx::Rect(10, 10, 30, 30));
w2->SetBounds(gfx::Rect(50, 10, 10, 10));
w1->AddTransientWindow(w2.get());
w2->SetModal();
// Send event that is over |w1|.
const ui::PointerEvent mouse_pressed(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, gfx::Point(15, 15), gfx::Point(15, 15),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
event_dispatcher()->ProcessEvent(mouse_pressed,
EventDispatcher::AcceleratorMatchPhase::ANY);
std::unique_ptr<DispatchedEventDetails> details =
test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails();
ASSERT_TRUE(details);
EXPECT_EQ(w2.get(), details->window);
EXPECT_TRUE(details->IsNonclientArea());
ASSERT_TRUE(details->event);
ASSERT_TRUE(details->event->IsPointerEvent());
ui::PointerEvent* dispatched_event = details->event->AsPointerEvent();
EXPECT_EQ(gfx::Point(15, 15), dispatched_event->root_location());
EXPECT_EQ(gfx::Point(-35, 5), dispatched_event->location());
}
// Tests that events on a modal child target the modal child itself.
TEST_F(EventDispatcherTest, ModalWindowEventOnModalChild) {
std::unique_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3));
std::unique_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 5));
root_window()->SetBounds(gfx::Rect(0, 0, 100, 100));
w1->SetBounds(gfx::Rect(10, 10, 30, 30));
w2->SetBounds(gfx::Rect(50, 10, 10, 10));
w1->AddTransientWindow(w2.get());
w2->SetModal();
// Send event that is over |w2|.
const ui::PointerEvent mouse_pressed(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, gfx::Point(55, 15), gfx::Point(55, 15),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
event_dispatcher()->ProcessEvent(mouse_pressed,
EventDispatcher::AcceleratorMatchPhase::ANY);
std::unique_ptr<DispatchedEventDetails> details =
test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails();
ASSERT_TRUE(details);
EXPECT_EQ(w2.get(), details->window);
EXPECT_TRUE(details->IsClientArea());
ASSERT_TRUE(details->event);
ASSERT_TRUE(details->event->IsPointerEvent());
ui::PointerEvent* dispatched_event = details->event->AsPointerEvent();
EXPECT_EQ(gfx::Point(55, 15), dispatched_event->root_location());
EXPECT_EQ(gfx::Point(5, 5), dispatched_event->location());
}
// Tests that events on an unrelated window are not affected by the modal
// window.
TEST_F(EventDispatcherTest, ModalWindowEventOnUnrelatedWindow) {
std::unique_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3));
std::unique_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 5));
std::unique_ptr<ServerWindow> w3 = CreateChildWindow(WindowId(1, 6));
root_window()->SetBounds(gfx::Rect(0, 0, 100, 100));
w1->SetBounds(gfx::Rect(10, 10, 30, 30));
w2->SetBounds(gfx::Rect(50, 10, 10, 10));
w3->SetBounds(gfx::Rect(70, 10, 10, 10));
w1->AddTransientWindow(w2.get());
w2->SetModal();
// Send event that is over |w3|.
const ui::PointerEvent mouse_pressed(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, gfx::Point(75, 15), gfx::Point(75, 15),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
event_dispatcher()->ProcessEvent(mouse_pressed,
EventDispatcher::AcceleratorMatchPhase::ANY);
std::unique_ptr<DispatchedEventDetails> details =
test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails();
ASSERT_TRUE(details);
EXPECT_EQ(w3.get(), details->window);
EXPECT_TRUE(details->IsClientArea());
ASSERT_TRUE(details->event);
ASSERT_TRUE(details->event->IsPointerEvent());
ui::PointerEvent* dispatched_event = details->event->AsPointerEvent();
EXPECT_EQ(gfx::Point(75, 15), dispatched_event->root_location());
EXPECT_EQ(gfx::Point(5, 5), dispatched_event->location());
}
// Tests that events events on a descendant of a modal parent target the modal
// child.
TEST_F(EventDispatcherTest, ModalWindowEventOnDescendantOfModalParent) {
std::unique_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3));
std::unique_ptr<ServerWindow> w11 =
CreateChildWindowWithParent(WindowId(1, 4), w1.get());
std::unique_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 5));
root_window()->SetBounds(gfx::Rect(0, 0, 100, 100));
w1->SetBounds(gfx::Rect(10, 10, 30, 30));
w11->SetBounds(gfx::Rect(10, 10, 10, 10));
w2->SetBounds(gfx::Rect(50, 10, 10, 10));
w1->AddTransientWindow(w2.get());
w2->SetModal();
// Send event that is over |w11|.
const ui::PointerEvent mouse_pressed(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, gfx::Point(25, 25), gfx::Point(25, 25),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
event_dispatcher()->ProcessEvent(mouse_pressed,
EventDispatcher::AcceleratorMatchPhase::ANY);
std::unique_ptr<DispatchedEventDetails> details =
test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails();
ASSERT_TRUE(details);
EXPECT_EQ(w2.get(), details->window);
EXPECT_TRUE(details->IsNonclientArea());
ASSERT_TRUE(details->event);
ASSERT_TRUE(details->event->IsPointerEvent());
ui::PointerEvent* dispatched_event = details->event->AsPointerEvent();
EXPECT_EQ(gfx::Point(25, 25), dispatched_event->root_location());
EXPECT_EQ(gfx::Point(-25, 15), dispatched_event->location());
}
// Tests that events on a system modal window target the modal window itself.
TEST_F(EventDispatcherTest, ModalWindowEventOnSystemModal) {
std::unique_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3));
root_window()->SetBounds(gfx::Rect(0, 0, 100, 100));
w1->SetBounds(gfx::Rect(10, 10, 30, 30));
w1->SetModal();
// Send event that is over |w1|.
const ui::PointerEvent mouse_pressed(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, gfx::Point(15, 15), gfx::Point(15, 15),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
event_dispatcher()->ProcessEvent(mouse_pressed,
EventDispatcher::AcceleratorMatchPhase::ANY);
std::unique_ptr<DispatchedEventDetails> details =
test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails();
ASSERT_TRUE(details);
EXPECT_EQ(w1.get(), details->window);
EXPECT_TRUE(details->IsClientArea());
ASSERT_TRUE(details->event);
ASSERT_TRUE(details->event->IsPointerEvent());
ui::PointerEvent* dispatched_event = details->event->AsPointerEvent();
EXPECT_EQ(gfx::Point(15, 15), dispatched_event->root_location());
EXPECT_EQ(gfx::Point(5, 5), dispatched_event->location());
}
// Tests that events outside of system modal window target the modal window.
TEST_F(EventDispatcherTest, ModalWindowEventOutsideSystemModal) {
std::unique_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3));
root_window()->SetBounds(gfx::Rect(0, 0, 100, 100));
w1->SetBounds(gfx::Rect(10, 10, 30, 30));
w1->SetModal();
event_dispatcher()->AddSystemModalWindow(w1.get());
// Send event that is over |w1|.
const ui::PointerEvent mouse_pressed(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, gfx::Point(45, 15), gfx::Point(45, 15),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
event_dispatcher()->ProcessEvent(mouse_pressed,
EventDispatcher::AcceleratorMatchPhase::ANY);
std::unique_ptr<DispatchedEventDetails> details =
test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails();
ASSERT_TRUE(details);
EXPECT_EQ(w1.get(), details->window);
EXPECT_TRUE(details->IsNonclientArea());
ASSERT_TRUE(details->event);
ASSERT_TRUE(details->event->IsPointerEvent());
ui::PointerEvent* dispatched_event = details->event->AsPointerEvent();
EXPECT_EQ(gfx::Point(45, 15), dispatched_event->root_location());
EXPECT_EQ(gfx::Point(35, 5), dispatched_event->location());
}
// Tests that setting capture to a descendant of a modal parent fails.
TEST_F(EventDispatcherTest, ModalWindowSetCaptureDescendantOfModalParent) {
std::unique_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3));
std::unique_ptr<ServerWindow> w11 =
CreateChildWindowWithParent(WindowId(1, 4), w1.get());
std::unique_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 5));
root_window()->SetBounds(gfx::Rect(0, 0, 100, 100));
w1->SetBounds(gfx::Rect(10, 10, 30, 30));
w11->SetBounds(gfx::Rect(10, 10, 10, 10));
w2->SetBounds(gfx::Rect(50, 10, 10, 10));
w1->AddTransientWindow(w2.get());
w2->SetModal();
EXPECT_FALSE(event_dispatcher()->SetCaptureWindow(w11.get(), kClientAreaId));
EXPECT_EQ(nullptr, event_dispatcher()->capture_window());
}
// Tests that setting capture to a window unrelated to a modal parent works.
TEST_F(EventDispatcherTest, ModalWindowSetCaptureUnrelatedWindow) {
std::unique_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3));
std::unique_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 4));
std::unique_ptr<ServerWindow> w3 = CreateChildWindow(WindowId(1, 5));
root_window()->SetBounds(gfx::Rect(0, 0, 100, 100));
w1->SetBounds(gfx::Rect(10, 10, 30, 30));
w2->SetBounds(gfx::Rect(50, 10, 10, 10));
w3->SetBounds(gfx::Rect(70, 10, 10, 10));
w1->AddTransientWindow(w2.get());
w2->SetModal();
EXPECT_TRUE(event_dispatcher()->SetCaptureWindow(w3.get(), kClientAreaId));
EXPECT_EQ(w3.get(), event_dispatcher()->capture_window());
}
// Tests that setting capture fails when there is a system modal window.
TEST_F(EventDispatcherTest, ModalWindowSystemSetCapture) {
std::unique_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3));
std::unique_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 4));
root_window()->SetBounds(gfx::Rect(0, 0, 100, 100));
w1->SetBounds(gfx::Rect(10, 10, 30, 30));
w2->SetBounds(gfx::Rect(50, 10, 10, 10));
event_dispatcher()->AddSystemModalWindow(w2.get());
EXPECT_FALSE(event_dispatcher()->SetCaptureWindow(w1.get(), kClientAreaId));
EXPECT_EQ(nullptr, event_dispatcher()->capture_window());
}
// Tests having multiple system modal windows.
TEST_F(EventDispatcherTest, ModalWindowMultipleSystemModals) {
std::unique_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3));
std::unique_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 4));
std::unique_ptr<ServerWindow> w3 = CreateChildWindow(WindowId(1, 5));
w2->SetVisible(false);
// In the beginning, there should be no active system modal window.
EXPECT_EQ(nullptr, GetActiveSystemModalWindow());
// Add a visible system modal window. It should become the active one.
event_dispatcher()->AddSystemModalWindow(w1.get());
EXPECT_EQ(w1.get(), GetActiveSystemModalWindow());
// Add an invisible system modal window. It should not change the active one.
event_dispatcher()->AddSystemModalWindow(w2.get());
EXPECT_EQ(w1.get(), GetActiveSystemModalWindow());
// Add another visible system modal window. It should become the active one.
event_dispatcher()->AddSystemModalWindow(w3.get());
EXPECT_EQ(w3.get(), GetActiveSystemModalWindow());
// Make an existing system modal window visible. It should become the active
// one.
w2->SetVisible(true);
EXPECT_EQ(w2.get(), GetActiveSystemModalWindow());
// Remove the active system modal window. Next one should become active.
w2.reset();
EXPECT_EQ(w3.get(), GetActiveSystemModalWindow());
// Remove an inactive system modal window. It should not change the active
// one.
w1.reset();
EXPECT_EQ(w3.get(), GetActiveSystemModalWindow());
// Remove the last remaining system modal window. There should be no active
// one anymore.
w3.reset();
EXPECT_EQ(nullptr, GetActiveSystemModalWindow());
}
TEST_F(EventDispatcherTest, CaptureNotResetOnParentChange) {
std::unique_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3));
DisableHitTest(w1.get());
std::unique_ptr<ServerWindow> w11 =
CreateChildWindowWithParent(WindowId(1, 4), w1.get());
std::unique_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 5));
DisableHitTest(w2.get());
root_window()->SetBounds(gfx::Rect(0, 0, 100, 100));
w1->SetBounds(gfx::Rect(0, 0, 100, 100));
w11->SetBounds(gfx::Rect(10, 10, 10, 10));
w2->SetBounds(gfx::Rect(0, 0, 100, 100));
// Send event that is over |w11|.
const ui::PointerEvent mouse_pressed(ui::MouseEvent(
ui::ET_MOUSE_PRESSED, gfx::Point(15, 15), gfx::Point(15, 15),
base::TimeTicks(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
event_dispatcher()->ProcessEvent(mouse_pressed,
EventDispatcher::AcceleratorMatchPhase::ANY);
event_dispatcher()->SetCaptureWindow(w11.get(), kClientAreaId);
std::unique_ptr<DispatchedEventDetails> details =
test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails();
ASSERT_TRUE(details);
EXPECT_EQ(w11.get(), details->window);
EXPECT_TRUE(details->IsClientArea());
// Move |w11| to |w2| and verify the mouse is still down, and |w11| has
// capture.
w2->Add(w11.get());
EXPECT_TRUE(IsMouseButtonDown());
EXPECT_EQ(w11.get(),
EventDispatcherTestApi(event_dispatcher()).capture_window());
}
TEST_F(EventDispatcherTest, ChangeCaptureFromClientToNonclient) {
std::unique_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3));
event_dispatcher()->SetCaptureWindow(child.get(), kNonclientAreaId);
EXPECT_EQ(kNonclientAreaId,
event_dispatcher()->capture_window_client_id());
EXPECT_EQ(nullptr, test_event_dispatcher_delegate()->lost_capture_window());
event_dispatcher()->SetCaptureWindow(child.get(), kClientAreaId);
// Changing capture from client to non-client should notify the delegate.
// The delegate can decide if it really wants to forward the event or not.
EXPECT_EQ(child.get(),
test_event_dispatcher_delegate()->lost_capture_window());
EXPECT_EQ(child.get(), event_dispatcher()->capture_window());
EXPECT_EQ(kClientAreaId, event_dispatcher()->capture_window_client_id());
}
} // namespace test
} // namespace ws
} // namespace ui