1// Copyright (c) 2011 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 "build/build_config.h"
6
7#include "chrome/browser/ui/webui/new_tab_ui.h"
8
9#include <set>
10
11#include "base/callback.h"
12#include "base/command_line.h"
13#include "base/i18n/rtl.h"
14#include "base/memory/singleton.h"
15#include "base/metrics/histogram.h"
16#include "base/string_number_conversions.h"
17#include "base/threading/thread.h"
18#include "base/utf_string_conversions.h"
19#include "chrome/browser/metrics/user_metrics.h"
20#include "chrome/browser/prefs/pref_service.h"
21#include "chrome/browser/profiles/profile.h"
22#include "chrome/browser/sessions/session_types.h"
23#include "chrome/browser/sessions/tab_restore_service.h"
24#include "chrome/browser/sessions/tab_restore_service_delegate.h"
25#include "chrome/browser/sessions/tab_restore_service_observer.h"
26#include "chrome/browser/sync/profile_sync_service.h"
27#include "chrome/browser/themes/theme_service.h"
28#include "chrome/browser/themes/theme_service_factory.h"
29#include "chrome/browser/ui/browser.h"
30#include "chrome/browser/ui/webui/app_launcher_handler.h"
31#include "chrome/browser/ui/webui/foreign_session_handler.h"
32#include "chrome/browser/ui/webui/most_visited_handler.h"
33#include "chrome/browser/ui/webui/new_tab_page_sync_handler.h"
34#include "chrome/browser/ui/webui/ntp_login_handler.h"
35#include "chrome/browser/ui/webui/ntp_resource_cache.h"
36#include "chrome/browser/ui/webui/shown_sections_handler.h"
37#include "chrome/browser/ui/webui/theme_source.h"
38#include "chrome/browser/ui/webui/value_helper.h"
39#include "chrome/common/chrome_switches.h"
40#include "chrome/common/extensions/extension.h"
41#include "chrome/common/pref_names.h"
42#include "chrome/common/url_constants.h"
43#include "content/browser/browser_thread.h"
44#include "content/browser/renderer_host/render_view_host.h"
45#include "content/browser/tab_contents/tab_contents.h"
46#include "content/common/notification_service.h"
47#include "grit/generated_resources.h"
48#include "grit/theme_resources.h"
49#include "ui/base/l10n/l10n_util.h"
50
51namespace {
52
53// The number of recent bookmarks we show.
54const int kRecentBookmarks = 9;
55
56// The number of search URLs to show.
57const int kSearchURLs = 3;
58
59// The amount of time there must be no painting for us to consider painting
60// finished.  Observed times are in the ~1200ms range on Windows.
61const int kTimeoutMs = 2000;
62
63// Strings sent to the page via jstemplates used to set the direction of the
64// HTML document based on locale.
65const char kRTLHtmlTextDirection[] = "rtl";
66const char kDefaultHtmlTextDirection[] = "ltr";
67
68///////////////////////////////////////////////////////////////////////////////
69// RecentlyClosedTabsHandler
70
71class RecentlyClosedTabsHandler : public WebUIMessageHandler,
72                                  public TabRestoreServiceObserver {
73 public:
74  RecentlyClosedTabsHandler() : tab_restore_service_(NULL) {}
75  virtual ~RecentlyClosedTabsHandler();
76
77  // WebUIMessageHandler implementation.
78  virtual void RegisterMessages();
79
80  // Callback for the "reopenTab" message. Rewrites the history of the
81  // currently displayed tab to be the one in TabRestoreService with a
82  // history of a session passed in through the content pointer.
83  void HandleReopenTab(const ListValue* args);
84
85  // Callback for the "getRecentlyClosedTabs" message.
86  void HandleGetRecentlyClosedTabs(const ListValue* args);
87
88  // Observer callback for TabRestoreServiceObserver. Sends data on
89  // recently closed tabs to the javascript side of this page to
90  // display to the user.
91  virtual void TabRestoreServiceChanged(TabRestoreService* service);
92
93  // Observer callback to notice when our associated TabRestoreService
94  // is destroyed.
95  virtual void TabRestoreServiceDestroyed(TabRestoreService* service);
96
97 private:
98  // TabRestoreService that we are observing.
99  TabRestoreService* tab_restore_service_;
100
101  DISALLOW_COPY_AND_ASSIGN(RecentlyClosedTabsHandler);
102};
103
104void RecentlyClosedTabsHandler::RegisterMessages() {
105  web_ui_->RegisterMessageCallback("getRecentlyClosedTabs",
106      NewCallback(this,
107                  &RecentlyClosedTabsHandler::HandleGetRecentlyClosedTabs));
108  web_ui_->RegisterMessageCallback("reopenTab",
109      NewCallback(this, &RecentlyClosedTabsHandler::HandleReopenTab));
110}
111
112RecentlyClosedTabsHandler::~RecentlyClosedTabsHandler() {
113  if (tab_restore_service_)
114    tab_restore_service_->RemoveObserver(this);
115}
116
117void RecentlyClosedTabsHandler::HandleReopenTab(const ListValue* args) {
118  TabRestoreServiceDelegate* delegate =
119      TabRestoreServiceDelegate::FindDelegateForController(
120      &web_ui_->tab_contents()->controller(), NULL);
121  if (!delegate)
122    return;
123
124  int session_to_restore;
125  if (ExtractIntegerValue(args, &session_to_restore))
126    tab_restore_service_->RestoreEntryById(delegate, session_to_restore, true);
127  // The current tab has been nuked at this point; don't touch any member
128  // variables.
129}
130
131void RecentlyClosedTabsHandler::HandleGetRecentlyClosedTabs(
132    const ListValue* args) {
133  if (!tab_restore_service_) {
134    tab_restore_service_ = web_ui_->GetProfile()->GetTabRestoreService();
135
136    // GetTabRestoreService() can return NULL (i.e., when in Off the
137    // Record mode)
138    if (tab_restore_service_) {
139      // This does nothing if the tabs have already been loaded or they
140      // shouldn't be loaded.
141      tab_restore_service_->LoadTabsFromLastSession();
142
143      tab_restore_service_->AddObserver(this);
144    }
145  }
146
147  if (tab_restore_service_)
148    TabRestoreServiceChanged(tab_restore_service_);
149}
150
151void RecentlyClosedTabsHandler::TabRestoreServiceChanged(
152    TabRestoreService* service) {
153  ListValue list_value;
154  NewTabUI::AddRecentlyClosedEntries(service->entries(), &list_value);
155
156  web_ui_->CallJavascriptFunction("recentlyClosedTabs", list_value);
157}
158
159void RecentlyClosedTabsHandler::TabRestoreServiceDestroyed(
160    TabRestoreService* service) {
161  tab_restore_service_ = NULL;
162}
163
164///////////////////////////////////////////////////////////////////////////////
165// MetricsHandler
166
167// Let the page contents record UMA actions. Only use when you can't do it from
168// C++. For example, we currently use it to let the NTP log the postion of the
169// Most Visited or Bookmark the user clicked on, as we don't get that
170// information through RequestOpenURL. You will need to update the metrics
171// dashboard with the action names you use, as our processor won't catch that
172// information (treat it as RecordComputedMetrics)
173class MetricsHandler : public WebUIMessageHandler {
174 public:
175  MetricsHandler() {}
176  virtual ~MetricsHandler() {}
177
178  // WebUIMessageHandler implementation.
179  virtual void RegisterMessages();
180
181  // Callback which records a user action.
182  void HandleMetrics(const ListValue* args);
183
184  // Callback for the "logEventTime" message.
185  void HandleLogEventTime(const ListValue* args);
186
187 private:
188
189  DISALLOW_COPY_AND_ASSIGN(MetricsHandler);
190};
191
192void MetricsHandler::RegisterMessages() {
193  web_ui_->RegisterMessageCallback("metrics",
194      NewCallback(this, &MetricsHandler::HandleMetrics));
195
196  web_ui_->RegisterMessageCallback("logEventTime",
197      NewCallback(this, &MetricsHandler::HandleLogEventTime));
198}
199
200void MetricsHandler::HandleMetrics(const ListValue* args) {
201  std::string string_action = UTF16ToUTF8(ExtractStringValue(args));
202  UserMetrics::RecordComputedAction(string_action, web_ui_->GetProfile());
203}
204
205void MetricsHandler::HandleLogEventTime(const ListValue* args) {
206  std::string event_name = UTF16ToUTF8(ExtractStringValue(args));
207  web_ui_->tab_contents()->LogNewTabTime(event_name);
208}
209
210///////////////////////////////////////////////////////////////////////////////
211// NewTabPageSetHomePageHandler
212
213// Sets the new tab page as home page when user clicks on "make this my home
214// page" link.
215class NewTabPageSetHomePageHandler : public WebUIMessageHandler {
216 public:
217  NewTabPageSetHomePageHandler() {}
218  virtual ~NewTabPageSetHomePageHandler() {}
219
220  // WebUIMessageHandler implementation.
221  virtual void RegisterMessages();
222
223  // Callback for "setHomePage".
224  void HandleSetHomePage(const ListValue* args);
225
226 private:
227
228  DISALLOW_COPY_AND_ASSIGN(NewTabPageSetHomePageHandler);
229};
230
231void NewTabPageSetHomePageHandler::RegisterMessages() {
232  web_ui_->RegisterMessageCallback("setHomePage", NewCallback(
233      this, &NewTabPageSetHomePageHandler::HandleSetHomePage));
234}
235
236void NewTabPageSetHomePageHandler::HandleSetHomePage(
237    const ListValue* args) {
238  web_ui_->GetProfile()->GetPrefs()->SetBoolean(prefs::kHomePageIsNewTabPage,
239                                                true);
240  ListValue list_value;
241  list_value.Append(new StringValue(
242      l10n_util::GetStringUTF16(IDS_NEW_TAB_HOME_PAGE_SET_NOTIFICATION)));
243  list_value.Append(new StringValue(
244      l10n_util::GetStringUTF16(IDS_NEW_TAB_HOME_PAGE_HIDE_NOTIFICATION)));
245  web_ui_->CallJavascriptFunction("onHomePageSet", list_value);
246}
247
248///////////////////////////////////////////////////////////////////////////////
249// NewTabPageClosePromoHandler
250
251// Turns off the promo line permanently when it has been explicitly closed by
252// the user.
253class NewTabPageClosePromoHandler : public WebUIMessageHandler {
254 public:
255  NewTabPageClosePromoHandler() {}
256  virtual ~NewTabPageClosePromoHandler() {}
257
258  // WebUIMessageHandler implementation.
259  virtual void RegisterMessages();
260
261  // Callback for "closePromo".
262  void HandleClosePromo(const ListValue* args);
263
264 private:
265
266  DISALLOW_COPY_AND_ASSIGN(NewTabPageClosePromoHandler);
267};
268
269void NewTabPageClosePromoHandler::RegisterMessages() {
270  web_ui_->RegisterMessageCallback("closePromo", NewCallback(
271      this, &NewTabPageClosePromoHandler::HandleClosePromo));
272}
273
274void NewTabPageClosePromoHandler::HandleClosePromo(
275    const ListValue* args) {
276  web_ui_->GetProfile()->GetPrefs()->SetBoolean(prefs::kNTPPromoClosed, true);
277  NotificationService* service = NotificationService::current();
278  service->Notify(NotificationType::PROMO_RESOURCE_STATE_CHANGED,
279                  Source<NewTabPageClosePromoHandler>(this),
280                  NotificationService::NoDetails());
281}
282
283}  // namespace
284
285///////////////////////////////////////////////////////////////////////////////
286// NewTabUI
287
288NewTabUI::NewTabUI(TabContents* contents)
289    : WebUI(contents) {
290  // Override some options on the Web UI.
291  hide_favicon_ = true;
292
293  if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kNewTabPage4))
294    force_bookmark_bar_visible_ = true;
295
296  focus_location_bar_by_default_ = true;
297  should_hide_url_ = true;
298  overridden_title_ = l10n_util::GetStringUTF16(IDS_NEW_TAB_TITLE);
299
300  // We count all link clicks as AUTO_BOOKMARK, so that site can be ranked more
301  // highly. Note this means we're including clicks on not only most visited
302  // thumbnails, but also clicks on recently bookmarked.
303  link_transition_type_ = PageTransition::AUTO_BOOKMARK;
304
305  if (NewTabUI::FirstRunDisabled())
306    NewTabHTMLSource::set_first_run(false);
307
308  static bool first_view = true;
309  if (first_view) {
310    first_view = false;
311  }
312
313  if (!GetProfile()->IsOffTheRecord()) {
314    PrefService* pref_service = GetProfile()->GetPrefs();
315    AddMessageHandler((new NTPLoginHandler())->Attach(this));
316    AddMessageHandler((new ShownSectionsHandler(pref_service))->Attach(this));
317    AddMessageHandler((new browser_sync::ForeignSessionHandler())->
318      Attach(this));
319    AddMessageHandler((new MostVisitedHandler())->Attach(this));
320    AddMessageHandler((new RecentlyClosedTabsHandler())->Attach(this));
321    AddMessageHandler((new MetricsHandler())->Attach(this));
322    if (GetProfile()->IsSyncAccessible())
323      AddMessageHandler((new NewTabPageSyncHandler())->Attach(this));
324    ExtensionService* service = GetProfile()->GetExtensionService();
325    // We might not have an ExtensionService (on ChromeOS when not logged in
326    // for example).
327    if (service)
328      AddMessageHandler((new AppLauncherHandler(service))->Attach(this));
329
330    AddMessageHandler((new NewTabPageSetHomePageHandler())->Attach(this));
331    AddMessageHandler((new NewTabPageClosePromoHandler())->Attach(this));
332  }
333
334  // Initializing the CSS and HTML can require some CPU, so do it after
335  // we've hooked up the most visited handler.  This allows the DB query
336  // for the new tab thumbs to happen earlier.
337  InitializeCSSCaches();
338  NewTabHTMLSource* html_source =
339      new NewTabHTMLSource(GetProfile()->GetOriginalProfile());
340  contents->profile()->GetChromeURLDataManager()->AddDataSource(html_source);
341
342  // Listen for theme installation.
343  registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED,
344                 NotificationService::AllSources());
345  // Listen for bookmark bar visibility changes.
346  registrar_.Add(this, NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED,
347                 NotificationService::AllSources());
348}
349
350NewTabUI::~NewTabUI() {
351}
352
353// The timer callback.  If enough time has elapsed since the last paint
354// message, we say we're done painting; otherwise, we keep waiting.
355void NewTabUI::PaintTimeout() {
356  // The amount of time there must be no painting for us to consider painting
357  // finished.  Observed times are in the ~1200ms range on Windows.
358  base::TimeTicks now = base::TimeTicks::Now();
359  if ((now - last_paint_) >= base::TimeDelta::FromMilliseconds(kTimeoutMs)) {
360    // Painting has quieted down.  Log this as the full time to run.
361    base::TimeDelta load_time = last_paint_ - start_;
362    int load_time_ms = static_cast<int>(load_time.InMilliseconds());
363    NotificationService::current()->Notify(
364        NotificationType::INITIAL_NEW_TAB_UI_LOAD,
365        NotificationService::AllSources(),
366        Details<int>(&load_time_ms));
367    UMA_HISTOGRAM_TIMES("NewTabUI load", load_time);
368  } else {
369    // Not enough quiet time has elapsed.
370    // Some more paints must've occurred since we set the timeout.
371    // Wait some more.
372    timer_.Start(base::TimeDelta::FromMilliseconds(kTimeoutMs), this,
373                 &NewTabUI::PaintTimeout);
374  }
375}
376
377void NewTabUI::StartTimingPaint(RenderViewHost* render_view_host) {
378  start_ = base::TimeTicks::Now();
379  last_paint_ = start_;
380  registrar_.Add(this, NotificationType::RENDER_WIDGET_HOST_DID_PAINT,
381      Source<RenderWidgetHost>(render_view_host));
382  timer_.Start(base::TimeDelta::FromMilliseconds(kTimeoutMs), this,
383               &NewTabUI::PaintTimeout);
384
385}
386void NewTabUI::RenderViewCreated(RenderViewHost* render_view_host) {
387  StartTimingPaint(render_view_host);
388}
389
390void NewTabUI::RenderViewReused(RenderViewHost* render_view_host) {
391  StartTimingPaint(render_view_host);
392}
393
394void NewTabUI::Observe(NotificationType type,
395                       const NotificationSource& source,
396                       const NotificationDetails& details) {
397  switch (type.value) {
398    case NotificationType::BROWSER_THEME_CHANGED: {
399      InitializeCSSCaches();
400      ListValue args;
401      args.Append(Value::CreateStringValue(
402          ThemeServiceFactory::GetForProfile(GetProfile())->HasCustomImage(
403              IDR_THEME_NTP_ATTRIBUTION) ?
404          "true" : "false"));
405      CallJavascriptFunction("themeChanged", args);
406      break;
407    }
408    case NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED: {
409      if (GetProfile()->GetPrefs()->IsManagedPreference(
410              prefs::kEnableBookmarkBar)) {
411        break;
412      }
413      if (GetProfile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar))
414        CallJavascriptFunction("bookmarkBarAttached");
415      else
416        CallJavascriptFunction("bookmarkBarDetached");
417      break;
418    }
419    case NotificationType::RENDER_WIDGET_HOST_DID_PAINT: {
420      last_paint_ = base::TimeTicks::Now();
421      break;
422    }
423    default:
424      CHECK(false) << "Unexpected notification: " << type.value;
425  }
426}
427
428void NewTabUI::InitializeCSSCaches() {
429  Profile* profile = GetProfile();
430  ThemeSource* theme = new ThemeSource(profile);
431  profile->GetChromeURLDataManager()->AddDataSource(theme);
432}
433
434// static
435void NewTabUI::RegisterUserPrefs(PrefService* prefs) {
436  prefs->RegisterIntegerPref(prefs::kNTPPrefVersion, 0);
437
438  MostVisitedHandler::RegisterUserPrefs(prefs);
439  ShownSectionsHandler::RegisterUserPrefs(prefs);
440
441  UpdateUserPrefsVersion(prefs);
442}
443
444// static
445bool NewTabUI::UpdateUserPrefsVersion(PrefService* prefs) {
446  const int old_pref_version = prefs->GetInteger(prefs::kNTPPrefVersion);
447  if (old_pref_version != current_pref_version()) {
448    MigrateUserPrefs(prefs, old_pref_version, current_pref_version());
449    prefs->SetInteger(prefs::kNTPPrefVersion, current_pref_version());
450    return true;
451  }
452  return false;
453}
454
455// static
456void NewTabUI::MigrateUserPrefs(PrefService* prefs, int old_pref_version,
457                                int new_pref_version) {
458  ShownSectionsHandler::MigrateUserPrefs(prefs, old_pref_version,
459                                         current_pref_version());
460}
461
462// static
463bool NewTabUI::FirstRunDisabled() {
464  const CommandLine* command_line = CommandLine::ForCurrentProcess();
465  return command_line->HasSwitch(switches::kDisableNewTabFirstRun);
466}
467
468// static
469void NewTabUI::SetURLTitleAndDirection(DictionaryValue* dictionary,
470                                       const string16& title,
471                                       const GURL& gurl) {
472  dictionary->SetString("url", gurl.spec());
473
474  bool using_url_as_the_title = false;
475  string16 title_to_set(title);
476  if (title_to_set.empty()) {
477    using_url_as_the_title = true;
478    title_to_set = UTF8ToUTF16(gurl.spec());
479  }
480
481  // We set the "dir" attribute of the title, so that in RTL locales, a LTR
482  // title is rendered left-to-right and truncated from the right. For example,
483  // the title of http://msdn.microsoft.com/en-us/default.aspx is "MSDN:
484  // Microsoft developer network". In RTL locales, in the [New Tab] page, if
485  // the "dir" of this title is not specified, it takes Chrome UI's
486  // directionality. So the title will be truncated as "soft developer
487  // network". Setting the "dir" attribute as "ltr" renders the truncated title
488  // as "MSDN: Microsoft D...". As another example, the title of
489  // http://yahoo.com is "Yahoo!". In RTL locales, in the [New Tab] page, the
490  // title will be rendered as "!Yahoo" if its "dir" attribute is not set to
491  // "ltr".
492  //
493  // Since the title can contain BiDi text, we need to mark the text as either
494  // RTL or LTR, depending on the characters in the string. If we use the URL
495  // as the title, we mark the title as LTR since URLs are always treated as
496  // left to right strings. Simply setting the title's "dir" attribute works
497  // fine for rendering and truncating the title. However, it does not work for
498  // entire title within a tooltip when the mouse is over the title link.. For
499  // example, without LRE-PDF pair, the title "Yahoo!" will be rendered as
500  // "!Yahoo" within the tooltip when the mouse is over the title link.
501  std::string direction = kDefaultHtmlTextDirection;
502  if (base::i18n::IsRTL()) {
503    if (using_url_as_the_title) {
504      base::i18n::WrapStringWithLTRFormatting(&title_to_set);
505    } else {
506      if (base::i18n::StringContainsStrongRTLChars(title)) {
507        base::i18n::WrapStringWithRTLFormatting(&title_to_set);
508        direction = kRTLHtmlTextDirection;
509      } else {
510        base::i18n::WrapStringWithLTRFormatting(&title_to_set);
511      }
512    }
513  }
514  dictionary->SetString("title", title_to_set);
515  dictionary->SetString("direction", direction);
516}
517
518namespace {
519
520bool IsTabUnique(const DictionaryValue* tab,
521                 std::set<std::string>* unique_items) {
522  DCHECK(unique_items);
523  std::string title;
524  std::string url;
525  if (tab->GetString("title", &title) &&
526      tab->GetString("url", &url)) {
527    // TODO(viettrungluu): this isn't obviously reliable, since different
528    // combinations of titles/urls may conceivably yield the same string.
529    std::string unique_key = title + url;
530    if (unique_items->find(unique_key) != unique_items->end())
531      return false;
532    else
533      unique_items->insert(unique_key);
534  }
535  return true;
536}
537
538}  // namespace
539
540// static
541void NewTabUI::AddRecentlyClosedEntries(
542    const TabRestoreService::Entries& entries, ListValue* entry_list_value) {
543  const int max_count = 10;
544  int added_count = 0;
545  std::set<std::string> unique_items;
546  // We filter the list of recently closed to only show 'interesting' entries,
547  // where an interesting entry is either a closed window or a closed tab
548  // whose selected navigation is not the new tab ui.
549  for (TabRestoreService::Entries::const_iterator it = entries.begin();
550       it != entries.end() && added_count < max_count; ++it) {
551    TabRestoreService::Entry* entry = *it;
552    scoped_ptr<DictionaryValue> entry_dict(new DictionaryValue());
553    if ((entry->type == TabRestoreService::TAB &&
554         ValueHelper::TabToValue(
555             *static_cast<TabRestoreService::Tab*>(entry),
556             entry_dict.get()) &&
557         IsTabUnique(entry_dict.get(), &unique_items)) ||
558        (entry->type == TabRestoreService::WINDOW &&
559         ValueHelper::WindowToValue(
560             *static_cast<TabRestoreService::Window*>(entry),
561             entry_dict.get()))) {
562      entry_dict->SetInteger("sessionId", entry->id);
563      entry_list_value->Append(entry_dict.release());
564      added_count++;
565    }
566  }
567}
568
569///////////////////////////////////////////////////////////////////////////////
570// NewTabHTMLSource
571
572bool NewTabUI::NewTabHTMLSource::first_run_ = true;
573
574NewTabUI::NewTabHTMLSource::NewTabHTMLSource(Profile* profile)
575    : DataSource(chrome::kChromeUINewTabHost, MessageLoop::current()),
576      profile_(profile) {
577}
578
579void NewTabUI::NewTabHTMLSource::StartDataRequest(const std::string& path,
580                                                  bool is_incognito,
581                                                  int request_id) {
582  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
583
584  if (AppLauncherHandler::HandlePing(profile_, path)) {
585    return;
586  } else if (!path.empty() && path[0] != '#') {
587    // A path under new-tab was requested; it's likely a bad relative
588    // URL from the new tab page, but in any case it's an error.
589    NOTREACHED();
590    return;
591  }
592
593  scoped_refptr<RefCountedBytes> html_bytes(
594      profile_->GetNTPResourceCache()->GetNewTabHTML(is_incognito));
595
596  SendResponse(request_id, html_bytes);
597}
598
599std::string NewTabUI::NewTabHTMLSource::GetMimeType(const std::string&) const {
600  return "text/html";
601}
602
603bool NewTabUI::NewTabHTMLSource::ShouldReplaceExistingSource() const {
604  return false;
605}
606