blob: ad9ad5dbadb956b25b0e1c9636e46a75daf49f0f [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/focus_handler.h"
#include "services/ws/client_change.h"
#include "services/ws/client_change_tracker.h"
#include "services/ws/proxy_window.h"
#include "services/ws/window_properties.h"
#include "services/ws/window_service.h"
#include "services/ws/window_service_delegate.h"
#include "services/ws/window_tree.h"
#include "ui/aura/client/focus_client.h"
#include "ui/wm/public/activation_client.h"
namespace ws {
FocusHandler::FocusHandler(WindowTree* window_tree)
: window_tree_(window_tree) {
window_tree_->window_service_->focus_client()->AddObserver(this);
}
FocusHandler::~FocusHandler() {
window_tree_->window_service_->focus_client()->RemoveObserver(this);
}
bool FocusHandler::SetFocus(aura::Window* window) {
if (window && !IsFocusableWindow(window)) {
DVLOG(1) << "SetFocus failed (access denied or invalid window)";
return false;
}
// The client shouldn't set focus with nullptr. When window is giving up its
// focus (like closing or hiding), the client should reset the focus within
// the client but the reset shouldn't be propagated to the server. The window
// server will pick up a new focused window meanwhile, on other hooks like
// visibility change or window state change. See https://crbug.com/897875.
if (!window) {
DVLOG(1) << "SetFocus failed (nullptr)";
return false;
}
aura::client::FocusClient* focus_client =
window_tree_->window_service_->focus_client();
ProxyWindow* proxy_window = ProxyWindow::GetMayBeNull(window);
if (window == focus_client->GetFocusedWindow()) {
if (proxy_window->focus_owner() != window_tree_) {
// The focused window didn't change, but the client that owns focus did
// (see |ProxyWindow::focus_owner_| for details on this). Notify the
// current owner that it lost focus.
if (proxy_window->focus_owner()) {
proxy_window->focus_owner()->window_tree_client_->OnWindowFocused(
kInvalidTransportId);
}
proxy_window->set_focus_owner(window_tree_);
}
return true;
}
ClientChange change(window_tree_->property_change_tracker_.get(), window,
ClientChangeType::kFocus);
// FocusController has a special API to reset focus inside the active window,
// which happens when a view requests focus (e.g. the find bar).
// https://crbug.com/880533
wm::ActivationClient* activation_client =
wm::GetActivationClient(window->GetRootWindow());
if (activation_client) {
aura::Window* active_window = activation_client->GetActiveWindow();
if (active_window && active_window->Contains(window)) {
focus_client->ResetFocusWithinActiveWindow(window);
if (focus_client->GetFocusedWindow() != window) {
DVLOG(1) << "SetFocus failed (FocusClient::ResetFocusWithinActiveWindow"
<< " failed for " << window->GetName() << ")";
return false;
}
if (proxy_window)
proxy_window->set_focus_owner(window_tree_);
return true;
}
}
focus_client->FocusWindow(window);
if (focus_client->GetFocusedWindow() != window) {
DVLOG(1) << "SetFocus failed (FocusClient::FocusWindow call failed for "
<< window->GetName() << ")";
return false;
}
if (proxy_window)
proxy_window->set_focus_owner(window_tree_);
return true;
}
void FocusHandler::SetCanFocus(aura::Window* window, bool can_focus) {
if (window && (window_tree_->IsClientCreatedWindow(window) ||
window_tree_->IsClientRootWindow(window))) {
window->SetProperty(kCanFocus, can_focus);
} else {
DVLOG(1) << "SetCanFocus failed (invalid or unknown window)";
}
}
bool FocusHandler::IsFocusableWindow(aura::Window* window) const {
if (!window)
return true; // Used to clear focus.
if (!window->IsVisible() || !window->GetRootWindow())
return false; // The window must be drawn and attached to a root.
return (window_tree_->IsClientCreatedWindow(window) ||
window_tree_->IsClientRootWindow(window));
}
bool FocusHandler::IsEmbeddedClient(ProxyWindow* proxy_window) const {
return proxy_window->embedded_window_tree() == window_tree_;
}
bool FocusHandler::IsOwningClient(ProxyWindow* proxy_window) const {
return proxy_window->owning_window_tree() == window_tree_;
}
void FocusHandler::OnWindowFocused(aura::Window* gained_focus,
aura::Window* lost_focus) {
ClientChangeTracker* change_tracker =
window_tree_->property_change_tracker_.get();
if (change_tracker->IsProcessingChangeForWindow(lost_focus,
ClientChangeType::kFocus) ||
change_tracker->IsProcessingChangeForWindow(gained_focus,
ClientChangeType::kFocus)) {
// The client initiated the change, don't notify the client.
return;
}
// The client did not request the focus change. Update state appropriately.
// Prefer the embedded client over the owning client.
bool notified_gained = false;
if (gained_focus) {
ProxyWindow* proxy_window = ProxyWindow::GetMayBeNull(gained_focus);
if (proxy_window && (IsEmbeddedClient(proxy_window) ||
(!proxy_window->embedded_window_tree() &&
IsOwningClient(proxy_window)))) {
proxy_window->set_focus_owner(window_tree_);
window_tree_->window_tree_client_->OnWindowFocused(
window_tree_->TransportIdForWindow(gained_focus));
notified_gained = true;
}
}
if (lost_focus && !notified_gained) {
ProxyWindow* proxy_window = ProxyWindow::GetMayBeNull(lost_focus);
if (proxy_window && proxy_window->focus_owner() == window_tree_) {
proxy_window->set_focus_owner(nullptr);
window_tree_->window_tree_client_->OnWindowFocused(kInvalidTransportId);
}
}
}
} // namespace ws