blob: 747ce8b1b983c37cdbe3935bd2c426f526c1f577 [file] [log] [blame]
// Copyright 2024 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/extensions/platform_test_extension_loader.h"
#include "chrome/browser/extensions/desktop_android/desktop_android_extension_system.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/render_process_host.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_user_script_loader.h"
#include "extensions/browser/install_prefs_helper.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "extensions/browser/user_script_manager.h"
#include "extensions/common/file_util.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/manifest_handlers/content_scripts_handler.h"
#include "extensions/common/manifest_handlers/incognito_info.h"
#include "extensions/test/extension_background_page_waiter.h"
#include "extensions/test/extension_test_notification_observer.h"
#include "extensions/test/test_content_script_load_waiter.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace extensions {
PlatformTestExtensionLoader::PlatformTestExtensionLoader(
content::BrowserContext* browser_context)
: browser_context_(browser_context),
extension_system_(ExtensionSystem::Get(browser_context)),
extension_service_(extension_system_->extension_service()),
extension_registry_(ExtensionRegistry::Get(browser_context)) {}
PlatformTestExtensionLoader::~PlatformTestExtensionLoader() = default;
scoped_refptr<const Extension> PlatformTestExtensionLoader::LoadExtension(
const base::FilePath& path) {
// Clean up the kMetadataFolder if necessary.
file_util::MaybeCleanupMetadataFolder(path);
scoped_refptr<const Extension> extension = LoadExtensionFromDirectory(path);
if (!extension) {
return nullptr;
}
extension_id_ = extension->id();
extension = extension_registry_->enabled_extensions().GetByID(extension_id_);
if (!extension) {
return nullptr;
}
if (!VerifyPermissions(extension.get())) {
ADD_FAILURE() << "The extension did not get the requested permissions.";
return nullptr;
}
if (!CheckInstallWarnings(*extension)) {
return nullptr;
}
if (!WaitForExtensionReady(*extension)) {
ADD_FAILURE() << "Failed to wait for extension ready";
return nullptr;
}
return extension;
}
scoped_refptr<const Extension>
PlatformTestExtensionLoader::LoadExtensionFromDirectory(
const base::FilePath& file_path) {
base::ScopedAllowBlockingForTesting allow_blocking;
std::string load_error;
scoped_refptr<Extension> extension = file_util::LoadExtension(
file_path, mojom::ManifestLocation::kUnpacked, 0, &load_error);
if (!extension) {
ADD_FAILURE() << "Failed to parse extension: " << load_error;
return nullptr;
}
// Force file access and/or incognito state and set install param if
// requested. This must occur before the call to AddExtension() below,
// because ExtensionRegistry observers do things like load content scripts,
// and the incognito status must be set.
ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context_);
if (allow_file_access_.has_value()) {
prefs->SetAllowFileAccess(extension->id(), *allow_file_access_);
}
if (allow_incognito_access_.has_value()) {
prefs->SetIsIncognitoEnabled(extension->id(), *allow_incognito_access_);
}
if (install_param_.has_value()) {
SetInstallParam(prefs, extension->id(), *install_param_);
}
extension_registry_->TriggerOnWillBeInstalled(extension.get(),
/*is_update=*/false,
/*old_name=*/std::string());
auto* android_system = static_cast<DesktopAndroidExtensionSystem*>(
ExtensionSystem::Get(browser_context_));
std::string error;
if (!android_system->AddExtension(extension, error)) {
ADD_FAILURE() << "Failed to add extension: " << error;
}
extension_registry_->TriggerOnInstalled(extension.get(), /*is_update=*/false);
ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context_);
if (!registry->enabled_extensions().Contains(extension->id())) {
ADD_FAILURE() << "Extension is not properly enabled.";
return nullptr;
}
return extension;
}
bool PlatformTestExtensionLoader::VerifyPermissions(
const Extension* extension) {
const ExtensionPrefs* prefs = ExtensionPrefs::Get(browser_context_);
if (allow_file_access_.has_value() &&
prefs->AllowFileAccess(extension->id()) != *allow_file_access_) {
return false;
}
if (allow_incognito_access_.has_value() &&
prefs->IsIncognitoEnabled(extension->id()) != *allow_incognito_access_) {
return false;
}
return true;
}
bool PlatformTestExtensionLoader::CheckInstallWarnings(
const Extension& extension) {
if (ignore_manifest_warnings_) {
return true;
}
const std::vector<InstallWarning>& install_warnings =
extension.install_warnings();
std::string install_warnings_string;
for (const InstallWarning& warning : install_warnings) {
// Don't fail on the manifest v2 deprecation warning in tests for now.
// TODO(crbug.com/40804030): Stop skipping this warning when all
// tests are updated to MV3.
if (warning.message == manifest_errors::kManifestV2IsDeprecatedWarning) {
continue;
}
install_warnings_string += " " + warning.message + "\n";
}
if (install_warnings_string.empty()) {
return true;
}
ADD_FAILURE() << "Unexpected warnings for extension:\n"
<< install_warnings_string;
return false;
}
bool PlatformTestExtensionLoader::WaitForExtensionReady(
const Extension& extension) {
UserScriptManager* user_script_manager =
ExtensionSystem::Get(browser_context_)->user_script_manager();
// Note: `user_script_manager` can be null in tests.
if (user_script_manager &&
!ContentScriptsInfo::GetContentScripts(&extension).empty()) {
ExtensionUserScriptLoader* user_script_loader =
user_script_manager->GetUserScriptLoaderForExtension(extension_id_);
if (!user_script_loader->HasLoadedScripts()) {
ContentScriptLoadWaiter waiter(user_script_loader);
waiter.Wait();
}
}
const int num_processes =
content::RenderProcessHost::GetCurrentRenderProcessCountForTesting();
// Determine whether or not to wait for extension renderers. By default, we
// base this on whether any renderer processes exist (which is also a proxy
// for whether this is a browser test, since MockRenderProcessHosts and
// similar don't count towards the render process host count), but we allow
// tests to override this behavior.
const bool should_wait_for_ready =
wait_for_renderers_.value_or(num_processes > 0);
if (!should_wait_for_ready) {
return true;
}
content::BrowserContext* context_to_use =
IncognitoInfo::IsSplitMode(&extension)
? browser_context_.get()
: Profile::FromBrowserContext(browser_context_)->GetOriginalProfile();
// If possible, wait for the extension's background context to be loaded.
std::string reason_unused;
if (ExtensionBackgroundPageWaiter::CanWaitFor(extension, reason_unused)) {
ExtensionBackgroundPageWaiter(context_to_use, extension)
.WaitForBackgroundInitialized();
}
// TODO(devlin): Should this use |context_to_use|? Or should
// WaitForExtensionViewsToLoad check both contexts if one is OTR?
if (!ExtensionTestNotificationObserver(browser_context_)
.WaitForExtensionViewsToLoad()) {
return false;
}
return true;
}
} // namespace extensions