910e62b5创建于 1月15日历史提交
// Copyright 2013 The Chromium Authors
// 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 <tuple>

#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/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_tree_host.h"
#include "ui/compositor/layer.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 {

WindowTargeter::WindowTargeter() = default;

WindowTargeter::~WindowTargeter() = default;

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;
}

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 =
        Env::GetInstance()->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 =
        Env::GetInstance()->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 BUILDFLAG(IS_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::Get()->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(lanwei): this code is likely not necessarily and will be removed.
    if (!root_window->bounds().Contains(event.location()))
      return root_window;
#endif
  }

  return nullptr;
}

ui::EventTarget* WindowTargeter::FindTargetForEvent(ui::EventTarget* root,
                                                    ui::Event* event) {
  Window* window = static_cast<Window*>(root);
  return event->IsKeyEvent() ? FindTargetForKeyEvent(window)
                             : FindTargetForNonKeyEvent(window, event);
}

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->GetCanProcessEventsWithinSubtree(focused_window)) {
    focus_client->FocusWindow(nullptr);
    return nullptr;
  }
  return focused_window ? focused_window : window;
}

void WindowTargeter::OnInstalled(Window* window) {
  window_ = window;
}

Window* WindowTargeter::FindTargetForLocatedEvent(Window* window,
                                                  ui::LocatedEvent* event) {
  if (!window->parent()) {
    Window* target = FindTargetInRootWindow(window, *event);
    if (target) {
      window->ConvertEventToTarget(target, event);

#if BUILDFLAG(IS_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);
}

ui::EventSink* WindowTargeter::GetNewEventSinkForEvent(
    const ui::EventTarget* current_root,
    ui::EventTarget* target,
    ui::Event* in_out_event) {
  const aura::Window* root_window = static_cast<const Window*>(current_root);
  aura::Window* target_window = static_cast<Window*>(target);
  if (!target_window || root_window->parent() ||
      root_window->Contains(target_window)) {
    // no need to re-target.
    return nullptr;
  }
  // |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_window->GetRootWindow();
  DCHECK(new_root);
  if (in_out_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 = in_out_event->AsLocatedEvent();
    located_event->ConvertLocationToTarget(target_window, new_root);
    WindowTreeHost* window_tree_host = new_root->GetHost();
    located_event->UpdateForRootTransform(
        window_tree_host->GetRootTransform(),
        window_tree_host->GetRootTransformForLocalEventCoordinates());
  }
  return new_root->GetHost()->GetEventSink();
}

bool WindowTargeter::SubtreeCanAcceptEvent(
    Window* window,
    const ui::LocatedEvent& event) const {
  if (!window->IsVisible())
    return false;
  if (window->event_targeting_policy() == EventTargetingPolicy::kNone ||
      window->event_targeting_policy() == EventTargetingPolicy::kTargetOnly) {
    return false;
  }
  client::EventClient* client = client::GetEventClient(window->GetRootWindow());
  if (client && !client->GetCanProcessEventsWithinSubtree(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::Point point = ConvertEventLocationToWindowCoordinates(window, event);

  BoundsType bounds_type = BoundsType::kMouse;
  if (event.IsTouchEvent())
    bounds_type = BoundsType::kTouch;
  else if (event.IsGestureEvent())
    bounds_type = BoundsType::kGesture;

  return PointInsideBounds(window, bounds_type, point);
}

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;
}

// static
gfx::Point WindowTargeter::ConvertEventLocationToWindowCoordinates(
    Window* window,
    const ui::LocatedEvent& event) {
  gfx::Point point = event.location();
  if (window->parent())
    Window::ConvertPointToTarget(window->parent(), window, &point);
  return point;
}

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;
}

bool WindowTargeter::PointInsideBounds(Window* window,
                                       BoundsType bounds_type,
                                       const gfx::Point& point) 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);

  const bool point_in_rect =
      bounds_type == BoundsType::kTouch || bounds_type == BoundsType::kGesture
          ? touch_rect.Contains(point)
          : mouse_rect.Contains(point);
  if (point_in_rect) {
    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;
  }

  if (ShouldUseExtendedBounds(window) &&
      (!mouse_extend_.IsEmpty() || !touch_extend_.IsEmpty())) {
    // Insets take priority over child traversal, otherwise it's possible to
    // have a window that has a non-interactable region in the middle.
    return false;
  }

  if (window->layer()->GetMasksToBounds()) {
    // If the layer masks to bounds, children are clipped and shouldn't receive
    // input events.
    return false;
  }

  // Child windows are not always fully contained in the current window.
  std::unique_ptr<ui::EventTargetIterator> iter = window->GetChildIterator();
  if (!iter)
    return false;

  for (ui::EventTarget* child = iter->GetNextTarget(); child;
       child = iter->GetNextTarget()) {
    auto* child_window = static_cast<Window*>(child);
    gfx::Point child_point(point);
    Window::ConvertPointToTarget(window, child_window, &child_point);
    if (PointInsideBounds(child_window, bounds_type, child_point))
      return true;
  }
  return false;
}

}  // namespace aura