blob: a995acf22a0aca43973b5174db5bba769848978b [file] [log] [blame]
// Copyright 2013 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 <memory>
#include <string>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/scoped_temp_dir.h"
#include "base/i18n/base_i18n_switches.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/statistics_recorder.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search/instant_service.h"
#include "chrome/browser/search/instant_service_factory.h"
#include "chrome/browser/search/instant_service_observer.h"
#include "chrome/browser/search/ntp_features.h"
#include "chrome/browser/search/search.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/search/instant_test_utils.h"
#include "chrome/browser/ui/search/local_ntp_test_utils.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/search/instant_types.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/ntp_tiles/constants.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/interstitial_page.h"
#include "content/public/browser/interstitial_page_delegate.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/download_test_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_navigation_throttle_inserter.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/web_feature.mojom.h"
#include "ui/native_theme/test_native_theme.h"
#include "url/gurl.h"
namespace {
// In a non-signed-in, fresh profile with no history, there should be one
// default TopSites tile (see history::PrepopulatedPage).
const int kDefaultMostVisitedItemCount = 1;
// This is the default maximum custom links we can have. The number comes from
// ntp_tiles::CustomLinksManager.
const int kDefaultCustomLinkMaxCount = 10;
// Name for the Most Visited iframe in the NTP.
const char kMostVisitedIframe[] = "mv-single";
// Name for the edit/add custom link iframe in the NTP.
const char kEditCustomLinkIframe[] = "custom-links-edit";
// Delimiter in the Most Visited icon URL that indicates a dark icon. Keep value
// in sync with NtpIconSource.
const char kMVIconDarkParameter[] = "/dark/";
// Returns the RenderFrameHost corresponding to the |iframe_name| in the
// given |tab|. |tab| must correspond to an NTP.
content::RenderFrameHost* GetIframe(content::WebContents* tab,
const char* iframe_name) {
for (content::RenderFrameHost* frame : tab->GetAllFrames()) {
if (frame->GetFrameName() == iframe_name) {
return frame;
}
}
return nullptr;
}
class TestMostVisitedObserver : public InstantServiceObserver {
public:
explicit TestMostVisitedObserver(InstantService* service)
: service_(service), expected_count_(0) {
service_->AddObserver(this);
}
~TestMostVisitedObserver() override { service_->RemoveObserver(this); }
void WaitForNumberOfItems(size_t count) {
DCHECK(!quit_closure_);
expected_count_ = count;
if (items_.size() == count) {
return;
}
base::RunLoop run_loop;
quit_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
private:
void ThemeInfoChanged(const ThemeBackgroundInfo&) override {}
void MostVisitedItemsChanged(const std::vector<InstantMostVisitedItem>& items,
bool is_custom_links) override {
items_ = items;
if (quit_closure_ && items_.size() == expected_count_) {
std::move(quit_closure_).Run();
quit_closure_.Reset();
}
}
InstantService* const service_;
std::vector<InstantMostVisitedItem> items_;
size_t expected_count_;
base::OnceClosure quit_closure_;
};
class LocalNTPTest : public InProcessBrowserTest {
public:
LocalNTPTest(const std::vector<base::Feature>& enabled_features,
const std::vector<base::Feature>& disabled_features) {
feature_list_.InitWithFeatures(enabled_features, disabled_features);
}
LocalNTPTest()
: LocalNTPTest(/*enabled_features=*/{features::kUseGoogleLocalNtp},
/*disabled_features=*/{}) {}
void SetUpOnMainThread() override {
// Some tests depend on the prepopulated most visited tiles coming from
// TopSites, so make sure they are available before running the tests.
// (TopSites is loaded asynchronously at startup, so without this, there's
// a chance that it hasn't finished and we receive 0 tiles.)
InstantService* instant_service =
InstantServiceFactory::GetForProfile(browser()->profile());
TestMostVisitedObserver mv_observer(instant_service);
// Make sure the observer knows about the current items. Typically, this
// gets triggered by navigating to an NTP.
instant_service->UpdateMostVisitedItemsInfo();
mv_observer.WaitForNumberOfItems(kDefaultMostVisitedItemCount);
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(LocalNTPTest, EmbeddedSearchAPIOnlyAvailableOnNTP) {
// Set up a test server, so we have some arbitrary non-NTP URL to navigate to.
net::EmbeddedTestServer test_server(net::EmbeddedTestServer::TYPE_HTTPS);
test_server.ServeFilesFromSourceDirectory("chrome/test/data");
ASSERT_TRUE(test_server.Start());
const GURL other_url = test_server.GetURL("/simple.html");
// Open an NTP.
content::WebContents* active_tab = local_ntp_test_utils::OpenNewTab(
browser(), GURL(chrome::kChromeUINewTabURL));
ASSERT_TRUE(search::IsInstantNTP(active_tab));
// Check that the embeddedSearch API is available.
bool result = false;
ASSERT_TRUE(instant_test_utils::GetBoolFromJS(
active_tab, "!!window.chrome.embeddedSearch", &result));
EXPECT_TRUE(result);
// Navigate somewhere else in the same tab.
content::TestNavigationObserver elsewhere_observer(active_tab);
ui_test_utils::NavigateToURL(browser(), other_url);
elsewhere_observer.Wait();
ASSERT_TRUE(elsewhere_observer.last_navigation_succeeded());
ASSERT_FALSE(search::IsInstantNTP(active_tab));
// Now the embeddedSearch API should have gone away.
ASSERT_TRUE(instant_test_utils::GetBoolFromJS(
active_tab, "!!window.chrome.embeddedSearch", &result));
EXPECT_FALSE(result);
// Navigate back to the NTP.
content::TestNavigationObserver back_observer(active_tab);
chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
back_observer.Wait();
// The API should be back.
ASSERT_TRUE(instant_test_utils::GetBoolFromJS(
active_tab, "!!window.chrome.embeddedSearch", &result));
EXPECT_TRUE(result);
// Navigate forward to the non-NTP page.
content::TestNavigationObserver fwd_observer(active_tab);
chrome::GoForward(browser(), WindowOpenDisposition::CURRENT_TAB);
fwd_observer.Wait();
// The API should be gone.
ASSERT_TRUE(instant_test_utils::GetBoolFromJS(
active_tab, "!!window.chrome.embeddedSearch", &result));
EXPECT_FALSE(result);
// Navigate to a new NTP instance.
ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUINewTabURL));
ASSERT_TRUE(search::IsInstantNTP(active_tab));
// Now the API should be available again.
ASSERT_TRUE(instant_test_utils::GetBoolFromJS(
active_tab, "!!window.chrome.embeddedSearch", &result));
EXPECT_TRUE(result);
}
// The spare RenderProcessHost is warmed up *before* the target destination is
// known and therefore doesn't include any special command-line flags that are
// used when launching a RenderProcessHost known to be needed for NTP. This
// test ensures that the spare RenderProcessHost doesn't accidentally end up
// being used for NTP navigations.
IN_PROC_BROWSER_TEST_F(LocalNTPTest, SpareProcessDoesntInterfereWithSearchAPI) {
content::WebContents* active_tab =
browser()->tab_strip_model()->GetActiveWebContents();
// Navigate to a non-NTP URL, so that the next step needs to swap the process.
GURL non_ntp_url = ui_test_utils::GetTestUrl(
base::FilePath(), base::FilePath().AppendASCII("title1.html"));
ui_test_utils::NavigateToURL(browser(), non_ntp_url);
content::RenderProcessHost* old_process =
active_tab->GetMainFrame()->GetProcess();
// Navigate to an NTP while a spare process is present.
content::RenderProcessHost::WarmupSpareRenderProcessHost(
browser()->profile());
ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUINewTabURL));
ASSERT_TRUE(search::IsInstantNTP(active_tab));
// Verify that a process swap has taken place. This is an indirect indication
// that the spare process could have been used (during the process swap).
// This assertion is a sanity check of the test setup, rather than
// verification of the core thing that the test cares about.
content::RenderProcessHost* new_process =
active_tab->GetMainFrame()->GetProcess();
ASSERT_NE(new_process, old_process);
// Check that the embeddedSearch API is available - the spare
// RenderProcessHost either shouldn't be used, or if used it should have been
// launched with the appropriate, NTP-specific cmdline flags.
bool result = false;
ASSERT_TRUE(instant_test_utils::GetBoolFromJS(
active_tab, "!!window.chrome.embeddedSearch", &result));
EXPECT_TRUE(result);
}
// Regression test for crbug.com/776660 and crbug.com/776655.
IN_PROC_BROWSER_TEST_F(LocalNTPTest, EmbeddedSearchAPIExposesStaticFunctions) {
// Open an NTP.
content::WebContents* active_tab = local_ntp_test_utils::OpenNewTab(
browser(), GURL(chrome::kChromeUINewTabURL));
ASSERT_TRUE(search::IsInstantNTP(active_tab));
struct TestCase {
const char* function_name;
const char* args;
} test_cases[] = {
{"window.chrome.embeddedSearch.searchBox.paste", "\"text\""},
{"window.chrome.embeddedSearch.searchBox.startCapturingKeyStrokes", ""},
{"window.chrome.embeddedSearch.searchBox.stopCapturingKeyStrokes", ""},
{"window.chrome.embeddedSearch.newTabPage.checkIsUserSignedIntoChromeAs",
"\"user@email.com\""},
{"window.chrome.embeddedSearch.newTabPage.checkIsUserSyncingHistory",
"\"user@email.com\""},
{"window.chrome.embeddedSearch.newTabPage.deleteMostVisitedItem", "1"},
{"window.chrome.embeddedSearch.newTabPage.deleteMostVisitedItem",
"\"1\""},
{"window.chrome.embeddedSearch.newTabPage.getMostVisitedItemData", "1"},
{"window.chrome.embeddedSearch.newTabPage.logEvent", "1"},
{"window.chrome.embeddedSearch.newTabPage.undoAllMostVisitedDeletions",
""},
{"window.chrome.embeddedSearch.newTabPage.undoMostVisitedDeletion", "1"},
{"window.chrome.embeddedSearch.newTabPage.undoMostVisitedDeletion",
"\"1\""},
};
for (const TestCase& test_case : test_cases) {
// Make sure that the API function exists.
bool result = false;
ASSERT_TRUE(instant_test_utils::GetBoolFromJS(
active_tab, base::StringPrintf("!!%s", test_case.function_name),
&result));
ASSERT_TRUE(result);
// Check that it can be called normally.
EXPECT_TRUE(content::ExecuteScript(
active_tab,
base::StringPrintf("%s(%s)", test_case.function_name, test_case.args)));
// Check that it can be called even after it's assigned to a var, i.e.
// without a "this" binding.
EXPECT_TRUE(content::ExecuteScript(
active_tab,
base::StringPrintf("var f = %s; f(%s)", test_case.function_name,
test_case.args)));
}
}
IN_PROC_BROWSER_TEST_F(LocalNTPTest, EmbeddedSearchAPIEndToEnd) {
content::WebContents* active_tab =
local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
TestMostVisitedObserver observer(
InstantServiceFactory::GetForProfile(browser()->profile()));
// Navigating to an NTP should trigger an update of the MV items.
local_ntp_test_utils::NavigateToNTPAndWaitUntilLoaded(browser());
observer.WaitForNumberOfItems(kDefaultMostVisitedItemCount);
// Make sure the same number of items is available in JS.
int most_visited_count = -1;
ASSERT_TRUE(instant_test_utils::GetIntFromJS(
active_tab, "window.chrome.embeddedSearch.newTabPage.mostVisited.length",
&most_visited_count));
ASSERT_EQ(kDefaultMostVisitedItemCount, most_visited_count);
// Get the ID of one item.
int most_visited_rid = -1;
ASSERT_TRUE(instant_test_utils::GetIntFromJS(
active_tab, "window.chrome.embeddedSearch.newTabPage.mostVisited[0].rid",
&most_visited_rid));
// Delete that item. The deletion should arrive on the native side.
ASSERT_TRUE(content::ExecuteScript(
active_tab,
base::StringPrintf(
"window.chrome.embeddedSearch.newTabPage.deleteMostVisitedItem(%d)",
most_visited_rid)));
observer.WaitForNumberOfItems(kDefaultMostVisitedItemCount - 1);
}
// Regression test for crbug.com/592273.
IN_PROC_BROWSER_TEST_F(LocalNTPTest, EmbeddedSearchAPIAfterDownload) {
// Set up a test server, so we have some URL to download.
net::EmbeddedTestServer test_server(net::EmbeddedTestServer::TYPE_HTTPS);
test_server.ServeFilesFromSourceDirectory("chrome/test/data");
ASSERT_TRUE(test_server.Start());
const GURL download_url = test_server.GetURL("/download-test1.lib");
content::WebContents* active_tab =
local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
TestMostVisitedObserver observer(
InstantServiceFactory::GetForProfile(browser()->profile()));
// Navigating to an NTP should trigger an update of the MV items.
local_ntp_test_utils::NavigateToNTPAndWaitUntilLoaded(browser());
observer.WaitForNumberOfItems(kDefaultMostVisitedItemCount);
// Download some file.
content::DownloadTestObserverTerminal download_observer(
content::BrowserContext::GetDownloadManager(browser()->profile()), 1,
content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_ACCEPT);
ui_test_utils::NavigateToURL(browser(), download_url);
download_observer.WaitForFinished();
// This should have changed the visible URL, but not the last committed one.
ASSERT_EQ(download_url, active_tab->GetVisibleURL());
ASSERT_EQ(GURL(chrome::kChromeUINewTabURL),
active_tab->GetLastCommittedURL());
// Make sure the same number of items is available in JS.
int most_visited_count = -1;
ASSERT_TRUE(instant_test_utils::GetIntFromJS(
active_tab, "window.chrome.embeddedSearch.newTabPage.mostVisited.length",
&most_visited_count));
ASSERT_EQ(kDefaultMostVisitedItemCount, most_visited_count);
// Get the ID of one item.
int most_visited_rid = -1;
ASSERT_TRUE(instant_test_utils::GetIntFromJS(
active_tab, "window.chrome.embeddedSearch.newTabPage.mostVisited[0].rid",
&most_visited_rid));
// Since the current page is still an NTP, it should be possible to delete MV
// items (as well as anything else that the embeddedSearch API allows).
ASSERT_TRUE(content::ExecuteScript(
active_tab,
base::StringPrintf(
"window.chrome.embeddedSearch.newTabPage.deleteMostVisitedItem(%d)",
most_visited_rid)));
observer.WaitForNumberOfItems(kDefaultMostVisitedItemCount - 1);
}
IN_PROC_BROWSER_TEST_F(LocalNTPTest, NTPRespectsBrowserLanguageSetting) {
// If the platform cannot load the French locale (GetApplicationLocale() is
// platform specific, and has been observed to fail on a small number of
// platforms), abort the test.
if (!local_ntp_test_utils::SwitchBrowserLanguageToFrench()) {
LOG(ERROR) << "Failed switching to French language, aborting test.";
return;
}
// Open a new tab.
content::WebContents* active_tab = local_ntp_test_utils::OpenNewTab(
browser(), GURL(chrome::kChromeUINewTabURL));
// Verify that the NTP is in French.
EXPECT_EQ(base::ASCIIToUTF16("Nouvel onglet"), active_tab->GetTitle());
}
IN_PROC_BROWSER_TEST_F(LocalNTPTest, GoogleNTPLoadsWithoutError) {
// Open a new blank tab.
content::WebContents* active_tab =
local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
ASSERT_FALSE(search::IsInstantNTP(active_tab));
// Attach a console observer, listening for any message ("*" pattern).
content::ConsoleObserverDelegate console_observer(active_tab, "*");
active_tab->SetDelegate(&console_observer);
base::HistogramTester histograms;
// Navigate to the NTP.
local_ntp_test_utils::NavigateToNTPAndWaitUntilLoaded(browser());
bool is_google = false;
ASSERT_TRUE(instant_test_utils::GetBoolFromJS(
active_tab, "!!window.configData && !!window.configData.isGooglePage",
&is_google));
EXPECT_TRUE(is_google);
// We shouldn't have gotten any console error messages.
EXPECT_TRUE(console_observer.message().empty()) << console_observer.message();
// Make sure load time metrics were recorded.
histograms.ExpectTotalCount("NewTabPage.LoadTime", 1);
histograms.ExpectTotalCount("NewTabPage.LoadTime.LocalNTP", 1);
histograms.ExpectTotalCount("NewTabPage.LoadTime.LocalNTP.Google", 1);
histograms.ExpectTotalCount("NewTabPage.LoadTime.MostVisited", 1);
histograms.ExpectTotalCount("NewTabPage.TilesReceivedTime", 1);
histograms.ExpectTotalCount("NewTabPage.TilesReceivedTime.LocalNTP", 1);
histograms.ExpectTotalCount("NewTabPage.TilesReceivedTime.MostVisited", 1);
// Make sure impression metrics were recorded. There should be 1 tile, the
// default prepopulated TopSites (see history::PrepopulatedPage).
histograms.ExpectTotalCount("NewTabPage.NumberOfTiles", 1);
histograms.ExpectBucketCount("NewTabPage.NumberOfTiles", 1, 1);
histograms.ExpectTotalCount("NewTabPage.SuggestionsImpression", 1);
histograms.ExpectBucketCount("NewTabPage.SuggestionsImpression", 0, 1);
histograms.ExpectTotalCount("NewTabPage.SuggestionsImpression.client", 1);
// The material design NTP shouldn't have any thumbnails.
histograms.ExpectTotalCount("NewTabPage.SuggestionsImpression.Thumbnail", 0);
histograms.ExpectTotalCount("NewTabPage.TileTitle", 1);
histograms.ExpectTotalCount("NewTabPage.TileTitle.client", 1);
histograms.ExpectTotalCount("NewTabPage.TileType", 1);
histograms.ExpectTotalCount("NewTabPage.TileType.client", 1);
}
IN_PROC_BROWSER_TEST_F(LocalNTPTest, NonGoogleNTPLoadsWithoutError) {
local_ntp_test_utils::SetUserSelectedDefaultSearchProvider(
browser()->profile(), "https://www.example.com",
/*ntp_url=*/"");
// Open a new blank tab.
content::WebContents* active_tab =
local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
ASSERT_FALSE(search::IsInstantNTP(active_tab));
// Attach a console observer, listening for any message ("*" pattern).
content::ConsoleObserverDelegate console_observer(active_tab, "*");
active_tab->SetDelegate(&console_observer);
base::HistogramTester histograms;
// Navigate to the NTP.
local_ntp_test_utils::NavigateToNTPAndWaitUntilLoaded(browser());
bool is_google = false;
ASSERT_TRUE(instant_test_utils::GetBoolFromJS(
active_tab, "!!window.configData && !!window.configData.isGooglePage",
&is_google));
EXPECT_FALSE(is_google);
// We shouldn't have gotten any console error messages.
EXPECT_TRUE(console_observer.message().empty()) << console_observer.message();
// Make sure load time metrics were recorded.
histograms.ExpectTotalCount("NewTabPage.LoadTime", 1);
histograms.ExpectTotalCount("NewTabPage.LoadTime.LocalNTP", 1);
histograms.ExpectTotalCount("NewTabPage.LoadTime.LocalNTP.Other", 1);
histograms.ExpectTotalCount("NewTabPage.LoadTime.MostVisited", 1);
histograms.ExpectTotalCount("NewTabPage.TilesReceivedTime", 1);
histograms.ExpectTotalCount("NewTabPage.TilesReceivedTime.LocalNTP", 1);
histograms.ExpectTotalCount("NewTabPage.TilesReceivedTime.MostVisited", 1);
// Make sure impression metrics were recorded. There should be 1 tile, the
// default prepopulated TopSites (see history::PrepopulatedPage).
histograms.ExpectTotalCount("NewTabPage.NumberOfTiles", 1);
histograms.ExpectBucketCount("NewTabPage.NumberOfTiles", 1, 1);
histograms.ExpectTotalCount("NewTabPage.SuggestionsImpression", 1);
histograms.ExpectBucketCount("NewTabPage.SuggestionsImpression", 0, 1);
histograms.ExpectTotalCount("NewTabPage.SuggestionsImpression.client", 1);
// The material design NTP shouldn't have any thumbnails.
histograms.ExpectTotalCount("NewTabPage.SuggestionsImpression.Thumbnail", 0);
histograms.ExpectTotalCount("NewTabPage.TileTitle", 1);
histograms.ExpectTotalCount("NewTabPage.TileTitle.client", 1);
histograms.ExpectTotalCount("NewTabPage.TileType", 1);
histograms.ExpectTotalCount("NewTabPage.TileType.client", 1);
}
IN_PROC_BROWSER_TEST_F(LocalNTPTest, FrenchGoogleNTPLoadsWithoutError) {
if (!local_ntp_test_utils::SwitchBrowserLanguageToFrench()) {
LOG(ERROR) << "Failed switching to French language, aborting test.";
return;
}
// Open a new blank tab.
content::WebContents* active_tab =
local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
ASSERT_FALSE(search::IsInstantNTP(active_tab));
// Attach a console observer, listening for any message ("*" pattern).
content::ConsoleObserverDelegate console_observer(active_tab, "*");
active_tab->SetDelegate(&console_observer);
// Navigate to the NTP and make sure it's actually in French.
local_ntp_test_utils::NavigateToNTPAndWaitUntilLoaded(browser());
ASSERT_EQ(base::ASCIIToUTF16("Nouvel onglet"), active_tab->GetTitle());
// We shouldn't have gotten any console error messages.
EXPECT_TRUE(console_observer.message().empty()) << console_observer.message();
}
IN_PROC_BROWSER_TEST_F(LocalNTPTest, LoadsMDIframe) {
content::WebContents* active_tab =
local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
local_ntp_test_utils::NavigateToNTPAndWaitUntilLoaded(browser());
// Get the Most Visited iframe and check that the tiles loaded correctly.
content::RenderFrameHost* iframe = GetIframe(active_tab, kMostVisitedIframe);
// Get the total number of (non-empty) tiles from the iframe.
int total_favicons = 0;
ASSERT_TRUE(instant_test_utils::GetIntFromJS(
iframe, "document.querySelectorAll('.md-icon').length", &total_favicons));
// Also get how many of the tiles succeeded and failed in loading their
// favicon images.
int succeeded_favicons = 0;
ASSERT_TRUE(instant_test_utils::GetIntFromJS(
iframe, "document.querySelectorAll('.md-icon img').length",
&succeeded_favicons));
int failed_favicons = 0;
ASSERT_TRUE(instant_test_utils::GetIntFromJS(
iframe, "document.querySelectorAll('.md-icon.failed-favicon').length",
&failed_favicons));
// Check if only one add button exists in the frame. This will be included in
// the total favicon count.
int add_button_favicon = 0;
ASSERT_TRUE(instant_test_utils::GetIntFromJS(
iframe, "document.querySelectorAll('.md-add-icon').length",
&add_button_favicon));
EXPECT_EQ(1, add_button_favicon);
// First, sanity check that the numbers line up (none of the css classes was
// renamed, etc).
EXPECT_EQ(total_favicons,
succeeded_favicons + add_button_favicon + failed_favicons);
// Since we're in a non-signed-in, fresh profile with no history, there should
// be the default TopSites tiles (see history::PrepopulatedPage).
// Check that there is at least one tile, and that all of them loaded their
// images successfully.
EXPECT_EQ(total_favicons, succeeded_favicons + add_button_favicon);
EXPECT_EQ(0, failed_favicons);
}
IN_PROC_BROWSER_TEST_F(LocalNTPTest, DontShowAddCustomLinkButtonWhenMaxLinks) {
content::WebContents* active_tab =
local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
TestMostVisitedObserver observer(
InstantServiceFactory::GetForProfile(browser()->profile()));
local_ntp_test_utils::NavigateToNTPAndWaitUntilLoaded(browser());
observer.WaitForNumberOfItems(kDefaultMostVisitedItemCount);
// Get the Most Visited iframe and add to maximum number of tiles.
content::RenderFrameHost* iframe = GetIframe(active_tab, kMostVisitedIframe);
for (int i = kDefaultMostVisitedItemCount; i < kDefaultCustomLinkMaxCount;
++i) {
std::string rid = std::to_string(i + 100);
std::string url = "https://" + rid + ".com";
std::string title = "url for " + rid;
// Add most visited tiles via the EmbeddedSearch API. rid = -1 means add new
// most visited tile.
local_ntp_test_utils::ExecuteScriptOnNTPAndWaitUntilLoaded(
iframe,
"window.chrome.embeddedSearch.newTabPage.updateCustomLink(-1, '" + url +
"', '" + title + "')");
}
// Confirm that there are max number of custom link tiles.
observer.WaitForNumberOfItems(kDefaultCustomLinkMaxCount);
// Check there is no add button in the iframe. Make sure not to select from
// old tiles that are in the process of being deleted.
bool no_add_button = false;
ASSERT_TRUE(instant_test_utils::GetBoolFromJS(
iframe,
"document.querySelectorAll('#mv-tiles .md-add-icon').length === 0",
&no_add_button));
EXPECT_TRUE(no_add_button);
}
IN_PROC_BROWSER_TEST_F(LocalNTPTest, ReorderCustomLinks) {
content::WebContents* active_tab =
local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
TestMostVisitedObserver observer(
InstantServiceFactory::GetForProfile(browser()->profile()));
local_ntp_test_utils::NavigateToNTPAndWaitUntilLoaded(browser());
observer.WaitForNumberOfItems(kDefaultMostVisitedItemCount);
// Fill tiles up to the maximum count.
content::RenderFrameHost* iframe = GetIframe(active_tab, kMostVisitedIframe);
for (int i = kDefaultMostVisitedItemCount; i < kDefaultCustomLinkMaxCount;
++i) {
std::string rid = std::to_string(i + 100);
std::string url = "https://" + rid + ".com";
std::string title = "url for " + rid;
local_ntp_test_utils::ExecuteScriptOnNTPAndWaitUntilLoaded(
iframe,
"window.chrome.embeddedSearch.newTabPage.updateCustomLink(-1, '" + url +
"', '" + title + "')");
}
// Confirm that there are max number of custom link tiles.
observer.WaitForNumberOfItems(kDefaultCustomLinkMaxCount);
// Get the title of the tile at index 1. Make sure not to select from old
// tiles that are in the process of being deleted.
std::string title;
ASSERT_TRUE(instant_test_utils::GetStringFromJS(
iframe, "document.querySelectorAll('#mv-tiles .md-title')[1].innerText",
&title));
// Move the tile to the front.
std::string tid;
ASSERT_TRUE(instant_test_utils::GetStringFromJS(
iframe,
"document.querySelectorAll('#mv-tiles "
".md-tile')[1].getAttribute('data-tid')",
&tid));
local_ntp_test_utils::ExecuteScriptOnNTPAndWaitUntilLoaded(
iframe, "window.chrome.embeddedSearch.newTabPage.reorderCustomLink(" +
tid + ", 0)");
// Check that the first tile is the tile that was moved.
std::string new_title;
ASSERT_TRUE(instant_test_utils::GetStringFromJS(
iframe, "document.querySelectorAll('#mv-tiles .md-title')[0].innerText",
&new_title));
EXPECT_EQ(new_title, title);
// Move the tile again to the end.
std::string end_index = std::to_string(kDefaultCustomLinkMaxCount - 1);
ASSERT_TRUE(instant_test_utils::GetStringFromJS(
iframe,
"document.querySelectorAll('#mv-tiles "
".md-tile')[0].getAttribute('data-tid')",
&tid));
local_ntp_test_utils::ExecuteScriptOnNTPAndWaitUntilLoaded(
iframe, "window.chrome.embeddedSearch.newTabPage.reorderCustomLink(" +
tid + ", " + end_index + ")");
// Check that the last tile is the tile that was moved.
new_title = std::string();
ASSERT_TRUE(instant_test_utils::GetStringFromJS(
iframe,
"document.querySelectorAll('#mv-tiles .md-title')[" + end_index +
"].innerText",
&new_title));
EXPECT_EQ(new_title, title);
}
class LocalNTPRTLTest : public LocalNTPTest {
public:
LocalNTPRTLTest() {}
private:
void SetUpCommandLine(base::CommandLine* cmdline) override {
cmdline->AppendSwitchASCII(switches::kForceUIDirection,
switches::kForceDirectionRTL);
}
};
IN_PROC_BROWSER_TEST_F(LocalNTPRTLTest, RightToLeft) {
// Open an NTP.
content::WebContents* active_tab = local_ntp_test_utils::OpenNewTab(
browser(), GURL(chrome::kChromeUINewTabURL));
ASSERT_TRUE(search::IsInstantNTP(active_tab));
// Check that the "dir" attribute on the main "html" element says "rtl".
std::string dir;
ASSERT_TRUE(instant_test_utils::GetStringFromJS(
active_tab, "document.documentElement.dir", &dir));
EXPECT_EQ("rtl", dir);
}
// Tests that dark mode styling is properly applied to the local NTP.
class LocalNTPDarkModeTest : public LocalNTPTest {
public:
LocalNTPDarkModeTest() {}
protected:
ui::TestNativeTheme* theme() { return &theme_; }
// Returns true if dark mode is applied on the |frame|.
bool GetIsDarkModeApplied(const content::ToRenderFrameHost& frame) {
bool dark_mode_applied = false;
if (instant_test_utils::GetBoolFromJS(
frame,
"document.documentElement.getAttribute('darkmode') === 'true'",
&dark_mode_applied)) {
return dark_mode_applied;
}
return false;
}
// Returns true if dark mode is applied to the Most Visited icon at |index|
// (i.e. the icon URL contains the |kMVIconDarkParameter|).
bool GetIsDarkTile(const content::ToRenderFrameHost& frame, int index) {
bool dark_tile = false;
if (instant_test_utils::GetBoolFromJS(
frame,
base::StringPrintf(
"document.querySelectorAll('#mv-tiles .md-icon img')[%d]"
".src.includes('%s')",
index, kMVIconDarkParameter),
&dark_tile)) {
return dark_tile;
}
return false;
}
private:
ui::TestNativeTheme theme_;
};
IN_PROC_BROWSER_TEST_F(LocalNTPDarkModeTest, ToggleDarkMode) {
// Initially disable dark mode.
InstantService* instant_service =
InstantServiceFactory::GetForProfile(browser()->profile());
theme()->SetDarkMode(false);
instant_service->SetDarkModeThemeForTesting(theme());
content::WebContents* active_tab =
local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
local_ntp_test_utils::NavigateToNTPAndWaitUntilLoaded(browser());
content::RenderFrameHost* mv_iframe =
GetIframe(active_tab, kMostVisitedIframe);
content::RenderFrameHost* cl_iframe =
GetIframe(active_tab, kEditCustomLinkIframe);
// Dark mode should not be applied to the main page, iframes, and Most Visited
// icons.
ASSERT_FALSE(GetIsDarkModeApplied(active_tab));
ASSERT_FALSE(GetIsDarkModeApplied(mv_iframe));
ASSERT_FALSE(GetIsDarkModeApplied(cl_iframe));
for (int i = 0; i < kDefaultMostVisitedItemCount; ++i) {
ASSERT_FALSE(GetIsDarkTile(mv_iframe, i));
}
// Enable dark mode and wait until the MV tiles have updated.
content::DOMMessageQueue msg_queue(active_tab);
theme()->SetDarkMode(true);
theme()->NotifyObservers();
local_ntp_test_utils::WaitUntilTilesLoaded(active_tab, &msg_queue,
/*delay=*/0);
// Check that dark mode has been properly applied.
EXPECT_TRUE(GetIsDarkModeApplied(active_tab));
EXPECT_TRUE(GetIsDarkModeApplied(mv_iframe));
EXPECT_TRUE(GetIsDarkModeApplied(cl_iframe));
for (int i = 0; i < kDefaultMostVisitedItemCount; ++i) {
EXPECT_TRUE(GetIsDarkTile(mv_iframe, i));
}
// Disable dark mode and wait until the MV tiles have updated.
msg_queue.ClearQueue();
theme()->SetDarkMode(false);
theme()->NotifyObservers();
local_ntp_test_utils::WaitUntilTilesLoaded(active_tab, &msg_queue,
/*delay=*/0);
// Check that dark mode has been removed.
EXPECT_FALSE(GetIsDarkModeApplied(active_tab));
EXPECT_FALSE(GetIsDarkModeApplied(mv_iframe));
EXPECT_FALSE(GetIsDarkModeApplied(cl_iframe));
for (int i = 0; i < kDefaultMostVisitedItemCount; ++i) {
EXPECT_FALSE(GetIsDarkTile(mv_iframe, i));
}
}
// Tests that dark mode styling is properly applied to the local NTP on start-
// up. The test parameter controls whether dark mode is initially enabled or
// disabled.
class LocalNTPDarkModeStartupTest : public LocalNTPDarkModeTest,
public testing::WithParamInterface<bool> {
public:
LocalNTPDarkModeStartupTest() {}
bool DarkModeEnabled() { return GetParam(); }
private:
void SetUpOnMainThread() override {
LocalNTPTest::SetUpOnMainThread();
InstantService* instant_service =
InstantServiceFactory::GetForProfile(browser()->profile());
theme()->SetDarkMode(GetParam());
instant_service->SetDarkModeThemeForTesting(theme());
}
};
IN_PROC_BROWSER_TEST_P(LocalNTPDarkModeStartupTest, DarkModeApplied) {
const bool kDarkModeEnabled = DarkModeEnabled();
content::WebContents* active_tab =
local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
local_ntp_test_utils::NavigateToNTPAndWaitUntilLoaded(browser());
content::RenderFrameHost* mv_iframe =
GetIframe(active_tab, kMostVisitedIframe);
content::RenderFrameHost* cl_iframe =
GetIframe(active_tab, kEditCustomLinkIframe);
// Check that dark mode, if enabled, has been properly applied to the main
// page, iframes, and Most Visited icons.
EXPECT_EQ(kDarkModeEnabled, GetIsDarkModeApplied(active_tab));
EXPECT_EQ(kDarkModeEnabled, GetIsDarkModeApplied(mv_iframe));
EXPECT_EQ(kDarkModeEnabled, GetIsDarkModeApplied(cl_iframe));
for (int i = 0; i < kDefaultMostVisitedItemCount; ++i) {
EXPECT_EQ(kDarkModeEnabled, GetIsDarkTile(mv_iframe, i));
}
}
INSTANTIATE_TEST_SUITE_P(, LocalNTPDarkModeStartupTest, testing::Bool());
// A minimal implementation of an interstitial page.
class TestInterstitialPageDelegate : public content::InterstitialPageDelegate {
public:
static void Show(content::WebContents* web_contents, const GURL& url) {
// The InterstitialPage takes ownership of this object, and will delete it
// when it gets destroyed itself.
new TestInterstitialPageDelegate(web_contents, url);
}
~TestInterstitialPageDelegate() override {}
private:
TestInterstitialPageDelegate(content::WebContents* web_contents,
const GURL& url) {
// |page| takes ownership of |this|.
content::InterstitialPage* page =
content::InterstitialPage::Create(web_contents, true, url, this);
page->Show();
}
std::string GetHTMLContents() override { return "<html></html>"; }
DISALLOW_COPY_AND_ASSIGN(TestInterstitialPageDelegate);
};
// A navigation throttle that will create an interstitial for all pages except
// chrome-search:// ones (i.e. the local NTP).
class TestNavigationThrottle : public content::NavigationThrottle {
public:
explicit TestNavigationThrottle(content::NavigationHandle* handle)
: content::NavigationThrottle(handle), weak_ptr_factory_(this) {}
static std::unique_ptr<NavigationThrottle> Create(
content::NavigationHandle* handle) {
return std::make_unique<TestNavigationThrottle>(handle);
}
private:
ThrottleCheckResult WillStartRequest() override {
const GURL& url = navigation_handle()->GetURL();
if (url.SchemeIs(chrome::kChromeSearchScheme)) {
return NavigationThrottle::PROCEED;
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindRepeating(&TestNavigationThrottle::ShowInterstitial,
weak_ptr_factory_.GetWeakPtr()));
return NavigationThrottle::DEFER;
}
const char* GetNameForLogging() override { return "TestNavigationThrottle"; }
void ShowInterstitial() {
TestInterstitialPageDelegate::Show(navigation_handle()->GetWebContents(),
navigation_handle()->GetURL());
}
base::WeakPtrFactory<TestNavigationThrottle> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(TestNavigationThrottle);
};
IN_PROC_BROWSER_TEST_F(LocalNTPTest, InterstitialsAreNotNTPs) {
// Set up a test server, so we have some non-NTP URL to navigate to.
net::EmbeddedTestServer test_server(net::EmbeddedTestServer::TYPE_HTTPS);
test_server.ServeFilesFromSourceDirectory("chrome/test/data");
ASSERT_TRUE(test_server.Start());
content::WebContents* active_tab =
local_ntp_test_utils::OpenNewTab(browser(), GURL("about:blank"));
content::TestNavigationThrottleInserter throttle_inserter(
active_tab, base::BindRepeating(&TestNavigationThrottle::Create));
local_ntp_test_utils::NavigateToNTPAndWaitUntilLoaded(browser());
ASSERT_TRUE(search::IsInstantNTP(active_tab));
// Navigate to some non-NTP URL, which will result in an interstitial.
const GURL blocked_url = test_server.GetURL("/simple.html");
ui_test_utils::NavigateToURL(browser(), blocked_url);
content::WaitForInterstitialAttach(active_tab);
ASSERT_TRUE(active_tab->ShowingInterstitialPage());
ASSERT_EQ(blocked_url, active_tab->GetVisibleURL());
// The interstitial is not an NTP (even though the committed URL may still
// point to an NTP, see crbug.com/448486).
EXPECT_FALSE(search::IsInstantNTP(active_tab));
// Go back to the NTP.
chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
content::WaitForInterstitialDetach(active_tab);
// Now the page should be an NTP again.
EXPECT_TRUE(search::IsInstantNTP(active_tab));
}
} // namespace