blob: ca304b2db1284b193504703491f3e1357030249c [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
Devlin Cronin7c1e9162018-12-21 21:39:12136 if (!WaitForExtensionReady(*extension)) {
rdevlin.cronin11f01682016-11-28 18:15:52137 ADD_FAILURE() << "Failed to wait for extension ready";
138 return nullptr;
139 }
140 return extension;
141}
142
Devlin Cronin7c1e9162018-12-21 21:39:12143bool ChromeTestExtensionLoader::WaitForExtensionReady(
144 const Extension& extension) {
145 SharedUserScriptMaster* user_script_master =
146 ExtensionSystem::Get(browser_context_)->shared_user_script_master();
147 // Note: |user_script_master| can be null in tests.
148 if (user_script_master &&
149 !ContentScriptsInfo::GetContentScripts(&extension).empty()) {
150 UserScriptLoader* user_script_loader = user_script_master->script_loader();
151 HostID host_id(HostID::EXTENSIONS, extension_id_);
152 if (!user_script_loader->HasLoadedScripts(host_id))
153 ContentScriptLoadWaiter(user_script_loader, host_id).Wait();
154 }
155
rdevlin.cronin11f01682016-11-28 18:15:52156 return ChromeExtensionTestNotificationObserver(browser_context_)
157 .WaitForExtensionViewsToLoad();
158}
159
160base::FilePath ChromeTestExtensionLoader::PackExtension(
161 const base::FilePath& unpacked_path) {
Francois Doraye6fb2d02017-10-18 21:29:13162 base::ScopedAllowBlockingForTesting allow_blocking;
rdevlin.cronin11f01682016-11-28 18:15:52163 if (!base::PathExists(unpacked_path)) {
164 ADD_FAILURE() << "Unpacked path does not exist: " << unpacked_path.value();
165 return base::FilePath();
166 }
167
rdevlin.cronin2f1ed4c2017-06-13 16:22:13168 if (!temp_dir_.CreateUniqueTempDir()) {
169 ADD_FAILURE() << "Could not create unique temp dir.";
170 return base::FilePath();
171 }
rdevlin.cronin11f01682016-11-28 18:15:52172 base::FilePath crx_path = temp_dir_.GetPath().AppendASCII("temp.crx");
173 if (base::PathExists(crx_path)) {
174 ADD_FAILURE() << "Crx path exists: " << crx_path.value()
175 << ", are you trying to reuse the same ChromeTestExtensionLoader?";
176 return base::FilePath();
177 }
178 base::FilePath fallback_pem_path =
179 temp_dir_.GetPath().AppendASCII("temp.pem");
180 if (base::PathExists(fallback_pem_path)) {
181 ADD_FAILURE() << "PEM path exists: " << fallback_pem_path.value()
182 << ", are you trying to reuse the same ChromeTestExtensionLoader?";
183 return base::FilePath();
184 }
185
rdevlin.cronin2f1ed4c2017-06-13 16:22:13186 base::FilePath empty_path;
187 base::FilePath* pem_path_to_use = &empty_path;
rdevlin.cronin11f01682016-11-28 18:15:52188 if (!pem_path_.empty()) {
189 pem_path_to_use = &pem_path_;
190 if (!base::PathExists(pem_path_)) {
191 ADD_FAILURE() << "Provided PEM path does not exist: "
192 << pem_path_.value();
193 return base::FilePath();
194 }
195 }
196
197 ExtensionCreator creator;
198 if (!creator.Run(unpacked_path, crx_path, *pem_path_to_use, fallback_pem_path,
199 ExtensionCreator::kOverwriteCRX)) {
200 ADD_FAILURE() << "ExtensionCreator::Run() failed: "
201 << creator.error_message();
202 return base::FilePath();
203 }
204
205 CHECK(base::PathExists(crx_path));
206
207 return crx_path;
208}
209
210scoped_refptr<const Extension> ChromeTestExtensionLoader::LoadCrx(
211 const base::FilePath& file_path) {
212 if (!file_path.MatchesExtension(FILE_PATH_LITERAL(".crx"))) {
213 ADD_FAILURE() << "Must pass a crx path to LoadCrx()";
214 return nullptr;
215 }
216
217 scoped_refptr<const Extension> extension;
218 {
219 // TODO(devlin): Allow consumers to specify the install ui type.
220 std::unique_ptr<ExtensionInstallPrompt> install_ui;
221 scoped_refptr<CrxInstaller> installer =
222 CrxInstaller::Create(extension_service_, std::move(install_ui));
223 installer->set_expected_id(expected_id_);
224 installer->set_creation_flags(creation_flags_);
225 installer->set_install_source(location_);
226 installer->set_install_immediately(install_immediately_);
227 installer->set_allow_silent_install(grant_permissions_);
228 if (!installer->is_gallery_install()) {
229 installer->set_off_store_install_allow_reason(
230 CrxInstaller::OffStoreInstallAllowedInTest);
231 }
232
233 content::WindowedNotificationObserver install_observer(
234 NOTIFICATION_CRX_INSTALLER_DONE,
235 content::Source<CrxInstaller>(installer.get()));
236 installer->InstallCrx(file_path);
237 install_observer.Wait();
238
239 extension =
240 content::Details<const Extension>(install_observer.details()).ptr();
241 }
242
243 return extension;
244}
245
Karan Bhatiaab07704b2017-08-22 22:22:53246void ChromeTestExtensionLoader::CheckPermissions(const Extension* extension) {
247 std::string id = extension->id();
248
249 // If the client explicitly set |allow_file_access_|, use that value. Else
250 // use the default as per the extensions manifest location.
251 if (!allow_file_access_) {
252 allow_file_access_ =
253 Manifest::ShouldAlwaysAllowFileAccess(extension->location());
254 }
255
256 // |extension| may be reloaded subsequently, invalidating the pointer. Hence
257 // make it null.
258 extension = nullptr;
rdevlin.cronin11f01682016-11-28 18:15:52259
260 // Toggling incognito or file access will reload the extension, so wait for
261 // the reload.
Karan Bhatiaab07704b2017-08-22 22:22:53262 if (*allow_file_access_ != util::AllowFileAccess(id, browser_context_)) {
rdevlin.cronin11f01682016-11-28 18:15:52263 TestExtensionRegistryObserver registry_observer(extension_registry_, id);
Karan Bhatiaab07704b2017-08-22 22:22:53264 util::SetAllowFileAccess(id, browser_context_, *allow_file_access_);
rdevlin.cronin11f01682016-11-28 18:15:52265 registry_observer.WaitForExtensionLoaded();
266 }
267
268 if (allow_incognito_access_ !=
269 util::IsIncognitoEnabled(id, browser_context_)) {
270 TestExtensionRegistryObserver registry_observer(extension_registry_, id);
271 util::SetIsIncognitoEnabled(id, browser_context_, true);
272 registry_observer.WaitForExtensionLoaded();
273 }
274}
275
276scoped_refptr<const Extension> ChromeTestExtensionLoader::LoadUnpacked(
277 const base::FilePath& file_path) {
278 const Extension* extension = nullptr;
279 TestExtensionRegistryObserver registry_observer(extension_registry_);
280 scoped_refptr<UnpackedInstaller> installer =
281 UnpackedInstaller::Create(extension_service_);
rdevlin.cronin11f01682016-11-28 18:15:52282 installer->set_require_modern_manifest_version(
283 require_modern_manifest_version_);
284 installer->Load(file_path);
Karan Bhatiab794ad52017-08-22 22:13:14285 if (!should_fail_) {
286 extension = registry_observer.WaitForExtensionLoaded();
287 } else {
288 EXPECT_TRUE(ExtensionTestNotificationObserver(browser_context_)
289 .WaitForExtensionLoadError())
290 << "No load error observed";
291 }
rdevlin.cronin11f01682016-11-28 18:15:52292
293 return extension;
294}
295
Karan Bhatiabd4983c2017-08-22 20:02:40296bool ChromeTestExtensionLoader::CheckInstallWarnings(
297 const Extension& extension) {
rdevlin.cronin11f01682016-11-28 18:15:52298 if (ignore_manifest_warnings_)
299 return true;
300 const std::vector<InstallWarning>& install_warnings =
301 extension.install_warnings();
302 if (install_warnings.empty())
303 return true;
304
305 std::string install_warnings_message = "Unexpected warnings for extension:\n";
306 for (const InstallWarning& warning : install_warnings)
307 install_warnings_message += " " + warning.message + "\n";
308
309 ADD_FAILURE() << install_warnings_message;
310 return false;
311}
312
313} // namespace extensions