1 // Copyright (c) 2011 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/extension_browsertest.h"
6
7 #include <vector>
8
9 #include "base/command_line.h"
10 #include "base/file_path.h"
11 #include "base/file_util.h"
12 #include "base/path_service.h"
13 #include "base/string_number_conversions.h"
14 #include "base/memory/scoped_temp_dir.h"
15 #include "chrome/browser/extensions/crx_installer.h"
16 #include "chrome/browser/extensions/extension_creator.h"
17 #include "chrome/browser/extensions/extension_error_reporter.h"
18 #include "chrome/browser/extensions/extension_host.h"
19 #include "chrome/browser/extensions/extension_install_ui.h"
20 #include "chrome/browser/extensions/extension_service.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/ui/browser.h"
23 #include "chrome/browser/ui/browser_window.h"
24 #include "chrome/browser/ui/omnibox/location_bar.h"
25 #include "chrome/common/chrome_paths.h"
26 #include "chrome/common/chrome_switches.h"
27 #include "chrome/test/ui_test_utils.h"
28 #include "content/common/notification_registrar.h"
29 #include "content/common/notification_service.h"
30 #include "content/common/notification_type.h"
31
ExtensionBrowserTest()32 ExtensionBrowserTest::ExtensionBrowserTest()
33 : loaded_(false),
34 installed_(false),
35 extension_installs_observed_(0),
36 target_page_action_count_(-1),
37 target_visible_page_action_count_(-1) {
38 EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
39 }
40
SetUpCommandLine(CommandLine * command_line)41 void ExtensionBrowserTest::SetUpCommandLine(CommandLine* command_line) {
42 // This enables DOM automation for tab contentses.
43 EnableDOMAutomation();
44
45 // This enables it for extension hosts.
46 ExtensionHost::EnableDOMAutomation();
47
48 PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_);
49 test_data_dir_ = test_data_dir_.AppendASCII("extensions");
50
51 #if defined(OS_CHROMEOS)
52 // This makes sure that we create the Default profile first, with no
53 // ExtensionService and then the real profile with one, as we do when
54 // running on chromeos.
55 command_line->AppendSwitchASCII(switches::kLoginUser,
56 "TestUser@gmail.com");
57 command_line->AppendSwitchASCII(switches::kLoginProfile, "user");
58 command_line->AppendSwitch(switches::kNoFirstRun);
59 #endif
60 }
61
LoadExtensionImpl(const FilePath & path,bool incognito_enabled,bool fileaccess_enabled)62 const Extension* ExtensionBrowserTest::LoadExtensionImpl(
63 const FilePath& path, bool incognito_enabled, bool fileaccess_enabled) {
64 ExtensionService* service = browser()->profile()->GetExtensionService();
65 {
66 NotificationRegistrar registrar;
67 registrar.Add(this, NotificationType::EXTENSION_LOADED,
68 NotificationService::AllSources());
69 service->LoadExtension(path);
70 ui_test_utils::RunMessageLoop();
71 }
72
73 // Find the extension by iterating backwards since it is likely last.
74 FilePath extension_path = path;
75 file_util::AbsolutePath(&extension_path);
76 const Extension* extension = NULL;
77 for (ExtensionList::const_reverse_iterator iter =
78 service->extensions()->rbegin();
79 iter != service->extensions()->rend(); ++iter) {
80 if ((*iter)->path() == extension_path) {
81 extension = *iter;
82 break;
83 }
84 }
85 if (!extension)
86 return NULL;
87
88 // The call to OnExtensionInstalled ensures the other extension prefs
89 // are set up with the defaults.
90 service->extension_prefs()->OnExtensionInstalled(
91 extension, Extension::ENABLED, false);
92 service->SetIsIncognitoEnabled(extension->id(), incognito_enabled);
93 service->SetAllowFileAccess(extension, fileaccess_enabled);
94
95 if (!WaitForExtensionHostsToLoad())
96 return NULL;
97
98 return extension;
99 }
100
LoadExtension(const FilePath & path)101 const Extension* ExtensionBrowserTest::LoadExtension(const FilePath& path) {
102 return LoadExtensionImpl(path, false, true);
103 }
104
LoadExtensionIncognito(const FilePath & path)105 const Extension* ExtensionBrowserTest::LoadExtensionIncognito(
106 const FilePath& path) {
107 return LoadExtensionImpl(path, true, true);
108 }
109
LoadExtensionNoFileAccess(const FilePath & path)110 const Extension* ExtensionBrowserTest::LoadExtensionNoFileAccess(
111 const FilePath& path) {
112 return LoadExtensionImpl(path, false, false);
113 }
114
LoadExtensionIncognitoNoFileAccess(const FilePath & path)115 const Extension* ExtensionBrowserTest::LoadExtensionIncognitoNoFileAccess(
116 const FilePath& path) {
117 return LoadExtensionImpl(path, true, false);
118 }
119
LoadExtensionAsComponent(const FilePath & path)120 bool ExtensionBrowserTest::LoadExtensionAsComponent(const FilePath& path) {
121 ExtensionService* service = browser()->profile()->GetExtensionService();
122
123 std::string manifest;
124 if (!file_util::ReadFileToString(path.Append(Extension::kManifestFilename),
125 &manifest))
126 return false;
127
128 service->LoadComponentExtension(
129 ExtensionService::ComponentExtensionInfo(manifest, path));
130
131 return true;
132 }
133
PackExtension(const FilePath & dir_path)134 FilePath ExtensionBrowserTest::PackExtension(const FilePath& dir_path) {
135 FilePath crx_path = temp_dir_.path().AppendASCII("temp.crx");
136 if (!file_util::Delete(crx_path, false)) {
137 ADD_FAILURE() << "Failed to delete crx: " << crx_path.value();
138 return FilePath();
139 }
140
141 FilePath pem_path = crx_path.DirName().AppendASCII("temp.pem");
142 if (!file_util::Delete(pem_path, false)) {
143 ADD_FAILURE() << "Failed to delete pem: " << pem_path.value();
144 return FilePath();
145 }
146
147 if (!file_util::PathExists(dir_path)) {
148 ADD_FAILURE() << "Extension dir not found: " << dir_path.value();
149 return FilePath();
150 }
151
152 scoped_ptr<ExtensionCreator> creator(new ExtensionCreator());
153 if (!creator->Run(dir_path,
154 crx_path,
155 FilePath(), // no existing pem, use empty path
156 pem_path)) {
157 ADD_FAILURE() << "ExtensionCreator::Run() failed.";
158 return FilePath();
159 }
160
161 if (!file_util::PathExists(crx_path)) {
162 ADD_FAILURE() << crx_path.value() << " was not created.";
163 return FilePath();
164 }
165 return crx_path;
166 }
167
168 // This class is used to simulate an installation abort by the user.
169 class MockAbortExtensionInstallUI : public ExtensionInstallUI {
170 public:
MockAbortExtensionInstallUI()171 MockAbortExtensionInstallUI() : ExtensionInstallUI(NULL) {}
172
173 // Simulate a user abort on an extension installation.
ConfirmInstall(Delegate * delegate,const Extension * extension)174 virtual void ConfirmInstall(Delegate* delegate, const Extension* extension) {
175 delegate->InstallUIAbort();
176 MessageLoopForUI::current()->Quit();
177 }
178
OnInstallSuccess(const Extension * extension,SkBitmap * icon)179 virtual void OnInstallSuccess(const Extension* extension, SkBitmap* icon) {}
180
OnInstallFailure(const std::string & error)181 virtual void OnInstallFailure(const std::string& error) {}
182 };
183
184 class MockAutoConfirmExtensionInstallUI : public ExtensionInstallUI {
185 public:
MockAutoConfirmExtensionInstallUI(Profile * profile)186 explicit MockAutoConfirmExtensionInstallUI(Profile* profile) :
187 ExtensionInstallUI(profile) {}
188
189 // Proceed without confirmation prompt.
ConfirmInstall(Delegate * delegate,const Extension * extension)190 virtual void ConfirmInstall(Delegate* delegate, const Extension* extension) {
191 delegate->InstallUIProceed();
192 }
193 };
194
InstallOrUpdateExtension(const std::string & id,const FilePath & path,InstallUIType ui_type,int expected_change)195 bool ExtensionBrowserTest::InstallOrUpdateExtension(const std::string& id,
196 const FilePath& path,
197 InstallUIType ui_type,
198 int expected_change) {
199 return InstallOrUpdateExtension(id, path, ui_type, expected_change,
200 browser()->profile());
201 }
202
InstallOrUpdateExtension(const std::string & id,const FilePath & path,InstallUIType ui_type,int expected_change,Profile * profile)203 bool ExtensionBrowserTest::InstallOrUpdateExtension(const std::string& id,
204 const FilePath& path,
205 InstallUIType ui_type,
206 int expected_change,
207 Profile* profile) {
208 ExtensionService* service = profile->GetExtensionService();
209 service->set_show_extensions_prompts(false);
210 size_t num_before = service->extensions()->size();
211
212 {
213 NotificationRegistrar registrar;
214 registrar.Add(this, NotificationType::EXTENSION_LOADED,
215 NotificationService::AllSources());
216 registrar.Add(this, NotificationType::EXTENSION_UPDATE_DISABLED,
217 NotificationService::AllSources());
218 registrar.Add(this, NotificationType::EXTENSION_INSTALL_ERROR,
219 NotificationService::AllSources());
220
221 ExtensionInstallUI* install_ui = NULL;
222 if (ui_type == INSTALL_UI_TYPE_CANCEL)
223 install_ui = new MockAbortExtensionInstallUI();
224 else if (ui_type == INSTALL_UI_TYPE_NORMAL)
225 install_ui = new ExtensionInstallUI(profile);
226 else if (ui_type == INSTALL_UI_TYPE_AUTO_CONFIRM)
227 install_ui = new MockAutoConfirmExtensionInstallUI(profile);
228
229 // TODO(tessamac): Update callers to always pass an unpacked extension
230 // and then always pack the extension here.
231 FilePath crx_path = path;
232 if (crx_path.Extension() != FILE_PATH_LITERAL(".crx")) {
233 crx_path = PackExtension(path);
234 }
235 if (crx_path.empty())
236 return false;
237
238 scoped_refptr<CrxInstaller> installer(
239 new CrxInstaller(service, install_ui));
240 installer->set_expected_id(id);
241 installer->InstallCrx(crx_path);
242
243 ui_test_utils::RunMessageLoop();
244 }
245
246 size_t num_after = service->extensions()->size();
247 if (num_after != (num_before + expected_change)) {
248 VLOG(1) << "Num extensions before: " << base::IntToString(num_before)
249 << " num after: " << base::IntToString(num_after)
250 << " Installed extensions follow:";
251
252 for (size_t i = 0; i < service->extensions()->size(); ++i)
253 VLOG(1) << " " << (*service->extensions())[i]->id();
254
255 VLOG(1) << "Errors follow:";
256 const std::vector<std::string>* errors =
257 ExtensionErrorReporter::GetInstance()->GetErrors();
258 for (std::vector<std::string>::const_iterator iter = errors->begin();
259 iter != errors->end(); ++iter)
260 VLOG(1) << *iter;
261
262 return false;
263 }
264
265 return WaitForExtensionHostsToLoad();
266 }
267
ReloadExtension(const std::string & extension_id)268 void ExtensionBrowserTest::ReloadExtension(const std::string& extension_id) {
269 ExtensionService* service = browser()->profile()->GetExtensionService();
270 service->ReloadExtension(extension_id);
271 ui_test_utils::RegisterAndWait(this,
272 NotificationType::EXTENSION_LOADED,
273 NotificationService::AllSources());
274 }
275
UnloadExtension(const std::string & extension_id)276 void ExtensionBrowserTest::UnloadExtension(const std::string& extension_id) {
277 ExtensionService* service = browser()->profile()->GetExtensionService();
278 service->UnloadExtension(extension_id, UnloadedExtensionInfo::DISABLE);
279 }
280
UninstallExtension(const std::string & extension_id)281 void ExtensionBrowserTest::UninstallExtension(const std::string& extension_id) {
282 ExtensionService* service = browser()->profile()->GetExtensionService();
283 service->UninstallExtension(extension_id, false, NULL);
284 }
285
DisableExtension(const std::string & extension_id)286 void ExtensionBrowserTest::DisableExtension(const std::string& extension_id) {
287 ExtensionService* service = browser()->profile()->GetExtensionService();
288 service->DisableExtension(extension_id);
289 }
290
EnableExtension(const std::string & extension_id)291 void ExtensionBrowserTest::EnableExtension(const std::string& extension_id) {
292 ExtensionService* service = browser()->profile()->GetExtensionService();
293 service->EnableExtension(extension_id);
294 }
295
WaitForPageActionCountChangeTo(int count)296 bool ExtensionBrowserTest::WaitForPageActionCountChangeTo(int count) {
297 LocationBarTesting* location_bar =
298 browser()->window()->GetLocationBar()->GetLocationBarForTesting();
299 if (location_bar->PageActionCount() != count) {
300 target_page_action_count_ = count;
301 ui_test_utils::RegisterAndWait(this,
302 NotificationType::EXTENSION_PAGE_ACTION_COUNT_CHANGED,
303 NotificationService::AllSources());
304 }
305 return location_bar->PageActionCount() == count;
306 }
307
WaitForPageActionVisibilityChangeTo(int count)308 bool ExtensionBrowserTest::WaitForPageActionVisibilityChangeTo(int count) {
309 LocationBarTesting* location_bar =
310 browser()->window()->GetLocationBar()->GetLocationBarForTesting();
311 if (location_bar->PageActionVisibleCount() != count) {
312 target_visible_page_action_count_ = count;
313 ui_test_utils::RegisterAndWait(this,
314 NotificationType::EXTENSION_PAGE_ACTION_VISIBILITY_CHANGED,
315 NotificationService::AllSources());
316 }
317 return location_bar->PageActionVisibleCount() == count;
318 }
319
WaitForExtensionHostsToLoad()320 bool ExtensionBrowserTest::WaitForExtensionHostsToLoad() {
321 // Wait for all the extension hosts that exist to finish loading.
322 NotificationRegistrar registrar;
323 registrar.Add(this, NotificationType::EXTENSION_HOST_DID_STOP_LOADING,
324 NotificationService::AllSources());
325
326 ExtensionProcessManager* manager =
327 browser()->profile()->GetExtensionProcessManager();
328 for (ExtensionProcessManager::const_iterator iter = manager->begin();
329 iter != manager->end();) {
330 if ((*iter)->did_stop_loading()) {
331 ++iter;
332 } else {
333 ui_test_utils::RunMessageLoop();
334
335 // Test activity may have modified the set of extension processes during
336 // message processing, so re-start the iteration to catch added/removed
337 // processes.
338 iter = manager->begin();
339 }
340 }
341 return true;
342 }
343
WaitForExtensionInstall()344 bool ExtensionBrowserTest::WaitForExtensionInstall() {
345 int before = extension_installs_observed_;
346 ui_test_utils::RegisterAndWait(this, NotificationType::EXTENSION_INSTALLED,
347 NotificationService::AllSources());
348 return extension_installs_observed_ == (before + 1);
349 }
350
WaitForExtensionInstallError()351 bool ExtensionBrowserTest::WaitForExtensionInstallError() {
352 int before = extension_installs_observed_;
353 ui_test_utils::RegisterAndWait(this,
354 NotificationType::EXTENSION_INSTALL_ERROR,
355 NotificationService::AllSources());
356 return extension_installs_observed_ == before;
357 }
358
WaitForExtensionLoad()359 void ExtensionBrowserTest::WaitForExtensionLoad() {
360 ui_test_utils::RegisterAndWait(this, NotificationType::EXTENSION_LOADED,
361 NotificationService::AllSources());
362 WaitForExtensionHostsToLoad();
363 }
364
WaitForExtensionCrash(const std::string & extension_id)365 bool ExtensionBrowserTest::WaitForExtensionCrash(
366 const std::string& extension_id) {
367 ExtensionService* service = browser()->profile()->GetExtensionService();
368
369 if (!service->GetExtensionById(extension_id, true)) {
370 // The extension is already unloaded, presumably due to a crash.
371 return true;
372 }
373 ui_test_utils::RegisterAndWait(this,
374 NotificationType::EXTENSION_PROCESS_TERMINATED,
375 NotificationService::AllSources());
376 return (service->GetExtensionById(extension_id, true) == NULL);
377 }
378
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)379 void ExtensionBrowserTest::Observe(NotificationType type,
380 const NotificationSource& source,
381 const NotificationDetails& details) {
382 switch (type.value) {
383 case NotificationType::EXTENSION_LOADED:
384 last_loaded_extension_id_ = Details<const Extension>(details).ptr()->id();
385 VLOG(1) << "Got EXTENSION_LOADED notification.";
386 MessageLoopForUI::current()->Quit();
387 break;
388
389 case NotificationType::EXTENSION_UPDATE_DISABLED:
390 VLOG(1) << "Got EXTENSION_UPDATE_DISABLED notification.";
391 MessageLoopForUI::current()->Quit();
392 break;
393
394 case NotificationType::EXTENSION_HOST_DID_STOP_LOADING:
395 VLOG(1) << "Got EXTENSION_HOST_DID_STOP_LOADING notification.";
396 MessageLoopForUI::current()->Quit();
397 break;
398
399 case NotificationType::EXTENSION_INSTALLED:
400 VLOG(1) << "Got EXTENSION_INSTALLED notification.";
401 ++extension_installs_observed_;
402 MessageLoopForUI::current()->Quit();
403 break;
404
405 case NotificationType::EXTENSION_INSTALL_ERROR:
406 VLOG(1) << "Got EXTENSION_INSTALL_ERROR notification.";
407 MessageLoopForUI::current()->Quit();
408 break;
409
410 case NotificationType::EXTENSION_PROCESS_CREATED:
411 VLOG(1) << "Got EXTENSION_PROCESS_CREATED notification.";
412 MessageLoopForUI::current()->Quit();
413 break;
414
415 case NotificationType::EXTENSION_PROCESS_TERMINATED:
416 VLOG(1) << "Got EXTENSION_PROCESS_TERMINATED notification.";
417 MessageLoopForUI::current()->Quit();
418 break;
419
420 case NotificationType::EXTENSION_PAGE_ACTION_COUNT_CHANGED: {
421 LocationBarTesting* location_bar =
422 browser()->window()->GetLocationBar()->GetLocationBarForTesting();
423 VLOG(1) << "Got EXTENSION_PAGE_ACTION_COUNT_CHANGED notification. Number "
424 "of page actions: " << location_bar->PageActionCount();
425 if (location_bar->PageActionCount() ==
426 target_page_action_count_) {
427 target_page_action_count_ = -1;
428 MessageLoopForUI::current()->Quit();
429 }
430 break;
431 }
432
433 case NotificationType::EXTENSION_PAGE_ACTION_VISIBILITY_CHANGED: {
434 LocationBarTesting* location_bar =
435 browser()->window()->GetLocationBar()->GetLocationBarForTesting();
436 VLOG(1) << "Got EXTENSION_PAGE_ACTION_VISIBILITY_CHANGED notification. "
437 "Number of visible page actions: "
438 << location_bar->PageActionVisibleCount();
439 if (location_bar->PageActionVisibleCount() ==
440 target_visible_page_action_count_) {
441 target_visible_page_action_count_ = -1;
442 MessageLoopForUI::current()->Quit();
443 }
444 break;
445 }
446
447 default:
448 NOTREACHED();
449 break;
450 }
451 }
452