blob: 56b1dacfc74d92fa90ff26ac54d6dcff1cf43724 [file] [log] [blame]
// 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 <stddef.h>
#include <memory>
#include <utility>
#include "base/threading/thread_restrictions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_web_ui.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/extensions/chrome_manifest_url_handlers.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "extensions/browser/extension_creator.h"
#include "extensions/common/constants.h"
#include "extensions/test/extension_test_message_listener.h"
#include "extensions/test/result_catcher.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
using content::WebContents;
namespace extensions {
class ExtensionOverrideTest : public ExtensionApiTest {
protected:
void SetUpOnMainThread() override {
ExtensionApiTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
}
bool CheckHistoryOverridesContainsNoDupes() {
// There should be no duplicate entries in the preferences.
const base::DictionaryValue* overrides =
browser()->profile()->GetPrefs()->GetDictionary(
ExtensionWebUI::kExtensionURLOverrides);
const base::ListValue* values = nullptr;
if (!overrides->GetList("history", &values))
return false;
std::set<std::string> seen_overrides;
for (const auto& val : *values) {
const base::DictionaryValue* dict = nullptr;
std::string entry;
if (!val.GetAsDictionary(&dict) || !dict->GetString("entry", &entry) ||
seen_overrides.count(entry) != 0)
return false;
seen_overrides.insert(entry);
}
return true;
}
// Returns AssertionSuccess() if the given |web_contents| is being actively
// controlled by the extension with |extension_id|.
testing::AssertionResult ExtensionControlsPage(
content::WebContents* web_contents,
const std::string& extension_id) {
if (!web_contents->GetController().GetLastCommittedEntry())
return testing::AssertionFailure() << "No last committed entry.";
// We can't just use WebContents::GetLastCommittedURL() here because
// trickiness makes it think that it committed chrome://newtab.
GURL gurl = web_contents->GetController().GetLastCommittedEntry()->GetURL();
if (!gurl.SchemeIs(kExtensionScheme))
return testing::AssertionFailure() << gurl;
if (gurl.host_piece() != extension_id)
return testing::AssertionFailure() << gurl;
return testing::AssertionSuccess();
}
base::FilePath data_dir() {
return test_data_dir_.AppendASCII("override");
}
};
// Basic test for overriding the NTP.
IN_PROC_BROWSER_TEST_F(ExtensionOverrideTest, OverrideNewTab) {
const Extension* extension = LoadExtension(data_dir().AppendASCII("newtab"));
{
// Navigate to the new tab page. The overridden new tab page
// will call chrome.test.sendMessage('controlled by first').
ExtensionTestMessageListener listener(false);
ui_test_utils::NavigateToURL(browser(), GURL("chrome://newtab/"));
EXPECT_TRUE(ExtensionControlsPage(
browser()->tab_strip_model()->GetActiveWebContents(),
extension->id()));
EXPECT_TRUE(listener.WaitUntilSatisfied());
EXPECT_EQ("controlled by first", listener.message());
}
}
// Check having multiple extensions with the same override.
IN_PROC_BROWSER_TEST_F(ExtensionOverrideTest, OverrideNewTabMultiple) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::FilePath temp_path = temp_dir.GetPath();
base::FilePath extension1_path = temp_path.AppendASCII("extension1.crx");
base::FilePath extension1_pem_path = temp_path.AppendASCII("extension1.pem");
base::FilePath extension1_unpacked_path = data_dir().AppendASCII("newtab");
ASSERT_TRUE(ExtensionCreator().Run(extension1_unpacked_path, extension1_path,
base::FilePath(), extension1_pem_path, 0));
base::FilePath extension2_path = temp_path.AppendASCII("extension2.crx");
base::FilePath extension2_pem_path = temp_path.AppendASCII("extension2.pem");
base::FilePath extension2_unpacked_path = data_dir().AppendASCII("newtab2");
ASSERT_TRUE(ExtensionCreator().Run(extension2_unpacked_path, extension2_path,
base::FilePath(), extension2_pem_path, 0));
// Prefer IDs because loading/unloading invalidates the extension ptrs.
const std::string extension1_id =
InstallExtensionWithPermissionsGranted(extension1_path, 1)->id();
const std::string extension2_id =
InstallExtensionWithPermissionsGranted(extension2_path, 1)->id();
{
// Navigate to the new tab page. Last extension installed wins, so
// the new tab page should be controlled by the second extension.
ExtensionTestMessageListener listener(false);
ui_test_utils::NavigateToURL(browser(), GURL("chrome://newtab/"));
EXPECT_TRUE(ExtensionControlsPage(
browser()->tab_strip_model()->GetActiveWebContents(),
extension2_id));
EXPECT_TRUE(listener.WaitUntilSatisfied());
EXPECT_EQ("controlled by second", listener.message());
}
// Unload and reload the first extension. This should *not* result in the
// first extension moving to the front of the line.
ReloadExtension(extension1_id);
{
// The page should still be controlled by the second extension.
ExtensionTestMessageListener listener(false);
ui_test_utils::NavigateToURL(browser(), GURL("chrome://newtab/"));
EXPECT_TRUE(ExtensionControlsPage(
browser()->tab_strip_model()->GetActiveWebContents(),
extension2_id));
EXPECT_TRUE(listener.WaitUntilSatisfied());
EXPECT_EQ("controlled by second", listener.message());
}
{
// Upgrade the first extension to a version that uses a different NTP url.
// This should *not* result in the first extension moving to the front of
// the line.
base::FilePath update_path = temp_path.AppendASCII("extension1_update.crx");
base::FilePath update_pem_path = extension1_pem_path;
base::FilePath update_unpacked_path =
data_dir().AppendASCII("newtab_upgrade");
ASSERT_TRUE(ExtensionCreator().Run(update_unpacked_path, update_path,
update_pem_path, base::FilePath(), 0));
const Extension* updated_extension =
UpdateExtension(extension1_id, update_path, 0);
ASSERT_TRUE(updated_extension);
EXPECT_EQ(extension1_id, updated_extension->id());
}
{
// The page should still be controlled by the second extension.
ExtensionTestMessageListener listener(false);
ui_test_utils::NavigateToURL(browser(), GURL("chrome://newtab/"));
EXPECT_TRUE(ExtensionControlsPage(
browser()->tab_strip_model()->GetActiveWebContents(), extension2_id));
EXPECT_TRUE(listener.WaitUntilSatisfied());
EXPECT_EQ("controlled by second", listener.message());
}
// Unload the (controlling) second extension. Now, and only now, should
// extension1 take over.
UnloadExtension(extension2_id);
{
ExtensionTestMessageListener listener(false);
ui_test_utils::NavigateToURL(browser(), GURL("chrome://newtab/"));
EXPECT_TRUE(ExtensionControlsPage(
browser()->tab_strip_model()->GetActiveWebContents(),
extension1_id));
EXPECT_TRUE(listener.WaitUntilSatisfied());
EXPECT_EQ("controlled by first upgrade", listener.message());
}
}
// Test that unloading an extension overriding the page reloads the page with
// the proper url.
IN_PROC_BROWSER_TEST_F(ExtensionOverrideTest,
OverridingExtensionUnloadedWithPageOpen) {
// Prefer IDs because loading/unloading invalidates the extension ptrs.
const std::string extension1_id =
LoadExtension(data_dir().AppendASCII("newtab"))->id();
const std::string extension2_id =
LoadExtension(data_dir().AppendASCII("newtab2"))->id();
{
// Navigate to the new tab page. Last extension installed wins, so
// the new tab page should be controlled by the second extension.
ExtensionTestMessageListener listener(false);
ui_test_utils::NavigateToURL(browser(), GURL("chrome://newtab/"));
EXPECT_TRUE(ExtensionControlsPage(
browser()->tab_strip_model()->GetActiveWebContents(),
extension2_id));
EXPECT_TRUE(listener.WaitUntilSatisfied());
EXPECT_EQ("controlled by second", listener.message());
}
{
// Unload the controlling extension. The page should be automatically
// reloaded with the new controlling extension.
ExtensionTestMessageListener listener(false);
UnloadExtension(extension2_id);
EXPECT_TRUE(listener.WaitUntilSatisfied());
EXPECT_EQ("controlled by first", listener.message());
EXPECT_TRUE(ExtensionControlsPage(
browser()->tab_strip_model()->GetActiveWebContents(),
extension1_id));
}
}
#if defined(OS_MACOSX)
// Hangy: http://crbug.com/70511
#define MAYBE_OverrideNewTabIncognito DISABLED_OverrideNewTabIncognito
#else
#define MAYBE_OverrideNewTabIncognito OverrideNewTabIncognito
#endif
IN_PROC_BROWSER_TEST_F(ExtensionOverrideTest, MAYBE_OverrideNewTabIncognito) {
LoadExtension(data_dir().AppendASCII("newtab"));
// Navigate an incognito tab to the new tab page. We should get the actual
// new tab page because we can't load chrome-extension URLs in incognito.
Browser* otr_browser =
OpenURLOffTheRecord(browser()->profile(), GURL("chrome://newtab/"));
WebContents* tab = otr_browser->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(tab->GetController().GetVisibleEntry());
EXPECT_FALSE(tab->GetController().GetVisibleEntry()->GetURL().
SchemeIs(kExtensionScheme));
}
// Check that when an overridden new tab page has focus, a subframe navigation
// on that page does not steal the focus away by focusing the omnibox.
// See https://crbug.com/700124.
IN_PROC_BROWSER_TEST_F(ExtensionOverrideTest,
SubframeNavigationInOverridenNTPDoesNotAffectFocus) {
// Load an extension that overrides the new tab page.
const Extension* extension = LoadExtension(data_dir().AppendASCII("newtab"));
// Navigate to the new tab page. The overridden new tab page
// will call chrome.test.sendMessage('controlled by first').
ExtensionTestMessageListener listener("controlled by first", false);
ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUINewTabURL));
WebContents* contents = browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(ExtensionControlsPage(contents, extension->id()));
EXPECT_TRUE(listener.WaitUntilSatisfied());
// Start off with the main page focused.
contents->Focus();
EXPECT_TRUE(contents->GetRenderWidgetHostView()->HasFocus());
// Inject an iframe and navigate it to a cross-site URL. With
// --site-per-process, this will go into a separate process.
GURL cross_site_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
std::string script = "var f = document.createElement('iframe');\n"
"f.src = '" + cross_site_url.spec() + "';\n"
"document.body.appendChild(f);\n";
EXPECT_TRUE(ExecuteScript(contents, script));
WaitForLoadStop(contents);
// The page should still have focus. The cross-process subframe navigation
// above should not try to focus the omnibox, which would make this false.
EXPECT_TRUE(contents->GetRenderWidgetHostView()->HasFocus());
}
// Times out consistently on Win, http://crbug.com/45173.
#if defined(OS_WIN)
#define MAYBE_OverrideHistory DISABLED_OverrideHistory
#else
#define MAYBE_OverrideHistory OverrideHistory
#endif // defined(OS_WIN)
IN_PROC_BROWSER_TEST_F(ExtensionOverrideTest, MAYBE_OverrideHistory) {
ASSERT_TRUE(RunExtensionTest("override/history")) << message_;
{
ResultCatcher catcher;
// Navigate to the history page. The overridden history page
// will call chrome.test.notifyPass() .
ui_test_utils::NavigateToURL(browser(), GURL("chrome://history/"));
ASSERT_TRUE(catcher.GetNextResult());
}
}
// Regression test for http://crbug.com/41442.
IN_PROC_BROWSER_TEST_F(ExtensionOverrideTest, ShouldNotCreateDuplicateEntries) {
const Extension* extension =
LoadExtension(test_data_dir_.AppendASCII("override/history"));
ASSERT_TRUE(extension);
// Simulate several LoadExtension() calls happening over the lifetime of
// a preferences file without corresponding UnloadExtension() calls.
for (size_t i = 0; i < 3; ++i) {
ExtensionWebUI::RegisterOrActivateChromeURLOverrides(
browser()->profile(),
URLOverrides::GetChromeURLOverrides(extension));
}
ASSERT_TRUE(CheckHistoryOverridesContainsNoDupes());
}
// TODO(devlin): This test seems a bit contrived. How would we end up with
// duplicate entries created?
IN_PROC_BROWSER_TEST_F(ExtensionOverrideTest, ShouldCleanUpDuplicateEntries) {
// Simulate several LoadExtension() calls happening over the lifetime of
// a preferences file without corresponding UnloadExtension() calls. This is
// the same as the above test, except for that it is testing the case where
// the file already contains dupes when an extension is loaded.
auto list = std::make_unique<base::ListValue>();
for (size_t i = 0; i < 3; ++i) {
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
dict->SetString("entry", "http://www.google.com/");
dict->SetBoolean("active", true);
list->Append(std::move(dict));
}
{
DictionaryPrefUpdate update(browser()->profile()->GetPrefs(),
ExtensionWebUI::kExtensionURLOverrides);
update.Get()->Set("history", std::move(list));
}
ASSERT_FALSE(CheckHistoryOverridesContainsNoDupes());
ExtensionWebUI::InitializeChromeURLOverrides(profile());
ASSERT_TRUE(CheckHistoryOverridesContainsNoDupes());
}
} // namespace extensions