chromium/chrome/browser/lacros/clipboard_history_lacros_browsertest.cc

// Copyright 2023 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/clipboard_history_lacros.h"

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/renderer_context_menu/render_view_context_menu_test_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/crosapi/mojom/clipboard_history.mojom.h"
#include "chromeos/lacros/lacros_service.h"
#include "content/public/test/browser_test.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/context_menu_data/edit_flags.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/controls/textfield/textfield_test_api.h"
#include "ui/views/widget/widget.h"

class ClipboardHistoryRefreshLacrosTest
    : public InProcessBrowserTest,
      public testing::WithParamInterface<
          /*enable_clipboard_history_refresh=*/bool> {
 public:
  // InProcessBrowserTest:
  void SetUp() override {
    std::vector<std::string> enabled_features{"ClipboardHistoryRefresh",
                                              "Jelly"};
    std::vector<std::string> disabled_features;
    if (!GetParam()) {
      std::swap(enabled_features, disabled_features);
    }
    StartUniqueAshChrome(enabled_features, disabled_features,
                         /*additional_cmdline_switches=*/{},
                         /*bug_number_and_reason=*/
                         {"b/267681869 Switch to shared ash when clipboard "
                          "history refresh is enabled by default"});

    InProcessBrowserTest::SetUp();
  }

  // Returns whether the clipboard history interface is available. It may not be
  // available on earlier versions of Ash Chrome.
  bool IsInterfaceAvailable() const {
    chromeos::LacrosService* lacros_service = chromeos::LacrosService::Get();
    return lacros_service &&
           lacros_service->IsAvailable<crosapi::mojom::ClipboardHistory>();
  }

  void WriteTextToClipboard(const std::u16string& text) {
    ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste).WriteText(text);

    // TODO(http://b/278916298): implement testing in an end-to-end way after
    // http://b/283834862 is fixed.
    if (chromeos::features::IsClipboardHistoryRefreshEnabled()) {
      crosapi::ClipboardHistoryLacros* clipboard_history_lacros =
          crosapi::ClipboardHistoryLacros::Get();
      std::vector<crosapi::mojom::ClipboardHistoryItemDescriptor>&
          cached_descriptors = clipboard_history_lacros->cached_descriptors_;
      cached_descriptors.insert(
          cached_descriptors.begin(),
          crosapi::mojom::ClipboardHistoryItemDescriptor(
              /*id=*/base::UnguessableToken::Create(),
              /*display_format=*/
              crosapi::mojom::ClipboardHistoryDisplayFormat::kText, text,
              /*file_count=*/0));
    }
  }
};

INSTANTIATE_TEST_SUITE_P(All,
                         ClipboardHistoryRefreshLacrosTest,
                         /*enable_clipboard_history_refresh=*/testing::Bool());

// Verifies that the Lacros render view context menu clipboard history option
// works as expected.
IN_PROC_BROWSER_TEST_P(ClipboardHistoryRefreshLacrosTest,
                       MenuOptionOnRenderViewContextMenu) {
  // If the clipboard history interface is not available on this version of
  // ash-chrome, this test cannot meaningfully run.
  if (!IsInterfaceAvailable()) {
    GTEST_SKIP() << "Unsupported Ash version.";
  }

  content::ContextMenuParams params;
  params.is_editable = true;
  params.edit_flags = blink::ContextMenuDataEditFlags::kCanPaste;

  const int clipboard_history_command_id =
      chromeos::features::IsClipboardHistoryRefreshEnabled()
          ? IDC_CONTENT_PASTE_FROM_CLIPBOARD
          : IDC_CONTENT_CLIPBOARD_HISTORY_MENU;

  {
    TestRenderViewContextMenu menu(*browser()
                                        ->tab_strip_model()
                                        ->GetActiveWebContents()
                                        ->GetPrimaryMainFrame(),
                                   params);
    menu.Init();

    // When clipboard history is empty, the Clipboard option should be disabled.
    EXPECT_TRUE(menu.IsItemPresent(clipboard_history_command_id));
    EXPECT_FALSE(menu.IsItemEnabled(clipboard_history_command_id));
  }

  // Populate the clipboard so that the menu can be shown.
  WriteTextToClipboard(u"text");

  {
    TestRenderViewContextMenu menu(*browser()
                                        ->tab_strip_model()
                                        ->GetActiveWebContents()
                                        ->GetPrimaryMainFrame(),
                                   params);
    menu.Init();

    const ui::SimpleMenuModel& menu_model = menu.menu_model();
    std::optional<size_t> target_command_index =
        menu_model.GetIndexOfCommandId(clipboard_history_command_id);
    ASSERT_TRUE(target_command_index);

    // The clipboard history menu option should be enabled since clipboard
    // history is non-empty.
    EXPECT_TRUE(menu_model.IsEnabledAt(*target_command_index));

    if (chromeos::features::IsClipboardHistoryRefreshEnabled()) {
      // Because the refresh feature is enabled, the clipboard history menu item
      // should be a submenu item.
      ui::MenuModel* const submenu_model =
          menu_model.GetSubmenuModelAt(*target_command_index);
      ASSERT_TRUE(submenu_model);
      ASSERT_EQ(submenu_model->GetItemCount(), 2u);
      EXPECT_EQ(submenu_model->GetLabelAt(0), u"text");
      EXPECT_EQ(submenu_model->GetLabelAt(1),
                l10n_util::GetStringUTF16(
                    IDS_CONTEXT_MENU_SHOW_CLIPBOARD_HISTORY_MENU));
    } else {
      // Because the refresh feature is disabled, the clipboard history menu
      // item should be a command item.
      EXPECT_EQ(menu_model.GetTypeAt(*target_command_index),
                ui::MenuModel::ItemType::TYPE_COMMAND);
    }
  }
}

// Checks that the Lacros text services context menu clipboard history option is
// 1. A command menu item if the clipboard history refresh feature is disabled;
// 2. A submenu item if the clipboard history refresh feature is enabled.
IN_PROC_BROWSER_TEST_P(ClipboardHistoryRefreshLacrosTest,
                       MenuOptionOnTextServicesContextMenu) {
  // If the clipboard history interface is not available on this version of
  // ash-chrome, this test cannot meaningfully run.
  if (!IsInterfaceAvailable()) {
    GTEST_SKIP() << "Unsupported Ash version.";
  }

  // Create a textfield.
  views::Widget::InitParams params(
      views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
  params.bounds = gfx::Rect(200, 200);
  auto textfield_widget = std::make_unique<views::Widget>();
  textfield_widget->Init(std::move(params));
  views::Textfield* textfield =
      textfield_widget->SetContentsView(std::make_unique<views::Textfield>());
  textfield->GetViewAccessibility().SetName(u"Textfield");
  textfield_widget->Show();

  views::TextfieldTestApi api(textfield);
  api.UpdateContextMenu();

  const int clipboard_history_command_id =
      chromeos::features::IsClipboardHistoryRefreshEnabled()
          ? IDS_APP_PASTE_FROM_CLIPBOARD
          : IDS_APP_SHOW_CLIPBOARD_HISTORY;

  // Search the parent model and the command index of
  // `clipboard_history_command_id`.
  ui::MenuModel* target_command_parent_model = api.context_menu_contents();
  size_t target_command_index = 0u;
  ui::MenuModel::GetModelAndIndexForCommandId(clipboard_history_command_id,
                                              &target_command_parent_model,
                                              &target_command_index);
  ASSERT_EQ(target_command_parent_model, api.context_menu_contents());
  ASSERT_GT(target_command_index, 0u);

  // Before having clipboard history item descriptors, the clipboard history
  // menu option is disabled.
  EXPECT_FALSE(target_command_parent_model->IsEnabledAt(target_command_index));

  // Write some clipboard data then update the context menu.
  WriteTextToClipboard(u"b");
  WriteTextToClipboard(u"a");
  api.UpdateContextMenu();
  target_command_parent_model = api.context_menu_contents();

  // The clipboard history menu option becomes enabled after writing data.
  EXPECT_TRUE(target_command_parent_model->IsEnabledAt(target_command_index));

  if (chromeos::features::IsClipboardHistoryRefreshEnabled()) {
    // Because the refresh feature is enabled, the clipboard history menu item
    // should be a submenu item.
    EXPECT_EQ(target_command_parent_model->GetTypeAt(target_command_index),
              ui::MenuModel::ItemType::TYPE_SUBMENU);

    // Check the submenu model data.
    ui::MenuModel* const submenu_model =
        target_command_parent_model->GetSubmenuModelAt(target_command_index);
    ASSERT_TRUE(submenu_model);
    ASSERT_EQ(submenu_model->GetItemCount(), 3u);
    EXPECT_EQ(submenu_model->GetLabelAt(0), u"a");
    EXPECT_EQ(submenu_model->GetLabelAt(1), u"b");
    EXPECT_EQ(submenu_model->GetLabelAt(2),
              l10n_util::GetStringUTF16(IDS_APP_SHOW_CLIPBOARD_HISTORY));
  } else {
    // Because the refresh feature is disabled, the clipboard history menu item
    // should be a command item.
    EXPECT_EQ(target_command_parent_model->GetTypeAt(target_command_index),
              ui::MenuModel::ItemType::TYPE_COMMAND);
  }
}