blob: 0e182f2d8e9a18377674ee33d119d1bd031704e1 [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/aura/window_targeter.h"
#include "services/ws/public/mojom/window_tree_constants.mojom.h"
#include "ui/aura/client/capture_client.h"
#include "ui/aura/client/event_client.h"
#include "ui/aura/client/focus_client.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/aura/env.h"
#include "ui/aura/mus/window_port_mus.h"
#include "ui/aura/mus/window_tree_client.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_tree_host.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/events/event_target.h"
#include "ui/events/event_target_iterator.h"
namespace aura {
namespace {
bool AreInsetsEmptyOrPositive(const gfx::Insets& insets) {
return insets.left() >= 0 && insets.right() >= 0 && insets.top() >= 0 &&
insets.bottom() >= 0;
}
void UpdateMusIfNecessary(aura::Window* window,
const gfx::Insets& mouse_extend,
const gfx::Insets& touch_extend) {
if (!window || window->env()->mode() != Env::Mode::MUS)
return;
// Negative insets are used solely to extend the hit-test region of child
// windows, which is not needed by code using MUS (negative insets are only
// used in the server).
if (AreInsetsEmptyOrPositive(mouse_extend) &&
AreInsetsEmptyOrPositive(touch_extend)) {
WindowPortMus::Get(window)->SetHitTestInsets(mouse_extend, touch_extend);
}
}
} // namespace
WindowTargeter::WindowTargeter() {}
WindowTargeter::~WindowTargeter() {}
bool WindowTargeter::SubtreeShouldBeExploredForEvent(
Window* window,
const ui::LocatedEvent& event) {
return SubtreeCanAcceptEvent(window, event) &&
EventLocationInsideBounds(window, event);
}
bool WindowTargeter::GetHitTestRects(Window* window,
gfx::Rect* hit_test_rect_mouse,
gfx::Rect* hit_test_rect_touch) const {
DCHECK(hit_test_rect_mouse);
DCHECK(hit_test_rect_touch);
*hit_test_rect_mouse = *hit_test_rect_touch = window->bounds();
if (ShouldUseExtendedBounds(window)) {
hit_test_rect_mouse->Inset(mouse_extend_);
hit_test_rect_touch->Inset(touch_extend_);
}
return true;
}
std::unique_ptr<WindowTargeter::HitTestRects>
WindowTargeter::GetExtraHitTestShapeRects(Window* target) const {
return nullptr;
}
void WindowTargeter::SetInsets(const gfx::Insets& mouse_and_touch_extend) {
SetInsets(mouse_and_touch_extend, mouse_and_touch_extend);
}
void WindowTargeter::SetInsets(const gfx::Insets& mouse_extend,
const gfx::Insets& touch_extend) {
if (mouse_extend_ == mouse_extend && touch_extend_ == touch_extend)
return;
mouse_extend_ = mouse_extend;
touch_extend_ = touch_extend;
UpdateMusIfNecessary();
}
Window* WindowTargeter::GetPriorityTargetInRootWindow(
Window* root_window,
const ui::LocatedEvent& event) {
DCHECK_EQ(root_window, root_window->GetRootWindow());
// Mouse events should be dispatched to the window that processed the
// mouse-press events (if any).
if (event.IsScrollEvent() || event.IsMouseEvent()) {
WindowEventDispatcher* dispatcher = root_window->GetHost()->dispatcher();
if (dispatcher->mouse_pressed_handler())
return dispatcher->mouse_pressed_handler();
}
// All events should be directed towards the capture window (if any).
Window* capture_window = client::GetCaptureWindow(root_window);
if (capture_window)
return capture_window;
if (event.IsPinchEvent()) {
DCHECK_EQ(event.AsGestureEvent()->details().device_type(),
ui::GestureDeviceType::DEVICE_TOUCHPAD);
WindowEventDispatcher* dispatcher = root_window->GetHost()->dispatcher();
if (dispatcher->touchpad_pinch_handler())
return dispatcher->touchpad_pinch_handler();
}
if (event.IsTouchEvent()) {
// Query the gesture-recognizer to find targets for touch events.
const ui::TouchEvent& touch = *event.AsTouchEvent();
ui::GestureConsumer* consumer =
root_window->env()->gesture_recognizer()->GetTouchLockedTarget(touch);
if (consumer)
return static_cast<Window*>(consumer);
}
return nullptr;
}
Window* WindowTargeter::FindTargetInRootWindow(Window* root_window,
const ui::LocatedEvent& event) {
DCHECK_EQ(root_window, root_window->GetRootWindow());
Window* priority_target = GetPriorityTargetInRootWindow(root_window, event);
if (priority_target)
return priority_target;
if (event.IsTouchEvent()) {
// Query the gesture-recognizer to find targets for touch events.
const ui::TouchEvent& touch = *event.AsTouchEvent();
// GetTouchLockedTarget() is handled in GetPriorityTargetInRootWindow().
ui::GestureRecognizer* gesture_recognizer =
root_window->env()->gesture_recognizer();
DCHECK(!gesture_recognizer->GetTouchLockedTarget(touch));
ui::GestureConsumer* consumer = gesture_recognizer->GetTargetForLocation(
event.location_f(), touch.source_device_id());
if (consumer)
return static_cast<Window*>(consumer);
#if defined(OS_CHROMEOS)
// If the initial touch is outside the window's display, target the root.
// This is used for bezel gesture events (eg. swiping in from screen edge).
display::Display display =
display::Screen::GetScreen()->GetDisplayNearestWindow(root_window);
// The window target may be null, so use the root's ScreenPositionClient.
gfx::Point screen_location = event.root_location();
if (client::GetScreenPositionClient(root_window)) {
client::GetScreenPositionClient(root_window)
->ConvertPointToScreen(root_window, &screen_location);
}
if (!display.bounds().Contains(screen_location))
return root_window;
#else
// If the initial touch is outside the root window, target the root.
// TODO: this code is likely not necessarily and will be removed.
if (!root_window->bounds().Contains(event.location()))
return root_window;
#endif
}
return nullptr;
}
bool WindowTargeter::ProcessEventIfTargetsDifferentRootWindow(
Window* root_window,
Window* target,
ui::Event* event) {
if (root_window->Contains(target))
return false;
// |window| is the root window, but |target| is not a descendent of
// |window|. So do not allow dispatching from here. Instead, dispatch the
// event through the WindowEventDispatcher that owns |target|.
Window* new_root = target->GetRootWindow();
DCHECK(new_root);
if (event->IsLocatedEvent()) {
// The event has been transformed to be in |target|'s coordinate system.
// But dispatching the event through the EventProcessor requires the event
// to be in the host's coordinate system. So, convert the event to be in
// the root's coordinate space, and then to the host's coordinate space by
// applying the host's transform.
ui::LocatedEvent* located_event = event->AsLocatedEvent();
located_event->ConvertLocationToTarget(target, new_root);
WindowTreeHost* window_tree_host = new_root->GetHost();
located_event->UpdateForRootTransform(
window_tree_host->GetRootTransform(),
window_tree_host->GetRootTransformForLocalEventCoordinates());
}
ignore_result(new_root->GetHost()->event_sink()->OnEventFromSource(event));
return true;
}
ui::EventTarget* WindowTargeter::FindTargetForEvent(ui::EventTarget* root,
ui::Event* event) {
Window* window = static_cast<Window*>(root);
Window* target = event->IsKeyEvent()
? FindTargetForKeyEvent(window)
: FindTargetForNonKeyEvent(window, event);
if (target && !window->parent() &&
ProcessEventIfTargetsDifferentRootWindow(window, target, event)) {
return nullptr;
}
return target;
}
ui::EventTarget* WindowTargeter::FindNextBestTarget(
ui::EventTarget* previous_target,
ui::Event* event) {
return nullptr;
}
Window* WindowTargeter::FindTargetForKeyEvent(Window* window) {
Window* root_window = window->GetRootWindow();
client::FocusClient* focus_client = client::GetFocusClient(root_window);
if (!focus_client)
return window;
Window* focused_window = focus_client->GetFocusedWindow();
if (!focused_window)
return window;
client::EventClient* event_client = client::GetEventClient(root_window);
if (event_client &&
!event_client->CanProcessEventsWithinSubtree(focused_window)) {
focus_client->FocusWindow(nullptr);
return nullptr;
}
return focused_window ? focused_window : window;
}
void WindowTargeter::OnInstalled(Window* window) {
// Needs to clear the existing insets when uninstalled.
if (!window)
aura::UpdateMusIfNecessary(window_, gfx::Insets(), gfx::Insets());
window_ = window;
UpdateMusIfNecessary();
}
Window* WindowTargeter::FindTargetForLocatedEvent(Window* window,
ui::LocatedEvent* event) {
if (!window->parent()) {
Window* target = FindTargetInRootWindow(window, *event);
if (target) {
window->ConvertEventToTarget(target, event);
#if defined(OS_CHROMEOS)
if (window->IsRootWindow() && event->HasNativeEvent()) {
// If window is root, and the target is in a different host, we need to
// convert the native event to the target's host as well. This happens
// while a widget is being dragged and when the majority of its bounds
// reside in a different display. Setting the widget's bounds at this
// point changes the window's root, and the event's target's root, but
// the events are still being generated relative to the original
// display. crbug.com/714578.
ui::LocatedEvent* e =
static_cast<ui::LocatedEvent*>(event->native_event());
gfx::PointF native_point = e->location_f();
aura::Window::ConvertNativePointToTargetHost(window, target,
&native_point);
e->set_location_f(native_point);
}
#endif
return target;
}
}
return FindTargetForLocatedEventRecursively(window, event);
}
bool WindowTargeter::SubtreeCanAcceptEvent(
Window* window,
const ui::LocatedEvent& event) const {
if (!window->IsVisible())
return false;
if (window->event_targeting_policy() ==
ws::mojom::EventTargetingPolicy::NONE ||
window->event_targeting_policy() ==
ws::mojom::EventTargetingPolicy::TARGET_ONLY) {
return false;
}
client::EventClient* client = client::GetEventClient(window->GetRootWindow());
if (client && !client->CanProcessEventsWithinSubtree(window))
return false;
Window* parent = window->parent();
if (parent && parent->delegate_ &&
!parent->delegate_->ShouldDescendIntoChildForEventHandling(
window, event.location())) {
return false;
}
return true;
}
bool WindowTargeter::EventLocationInsideBounds(
Window* window,
const ui::LocatedEvent& event) const {
gfx::Rect mouse_rect;
gfx::Rect touch_rect;
if (!GetHitTestRects(window, &mouse_rect, &touch_rect))
return false;
const gfx::Vector2d offset = -window->bounds().OffsetFromOrigin();
mouse_rect.Offset(offset);
touch_rect.Offset(offset);
gfx::Point point = event.location();
if (window->parent())
Window::ConvertPointToTarget(window->parent(), window, &point);
const bool point_in_rect = event.IsTouchEvent() || event.IsGestureEvent()
? touch_rect.Contains(point)
: mouse_rect.Contains(point);
if (!point_in_rect)
return false;
auto shape_rects = GetExtraHitTestShapeRects(window);
if (!shape_rects)
return true;
for (const gfx::Rect& shape_rect : *shape_rects) {
if (shape_rect.Contains(point)) {
return true;
}
}
return false;
}
bool WindowTargeter::ShouldUseExtendedBounds(const aura::Window* w) const {
// window() is null when this is used as the default targeter (by
// WindowEventDispatcher). Insets should never be set in this case, so the
// return should not matter.
if (!window()) {
DCHECK(mouse_extend_.IsEmpty());
DCHECK(touch_extend_.IsEmpty());
return false;
}
// Insets should only apply to the window. Subclasses may enforce other
// policies.
return window() == w;
}
// TODO: this function should go away once https://crbug.com/879308 is fixed.
void WindowTargeter::UpdateMusIfNecessary() {
aura::UpdateMusIfNecessary(window_, mouse_extend_, touch_extend_);
}
Window* WindowTargeter::FindTargetForNonKeyEvent(Window* root_window,
ui::Event* event) {
if (!event->IsLocatedEvent())
return root_window;
return FindTargetForLocatedEvent(root_window,
static_cast<ui::LocatedEvent*>(event));
}
Window* WindowTargeter::FindTargetForLocatedEventRecursively(
Window* root_window,
ui::LocatedEvent* event) {
std::unique_ptr<ui::EventTargetIterator> iter =
root_window->GetChildIterator();
if (iter) {
ui::EventTarget* target = root_window;
for (ui::EventTarget* child = iter->GetNextTarget(); child;
child = iter->GetNextTarget()) {
WindowTargeter* targeter =
static_cast<WindowTargeter*>(child->GetEventTargeter());
if (!targeter)
targeter = this;
if (!targeter->SubtreeShouldBeExploredForEvent(
static_cast<Window*>(child), *event)) {
continue;
}
target->ConvertEventToTarget(child, event);
target = child;
Window* child_target_window =
static_cast<Window*>(targeter->FindTargetForEvent(child, event));
if (child_target_window)
return child_target_window;
}
target->ConvertEventToTarget(root_window, event);
}
return root_window->CanAcceptEvent(*event) ? root_window : nullptr;
}
} // namespace aura