// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/command_line.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/extensions/extension_host.h" #include "chrome/browser/extensions/extension_install_ui.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_system.h" #include "chrome/browser/extensions/startup_helper.h" #include "chrome/browser/extensions/webstore_standalone_installer.h" #include "chrome/browser/infobars/infobar_service.h" #include "chrome/browser/managed_mode/managed_user_service.h" #include "chrome/browser/managed_mode/managed_user_service_factory.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/extension_builder.h" #include "chrome/common/extensions/value_builder.h" #include "chrome/common/pref_names.h" #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/test_switches.h" #include "chrome/test/base/ui_test_utils.h" #include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_types.h" #include "content/public/browser/web_contents.h" #include "content/public/test/browser_test_utils.h" #include "net/base/host_port_pair.h" #include "net/dns/mock_host_resolver.h" #include "url/gurl.h" using content::WebContents; using extensions::DictionaryBuilder; using extensions::Extension; using extensions::ExtensionBuilder; using extensions::ListBuilder; const char kWebstoreDomain[] = "cws.com"; const char kAppDomain[] = "app.com"; const char kNonAppDomain[] = "nonapp.com"; const char kTestExtensionId[] = "ecglahbcnmdpdciemllbhojghbkagdje"; class WebstoreStartupInstallerTest : public InProcessBrowserTest { public: virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { // We start the test server now instead of in // SetUpInProcessBrowserTestFixture so that we can get its port number. ASSERT_TRUE(test_server()->Start()); net::HostPortPair host_port = test_server()->host_port_pair(); test_gallery_url_ = base::StringPrintf( "http://%s:%d/files/extensions/api_test/webstore_inline_install", kWebstoreDomain, host_port.port()); command_line->AppendSwitchASCII( switches::kAppsGalleryURL, test_gallery_url_); GURL crx_url = GenerateTestServerUrl(kWebstoreDomain, "extension.crx"); CommandLine::ForCurrentProcess()->AppendSwitchASCII( switches::kAppsGalleryUpdateURL, crx_url.spec()); // Allow tests to call window.gc(), so that we can check that callback // functions don't get collected prematurely. command_line->AppendSwitchASCII(switches::kJavaScriptFlags, "--expose-gc"); } virtual void SetUpInProcessBrowserTestFixture() OVERRIDE { host_resolver()->AddRule(kWebstoreDomain, "127.0.0.1"); host_resolver()->AddRule(kAppDomain, "127.0.0.1"); host_resolver()->AddRule(kNonAppDomain, "127.0.0.1"); } protected: GURL GenerateTestServerUrl(const std::string& domain, const std::string& page_filename) { GURL page_url = test_server()->GetURL( "files/extensions/api_test/webstore_inline_install/" + page_filename); GURL::Replacements replace_host; replace_host.SetHostStr(domain); return page_url.ReplaceComponents(replace_host); } void RunTest(const std::string& test_function_name) { bool result = false; std::string script = base::StringPrintf( "%s('%s')", test_function_name.c_str(), test_gallery_url_.c_str()); ASSERT_TRUE(content::ExecuteScriptAndExtractBool( browser()->tab_strip_model()->GetActiveWebContents(), script, &result)); EXPECT_TRUE(result); } // Passes |i| to |test_function_name|, and expects that function to // return one of "FAILED", "KEEPGOING" or "DONE". KEEPGOING should be // returned if more tests remain to be run and the current test succeeded, // FAILED is returned when a test fails, and DONE is returned by the last // test if it succeeds. // This methods returns true iff there are more tests that need to be run. bool RunIndexedTest(const std::string& test_function_name, int i) { std::string result = "FAILED"; std::string script = base::StringPrintf("%s('%s', %d)", test_function_name.c_str(), test_gallery_url_.c_str(), i); EXPECT_TRUE(content::ExecuteScriptAndExtractString( browser()->tab_strip_model()->GetActiveWebContents(), script, &result)); EXPECT_TRUE(result != "FAILED"); return result == "KEEPGOING"; } std::string test_gallery_url_; }; IN_PROC_BROWSER_TEST_F(WebstoreStartupInstallerTest, Install) { CommandLine::ForCurrentProcess()->AppendSwitchASCII( switches::kAppsGalleryInstallAutoConfirmForTests, "accept"); ui_test_utils::NavigateToURL( browser(), GenerateTestServerUrl(kAppDomain, "install.html")); RunTest("runTest"); const extensions::Extension* extension = browser()->profile()-> GetExtensionService()->GetExtensionById(kTestExtensionId, false); EXPECT_TRUE(extension); } IN_PROC_BROWSER_TEST_F(WebstoreStartupInstallerTest, InstallNotAllowedFromNonVerifiedDomains) { CommandLine::ForCurrentProcess()->AppendSwitchASCII( switches::kAppsGalleryInstallAutoConfirmForTests, "cancel"); ui_test_utils::NavigateToURL( browser(), GenerateTestServerUrl(kNonAppDomain, "install_non_verified_domain.html")); RunTest("runTest1"); RunTest("runTest2"); } IN_PROC_BROWSER_TEST_F(WebstoreStartupInstallerTest, FindLink) { ui_test_utils::NavigateToURL( browser(), GenerateTestServerUrl(kAppDomain, "find_link.html")); RunTest("runTest"); } // Flakes on all platforms: http://crbug.com/95713, http://crbug.com/229947 IN_PROC_BROWSER_TEST_F(WebstoreStartupInstallerTest, DISABLED_ArgumentValidation) { CommandLine::ForCurrentProcess()->AppendSwitchASCII( switches::kAppsGalleryInstallAutoConfirmForTests, "cancel"); // Each of these tests has to run separately, since one page/tab can // only have one in-progress install request. These tests don't all pass // callbacks to install, so they have no way to wait for the installation // to complete before starting the next test. bool is_finished = false; for (int i = 0; !is_finished; ++i) { ui_test_utils::NavigateToURL( browser(), GenerateTestServerUrl(kAppDomain, "argument_validation.html")); is_finished = !RunIndexedTest("runTest", i); } } IN_PROC_BROWSER_TEST_F(WebstoreStartupInstallerTest, MultipleInstallCalls) { CommandLine::ForCurrentProcess()->AppendSwitchASCII( switches::kAppsGalleryInstallAutoConfirmForTests, "cancel"); ui_test_utils::NavigateToURL( browser(), GenerateTestServerUrl(kAppDomain, "multiple_install_calls.html")); RunTest("runTest"); } IN_PROC_BROWSER_TEST_F(WebstoreStartupInstallerTest, InstallNotSupported) { CommandLine::ForCurrentProcess()->AppendSwitchASCII( switches::kAppsGalleryInstallAutoConfirmForTests, "cancel"); ui_test_utils::NavigateToURL( browser(), GenerateTestServerUrl(kAppDomain, "install_not_supported.html")); ui_test_utils::WindowedTabAddedNotificationObserver observer( content::NotificationService::AllSources()); RunTest("runTest"); observer.Wait(); // The inline install should fail, and a store-provided URL should be opened // in a new tab. WebContents* web_contents = browser()->tab_strip_model()->GetActiveWebContents(); EXPECT_EQ(GURL("http://cws.com/show-me-the-money"), web_contents->GetURL()); } // Regression test for http://crbug.com/144991. IN_PROC_BROWSER_TEST_F(WebstoreStartupInstallerTest, InstallFromHostedApp) { CommandLine::ForCurrentProcess()->AppendSwitchASCII( switches::kAppsGalleryInstallAutoConfirmForTests, "accept"); const GURL kInstallUrl = GenerateTestServerUrl(kAppDomain, "install.html"); // We're forced to construct a hosted app dynamically because we need the // app to run on a declared URL, but we don't know the port ahead of time. scoped_refptr hosted_app = ExtensionBuilder() .SetManifest(DictionaryBuilder() .Set("name", "hosted app") .Set("version", "1") .Set("app", DictionaryBuilder() .Set("urls", ListBuilder().Append(kInstallUrl.spec())) .Set("launch", DictionaryBuilder() .Set("web_url", kInstallUrl.spec()))) .Set("manifest_version", 2)) .Build(); ASSERT_TRUE(hosted_app.get()); ExtensionService* extension_service = extensions::ExtensionSystem::Get(browser()->profile())-> extension_service(); extension_service->AddExtension(hosted_app.get()); EXPECT_TRUE(extension_service->extensions()->Contains(hosted_app->id())); ui_test_utils::NavigateToURL(browser(), kInstallUrl); EXPECT_FALSE(extension_service->extensions()->Contains(kTestExtensionId)); RunTest("runTest"); EXPECT_TRUE(extension_service->extensions()->Contains(kTestExtensionId)); } IN_PROC_BROWSER_TEST_F(WebstoreStartupInstallerTest, InstallProhibitedForManagedUsers) { #if defined(OS_WIN) && defined(USE_ASH) // Disable this test in Metro+Ash for now (http://crbug.com/262796). if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAshBrowserTests)) return; #endif CommandLine::ForCurrentProcess()->AppendSwitchASCII( switches::kAppsGalleryInstallAutoConfirmForTests, "accept"); // Make the profile managed such that no extension installs are allowed. browser()->profile()->GetPrefs()->SetBoolean(prefs::kProfileIsManaged, true); ManagedUserService* service = ManagedUserServiceFactory::GetForProfile(browser()->profile()); service->Init(); ui_test_utils::NavigateToURL( browser(), GenerateTestServerUrl(kAppDomain, "install_prohibited.html")); RunTest("runTest"); // No error infobar should show up. WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents(); InfoBarService* info_bar_service = InfoBarService::FromWebContents(contents); EXPECT_EQ(info_bar_service->infobar_count(), 0u); } // The unpack failure test needs to use a different install .crx, which is // specified via a command-line flag, so it needs its own test subclass. class WebstoreStartupInstallUnpackFailureTest : public WebstoreStartupInstallerTest { public: virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { WebstoreStartupInstallerTest::SetUpCommandLine(command_line); GURL crx_url = GenerateTestServerUrl( kWebstoreDomain, "malformed_extension.crx"); CommandLine::ForCurrentProcess()->AppendSwitchASCII( switches::kAppsGalleryUpdateURL, crx_url.spec()); } virtual void SetUpInProcessBrowserTestFixture() OVERRIDE { WebstoreStartupInstallerTest::SetUpInProcessBrowserTestFixture(); ExtensionInstallUI::DisableFailureUIForTests(); } }; IN_PROC_BROWSER_TEST_F(WebstoreStartupInstallUnpackFailureTest, WebstoreStartupInstallUnpackFailureTest) { CommandLine::ForCurrentProcess()->AppendSwitchASCII( switches::kAppsGalleryInstallAutoConfirmForTests, "accept"); ui_test_utils::NavigateToURL(browser(), GenerateTestServerUrl(kAppDomain, "install_unpack_failure.html")); RunTest("runTest"); } class CommandLineWebstoreInstall : public WebstoreStartupInstallerTest, public content::NotificationObserver { public: CommandLineWebstoreInstall() : saw_install_(false), browser_open_count_(0) {} virtual ~CommandLineWebstoreInstall() {} virtual void SetUpOnMainThread() OVERRIDE { WebstoreStartupInstallerTest::SetUpOnMainThread(); registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED, content::NotificationService::AllSources()); registrar_.Add(this, chrome::NOTIFICATION_BROWSER_OPENED, content::NotificationService::AllSources()); } bool saw_install() { return saw_install_; } int browser_open_count() { return browser_open_count_; } // NotificationObserver interface. virtual void Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) OVERRIDE { if (type == chrome::NOTIFICATION_EXTENSION_INSTALLED) { const Extension* extension = content::Details(details)-> extension; ASSERT_TRUE(extension != NULL); EXPECT_EQ(extension->id(), kTestExtensionId); saw_install_ = true; } else if (type == chrome::NOTIFICATION_BROWSER_OPENED) { browser_open_count_++; } else { ASSERT_TRUE(false) << "Unexpected notification type : " << type; } } content::NotificationRegistrar registrar_; // Have we seen an installation notification for kTestExtensionId ? bool saw_install_; // How many NOTIFICATION_BROWSER_OPENED notifications have we seen? int browser_open_count_; }; IN_PROC_BROWSER_TEST_F(CommandLineWebstoreInstall, Accept) { CommandLine* command_line = CommandLine::ForCurrentProcess(); command_line->AppendSwitchASCII( switches::kInstallFromWebstore, kTestExtensionId); command_line->AppendSwitchASCII( switches::kAppsGalleryInstallAutoConfirmForTests, "accept"); extensions::StartupHelper helper; EXPECT_TRUE(helper.InstallFromWebstore(*command_line, browser()->profile())); EXPECT_TRUE(saw_install()); EXPECT_EQ(0, browser_open_count()); } IN_PROC_BROWSER_TEST_F(CommandLineWebstoreInstall, Cancel) { CommandLine* command_line = CommandLine::ForCurrentProcess(); command_line->AppendSwitchASCII( switches::kInstallFromWebstore, kTestExtensionId); command_line->AppendSwitchASCII( switches::kAppsGalleryInstallAutoConfirmForTests, "cancel"); extensions::StartupHelper helper; EXPECT_FALSE(helper.InstallFromWebstore(*command_line, browser()->profile())); EXPECT_FALSE(saw_install()); EXPECT_EQ(0, browser_open_count()); } IN_PROC_BROWSER_TEST_F(CommandLineWebstoreInstall, LimitedAccept) { extensions::StartupHelper helper; // Small test of "WebStoreIdFromLimitedInstallCmdLine" which made more // sense together with the rest of the test for "LimitedInstallFromWebstore". CommandLine command_line_test1(CommandLine::NO_PROGRAM); command_line_test1.AppendSwitchASCII(switches::kLimitedInstallFromWebstore, "1"); EXPECT_EQ("nckgahadagoaajjgafhacjanaoiihapd", helper.WebStoreIdFromLimitedInstallCmdLine(command_line_test1)); CommandLine command_line_test2(CommandLine::NO_PROGRAM); command_line_test1.AppendSwitchASCII(switches::kLimitedInstallFromWebstore, "2"); EXPECT_EQ(kTestExtensionId, helper.WebStoreIdFromLimitedInstallCmdLine(command_line_test1)); // Now, on to the real test for LimitedInstallFromWebstore. CommandLine* command_line = CommandLine::ForCurrentProcess(); command_line->AppendSwitchASCII( switches::kLimitedInstallFromWebstore, "2"); helper.LimitedInstallFromWebstore(*command_line, browser()->profile(), base::MessageLoop::QuitWhenIdleClosure()); base::MessageLoop::current()->Run(); EXPECT_TRUE(saw_install()); EXPECT_EQ(0, browser_open_count()); }