chromium/ash/wm/window_finder.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/public/cpp/window_finder.h"

#include "ash/public/cpp/shell_window_ids.h"
#include "ash/shell.h"
#include "ash/wm/overview/overview_controller.h"
#include "ash/wm/overview/overview_grid.h"
#include "ash/wm/overview/overview_session.h"
#include "ash/wm/window_util.h"
#include "base/containers/adapters.h"
#include "base/containers/contains.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/aura/window.h"
#include "ui/aura/window_targeter.h"
#include "ui/events/event.h"

namespace ash {
namespace {

// Returns true if |window| is considered to be a toplevel window.
// Please see the aura::Window::GetToplevelWindow() for the condition.
bool IsTopLevelWindow(aura::Window* window) {
  // It can happen that we're trying to find the next top-level window as a
  // result of a window being destroyed (i.e. inside
  // WindowObserver::OnWindowDestroying()). In this case, the destroying window
  // should not be returned again as the top-level window.
  return !!window->delegate() && !window->is_destroying();
}

// Returns true if |window| can be a target at |screen_point| by |targeter|.
// If |targeter| is null, it will check with Window::GetEventHandlerForPoint().
bool IsWindowTargeted(aura::Window* window,
                      const gfx::Point& screen_point,
                      aura::WindowTargeter* targeter) {
  aura::client::ScreenPositionClient* client =
      aura::client::GetScreenPositionClient(window->GetRootWindow());
  gfx::Point local_point = screen_point;
  if (targeter) {
    client->ConvertPointFromScreen(window->parent(), &local_point);
    // TODO(mukai): consider the hittest differences between mouse and touch.
    gfx::Point point_in_root = local_point;
    aura::Window::ConvertPointToTarget(window, window->GetRootWindow(),
                                       &point_in_root);
    ui::MouseEvent event(ui::EventType::kMouseMoved, local_point, point_in_root,
                         base::TimeTicks::Now(), 0, 0);
    return targeter->SubtreeShouldBeExploredForEvent(window, event);
  }
  // TODO(mukai): maybe we can remove this, simply return false if targeter does
  // not exist.
  client->ConvertPointFromScreen(window, &local_point);
  return window->GetEventHandlerForPoint(local_point);
}

// Get the toplevel window at |screen_point| among the descendants of |window|.
aura::Window* GetTopmostWindowAtPointWithinWindow(
    const gfx::Point& screen_point,
    aura::Window* window,
    aura::WindowTargeter* targeter,
    const std::set<aura::Window*>& ignore) {
  if (!window->IsVisible()) {
    return nullptr;
  }

  if (window->GetId() == kShellWindowId_PhantomWindow ||
      window->GetId() == kShellWindowId_OverlayContainer ||
      window->GetId() == kShellWindowId_MouseCursorContainer) {
    return nullptr;
  }

  if (IsTopLevelWindow(window)) {
    if (IsWindowTargeted(window, screen_point, targeter)) {
      return base::Contains(ignore, window) ? nullptr : window;
    }
    return nullptr;
  }

  for (aura::Window* child : base::Reversed(window->children())) {
    aura::WindowTargeter* child_targeter =
        child->targeter() ? child->targeter() : targeter;
    aura::Window* result = GetTopmostWindowAtPointWithinWindow(
        screen_point, child, child_targeter, ignore);
    if (result) {
      return result;
    }
  }
  return nullptr;
}

// Finds the top level window in overview that contains |screen_point| while
// ignoring |ignore|. Returns nullptr if there is no such window. Note the
// returned window might be a minimized window that's currently showing in
// overview.
aura::Window* GetToplevelWindowInOverviewAtPoint(
    const gfx::Point& screen_point,
    const std::set<aura::Window*>& ignore) {
  OverviewController* overview_controller = Shell::Get()->overview_controller();
  if (!overview_controller->InOverviewSession()) {
    return nullptr;
  }

  OverviewGrid* grid =
      overview_controller->overview_session()->GetGridWithRootWindow(
          window_util::GetRootWindowAt(screen_point));
  if (!grid) {
    return nullptr;
  }

  aura::Window* window = grid->GetTargetWindowOnLocation(
      gfx::PointF(screen_point), /*ignored_item=*/nullptr);
  if (!window) {
    return nullptr;
  }

  window = window->GetToplevelWindow();
  return base::Contains(ignore, window) ? nullptr : window;
}

}  // namespace

aura::Window* GetTopmostWindowAtPoint(const gfx::Point& screen_point,
                                      const std::set<aura::Window*>& ignore) {
  aura::Window* overview_window =
      GetToplevelWindowInOverviewAtPoint(screen_point, ignore);
  if (overview_window)
    return overview_window;
  aura::Window* root = window_util::GetRootWindowAt(screen_point);
  return GetTopmostWindowAtPointWithinWindow(screen_point, root,
                                             root->targeter(), ignore);
}

}  // namespace ash