chromium/chrome/browser/ash/system_web_apps/apps/media_app/media_app_ocr_integration_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 <memory>
#include <utility>

#include "ash/constants/ash_features.h"
#include "ash/webui/media_app_ui/test/media_app_ui_browsertest.h"
#include "ash/webui/media_app_ui/url_constants.h"
#include "base/check_deref.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/path_service.h"
#include "chrome/browser/accessibility/media_app/ax_media_app_handler_factory.h"
#include "chrome/browser/apps/app_service/app_launch_params.h"
#include "chrome/browser/ash/hats/hats_config.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/ash/system_web_apps/test_support/system_web_app_integration_test.h"
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_list_observer.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"

namespace {

// Path to a subfolder in chrome/test/data that holds test files.
constexpr base::FilePath::CharType kTestFilesFolderInTestData[] =
    FILE_PATH_LITERAL("chromeos/file_manager");

// A small square image PDF created by a camera.
constexpr char kFilePdfImg[] = "img.pdf";

// A 1-page (8.5" x 11") PDF with some text and metadata.
constexpr char kFilePdfTall[] = "tall.pdf";

class MediaAppOcrIntegrationTest : public ash::SystemWebAppIntegrationTest {
 public:
  MediaAppOcrIntegrationTest() {}

  void SetUpOnMainThread() override {
    SystemWebAppIntegrationTest::SetUpOnMainThread();
    WaitForTestSystemAppInstall();
  }

  void LaunchAndWait(const ash::SystemAppLaunchParams& params) {
    content::TestNavigationObserver observer =
        content::TestNavigationObserver(GURL(ash::kChromeUIMediaAppURL));
    observer.StartWatchingNewWebContents();
    ash::LaunchSystemWebAppAsync(profile(), ash::SystemWebAppType::MEDIA,
                                 params);
    observer.Wait();
  }
};

// Waits for the number of active Browsers in the test process to reach `count`.
void WaitForBrowserCount(size_t count) {
  EXPECT_LE(BrowserList::GetInstance()->size(), count) << "Too many browsers";
  while (BrowserList::GetInstance()->size() < count) {
    ui_test_utils::WaitForBrowserToOpen();
  }
}

// Gets the base::FilePath for a named file in the test folder.
base::FilePath TestFile(const std::string& ascii_name) {
  base::FilePath path;
  EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &path));
  path = path.Append(kTestFilesFolderInTestData);
  path = path.AppendASCII(ascii_name);

  base::ScopedAllowBlockingForTesting allow_blocking;
  EXPECT_TRUE(base::PathExists(path));
  return path;
}

// Waits for a file to finish loading, assuming the busy attribute on the app
// element disappears when this happens. Uses the last active web UI.
void WaitForFirstFileLoadInActiveWindow(const std::string& filename) {
  constexpr char kWaitForAppIdleScript[] = R"(
      (async function waitForFileLoad() {
        await waitForNode('.app-bar-filename[filename="$1"]',
                          ['backlight-app-bar', 'backlight-app']);
        await waitForNode('backlight-app:not([busy])');
        return 'loaded';
      })();
  )";

  Browser* app_browser = chrome::FindBrowserWithActiveWindow();
  content::WebContents* web_ui =
      app_browser->tab_strip_model()->GetActiveWebContents();
  MediaAppUiBrowserTest::PrepareAppForTest(web_ui);

  EXPECT_EQ("loaded",
            MediaAppUiBrowserTest::EvalJsInAppFrame(
                web_ui, base::ReplaceStringPlaceholders(kWaitForAppIdleScript,
                                                        {filename}, nullptr)));
}

}  // namespace

// Test that the Media App connects to the OCR service when opening PDFs.
IN_PROC_BROWSER_TEST_P(MediaAppOcrIntegrationTest, MediaAppLaunchPdfMulti) {
  // Without any instance of MediaApp open, there are no corresponding handlers.
  auto* ax_factory = ash::AXMediaAppHandlerFactory::GetInstance();
  EXPECT_EQ(ax_factory->media_app_receivers().size(), 0u);

  // Launch one PDF window and test one handler was created for the guest frame.
  ash::SystemAppLaunchParams pdf_params_window1;
  pdf_params_window1.launch_paths = {TestFile(kFilePdfImg)};
  LaunchAndWait(pdf_params_window1);
  WaitForBrowserCount(2);  // 1 extra for the browser test browser.
  const BrowserList* browser_list = BrowserList::GetInstance();
  EXPECT_EQ(browser_list->size(), 2u);

  WaitForFirstFileLoadInActiveWindow(kFilePdfImg);
  // There should be one handler after one PDF window is opened. If it's in the
  // UniqueReceiverSet, this also means it's bound to a remote.
  EXPECT_EQ(ax_factory->media_app_receivers().size(), 1u);

  // Launch a second PDF window and check it's got a second handler.
  ash::SystemAppLaunchParams pdf_params_window2;
  pdf_params_window2.launch_paths = {TestFile(kFilePdfTall)};
  LaunchAndWait(pdf_params_window2);
  WaitForBrowserCount(3);  // 1 extra for the browser test browser.
  EXPECT_EQ(browser_list->size(), 3u);

  WaitForFirstFileLoadInActiveWindow(kFilePdfTall);
  // There should be a second handler after a second PDF window is opened.
  EXPECT_EQ(ax_factory->media_app_receivers().size(), 2u);
}

INSTANTIATE_SYSTEM_WEB_APP_MANAGER_TEST_SUITE_REGULAR_PROFILE_P(
    MediaAppOcrIntegrationTest);