extension_browsertest.cc revision effb81e5f8246d0db0270817048dc992db66e9fb
1// Copyright (c) 2012 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_util.h"
11#include "base/files/file_path.h"
12#include "base/files/scoped_temp_dir.h"
13#include "base/path_service.h"
14#include "base/strings/string_number_conversions.h"
15#include "base/strings/stringprintf.h"
16#include "base/strings/utf_string_conversions.h"
17#include "chrome/browser/chrome_notification_types.h"
18#include "chrome/browser/extensions/browsertest_util.h"
19#include "chrome/browser/extensions/component_loader.h"
20#include "chrome/browser/extensions/crx_installer.h"
21#include "chrome/browser/extensions/extension_creator.h"
22#include "chrome/browser/extensions/extension_error_reporter.h"
23#include "chrome/browser/extensions/extension_install_prompt.h"
24#include "chrome/browser/extensions/extension_service.h"
25#include "chrome/browser/extensions/extension_util.h"
26#include "chrome/browser/extensions/unpacked_installer.h"
27#include "chrome/browser/extensions/updater/extension_cache_fake.h"
28#include "chrome/browser/profiles/profile.h"
29#include "chrome/browser/profiles/profile_manager.h"
30#include "chrome/browser/ui/browser.h"
31#include "chrome/browser/ui/browser_window.h"
32#include "chrome/browser/ui/tabs/tab_strip_model.h"
33#include "chrome/common/chrome_paths.h"
34#include "chrome/common/chrome_switches.h"
35#include "chrome/common/chrome_version_info.h"
36#include "chrome/test/base/ui_test_utils.h"
37#include "content/public/browser/navigation_controller.h"
38#include "content/public/browser/navigation_entry.h"
39#include "content/public/browser/notification_registrar.h"
40#include "content/public/browser/notification_service.h"
41#include "content/public/browser/render_view_host.h"
42#include "content/public/test/browser_test_utils.h"
43#include "extensions/browser/extension_host.h"
44#include "extensions/browser/extension_prefs.h"
45#include "extensions/browser/extension_system.h"
46#include "extensions/common/constants.h"
47#include "extensions/common/extension_set.h"
48#include "sync/api/string_ordinal.h"
49
50#if defined(OS_CHROMEOS)
51#include "chromeos/chromeos_switches.h"
52#endif
53
54using extensions::Extension;
55using extensions::ExtensionCreator;
56using extensions::FeatureSwitch;
57using extensions::Manifest;
58
59ExtensionBrowserTest::ExtensionBrowserTest()
60    : loaded_(false),
61      installed_(false),
62#if defined(OS_CHROMEOS)
63      set_chromeos_user_(true),
64#endif
65      current_channel_(chrome::VersionInfo::CHANNEL_DEV),
66      override_prompt_for_external_extensions_(
67          FeatureSwitch::prompt_for_external_extensions(),
68          false),
69      profile_(NULL) {
70  EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
71}
72
73ExtensionBrowserTest::~ExtensionBrowserTest() {
74}
75
76Profile* ExtensionBrowserTest::profile() {
77  if (!profile_) {
78    if (browser())
79      profile_ = browser()->profile();
80    else
81      profile_ = ProfileManager::GetActiveUserProfile();
82  }
83  return profile_;
84}
85
86// static
87const Extension* ExtensionBrowserTest::GetExtensionByPath(
88    const extensions::ExtensionSet* extensions, const base::FilePath& path) {
89  base::FilePath extension_path = base::MakeAbsoluteFilePath(path);
90  EXPECT_TRUE(!extension_path.empty());
91  for (extensions::ExtensionSet::const_iterator iter = extensions->begin();
92       iter != extensions->end(); ++iter) {
93    if ((*iter)->path() == extension_path) {
94      return iter->get();
95    }
96  }
97  return NULL;
98}
99
100void ExtensionBrowserTest::SetUp() {
101  test_extension_cache_.reset(new extensions::ExtensionCacheFake());
102  InProcessBrowserTest::SetUp();
103}
104
105void ExtensionBrowserTest::SetUpCommandLine(CommandLine* command_line) {
106  PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_);
107  test_data_dir_ = test_data_dir_.AppendASCII("extensions");
108  observer_.reset(new ExtensionTestNotificationObserver(browser()));
109
110#if defined(OS_CHROMEOS)
111  if (set_chromeos_user_) {
112    // This makes sure that we create the Default profile first, with no
113    // ExtensionService and then the real profile with one, as we do when
114    // running on chromeos.
115    command_line->AppendSwitchASCII(chromeos::switches::kLoginUser,
116                                    "TestUser@gmail.com");
117    command_line->AppendSwitchASCII(chromeos::switches::kLoginProfile, "user");
118  }
119#endif
120}
121
122void ExtensionBrowserTest::SetUpOnMainThread() {
123  InProcessBrowserTest::SetUpOnMainThread();
124  observer_.reset(new ExtensionTestNotificationObserver(browser()));
125}
126
127const Extension* ExtensionBrowserTest::LoadExtension(
128    const base::FilePath& path) {
129  return LoadExtensionWithFlags(path, kFlagEnableFileAccess);
130}
131
132const Extension* ExtensionBrowserTest::LoadExtensionIncognito(
133    const base::FilePath& path) {
134  return LoadExtensionWithFlags(path,
135                                kFlagEnableFileAccess | kFlagEnableIncognito);
136}
137
138const Extension* ExtensionBrowserTest::LoadExtensionWithFlags(
139    const base::FilePath& path, int flags) {
140  return LoadExtensionWithInstallParam(path, flags, std::string());
141}
142
143const extensions::Extension*
144ExtensionBrowserTest::LoadExtensionWithInstallParam(
145    const base::FilePath& path,
146    int flags,
147    const std::string& install_param) {
148  ExtensionService* service = extensions::ExtensionSystem::Get(
149      profile())->extension_service();
150  {
151    observer_->Watch(chrome::NOTIFICATION_EXTENSION_LOADED,
152                     content::NotificationService::AllSources());
153
154    scoped_refptr<extensions::UnpackedInstaller> installer(
155        extensions::UnpackedInstaller::Create(service));
156    installer->set_prompt_for_plugins(false);
157    installer->set_require_modern_manifest_version(
158        (flags & kFlagAllowOldManifestVersions) == 0);
159    installer->Load(path);
160
161    observer_->Wait();
162  }
163
164  // Find the loaded extension by its path. See crbug.com/59531 for why
165  // we cannot just use last_loaded_extension_id().
166  const Extension* extension = GetExtensionByPath(service->extensions(), path);
167  if (!extension)
168    return NULL;
169
170  if (!(flags & kFlagIgnoreManifestWarnings)) {
171    const std::vector<extensions::InstallWarning>& install_warnings =
172        extension->install_warnings();
173    if (!install_warnings.empty()) {
174      std::string install_warnings_message = base::StringPrintf(
175          "Unexpected warnings when loading test extension %s:\n",
176          path.AsUTF8Unsafe().c_str());
177
178      for (std::vector<extensions::InstallWarning>::const_iterator it =
179          install_warnings.begin(); it != install_warnings.end(); ++it) {
180        install_warnings_message += "  " + it->message + "\n";
181      }
182
183      EXPECT_TRUE(extension->install_warnings().empty()) <<
184          install_warnings_message;
185      return NULL;
186    }
187  }
188
189  const std::string extension_id = extension->id();
190
191  if (!install_param.empty()) {
192    extensions::ExtensionPrefs::Get(profile())
193        ->SetInstallParam(extension_id, install_param);
194    // Re-enable the extension if needed.
195    if (service->extensions()->Contains(extension_id)) {
196      content::WindowedNotificationObserver load_signal(
197          chrome::NOTIFICATION_EXTENSION_LOADED,
198          content::Source<Profile>(profile()));
199      // Reload the extension so that the NOTIFICATION_EXTENSION_LOADED
200      // observers may access |install_param|.
201      service->ReloadExtension(extension_id);
202      load_signal.Wait();
203      extension = service->GetExtensionById(extension_id, false);
204      CHECK(extension) << extension_id << " not found after reloading.";
205    }
206  }
207
208  // Toggling incognito or file access will reload the extension, so wait for
209  // the reload and grab the new extension instance. The default state is
210  // incognito disabled and file access enabled, so we don't wait in those
211  // cases.
212  {
213    content::WindowedNotificationObserver load_signal(
214        chrome::NOTIFICATION_EXTENSION_LOADED,
215        content::Source<Profile>(profile()));
216    CHECK(!extensions::util::IsIncognitoEnabled(extension_id, profile()));
217
218    if (flags & kFlagEnableIncognito) {
219      extensions::util::SetIsIncognitoEnabled(extension_id, profile(), true);
220      load_signal.Wait();
221      extension = service->GetExtensionById(extension_id, false);
222      CHECK(extension) << extension_id << " not found after reloading.";
223    }
224  }
225
226  {
227    content::WindowedNotificationObserver load_signal(
228        chrome::NOTIFICATION_EXTENSION_LOADED,
229        content::Source<Profile>(profile()));
230    CHECK(extensions::util::AllowFileAccess(extension_id, profile()));
231    if (!(flags & kFlagEnableFileAccess)) {
232      extensions::util::SetAllowFileAccess(extension_id, profile(), false);
233      load_signal.Wait();
234      extension = service->GetExtensionById(extension_id, false);
235      CHECK(extension) << extension_id << " not found after reloading.";
236    }
237  }
238
239  if (!observer_->WaitForExtensionViewsToLoad())
240    return NULL;
241
242  return extension;
243}
244
245const Extension* ExtensionBrowserTest::LoadExtensionAsComponentWithManifest(
246    const base::FilePath& path,
247    const base::FilePath::CharType* manifest_relative_path) {
248  ExtensionService* service = extensions::ExtensionSystem::Get(
249      profile())->extension_service();
250
251  std::string manifest;
252  if (!base::ReadFileToString(path.Append(manifest_relative_path), &manifest)) {
253    return NULL;
254  }
255
256  std::string extension_id = service->component_loader()->Add(manifest, path);
257  const Extension* extension = service->extensions()->GetByID(extension_id);
258  if (!extension)
259    return NULL;
260  observer_->set_last_loaded_extension_id(extension->id());
261  return extension;
262}
263
264const Extension* ExtensionBrowserTest::LoadExtensionAsComponent(
265    const base::FilePath& path) {
266  return LoadExtensionAsComponentWithManifest(path,
267                                              extensions::kManifestFilename);
268}
269
270base::FilePath ExtensionBrowserTest::PackExtension(
271    const base::FilePath& dir_path) {
272  base::FilePath crx_path = temp_dir_.path().AppendASCII("temp.crx");
273  if (!base::DeleteFile(crx_path, false)) {
274    ADD_FAILURE() << "Failed to delete crx: " << crx_path.value();
275    return base::FilePath();
276  }
277
278  // Look for PEM files with the same name as the directory.
279  base::FilePath pem_path =
280      dir_path.ReplaceExtension(FILE_PATH_LITERAL(".pem"));
281  base::FilePath pem_path_out;
282
283  if (!base::PathExists(pem_path)) {
284    pem_path = base::FilePath();
285    pem_path_out = crx_path.DirName().AppendASCII("temp.pem");
286    if (!base::DeleteFile(pem_path_out, false)) {
287      ADD_FAILURE() << "Failed to delete pem: " << pem_path_out.value();
288      return base::FilePath();
289    }
290  }
291
292  return PackExtensionWithOptions(dir_path, crx_path, pem_path, pem_path_out);
293}
294
295base::FilePath ExtensionBrowserTest::PackExtensionWithOptions(
296    const base::FilePath& dir_path,
297    const base::FilePath& crx_path,
298    const base::FilePath& pem_path,
299    const base::FilePath& pem_out_path) {
300  if (!base::PathExists(dir_path)) {
301    ADD_FAILURE() << "Extension dir not found: " << dir_path.value();
302    return base::FilePath();
303  }
304
305  if (!base::PathExists(pem_path) && pem_out_path.empty()) {
306    ADD_FAILURE() << "Must specify a PEM file or PEM output path";
307    return base::FilePath();
308  }
309
310  scoped_ptr<ExtensionCreator> creator(new ExtensionCreator());
311  if (!creator->Run(dir_path,
312                    crx_path,
313                    pem_path,
314                    pem_out_path,
315                    ExtensionCreator::kOverwriteCRX)) {
316    ADD_FAILURE() << "ExtensionCreator::Run() failed: "
317                  << creator->error_message();
318    return base::FilePath();
319  }
320
321  if (!base::PathExists(crx_path)) {
322    ADD_FAILURE() << crx_path.value() << " was not created.";
323    return base::FilePath();
324  }
325  return crx_path;
326}
327
328// This class is used to simulate an installation abort by the user.
329class MockAbortExtensionInstallPrompt : public ExtensionInstallPrompt {
330 public:
331  MockAbortExtensionInstallPrompt() : ExtensionInstallPrompt(NULL) {
332  }
333
334  // Simulate a user abort on an extension installation.
335  virtual void ConfirmInstall(
336      Delegate* delegate,
337      const Extension* extension,
338      const ShowDialogCallback& show_dialog_callback) OVERRIDE {
339    delegate->InstallUIAbort(true);
340    base::MessageLoopForUI::current()->Quit();
341  }
342
343  virtual void OnInstallSuccess(const Extension* extension,
344                                SkBitmap* icon) OVERRIDE {}
345
346  virtual void OnInstallFailure(
347      const extensions::CrxInstallerError& error) OVERRIDE {}
348};
349
350class MockAutoConfirmExtensionInstallPrompt : public ExtensionInstallPrompt {
351 public:
352  explicit MockAutoConfirmExtensionInstallPrompt(
353      content::WebContents* web_contents)
354    : ExtensionInstallPrompt(web_contents) {}
355
356  // Proceed without confirmation prompt.
357  virtual void ConfirmInstall(
358      Delegate* delegate,
359      const Extension* extension,
360      const ShowDialogCallback& show_dialog_callback) OVERRIDE {
361    delegate->InstallUIProceed();
362  }
363};
364
365const Extension* ExtensionBrowserTest::UpdateExtensionWaitForIdle(
366    const std::string& id,
367    const base::FilePath& path,
368    int expected_change) {
369  return InstallOrUpdateExtension(id,
370                                  path,
371                                  INSTALL_UI_TYPE_NONE,
372                                  expected_change,
373                                  Manifest::INTERNAL,
374                                  browser(),
375                                  Extension::NO_FLAGS,
376                                  true);
377}
378
379const Extension* ExtensionBrowserTest::InstallExtensionFromWebstore(
380    const base::FilePath& path,
381    int expected_change) {
382  return InstallOrUpdateExtension(std::string(),
383                                  path,
384                                  INSTALL_UI_TYPE_NONE,
385                                  expected_change,
386                                  Manifest::INTERNAL,
387                                  browser(),
388                                  Extension::FROM_WEBSTORE,
389                                  false);
390}
391
392const Extension* ExtensionBrowserTest::InstallOrUpdateExtension(
393    const std::string& id,
394    const base::FilePath& path,
395    InstallUIType ui_type,
396    int expected_change) {
397  return InstallOrUpdateExtension(id, path, ui_type, expected_change,
398      Manifest::INTERNAL, browser(), Extension::NO_FLAGS, false);
399}
400
401const Extension* ExtensionBrowserTest::InstallOrUpdateExtension(
402    const std::string& id,
403    const base::FilePath& path,
404    InstallUIType ui_type,
405    int expected_change,
406    Browser* browser,
407    Extension::InitFromValueFlags creation_flags) {
408  return InstallOrUpdateExtension(id, path, ui_type, expected_change,
409                                  Manifest::INTERNAL, browser, creation_flags,
410                                  false);
411}
412
413const Extension* ExtensionBrowserTest::InstallOrUpdateExtension(
414    const std::string& id,
415    const base::FilePath& path,
416    InstallUIType ui_type,
417    int expected_change,
418    Manifest::Location install_source) {
419  return InstallOrUpdateExtension(id, path, ui_type, expected_change,
420      install_source, browser(), Extension::NO_FLAGS, false);
421}
422
423const Extension* ExtensionBrowserTest::InstallOrUpdateExtension(
424    const std::string& id,
425    const base::FilePath& path,
426    InstallUIType ui_type,
427    int expected_change,
428    Manifest::Location install_source,
429    Browser* browser,
430    Extension::InitFromValueFlags creation_flags,
431    bool wait_for_idle) {
432  ExtensionService* service = profile()->GetExtensionService();
433  service->set_show_extensions_prompts(false);
434  size_t num_before = service->extensions()->size();
435
436  {
437    scoped_ptr<ExtensionInstallPrompt> install_ui;
438    if (ui_type == INSTALL_UI_TYPE_CANCEL) {
439      install_ui.reset(new MockAbortExtensionInstallPrompt());
440    } else if (ui_type == INSTALL_UI_TYPE_NORMAL) {
441      install_ui.reset(new ExtensionInstallPrompt(
442          browser->tab_strip_model()->GetActiveWebContents()));
443    } else if (ui_type == INSTALL_UI_TYPE_AUTO_CONFIRM) {
444      install_ui.reset(new MockAutoConfirmExtensionInstallPrompt(
445          browser->tab_strip_model()->GetActiveWebContents()));
446    }
447
448    // TODO(tessamac): Update callers to always pass an unpacked extension
449    //                 and then always pack the extension here.
450    base::FilePath crx_path = path;
451    if (crx_path.Extension() != FILE_PATH_LITERAL(".crx")) {
452      crx_path = PackExtension(path);
453    }
454    if (crx_path.empty())
455      return NULL;
456
457    scoped_refptr<extensions::CrxInstaller> installer(
458        extensions::CrxInstaller::Create(service, install_ui.Pass()));
459    installer->set_expected_id(id);
460    installer->set_creation_flags(creation_flags);
461    installer->set_install_source(install_source);
462    installer->set_install_wait_for_idle(wait_for_idle);
463    if (!installer->is_gallery_install()) {
464      installer->set_off_store_install_allow_reason(
465          extensions::CrxInstaller::OffStoreInstallAllowedInTest);
466    }
467
468    observer_->Watch(
469        chrome::NOTIFICATION_CRX_INSTALLER_DONE,
470        content::Source<extensions::CrxInstaller>(installer.get()));
471
472    installer->InstallCrx(crx_path);
473
474    observer_->Wait();
475  }
476
477  size_t num_after = service->extensions()->size();
478  EXPECT_EQ(num_before + expected_change, num_after);
479  if (num_before + expected_change != num_after) {
480    VLOG(1) << "Num extensions before: " << base::IntToString(num_before)
481            << " num after: " << base::IntToString(num_after)
482            << " Installed extensions follow:";
483
484    for (extensions::ExtensionSet::const_iterator it =
485             service->extensions()->begin();
486         it != service->extensions()->end(); ++it)
487      VLOG(1) << "  " << (*it)->id();
488
489    VLOG(1) << "Errors follow:";
490    const std::vector<base::string16>* errors =
491        ExtensionErrorReporter::GetInstance()->GetErrors();
492    for (std::vector<base::string16>::const_iterator iter = errors->begin();
493         iter != errors->end(); ++iter)
494      VLOG(1) << *iter;
495
496    return NULL;
497  }
498
499  if (!observer_->WaitForExtensionViewsToLoad())
500    return NULL;
501  return service->GetExtensionById(last_loaded_extension_id(), false);
502}
503
504void ExtensionBrowserTest::ReloadExtension(const std::string extension_id) {
505  observer_->Watch(chrome::NOTIFICATION_EXTENSION_LOADED,
506                content::NotificationService::AllSources());
507
508  ExtensionService* service =
509      extensions::ExtensionSystem::Get(profile())->extension_service();
510  service->ReloadExtension(extension_id);
511
512  observer_->Wait();
513  observer_->WaitForExtensionViewsToLoad();
514}
515
516void ExtensionBrowserTest::UnloadExtension(const std::string& extension_id) {
517  ExtensionService* service = extensions::ExtensionSystem::Get(
518      profile())->extension_service();
519  service->UnloadExtension(extension_id,
520                           extensions::UnloadedExtensionInfo::REASON_DISABLE);
521}
522
523void ExtensionBrowserTest::UninstallExtension(const std::string& extension_id) {
524  ExtensionService* service = extensions::ExtensionSystem::Get(
525      profile())->extension_service();
526  service->UninstallExtension(extension_id, false, NULL);
527}
528
529void ExtensionBrowserTest::DisableExtension(const std::string& extension_id) {
530  ExtensionService* service = extensions::ExtensionSystem::Get(
531      profile())->extension_service();
532  service->DisableExtension(extension_id, Extension::DISABLE_USER_ACTION);
533}
534
535void ExtensionBrowserTest::EnableExtension(const std::string& extension_id) {
536  ExtensionService* service = extensions::ExtensionSystem::Get(
537      profile())->extension_service();
538  service->EnableExtension(extension_id);
539}
540
541void ExtensionBrowserTest::OpenWindow(content::WebContents* contents,
542                                      const GURL& url,
543                                      bool newtab_process_should_equal_opener,
544                                      content::WebContents** newtab_result) {
545  content::WindowedNotificationObserver windowed_observer(
546      content::NOTIFICATION_LOAD_STOP,
547      content::NotificationService::AllSources());
548  ASSERT_TRUE(content::ExecuteScript(contents,
549                                     "window.open('" + url.spec() + "');"));
550
551  // The above window.open call is not user-initiated, so it will create
552  // a popup window instead of a new tab in current window.
553  // The stop notification will come from the new tab.
554  windowed_observer.Wait();
555  content::NavigationController* controller =
556      content::Source<content::NavigationController>(
557          windowed_observer.source()).ptr();
558  content::WebContents* newtab = controller->GetWebContents();
559  ASSERT_TRUE(newtab);
560  EXPECT_EQ(url, controller->GetLastCommittedEntry()->GetURL());
561  if (newtab_process_should_equal_opener)
562    EXPECT_EQ(contents->GetRenderProcessHost(), newtab->GetRenderProcessHost());
563  else
564    EXPECT_NE(contents->GetRenderProcessHost(), newtab->GetRenderProcessHost());
565
566  if (newtab_result)
567    *newtab_result = newtab;
568}
569
570void ExtensionBrowserTest::NavigateInRenderer(content::WebContents* contents,
571                                              const GURL& url) {
572  bool result = false;
573  content::WindowedNotificationObserver windowed_observer(
574      content::NOTIFICATION_LOAD_STOP,
575      content::NotificationService::AllSources());
576  ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
577      contents,
578      "window.addEventListener('unload', function() {"
579      "    window.domAutomationController.send(true);"
580      "}, false);"
581      "window.location = '" + url.spec() + "';",
582      &result));
583  ASSERT_TRUE(result);
584  windowed_observer.Wait();
585  EXPECT_EQ(url, contents->GetController().GetLastCommittedEntry()->GetURL());
586}
587
588extensions::ExtensionHost* ExtensionBrowserTest::FindHostWithPath(
589    extensions::ProcessManager* manager,
590    const std::string& path,
591    int expected_hosts) {
592  extensions::ExtensionHost* host = NULL;
593  int num_hosts = 0;
594  extensions::ProcessManager::ExtensionHostSet background_hosts =
595      manager->background_hosts();
596  for (extensions::ProcessManager::const_iterator iter =
597           background_hosts.begin();
598       iter != background_hosts.end();
599       ++iter) {
600    if ((*iter)->GetURL().path() == path) {
601      EXPECT_FALSE(host);
602      host = *iter;
603    }
604    num_hosts++;
605  }
606  EXPECT_EQ(expected_hosts, num_hosts);
607  return host;
608}
609
610std::string ExtensionBrowserTest::ExecuteScriptInBackgroundPage(
611    const std::string& extension_id,
612    const std::string& script) {
613  return extensions::browsertest_util::ExecuteScriptInBackgroundPage(
614      profile(), extension_id, script);
615}
616