Commit df114abe authored by Erik Chen's avatar Erik Chen Committed by Chromium LUCI CQ

lacros: Add TestController crosapi.

This CL adds plumbing for the TestController crosapi. This is only
available on Linux (non-device). It's used to provide a synchronization
primitive between crosapi and Wayland, and basic automation
capabilities. This is to support more full-fledged crosapi tests.

This CL moves the previous implementation of
WaitForLacrosToBeAvailableInAsh back into
screen_manager_lacros_browsertest.cc. This CL adds the new methods
WaitForWindowToBeAvailableInAsh and SendAndWaitForMouseClick.

Change-Id: Ia497c13f2b38af62eba3618cdca526b0206b32ec
Bug: 1157946
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2587707
Commit-Queue: Erik Chen <erikchen@chromium.org>
Reviewed-by: default avatarJames Cook <jamescook@chromium.org>
Cr-Commit-Position: refs/heads/master@{#836984}
parent 1c9ba0d0
...@@ -1068,8 +1068,12 @@ source_set("chromeos") { ...@@ -1068,8 +1068,12 @@ source_set("chromeos") {
"crosapi/screen_manager_ash.h", "crosapi/screen_manager_ash.h",
"crosapi/select_file_ash.cc", "crosapi/select_file_ash.cc",
"crosapi/select_file_ash.h", "crosapi/select_file_ash.h",
"crosapi/test_controller_ash.cc",
"crosapi/test_controller_ash.h",
"crosapi/test_mojo_connection_manager.cc", "crosapi/test_mojo_connection_manager.cc",
"crosapi/test_mojo_connection_manager.h", "crosapi/test_mojo_connection_manager.h",
"crosapi/window_util.cc",
"crosapi/window_util.h",
"crostini/ansible/ansible_management_service.cc", "crostini/ansible/ansible_management_service.cc",
"crostini/ansible/ansible_management_service.h", "crostini/ansible/ansible_management_service.h",
"crostini/ansible/ansible_management_service_factory.cc", "crostini/ansible/ansible_management_service_factory.cc",
......
...@@ -18,4 +18,7 @@ specific_include_rules = { ...@@ -18,4 +18,7 @@ specific_include_rules = {
# For Chrome OS-specific file manager parameters. # For Chrome OS-specific file manager parameters.
"+chrome/browser/ui/views/select_file_dialog_extension.h", "+chrome/browser/ui/views/select_file_dialog_extension.h",
], ],
"window_util\.cc":[
"+ash/shell.h",
],
} }
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include "chrome/browser/chromeos/crosapi/metrics_reporting_ash.h" #include "chrome/browser/chromeos/crosapi/metrics_reporting_ash.h"
#include "chrome/browser/chromeos/crosapi/screen_manager_ash.h" #include "chrome/browser/chromeos/crosapi/screen_manager_ash.h"
#include "chrome/browser/chromeos/crosapi/select_file_ash.h" #include "chrome/browser/chromeos/crosapi/select_file_ash.h"
#include "chrome/browser/chromeos/crosapi/test_controller_ash.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h" #include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/profiles/profile_manager.h"
...@@ -44,7 +45,8 @@ AshChromeServiceImpl::AshChromeServiceImpl( ...@@ -44,7 +45,8 @@ AshChromeServiceImpl::AshChromeServiceImpl(
metrics_reporting_ash_(std::make_unique<MetricsReportingAsh>( metrics_reporting_ash_(std::make_unique<MetricsReportingAsh>(
g_browser_process->local_state())), g_browser_process->local_state())),
screen_manager_ash_(std::make_unique<ScreenManagerAsh>()), screen_manager_ash_(std::make_unique<ScreenManagerAsh>()),
cert_database_ash_(std::make_unique<CertDatabaseAsh>()) { cert_database_ash_(std::make_unique<CertDatabaseAsh>()),
test_controller_ash_(std::make_unique<TestControllerAsh>()) {
// TODO(hidehiko): Remove non-critical log from here. // TODO(hidehiko): Remove non-critical log from here.
// Currently this is the signal that the connection is established. // Currently this is the signal that the connection is established.
LOG(WARNING) << "AshChromeService connected."; LOG(WARNING) << "AshChromeService connected.";
...@@ -161,6 +163,11 @@ void AshChromeServiceImpl::BindCertDatabase( ...@@ -161,6 +163,11 @@ void AshChromeServiceImpl::BindCertDatabase(
cert_database_ash_->BindReceiver(std::move(receiver)); cert_database_ash_->BindReceiver(std::move(receiver));
} }
void AshChromeServiceImpl::BindTestController(
mojo::PendingReceiver<mojom::TestController> receiver) {
test_controller_ash_->BindReceiver(std::move(receiver));
}
void AshChromeServiceImpl::OnLacrosStartup(mojom::LacrosInfoPtr lacros_info) { void AshChromeServiceImpl::OnLacrosStartup(mojom::LacrosInfoPtr lacros_info) {
BrowserManager::Get()->set_lacros_version(lacros_info->lacros_version); BrowserManager::Get()->set_lacros_version(lacros_info->lacros_version);
} }
......
...@@ -22,6 +22,7 @@ class MessageCenterAsh; ...@@ -22,6 +22,7 @@ class MessageCenterAsh;
class MetricsReportingAsh; class MetricsReportingAsh;
class ScreenManagerAsh; class ScreenManagerAsh;
class SelectFileAsh; class SelectFileAsh;
class TestControllerAsh;
// Implementation of AshChromeService. It provides a set of APIs that // Implementation of AshChromeService. It provides a set of APIs that
// lacros-chrome can call into. // lacros-chrome can call into.
...@@ -61,6 +62,8 @@ class AshChromeServiceImpl : public mojom::AshChromeService { ...@@ -61,6 +62,8 @@ class AshChromeServiceImpl : public mojom::AshChromeService {
void BindMediaSessionAudioFocusDebug( void BindMediaSessionAudioFocusDebug(
mojo::PendingReceiver<media_session::mojom::AudioFocusManagerDebug> mojo::PendingReceiver<media_session::mojom::AudioFocusManagerDebug>
receiver) override; receiver) override;
void BindTestController(
mojo::PendingReceiver<mojom::TestController> receiver) override;
private: private:
mojo::Receiver<mojom::AshChromeService> receiver_; mojo::Receiver<mojom::AshChromeService> receiver_;
...@@ -74,6 +77,7 @@ class AshChromeServiceImpl : public mojom::AshChromeService { ...@@ -74,6 +77,7 @@ class AshChromeServiceImpl : public mojom::AshChromeService {
std::unique_ptr<SelectFileAsh> select_file_ash_; std::unique_ptr<SelectFileAsh> select_file_ash_;
std::unique_ptr<FeedbackAsh> feedback_ash_; std::unique_ptr<FeedbackAsh> feedback_ash_;
std::unique_ptr<CertDatabaseAsh> cert_database_ash_; std::unique_ptr<CertDatabaseAsh> cert_database_ash_;
std::unique_ptr<TestControllerAsh> test_controller_ash_;
}; };
} // namespace crosapi } // namespace crosapi
......
...@@ -175,7 +175,7 @@ bool IsLacrosWindow(const aura::Window* window) { ...@@ -175,7 +175,7 @@ bool IsLacrosWindow(const aura::Window* window) {
base::flat_map<base::Token, uint32_t> GetInterfaceVersions() { base::flat_map<base::Token, uint32_t> GetInterfaceVersions() {
static_assert( static_assert(
crosapi::mojom::AshChromeService::Version_ == 8, crosapi::mojom::AshChromeService::Version_ == 9,
"if you add a new crosapi, please add it to the version map here"); "if you add a new crosapi, please add it to the version map here");
InterfaceVersions versions; InterfaceVersions versions;
AddVersion<crosapi::mojom::AccountManager>(&versions); AddVersion<crosapi::mojom::AccountManager>(&versions);
...@@ -188,6 +188,7 @@ base::flat_map<base::Token, uint32_t> GetInterfaceVersions() { ...@@ -188,6 +188,7 @@ base::flat_map<base::Token, uint32_t> GetInterfaceVersions() {
AddVersion<crosapi::mojom::MetricsReporting>(&versions); AddVersion<crosapi::mojom::MetricsReporting>(&versions);
AddVersion<crosapi::mojom::ScreenManager>(&versions); AddVersion<crosapi::mojom::ScreenManager>(&versions);
AddVersion<crosapi::mojom::SnapshotCapturer>(&versions); AddVersion<crosapi::mojom::SnapshotCapturer>(&versions);
AddVersion<crosapi::mojom::TestController>(&versions);
AddVersion<device::mojom::HidConnection>(&versions); AddVersion<device::mojom::HidConnection>(&versions);
AddVersion<device::mojom::HidManager>(&versions); AddVersion<device::mojom::HidManager>(&versions);
AddVersion<media_session::mojom::MediaControllerManager>(&versions); AddVersion<media_session::mojom::MediaControllerManager>(&versions);
......
...@@ -13,9 +13,9 @@ ...@@ -13,9 +13,9 @@
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/memory/ref_counted.h" #include "base/memory/ref_counted.h"
#include "base/numerics/ranges.h" #include "base/numerics/ranges.h"
#include "chrome/browser/chromeos/crosapi/window_util.h"
#include "chrome/browser/ui/views/select_file_dialog_extension.h" #include "chrome/browser/ui/views/select_file_dialog_extension.h"
#include "chromeos/crosapi/mojom/select_file.mojom.h" #include "chromeos/crosapi/mojom/select_file.mojom.h"
#include "components/exo/shell_surface_util.h"
#include "ui/shell_dialogs/select_file_dialog.h" #include "ui/shell_dialogs/select_file_dialog.h"
#include "ui/shell_dialogs/select_file_policy.h" #include "ui/shell_dialogs/select_file_policy.h"
#include "ui/shell_dialogs/selected_file_info.h" #include "ui/shell_dialogs/selected_file_info.h"
...@@ -53,32 +53,6 @@ ui::SelectFileDialog::FileTypeInfo::AllowedPaths GetUiAllowedPaths( ...@@ -53,32 +53,6 @@ ui::SelectFileDialog::FileTypeInfo::AllowedPaths GetUiAllowedPaths(
} }
} }
// Performs a depth-first search for a window with a given exo ShellSurface
// |app_id| starting at |root|.
aura::Window* FindWindowWithShellAppId(aura::Window* root,
const std::string& app_id) {
const std::string* id = exo::GetShellApplicationId(root);
if (id && *id == app_id)
return root;
for (aura::Window* child : root->children()) {
aura::Window* found = FindWindowWithShellAppId(child, app_id);
if (found)
return found;
}
return nullptr;
}
// Searches all displays for a ShellSurfaceBase with |app_id| and
// returns its aura::Window. Returns null if no such shell surface exists.
aura::Window* GetShellSurfaceWindow(const std::string& app_id) {
for (aura::Window* display_root : ash::Shell::GetAllRootWindows()) {
aura::Window* window = FindWindowWithShellAppId(display_root, app_id);
if (window)
return window;
}
return nullptr;
}
// Manages a single open/save dialog. There may be multiple dialogs showing at // Manages a single open/save dialog. There may be multiple dialogs showing at
// the same time. Deletes itself when the dialog is closed. // the same time. Deletes itself when the dialog is closed.
class SelectFileDialogHolder : public ui::SelectFileDialog::Listener { class SelectFileDialogHolder : public ui::SelectFileDialog::Listener {
......
// Copyright 2020 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 "chrome/browser/chromeos/crosapi/test_controller_ash.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/chromeos/crosapi/window_util.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_tree_host.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/events/event_source.h"
#include "ui/gfx/geometry/point.h"
namespace crosapi {
namespace {
// Returns whether the dispatcher or target was destroyed.
bool DispatchMouseEvent(aura::Window* window, ui::EventType type) {
const gfx::Point center = window->bounds().CenterPoint();
ui::MouseEvent press(type, center, center, ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON);
ui::EventDispatchDetails dispatch_details =
window->GetHost()->GetEventSource()->SendEventToSink(&press);
return dispatch_details.dispatcher_destroyed ||
dispatch_details.target_destroyed;
}
} // namespace
TestControllerAsh::TestControllerAsh() = default;
TestControllerAsh::~TestControllerAsh() = default;
void TestControllerAsh::BindReceiver(
mojo::PendingReceiver<mojom::TestController> receiver) {
// This interface is not available on production devices. It's only needed for
// tests that run on Linux-chrome so no reason to expose it.
#if BUILDFLAG(IS_CHROMEOS_DEVICE)
LOG(ERROR) << "Ash does not support TestController on devices";
#else
receivers_.Add(this, std::move(receiver));
#endif
}
void TestControllerAsh::DoesWindowExist(const std::string& window_id,
DoesWindowExistCallback callback) {
aura::Window* window = GetShellSurfaceWindow(window_id);
std::move(callback).Run(window != nullptr);
}
void TestControllerAsh::ClickWindow(const std::string& window_id) {
aura::Window* window = GetShellSurfaceWindow(window_id);
if (!window)
return;
bool destroyed = DispatchMouseEvent(window, ui::ET_MOUSE_PRESSED);
if (!destroyed) {
DispatchMouseEvent(window, ui::ET_MOUSE_RELEASED);
}
}
} // namespace crosapi
// Copyright 2020 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.
#ifndef CHROME_BROWSER_CHROMEOS_CROSAPI_TEST_CONTROLLER_ASH_H_
#define CHROME_BROWSER_CHROMEOS_CROSAPI_TEST_CONTROLLER_ASH_H_
#include "chromeos/crosapi/mojom/test_controller.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
namespace crosapi {
// This class is the ash-chrome implementation of the TestController interface.
// This class must only be used from the main thread.
class TestControllerAsh : public mojom::TestController {
public:
TestControllerAsh();
TestControllerAsh(const TestControllerAsh&) = delete;
TestControllerAsh& operator=(const TestControllerAsh&) = delete;
~TestControllerAsh() override;
void BindReceiver(mojo::PendingReceiver<mojom::TestController> receiver);
// crosapi::mojom::TestController:
void DoesWindowExist(const std::string& window_id,
DoesWindowExistCallback callback) override;
void ClickWindow(const std::string& window_id) override;
private:
// This class supports any number of connections. This allows multiple
// crosapi clients.
mojo::ReceiverSet<mojom::TestController> receivers_;
};
} // namespace crosapi
#endif // CHROME_BROWSER_CHROMEOS_CROSAPI_TEST_CONTROLLER_ASH_H_
// Copyright 2020 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 "chrome/browser/chromeos/crosapi/window_util.h"
#include "ash/shell.h"
#include "components/exo/shell_surface_util.h"
#include "ui/aura/window.h"
namespace crosapi {
namespace {
// Performs a depth-first search for a window with a given exo ShellSurface
// |app_id| starting at |root|.
aura::Window* FindWindowWithShellAppId(aura::Window* root,
const std::string& app_id) {
const std::string* id = exo::GetShellApplicationId(root);
if (id && *id == app_id)
return root;
for (aura::Window* child : root->children()) {
aura::Window* found = FindWindowWithShellAppId(child, app_id);
if (found)
return found;
}
return nullptr;
}
} // namespace
aura::Window* GetShellSurfaceWindow(const std::string& app_id) {
for (aura::Window* display_root : ash::Shell::GetAllRootWindows()) {
aura::Window* window = FindWindowWithShellAppId(display_root, app_id);
if (window)
return window;
}
return nullptr;
}
} // namespace crosapi
// Copyright 2020 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.
//
// This file contains window utility functions for ash's implementation of
// crosapi.
#ifndef CHROME_BROWSER_CHROMEOS_CROSAPI_WINDOW_UTIL_H_
#define CHROME_BROWSER_CHROMEOS_CROSAPI_WINDOW_UTIL_H_
#include <string>
namespace aura {
class Window;
} // namespace aura
namespace crosapi {
// Searches all displays for a ShellSurfaceBase with |app_id| and
// returns its aura::Window. Returns null if no such shell surface exists.
aura::Window* GetShellSurfaceWindow(const std::string& app_id);
} // namespace crosapi
#endif // CHROME_BROWSER_CHROMEOS_CROSAPI_WINDOW_UTIL_H_
...@@ -4,98 +4,105 @@ ...@@ -4,98 +4,105 @@
#include "chrome/browser/lacros/browser_test_util.h" #include "chrome/browser/lacros/browser_test_util.h"
#include <string>
#include <vector>
#include "base/bind.h" #include "base/bind.h"
#include "base/rand_util.h" #include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "base/timer/timer.h" #include "base/timer/timer.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chromeos/crosapi/mojom/screen_manager.mojom.h"
#include "chromeos/lacros/lacros_chrome_service_impl.h" #include "chromeos/lacros/lacros_chrome_service_impl.h"
#include "mojo/public/cpp/bindings/pending_remote.h" #include "ui/aura/env.h"
#include "mojo/public/cpp/bindings/remote.h" #include "ui/aura/window.h"
#include "mojo/public/cpp/bindings/sync_call_restrictions.h" #include "ui/aura/window_event_dispatcher_observer.h"
#include "ui/base/l10n/l10n_util.h" #include "ui/aura/window_tree_host_platform.h"
#include "url/gurl.h" #include "ui/platform_window/platform_window.h"
namespace { namespace {
const char* kLacrosPageTitleFormat = "Title Of Lacros Browser Test %lu";
const char* kLacrosPageTitleHTMLFormat =
"<html><head><title>%s</title></head>"
"<body>This page has a title.</body></html>";
mojo::Remote<crosapi::mojom::SnapshotCapturer> GetWindowCapturer() {
auto* lacros_chrome_service = chromeos::LacrosChromeServiceImpl::Get();
mojo::PendingRemote<crosapi::mojom::ScreenManager> pending_screen_manager;
lacros_chrome_service->BindScreenManagerReceiver(
pending_screen_manager.InitWithNewPipeAndPassReceiver());
mojo::Remote<crosapi::mojom::ScreenManager> screen_manager; std::string GetWindowUniqueId(aura::Window* window) {
screen_manager.Bind(std::move(pending_screen_manager)); DCHECK(window);
DCHECK(window->IsRootWindow());
mojo::Remote<crosapi::mojom::SnapshotCapturer> capturer; // On desktop aura there is one WindowTreeHost per top-level window.
screen_manager->GetWindowCapturer(capturer.BindNewPipeAndPassReceiver()); aura::WindowTreeHost* window_tree_host = window->GetHost();
DCHECK(window_tree_host);
return capturer; // Lacros is based on Ozone/Wayland, which uses PlatformWindow and
// aura::WindowTreeHostPlatform.
aura::WindowTreeHostPlatform* window_tree_host_platform =
static_cast<aura::WindowTreeHostPlatform*>(window_tree_host);
return window_tree_host_platform->platform_window()->GetWindowUniqueId();
} }
// Used to find the window corresponding to the test page. // Observes Aura and waits for both a mouse down and a mouse up event.
uint64_t WaitForWindow(std::string title) { class AuraObserver : public aura::WindowEventDispatcherObserver {
mojo::Remote<crosapi::mojom::SnapshotCapturer> capturer = GetWindowCapturer(); public:
explicit AuraObserver(base::RunLoop* run_loop) : run_loop_(run_loop) {}
void OnWindowEventDispatcherStartedProcessing(
aura::WindowEventDispatcher* dispatcher,
const ui::Event& event) override {
if (event.type() == ui::EventType::ET_MOUSE_PRESSED)
mouse_down_seen_ = true;
if (mouse_down_seen_ && event.type() == ui::EventType::ET_MOUSE_RELEASED)
mouse_up_seen_ = true;
if (mouse_down_seen_ && mouse_up_seen_)
run_loop_->Quit();
}
private:
// Must outlive the observer.
base::RunLoop* run_loop_;
bool mouse_down_seen_ = false;
bool mouse_up_seen_ = false;
};
base::RunLoop run_loop; } // namespace
uint64_t window_id;
base::string16 tab_title(base::ASCIIToUTF16(title)); void WaitForWindowToBeAvailableInAsh(aura::Window* window) {
auto look_for_window = base::BindRepeating( DCHECK(window->IsRootWindow());
[](mojo::Remote<crosapi::mojom::SnapshotCapturer>* capturer, std::string id = GetWindowUniqueId(window);
base::RunLoop* run_loop, uint64_t* window_id,
base::string16 tab_title) { base::RunLoop outer_loop;
std::string expected_window_title = l10n_util::GetStringFUTF8( auto wait_for_window = base::BindRepeating(
IDS_BROWSER_WINDOW_TITLE_FORMAT, tab_title); [](base::RunLoop* outer_loop, const std::string& id) {
std::vector<crosapi::mojom::SnapshotSourcePtr> windows; auto* lacros_chrome_service = chromeos::LacrosChromeServiceImpl::Get();
{ CHECK(lacros_chrome_service->IsTestControllerAvailable());
mojo::ScopedAllowSyncCallForTesting allow_sync_call;
(*capturer)->ListSources(&windows); base::RunLoop inner_loop(base::RunLoop::Type::kNestableTasksAllowed);
} bool exists = false;
for (auto& window : windows) { lacros_chrome_service->test_controller_remote()->DoesWindowExist(
if (window->title == expected_window_title) { id, base::BindOnce(
if (window_id) [](base::RunLoop* loop, bool* out_exist, bool exist) {
(*window_id) = window->id; *out_exist = std::move(exist);
run_loop->Quit(); loop->Quit();
break; },
} &inner_loop, &exists));
} inner_loop.Run();
if (exists)
outer_loop->Quit();
}, },
&capturer, &run_loop, &window_id, std::move(tab_title)); &outer_loop, id);
// When the browser test start, there is no guarantee that the window is // Wait for the window to be available.
// open from ash's perspective.
base::RepeatingTimer timer; base::RepeatingTimer timer;
timer.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(1), timer.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(1),
std::move(look_for_window)); std::move(wait_for_window));
run_loop.Run(); outer_loop.Run();
return window_id;
} }
} // namespace // Sends a TestController message to Ash to send a mouse click to this |window|.
// Waits for both the mouse-down and the mouse-up events to be seen by
// |window|. The AuraObserver only waits for the up-event to start processing
// before quitting the run loop.
void SendAndWaitForMouseClick(aura::Window* window) {
DCHECK(window->IsRootWindow());
std::string id = GetWindowUniqueId(window);
uint64_t WaitForLacrosToBeAvailableInAsh(Browser* browser) { base::RunLoop run_loop;
// Generate a random window title so that multiple lacros_chrome_browsertests std::unique_ptr<AuraObserver> obs = std::make_unique<AuraObserver>(&run_loop);
// can run at the same time without confusing windows. aura::Env::GetInstance()->AddWindowEventDispatcherObserver(obs.get());
std::string title =
base::StringPrintf(kLacrosPageTitleFormat, base::RandUint64()); auto* lacros_chrome_service = chromeos::LacrosChromeServiceImpl::Get();
std::string html = lacros_chrome_service->test_controller_remote()->ClickWindow(id);
base::StringPrintf(kLacrosPageTitleHTMLFormat, title.c_str()); run_loop.Run();
GURL url(std::string("data:text/html,") + html); aura::Env::GetInstance()->RemoveWindowEventDispatcherObserver(obs.get());
ui_test_utils::NavigateToURL(browser, url);
return WaitForWindow(std::move(title));
} }
...@@ -7,18 +7,27 @@ ...@@ -7,18 +7,27 @@
#ifndef CHROME_BROWSER_LACROS_BROWSER_TEST_UTIL_H_ #ifndef CHROME_BROWSER_LACROS_BROWSER_TEST_UTIL_H_
#define CHROME_BROWSER_LACROS_BROWSER_TEST_UTIL_H_ #define CHROME_BROWSER_LACROS_BROWSER_TEST_UTIL_H_
#include <stdint.h> namespace aura {
class Window;
} // namespace aura
class Browser; // Some crosapi methods rely on the assumption that ash/exo are aware of the
// existence of a Lacros Window. Wayland is an async protocol that uses a
// different communication channel than crosapi. This method provides a
// synchronization mechanism for window creation and identification.
//
// |window| must be a root window.
void WaitForWindowToBeAvailableInAsh(aura::Window* window);
// Some crosapi or Wayland methods rely on assumptions in exo about Window // This function relies on |window| already being available in ash. It prompts
// focus, or other window attributes. Since Wayland is an async protocol, we // ash to send the Wayland events associated with a mouse click to the |window|.
// sometimes need to first wait for exo/Wayland to be aware of the Lacros // It waits for the events to be seen by Aura.
// windows before proceeding. We use the Snapshot crosapi as a convenient //
// mechanism for doing so. // This function is necessary to prime the Wayland serial_id. The serial_id is
// generated by the Wayland server and sent to Wayland client along-side user
// events. This serial_id is a required input to Wayland clipboard APIs.
// //
// |browser| is a browser instance that will be navigated to a fixed URL. // |window| must be a root window.
// Returns the window ID from the snapshot crosapi. void SendAndWaitForMouseClick(aura::Window* window);
uint64_t WaitForLacrosToBeAvailableInAsh(Browser* browser);
#endif // CHROME_BROWSER_LACROS_BROWSER_TEST_UTIL_H_ #endif // CHROME_BROWSER_LACROS_BROWSER_TEST_UTIL_H_
...@@ -6,8 +6,17 @@ ...@@ -6,8 +6,17 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "base/bind.h"
#include "base/rand_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/browser/lacros/browser_test_util.h" #include "chrome/browser/lacros/browser_test_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/grit/chromium_strings.h"
#include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chromeos/crosapi/mojom/screen_manager.mojom.h" #include "chromeos/crosapi/mojom/screen_manager.mojom.h"
#include "chromeos/lacros/lacros_chrome_service_impl.h" #include "chromeos/lacros/lacros_chrome_service_impl.h"
#include "content/public/test/browser_test.h" #include "content/public/test/browser_test.h"
...@@ -16,6 +25,86 @@ ...@@ -16,6 +25,86 @@
#include "mojo/public/cpp/bindings/remote.h" #include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/sync_call_restrictions.h" #include "mojo/public/cpp/bindings/sync_call_restrictions.h"
#include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
namespace {
const char* kLacrosPageTitleFormat = "Title Of Lacros Browser Test %lu";
const char* kLacrosPageTitleHTMLFormat =
"<html><head><title>%s</title></head>"
"<body>This page has a title.</body></html>";
mojo::Remote<crosapi::mojom::SnapshotCapturer> GetWindowCapturer() {
auto* lacros_chrome_service = chromeos::LacrosChromeServiceImpl::Get();
mojo::PendingRemote<crosapi::mojom::ScreenManager> pending_screen_manager;
lacros_chrome_service->BindScreenManagerReceiver(
pending_screen_manager.InitWithNewPipeAndPassReceiver());
mojo::Remote<crosapi::mojom::ScreenManager> screen_manager;
screen_manager.Bind(std::move(pending_screen_manager));
mojo::Remote<crosapi::mojom::SnapshotCapturer> capturer;
screen_manager->GetWindowCapturer(capturer.BindNewPipeAndPassReceiver());
return capturer;
}
// Used to find the window corresponding to the test page.
uint64_t WaitForWindow(std::string title) {
mojo::Remote<crosapi::mojom::SnapshotCapturer> capturer = GetWindowCapturer();
base::RunLoop run_loop;
uint64_t window_id;
base::string16 tab_title(base::ASCIIToUTF16(title));
auto look_for_window = base::BindRepeating(
[](mojo::Remote<crosapi::mojom::SnapshotCapturer>* capturer,
base::RunLoop* run_loop, uint64_t* window_id,
base::string16 tab_title) {
std::string expected_window_title = l10n_util::GetStringFUTF8(
IDS_BROWSER_WINDOW_TITLE_FORMAT, tab_title);
std::vector<crosapi::mojom::SnapshotSourcePtr> windows;
{
mojo::ScopedAllowSyncCallForTesting allow_sync_call;
(*capturer)->ListSources(&windows);
}
for (auto& window : windows) {
if (window->title == expected_window_title) {
if (window_id)
(*window_id) = window->id;
run_loop->Quit();
break;
}
}
},
&capturer, &run_loop, &window_id, std::move(tab_title));
// When the browser test start, there is no guarantee that the window is
// open from ash's perspective.
base::RepeatingTimer timer;
timer.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(1),
std::move(look_for_window));
run_loop.Run();
return window_id;
}
// |browser| is a browser instance that will be navigated to a fixed URL.
// Returns the window ID from the snapshot crosapi.
uint64_t WaitForLacrosToBeAvailableInAsh(Browser* browser) {
// Generate a random window title so that multiple lacros_chrome_browsertests
// can run at the same time without confusing windows.
std::string title =
base::StringPrintf(kLacrosPageTitleFormat, base::RandUint64());
std::string html =
base::StringPrintf(kLacrosPageTitleHTMLFormat, title.c_str());
GURL url(std::string("data:text/html,") + html);
ui_test_utils::NavigateToURL(browser, url);
return WaitForWindow(std::move(title));
}
} // namespace
class ScreenManagerLacrosBrowserTest : public InProcessBrowserTest { class ScreenManagerLacrosBrowserTest : public InProcessBrowserTest {
protected: protected:
......
...@@ -18,6 +18,7 @@ mojom("mojom") { ...@@ -18,6 +18,7 @@ mojom("mojom") {
"notification.mojom", "notification.mojom",
"screen_manager.mojom", "screen_manager.mojom",
"select_file.mojom", "select_file.mojom",
"test_controller.mojom",
] ]
disable_variants = true disable_variants = true
......
...@@ -13,6 +13,7 @@ import "chromeos/crosapi/mojom/message_center.mojom"; ...@@ -13,6 +13,7 @@ import "chromeos/crosapi/mojom/message_center.mojom";
import "chromeos/crosapi/mojom/metrics_reporting.mojom"; import "chromeos/crosapi/mojom/metrics_reporting.mojom";
import "chromeos/crosapi/mojom/screen_manager.mojom"; import "chromeos/crosapi/mojom/screen_manager.mojom";
import "chromeos/crosapi/mojom/select_file.mojom"; import "chromeos/crosapi/mojom/select_file.mojom";
import "chromeos/crosapi/mojom/test_controller.mojom";
import "mojo/public/mojom/base/big_string.mojom"; import "mojo/public/mojom/base/big_string.mojom";
import "mojo/public/mojom/base/file_path.mojom"; import "mojo/public/mojom/base/file_path.mojom";
import "mojo/public/mojom/base/token.mojom"; import "mojo/public/mojom/base/token.mojom";
...@@ -41,8 +42,8 @@ struct LacrosInfo { ...@@ -41,8 +42,8 @@ struct LacrosInfo {
// milestone when you added it, to help us reason about compatibility between // milestone when you added it, to help us reason about compatibility between
// lacros-chrome and older ash-chrome binaries. // lacros-chrome and older ash-chrome binaries.
// //
// Next version: 9 // Next version: 10
// Next method id: 14 // Next method id: 15
[Stable, Uuid="8b79c34f-2bf8-4499-979a-b17cac522c1e"] [Stable, Uuid="8b79c34f-2bf8-4499-979a-b17cac522c1e"]
interface AshChromeService { interface AshChromeService {
// Binds Chrome OS Account Manager for Identity management. // Binds Chrome OS Account Manager for Identity management.
...@@ -108,6 +109,11 @@ interface AshChromeService { ...@@ -108,6 +109,11 @@ interface AshChromeService {
// Added in M89. // Added in M89.
[MinVersion=7] BindCertDatabase@12(pending_receiver<CertDatabase> receiver); [MinVersion=7] BindCertDatabase@12(pending_receiver<CertDatabase> receiver);
// Binds the test controller service, which tests can use to mutate ash. This
// is not available on production devices.
[MinVersion=9] BindTestController@14(
pending_receiver<TestController> receiver);
// Passes generic lacros information such as lacros version, etc into ash // Passes generic lacros information such as lacros version, etc into ash
// in |lacros_info| during startup. // in |lacros_info| during startup.
// Added in M87. // Added in M87.
......
// Copyright 2020 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.
module crosapi.mojom;
// This interface is implemented by Ash-Chrome.
// This interface provides tests a mechanism to mutate or query ash.
// In the future, this interface may merge with an automation or a11y interface.
[Stable, Uuid="1f93f9d7-e466-466c-a675-c21b48cf30d3"]
interface TestController {
// Queries whether a window with the given |window_id| exists. |window_id|
// should be obtained from PlatformWindow::GetWindowUniqueId(). A typical
// format might be: "org.chromium.lacros.9A82A161B2A0B9BADF75E9BB958B9FCB"
DoesWindowExist@0(string window_id) => (bool exist);
// Clicks the middle of the window. Assumes that the window exists and is
// visible on screen. |window_id| should be obtained from
// PlatformWindow::GetWindowUniqueId(). A typical format might be:
// "org.chromium.lacros.9A82A161B2A0B9BADF75E9BB958B9FCB"
ClickWindow@1(string window_id);
};
...@@ -14,6 +14,7 @@ component("lacros") { ...@@ -14,6 +14,7 @@ component("lacros") {
defines = [ "IS_CHROMEOS_LACROS_IMPL" ] defines = [ "IS_CHROMEOS_LACROS_IMPL" ]
deps = [ deps = [
"//base", "//base",
"//build:chromeos_buildflags",
"//chromeos/crosapi/mojom", "//chromeos/crosapi/mojom",
"//chromeos/startup", "//chromeos/startup",
"//mojo/public/cpp/bindings", "//mojo/public/cpp/bindings",
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "base/logging.h" #include "base/logging.h"
#include "base/task/task_traits.h" #include "base/task/task_traits.h"
#include "base/task/thread_pool.h" #include "base/task/thread_pool.h"
#include "build/chromeos_buildflags.h"
#include "chromeos/crosapi/mojom/crosapi.mojom.h" #include "chromeos/crosapi/mojom/crosapi.mojom.h"
#include "chromeos/lacros/lacros_chrome_service_delegate.h" #include "chromeos/lacros/lacros_chrome_service_delegate.h"
#include "chromeos/startup/startup.h" #include "chromeos/startup/startup.h"
...@@ -237,6 +238,12 @@ class LacrosChromeServiceNeverBlockingState ...@@ -237,6 +238,12 @@ class LacrosChromeServiceNeverBlockingState
ash_chrome_service_->BindMetricsReporting(std::move(receiver)); ash_chrome_service_->BindMetricsReporting(std::move(receiver));
} }
void BindTestControllerReceiver(
mojo::PendingReceiver<crosapi::mojom::TestController> pending_receiver) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ash_chrome_service_->BindTestController(std::move(pending_receiver));
}
base::WeakPtr<LacrosChromeServiceNeverBlockingState> GetWeakPtr() { base::WeakPtr<LacrosChromeServiceNeverBlockingState> GetWeakPtr() {
return weak_factory_.GetWeakPtr(); return weak_factory_.GetWeakPtr();
} }
...@@ -421,6 +428,16 @@ void LacrosChromeServiceImpl::BindReceiver( ...@@ -421,6 +428,16 @@ void LacrosChromeServiceImpl::BindReceiver(
&LacrosChromeServiceNeverBlockingState::BindFileManagerReceiver, &LacrosChromeServiceNeverBlockingState::BindFileManagerReceiver,
weak_sequenced_state_, std::move(pending_receiver))); weak_sequenced_state_, std::move(pending_receiver)));
} }
if (IsTestControllerAvailable()) {
mojo::PendingReceiver<crosapi::mojom::TestController> pending_receiver =
test_controller_remote_.BindNewPipeAndPassReceiver();
never_blocking_sequence_->PostTask(
FROM_HERE,
base::BindOnce(
&LacrosChromeServiceNeverBlockingState::BindTestControllerReceiver,
weak_sequenced_state_, std::move(pending_receiver)));
}
} }
// static // static
...@@ -486,6 +503,19 @@ bool LacrosChromeServiceImpl::IsFileManagerAvailable() { ...@@ -486,6 +503,19 @@ bool LacrosChromeServiceImpl::IsFileManagerAvailable() {
AshChromeService::MethodMinVersions::kBindFileManagerMinVersion; AshChromeService::MethodMinVersions::kBindFileManagerMinVersion;
} }
bool LacrosChromeServiceImpl::IsTestControllerAvailable() {
#if BUILDFLAG(IS_CHROMEOS_DEVICE)
// The test controller is not available on production devices as tests only
// run on Linux.
return false;
#else
base::Optional<uint32_t> version = AshChromeServiceVersion();
return version &&
version.value() >=
AshChromeService::MethodMinVersions::kBindTestControllerMinVersion;
#endif
}
bool LacrosChromeServiceImpl::IsScreenManagerAvailable() { bool LacrosChromeServiceImpl::IsScreenManagerAvailable() {
base::Optional<uint32_t> version = AshChromeServiceVersion(); base::Optional<uint32_t> version = AshChromeServiceVersion();
return version && return version &&
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include "chromeos/crosapi/mojom/metrics_reporting.mojom.h" #include "chromeos/crosapi/mojom/metrics_reporting.mojom.h"
#include "chromeos/crosapi/mojom/screen_manager.mojom.h" #include "chromeos/crosapi/mojom/screen_manager.mojom.h"
#include "chromeos/crosapi/mojom/select_file.mojom.h" #include "chromeos/crosapi/mojom/select_file.mojom.h"
#include "chromeos/crosapi/mojom/test_controller.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h" #include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h" #include "mojo/public/cpp/bindings/remote.h"
...@@ -191,6 +192,16 @@ class COMPONENT_EXPORT(CHROMEOS_LACROS) LacrosChromeServiceImpl { ...@@ -191,6 +192,16 @@ class COMPONENT_EXPORT(CHROMEOS_LACROS) LacrosChromeServiceImpl {
return file_manager_remote_; return file_manager_remote_;
} }
// test_controller_remote() can only be used if this method returns true.
bool IsTestControllerAvailable();
// Must be called on the affine sequence.
mojo::Remote<crosapi::mojom::TestController>& test_controller_remote() {
DCHECK_CALLED_ON_VALID_SEQUENCE(affine_sequence_checker_);
DCHECK(IsTestControllerAvailable());
return test_controller_remote_;
}
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// Some clients will want to use mojo::Remotes on arbitrary sequences (e.g. // Some clients will want to use mojo::Remotes on arbitrary sequences (e.g.
// background threads). The following methods allow the client to construct a // background threads). The following methods allow the client to construct a
...@@ -283,6 +294,7 @@ class COMPONENT_EXPORT(CHROMEOS_LACROS) LacrosChromeServiceImpl { ...@@ -283,6 +294,7 @@ class COMPONENT_EXPORT(CHROMEOS_LACROS) LacrosChromeServiceImpl {
mojo::Remote<crosapi::mojom::CertDatabase> cert_database_remote_; mojo::Remote<crosapi::mojom::CertDatabase> cert_database_remote_;
mojo::Remote<crosapi::mojom::KeystoreService> keystore_service_remote_; mojo::Remote<crosapi::mojom::KeystoreService> keystore_service_remote_;
mojo::Remote<crosapi::mojom::FileManager> file_manager_remote_; mojo::Remote<crosapi::mojom::FileManager> file_manager_remote_;
mojo::Remote<crosapi::mojom::TestController> test_controller_remote_;
// This member is instantiated on the affine sequence alongside the // This member is instantiated on the affine sequence alongside the
// constructor. All subsequent invocations of this member, including // constructor. All subsequent invocations of this member, including
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment