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
32ExtensionBrowserTest::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
41void 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
62const 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
101const Extension* ExtensionBrowserTest::LoadExtension(const FilePath& path) {
102  return LoadExtensionImpl(path, false, true);
103}
104
105const Extension* ExtensionBrowserTest::LoadExtensionIncognito(
106    const FilePath& path) {
107  return LoadExtensionImpl(path, true, true);
108}
109
110const Extension* ExtensionBrowserTest::LoadExtensionNoFileAccess(
111    const FilePath& path) {
112  return LoadExtensionImpl(path, false, false);
113}
114
115const Extension* ExtensionBrowserTest::LoadExtensionIncognitoNoFileAccess(
116    const FilePath& path) {
117  return LoadExtensionImpl(path, true, false);
118}
119
120bool 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
134FilePath 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.
169class MockAbortExtensionInstallUI : public ExtensionInstallUI {
170 public:
171  MockAbortExtensionInstallUI() : ExtensionInstallUI(NULL) {}
172
173  // Simulate a user abort on an extension installation.
174  virtual void ConfirmInstall(Delegate* delegate, const Extension* extension) {
175    delegate->InstallUIAbort();
176    MessageLoopForUI::current()->Quit();
177  }
178
179  virtual void OnInstallSuccess(const Extension* extension, SkBitmap* icon) {}
180
181  virtual void OnInstallFailure(const std::string& error) {}
182};
183
184class MockAutoConfirmExtensionInstallUI : public ExtensionInstallUI {
185 public:
186  explicit MockAutoConfirmExtensionInstallUI(Profile* profile) :
187      ExtensionInstallUI(profile) {}
188
189  // Proceed without confirmation prompt.
190  virtual void ConfirmInstall(Delegate* delegate, const Extension* extension) {
191    delegate->InstallUIProceed();
192  }
193};
194
195bool 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
203bool 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
268void 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
276void ExtensionBrowserTest::UnloadExtension(const std::string& extension_id) {
277  ExtensionService* service = browser()->profile()->GetExtensionService();
278  service->UnloadExtension(extension_id, UnloadedExtensionInfo::DISABLE);
279}
280
281void ExtensionBrowserTest::UninstallExtension(const std::string& extension_id) {
282  ExtensionService* service = browser()->profile()->GetExtensionService();
283  service->UninstallExtension(extension_id, false, NULL);
284}
285
286void ExtensionBrowserTest::DisableExtension(const std::string& extension_id) {
287  ExtensionService* service = browser()->profile()->GetExtensionService();
288  service->DisableExtension(extension_id);
289}
290
291void ExtensionBrowserTest::EnableExtension(const std::string& extension_id) {
292  ExtensionService* service = browser()->profile()->GetExtensionService();
293  service->EnableExtension(extension_id);
294}
295
296bool 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
308bool 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
320bool 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
344bool 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
351bool 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
359void ExtensionBrowserTest::WaitForExtensionLoad() {
360  ui_test_utils::RegisterAndWait(this, NotificationType::EXTENSION_LOADED,
361                                 NotificationService::AllSources());
362  WaitForExtensionHostsToLoad();
363}
364
365bool 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
379void 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