| // 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 |