blob: c99acdd734700fb06071386254bdecf890a0909c [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/test/ash_test_util.h"
#include <string>
#include <vector>
#include "ash/frame/non_client_frame_view_ash.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/root_window_controller.h"
#include "ash/shell.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/unified/unified_system_tray.h"
#include "base/auto_reset.h"
#include "base/check.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "chromeos/ui/frame/multitask_menu/multitask_menu.h"
#include "chromeos/ui/frame/multitask_menu/multitask_menu_metrics.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window_observer.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_util.h"
#include "ui/snapshot/snapshot_aura.h"
#include "ui/views/controls/menu/menu_item_view.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/any_widget_observer.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
// Helper functions ------------------------------------------------------------
void SnapshotCallback(base::RunLoop* run_loop,
gfx::Image* ret_image,
gfx::Image image) {
*ret_image = image;
run_loop->Quit();
}
// Returns the menu item view with `label` from the specified view.
views::MenuItemView* FindMenuItemWithLabelFromView(
views::View* search_root,
const std::u16string& label) {
// Check whether `search_root` is the target menu item view.
if (views::MenuItemView* const menu_item_view =
views::AsViewClass<views::MenuItemView>(search_root);
menu_item_view && menu_item_view->title() == label) {
return menu_item_view;
}
// Keep searching in children views.
for (views::View* const child : search_root->children()) {
if (views::MenuItemView* const found =
FindMenuItemWithLabelFromView(child, label)) {
return found;
}
}
return nullptr;
}
// Returns the menu item view with `label` from the specified window.
views::MenuItemView* FindMenuItemWithLabelFromWindow(
aura::Window* search_root,
const std::u16string& label) {
// If `search_root` is a window for widget, search the target menu item view
// in the view tree.
if (views::Widget* const root_widget =
views::Widget::GetWidgetForNativeWindow(search_root)) {
return FindMenuItemWithLabelFromView(root_widget->GetRootView(), label);
}
for (aura::Window* const child : search_root->children()) {
if (auto* found = FindMenuItemWithLabelFromWindow(child, label)) {
return found;
}
}
return nullptr;
}
// MenuItemViewWithLabelWaiter -------------------------------------------------
class MenuItemViewWithLabelWaiter : public aura::WindowObserver {
public:
explicit MenuItemViewWithLabelWaiter(const std::u16string& label)
: label_(label),
menu_container_(Shell::GetContainer(Shell::GetPrimaryRootWindow(),
kShellWindowId_MenuContainer)) {
CHECK(menu_container_) << "The menu container is expected to exist.";
}
// Waits until the target menu item view is found. Returns the pointer to the
// target view.
views::MenuItemView* Wait() {
// Return early if the target view already exists.
if ((cached_target_view_ =
FindMenuItemWithLabelFromWindow(menu_container_, label_))) {
return cached_target_view_;
}
base::AutoReset<std::unique_ptr<base::RunLoop>> auto_reset(
&run_loop_, std::make_unique<base::RunLoop>());
// Start the observation on `menu_container_`. A new menu should add
// itself under `menu_container_`.
base::ScopedObservation<aura::Window, aura::WindowObserver> observation{
this};
observation.Observe(menu_container_);
run_loop_->Run();
CHECK(cached_target_view_);
return cached_target_view_;
}
private:
// aura::WindowObserver:
void OnWindowAdded(aura::Window* window) override {
// Menu items are built after the window is added. Therefore, check the
// target menu item in an asynchronous task.
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&MenuItemViewWithLabelWaiter::CacheTargetView,
weak_factory_.GetWeakPtr()));
}
void OnWindowDestroying(aura::Window* window) override {
CHECK(!run_loop_)
<< "The menu container was destroyed while we were waiting for "
"the target menu item.";
}
void CacheTargetView() {
if ((cached_target_view_ =
FindMenuItemWithLabelFromWindow(menu_container_, label_))) {
run_loop_->Quit();
}
}
const std::u16string label_;
const raw_ptr<aura::Window> menu_container_;
std::unique_ptr<base::RunLoop> run_loop_;
raw_ptr<views::MenuItemView> cached_target_view_ = nullptr;
base::WeakPtrFactory<MenuItemViewWithLabelWaiter> weak_factory_{this};
};
} // namespace
bool TakePrimaryDisplayScreenshotAndSave(const base::FilePath& file_path) {
// Return false if the file extension is not "png".
if (file_path.Extension() != ".png")
return false;
// Return false if `file_path`'s directory does not exist.
const base::FilePath directory_name = file_path.DirName();
if (!base::PathExists(directory_name))
return false;
base::RunLoop run_loop;
gfx::Image image;
ui::GrabWindowSnapshotAsyncAura(
Shell::Get()->GetPrimaryRootWindow(),
Shell::Get()->GetPrimaryRootWindow()->bounds(),
base::BindOnce(&SnapshotCallback, &run_loop, &image));
run_loop.Run();
auto data = image.As1xPNGBytes();
DCHECK_GT(data->size(), 0u);
return base::WriteFile(file_path, *data);
}
void GiveItSomeTimeForDebugging(base::TimeDelta time_duration) {
base::RunLoop run_loop;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), time_duration);
run_loop.Run();
}
bool IsSystemTrayForRootWindowVisible(size_t root_window_index) {
aura::Window::Windows root_windows = Shell::GetAllRootWindows();
RootWindowController* controller =
RootWindowController::ForWindow(root_windows[root_window_index]);
return controller->GetStatusAreaWidget()->unified_system_tray()->GetVisible();
}
gfx::ImageSkia CreateSolidColorTestImage(const gfx::Size& image_size,
SkColor color) {
SkBitmap bitmap;
bitmap.allocN32Pixels(image_size.width(), image_size.height());
bitmap.eraseColor(color);
gfx::ImageSkia image = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
return image;
}
std::string CreateEncodedImageForTesting(const gfx::Size& size,
SkColor color,
data_decoder::mojom::ImageCodec codec,
gfx::ImageSkia* image_out) {
gfx::ImageSkia test_image = CreateSolidColorTestImage(size, color);
CHECK(!test_image.isNull());
if (image_out) {
*image_out = test_image;
}
std::vector<unsigned char> encoded_image;
switch (codec) {
case data_decoder::mojom::ImageCodec::kDefault:
CHECK(gfx::JPEG1xEncodedDataFromImage(gfx::Image(test_image), 100,
&encoded_image));
break;
case data_decoder::mojom::ImageCodec::kPng:
CHECK(gfx::PNGCodec::EncodeBGRASkBitmap(
*test_image.bitmap(), /*discard_transparency=*/true, &encoded_image));
break;
}
return std::string(reinterpret_cast<const char*>(encoded_image.data()),
encoded_image.size());
}
void DecorateWindow(aura::Window* window,
const std::u16string& title,
SkColor color) {
auto* widget = views::Widget::GetWidgetForNativeWindow(window);
DCHECK(widget);
widget->client_view()->AddChildView(
views::Builder<views::View>()
.SetBackground(views::CreateRoundedRectBackground(color, 4.f))
.Build());
// Add a title and an app icon so that the header is fully stocked.
window->SetTitle(title);
SkBitmap bitmap;
bitmap.allocN32Pixels(1, 1);
bitmap.eraseColor(SK_ColorCYAN);
window->SetProperty(aura::client::kAppIconKey,
gfx::ImageSkia::CreateFrom1xBitmap(bitmap));
}
views::MenuItemView* WaitForMenuItemWithLabel(const std::u16string& label) {
return MenuItemViewWithLabelWaiter(label).Wait();
}
chromeos::MultitaskMenu* ShowAndWaitMultitaskMenuForWindow(
absl::variant<aura::Window*, chromeos::FrameSizeButton*>
window_or_size_button,
chromeos::MultitaskMenuEntryType entry_type) {
// If a size button object is passed, use that. Otherwise retrieve it from the
// non client frame view ash.
chromeos::FrameSizeButton* size_button = nullptr;
if (absl::holds_alternative<chromeos::FrameSizeButton*>(
window_or_size_button)) {
size_button = absl::get<chromeos::FrameSizeButton*>(window_or_size_button);
} else {
aura::Window* window = absl::get<aura::Window*>(window_or_size_button);
CHECK(window);
auto* frame_view = NonClientFrameViewAsh::Get(window);
if (!frame_view) {
return nullptr;
}
size_button = views::AsViewClass<chromeos::FrameSizeButton>(
frame_view->GetHeaderView()->caption_button_container()->size_button());
}
views::NamedWidgetShownWaiter waiter(
views::test::AnyWidgetTestPasskey{},
std::string("MultitaskMenuBubbleWidget"));
size_button->ShowMultitaskMenu(entry_type);
views::WidgetDelegate* delegate =
waiter.WaitIfNeededAndGet()->widget_delegate();
auto* multitask_menu =
static_cast<chromeos::MultitaskMenu*>(delegate->AsDialogDelegate());
return multitask_menu;
}
void SendKey(ui::KeyboardCode key_code,
ui::test::EventGenerator* event_generator,
int flags,
int count) {
for (int i = 0; i < count; ++i) {
event_generator->PressAndReleaseKey(key_code, flags);
}
}
} // namespace ash