blob: 2eb57024ea09d835e8bfa9ea4a4ce2b9338788df [file] [log] [blame]
// Copyright (c) 2006-2009 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 "build/build_config.h"
#include "chrome/browser/dom_ui/new_tab_ui.h"
#include <set>
#include "app/l10n_util.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/histogram.h"
#include "base/singleton.h"
#include "base/thread.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/chrome_thread.h"
#include "chrome/browser/dom_ui/dom_ui_theme_source.h"
#include "chrome/browser/dom_ui/most_visited_handler.h"
#include "chrome/browser/dom_ui/new_tab_page_sync_handler.h"
#include "chrome/browser/dom_ui/ntp_resource_cache.h"
#include "chrome/browser/dom_ui/shown_sections_handler.h"
#include "chrome/browser/dom_ui/tips_handler.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/renderer_host/render_view_host.h"
#include "chrome/browser/sessions/session_types.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "chrome/browser/sessions/tab_restore_service.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/pref_service.h"
#include "chrome/common/url_constants.h"
#include "grit/generated_resources.h"
namespace {
// The number of recent bookmarks we show.
const int kRecentBookmarks = 9;
// The number of search URLs to show.
const int kSearchURLs = 3;
// Strings sent to the page via jstemplates used to set the direction of the
// HTML document based on locale.
const wchar_t kRTLHtmlTextDirection[] = L"rtl";
const wchar_t kDefaultHtmlTextDirection[] = L"ltr";
////////////////////////////////////////////////////////////////////////////////
// PaintTimer
// To measure end-to-end performance of the new tab page, we observe paint
// messages and wait for the page to stop repainting.
class PaintTimer : public RenderWidgetHost::PaintObserver {
public:
PaintTimer() {
Start();
}
// Start the benchmarking and the timer.
void Start() {
start_ = base::TimeTicks::Now();
last_paint_ = start_;
timer_.Start(base::TimeDelta::FromMilliseconds(kTimeoutMs), this,
&PaintTimer::Timeout);
}
// A callback that is invoked whenever our RenderWidgetHost paints.
virtual void RenderWidgetHostDidPaint(RenderWidgetHost* rwh) {
last_paint_ = base::TimeTicks::Now();
}
// The timer callback. If enough time has elapsed since the last paint
// message, we say we're done painting; otherwise, we keep waiting.
void Timeout() {
base::TimeTicks now = base::TimeTicks::Now();
if ((now - last_paint_) >= base::TimeDelta::FromMilliseconds(kTimeoutMs)) {
// Painting has quieted down. Log this as the full time to run.
base::TimeDelta load_time = last_paint_ - start_;
int load_time_ms = static_cast<int>(load_time.InMilliseconds());
NotificationService::current()->Notify(
NotificationType::INITIAL_NEW_TAB_UI_LOAD,
NotificationService::AllSources(),
Details<int>(&load_time_ms));
UMA_HISTOGRAM_TIMES("NewTabUI load", load_time);
} else {
// Not enough quiet time has elapsed.
// Some more paints must've occurred since we set the timeout.
// Wait some more.
timer_.Start(base::TimeDelta::FromMilliseconds(kTimeoutMs), this,
&PaintTimer::Timeout);
}
}
private:
// The amount of time there must be no painting for us to consider painting
// finished. Observed times are in the ~1200ms range on Windows.
static const int kTimeoutMs = 2000;
// The time when we started benchmarking.
base::TimeTicks start_;
// The last time we got a paint notification.
base::TimeTicks last_paint_;
// Scoping so we can be sure our timeouts don't outlive us.
base::OneShotTimer<PaintTimer> timer_;
DISALLOW_COPY_AND_ASSIGN(PaintTimer);
};
///////////////////////////////////////////////////////////////////////////////
// PromotionalMessageHandler
class PromotionalMessageHandler : public DOMMessageHandler {
public:
PromotionalMessageHandler() {}
virtual ~PromotionalMessageHandler() {}
// DOMMessageHandler implementation.
virtual void RegisterMessages();
// Zero promotional message counter.
void HandleClosePromotionalMessage(const Value* content);
private:
DISALLOW_COPY_AND_ASSIGN(PromotionalMessageHandler);
};
void PromotionalMessageHandler::RegisterMessages() {
dom_ui_->RegisterMessageCallback("stopPromoLineMessage",
NewCallback(this,
&PromotionalMessageHandler::HandleClosePromotionalMessage));
}
void PromotionalMessageHandler::HandleClosePromotionalMessage(
const Value* content) {
dom_ui_->GetProfile()->GetPrefs()->SetInteger(
prefs::kNTPPromoLineRemaining, 0);
}
///////////////////////////////////////////////////////////////////////////////
// RecentlyClosedTabsHandler
class RecentlyClosedTabsHandler : public DOMMessageHandler,
public TabRestoreService::Observer {
public:
RecentlyClosedTabsHandler() : tab_restore_service_(NULL) {}
virtual ~RecentlyClosedTabsHandler();
// DOMMessageHandler implementation.
virtual void RegisterMessages();
// Callback for the "reopenTab" message. Rewrites the history of the
// currently displayed tab to be the one in TabRestoreService with a
// history of a session passed in through the content pointer.
void HandleReopenTab(const Value* content);
// Callback for the "getRecentlyClosedTabs" message.
void HandleGetRecentlyClosedTabs(const Value* content);
// Observer callback for TabRestoreService::Observer. Sends data on
// recently closed tabs to the javascript side of this page to
// display to the user.
virtual void TabRestoreServiceChanged(TabRestoreService* service);
// Observer callback to notice when our associated TabRestoreService
// is destroyed.
virtual void TabRestoreServiceDestroyed(TabRestoreService* service);
private:
// Converts a closed tab to the value sent down to the NTP. Returns true on
// success, false if the value shouldn't be sent down.
bool TabToValue(const TabRestoreService::Tab& tab,
DictionaryValue* dictionary);
// Converts a closed window to the value sent down to the NTP. Returns true
// on success, false if the value shouldn't be sent down.
bool WindowToValue(const TabRestoreService::Window& window,
DictionaryValue* dictionary);
// Adds tab to unique_items list if it is not present. Returns false if
// tab was already in the list, true if it was absent. A tab is
// considered unique if no other tab shares both its title and its url.
bool EnsureTabIsUnique(const DictionaryValue* tab,
std::set<std::wstring>& unique_items);
// TabRestoreService that we are observing.
TabRestoreService* tab_restore_service_;
DISALLOW_COPY_AND_ASSIGN(RecentlyClosedTabsHandler);
};
void RecentlyClosedTabsHandler::RegisterMessages() {
dom_ui_->RegisterMessageCallback("getRecentlyClosedTabs",
NewCallback(this,
&RecentlyClosedTabsHandler::HandleGetRecentlyClosedTabs));
dom_ui_->RegisterMessageCallback("reopenTab",
NewCallback(this, &RecentlyClosedTabsHandler::HandleReopenTab));
}
RecentlyClosedTabsHandler::~RecentlyClosedTabsHandler() {
if (tab_restore_service_)
tab_restore_service_->RemoveObserver(this);
}
void RecentlyClosedTabsHandler::HandleReopenTab(const Value* content) {
Browser* browser = Browser::GetBrowserForController(
&dom_ui_->tab_contents()->controller(), NULL);
if (!browser)
return;
// Extract the integer value of the tab session to restore from the
// incoming string array. This will be greatly simplified when
// DOMUIBindings::send() is generalized to all data types instead of
// silently failing when passed anything other then an array of
// strings.
if (content->GetType() == Value::TYPE_LIST) {
const ListValue* list_value = static_cast<const ListValue*>(content);
Value* list_member;
if (list_value->Get(0, &list_member) &&
list_member->GetType() == Value::TYPE_STRING) {
const StringValue* string_value =
static_cast<const StringValue*>(list_member);
std::wstring wstring_value;
if (string_value->GetAsString(&wstring_value)) {
int session_to_restore = StringToInt(WideToUTF16Hack(wstring_value));
tab_restore_service_->RestoreEntryById(browser, session_to_restore,
true);
// The current tab has been nuked at this point; don't touch any member
// variables.
}
}
}
}
void RecentlyClosedTabsHandler::HandleGetRecentlyClosedTabs(
const Value* content) {
if (!tab_restore_service_) {
tab_restore_service_ = dom_ui_->GetProfile()->GetTabRestoreService();
// GetTabRestoreService() can return NULL (i.e., when in Off the
// Record mode)
if (tab_restore_service_) {
// This does nothing if the tabs have already been loaded or they
// shouldn't be loaded.
tab_restore_service_->LoadTabsFromLastSession();
tab_restore_service_->AddObserver(this);
}
}
if (tab_restore_service_)
TabRestoreServiceChanged(tab_restore_service_);
}
void RecentlyClosedTabsHandler::TabRestoreServiceChanged(
TabRestoreService* service) {
const TabRestoreService::Entries& entries = service->entries();
ListValue list_value;
std::set<std::wstring> unique_items;
int added_count = 0;
const int max_count = 10;
// We filter the list of recently closed to only show 'interesting' entries,
// where an interesting entry is either a closed window or a closed tab
// whose selected navigation is not the new tab ui.
for (TabRestoreService::Entries::const_iterator it = entries.begin();
it != entries.end() && added_count < max_count; ++it) {
TabRestoreService::Entry* entry = *it;
DictionaryValue* value = new DictionaryValue();
if ((entry->type == TabRestoreService::TAB &&
TabToValue(*static_cast<TabRestoreService::Tab*>(entry), value) &&
EnsureTabIsUnique(value, unique_items)) ||
(entry->type == TabRestoreService::WINDOW &&
WindowToValue(*static_cast<TabRestoreService::Window*>(entry),
value))) {
value->SetInteger(L"sessionId", entry->id);
list_value.Append(value);
added_count++;
} else {
delete value;
}
}
dom_ui_->CallJavascriptFunction(L"recentlyClosedTabs", list_value);
}
void RecentlyClosedTabsHandler::TabRestoreServiceDestroyed(
TabRestoreService* service) {
tab_restore_service_ = NULL;
}
bool RecentlyClosedTabsHandler::TabToValue(
const TabRestoreService::Tab& tab,
DictionaryValue* dictionary) {
if (tab.navigations.empty())
return false;
const TabNavigation& current_navigation =
tab.navigations.at(tab.current_navigation_index);
if (current_navigation.url() == GURL(chrome::kChromeUINewTabURL))
return false;
NewTabUI::SetURLTitleAndDirection(dictionary, current_navigation.title(),
current_navigation.url());
dictionary->SetString(L"type", L"tab");
dictionary->SetReal(L"timestamp", tab.timestamp.ToDoubleT());
return true;
}
bool RecentlyClosedTabsHandler::WindowToValue(
const TabRestoreService::Window& window,
DictionaryValue* dictionary) {
if (window.tabs.empty()) {
NOTREACHED();
return false;
}
ListValue* tab_values = new ListValue();
for (size_t i = 0; i < window.tabs.size(); ++i) {
DictionaryValue* tab_value = new DictionaryValue();
if (TabToValue(window.tabs[i], tab_value))
tab_values->Append(tab_value);
else
delete tab_value;
}
if (tab_values->GetSize() == 0) {
delete tab_values;
return false;
}
dictionary->SetString(L"type", L"window");
dictionary->SetReal(L"timestamp", window.timestamp.ToDoubleT());
dictionary->Set(L"tabs", tab_values);
return true;
}
bool RecentlyClosedTabsHandler::EnsureTabIsUnique(const DictionaryValue* tab,
std::set<std::wstring>& unique_items) {
std::wstring title;
std::wstring url;
if (tab->GetString(L"title", &title) &&
tab->GetString(L"url", &url)) {
std::wstring unique_key = title + url;
if (unique_items.find(unique_key) != unique_items.end())
return false;
else
unique_items.insert(unique_key);
}
return true;
}
///////////////////////////////////////////////////////////////////////////////
// MetricsHandler
// Let the page contents record UMA actions. Only use when you can't do it from
// C++. For example, we currently use it to let the NTP log the postion of the
// Most Visited or Bookmark the user clicked on, as we don't get that
// information through RequestOpenURL. You will need to update the metrics
// dashboard with the action names you use, as our processor won't catch that
// information (treat it as RecordComputedMetrics)
class MetricsHandler : public DOMMessageHandler {
public:
MetricsHandler() {}
virtual ~MetricsHandler() {}
// DOMMessageHandler implementation.
virtual void RegisterMessages();
// Callback which records a user action.
void HandleMetrics(const Value* content);
// Callback for the "logEventTime" message.
void HandleLogEventTime(const Value* content);
private:
DISALLOW_COPY_AND_ASSIGN(MetricsHandler);
};
void MetricsHandler::RegisterMessages() {
dom_ui_->RegisterMessageCallback("metrics",
NewCallback(this, &MetricsHandler::HandleMetrics));
dom_ui_->RegisterMessageCallback("logEventTime",
NewCallback(this, &MetricsHandler::HandleLogEventTime));
}
void MetricsHandler::HandleMetrics(const Value* content) {
if (content && content->GetType() == Value::TYPE_LIST) {
const ListValue* list_value = static_cast<const ListValue*>(content);
Value* list_member;
if (list_value->Get(0, &list_member) &&
list_member->GetType() == Value::TYPE_STRING) {
const StringValue* string_value =
static_cast<const StringValue*>(list_member);
std::wstring wstring_value;
if (string_value->GetAsString(&wstring_value)) {
UserMetrics::RecordComputedAction(WideToASCII(wstring_value),
dom_ui_->GetProfile());
}
}
}
}
void MetricsHandler::HandleLogEventTime(const Value* content) {
if (content && content->GetType() == Value::TYPE_LIST) {
const ListValue* list_value = static_cast<const ListValue*>(content);
Value* list_member;
if (list_value->Get(0, &list_member) &&
list_member->GetType() == Value::TYPE_STRING) {
std::string event_name;
if (list_member->GetAsString(&event_name)) {
dom_ui_->tab_contents()->LogNewTabTime(event_name);
}
}
}
}
///////////////////////////////////////////////////////////////////////////////
// NewTabPageSetHomePageHandler
// Sets the new tab page as home page when user clicks on "make this my home
// page" link.
class NewTabPageSetHomePageHandler : public DOMMessageHandler {
public:
NewTabPageSetHomePageHandler() {}
virtual ~NewTabPageSetHomePageHandler() {}
// DOMMessageHandler implementation.
virtual void RegisterMessages();
// Callback for "setHomePage".
void HandleSetHomePage(const Value* value);
private:
DISALLOW_COPY_AND_ASSIGN(NewTabPageSetHomePageHandler);
};
void NewTabPageSetHomePageHandler::RegisterMessages() {
dom_ui_->RegisterMessageCallback("setHomePage", NewCallback(
this, &NewTabPageSetHomePageHandler::HandleSetHomePage));
}
void NewTabPageSetHomePageHandler::HandleSetHomePage(
const Value* value) {
dom_ui_->GetProfile()->GetPrefs()->SetBoolean(prefs::kHomePageIsNewTabPage,
true);
ListValue list_value;
list_value.Append(new StringValue(
l10n_util::GetString(IDS_NEW_TAB_HOME_PAGE_SET_NOTIFICATION)));
list_value.Append(new StringValue(
l10n_util::GetString(IDS_NEW_TAB_HOME_PAGE_HIDE_NOTIFICATION)));
dom_ui_->CallJavascriptFunction(L"onHomePageSet", list_value);
}
} // namespace
///////////////////////////////////////////////////////////////////////////////
// NewTabUI
NewTabUI::NewTabUI(TabContents* contents)
: DOMUI(contents) {
// Override some options on the DOM UI.
hide_favicon_ = true;
force_bookmark_bar_visible_ = true;
force_extension_shelf_visible_ = true;
focus_location_bar_by_default_ = true;
should_hide_url_ = true;
overridden_title_ = WideToUTF16Hack(l10n_util::GetString(IDS_NEW_TAB_TITLE));
// We count all link clicks as AUTO_BOOKMARK, so that site can be ranked more
// highly. Note this means we're including clicks on not only most visited
// thumbnails, but also clicks on recently bookmarked.
link_transition_type_ = PageTransition::AUTO_BOOKMARK;
if (NewTabUI::FirstRunDisabled())
NewTabHTMLSource::set_first_run(false);
static bool first_view = true;
if (first_view) {
Profile* profile = GetProfile();
// Decrement ntp promo counters; the default values are specified in
// Browser::RegisterUserPrefs.
profile->GetPrefs()->SetInteger(prefs::kNTPPromoLineRemaining,
profile->GetPrefs()->GetInteger(prefs::kNTPPromoLineRemaining) - 1);
profile->GetPrefs()->SetInteger(prefs::kNTPPromoImageRemaining,
profile->GetPrefs()->GetInteger(prefs::kNTPPromoImageRemaining) - 1);
first_view = false;
}
if (!GetProfile()->IsOffTheRecord()) {
AddMessageHandler((new ShownSectionsHandler())->Attach(this));
AddMessageHandler((new MostVisitedHandler())->Attach(this));
AddMessageHandler((new RecentlyClosedTabsHandler())->Attach(this));
AddMessageHandler((new MetricsHandler())->Attach(this));
if (WebResourcesEnabled())
AddMessageHandler((new TipsHandler())->Attach(this));
if (ProfileSyncService::IsSyncEnabled()) {
AddMessageHandler((new NewTabPageSyncHandler())->Attach(this));
}
AddMessageHandler((new NewTabPageSetHomePageHandler())->Attach(this));
AddMessageHandler((new PromotionalMessageHandler())->Attach(this));
}
// Initializing the CSS and HTML can require some CPU, so do it after
// we've hooked up the most visited handler. This allows the DB query
// for the new tab thumbs to happen earlier.
InitializeCSSCaches();
NewTabHTMLSource* html_source =
new NewTabHTMLSource(GetProfile()->GetOriginalProfile());
ChromeThread::PostTask(
ChromeThread::IO, FROM_HERE,
NewRunnableMethod(
Singleton<ChromeURLDataManager>::get(),
&ChromeURLDataManager::AddDataSource,
make_scoped_refptr(html_source)));
// Listen for theme installation.
registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED,
NotificationService::AllSources());
// Listen for bookmark bar visibility changes.
registrar_.Add(this, NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED,
NotificationService::AllSources());
}
NewTabUI::~NewTabUI() {
}
void NewTabUI::RenderViewCreated(RenderViewHost* render_view_host) {
render_view_host->set_paint_observer(new PaintTimer);
}
void NewTabUI::RenderViewReused(RenderViewHost* render_view_host) {
render_view_host->set_paint_observer(new PaintTimer);
}
void NewTabUI::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
if (NotificationType::BROWSER_THEME_CHANGED == type) {
InitializeCSSCaches();
CallJavascriptFunction(L"themeChanged");
} else if (NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED) {
if (GetProfile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar))
CallJavascriptFunction(L"bookmarkBarAttached");
else
CallJavascriptFunction(L"bookmarkBarDetached");
}
}
void NewTabUI::InitializeCSSCaches() {
DOMUIThemeSource* theme = new DOMUIThemeSource(GetProfile());
ChromeThread::PostTask(
ChromeThread::IO, FROM_HERE,
NewRunnableMethod(
Singleton<ChromeURLDataManager>::get(),
&ChromeURLDataManager::AddDataSource,
make_scoped_refptr(theme)));
}
// static
void NewTabUI::RegisterUserPrefs(PrefService* prefs) {
prefs->RegisterIntegerPref(prefs::kNTPPrefVersion, 0);
MostVisitedHandler::RegisterUserPrefs(prefs);
ShownSectionsHandler::RegisterUserPrefs(prefs);
if (NewTabUI::WebResourcesEnabled())
TipsHandler::RegisterUserPrefs(prefs);
UpdateUserPrefsVersion(prefs);
}
// static
bool NewTabUI::UpdateUserPrefsVersion(PrefService* prefs) {
const int old_pref_version = prefs->GetInteger(prefs::kNTPPrefVersion);
if (old_pref_version != current_pref_version()) {
MigrateUserPrefs(prefs, old_pref_version, current_pref_version());
prefs->SetInteger(prefs::kNTPPrefVersion, current_pref_version());
return true;
}
return false;
}
// static
void NewTabUI::MigrateUserPrefs(PrefService* prefs, int old_pref_version,
int new_pref_version) {
ShownSectionsHandler::MigrateUserPrefs(prefs, old_pref_version,
current_pref_version());
}
// static
bool NewTabUI::WebResourcesEnabled() {
const CommandLine* command_line = CommandLine::ForCurrentProcess();
return !command_line->HasSwitch(switches::kDisableWebResources);
}
// static
bool NewTabUI::FirstRunDisabled() {
const CommandLine* command_line = CommandLine::ForCurrentProcess();
return command_line->HasSwitch(switches::kDisableNewTabFirstRun);
}
// static
void NewTabUI::SetURLTitleAndDirection(DictionaryValue* dictionary,
const string16& title,
const GURL& gurl) {
std::wstring wstring_url = UTF8ToWide(gurl.spec());
dictionary->SetString(L"url", wstring_url);
std::wstring wstring_title = UTF16ToWide(title);
bool using_url_as_the_title = false;
std::wstring title_to_set(wstring_title);
if (title_to_set.empty()) {
using_url_as_the_title = true;
title_to_set = wstring_url;
}
// We set the "dir" attribute of the title, so that in RTL locales, a LTR
// title is rendered left-to-right and truncated from the right. For example,
// the title of http://msdn.microsoft.com/en-us/default.aspx is "MSDN:
// Microsoft developer network". In RTL locales, in the [New Tab] page, if
// the "dir" of this title is not specified, it takes Chrome UI's
// directionality. So the title will be truncated as "soft developer
// network". Setting the "dir" attribute as "ltr" renders the truncated title
// as "MSDN: Microsoft D...". As another example, the title of
// http://yahoo.com is "Yahoo!". In RTL locales, in the [New Tab] page, the
// title will be rendered as "!Yahoo" if its "dir" attribute is not set to
// "ltr".
//
// Since the title can contain BiDi text, we need to mark the text as either
// RTL or LTR, depending on the characters in the string. If we use the URL
// as the title, we mark the title as LTR since URLs are always treated as
// left to right strings. Simply setting the title's "dir" attribute works
// fine for rendering and truncating the title. However, it does not work for
// entire title within a tooltip when the mouse is over the title link.. For
// example, without LRE-PDF pair, the title "Yahoo!" will be rendered as
// "!Yahoo" within the tooltip when the mouse is over the title link.
std::wstring direction = kDefaultHtmlTextDirection;
if (l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT) {
if (using_url_as_the_title) {
l10n_util::WrapStringWithLTRFormatting(&title_to_set);
} else {
if (l10n_util::StringContainsStrongRTLChars(wstring_title)) {
l10n_util::WrapStringWithRTLFormatting(&title_to_set);
direction = kRTLHtmlTextDirection;
} else {
l10n_util::WrapStringWithLTRFormatting(&title_to_set);
}
}
}
dictionary->SetString(L"title", title_to_set);
dictionary->SetString(L"direction", direction);
}
///////////////////////////////////////////////////////////////////////////////
// NewTabHTMLSource
bool NewTabUI::NewTabHTMLSource::first_run_ = true;
NewTabUI::NewTabHTMLSource::NewTabHTMLSource(Profile* profile)
: DataSource(chrome::kChromeUINewTabHost, MessageLoop::current()),
profile_(profile) {
}
void NewTabUI::NewTabHTMLSource::StartDataRequest(const std::string& path,
bool is_off_the_record, int request_id) {
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI));
if (!path.empty()) {
// A path under new-tab was requested; it's likely a bad relative
// URL from the new tab page, but in any case it's an error.
NOTREACHED();
return;
}
scoped_refptr<RefCountedBytes> html_bytes =
profile_->GetNTPResourceCache()->GetNewTabHTML(is_off_the_record);
SendResponse(request_id, html_bytes);
}