910e62b5创建于 1月15日历史提交
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "extensions/shell/browser/shell_extension_loader.h"

#include <memory>
#include <string>

#include "apps/app_lifetime_monitor_factory.h"
#include "base/memory/raw_ptr.h"
#include "base/path_service.h"
#include "components/crx_file/id_util.h"
#include "components/keep_alive_registry/keep_alive_registry.h"
#include "components/user_prefs/user_prefs.h"
#include "content/public/browser/browser_context.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registrar_factory.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extensions_test.h"
#include "extensions/browser/mock_extension_system.h"
#include "extensions/browser/null_app_sorting.h"
#include "extensions/browser/test_event_router.h"
#include "extensions/browser/test_extensions_browser_client.h"
#include "extensions/common/api/app_runtime.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/extension_paths.h"
#include "extensions/test/test_extension_dir.h"

#if defined(USE_AURA)
#include "extensions/browser/app_window/app_window.h"
#include "extensions/shell/browser/shell_app_window_client.h"
#include "extensions/shell/browser/shell_native_app_window_aura.h"
#include "extensions/shell/test/shell_test_extensions_browser_client.h"
#include "extensions/shell/test/shell_test_helper_aura.h"
#endif

namespace extensions {

namespace OnLaunched = api::app_runtime::OnLaunched;

namespace {

class TestExtensionSystem : public MockExtensionSystem {
 public:
  explicit TestExtensionSystem(content::BrowserContext* context)
      : MockExtensionSystem(context) {}

  TestExtensionSystem(const TestExtensionSystem&) = delete;
  TestExtensionSystem& operator=(const TestExtensionSystem&) = delete;

  ~TestExtensionSystem() override = default;

  AppSorting* app_sorting() override { return &app_sorting_; }

 private:
  NullAppSorting app_sorting_;
};

#if defined(USE_AURA)
// An AppWindowClient for use without a DesktopController.
class TestAppWindowClient : public ShellAppWindowClient {
 public:
  TestAppWindowClient() = default;

  TestAppWindowClient(const TestAppWindowClient&) = delete;
  TestAppWindowClient& operator=(const TestAppWindowClient&) = delete;

  ~TestAppWindowClient() override = default;

  // ShellAppWindowClient:
  std::unique_ptr<NativeAppWindow> CreateNativeAppWindow(
      AppWindow* window,
      AppWindow::CreateParams* params) override {
    return std::make_unique<ShellNativeAppWindowAura>(window, *params);
  }
};
#endif

}  // namespace

class ShellExtensionLoaderTest : public ExtensionsTest {
 public:
  ShellExtensionLoaderTest(const ShellExtensionLoaderTest&) = delete;
  ShellExtensionLoaderTest& operator=(const ShellExtensionLoaderTest&) = delete;

 protected:
  ShellExtensionLoaderTest() = default;
  ~ShellExtensionLoaderTest() override = default;

  void SetUp() override {
    // Register factory so it's created with the BrowserContext.
    apps::AppLifetimeMonitorFactory::GetInstance();

    // Ensure ExtensionRegistrarFactory instance is created.
    ExtensionRegistrarFactory::GetInstance();

    ExtensionsTest::SetUp();
    extensions_browser_client()->set_extension_system_factory(&factory_);
    // ExtensionsTest sets up the ExtensionPrefs, but we still need to attach
    // the PrefService to the browser context.
    user_prefs::UserPrefs::Set(browser_context(), pref_service());
    event_router_ = CreateAndUseTestEventRouter(browser_context());
  }

  void TearDown() override {
    EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsKeepingAlive());
    event_router_ = nullptr;
    ExtensionsTest::TearDown();
  }

  // Returns the path to a test directory.
  base::FilePath GetExtensionPath(const std::string& dir) {
    base::FilePath test_data_dir;
    base::PathService::Get(DIR_TEST_DATA, &test_data_dir);
    return test_data_dir.AppendASCII(dir);
  }

  // Verifies the extension is correctly created and enabled.
  void ExpectEnabled(const Extension* extension) {
    ASSERT_TRUE(extension);
    EXPECT_EQ(mojom::ManifestLocation::kCommandLine, extension->location());
    ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
    EXPECT_TRUE(registry->enabled_extensions().Contains(extension->id()));
    EXPECT_FALSE(registry->GetExtensionById(
        extension->id(),
        ExtensionRegistry::EVERYTHING & ~ExtensionRegistry::ENABLED));
  }

  // Verifies the extension with the given ID is disabled.
  void ExpectDisabled(const ExtensionId& extension_id) {
    ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
    EXPECT_TRUE(registry->disabled_extensions().Contains(extension_id));
    EXPECT_FALSE(registry->GetExtensionById(
        extension_id,
        ExtensionRegistry::EVERYTHING & ~ExtensionRegistry::DISABLED));
  }

  TestEventRouter* event_router() { return event_router_; }

 private:
  MockExtensionSystemFactory<TestExtensionSystem> factory_;

  raw_ptr<TestEventRouter> event_router_ = nullptr;  // Created in SetUp().
};

// Tests with a non-existent directory.
TEST_F(ShellExtensionLoaderTest, NotFound) {
  ShellExtensionLoader loader(browser_context());

  const Extension* extension =
      loader.LoadExtension(GetExtensionPath("nonexistent"));
  ASSERT_FALSE(extension);
  EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsKeepingAlive());
}

// Tests loading and reloading an extension.
TEST_F(ShellExtensionLoaderTest, Extension) {
  ShellExtensionLoader loader(browser_context());

  const Extension* extension =
      loader.LoadExtension(GetExtensionPath("extension"));
  ExpectEnabled(extension);

  // Extensions shouldn't receive the onLaunched event.
  EXPECT_EQ(0, event_router()->GetEventCount(OnLaunched::kEventName));

  // No keep-alives are used for non-app extensions.
  const ExtensionId extension_id = extension->id();
  loader.ReloadExtension(extension->id());
  ExpectDisabled(extension_id);
  EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsKeepingAlive());

  // Wait for load.
  content::RunAllTasksUntilIdle();
  extension = ExtensionRegistry::Get(browser_context())
                  ->GetInstalledExtension(extension_id);
  ExpectEnabled(extension);
  EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsKeepingAlive());

  // Not an app.
  EXPECT_EQ(0, event_router()->GetEventCount(OnLaunched::kEventName));
}

// Tests that the extension is added as enabled even if is disabled in
// ExtensionPrefs. Unlike Chrome, AppShell doesn't have a UI surface for
// re-enabling a disabled extension.
TEST_F(ShellExtensionLoaderTest, LoadAfterReloadFailure) {
  base::FilePath extension_path = GetExtensionPath("extension");
  const ExtensionId extension_id =
      crx_file::id_util::GenerateIdForPath(extension_path);
  ExtensionPrefs::Get(browser_context())
      ->AddDisableReason(extension_id, disable_reason::DISABLE_RELOAD);

  ShellExtensionLoader loader(browser_context());
  const Extension* extension = loader.LoadExtension(extension_path);
  ExpectEnabled(extension);
}

// Tests that the extension is not added if it is disabled in ExtensionPrefs
// for reasons beyond reloading.
TEST_F(ShellExtensionLoaderTest, LoadDisabledExtension) {
  base::FilePath extension_path = GetExtensionPath("extension");
  const ExtensionId extension_id =
      crx_file::id_util::GenerateIdForPath(extension_path);
  ExtensionPrefs::Get(browser_context())
      ->AddDisableReasons(extension_id, {disable_reason::DISABLE_RELOAD,
                                         disable_reason::DISABLE_USER_ACTION});

  ShellExtensionLoader loader(browser_context());
  const Extension* extension = loader.LoadExtension(extension_path);
  ExpectDisabled(extension->id());
}

#if defined(USE_AURA)
class ShellExtensionLoaderTestAura : public ShellExtensionLoaderTest {
 public:
  ShellExtensionLoaderTestAura(const ShellExtensionLoaderTestAura&) = delete;
  ShellExtensionLoaderTestAura& operator=(const ShellExtensionLoaderTestAura&) =
      delete;

 protected:
  ShellExtensionLoaderTestAura() = default;
  ~ShellExtensionLoaderTestAura() override = default;

  void SetUp() override {
    aura_helper_ = std::make_unique<ShellTestHelperAura>();
    aura_helper_->SetUp();

    std::unique_ptr<TestExtensionsBrowserClient>
        test_extensions_browser_client =
            std::make_unique<ShellTestExtensionsBrowserClient>();
    SetExtensionsBrowserClient(std::move(test_extensions_browser_client));
    AppWindowClient::Set(&app_window_client_);

    ShellExtensionLoaderTest::SetUp();
  }

  void TearDown() override {
    ShellExtensionLoaderTest::TearDown();
    AppWindowClient::Set(nullptr);
    aura_helper_->TearDown();
  }

  // Returns an initialized app window for the extension.
  // The app window deletes itself when its native window is closed.
  AppWindow* CreateAppWindow(const Extension* extension) {
    AppWindow* app_window =
        app_window_client_.CreateAppWindow(browser_context(), extension);
    aura_helper_->InitAppWindow(app_window);
    return app_window;
  }

 private:
  std::unique_ptr<ShellTestHelperAura> aura_helper_;
  TestAppWindowClient app_window_client_;
};

// Tests loading and launching a platform app.
TEST_F(ShellExtensionLoaderTestAura, AppLaunch) {
  ShellExtensionLoader loader(browser_context());

  const Extension* extension =
      loader.LoadExtension(GetExtensionPath("platform_app"));
  ExpectEnabled(extension);

  // A keep-alive is waiting for the app to launch its first window.
  // (Not strictly necessary in AppShell, because DesktopWindowControllerAura
  // doesn't consider quitting until an already-open window is closed, but
  // ShellExtensionLoader and ShellKeepAliveRequester don't make that
  // assumption.)
  EXPECT_TRUE(KeepAliveRegistry::GetInstance()->IsKeepingAlive());

  {
    // When AppShell launches the app window, the keep-alive is removed.
    AppWindow* app_window = CreateAppWindow(extension);
    EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsKeepingAlive());
    app_window->GetBaseWindow()->Close();  // Deletes |app_window|.
  }
}

// Tests loading, launching and reloading a platform app.
TEST_F(ShellExtensionLoaderTestAura, AppLaunchAndReload) {
  ShellExtensionLoader loader(browser_context());

  const Extension* extension =
      loader.LoadExtension(GetExtensionPath("platform_app"));
  const ExtensionId extension_id = extension->id();
  ExpectEnabled(extension);
  EXPECT_TRUE(KeepAliveRegistry::GetInstance()->IsKeepingAlive());

  CreateAppWindow(extension);
  EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsKeepingAlive());

  // Reload the app.
  loader.ReloadExtension(extension->id());
  ExpectDisabled(extension_id);
  EXPECT_TRUE(KeepAliveRegistry::GetInstance()->IsKeepingAlive());

  // Complete the reload. ShellExtensionLoader sends the onLaunched event.
  content::RunAllTasksUntilIdle();
  extension = ExtensionRegistry::Get(browser_context())
                  ->GetInstalledExtension(extension_id);
  ExpectEnabled(extension);
  EXPECT_EQ(1, event_router()->GetEventCount(OnLaunched::kEventName));
  EXPECT_TRUE(KeepAliveRegistry::GetInstance()->IsKeepingAlive());

  {
    // The keep-alive is destroyed when an app window is launched.
    AppWindow* app_window = CreateAppWindow(extension);
    EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsKeepingAlive());

    app_window->GetBaseWindow()->Close();
  }
}

// Tests failing to reload an app.
// TODO(crbug.com/40742257): Flaky on Linux, ChromeOS, and similar.
TEST_F(ShellExtensionLoaderTestAura, DISABLED_ReloadFailure) {
  ShellExtensionLoader loader(browser_context());
  ExtensionId extension_id;

  // Create an extension in a temporary directory so we can delete it before
  // trying to reload it.
  {
    TestExtensionDir extension_dir;
    extension_dir.WriteManifest(
        R"({
             "name": "Test Platform App",
             "version": "1",
             "manifest_version": 2,
              "app": {
                "background": {
                  "scripts": ["background.js"]
                }
              }
           })");
    extension_dir.WriteFile(FILE_PATH_LITERAL("background.js"), "");

    const Extension* extension =
        loader.LoadExtension(extension_dir.UnpackedPath());
    ASSERT_TRUE(extension);
    extension_id = extension->id();
    ExpectEnabled(extension);
    EXPECT_TRUE(KeepAliveRegistry::GetInstance()->IsKeepingAlive());

    // Launch an app window.
    CreateAppWindow(extension);
    EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsKeepingAlive());

    // Reload the app.
    loader.ReloadExtension(extension->id());
    EXPECT_TRUE(KeepAliveRegistry::GetInstance()->IsKeepingAlive());
  }

  // Wait for load (which will fail because the directory is missing).
  content::RunAllTasksUntilIdle();

  ExpectDisabled(extension_id);
  EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsKeepingAlive());

  // We can't reload an extension that is already disabled for reloading, so
  // trying to reload this extension shouldn't result in a dangling keep-alive.
  loader.ReloadExtension(extension_id);
  EXPECT_FALSE(KeepAliveRegistry::GetInstance()->IsKeepingAlive());
}
#endif

}  // namespace extensions