blob: 4d4fb981a3953949122e6f902e8e387041a2d807 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// 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/macros.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_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/runtime_data.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),
runtime_data_(ExtensionRegistry::Get(context)) {}
~TestExtensionSystem() override = default;
RuntimeData* runtime_data() override { return &runtime_data_; }
AppSorting* app_sorting() override { return &app_sorting_; }
private:
RuntimeData runtime_data_;
NullAppSorting app_sorting_;
DISALLOW_COPY_AND_ASSIGN(TestExtensionSystem);
};
#if defined(USE_AURA)
// An AppWindowClient for use without a DesktopController.
class TestAppWindowClient : public ShellAppWindowClient {
public:
TestAppWindowClient() = default;
~TestAppWindowClient() override = default;
// ShellAppWindowClient:
NativeAppWindow* CreateNativeAppWindow(
AppWindow* window,
AppWindow::CreateParams* params) override {
return new ShellNativeAppWindowAura(window, *params);
}
private:
DISALLOW_COPY_AND_ASSIGN(TestAppWindowClient);
};
#endif
} // namespace
class ShellExtensionLoaderTest : public ExtensionsTest {
protected:
ShellExtensionLoaderTest() = default;
~ShellExtensionLoaderTest() override = default;
void SetUp() override {
// Register factory so it's created with the BrowserContext.
apps::AppLifetimeMonitorFactory::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());
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(Manifest::COMMAND_LINE, 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_;
TestEventRouter* event_router_ = nullptr; // Created in SetUp().
DISALLOW_COPY_AND_ASSIGN(ShellExtensionLoaderTest);
};
// 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())
->SetExtensionDisabled(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())
->SetExtensionDisabled(
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 {
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_;
DISALLOW_COPY_AND_ASSIGN(ShellExtensionLoaderTestAura);
};
// 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.
TEST_F(ShellExtensionLoaderTestAura, 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