chromium/chrome/browser/extensions/extension_keeplist_lacros_browsertest.cc

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

#include "base/containers/contains.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_keeplist_chromeos.h"
#include "chrome/browser/lacros/browser_test_util.h"
#include "chrome/browser/lacros/for_which_extension_type.h"
#include "chrome/browser/lacros/lacros_extension_apps_controller.h"
#include "chrome/browser/lacros/lacros_extension_apps_publisher.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chromeos/lacros/lacros_test_helper.h"
#include "chromeos/startup/browser_params_proxy.h"
#include "content/public/test/browser_test.h"
#include "extension_keeplist_chromeos.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h"

namespace extensions {

namespace {

const char kExtensionRunInAshAndLacrosId[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaa";
const char kExtensionAppRunInAshAndLacrosId[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbb";
const char kExtensionRunInAshOnlyId[] = "cccccccccccccccccccccccccccc";
const char kExtensionAppRunInAshOnlyId[] = "dddddddddddddddddddddddddddd";

const char kTestExtensionId[] = "pkplfbidichfdicaijlchgnapepdginl";
const char kTestChromeAppId[] = "knldjmfmopnpolahpmmgbagdohdnhkik";
}  // namespace

using LacrosExtensionKeeplistTest = ExtensionApiTest;

// Tests that Ash extension keeplist data is passed from Ash to Lacros via
// crosapi::mojom::BrowserInitParams.
IN_PROC_BROWSER_TEST_F(LacrosExtensionKeeplistTest,
                       AshKeeplistFromBrowserInitParams) {
  // Verify Ash extension keeplist data is passed to Lacros from Ash via
  // crosapi::mojom::BrowserInitParams, and do some minimum sanity check to make
  // sure the extension list passed from Ash is not empty. We have a more
  // sophiscaited test in extension_keeplist_ash_browsertest.cc to verify the
  // keep lists are idnetical in Ash and Lacros for such case.
  ASSERT_FALSE(
      chromeos::BrowserParamsProxy::Get()->ExtensionKeepList().is_null());
  EXPECT_FALSE(GetExtensionsRunInOSAndStandaloneBrowser().empty());
  EXPECT_FALSE(GetExtensionAppsRunInOSAndStandaloneBrowser().empty());
  EXPECT_FALSE(GetExtensionsRunInOSOnly().empty());
  EXPECT_FALSE(GetExtensionAppsRunInOSOnly().empty());
}

class ExtensionAppsAppServiceBlocklistTest : public ExtensionBrowserTest {
 public:
  void SetUp() override {
    // Start unique Ash instance and pass ids of testing extension and chrome
    // app for Ash Extension Keeplist in additional Ash commandline switches.
    StartUniqueAshChrome(
        /*enabled_features=*/{}, /*disabled_features=*/{},
        {base::StringPrintf("extensions-run-in-ash-and-lacros=%s",
                            kTestExtensionId),
         base::StringPrintf("extension-apps-run-in-ash-and-lacros=%s",
                            kTestChromeAppId),
         base::StringPrintf("extension-apps-block-for-app-service-in-ash=%s",
                            kTestChromeAppId)},
        "crbug/1409199 test ash keeplist");
    ExtensionBrowserTest::SetUp();
  }

  void InstallTestChromeApp() {
    DCHECK(test_app_id_.empty());
    const Extension* extension = LoadExtension(
        test_data_dir_.AppendASCII("ash_extension_keeplist/simple_app"));
    test_app_id_ = extension->id();
    EXPECT_EQ(test_app_id_, kTestChromeAppId);
  }

  void InstallTestExtension() {
    DCHECK(!test_extension_);
    test_extension_ = LoadExtension(
        test_data_dir_.AppendASCII("ash_extension_keeplist/simple_extension"));
    EXPECT_EQ(test_extension_->id(), kTestExtensionId);
  }

  const std::string& test_app_id() const { return test_app_id_; }
  const Extension* test_extension() { return test_extension_; }

 private:
  // ExtensionBrowserTest:
  void TearDownOnMainThread() override {
    test_extension_ = nullptr;
    CloseAllAppWindows();
    ExtensionBrowserTest::TearDownOnMainThread();
  }

  void CloseAllAppWindows() {
    for (AppWindow* app_window :
         AppWindowRegistry::Get(profile())->app_windows()) {
      app_window->GetBaseWindow()->Close();
    }

    // Wait for item to stop existing in shelf.
    if (!test_app_id_.empty()) {
      ASSERT_TRUE(browser_test_util::WaitForShelfItem(test_app_id_,
                                                      /*exists=*/false));
    }
  }

  std::string test_app_id_;
  raw_ptr<const Extension> test_extension_ = nullptr;
};

// This tests publishing and launching the test app (running in both ash and
// lacros, but only published to App Service in Lacros) with app service.
IN_PROC_BROWSER_TEST_F(ExtensionAppsAppServiceBlocklistTest,
                       TestAppLaunchInAppList) {
  CHECK(IsAppServiceBlocklistCrosapiSupported());

  // Create the controller and publisher.
  std::unique_ptr<LacrosExtensionAppsPublisher> publisher =
      LacrosExtensionAppsPublisher::MakeForChromeApps();
  publisher->Initialize();
  std::unique_ptr<LacrosExtensionAppsController> controller =
      LacrosExtensionAppsController::MakeForChromeApps();
  controller->Initialize(publisher->publisher());

  // Install the testing chrome app in Lacros.
  InstallTestChromeApp();

  // TODO(crbug.com/40274283): Install the testing chrome app in Ash and make
  // sure it is not published to App Service in Ash. Since we don't have a
  // convenient way to install an extension app in Ash from Lacros browser test,
  // we will defer that until crbug/1459375 is fixed.

  EXPECT_TRUE(ExtensionAppRunsInBothOSAndStandaloneBrowser(test_app_id()));
  EXPECT_FALSE(
      ExtensionAppBlockListedForAppServiceInStandaloneBrowser(test_app_id()));

  // The test chrome app item should not exist in the shelf before the app is
  // launched.
  ASSERT_TRUE(
      browser_test_util::WaitForShelfItem(test_app_id(), /*exists=*/false));

  // There should be no app windows.
  ASSERT_TRUE(AppWindowRegistry::Get(profile())->app_windows().empty());

  // The test app should have been published in app service by lacros,
  // and can be launched from app list.
  chromeos::LacrosService::Get()
      ->GetRemote<crosapi::mojom::TestController>()
      ->LaunchAppFromAppList(test_app_id());

  // Wait for item to exist in shelf.
  ASSERT_TRUE(
      browser_test_util::WaitForShelfItem(test_app_id(), /*exists=*/true));
}

// This tests the test extension (running in both ash and lacros, but not
// published to app service) should be rejected by ForWhichExtensionType, i.e.,
// returning false for Matches()).
IN_PROC_BROWSER_TEST_F(ExtensionAppsAppServiceBlocklistTest,
                       ExtensionNotMatch) {
  CHECK(IsAppServiceBlocklistCrosapiSupported());

  ForWhichExtensionType for_which_type =
      ForWhichExtensionType(InitForExtensions());

  InstallTestExtension();
  EXPECT_TRUE(
      ExtensionRunsInBothOSAndStandaloneBrowser(test_extension()->id()));
  EXPECT_FALSE(for_which_type.Matches(test_extension()));
}

class KeeplistIdsFromAshCmdlineSwitchTest : public ExtensionBrowserTest {
 public:
  void SetUp() override {
    // Start unique Ash instance and pass ids of testing extensions and chrome
    // apps for Ash Extension Keeplist in the additional Ash commandline
    // switches.
    StartUniqueAshChrome(
        /*enabled_features=*/{}, /*disabled_features=*/{},
        {base::StringPrintf("extensions-run-in-ash-and-lacros=%s",
                            kExtensionRunInAshAndLacrosId),
         base::StringPrintf("extension-apps-run-in-ash-and-lacros=%s",
                            kExtensionAppRunInAshAndLacrosId),
         base::StringPrintf("extensions-run-in-ash-only=%s",
                            kExtensionRunInAshOnlyId),
         base::StringPrintf("extension-apps-run-in-ash-only=%s",
                            kExtensionAppRunInAshOnlyId)},
        "crbug/1371250 extension and chrome app running in both ash and "
        "lacros");
    ExtensionBrowserTest::SetUp();
  }
};

IN_PROC_BROWSER_TEST_F(KeeplistIdsFromAshCmdlineSwitchTest, GetTestIds) {
  EXPECT_TRUE(
      ExtensionRunsInBothOSAndStandaloneBrowser(kExtensionRunInAshAndLacrosId));
  EXPECT_TRUE(ExtensionRunsInOS(kExtensionRunInAshAndLacrosId));
  EXPECT_TRUE(ExtensionAppRunsInBothOSAndStandaloneBrowser(
      kExtensionAppRunInAshAndLacrosId));
  EXPECT_TRUE(ExtensionAppRunsInOS(kExtensionAppRunInAshAndLacrosId));
  EXPECT_TRUE(ExtensionRunsInOSOnly(kExtensionRunInAshOnlyId));
  EXPECT_TRUE(ExtensionRunsInOS(kExtensionRunInAshOnlyId));
  EXPECT_TRUE(ExtensionAppRunsInOSOnly(kExtensionAppRunInAshOnlyId));
  EXPECT_TRUE(ExtensionAppRunsInOS(kExtensionAppRunInAshOnlyId));
}

}  // namespace extensions