chromium/chrome/browser/lacros/browser_test_util.cc

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