blob: 1b60d34cc9dcd6217d01f624882c89767a32ec34 [file] [log] [blame]
rdevlin.cronin11f01682016-11-28 18:15:521// Copyright 2016 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/extensions/chrome_test_extension_loader.h"
6
7#include <memory>
8
9#include "base/files/file_util.h"
10#include "base/run_loop.h"
Devlin Cronin7c1e9162018-12-21 21:39:1211#include "base/scoped_observer.h"
rdevlin.cronin62018a12017-06-22 17:34:0612#include "base/threading/thread_restrictions.h"
rdevlin.cronin11f01682016-11-28 18:15:5213#include "chrome/browser/extensions/chrome_extension_test_notification_observer.h"
14#include "chrome/browser/extensions/crx_installer.h"
rdevlin.cronin11f01682016-11-28 18:15:5215#include "chrome/browser/extensions/extension_service.h"
16#include "chrome/browser/extensions/extension_util.h"
17#include "chrome/browser/extensions/unpacked_installer.h"
Devlin Cronin7c1e9162018-12-21 21:39:1218#include "chrome/browser/profiles/profile.h"
rdevlin.cronin11f01682016-11-28 18:15:5219#include "content/public/browser/notification_details.h"
20#include "content/public/browser/notification_source.h"
21#include "content/public/test/test_utils.h"
Devlin Cronin420995d2018-01-22 20:54:0622#include "extensions/browser/extension_creator.h"
rdevlin.cronin11f01682016-11-28 18:15:5223#include "extensions/browser/extension_registry.h"
24#include "extensions/browser/extension_system.h"
karandeepb810e33402017-04-05 23:41:2225#include "extensions/browser/extension_util.h"
rdevlin.cronin11f01682016-11-28 18:15:5226#include "extensions/browser/notification_types.h"
Devlin Cronin7c1e9162018-12-21 21:39:1227#include "extensions/browser/shared_user_script_master.h"
rdevlin.cronin11f01682016-11-28 18:15:5228#include "extensions/browser/test_extension_registry_observer.h"
Devlin Cronin7c1e9162018-12-21 21:39:1229#include "extensions/browser/user_script_loader.h"
30#include "extensions/common/manifest_handlers/content_scripts_handler.h"
Karan Bhatiab794ad52017-08-22 22:13:1431#include "extensions/test/extension_test_notification_observer.h"
rdevlin.cronin11f01682016-11-28 18:15:5232#include "testing/gtest/include/gtest/gtest.h"
33
34namespace extensions {
35
Devlin Cronin7c1e9162018-12-21 21:39:1236namespace {
37
38// A single-use class to wait for content scripts to be loaded.
39class ContentScriptLoadWaiter : public UserScriptLoader::Observer {
40 public:
41 ContentScriptLoadWaiter(UserScriptLoader* loader, const HostID& host_id)
42 : host_id_(host_id), loader_(loader), scoped_observer_(this) {
43 scoped_observer_.Add(loader_);
44 }
45 ~ContentScriptLoadWaiter() = default;
46
47 void Wait() { run_loop_.Run(); }
48
49 private:
50 // UserScriptLoader::Observer:
51 void OnScriptsLoaded(UserScriptLoader* loader) override {
52 if (loader_->HasLoadedScripts(host_id_)) {
53 // Quit when idle in order to allow other observers to run.
54 run_loop_.QuitWhenIdle();
55 }
56 }
57 void OnUserScriptLoaderDestroyed(UserScriptLoader* loader) override {}
58
59 const HostID host_id_;
60 UserScriptLoader* const loader_;
61 base::RunLoop run_loop_;
62 ScopedObserver<UserScriptLoader, UserScriptLoader::Observer> scoped_observer_;
63
64 DISALLOW_COPY_AND_ASSIGN(ContentScriptLoadWaiter);
65};
66
67} // namespace
68
rdevlin.cronin11f01682016-11-28 18:15:5269ChromeTestExtensionLoader::ChromeTestExtensionLoader(
70 content::BrowserContext* browser_context)
71 : browser_context_(browser_context),
72 extension_system_(ExtensionSystem::Get(browser_context)),
73 extension_service_(extension_system_->extension_service()),
74 extension_registry_(ExtensionRegistry::Get(browser_context)) {}
75
rdevlin.cronin62018a12017-06-22 17:34:0676ChromeTestExtensionLoader::~ChromeTestExtensionLoader() {
77 // If there was a temporary directory created for a CRX, we need to clean it
78 // up before the member is destroyed so we can explicitly allow IO.
Francois Doraye6fb2d02017-10-18 21:29:1379 base::ScopedAllowBlockingForTesting allow_blocking;
rdevlin.cronin62018a12017-06-22 17:34:0680 if (temp_dir_.IsValid())
81 EXPECT_TRUE(temp_dir_.Delete());
82}
rdevlin.cronin11f01682016-11-28 18:15:5283
84scoped_refptr<const Extension> ChromeTestExtensionLoader::LoadExtension(
85 const base::FilePath& path) {
86 scoped_refptr<const Extension> extension;
87 if (path.MatchesExtension(FILE_PATH_LITERAL(".crx"))) {
88 extension = LoadCrx(path);
89 } else if (pack_extension_) {
90 base::FilePath crx_path = PackExtension(path);
91 if (crx_path.empty())
92 return nullptr;
93 extension = LoadCrx(crx_path);
94 } else {
95 extension = LoadUnpacked(path);
96 }
97
98 if (should_fail_ && extension)
Karan Bhatiab794ad52017-08-22 22:13:1499 ADD_FAILURE() << "Expected extension load failure, but succeeded";
rdevlin.cronin11f01682016-11-28 18:15:52100 else if (!should_fail_ && !extension)
Karan Bhatiab794ad52017-08-22 22:13:14101 ADD_FAILURE() << "Failed to load extension";
rdevlin.cronin11f01682016-11-28 18:15:52102
103 if (!extension)
104 return nullptr;
105
106 extension_id_ = extension->id();
Devlin Cronin7c1e9162018-12-21 21:39:12107
rdevlin.cronin55f2b6232016-12-05 21:25:53108 // Trying to reload a shared module (as we do when adjusting extension
109 // permissions) causes ExtensionService to crash. Only adjust permissions for
110 // non-shared modules.
111 // TODO(devlin): That's not good; we shouldn't be crashing.
112 if (!SharedModuleInfo::IsSharedModule(extension.get())) {
Karan Bhatiaab07704b2017-08-22 22:22:53113 CheckPermissions(extension.get());
114 // Make |extension| null since it may have been reloaded invalidating
115 // pointers to it.
rdevlin.cronin55f2b6232016-12-05 21:25:53116 extension = nullptr;
rdevlin.cronin55f2b6232016-12-05 21:25:53117 }
rdevlin.cronin11f01682016-11-28 18:15:52118
119 if (!install_param_.empty()) {
120 ExtensionPrefs::Get(browser_context_)
121 ->SetInstallParam(extension_id_, install_param_);
122 // Reload the extension so listeners of the loaded notification have access
123 // to the install param.
124 TestExtensionRegistryObserver registry_observer(extension_registry_,
125 extension_id_);
126 extension_service_->ReloadExtension(extension_id_);
127 registry_observer.WaitForExtensionLoaded();
128 }
129
130 extension = extension_registry_->enabled_extensions().GetByID(extension_id_);
131 if (!extension)
132 return nullptr;
Karan Bhatiabd4983c2017-08-22 20:02:40133 if (!CheckInstallWarnings(*extension))
rdevlin.cronin11f01682016-11-28 18:15:52134 return nullptr;
135
136 base::RunLoop().RunUntilIdle();
Devlin Cronin7c1e9162018-12-21 21:39:12137 if (!WaitForExtensionReady(*extension)) {
rdevlin.cronin11f01682016-11-28 18:15:52138 ADD_FAILURE() << "Failed to wait for extension ready";
139 return nullptr;
140 }
141 return extension;
142}
143
Devlin Cronin7c1e9162018-12-21 21:39:12144bool ChromeTestExtensionLoader::WaitForExtensionReady(
145 const Extension& extension) {
146 SharedUserScriptMaster* user_script_master =
147 ExtensionSystem::Get(browser_context_)->shared_user_script_master();
148 // Note: |user_script_master| can be null in tests.
149 if (user_script_master &&
150 !ContentScriptsInfo::GetContentScripts(&extension).empty()) {
151 UserScriptLoader* user_script_loader = user_script_master->script_loader();
152 HostID host_id(HostID::EXTENSIONS, extension_id_);
153 if (!user_script_loader->HasLoadedScripts(host_id))
154 ContentScriptLoadWaiter(user_script_loader, host_id).Wait();
155 }
156
rdevlin.cronin11f01682016-11-28 18:15:52157 return ChromeExtensionTestNotificationObserver(browser_context_)
158 .WaitForExtensionViewsToLoad();
159}
160
161base::FilePath ChromeTestExtensionLoader::PackExtension(
162 const base::FilePath& unpacked_path) {
Francois Doraye6fb2d02017-10-18 21:29:13163 base::ScopedAllowBlockingForTesting allow_blocking;
rdevlin.cronin11f01682016-11-28 18:15:52164 if (!base::PathExists(unpacked_path)) {
165 ADD_FAILURE() << "Unpacked path does not exist: " << unpacked_path.value();
166 return base::FilePath();
167 }
168
rdevlin.cronin2f1ed4c2017-06-13 16:22:13169 if (!temp_dir_.CreateUniqueTempDir()) {
170 ADD_FAILURE() << "Could not create unique temp dir.";
171 return base::FilePath();
172 }
rdevlin.cronin11f01682016-11-28 18:15:52173 base::FilePath crx_path = temp_dir_.GetPath().AppendASCII("temp.crx");
174 if (base::PathExists(crx_path)) {
175 ADD_FAILURE() << "Crx path exists: " << crx_path.value()
176 << ", are you trying to reuse the same ChromeTestExtensionLoader?";
177 return base::FilePath();
178 }
179 base::FilePath fallback_pem_path =
180 temp_dir_.GetPath().AppendASCII("temp.pem");
181 if (base::PathExists(fallback_pem_path)) {
182 ADD_FAILURE() << "PEM path exists: " << fallback_pem_path.value()
183 << ", are you trying to reuse the same ChromeTestExtensionLoader?";
184 return base::FilePath();
185 }
186
rdevlin.cronin2f1ed4c2017-06-13 16:22:13187 base::FilePath empty_path;
188 base::FilePath* pem_path_to_use = &empty_path;
rdevlin.cronin11f01682016-11-28 18:15:52189 if (!pem_path_.empty()) {
190 pem_path_to_use = &pem_path_;
191 if (!base::PathExists(pem_path_)) {
192 ADD_FAILURE() << "Provided PEM path does not exist: "
193 << pem_path_.value();
194 return base::FilePath();
195 }
196 }
197
198 ExtensionCreator creator;
199 if (!creator.Run(unpacked_path, crx_path, *pem_path_to_use, fallback_pem_path,
200 ExtensionCreator::kOverwriteCRX)) {
201 ADD_FAILURE() << "ExtensionCreator::Run() failed: "
202 << creator.error_message();
203 return base::FilePath();
204 }
205
206 CHECK(base::PathExists(crx_path));
207
208 return crx_path;
209}
210
211scoped_refptr<const Extension> ChromeTestExtensionLoader::LoadCrx(
212 const base::FilePath& file_path) {
213 if (!file_path.MatchesExtension(FILE_PATH_LITERAL(".crx"))) {
214 ADD_FAILURE() << "Must pass a crx path to LoadCrx()";
215 return nullptr;
216 }
217
218 scoped_refptr<const Extension> extension;
219 {
220 // TODO(devlin): Allow consumers to specify the install ui type.
221 std::unique_ptr<ExtensionInstallPrompt> install_ui;
222 scoped_refptr<CrxInstaller> installer =
223 CrxInstaller::Create(extension_service_, std::move(install_ui));
224 installer->set_expected_id(expected_id_);
225 installer->set_creation_flags(creation_flags_);
226 installer->set_install_source(location_);
227 installer->set_install_immediately(install_immediately_);
228 installer->set_allow_silent_install(grant_permissions_);
229 if (!installer->is_gallery_install()) {
230 installer->set_off_store_install_allow_reason(
231 CrxInstaller::OffStoreInstallAllowedInTest);
232 }
233
234 content::WindowedNotificationObserver install_observer(
235 NOTIFICATION_CRX_INSTALLER_DONE,
236 content::Source<CrxInstaller>(installer.get()));
237 installer->InstallCrx(file_path);
238 install_observer.Wait();
239
240 extension =
241 content::Details<const Extension>(install_observer.details()).ptr();
242 }
243
244 return extension;
245}
246
Karan Bhatiaab07704b2017-08-22 22:22:53247void ChromeTestExtensionLoader::CheckPermissions(const Extension* extension) {
248 std::string id = extension->id();
249
250 // If the client explicitly set |allow_file_access_|, use that value. Else
251 // use the default as per the extensions manifest location.
252 if (!allow_file_access_) {
253 allow_file_access_ =
254 Manifest::ShouldAlwaysAllowFileAccess(extension->location());
255 }
256
257 // |extension| may be reloaded subsequently, invalidating the pointer. Hence
258 // make it null.
259 extension = nullptr;
rdevlin.cronin11f01682016-11-28 18:15:52260
261 // Toggling incognito or file access will reload the extension, so wait for
262 // the reload.
Karan Bhatiaab07704b2017-08-22 22:22:53263 if (*allow_file_access_ != util::AllowFileAccess(id, browser_context_)) {
rdevlin.cronin11f01682016-11-28 18:15:52264 TestExtensionRegistryObserver registry_observer(extension_registry_, id);
Karan Bhatiaab07704b2017-08-22 22:22:53265 util::SetAllowFileAccess(id, browser_context_, *allow_file_access_);
rdevlin.cronin11f01682016-11-28 18:15:52266 registry_observer.WaitForExtensionLoaded();
267 }
268
269 if (allow_incognito_access_ !=
270 util::IsIncognitoEnabled(id, browser_context_)) {
271 TestExtensionRegistryObserver registry_observer(extension_registry_, id);
272 util::SetIsIncognitoEnabled(id, browser_context_, true);
273 registry_observer.WaitForExtensionLoaded();
274 }
275}
276
277scoped_refptr<const Extension> ChromeTestExtensionLoader::LoadUnpacked(
278 const base::FilePath& file_path) {
279 const Extension* extension = nullptr;
280 TestExtensionRegistryObserver registry_observer(extension_registry_);
281 scoped_refptr<UnpackedInstaller> installer =
282 UnpackedInstaller::Create(extension_service_);
rdevlin.cronin11f01682016-11-28 18:15:52283 installer->set_require_modern_manifest_version(
284 require_modern_manifest_version_);
285 installer->Load(file_path);
Karan Bhatiab794ad52017-08-22 22:13:14286 if (!should_fail_) {
287 extension = registry_observer.WaitForExtensionLoaded();
288 } else {
289 EXPECT_TRUE(ExtensionTestNotificationObserver(browser_context_)
290 .WaitForExtensionLoadError())
291 << "No load error observed";
292 }
rdevlin.cronin11f01682016-11-28 18:15:52293
294 return extension;
295}
296
Karan Bhatiabd4983c2017-08-22 20:02:40297bool ChromeTestExtensionLoader::CheckInstallWarnings(
298 const Extension& extension) {
rdevlin.cronin11f01682016-11-28 18:15:52299 if (ignore_manifest_warnings_)
300 return true;
301 const std::vector<InstallWarning>& install_warnings =
302 extension.install_warnings();
303 if (install_warnings.empty())
304 return true;
305
306 std::string install_warnings_message = "Unexpected warnings for extension:\n";
307 for (const InstallWarning& warning : install_warnings)
308 install_warnings_message += " " + warning.message + "\n";
309
310 ADD_FAILURE() << install_warnings_message;
311 return false;
312}
313
314} // namespace extensions