// 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/client_root.h" #include "base/bind.h" #include "base/callback_forward.h" #include "base/command_line.h" #include "base/memory/ptr_util.h" #include "base/trace_event/trace_event.h" #include "components/viz/common/surfaces/surface_info.h" #include "components/viz/host/host_frame_sink_manager.h" #include "services/ws/client_change.h" #include "services/ws/client_change_tracker.h" #include "services/ws/common/switches.h" #include "services/ws/proxy_window.h" #include "services/ws/top_level_proxy_window.h" #include "services/ws/window_service.h" #include "services/ws/window_tree.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/env.h" #include "ui/aura/mus/client_surface_embedder.h" #include "ui/aura/mus/property_converter.h" #include "ui/aura/window.h" #include "ui/aura/window_tree_host.h" #include "ui/aura_extra/window_position_in_root_monitor.h" #include "ui/base/layout.h" #include "ui/compositor/compositor.h" #include "ui/compositor/dip_util.h" #include "ui/compositor/property_change_reason.h" #include "ui/display/display.h" #include "ui/display/screen.h" #include "ui/gfx/geometry/point_conversions.h" #include "ui/wm/core/coordinate_conversion.h" namespace ws { namespace { bool ShouldAssignLocalSurfaceIdImpl(aura::Window* window, bool is_top_level) { // The window service assigns LocalSurfaceIds in two cases: // . Top-levels. This is because the window service is the one creating the // Window, and effectively embedding the client. // . An embedding created by a WindowTree that was not itself embedded. This // scenario is similar to top-levels, where the Window is not itself // embedded in another window. An example of this is the app-list embedding // a Window that contains a WebContents, where the app-list runs in process // (not using the window-service APIs). if (is_top_level) return true; ProxyWindow* proxy_window = ProxyWindow::GetMayBeNull(window); return proxy_window->owning_window_tree() == nullptr; } // Returns the bounds of the |window| in screen coordinate, without affect of // the gfx::Transform. aura::Window::GetBoundsInScreen() is affected by // Transform and may return wrong origin on overview mode, which may confuse // locations of transient clients or tooltip windows. See // https://crbug.com/931161. gfx::Rect GetBoundsToSend(aura::Window* window) { gfx::Rect bounds = window->bounds(); // Window may not have the root window in some tests, so use the topmost // window in the hierarchy for root window. aura::Window* root = window; for (auto* w = window->parent(); w; w = w->parent()) { bounds += w->bounds().OffsetFromOrigin(); root = w; } // Typically root window bounds should be (0, 0), but it's not on some tests. bounds += (root->GetBoundsInScreen().origin() - root->bounds().origin()); return bounds; } // Converts the size of the window to pixels. This function must match that used // by WindowTreeHostMus (otherwise this code may not generate a new // LocalSurfaceId when the client believes one should be generated). See // https://crbug.com/952095 for more details. gfx::Size ConvertWindowSizeToPixels(aura::Window* window) { return gfx::ScaleToCeiledSize(window->bounds().size(), window->layer()->device_scale_factor()); } } // namespace ClientRoot::ClientRoot(WindowTree* window_tree, aura::Window* window, bool is_top_level) : window_tree_(window_tree), window_(window), is_top_level_(is_top_level), last_bounds_(GetBoundsToSend(window)), last_visible_(!is_top_level && window->IsVisible()), last_display_id_(display::kInvalidDisplayId) { window_->AddObserver(this); if (window_->GetHost()) window->GetHost()->AddObserver(this); display::Screen::GetScreen()->AddObserver(this); client_surface_embedder_ = std::make_unique(window_); if (ShouldAssignLocalSurfaceIdImpl(window, is_top_level_)) parent_local_surface_id_allocator_.emplace(); // Ensure there is a valid LocalSurfaceId (if necessary). GenerateLocalSurfaceIdIfNecessary(); if (!is_top_level) { root_position_monitor_ = std::make_unique( window, base::BindRepeating(&ClientRoot::OnPositionInRootChanged, base::Unretained(this))); } } ClientRoot::~ClientRoot() { if (force_visible_) force_visible_->OnClientRootDestroyed(); ProxyWindow* proxy_window = ProxyWindow::GetMayBeNull(window_); window_->RemoveObserver(this); if (window_->GetHost()) window_->GetHost()->RemoveObserver(this); display::Screen::GetScreen()->RemoveObserver(this); viz::HostFrameSinkManager* host_frame_sink_manager = window_->env()->context_factory_private()->GetHostFrameSinkManager(); host_frame_sink_manager->InvalidateFrameSinkId(proxy_window->frame_sink_id()); } void ClientRoot::RegisterVizEmbeddingSupport() { // This function should only be called once. viz::HostFrameSinkManager* host_frame_sink_manager = window_->env()->context_factory_private()->GetHostFrameSinkManager(); viz::FrameSinkId frame_sink_id = ProxyWindow::GetMayBeNull(window_)->frame_sink_id(); // This code only needs first-surface-activation for tests. const bool wants_first_surface_activation = base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kUseTestConfig); host_frame_sink_manager->RegisterFrameSinkId( frame_sink_id, this, wants_first_surface_activation ? viz::ReportFirstSurfaceActivation::kYes : viz::ReportFirstSurfaceActivation::kNo); window_->SetEmbedFrameSinkId(frame_sink_id); UpdateLocalSurfaceIdAndClientSurfaceEmbedder(); } void ClientRoot::OnForceVisibleDestroyed() { force_visible_ = nullptr; NotifyClientOfVisibilityChange(); } void ClientRoot::GenerateLocalSurfaceIdIfNecessary() { if (!ShouldAssignLocalSurfaceId()) return; gfx::Size size_in_pixels = ConvertWindowSizeToPixels(window_); ProxyWindow* proxy_window = ProxyWindow::GetMayBeNull(window_); // It's expected by cc code that any time the size changes a new // LocalSurfaceId is used. if (last_surface_size_in_pixels_ != size_in_pixels || !proxy_window->local_surface_id_allocation().has_value() || last_device_scale_factor_ != window_->layer()->device_scale_factor()) { parent_local_surface_id_allocator_->GenerateId(); UpdateSurfacePropertiesCache(); } } void ClientRoot::UpdateSurfacePropertiesCache() { ProxyWindow::GetMayBeNull(window_)->set_local_surface_id_allocation( parent_local_surface_id_allocator_->GetCurrentLocalSurfaceIdAllocation()); last_device_scale_factor_ = window_->layer()->device_scale_factor(); last_surface_size_in_pixels_ = ConvertWindowSizeToPixels(window_); } bool ClientRoot::SetBoundsInScreenFromClient( const gfx::Rect& bounds, const base::Optional& allocation) { ProxyWindow* proxy_window = ProxyWindow::GetMayBeNull(window_); const base::Optional starting_allocation = proxy_window->local_surface_id_allocation(); { base::AutoReset resetter(&setting_bounds_from_client_, true); display::Display dst_display = display::Screen::GetScreen()->GetDisplayMatching(bounds); window_->SetBoundsInScreen(bounds, dst_display); } if (allocation) parent_local_surface_id_allocator_->UpdateFromChild(*allocation); const bool needs_new_surface_id = !allocation || bounds.size() != window_->bounds().size(); if (needs_new_surface_id) parent_local_surface_id_allocator_->GenerateId(); UpdateSurfacePropertiesCache(); if (starting_allocation != proxy_window->local_surface_id_allocation()) UpdateLocalSurfaceIdAndClientSurfaceEmbedder(); const bool succeeded = bounds == GetBoundsToSend(window_); // The bounds and id form a unique pair, so that if either differ from what // the client requested, the client needs to be notified by way of // NotifyClientOfNewBounds(). if (!succeeded || needs_new_surface_id) NotifyClientOfNewBounds(); return succeeded; } void ClientRoot::UpdateLocalSurfaceIdFromChild( const viz::LocalSurfaceIdAllocation& local_surface_id_allocation) { if (!parent_local_surface_id_allocator_->UpdateFromChild( local_surface_id_allocation)) { return; } UpdateSurfacePropertiesCache(); UpdateLocalSurfaceIdAndClientSurfaceEmbedder(); } void ClientRoot::OnLocalSurfaceIdChanged() { if (ShouldAssignLocalSurfaceId()) return; HandleBoundsOrScaleFactorChange(); } void ClientRoot::AllocateLocalSurfaceIdAndNotifyClient() { if (!ShouldAssignLocalSurfaceId()) return; // Setting a null LocalSurfaceIdAllocation forces allocating a new one. ProxyWindow::GetMayBeNull(window_)->set_local_surface_id_allocation( base::nullopt); UpdateLocalSurfaceIdAndClientSurfaceEmbedder(); NotifyClientOfNewBounds(); } void ClientRoot::AttachChildFrameSinkId(ProxyWindow* proxy_window) { DCHECK(proxy_window->attached_frame_sink_id().is_valid()); DCHECK(ProxyWindow::GetMayBeNull(window_)->frame_sink_id().is_valid()); viz::HostFrameSinkManager* host_frame_sink_manager = window_->env()->context_factory_private()->GetHostFrameSinkManager(); const viz::FrameSinkId& frame_sink_id = proxy_window->attached_frame_sink_id(); if (host_frame_sink_manager->IsFrameSinkIdRegistered(frame_sink_id)) { host_frame_sink_manager->RegisterFrameSinkHierarchy( ProxyWindow::GetMayBeNull(window_)->frame_sink_id(), frame_sink_id); } } void ClientRoot::UnattachChildFrameSinkId(ProxyWindow* proxy_window) { DCHECK(proxy_window->attached_frame_sink_id().is_valid()); DCHECK(ProxyWindow::GetMayBeNull(window_)->frame_sink_id().is_valid()); viz::HostFrameSinkManager* host_frame_sink_manager = window_->env()->context_factory_private()->GetHostFrameSinkManager(); const viz::FrameSinkId& root_frame_sink_id = ProxyWindow::GetMayBeNull(window_)->frame_sink_id(); const viz::FrameSinkId& window_frame_sink_id = proxy_window->attached_frame_sink_id(); if (host_frame_sink_manager->IsFrameSinkHierarchyRegistered( root_frame_sink_id, window_frame_sink_id)) { host_frame_sink_manager->UnregisterFrameSinkHierarchy(root_frame_sink_id, window_frame_sink_id); } } void ClientRoot::AttachChildFrameSinkIdRecursive(ProxyWindow* proxy_window) { if (proxy_window->attached_frame_sink_id().is_valid()) AttachChildFrameSinkId(proxy_window); for (aura::Window* child : proxy_window->window()->children()) { ProxyWindow* child_proxy_window = ProxyWindow::GetMayBeNull(child); if (child_proxy_window->owning_window_tree() == window_tree_) AttachChildFrameSinkIdRecursive(child_proxy_window); } } void ClientRoot::UnattachChildFrameSinkIdRecursive(ProxyWindow* proxy_window) { if (proxy_window->attached_frame_sink_id().is_valid()) UnattachChildFrameSinkId(proxy_window); for (aura::Window* child : proxy_window->window()->children()) { ProxyWindow* child_proxy_window = ProxyWindow::GetMayBeNull(child); if (child_proxy_window->owning_window_tree() == window_tree_) UnattachChildFrameSinkIdRecursive(child_proxy_window); } } std::unique_ptr ClientRoot::ForceWindowVisible() { // At this time there is only a need for a single force visible. DCHECK(!force_visible_); // Use WrapUnique() as constructor is private. std::unique_ptr force_visible = base::WrapUnique(new ScopedForceVisible(this)); force_visible_ = force_visible.get(); NotifyClientOfVisibilityChange(); return force_visible; } void ClientRoot::OnWindowTreeHostDisplayIdChanged() { if (last_display_id_ != window_->GetHost()->GetDisplayId()) NotifyClientOfDisplayIdChange(); } void ClientRoot::UpdateLocalSurfaceIdAndClientSurfaceEmbedder() { GenerateLocalSurfaceIdIfNecessary(); ProxyWindow* proxy_window = ProxyWindow::GetMayBeNull(window_); if (!proxy_window->local_surface_id_allocation().has_value()) return; const viz::SurfaceId surface_id( window_->GetFrameSinkId(), proxy_window->local_surface_id_allocation()->local_surface_id()); const bool surface_id_changed = surface_id != client_surface_embedder_->GetSurfaceId(); client_surface_embedder_->SetSurfaceId(surface_id); // This triggers holding events until the frame has been activated. This // ensures smooth resizes. if (surface_id_changed && ShouldAssignLocalSurfaceId() && window_->GetHost()) window_->GetHost()->compositor()->OnChildResizing(); } void ClientRoot::CheckForScaleFactorChange() { if (!ShouldAssignLocalSurfaceId() || last_device_scale_factor_ == window_->layer()->device_scale_factor()) { return; } HandleBoundsOrScaleFactorChange(); } void ClientRoot::HandleBoundsOrScaleFactorChange() { if (setting_bounds_from_client_) return; UpdateLocalSurfaceIdAndClientSurfaceEmbedder(); NotifyClientOfNewBounds(); } void ClientRoot::NotifyClientOfNewBounds() { last_bounds_ = GetBoundsToSend(window_); auto id = ProxyWindow::GetMayBeNull(window_)->local_surface_id_allocation(); TRACE_EVENT_WITH_FLOW0("ui", "ClientRoot::NotifyClientOfNewBounds", id->local_surface_id().hash(), TRACE_EVENT_FLAG_FLOW_OUT); window_tree_->window_tree_client_->OnWindowBoundsChanged( window_tree_->TransportIdForWindow(window_), last_bounds_, window_->GetProperty(aura::client::kShowStateKey), ProxyWindow::GetMayBeNull(window_)->local_surface_id_allocation()); } void ClientRoot::NotifyClientOfVisibilityChange(base::Optional visible) { const bool new_value = visible.has_value() ? *visible : IsWindowVisible(); if (last_visible_ == new_value) return; last_visible_ = new_value; if (!window_tree_->property_change_tracker_->IsProcessingChangeForWindow( window_, ClientChangeType::kVisibility)) { window_tree_->window_tree_client_->OnWindowVisibilityChanged( window_tree_->TransportIdForWindow(window_), last_visible_); } } void ClientRoot::NotifyClientOfDisplayIdChange() { last_display_id_ = window_->GetHost()->GetDisplayId(); window_tree_->window_tree_client_->OnWindowDisplayChanged( window_tree_->TransportIdForWindow(window_), last_display_id_); } void ClientRoot::OnPositionInRootChanged() { DCHECK(!is_top_level_); gfx::Rect bounds_in_screen = GetBoundsToSend(window_); if (bounds_in_screen.origin() != last_bounds_.origin()) NotifyClientOfNewBounds(); } bool ClientRoot::IsWindowVisible() { if (force_visible_) return true; return is_top_level_ ? window_->TargetVisibility() : window_->IsVisible(); } void ClientRoot::OnWindowPropertyChanged(aura::Window* window, const void* key, intptr_t old) { if (window_tree_->property_change_tracker_ ->IsProcessingPropertyChangeForWindow(window, key)) { // Do not send notifications for changes intiated by the client. return; } std::string transport_name; std::unique_ptr> transport_value; if (window_tree_->window_service() ->property_converter() ->ConvertPropertyForTransport(window, key, &transport_name, &transport_value)) { base::Optional> transport_value_mojo; if (transport_value) transport_value_mojo.emplace(std::move(*transport_value)); window_tree_->window_tree_client_->OnWindowSharedPropertyChanged( window_tree_->TransportIdForWindow(window), transport_name, transport_value_mojo); } } void ClientRoot::OnWindowBoundsChanged(aura::Window* window, const gfx::Rect& old_bounds, const gfx::Rect& new_bounds, ui::PropertyChangeReason reason) { if (setting_bounds_from_client_) return; // Early out when a top level is in middle of moving to a new display. // Bounds change will be sent after it is added to the new root window. if (is_top_level_ && is_moving_across_displays_) return; HandleBoundsOrScaleFactorChange(); } void ClientRoot::OnWindowAddedToRootWindow(aura::Window* window) { DCHECK_EQ(window, window_); DCHECK(window->GetHost()); window->GetHost()->AddObserver(this); is_moving_across_displays_ = false; NotifyClientOfDisplayIdChange(); // When added to a new root window, the window bounds in screen may have // changed even though its bounds in the root window remain the same. if (is_top_level_) HandleBoundsOrScaleFactorChange(); else CheckForScaleFactorChange(); NotifyClientOfVisibilityChange(); } void ClientRoot::OnWindowRemovingFromRootWindow(aura::Window* window, aura::Window* new_root) { DCHECK_EQ(window, window_); DCHECK(window->GetHost()); window->GetHost()->RemoveObserver(this); if (new_root) { DCHECK(!is_moving_across_displays_); is_moving_across_displays_ = true; } else { NotifyClientOfVisibilityChange(false); } } void ClientRoot::OnWindowVisibilityChanged(aura::Window* window, bool visible) { NotifyClientOfVisibilityChange(); } void ClientRoot::OnHostResized(aura::WindowTreeHost* host) { // This function is also called when the device-scale-factor changes too. CheckForScaleFactorChange(); } void ClientRoot::OnDisplayMetricsChanged(const display::Display& display, uint32_t changed_metrics) { // WindowTreeHost display id should be updated before OnDisplayMetricsChanged. // Early out if the changed display is not relevant. if (!window_->GetHost() || window_->GetHost()->GetDisplayId() != display.id()) return; // Only handle changes that could change the origin of the ClientRoot. if (!(changed_metrics & (DISPLAY_METRIC_BOUNDS | DISPLAY_METRIC_ROTATION))) return; // Size or device-scale-factor change is handled in OnHostResized. const gfx::Rect new_bounds = GetBoundsToSend(window_); if (last_bounds_.size() != new_bounds.size() || last_device_scale_factor_ != ui::GetScaleFactorForNativeView(window_)) { return; } if (last_bounds_ != new_bounds) NotifyClientOfNewBounds(); } void ClientRoot::OnFirstSurfaceActivation( const viz::SurfaceInfo& surface_info) { // NOTE: this function is only called if kUseTestConfig is supplied. See // call to RegisterFrameSinkId(). if (window_tree_->client_name().empty()) return; ProxyWindow* proxy_window = ProxyWindow::GetMayBeNull(window_); // OnFirstSurfaceActivation() should only be called after // AttachCompositorFrameSink(). DCHECK(proxy_window->attached_compositor_frame_sink()); window_tree_->window_service()->OnFirstSurfaceActivation( window_tree_->client_name()); } void ClientRoot::OnFrameTokenChanged(uint32_t frame_token) { // TODO: this needs to call through to WindowTreeClient. // https://crbug.com/848022. } } // namespace ws