// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/lacros/browser_test_util.h"
#include "base/memory/raw_ptr.h"
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/lacros/window_utility.h"
#include "chromeos/lacros/lacros_service.h"
#include "ui/aura/env.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher_observer.h"
#include "ui/platform_window/platform_window.h"
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_lacros.h"
namespace browser_test_util {
namespace {
// Observes Aura and waits for both a mouse down and a mouse up event.
class AuraObserver : public aura::WindowEventDispatcherObserver {
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::kMousePressed) {
mouse_down_seen_ = true;
}
if (mouse_down_seen_ && event.type() == ui::EventType::kMouseReleased) {
mouse_up_seen_ = true;
}
if (Done()) {
run_loop_->Quit();
}
}
bool Done() const { return mouse_down_seen_ && mouse_up_seen_; }
private:
// Must outlive the observer.
raw_ptr<base::RunLoop> run_loop_;
bool mouse_down_seen_ = false;
bool mouse_up_seen_ = false;
};
// Returns true if TestController is available in LacrosService.
bool IsTestControllerAvailable() {
auto* lacros_service = chromeos::LacrosService::Get();
return lacros_service &&
lacros_service->IsAvailable<crosapi::mojom::TestController>();
}
// Returns true if TestController in Ash supports the method requiring at least
// |min_version|.
bool DoesTestControllerSupport(
crosapi::mojom::TestController::MethodMinVersions min_version) {
CHECK(IsTestControllerAvailable());
int interface_version =
chromeos::LacrosService::Get()
->GetInterfaceVersion<crosapi::mojom::TestController>();
return (interface_version >= static_cast<int>(min_version));
}
bool WaitForWindow(const std::string& id, bool exists) {
CHECK(IsTestControllerAvailable());
base::RunLoop outer_loop;
bool actual_exists = false;
auto wait_for_window = base::BindRepeating(
[](base::RunLoop* outer_loop, const std::string& id, bool expected_exists,
bool* actual_exists) {
auto* lacros_service = chromeos::LacrosService::Get();
base::RunLoop inner_loop(base::RunLoop::Type::kNestableTasksAllowed);
lacros_service->GetRemote<crosapi::mojom::TestController>()
->DoesWindowExist(
id, base::BindOnce(
[](base::RunLoop* loop, bool* out_exist, bool exist) {
*out_exist = std::move(exist);
loop->Quit();
},
&inner_loop, actual_exists));
inner_loop.Run();
if (*actual_exists == expected_exists) {
outer_loop->Quit();
}
},
&outer_loop, id, exists, &actual_exists);
// Wait for the window to exist / not exist.
base::RepeatingTimer timer;
timer.Start(FROM_HERE, base::Milliseconds(1), std::move(wait_for_window));
outer_loop.Run();
return actual_exists == exists;
}
bool WaitForElement(const std::string& id, bool exists) {
CHECK(IsTestControllerAvailable());
base::RunLoop outer_loop;
bool actual_exists = false;
auto wait_for_element = base::BindRepeating(
[](base::RunLoop* outer_loop, const std::string& id, bool expected_exists,
bool* actual_exists) {
auto* lacros_service = chromeos::LacrosService::Get();
base::RunLoop inner_loop(base::RunLoop::Type::kNestableTasksAllowed);
lacros_service->GetRemote<crosapi::mojom::TestController>()
->DoesElementExist(
id, base::BindOnce(
[](base::RunLoop* loop, bool* out_exist, bool exist) {
*out_exist = std::move(exist);
loop->Quit();
},
&inner_loop, actual_exists));
inner_loop.Run();
if (*actual_exists == expected_exists) {
outer_loop->Quit();
}
},
&outer_loop, id, exists, &actual_exists);
// Wait for the element to exist / not exist.
base::RepeatingTimer timer;
timer.Start(FROM_HERE, base::Milliseconds(1), std::move(wait_for_element));
outer_loop.Run();
return actual_exists == exists;
}
} // namespace
bool WaitForElementCreation(const std::string& element_name) {
return WaitForElement(element_name, /*exists=*/true);
}
bool WaitForWindowCreation(const std::string& id) {
return WaitForWindow(id, /*exists=*/true);
}
bool WaitForWindowCreation(Browser* browser) {
aura::Window* root = browser->window()->GetNativeWindow()->GetRootWindow();
return WaitForWindow(lacros_window_utility::GetRootWindowUniqueId(root),
/*exists=*/true);
}
bool WaitForWindowDestruction(const std::string& id) {
return WaitForWindow(id, /*exists=*/false);
}
bool WaitForShelfItem(const std::string& id, bool exists) {
CHECK(IsTestControllerAvailable());
base::RunLoop outer_loop;
bool actual_exists = false;
auto wait_for_shelf_item = base::BindRepeating(
[](base::RunLoop* outer_loop, const std::string& id, bool expected_exists,
bool* actual_exists) {
auto* lacros_service = chromeos::LacrosService::Get();
base::RunLoop inner_loop(base::RunLoop::Type::kNestableTasksAllowed);
lacros_service->GetRemote<crosapi::mojom::TestController>()
->DoesItemExistInShelf(
id, base::BindOnce(
[](base::RunLoop* loop, bool* out_exist, bool exist) {
*out_exist = std::move(exist);
loop->Quit();
},
&inner_loop, actual_exists));
inner_loop.Run();
if (*actual_exists == expected_exists) {
outer_loop->Quit();
}
},
&outer_loop, id, exists, &actual_exists);
// Wait for the item to exist / not exist.
base::RepeatingTimer timer;
timer.Start(FROM_HERE, base::Milliseconds(1), std::move(wait_for_shelf_item));
outer_loop.Run();
return actual_exists == exists;
}
bool WaitForShelfItemState(const std::string& id,
uint32_t state,
const base::Location& location) {
if (!DoesTestControllerSupport(
crosapi::mojom::TestController::MethodMinVersions::
kGetShelfItemStateMinVersion)) {
return false;
}
base::RunLoop outer_loop;
uint32_t actual_state =
static_cast<uint32_t>(crosapi::mojom::ShelfItemState::kNormal);
auto wait_for_state = base::BindRepeating(
[](base::RunLoop* outer_loop, const std::string& id,
uint32_t expected_state, uint32_t* actual_state) {
auto* lacros_service = chromeos::LacrosService::Get();
base::RunLoop inner_loop(base::RunLoop::Type::kNestableTasksAllowed);
lacros_service->GetRemote<crosapi::mojom::TestController>()
->GetShelfItemState(id,
base::BindOnce(
[](base::RunLoop* loop, uint32_t* out_state,
uint32_t state) {
*out_state = std::move(state);
loop->Quit();
},
&inner_loop, actual_state));
inner_loop.Run();
if (*actual_state == expected_state) {
outer_loop->Quit();
}
},
&outer_loop, id, state, &actual_state);
base::RepeatingTimer timer;
timer.Start(FROM_HERE, base::Milliseconds(1), std::move(wait_for_state));
outer_loop.Run(location);
return actual_state == state;
}
// 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.
bool SendAndWaitForMouseClick(aura::Window* window) {
CHECK(IsTestControllerAvailable());
DCHECK(window->IsRootWindow());
std::string id = lacros_window_utility::GetRootWindowUniqueId(window);
base::RunLoop run_loop;
std::unique_ptr<AuraObserver> obs = std::make_unique<AuraObserver>(&run_loop);
aura::Env::GetInstance()->AddWindowEventDispatcherObserver(obs.get());
auto* lacros_service = chromeos::LacrosService::Get();
lacros_service->GetRemote<crosapi::mojom::TestController>()->ClickWindow(id);
run_loop.Run();
aura::Env::GetInstance()->RemoveWindowEventDispatcherObserver(obs.get());
return obs->Done();
}
} // namespace browser_test_util