chromium/chrome/browser/apps/app_shim/web_app_shim_manager_delegate_mac_unittest.cc

// Copyright 2013 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/apps/app_shim/web_app_shim_manager_delegate_mac.h"

#include <optional>

#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/test/bind.h"
#include "chrome/browser/apps/app_service/app_launch_params.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/mojom/user_display_mode.mojom.h"
#include "chrome/browser/web_applications/test/fake_os_integration_manager.h"
#include "chrome/browser/web_applications/test/fake_web_app_provider.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_test.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace web_app {

namespace {

class MockDelegate : public apps::AppShimManager::Delegate {
 public:
  MockDelegate() {}
  MockDelegate(const MockDelegate&) = delete;
  MockDelegate& operator=(const MockDelegate&) = delete;
  ~MockDelegate() override = default;

  MOCK_METHOD(bool, ShowAppWindows, (Profile*, const std::string&), (override));
  MOCK_METHOD(void,
              CloseAppWindows,
              (Profile*, const std::string&),
              (override));
  MOCK_METHOD(bool, AppIsInstalled, (Profile*, const std::string&), (override));
  MOCK_METHOD(bool,
              AppCanCreateHost,
              (Profile*, const std::string&),
              (override));
  MOCK_METHOD(bool,
              AppUsesRemoteCocoa,
              (Profile*, const std::string&),
              (override));
  MOCK_METHOD(bool,
              AppIsMultiProfile,
              (Profile*, const std::string&),
              (override));
  MOCK_METHOD(void,
              EnableExtension,
              (Profile*, const std::string&, base::OnceCallback<void()>),
              (override));
  MOCK_METHOD(void,
              LaunchApp,
              (Profile*,
               const std::string&,
               const std::vector<base::FilePath>&,
               const std::vector<GURL>&,
               const GURL&,
               chrome::mojom::AppShimLoginItemRestoreState,
               base::OnceClosure),
              (override));
  MOCK_METHOD(void,
              LaunchShim,
              (Profile*,
               const std::string&,
               LaunchShimUpdateBehavior,
               ShimLaunchMode,
               apps::ShimLaunchedCallback,
               apps::ShimTerminatedCallback),
              (override));
  MOCK_METHOD(bool, HasNonBookmarkAppWindowsOpen, (), (override));
  MOCK_METHOD(std::vector<chrome::mojom::ApplicationDockMenuItemPtr>,
              GetAppShortcutsMenuItemInfos,
              (Profile*, const std::string&),
              (override));
};

class WebAppShimManagerDelegateTest : public WebAppTest {
 public:
  WebAppShimManagerDelegateTest() {}
  WebAppShimManagerDelegateTest(const WebAppShimManagerDelegateTest&) = delete;
  WebAppShimManagerDelegateTest& operator=(
      const WebAppShimManagerDelegateTest&) = delete;
  ~WebAppShimManagerDelegateTest() override = default;

  void SetUp() override {
    WebAppTest::SetUp();

    auto* provider = web_app::FakeWebAppProvider::Get(profile());

    provider->SetOsIntegrationManager(
        std::make_unique<FakeOsIntegrationManager>(
            profile(),
            /*file_handler_manager=*/nullptr,
            /*protocol_handler_manager=*/nullptr));

    web_app::test::AwaitStartWebAppProviderAndSubsystems(profile());

    // Install a dummy app
    auto web_app_info = WebAppInstallInfo::CreateWithStartUrlForTesting(
        GURL("https://testpwa.com/"));
    web_app_info->title = u"WebAppTest";
    web_app_info->scope = GURL("https://testpwa.com/");
    web_app_info->display_mode = blink::mojom::DisplayMode::kStandalone;

    // Basic plain text format.
    {
      apps::FileHandler entry;
      entry.action = GURL("https://testpwa.com/files-txt");
      entry.accept.emplace_back();
      entry.accept[0].mime_type = "text/*";
      entry.accept[0].file_extensions.insert(".txt");
      web_app_info->file_handlers.push_back(std::move(entry));
    }

    // Png handler.
    {
      apps::FileHandler entry;
      entry.action = GURL("https://testpwa.com/files-png");
      entry.accept.emplace_back();
      entry.accept[0].mime_type = "image/png";
      entry.accept[0].file_extensions.insert(".png");
      entry.launch_type = apps::FileHandler::LaunchType::kMultipleClients;
      web_app_info->file_handlers.push_back(std::move(entry));
    }

    app_id_ = test::InstallWebApp(profile(), std::move(web_app_info));
  }

 protected:
  apps::AppLaunchParams CreateLaunchParams(
      const std::vector<base::FilePath>& launch_files,
      const std::optional<GURL>& url_handler_launch_url,
      const std::optional<GURL>& protocol_handler_launch_url,
      const GURL& override_url) {
    apps::AppLaunchParams params(app_id_,
                                 apps::LaunchContainer::kLaunchContainerWindow,
                                 WindowOpenDisposition::NEW_WINDOW,
                                 apps::LaunchSource::kFromCommandLine);

    params.launch_files = launch_files;
    params.url_handler_launch_url = url_handler_launch_url;
    params.protocol_handler_launch_url = protocol_handler_launch_url;
    params.override_url = override_url;
    return params;
  }

  void ValidateOptionalGURL(const std::optional<GURL>& actual,
                            const std::optional<GURL>& expected) {
    ASSERT_EQ(actual.has_value(), expected.has_value());
    if (actual.has_value()) {
      EXPECT_EQ(actual.value(), expected.value());
    }
  }

  void ValidateLaunchParams(const apps::AppLaunchParams& actual_results,
                            const apps::AppLaunchParams& expected_results) {
    EXPECT_EQ(actual_results.app_id, expected_results.app_id);
    EXPECT_EQ(actual_results.command_line.GetArgs(),
              expected_results.command_line.GetArgs());
    EXPECT_EQ(actual_results.current_directory,
              expected_results.current_directory);
    EXPECT_EQ(actual_results.launch_source, expected_results.launch_source);
    EXPECT_EQ(actual_results.launch_files, expected_results.launch_files);
    EXPECT_EQ(actual_results.url_handler_launch_url,
              expected_results.url_handler_launch_url);
    EXPECT_EQ(actual_results.override_url, expected_results.override_url);
    ValidateOptionalGURL(actual_results.url_handler_launch_url,
                         expected_results.url_handler_launch_url);
    ValidateOptionalGURL(actual_results.protocol_handler_launch_url,
                         expected_results.protocol_handler_launch_url);
    EXPECT_EQ(actual_results.protocol_handler_launch_url,
              expected_results.protocol_handler_launch_url);
  }

  const webapps::AppId& AppId() const { return app_id_; }

 private:
  webapps::AppId app_id_;
};

TEST_F(WebAppShimManagerDelegateTest, LaunchApp) {
  apps::AppLaunchParams expected_results = CreateLaunchParams(
      std::vector<base::FilePath>(), std::nullopt, std::nullopt, GURL());

  std::unique_ptr<MockDelegate> delegate = std::make_unique<MockDelegate>();
  WebAppShimManagerDelegate shim_manager(std::move(delegate));

  SetBrowserAppLauncherForTesting(base::BindLambdaForTesting(
      [&](const apps::AppLaunchParams& results) -> content::WebContents* {
        ValidateLaunchParams(results, expected_results);
        return nullptr;
      }));

  shim_manager.LaunchApp(profile(), AppId(), std::vector<base::FilePath>(),
                         std::vector<GURL>(), GURL(),
                         chrome::mojom::AppShimLoginItemRestoreState::kNone,
                         base::DoNothing());
}

TEST_F(WebAppShimManagerDelegateTest, LaunchApp_ProtocolWebPrefix) {
  GURL protocol_handler_launch_url("web+test://test");

  apps::AppLaunchParams expected_results =
      CreateLaunchParams(std::vector<base::FilePath>(), std::nullopt,
                         protocol_handler_launch_url, GURL());
  expected_results.launch_source = apps::LaunchSource::kFromProtocolHandler;

  std::unique_ptr<MockDelegate> delegate = std::make_unique<MockDelegate>();
  WebAppShimManagerDelegate shim_manager(std::move(delegate));

  SetBrowserAppLauncherForTesting(base::BindLambdaForTesting(
      [&](const apps::AppLaunchParams& results) -> content::WebContents* {
        ValidateLaunchParams(results, expected_results);
        return nullptr;
      }));

  shim_manager.LaunchApp(profile(), AppId(), std::vector<base::FilePath>(),
                         {protocol_handler_launch_url}, GURL(),
                         chrome::mojom::AppShimLoginItemRestoreState::kNone,
                         base::DoNothing());
}

TEST_F(WebAppShimManagerDelegateTest, LaunchApp_ProtocolMailTo) {
  GURL protocol_handler_launch_url("mailto://[email protected]");

  apps::AppLaunchParams expected_results =
      CreateLaunchParams(std::vector<base::FilePath>(), std::nullopt,
                         protocol_handler_launch_url, GURL());
  expected_results.launch_source = apps::LaunchSource::kFromProtocolHandler;

  std::unique_ptr<MockDelegate> delegate = std::make_unique<MockDelegate>();
  WebAppShimManagerDelegate shim_manager(std::move(delegate));

  SetBrowserAppLauncherForTesting(base::BindLambdaForTesting(
      [&](const apps::AppLaunchParams& results) -> content::WebContents* {
        ValidateLaunchParams(results, expected_results);
        return nullptr;
      }));

  shim_manager.LaunchApp(profile(), AppId(), std::vector<base::FilePath>(),
                         {protocol_handler_launch_url}, GURL(),
                         chrome::mojom::AppShimLoginItemRestoreState::kNone,
                         base::DoNothing());
}

TEST_F(WebAppShimManagerDelegateTest, LaunchApp_ProtocolFile) {
  GURL protocol_handler_launch_url("file:///test_app_path/test_app_file.txt");

  apps::AppLaunchParams expected_results = CreateLaunchParams(
      {base::FilePath("/test_app_path/test_app_file.txt")}, std::nullopt,
      std::nullopt, GURL("https://testpwa.com/files-txt"));

  std::unique_ptr<MockDelegate> delegate = std::make_unique<MockDelegate>();
  WebAppShimManagerDelegate shim_manager(std::move(delegate));

  SetBrowserAppLauncherForTesting(base::BindLambdaForTesting(
      [&](const apps::AppLaunchParams& results) -> content::WebContents* {
        ValidateLaunchParams(results, expected_results);
        return nullptr;
      }));

  shim_manager.LaunchApp(profile(), AppId(), std::vector<base::FilePath>(),
                         {protocol_handler_launch_url}, GURL(),
                         chrome::mojom::AppShimLoginItemRestoreState::kNone,
                         base::DoNothing());
}

TEST_F(WebAppShimManagerDelegateTest, LaunchApp_ProtocolDisallowed) {
  GURL protocol_handler_launch_url("https://www.test.com/");

  apps::AppLaunchParams expected_results = CreateLaunchParams(
      std::vector<base::FilePath>(), std::nullopt, std::nullopt, GURL());

  std::unique_ptr<MockDelegate> delegate = std::make_unique<MockDelegate>();
  WebAppShimManagerDelegate shim_manager(std::move(delegate));

  SetBrowserAppLauncherForTesting(base::BindLambdaForTesting(
      [&](const apps::AppLaunchParams& results) -> content::WebContents* {
        ValidateLaunchParams(results, expected_results);
        return nullptr;
      }));

  shim_manager.LaunchApp(profile(), AppId(), std::vector<base::FilePath>(),
                         {protocol_handler_launch_url}, GURL(),
                         chrome::mojom::AppShimLoginItemRestoreState::kNone,
                         base::DoNothing());
}

TEST_F(WebAppShimManagerDelegateTest, LaunchApp_FileFullPath) {
  const base::FilePath::CharType kTestPath[] =
      FILE_PATH_LITERAL("/test_app_path/test_app_file.txt");
  base::FilePath test_path(kTestPath);

  apps::AppLaunchParams expected_results =
      CreateLaunchParams({test_path}, std::nullopt, std::nullopt,
                         GURL("https://testpwa.com/files-txt"));

  std::unique_ptr<MockDelegate> delegate = std::make_unique<MockDelegate>();
  WebAppShimManagerDelegate shim_manager(std::move(delegate));

  SetBrowserAppLauncherForTesting(base::BindLambdaForTesting(
      [&](const apps::AppLaunchParams& results) -> content::WebContents* {
        ValidateLaunchParams(results, expected_results);
        return nullptr;
      }));

  shim_manager.LaunchApp(
      profile(), AppId(), {test_path}, std::vector<GURL>(), GURL(),
      chrome::mojom::AppShimLoginItemRestoreState::kNone, base::DoNothing());
}

TEST_F(WebAppShimManagerDelegateTest, LaunchApp_FileRelativePath) {
  const base::FilePath::CharType kTestPath[] =
      FILE_PATH_LITERAL("test_app_path/test_app_file.txt");
  base::FilePath test_path(kTestPath);

  apps::AppLaunchParams expected_results =
      CreateLaunchParams({test_path}, std::nullopt, std::nullopt,
                         GURL("https://testpwa.com/files-txt"));

  std::unique_ptr<MockDelegate> delegate = std::make_unique<MockDelegate>();
  WebAppShimManagerDelegate shim_manager(std::move(delegate));

  SetBrowserAppLauncherForTesting(base::BindLambdaForTesting(
      [&](const apps::AppLaunchParams& results) -> content::WebContents* {
        ValidateLaunchParams(results, expected_results);
        return nullptr;
      }));

  shim_manager.LaunchApp(
      profile(), AppId(), {test_path}, std::vector<GURL>(), GURL(),
      chrome::mojom::AppShimLoginItemRestoreState::kNone, base::DoNothing());
}

TEST_F(WebAppShimManagerDelegateTest, LaunchApp_FileHandlersLaunchType) {
  const base::FilePath::CharType kTestPathTxt[] =
      FILE_PATH_LITERAL("test_app_path/test_app_file.txt");
  const base::FilePath::CharType kTestPathTxt2[] =
      FILE_PATH_LITERAL("test_app_path/test_app_file2.txt");
  const base::FilePath::CharType kTestPathPng[] =
      FILE_PATH_LITERAL("test_app_path/test_app_file.png");
  const base::FilePath::CharType kTestPathPng2[] =
      FILE_PATH_LITERAL("test_app_path/test_app_file2.png");

  std::unique_ptr<MockDelegate> delegate = std::make_unique<MockDelegate>();
  WebAppShimManagerDelegate shim_manager(std::move(delegate));
  int launches = 0;

  SetBrowserAppLauncherForTesting(base::BindLambdaForTesting(
      [&](const apps::AppLaunchParams& result) -> content::WebContents* {
        if (result.launch_files.size() == 1u) {
          EXPECT_EQ(GURL("https://testpwa.com/files-png"), result.override_url);
        } else {
          EXPECT_EQ(2U, result.launch_files.size());
          EXPECT_EQ(GURL("https://testpwa.com/files-txt"), result.override_url);
        }
        ++launches;
        return nullptr;
      }));

  shim_manager.LaunchApp(
      profile(), AppId(),
      {base::FilePath(kTestPathTxt), base::FilePath(kTestPathPng),
       base::FilePath(kTestPathTxt2), base::FilePath(kTestPathPng2)},
      {}, GURL(), chrome::mojom::AppShimLoginItemRestoreState::kNone,
      base::DoNothing());
  EXPECT_EQ(3, launches);
}

TEST_F(WebAppShimManagerDelegateTest, LaunchApp_ProtocolAndFileHandlerMixed) {
  GURL protocol_handler_launch_url("web+test://test");
  const base::FilePath::CharType kTestPath[] =
      FILE_PATH_LITERAL("test_app_path/test_app_file.txt");
  base::FilePath test_path(kTestPath);

  apps::AppLaunchParams expected_results =
      CreateLaunchParams({}, std::nullopt, protocol_handler_launch_url, GURL());
  expected_results.launch_source = apps::LaunchSource::kFromProtocolHandler;

  std::unique_ptr<MockDelegate> delegate = std::make_unique<MockDelegate>();
  WebAppShimManagerDelegate shim_manager(std::move(delegate));

  SetBrowserAppLauncherForTesting(base::BindLambdaForTesting(
      [&](const apps::AppLaunchParams& results) -> content::WebContents* {
        ValidateLaunchParams(results, expected_results);
        return nullptr;
      }));

  shim_manager.LaunchApp(
      profile(), AppId(), {test_path}, {protocol_handler_launch_url}, GURL(),
      chrome::mojom::AppShimLoginItemRestoreState::kNone, base::DoNothing());
}

TEST_F(WebAppShimManagerDelegateTest,
       LaunchApp_ProtocolWithFileAndFileHandlerMixed) {
  GURL protocol_handler_launch_url("web+test://test");
  GURL protocol_handler_file_url("file:///test_app_path/test_app_file.txt");
  const base::FilePath::CharType kTestPath[] =
      FILE_PATH_LITERAL("test_app_path/test_app_file.txt");
  base::FilePath test_path(kTestPath);

  apps::AppLaunchParams expected_results =
      CreateLaunchParams({}, std::nullopt, protocol_handler_launch_url, GURL());
  expected_results.launch_source = apps::LaunchSource::kFromProtocolHandler;

  std::unique_ptr<MockDelegate> delegate = std::make_unique<MockDelegate>();
  WebAppShimManagerDelegate shim_manager(std::move(delegate));

  SetBrowserAppLauncherForTesting(base::BindLambdaForTesting(
      [&](const apps::AppLaunchParams& results) -> content::WebContents* {
        ValidateLaunchParams(results, expected_results);
        return nullptr;
      }));

  shim_manager.LaunchApp(
      profile(), AppId(), {test_path},
      {protocol_handler_launch_url, protocol_handler_file_url}, GURL(),
      chrome::mojom::AppShimLoginItemRestoreState::kNone, base::DoNothing());
}

TEST_F(WebAppShimManagerDelegateTest, LaunchApp_OverrideUrl) {
  GURL override_url("index.html");
  apps::AppLaunchParams expected_results = CreateLaunchParams(
      std::vector<base::FilePath>(), std::nullopt, std::nullopt, override_url);

  std::unique_ptr<MockDelegate> delegate = std::make_unique<MockDelegate>();
  WebAppShimManagerDelegate shim_manager(std::move(delegate));

  SetBrowserAppLauncherForTesting(base::BindLambdaForTesting(
      [&](const apps::AppLaunchParams& results) -> content::WebContents* {
        ValidateLaunchParams(results, expected_results);
        return nullptr;
      }));

  shim_manager.LaunchApp(profile(), AppId(), std::vector<base::FilePath>(),
                         std::vector<GURL>(), override_url,
                         chrome::mojom::AppShimLoginItemRestoreState::kNone,
                         base::DoNothing());
}

TEST_F(WebAppShimManagerDelegateTest, GetAppShortcutsMenuItemInfos) {
  std::unique_ptr<MockDelegate> delegate = std::make_unique<MockDelegate>();
  WebAppShimManagerDelegate shim_manager(std::move(delegate));

  // Validate empty array when app does not have shortcut menus declared in the
  // manifest.
  {
    auto shortcut_menu_items =
        shim_manager.GetAppShortcutsMenuItemInfos(profile(), AppId());
    EXPECT_EQ(0U, shortcut_menu_items.size());
  }

  // Validate array when app does declare shortcut menus in the manifest.
  {
    // Install a dummy app with shortcut menu items
    WebAppShortcutsMenuItemInfo shortcut_info1;
    WebAppShortcutsMenuItemInfo shortcut_info2;
    WebAppShortcutsMenuItemInfo shortcut_info3;

    auto web_app_info = WebAppInstallInfo::CreateWithStartUrlForTesting(
        GURL("https://mytestpwa.com/"));
    web_app_info->title = u"WebAppTestWithShortcutMenuItems";
    web_app_info->scope = web_app_info->start_url();
    web_app_info->description = web_app_info->title;
    web_app_info->user_display_mode =
        web_app::mojom::UserDisplayMode::kStandalone;

    shortcut_info1.name = u"shortcut_info1";
    shortcut_info1.url = GURL(".");
    web_app_info->shortcuts_menu_item_infos.push_back(shortcut_info1);

    shortcut_info2.name = u"shortcut_info2";
    shortcut_info2.url = GURL("/settings");
    web_app_info->shortcuts_menu_item_infos.push_back(shortcut_info2);

    shortcut_info3.name = u"shortcut_info3";
    shortcut_info3.url = GURL("https://anothersite.com");
    web_app_info->shortcuts_menu_item_infos.push_back(shortcut_info3);

    webapps::AppId shortcut_app_id =
        web_app::test::InstallWebApp(profile(), std::move(web_app_info));
    auto shortcut_menu_items =
        shim_manager.GetAppShortcutsMenuItemInfos(profile(), shortcut_app_id);

    ASSERT_EQ(3U, shortcut_menu_items.size());
    EXPECT_EQ(shortcut_info1.name, shortcut_menu_items[0]->name);
    EXPECT_EQ(shortcut_info1.url, shortcut_menu_items[0]->url);
    EXPECT_EQ(shortcut_info2.name, shortcut_menu_items[1]->name);
    EXPECT_EQ(shortcut_info2.url, shortcut_menu_items[1]->url);
    EXPECT_EQ(shortcut_info3.name, shortcut_menu_items[2]->name);
    EXPECT_EQ(shortcut_info3.url, shortcut_menu_items[2]->url);
  }
}

}  // namespace

}  // namespace web_app