1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
|
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <string>
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/extension_disabled_ui.h"
#include "chrome/browser/extensions/extension_error_controller.h"
#include "chrome/browser/extensions/extension_error_ui_desktop.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/external_install_error.h"
#include "chrome/browser/extensions/external_provider_manager.h"
#include "chrome/browser/extensions/scoped_test_mv2_enabler.h"
#include "chrome/browser/extensions/test_blocklist.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/recovery/recovery_install_global_error.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/global_error/global_error_observer.h"
#include "chrome/browser/ui/global_error/global_error_service.h"
#include "chrome/browser/ui/global_error/global_error_service_factory.h"
#include "chrome/browser/ui/test/test_browser_dialog.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "components/crx_file/crx_verifier.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/extension_creator.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/mock_external_provider.h"
#include "extensions/browser/sandboxed_unpacker.h"
#include "extensions/common/api/extension_action/action_info.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/feature_switch.h"
namespace {
// Shows the first GlobalError with associated UI associated with |browser|.
void ShowPendingError(Browser* browser) {
GlobalErrorService* service =
GlobalErrorServiceFactory::GetForProfile(browser->profile());
GlobalError* error = service->GetFirstGlobalErrorWithBubbleView();
ASSERT_TRUE(error);
error->ShowBubbleView(browser);
}
// Packs an extension from the extensions test data folder into a crx.
base::FilePath PackCRXInTempDir(base::ScopedTempDir* temp_dir,
const char* extension_folder,
const char* pem_file) {
EXPECT_TRUE(temp_dir->CreateUniqueTempDir());
base::FilePath crx_path = temp_dir->GetPath().AppendASCII("temp.crx");
base::FilePath test_data;
EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data));
test_data = test_data.AppendASCII("extensions");
base::FilePath dir_path = test_data.AppendASCII(extension_folder);
base::FilePath pem_path = test_data.AppendASCII(pem_file);
EXPECT_TRUE(extensions::ExtensionCreator().Run(
dir_path, crx_path, pem_path, base::FilePath(),
extensions::ExtensionCreator::kOverwriteCRX));
EXPECT_TRUE(base::PathExists(crx_path));
return crx_path;
}
// Helper to wait for a global error to be added. To stop waiting, the global
// error must have a bubble view.
class GlobalErrorWaiter : public GlobalErrorObserver {
public:
explicit GlobalErrorWaiter(Profile* profile)
: service_(GlobalErrorServiceFactory::GetForProfile(profile)) {
scoped_observation_.Observe(service_.get());
}
GlobalErrorWaiter(const GlobalErrorWaiter&) = delete;
GlobalErrorWaiter& operator=(const GlobalErrorWaiter&) = delete;
~GlobalErrorWaiter() override = default;
// GlobalErrorObserver
void OnGlobalErrorsChanged() override {
if (service_->GetFirstGlobalErrorWithBubbleView()) {
run_loop_.Quit();
}
}
void Wait() { run_loop_.Run(); }
private:
base::RunLoop run_loop_;
raw_ptr<GlobalErrorService> service_;
base::ScopedObservation<GlobalErrorService, GlobalErrorObserver>
scoped_observation_{this};
};
} // namespace
class GlobalErrorBubbleTest : public DialogBrowserTest {
public:
GlobalErrorBubbleTest() {
extensions::ExtensionPrefs::SetRunAlertsInFirstRunForTest();
}
GlobalErrorBubbleTest(const GlobalErrorBubbleTest&) = delete;
GlobalErrorBubbleTest& operator=(const GlobalErrorBubbleTest&) = delete;
// DialogBrowserTest:
void ShowUi(const std::string& name) override;
};
void GlobalErrorBubbleTest::ShowUi(const std::string& name) {
Profile* profile = browser()->profile();
extensions::ExtensionRegistry* extension_registry =
extensions::ExtensionRegistry::Get(profile);
extensions::ExtensionBuilder builder("Browser Action");
builder.SetAction(extensions::ActionInfo::Type::kBrowser);
builder.SetLocation(extensions::mojom::ManifestLocation::kInternal);
scoped_refptr<const extensions::Extension> test_extension = builder.Build();
extensions::ExtensionRegistrar::Get(profile)->AddExtension(test_extension);
if (name == "ExtensionDisabledGlobalError") {
GlobalErrorWaiter waiter(profile);
extensions::AddExtensionDisabledError(profile, test_extension.get(), false);
waiter.Wait();
ShowPendingError(browser());
} else if (name == "ExtensionWithLongNameDisabledGlobalError") {
const std::string long_name =
"This extension name should be longer than our truncation threshold "
"to test that the bubble can handle long names";
scoped_refptr<const extensions::Extension> long_name_extension =
extensions::ExtensionBuilder(long_name).Build();
extensions::ExtensionRegistrar::Get(profile)->AddExtension(
long_name_extension);
GlobalErrorWaiter waiter(profile);
extensions::AddExtensionDisabledError(profile, long_name_extension.get(),
/*is_remote_install=*/false);
waiter.Wait();
ShowPendingError(browser());
} else if (name == "ExtensionDisabledGlobalErrorRemote") {
GlobalErrorWaiter waiter(profile);
extensions::AddExtensionDisabledError(profile, test_extension.get(), true);
waiter.Wait();
ShowPendingError(browser());
} else if (name == "ExtensionGlobalError") {
extensions::TestBlocklist test_blocklist(
extensions::Blocklist::Get(profile));
extension_registry->AddBlocklisted(test_extension);
// Only BLOCKLISTED_MALWARE results in a bubble displaying to the user.
// Other types are greylisted, not blocklisted.
test_blocklist.SetBlocklistState(test_extension->id(),
extensions::BLOCKLISTED_MALWARE, true);
// Ensure ExtensionService::ManageBlocklist() runs, which shows the dialog.
// (This flow doesn't use OnGlobalErrorsChanged.) This is asynchronous, and
// using TestBlocklist ensures the tasks run without delay, but some tasks
// run on the IO thread, so post a task there to ensure it was flushed. The
// test also needs to invoke OnBlocklistUpdated() directly. Usually this
// happens via a callback from the SafeBrowsing DB, but TestBlocklist
// replaced the SafeBrowsing DB with a fake one, so the notification source
// is different.
extensions::ExtensionService* extension_service =
extensions::ExtensionSystem::Get(profile)->extension_service();
static_cast<extensions::Blocklist::Observer*>(extension_service)
->OnBlocklistUpdated();
base::RunLoop().RunUntilIdle();
base::RunLoop flush_io;
content::GetIOThreadTaskRunner({})->PostTaskAndReply(
FROM_HERE, base::DoNothing(), flush_io.QuitClosure());
flush_io.Run();
// Oh no! This relies on RunUntilIdle() to show the bubble. The bubble is
// not persistent, so events from the OS can also cause the bubble to close
// in the following call.
base::RunLoop().RunUntilIdle();
} else if (name == "ExternalInstallBubbleAlert") {
// To trigger a bubble alert (rather than a menu alert), the extension must
// come from the webstore, which needs the update to come from a signed crx.
const char kExtensionWithUpdateUrl[] = "akjooamlhcgeopfifcmlggaebeocgokj";
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
base::FilePath crx_path = PackCRXInTempDir(
&temp_dir, "update_from_webstore", "update_from_webstore.pem");
GlobalErrorWaiter waiter(profile);
extensions::ExternalProviderManager* external_provider_manager =
extensions::ExternalProviderManager::Get(profile);
auto provider = std::make_unique<extensions::MockExternalProvider>(
external_provider_manager,
extensions::mojom::ManifestLocation::kExternalPref);
extensions::MockExternalProvider* provider_ptr = provider.get();
external_provider_manager->AddProviderForTesting(std::move(provider));
provider_ptr->UpdateOrAddExtension(kExtensionWithUpdateUrl, "1.0.0.0",
crx_path);
external_provider_manager->CheckForExternalUpdates();
// ExternalInstallError::OnDialogReady() adds the error and shows the dialog
// immediately.
waiter.Wait();
} else if (name == "RecoveryInstallGlobalError") {
GlobalErrorWaiter waiter(profile);
g_browser_process->local_state()->SetBoolean(
prefs::kRecoveryComponentNeedsElevation, true);
waiter.Wait();
ShowPendingError(browser());
} else {
ADD_FAILURE();
}
}
IN_PROC_BROWSER_TEST_F(GlobalErrorBubbleTest,
InvokeUi_ExtensionDisabledGlobalError) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GlobalErrorBubbleTest,
InvokeUi_ExtensionWithLongNameDisabledGlobalError) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GlobalErrorBubbleTest,
InvokeUi_ExtensionDisabledGlobalErrorRemote) {
ShowAndVerifyUi();
}
// This shows a non-persistent dialog during a RunLoop::RunUntilIdle(), so it's
// not possible to guarantee that events to dismiss the dialog are not processed
// as well. Disable by default to prevent flakiness in browser_tests.
IN_PROC_BROWSER_TEST_F(GlobalErrorBubbleTest,
DISABLED_InvokeUi_ExtensionGlobalError) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GlobalErrorBubbleTest,
InvokeUi_ExternalInstallBubbleAlert) {
// TODO(https://crbug.com/40804030): Remove this when updated to use MV3.
extensions::ScopedTestMV2Enabler mv2_enabler;
extensions::SandboxedUnpacker::ScopedVerifierFormatOverrideForTest
verifier_format_override(crx_file::VerifierFormat::CRX3);
extensions::FeatureSwitch::ScopedOverride prompt(
extensions::FeatureSwitch::prompt_for_external_extensions(), true);
ShowAndVerifyUi();
}
// RecoveryInstallGlobalError only exists on Windows and Mac.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
IN_PROC_BROWSER_TEST_F(GlobalErrorBubbleTest,
InvokeUi_RecoveryInstallGlobalError) {
ShowAndVerifyUi();
}
#endif
|