blob: eb38c3c1709f2dddd7c918d8b80a47abfad9e6b1 [file] [log] [blame]
// Copyright 2018 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/ws/proxy_window.h"
#include <utility>
#include "base/containers/flat_map.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "services/ws/client_root.h"
#include "services/ws/drag_drop_delegate.h"
#include "services/ws/embedding.h"
#include "services/ws/top_level_proxy_window_impl.h"
#include "services/ws/window_tree.h"
#include "services/ws/window_utils.h"
#include "ui/aura/client/capture_client_observer.h"
#include "ui/aura/env.h"
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"
#include "ui/aura/window_targeter.h"
#include "ui/compositor/compositor.h"
#include "ui/events/event_handler.h"
#include "ui/wm/core/capture_controller.h"
#include "ui/wm/core/window_modality_controller.h"
DEFINE_UI_CLASS_PROPERTY_TYPE(ws::ProxyWindow*)
namespace ws {
namespace {
DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(ProxyWindow, kProxyWindowKey, nullptr)
bool IsPointerPressedEvent(const ui::Event& event) {
return event.type() == ui::ET_MOUSE_PRESSED ||
event.type() == ui::ET_TOUCH_PRESSED;
}
bool IsPointerEvent(const ui::Event& event) {
return event.IsMouseEvent() || event.IsTouchEvent();
}
bool IsLastMouseButtonRelease(const ui::Event& event) {
return event.type() == ui::ET_MOUSE_RELEASED &&
event.AsMouseEvent()->button_flags() ==
event.AsMouseEvent()->changed_button_flags();
}
bool IsPointerReleased(const ui::Event& event) {
return IsLastMouseButtonRelease(event) ||
event.type() == ui::ET_TOUCH_RELEASED;
}
ui::PointerId GetPointerId(const ui::Event& event) {
if (event.IsMouseEvent())
return ui::MouseEvent::kMousePointerId;
DCHECK(event.IsTouchEvent());
return event.AsTouchEvent()->pointer_details().id;
}
// WindowTargeter used for ProxyWindows. This is used for two purposes:
// . If the location is in the non-client area, then child Windows are not
// considered. This is done to ensure the delegate of the window (which is
// local) sees the event.
// . To ensure |WindowTree::intercepts_events_| is honored.
class ProxyWindowTargeter : public aura::WindowTargeter {
public:
explicit ProxyWindowTargeter(ProxyWindow* proxy_window)
: proxy_window_(proxy_window) {}
~ProxyWindowTargeter() override = default;
// aura::WindowTargeter:
bool SubtreeShouldBeExploredForEvent(aura::Window* window,
const ui::LocatedEvent& event) override {
// If the top-level does not have insets, then forward the call to the
// parent's WindowTargeter. This is necessary for targeters such as
// EasyResizeWindowTargeter to work correctly.
if (mouse_extend().IsEmpty() && touch_extend().IsEmpty() &&
proxy_window_->IsTopLevel() && window->parent()) {
aura::WindowTargeter* parent_targeter =
static_cast<WindowTargeter*>(window->parent()->targeter());
if (parent_targeter)
return parent_targeter->SubtreeShouldBeExploredForEvent(window, event);
}
return aura::WindowTargeter::SubtreeShouldBeExploredForEvent(window, event);
}
ui::EventTarget* FindTargetForEvent(ui::EventTarget* event_target,
ui::Event* event) override {
aura::Window* window = static_cast<aura::Window*>(event_target);
DCHECK_EQ(window, proxy_window_->window());
if (proxy_window_->DoesOwnerInterceptEvents()) {
// If the owner intercepts events, then don't recurse (otherwise events
// would go to a descendant).
return event_target->CanAcceptEvent(*event) ? window : nullptr;
}
// Ensure events in the non-client area target the top-level window.
// TopLevelEventHandler will ensure these are routed correctly.
if (event->IsLocatedEvent() &&
IsLocationInNonClientArea(window,
event->AsLocatedEvent()->location())) {
return window;
}
return aura::WindowTargeter::FindTargetForEvent(event_target, event);
}
private:
ProxyWindow* const proxy_window_;
DISALLOW_COPY_AND_ASSIGN(ProxyWindowTargeter);
};
// ProxyWindowEventHandler is used to forward events to the client.
// ProxyWindowEventHandler adds itself to the pre-phase to ensure it's
// considered before the Window's delegate (or other EventHandlers).
class ProxyWindowEventHandler : public ui::EventHandler {
public:
explicit ProxyWindowEventHandler(ProxyWindow* proxy_window)
: proxy_window_(proxy_window) {
// Use |kDefault| so as not to conflict with other important pre-target
// handlers (such as laser pointer).
window()->AddPreTargetHandler(this, ui::EventTarget::Priority::kDefault);
}
~ProxyWindowEventHandler() override {
window()->RemovePreTargetHandler(this);
}
ProxyWindow* proxy_window() { return proxy_window_; }
aura::Window* window() { return proxy_window_->window(); }
// ui::EventHandler:
void OnEvent(ui::Event* event) override {
if (event->phase() != ui::EP_PRETARGET) {
// All work is done in the pre-phase. If this branch is hit, it means
// event propagation was not stopped, and normal processing should
// continue. Early out to avoid sending the event to the client again.
return;
}
if (HandleInterceptedEvent(event) || ShouldIgnoreEvent(*event))
return;
auto* owning = proxy_window_->owning_window_tree();
auto* embedded = proxy_window_->embedded_window_tree();
WindowTree* target_client = nullptr;
if (proxy_window_->DoesOwnerInterceptEvents()) {
// A client that intercepts events, always gets the event regardless of
// focus/capture.
target_client = owning;
} else if (event->IsKeyEvent()) {
if (!proxy_window_->focus_owner())
return; // The local environment is going to process the event.
target_client = proxy_window_->focus_owner();
} else if (proxy_window()->capture_owner()) {
target_client = proxy_window()->capture_owner();
} else {
// Prefer embedded over owner.
target_client = !embedded ? owning : embedded;
}
DCHECK(target_client);
// Don't send located events to the client during a move loop. Normally
// the client shouldn't be the target at this point, but it's entirely
// possible for held events to be released, triggering a spurious call.
if (event->IsLocatedEvent() && target_client->IsMovingWindow())
return;
target_client->SendEventToClient(window(), *event);
// The event was forwarded to the remote client. We don't want it handled
// locally too.
if (event->cancelable())
event->StopPropagation();
}
protected:
// Returns true if the event is a pinch event generated from the touchpad.
bool IsPinchEventOnTouchpad(const ui::Event& event) {
return event.IsPinchEvent() &&
event.AsGestureEvent()->details().device_type() ==
ui::GestureDeviceType::DEVICE_TOUCHPAD;
}
// Returns true if the event should be ignored (not forwarded to the client).
bool ShouldIgnoreEvent(const ui::Event& event) {
// It's assumed clients do their own gesture recognizition, which means
// GestureEvents should not be forwarded to clients. Pinch events are
// exceptional since they aren't created through gesture recognition but
// from the touchpad directly. See https://crbug.com/933985.
if (event.IsGestureEvent() && !IsPinchEventOnTouchpad(event))
return true;
if (static_cast<aura::Window*>(event.target()) != window()) {
// As ProxyWindow is a EP_PRETARGET EventHandler it gets events *before*
// descendants. Ignore all such events, and only process when
// window() is the the target.
return true;
}
if (wm::GetModalTransient(window()))
return true; // Do not send events to clients blocked by a modal window.
return ShouldIgnoreEventType(event.type());
}
bool ShouldIgnoreEventType(ui::EventType type) const {
// WindowTreeClient takes care of sending ET_MOUSE_CAPTURE_CHANGED at the
// right point. The enter events are effectively synthetic, and indirectly
// generated in the client as the result of a move event.
switch (type) {
case ui::ET_MOUSE_CAPTURE_CHANGED:
case ui::ET_MOUSE_ENTERED:
return true;
default:
break;
}
return false;
}
// If |window| identifies an embedding and the owning client intercepts
// events, this forwards to the owner and returns true. Otherwise returns
// false.
bool HandleInterceptedEvent(ui::Event* event) {
if (ShouldIgnoreEventType(event->type()))
return false;
// KeyEvents, and events when there is capture, do not go through through
// ProxyWindowTargeter. As a result ProxyWindowEventHandler has to check
// for a client intercepting events.
if (proxy_window_->DoesOwnerInterceptEvents()) {
proxy_window_->owning_window_tree()->SendEventToClient(window(), *event);
if (event->cancelable())
event->StopPropagation();
return true;
}
return false;
}
private:
ProxyWindow* const proxy_window_;
DISALLOW_COPY_AND_ASSIGN(ProxyWindowEventHandler);
};
class TopLevelEventHandler;
// PointerPressHandler is used to track state while a pointer is down.
// PointerPressHandler is typically destroyed when the pointer is released, but
// it may be destroyed at other times, such as when capture changes.
class PointerPressHandler : public aura::client::CaptureClientObserver,
public aura::WindowObserver {
public:
PointerPressHandler(TopLevelEventHandler* top_level_event_handler,
ui::PointerId pointer_id,
const gfx::Point& location);
~PointerPressHandler() override;
bool in_non_client_area() const { return in_non_client_area_; }
private:
// aura::client::CaptureClientObserver:
void OnCaptureChanged(aura::Window* lost_capture,
aura::Window* gained_capture) override;
// aura::WindowObserver:
void OnWindowVisibilityChanged(aura::Window* window, bool visible) override;
TopLevelEventHandler* top_level_event_handler_;
// True if the pointer down occurred in the non-client area.
const bool in_non_client_area_;
// Id of the pointer the handler was created for.
const ui::PointerId pointer_id_;
DISALLOW_COPY_AND_ASSIGN(PointerPressHandler);
};
// ui::EventHandler used for top-levels. Some events that target the non-client
// area are not sent to the client, instead are handled locally. For example,
// if a press occurs in the non-client area, then the event is not sent to
// the client, it's handled locally.
class TopLevelEventHandler : public ProxyWindowEventHandler {
public:
explicit TopLevelEventHandler(ProxyWindow* proxy_window)
: ProxyWindowEventHandler(proxy_window) {
// Top-levels should always have an owning_window_tree().
// OnEvent() assumes this.
DCHECK(proxy_window->owning_window_tree());
}
~TopLevelEventHandler() override = default;
void DestroyPointerPressHandler(ui::PointerId id) {
pointer_press_handlers_.erase(id);
}
// Returns true if the pointer with |pointer_id| was pressed over the
// top-level. If this returns true, TopLevelEventHandler is waiting on a
// release to reset state.
bool IsHandlingPointerPress(ui::PointerId pointer_id) const {
return pointer_press_handlers_.count(pointer_id) > 0;
}
// Called when the capture owner changes.
void OnCaptureOwnerChanged() {
// Changing the capture owner toggles between local and the client getting
// the event. The |pointer_press_handlers_| are no longer applicable
// (because the target is purely dicatated by capture owner).
pointer_press_handlers_.clear();
}
// ProxyWindowEventHandler:
void OnEvent(ui::Event* event) override {
if (event->phase() != ui::EP_PRETARGET) {
// All work is done in the pre-phase. If this branch is hit, it means
// event propagation was not stopped, and normal processing should
// continue. Early out to avoid sending the event to the client again.
return;
}
if (HandleInterceptedEvent(event))
return;
if (!event->IsLocatedEvent()) {
ProxyWindowEventHandler::OnEvent(event);
return;
}
// When the gesture-end happens in the server side, the gesture state
// is cleaned up there; this state should be synchronized with the client.
if (event->type() == ui::ET_GESTURE_END &&
event->AsGestureEvent()->details().touch_points() == 1) {
proxy_window()->owning_window_tree()->CleanupGestureState(window());
return;
}
if (ShouldIgnoreEvent(*event))
return;
// If there is capture, send the event to the client that owns it. A null
// capture owner means the local environment should handle the event.
if (wm::CaptureController::Get()->GetCaptureWindow()) {
if (proxy_window()->capture_owner()) {
proxy_window()->capture_owner()->SendEventToClient(window(), *event);
if (event->cancelable())
event->StopPropagation();
return;
}
return;
}
// This code has two specific behaviors. It's used to ensure events go to
// the right target (either local, or the remote client).
// . a press-release sequence targets only one. If in non-client area then
// local, otherwise remote client.
// . mouse-moves (not drags) go to both targets.
bool stop_propagation = false;
if (proxy_window()->HasNonClientArea() && IsPointerEvent(*event)) {
const ui::PointerId pointer_id = GetPointerId(*event);
if (!pointer_press_handlers_.count(pointer_id)) {
if (IsPointerPressedEvent(*event)) {
std::unique_ptr<PointerPressHandler> handler_ptr =
std::make_unique<PointerPressHandler>(
this, pointer_id, event->AsLocatedEvent()->location());
PointerPressHandler* handler = handler_ptr.get();
pointer_press_handlers_[pointer_id] = std::move(handler_ptr);
if (handler->in_non_client_area())
return; // Don't send presses in non-client area to client.
stop_propagation = true;
}
} else {
// Currently handling a pointer press and waiting on release.
PointerPressHandler* handler =
pointer_press_handlers_[pointer_id].get();
const bool was_press_in_non_client_area = handler->in_non_client_area();
if (IsPointerReleased(*event))
pointer_press_handlers_.erase(pointer_id);
if (was_press_in_non_client_area)
return; // Don't send release to client since press didn't go there.
stop_propagation = true;
}
}
proxy_window()->owning_window_tree()->SendEventToClient(window(), *event);
if (stop_propagation && event->cancelable())
event->StopPropagation();
}
private:
// Non-null while in a pointer press press-drag-release cycle. Maps from
// pointer-id of the pointer that is down to the handler.
base::flat_map<ui::PointerId, std::unique_ptr<PointerPressHandler>>
pointer_press_handlers_;
DISALLOW_COPY_AND_ASSIGN(TopLevelEventHandler);
};
PointerPressHandler::PointerPressHandler(
TopLevelEventHandler* top_level_event_handler,
ui::PointerId pointer_id,
const gfx::Point& location)
: top_level_event_handler_(top_level_event_handler),
in_non_client_area_(
IsLocationInNonClientArea(top_level_event_handler->window(),
location)),
pointer_id_(pointer_id) {
wm::CaptureController::Get()->AddObserver(this);
top_level_event_handler_->window()->AddObserver(this);
}
PointerPressHandler::~PointerPressHandler() {
top_level_event_handler_->window()->RemoveObserver(this);
wm::CaptureController::Get()->RemoveObserver(this);
}
void PointerPressHandler::OnCaptureChanged(aura::Window* lost_capture,
aura::Window* gained_capture) {
if (gained_capture != top_level_event_handler_->window())
top_level_event_handler_->DestroyPointerPressHandler(pointer_id_);
}
void PointerPressHandler::OnWindowVisibilityChanged(aura::Window* window,
bool visible) {
if (!top_level_event_handler_->window()->IsVisible())
top_level_event_handler_->DestroyPointerPressHandler(pointer_id_);
}
} // namespace
ProxyWindow::~ProxyWindow() {
// WindowTree/ClientRoot should have reset |attached_frame_sink_id_| before
// the Window is destroyed.
DCHECK(!attached_frame_sink_id_.is_valid());
}
// static
ProxyWindow* ProxyWindow::Create(aura::Window* window,
WindowTree* tree,
const viz::FrameSinkId& frame_sink_id,
bool is_top_level) {
DCHECK(!GetMayBeNull(window));
// Owned by |window|.
ProxyWindow* proxy_window =
new ProxyWindow(window, tree, frame_sink_id, is_top_level);
return proxy_window;
}
// static
const ProxyWindow* ProxyWindow::GetMayBeNull(const aura::Window* window) {
return window ? window->GetProperty(kProxyWindowKey) : nullptr;
}
void ProxyWindow::Destroy() {
// This should only be called for windows created locally for an embedding
// (not created by a remote client). Such windows do not have an owner.
DCHECK(!owning_window_tree_);
// static_cast is needed to determine which function SetProperty() applies
// to.
window_->SetProperty(kProxyWindowKey, static_cast<ProxyWindow*>(nullptr));
}
WindowTree* ProxyWindow::embedded_window_tree() {
return embedding_ ? embedding_->embedded_tree() : nullptr;
}
const WindowTree* ProxyWindow::embedded_window_tree() const {
return embedding_ ? embedding_->embedded_tree() : nullptr;
}
void ProxyWindow::SetClientArea(
const gfx::Insets& insets,
const std::vector<gfx::Rect>& additional_client_areas) {
if (client_area_ == insets &&
additional_client_areas == additional_client_areas_) {
return;
}
additional_client_areas_ = additional_client_areas;
client_area_ = insets;
}
void ProxyWindow::SetHitTestInsets(const gfx::Insets& mouse,
const gfx::Insets& touch) {
window_targeter_->SetInsets(mouse, touch);
}
void ProxyWindow::SetCaptureOwner(WindowTree* owner) {
capture_owner_ = owner;
if (!IsTopLevel())
return;
static_cast<TopLevelEventHandler*>(event_handler_.get())
->OnCaptureOwnerChanged();
}
void ProxyWindow::StoreCursor(const ui::Cursor& cursor) {
cursor_ = cursor;
}
bool ProxyWindow::DoesOwnerInterceptEvents() const {
return embedding_ && embedding_->embedding_tree_intercepts_events();
}
void ProxyWindow::SetEmbedding(std::unique_ptr<Embedding> embedding) {
embedding_ = std::move(embedding);
}
bool ProxyWindow::HasNonClientArea() const {
return owning_window_tree_ && owning_window_tree_->IsTopLevel(window_) &&
(!client_area_.IsEmpty() || !additional_client_areas_.empty());
}
bool ProxyWindow::IsTopLevel() const {
return owning_window_tree_ && owning_window_tree_->IsTopLevel(window_);
}
void ProxyWindow::AttachCompositorFrameSink(
viz::mojom::CompositorFrameSinkRequest compositor_frame_sink,
viz::mojom::CompositorFrameSinkClientPtr client) {
attached_compositor_frame_sink_ = true;
viz::HostFrameSinkManager* host_frame_sink_manager =
window_->env()->context_factory_private()->GetHostFrameSinkManager();
host_frame_sink_manager->CreateCompositorFrameSink(
frame_sink_id_, std::move(compositor_frame_sink), std::move(client));
}
void ProxyWindow::SetDragDropDelegate(
std::unique_ptr<DragDropDelegate> drag_drop_delegate) {
drag_drop_delegate_ = std::move(drag_drop_delegate);
}
void ProxyWindow::SetTopLevelProxyWindow(
std::unique_ptr<TopLevelProxyWindowImpl> window) {
top_level_proxy_window_ = std::move(window);
}
std::string ProxyWindow::GetIdForDebugging() {
return owning_window_tree_
? owning_window_tree_->ClientWindowIdForWindow(window_).ToString()
: frame_sink_id_.ToString();
}
ProxyWindow::ProxyWindow(aura::Window* window,
WindowTree* tree,
const viz::FrameSinkId& frame_sink_id,
bool is_top_level)
: window_(window),
owning_window_tree_(tree),
frame_sink_id_(frame_sink_id) {
window_->SetProperty(kProxyWindowKey, this);
if (is_top_level)
event_handler_ = std::make_unique<TopLevelEventHandler>(this);
else
event_handler_ = std::make_unique<ProxyWindowEventHandler>(this);
auto proxy_window_targeter = std::make_unique<ProxyWindowTargeter>(this);
window_targeter_ = proxy_window_targeter.get();
window_->SetEventTargeter(std::move(proxy_window_targeter));
// In order for a window to receive events it must have a target_handler()
// (see Window::CanAcceptEvent()). Normally the delegate is the TargetHandler,
// but if the delegate is null, then so is the target_handler(). Set
// |event_handler_| as the target_handler() to force the Window to accept
// events.
if (!window_->delegate())
window_->SetTargetHandler(event_handler_.get());
}
bool ProxyWindow::IsHandlingPointerPressForTesting(ui::PointerId pointer_id) {
DCHECK(IsTopLevel());
return static_cast<TopLevelEventHandler*>(event_handler_.get())
->IsHandlingPointerPress(pointer_id);
}
} // namespace ws