// Copyright 2016 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/ui/ws/test_utils.h" #include #include "base/memory/ptr_util.h" #include "cc/output/copy_output_request.h" #include "gpu/ipc/client/gpu_channel_host.h" #include "services/service_manager/public/interfaces/connector.mojom.h" #include "services/ui/public/interfaces/cursor.mojom.h" #include "services/ui/ws/display_binding.h" #include "services/ui/ws/display_manager.h" #include "services/ui/ws/platform_display_init_params.h" #include "services/ui/ws/window_manager_access_policy.h" #include "services/ui/ws/window_manager_window_tree_factory.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/gfx/geometry/dip_util.h" namespace ui { namespace ws { namespace test { namespace { // ----------------------------------------------------------------------------- // Empty implementation of PlatformDisplay. class TestPlatformDisplay : public PlatformDisplay { public: explicit TestPlatformDisplay(const PlatformDisplayInitParams& params, mojom::Cursor* cursor_storage) : id_(params.display_id), display_metrics_(params.metrics), cursor_storage_(cursor_storage) { display_metrics_.bounds = gfx::Rect(0, 0, 400, 300); display_metrics_.device_scale_factor = 1.f; } ~TestPlatformDisplay() override {} // PlatformDisplay: void Init(PlatformDisplayDelegate* delegate) override { delegate->OnAcceleratedWidgetAvailable(); } int64_t GetId() const override { return id_; } void SetViewportSize(const gfx::Size& size) override {} void SetTitle(const base::string16& title) override {} void SetCapture() override {} void ReleaseCapture() override {} void SetCursorById(mojom::Cursor cursor) override { *cursor_storage_ = cursor; } void UpdateTextInputState(const ui::TextInputState& state) override {} void SetImeVisibility(bool visible) override {} gfx::Rect GetBounds() const override { return display_metrics_.bounds; } bool UpdateViewportMetrics(const display::ViewportMetrics& metrics) override { if (display_metrics_ == metrics) return false; display_metrics_ = metrics; return true; } const display::ViewportMetrics& GetViewportMetrics() const override { return display_metrics_; } gfx::AcceleratedWidget GetAcceleratedWidget() const override { return gfx::kNullAcceleratedWidget; } FrameGenerator* GetFrameGenerator() override { return nullptr; } private: const int64_t id_; display::ViewportMetrics display_metrics_; mojom::Cursor* cursor_storage_; DISALLOW_COPY_AND_ASSIGN(TestPlatformDisplay); }; ClientWindowId NextUnusedClientWindowId(WindowTree* tree) { ClientWindowId client_id; for (ClientSpecificId id = 1;; ++id) { // Used the id of the client in the upper bits to simplify things. const ClientWindowId client_id = ClientWindowId(WindowIdToTransportId(WindowId(tree->id(), id))); if (!tree->GetWindowByClientId(client_id)) return client_id; } } // Creates a Display with |id| and same attributes as |metrics|. display::Display CreateDisplay(int64_t id, const display::ViewportMetrics& metrics) { display::Display display(id); display.set_bounds(metrics.bounds); display.set_work_area(metrics.work_area); display.set_device_scale_factor(metrics.device_scale_factor); display.set_rotation(metrics.rotation); display.set_touch_support(metrics.touch_support); return display; } } // namespace // TestScreenManager ------------------------------------------------- TestScreenManager::TestScreenManager() {} TestScreenManager::~TestScreenManager() { display::Screen::SetScreenInstance(nullptr); } int64_t TestScreenManager::AddDisplay() { return AddDisplay(MakeViewportMetrics(0, 0, 100, 100, 1.0f)); } int64_t TestScreenManager::AddDisplay(const display::ViewportMetrics& metrics) { // Generate a unique display id. int64_t display_id = display_ids_.empty() ? 1 : *display_ids_.rbegin() + 1; display_ids_.insert(display_id); // First display added will be the primary display. display::DisplayList::Type type = display::DisplayList::Type::NOT_PRIMARY; if (display_ids_.size() == 1) type = display::DisplayList::Type::PRIMARY; screen_->display_list().AddDisplay(CreateDisplay(display_id, metrics), type); delegate_->OnDisplayAdded(display_id, metrics); if (type == display::DisplayList::Type::PRIMARY) delegate_->OnPrimaryDisplayChanged(display_id); return display_id; } void TestScreenManager::ModifyDisplay(int64_t display_id, const display::ViewportMetrics& metrics) { DCHECK(display_ids_.count(display_id) == 1); screen_->display_list().UpdateDisplay(CreateDisplay(display_id, metrics)); delegate_->OnDisplayModified(display_id, metrics); } void TestScreenManager::RemoveDisplay(int64_t display_id) { DCHECK(display_ids_.count(display_id) == 1); screen_->display_list().RemoveDisplay(display_id); delegate_->OnDisplayRemoved(display_id); display_ids_.erase(display_id); } void TestScreenManager::Init(display::ScreenManagerDelegate* delegate) { delegate_ = delegate; // Reset everything. display_ids_.clear(); display::Screen::SetScreenInstance(nullptr); screen_ = base::MakeUnique(); display::Screen::SetScreenInstance(screen_.get()); } // TestPlatformDisplayFactory ------------------------------------------------- TestPlatformDisplayFactory::TestPlatformDisplayFactory( mojom::Cursor* cursor_storage) : cursor_storage_(cursor_storage) {} TestPlatformDisplayFactory::~TestPlatformDisplayFactory() {} std::unique_ptr TestPlatformDisplayFactory::CreatePlatformDisplay( const PlatformDisplayInitParams& init_params) { return base::MakeUnique(init_params, cursor_storage_); } // TestFrameGeneratorDelegate ------------------------------------------------- TestFrameGeneratorDelegate::TestFrameGeneratorDelegate() {} TestFrameGeneratorDelegate::~TestFrameGeneratorDelegate() {} bool TestFrameGeneratorDelegate::IsInHighContrastMode() { return false; } // WindowTreeTestApi --------------------------------------------------------- WindowTreeTestApi::WindowTreeTestApi(WindowTree* tree) : tree_(tree) {} WindowTreeTestApi::~WindowTreeTestApi() {} void WindowTreeTestApi::StartPointerWatcher(bool want_moves) { tree_->StartPointerWatcher(want_moves); } void WindowTreeTestApi::StopPointerWatcher() { tree_->StopPointerWatcher(); } // DisplayTestApi ------------------------------------------------------------ DisplayTestApi::DisplayTestApi(Display* display) : display_(display) {} DisplayTestApi::~DisplayTestApi() {} // EventDispatcherTestApi ---------------------------------------------------- bool EventDispatcherTestApi::IsWindowPointerTarget( const ServerWindow* window) const { for (const auto& pair : ed_->pointer_targets_) { if (pair.second.window == window) return true; } return false; } int EventDispatcherTestApi::NumberPointerTargetsForWindow( ServerWindow* window) { int count = 0; for (const auto& pair : ed_->pointer_targets_) if (pair.second.window == window) count++; return count; } // TestDisplayBinding --------------------------------------------------------- WindowTree* TestDisplayBinding::CreateWindowTree(ServerWindow* root) { const uint32_t embed_flags = 0; WindowTree* tree = window_server_->EmbedAtWindow( root, service_manager::mojom::kRootUserID, ui::mojom::WindowTreeClientPtr(), embed_flags, base::WrapUnique(new WindowManagerAccessPolicy)); tree->ConfigureWindowManager(); return tree; } // TestWindowManager ---------------------------------------------------------- void TestWindowManager::WmDisplayRemoved(int64_t display_id) { got_display_removed_ = true; display_removed_id_ = display_id; } void TestWindowManager::WmCreateTopLevelWindow( uint32_t change_id, ClientSpecificId requesting_client_id, const std::unordered_map>& properties) { got_create_top_level_window_ = true; change_id_ = change_id; } void TestWindowManager::WmClientJankinessChanged(ClientSpecificId client_id, bool janky) {} void TestWindowManager::WmPerformMoveLoop(uint32_t change_id, uint32_t window_id, mojom::MoveLoopSource source, const gfx::Point& cursor_location) { on_perform_move_loop_called_ = true; } void TestWindowManager::WmCancelMoveLoop(uint32_t window_id) {} void TestWindowManager::WmDeactivateWindow(uint32_t window_id) {} void TestWindowManager::WmStackAbove(uint32_t change_id, uint32_t above_id, uint32_t below_id) {} void TestWindowManager::WmStackAtTop(uint32_t change_id, uint32_t window_id) {} void TestWindowManager::OnAccelerator(uint32_t ack_id, uint32_t accelerator_id, std::unique_ptr event) { on_accelerator_called_ = true; on_accelerator_id_ = accelerator_id; } // TestWindowTreeClient ------------------------------------------------------- TestWindowTreeClient::TestWindowTreeClient() : binding_(this), record_on_change_completed_(false) {} TestWindowTreeClient::~TestWindowTreeClient() {} void TestWindowTreeClient::Bind( mojo::InterfaceRequest request) { binding_.Bind(std::move(request)); } void TestWindowTreeClient::OnEmbed(uint16_t client_id, mojom::WindowDataPtr root, ui::mojom::WindowTreePtr tree, int64_t display_id, Id focused_window_id, bool drawn) { // TODO(sky): add test coverage of |focused_window_id|. tracker_.OnEmbed(client_id, std::move(root), drawn); } void TestWindowTreeClient::OnEmbeddedAppDisconnected(uint32_t window) { tracker_.OnEmbeddedAppDisconnected(window); } void TestWindowTreeClient::OnUnembed(Id window_id) { tracker_.OnUnembed(window_id); } void TestWindowTreeClient::OnCaptureChanged(Id new_capture_window_id, Id old_capture_window_id) { tracker_.OnCaptureChanged(new_capture_window_id, old_capture_window_id); } void TestWindowTreeClient::OnTopLevelCreated(uint32_t change_id, mojom::WindowDataPtr data, int64_t display_id, bool drawn) { tracker_.OnTopLevelCreated(change_id, std::move(data), drawn); } void TestWindowTreeClient::OnWindowBoundsChanged(uint32_t window, const gfx::Rect& old_bounds, const gfx::Rect& new_bounds) { tracker_.OnWindowBoundsChanged(window, std::move(old_bounds), std::move(new_bounds)); } void TestWindowTreeClient::OnClientAreaChanged( uint32_t window_id, const gfx::Insets& new_client_area, const std::vector& new_additional_client_areas) {} void TestWindowTreeClient::OnTransientWindowAdded( uint32_t window_id, uint32_t transient_window_id) {} void TestWindowTreeClient::OnTransientWindowRemoved( uint32_t window_id, uint32_t transient_window_id) {} void TestWindowTreeClient::OnWindowHierarchyChanged( uint32_t window, uint32_t old_parent, uint32_t new_parent, std::vector windows) { tracker_.OnWindowHierarchyChanged(window, old_parent, new_parent, std::move(windows)); } void TestWindowTreeClient::OnWindowReordered(uint32_t window_id, uint32_t relative_window_id, mojom::OrderDirection direction) { tracker_.OnWindowReordered(window_id, relative_window_id, direction); } void TestWindowTreeClient::OnWindowDeleted(uint32_t window) { tracker_.OnWindowDeleted(window); } void TestWindowTreeClient::OnWindowVisibilityChanged(uint32_t window, bool visible) { tracker_.OnWindowVisibilityChanged(window, visible); } void TestWindowTreeClient::OnWindowOpacityChanged(uint32_t window, float old_opacity, float new_opacity) { tracker_.OnWindowOpacityChanged(window, new_opacity); } void TestWindowTreeClient::OnWindowParentDrawnStateChanged(uint32_t window, bool drawn) { tracker_.OnWindowParentDrawnStateChanged(window, drawn); } void TestWindowTreeClient::OnWindowSharedPropertyChanged( uint32_t window, const std::string& name, const base::Optional>& new_data) { tracker_.OnWindowSharedPropertyChanged(window, name, new_data); } void TestWindowTreeClient::OnWindowInputEvent(uint32_t event_id, uint32_t window, int64_t display_id, std::unique_ptr event, bool matches_pointer_watcher) { tracker_.OnWindowInputEvent(window, *event.get(), matches_pointer_watcher); } void TestWindowTreeClient::OnPointerEventObserved( std::unique_ptr event, uint32_t window_id, int64_t display_id) { tracker_.OnPointerEventObserved(*event.get(), window_id); } void TestWindowTreeClient::OnWindowFocused(uint32_t focused_window_id) { tracker_.OnWindowFocused(focused_window_id); } void TestWindowTreeClient::OnWindowPredefinedCursorChanged( uint32_t window_id, mojom::Cursor cursor_id) { tracker_.OnWindowPredefinedCursorChanged(window_id, cursor_id); } void TestWindowTreeClient::OnWindowSurfaceChanged( Id window_id, const cc::SurfaceInfo& surface_info) {} void TestWindowTreeClient::OnDragDropStart( const std::unordered_map>& mime_data) {} void TestWindowTreeClient::OnDragEnter(uint32_t window, uint32_t key_state, const gfx::Point& position, uint32_t effect_bitmask, const OnDragEnterCallback& callback) {} void TestWindowTreeClient::OnDragOver(uint32_t window, uint32_t key_state, const gfx::Point& position, uint32_t effect_bitmask, const OnDragOverCallback& callback) {} void TestWindowTreeClient::OnDragLeave(uint32_t window) {} void TestWindowTreeClient::OnCompleteDrop( uint32_t window, uint32_t key_state, const gfx::Point& position, uint32_t effect_bitmask, const OnCompleteDropCallback& callback) {} void TestWindowTreeClient::OnPerformDragDropCompleted(uint32_t window, bool success, uint32_t action_taken) {} void TestWindowTreeClient::OnDragDropDone() {} void TestWindowTreeClient::OnChangeCompleted(uint32_t change_id, bool success) { if (record_on_change_completed_) tracker_.OnChangeCompleted(change_id, success); } void TestWindowTreeClient::RequestClose(uint32_t window_id) {} void TestWindowTreeClient::GetWindowManager( mojo::AssociatedInterfaceRequest internal) {} // TestWindowTreeBinding ------------------------------------------------------ TestWindowTreeBinding::TestWindowTreeBinding( WindowTree* tree, std::unique_ptr client) : WindowTreeBinding(client.get()), tree_(tree), client_(std::move(client)) {} TestWindowTreeBinding::~TestWindowTreeBinding() {} mojom::WindowManager* TestWindowTreeBinding::GetWindowManager() { if (!window_manager_.get()) window_manager_ = base::MakeUnique(); return window_manager_.get(); } void TestWindowTreeBinding::SetIncomingMethodCallProcessingPaused(bool paused) { is_paused_ = paused; } mojom::WindowTreeClient* TestWindowTreeBinding::CreateClientForShutdown() { DCHECK(!client_after_reset_); client_after_reset_ = base::MakeUnique(); return client_after_reset_.get(); } // TestWindowServerDelegate ---------------------------------------------- TestWindowServerDelegate::TestWindowServerDelegate() {} TestWindowServerDelegate::~TestWindowServerDelegate() {} void TestWindowServerDelegate::StartDisplayInit() {} void TestWindowServerDelegate::OnNoMoreDisplays() { got_on_no_more_displays_ = true; } std::unique_ptr TestWindowServerDelegate::CreateWindowTreeBinding( BindingType type, ws::WindowServer* window_server, ws::WindowTree* tree, mojom::WindowTreeRequest* tree_request, mojom::WindowTreeClientPtr* client) { std::unique_ptr binding = base::MakeUnique(tree); bindings_.push_back(binding.get()); return std::move(binding); } bool TestWindowServerDelegate::IsTestConfig() const { return true; } // WindowServerTestHelper --------------------------------------------------- WindowServerTestHelper::WindowServerTestHelper() : cursor_id_(mojom::Cursor::CURSOR_NULL), platform_display_factory_(&cursor_id_) { // Some tests create their own message loop, for example to add a task runner. if (!base::MessageLoop::current()) message_loop_ = base::MakeUnique(); PlatformDisplay::set_factory_for_testing(&platform_display_factory_); window_server_ = base::MakeUnique(&window_server_delegate_); window_server_delegate_.set_window_server(window_server_.get()); } WindowServerTestHelper::~WindowServerTestHelper() { // Destroy |window_server_| while the message-loop is still alive. window_server_.reset(); } // WindowEventTargetingHelper ------------------------------------------------ WindowEventTargetingHelper::WindowEventTargetingHelper() { display_ = new Display(window_server()); display_binding_ = new TestDisplayBinding(window_server()); display_->Init(PlatformDisplayInitParams(), base::WrapUnique(display_binding_)); wm_client_ = ws_test_helper_.window_server_delegate()->last_client(); wm_client_->tracker()->changes()->clear(); } WindowEventTargetingHelper::~WindowEventTargetingHelper() {} ServerWindow* WindowEventTargetingHelper::CreatePrimaryTree( const gfx::Rect& root_window_bounds, const gfx::Rect& window_bounds) { WindowTree* wm_tree = window_server()->GetTreeWithId(1); const ClientWindowId embed_window_id(WindowIdToTransportId( WindowId(wm_tree->id(), next_primary_tree_window_id_++))); EXPECT_TRUE(wm_tree->NewWindow(embed_window_id, ServerWindow::Properties())); EXPECT_TRUE(wm_tree->SetWindowVisibility(embed_window_id, true)); EXPECT_TRUE(wm_tree->AddWindow(FirstRootId(wm_tree), embed_window_id)); display_->root_window()->SetBounds(root_window_bounds); mojom::WindowTreeClientPtr client; mojom::WindowTreeClientRequest client_request(&client); ws_test_helper_.window_server_delegate()->last_client()->Bind( std::move(client_request)); const uint32_t embed_flags = 0; wm_tree->Embed(embed_window_id, std::move(client), embed_flags); ServerWindow* embed_window = wm_tree->GetWindowByClientId(embed_window_id); embed_window->set_event_targeting_policy( mojom::EventTargetingPolicy::DESCENDANTS_ONLY); WindowTree* tree1 = window_server()->GetTreeWithRoot(embed_window); EXPECT_NE(nullptr, tree1); EXPECT_NE(tree1, wm_tree); WindowTreeTestApi(tree1).set_user_id(wm_tree->user_id()); embed_window->SetBounds(window_bounds); return embed_window; } void WindowEventTargetingHelper::CreateSecondaryTree( ServerWindow* embed_window, const gfx::Rect& window_bounds, TestWindowTreeClient** out_client, WindowTree** window_tree, ServerWindow** window) { WindowTree* tree1 = window_server()->GetTreeWithRoot(embed_window); ASSERT_TRUE(tree1 != nullptr); const ClientWindowId child1_id( WindowIdToTransportId(WindowId(tree1->id(), 1))); ASSERT_TRUE(tree1->NewWindow(child1_id, ServerWindow::Properties())); ServerWindow* child1 = tree1->GetWindowByClientId(child1_id); ASSERT_TRUE(child1); EXPECT_TRUE(tree1->AddWindow(ClientWindowIdForWindow(tree1, embed_window), child1_id)); tree1->GetDisplay(embed_window)->AddActivationParent(embed_window); child1->SetVisible(true); child1->SetBounds(window_bounds); TestWindowTreeClient* embed_client = ws_test_helper_.window_server_delegate()->last_client(); embed_client->tracker()->changes()->clear(); wm_client_->tracker()->changes()->clear(); *out_client = embed_client; *window_tree = tree1; *window = child1; } void WindowEventTargetingHelper::SetTaskRunner( scoped_refptr task_runner) { base::MessageLoop::current()->SetTaskRunner(task_runner); } // ---------------------------------------------------------------------------- void AddWindowManager(WindowServer* window_server, const UserId& user_id) { window_server->window_manager_window_tree_factory_set() ->Add(user_id, nullptr) ->CreateWindowTree(nullptr, nullptr); } display::ViewportMetrics MakeViewportMetrics(int origin_x, int origin_y, int width_pixels, int height_pixels, float scale_factor) { display::ViewportMetrics metrics; gfx::Size scaled_size = gfx::ConvertSizeToDIP( scale_factor, gfx::Size(width_pixels, height_pixels)); metrics.bounds = gfx::Rect(gfx::Point(origin_x, origin_y), scaled_size); metrics.work_area = metrics.bounds; metrics.pixel_size = gfx::Size(width_pixels, height_pixels); metrics.device_scale_factor = scale_factor; return metrics; } ServerWindow* FirstRoot(WindowTree* tree) { return tree->roots().size() == 1u ? tree->GetWindow((*tree->roots().begin())->id()) : nullptr; } ClientWindowId FirstRootId(WindowTree* tree) { ServerWindow* first_root = FirstRoot(tree); return first_root ? ClientWindowIdForWindow(tree, first_root) : ClientWindowId(); } ClientWindowId ClientWindowIdForWindow(WindowTree* tree, const ServerWindow* window) { ClientWindowId client_window_id; // If window isn't known we'll return 0, which should then error out. tree->IsWindowKnown(window, &client_window_id); return client_window_id; } ServerWindow* NewWindowInTree(WindowTree* tree, ClientWindowId* client_id) { return NewWindowInTreeWithParent(tree, FirstRoot(tree), client_id); } ServerWindow* NewWindowInTreeWithParent(WindowTree* tree, ServerWindow* parent, ClientWindowId* client_id) { if (!parent) return nullptr; ClientWindowId parent_client_id; if (!tree->IsWindowKnown(parent, &parent_client_id)) return nullptr; ClientWindowId client_window_id = NextUnusedClientWindowId(tree); if (!tree->NewWindow(client_window_id, ServerWindow::Properties())) return nullptr; if (!tree->SetWindowVisibility(client_window_id, true)) return nullptr; if (!tree->AddWindow(parent_client_id, client_window_id)) return nullptr; if (client_id) *client_id = client_window_id; return tree->GetWindowByClientId(client_window_id); } } // namespace test } // namespace ws } // namespace ui