summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
authorarv@chromium.org <arv@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-19 00:55:50 +0000
committerarv@chromium.org <arv@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-19 00:55:50 +0000
commit5ed4ba5f65ce49c736bb16ddff31948c17c5938e (patch)
tree35f65274447876453c22ad46f7b2ef6ab53e4830 /chrome/browser
parentabcfb02cc5b7605049855bf0a1c53e445f51be4b (diff)
downloadchromium_src-5ed4ba5f65ce49c736bb16ddff31948c17c5938e.zip
chromium_src-5ed4ba5f65ce49c736bb16ddff31948c17c5938e.tar.gz
chromium_src-5ed4ba5f65ce49c736bb16ddff31948c17c5938e.tar.bz2
Adds a new command line switch called new-new-tab-page which enables
the prototype new new tab page. Split the DownloadsDOMHandler into its own file. The prototype is rough and has bugs. Don't file any bugs on it! TEST=Run with the command line above and you should see an alternative new tab page. Review URL: http://codereview.chromium.org/115426 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@16351 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-rw-r--r--chrome/browser/browser.vcproj8
-rw-r--r--chrome/browser/browser_resources.grd1
-rw-r--r--chrome/browser/dom_ui/downloads_dom_handler.cc334
-rw-r--r--chrome/browser/dom_ui/downloads_dom_handler.h97
-rw-r--r--chrome/browser/dom_ui/downloads_ui.cc406
-rw-r--r--chrome/browser/dom_ui/new_tab_ui.cc29
-rw-r--r--chrome/browser/dom_ui/new_tab_ui.h7
-rw-r--r--chrome/browser/resources/new_new_tab.html1401
8 files changed, 1878 insertions, 405 deletions
diff --git a/chrome/browser/browser.vcproj b/chrome/browser/browser.vcproj
index 1ebd1fb..6ba37de 100644
--- a/chrome/browser/browser.vcproj
+++ b/chrome/browser/browser.vcproj
@@ -1594,6 +1594,14 @@
>
</File>
<File
+ RelativePath=".\dom_ui\downloads_dom_handler.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\dom_ui\downloads_dom_handler.h"
+ >
+ </File>
+ <File
RelativePath=".\dom_ui\downloads_ui.cc"
>
</File>
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index b4eff3d..69733b7 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -19,6 +19,7 @@ without changes to the corresponding grd file. -->
<include name="IDR_SSL_ERROR_HTML" file="security\resources\ssl_error.html" flattenhtml="true" type="BINDATA" />
<include name="IDR_NEW_TAB_HTML" file="resources\new_tab.html" flattenhtml="true" type="BINDATA" />
<include name="IDR_NEW_TAB_THEME_CSS" file="resources\new_tab_theme.css" flattenhtml="true" type="BINDATA" />
+ <include name="IDR_NEW_NEW_TAB_HTML" file="resources\new_new_tab.html" flattenhtml="true" type="BINDATA" />
<include name="IDR_SAFE_BROWSING_MALWARE_BLOCK" file="resources\safe_browsing_malware_block.html" flattenhtml="true" type="BINDATA" />
<include name="IDR_SAFE_BROWSING_PHISHING_BLOCK" file="resources\safe_browsing_phishing_block.html" flattenhtml="true" type="BINDATA" />
<include name="IDR_SAFE_BROWSING_MULTIPLE_THREAT_BLOCK" file="resources\safe_browsing_multiple_threat_block.html" flattenhtml="true" type="BINDATA" />
diff --git a/chrome/browser/dom_ui/downloads_dom_handler.cc b/chrome/browser/dom_ui/downloads_dom_handler.cc
new file mode 100644
index 0000000..ea56c07
--- /dev/null
+++ b/chrome/browser/dom_ui/downloads_dom_handler.cc
@@ -0,0 +1,334 @@
+// 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 "chrome/browser/dom_ui/downloads_dom_handler.h"
+
+#include "app/l10n_util.h"
+#include "base/gfx/png_encoder.h"
+#include "base/string_piece.h"
+#include "base/thread.h"
+#include "base/time_format.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/dom_ui/chrome_url_data_manager.h"
+#include "chrome/browser/dom_ui/fileicon_source.h"
+#if defined(OS_WIN)
+// TODO(port): re-enable when download_util is ported
+#include "chrome/browser/download/download_util.h"
+#endif
+#include "chrome/browser/metrics/user_metrics.h"
+#include "chrome/browser/profile.h"
+#include "chrome/common/jstemplate_builder.h"
+#include "chrome/common/time_format.h"
+#include "chrome/common/url_constants.h"
+#include "grit/browser_resources.h"
+#include "grit/generated_resources.h"
+
+namespace {
+
+// Maximum number of downloads to show. TODO(glen): Remove this and instead
+// stuff the downloads down the pipe slowly.
+static const int kMaxDownloads = 150;
+
+// Sort DownloadItems into descending order by their start time.
+class DownloadItemSorter : public std::binary_function<DownloadItem*,
+ DownloadItem*,
+ bool> {
+ public:
+ bool operator()(const DownloadItem* lhs, const DownloadItem* rhs) {
+ return lhs->start_time() > rhs->start_time();
+ }
+};
+
+} // namespace
+
+DownloadsDOMHandler::DownloadsDOMHandler(DOMUI* dom_ui, DownloadManager* dlm)
+ : DOMMessageHandler(dom_ui),
+ search_text_(),
+ download_manager_(dlm) {
+ dom_ui_->RegisterMessageCallback("getDownloads",
+ NewCallback(this, &DownloadsDOMHandler::HandleGetDownloads));
+ dom_ui_->RegisterMessageCallback("openFile",
+ NewCallback(this, &DownloadsDOMHandler::HandleOpenFile));
+
+ dom_ui_->RegisterMessageCallback("drag",
+ NewCallback(this, &DownloadsDOMHandler::HandleDrag));
+
+ dom_ui_->RegisterMessageCallback("saveDangerous",
+ NewCallback(this, &DownloadsDOMHandler::HandleSaveDangerous));
+ dom_ui_->RegisterMessageCallback("discardDangerous",
+ NewCallback(this, &DownloadsDOMHandler::HandleDiscardDangerous));
+ dom_ui_->RegisterMessageCallback("show",
+ NewCallback(this, &DownloadsDOMHandler::HandleShow));
+ dom_ui_->RegisterMessageCallback("togglepause",
+ NewCallback(this, &DownloadsDOMHandler::HandlePause));
+ dom_ui_->RegisterMessageCallback("resume",
+ NewCallback(this, &DownloadsDOMHandler::HandlePause));
+ dom_ui_->RegisterMessageCallback("cancel",
+ NewCallback(this, &DownloadsDOMHandler::HandleCancel));
+ dom_ui_->RegisterMessageCallback("clearAll",
+ NewCallback(this, &DownloadsDOMHandler::HandleClearAll));
+
+
+ // Create our fileicon data source.
+ g_browser_process->io_thread()->message_loop()->PostTask(FROM_HERE,
+ NewRunnableMethod(&chrome_url_data_manager,
+ &ChromeURLDataManager::AddDataSource,
+ new FileIconSource()));
+}
+
+DownloadsDOMHandler::~DownloadsDOMHandler() {
+ ClearDownloadItems();
+ download_manager_->RemoveObserver(this);
+}
+
+// DownloadsDOMHandler, public: -----------------------------------------------
+
+void DownloadsDOMHandler::Init() {
+ download_manager_->AddObserver(this);
+}
+
+void DownloadsDOMHandler::OnDownloadUpdated(DownloadItem* download) {
+ // Get the id for the download. Our downloads are sorted latest to first,
+ // and the id is the index into that list. We should be careful of sync
+ // errors between the UI and the download_items_ list (we may wish to use
+ // something other than 'id').
+ OrderedDownloads::iterator it = find(download_items_.begin(),
+ download_items_.end(),
+ download);
+ if (it == download_items_.end())
+ return;
+ const int id = static_cast<int>(it - download_items_.begin());
+
+ ListValue results_value;
+ results_value.Append(CreateDownloadItemValue(download, id));
+ dom_ui_->CallJavascriptFunction(L"downloadUpdated", results_value);
+}
+
+// A download has started or been deleted. Query our DownloadManager for the
+// current set of downloads, which will call us back in SetDownloads once it
+// has retrieved them.
+void DownloadsDOMHandler::ModelChanged() {
+ ClearDownloadItems();
+ download_manager_->GetDownloads(this, search_text_);
+}
+
+void DownloadsDOMHandler::SetDownloads(
+ std::vector<DownloadItem*>& downloads) {
+ ClearDownloadItems();
+
+ // Swap new downloads in.
+ download_items_.swap(downloads);
+ sort(download_items_.begin(), download_items_.end(), DownloadItemSorter());
+
+ // Scan for any in progress downloads and add ourself to them as an observer.
+ for (OrderedDownloads::iterator it = download_items_.begin();
+ it != download_items_.end(); ++it) {
+ if (static_cast<int>(it - download_items_.begin()) > kMaxDownloads)
+ break;
+
+ DownloadItem* download = *it;
+ if (download->state() == DownloadItem::IN_PROGRESS) {
+ // We want to know what happens as the download progresses.
+ download->AddObserver(this);
+ } else if (download->safety_state() == DownloadItem::DANGEROUS) {
+ // We need to be notified when the user validates the dangerous download.
+ download->AddObserver(this);
+ }
+ }
+
+ SendCurrentDownloads();
+}
+
+void DownloadsDOMHandler::HandleGetDownloads(const Value* value) {
+ std::wstring new_search = ExtractStringValue(value);
+ if (search_text_.compare(new_search) != 0) {
+ search_text_ = new_search;
+ ClearDownloadItems();
+ download_manager_->GetDownloads(this, search_text_);
+ } else {
+ SendCurrentDownloads();
+ }
+}
+
+void DownloadsDOMHandler::HandleOpenFile(const Value* value) {
+ DownloadItem* file = GetDownloadByValue(value);
+ if (file)
+ download_manager_->OpenDownload(file, NULL);
+}
+
+void DownloadsDOMHandler::HandleDrag(const Value* value) {
+ DownloadItem* file = GetDownloadByValue(value);
+ if (file) {
+ IconManager* im = g_browser_process->icon_manager();
+ SkBitmap* icon = im->LookupIcon(file->full_path(), IconLoader::NORMAL);
+ download_util::DragDownload(file, icon);
+ }
+}
+
+void DownloadsDOMHandler::HandleSaveDangerous(const Value* value) {
+ DownloadItem* file = GetDownloadByValue(value);
+ if (file)
+ download_manager_->DangerousDownloadValidated(file);
+}
+
+void DownloadsDOMHandler::HandleDiscardDangerous(const Value* value) {
+ DownloadItem* file = GetDownloadByValue(value);
+ if (file)
+ file->Remove(true);
+}
+
+void DownloadsDOMHandler::HandleShow(const Value* value) {
+ DownloadItem* file = GetDownloadByValue(value);
+ if (file)
+ download_manager_->ShowDownloadInShell(file);
+}
+
+void DownloadsDOMHandler::HandlePause(const Value* value) {
+ DownloadItem* file = GetDownloadByValue(value);
+ if (file)
+ file->TogglePause();
+}
+
+void DownloadsDOMHandler::HandleCancel(const Value* value) {
+ DownloadItem* file = GetDownloadByValue(value);
+ if (file)
+ file->Cancel(true);
+}
+
+void DownloadsDOMHandler::HandleClearAll(const Value* value) {
+ download_manager_->RemoveAllDownloads();
+}
+
+// DownloadsDOMHandler, private: ----------------------------------------------
+
+void DownloadsDOMHandler::SendCurrentDownloads() {
+ ListValue results_value;
+ for (OrderedDownloads::iterator it = download_items_.begin();
+ it != download_items_.end(); ++it) {
+ int index = static_cast<int>(it - download_items_.begin());
+ if (index > kMaxDownloads)
+ break;
+ results_value.Append(CreateDownloadItemValue(*it,index));
+ }
+
+ dom_ui_->CallJavascriptFunction(L"downloadsList", results_value);
+}
+
+DictionaryValue* DownloadsDOMHandler::CreateDownloadItemValue(
+ DownloadItem* download, int id) {
+ DictionaryValue* file_value = new DictionaryValue();
+
+ file_value->SetInteger(L"started",
+ static_cast<int>(download->start_time().ToTimeT()));
+ file_value->SetString(L"since_string",
+ TimeFormat::RelativeDate(download->start_time(), NULL));
+ file_value->SetString(L"date_string",
+ base::TimeFormatShortDate(download->start_time()));
+ file_value->SetInteger(L"id", id);
+ file_value->SetString(L"file_path", download->full_path().ToWStringHack());
+ file_value->SetString(L"file_name", download->GetFileName().ToWStringHack());
+ file_value->SetString(L"url", download->url().spec());
+
+ if (download->state() == DownloadItem::IN_PROGRESS) {
+ if (download->safety_state() == DownloadItem::DANGEROUS) {
+ file_value->SetString(L"state", L"DANGEROUS");
+ } else if (download->is_paused()) {
+ file_value->SetString(L"state", L"PAUSED");
+ } else {
+ file_value->SetString(L"state", L"IN_PROGRESS");
+ }
+
+ file_value->SetString(L"progress_status_text",
+ GetProgressStatusText(download));
+
+ file_value->SetInteger(L"percent",
+ static_cast<int>(download->PercentComplete()));
+ file_value->SetInteger(L"received",
+ static_cast<int>(download->received_bytes()));
+ } else if (download->state() == DownloadItem::CANCELLED) {
+ file_value->SetString(L"state", L"CANCELLED");
+ } else if (download->state() == DownloadItem::COMPLETE) {
+ if (download->safety_state() == DownloadItem::DANGEROUS) {
+ file_value->SetString(L"state", L"DANGEROUS");
+ } else {
+ file_value->SetString(L"state", L"COMPLETE");
+ }
+ }
+
+ file_value->SetInteger(L"total",
+ static_cast<int>(download->total_bytes()));
+
+ return file_value;
+}
+
+void DownloadsDOMHandler::ClearDownloadItems() {
+ // Clear out old state and remove self as observer for each download.
+ for (OrderedDownloads::iterator it = download_items_.begin();
+ it != download_items_.end(); ++it) {
+ (*it)->RemoveObserver(this);
+ }
+ download_items_.clear();
+}
+
+DownloadItem* DownloadsDOMHandler::GetDownloadById(int id) {
+ for (OrderedDownloads::iterator it = download_items_.begin();
+ it != download_items_.end(); ++it) {
+ if (static_cast<int>(it - download_items_.begin() == id)) {
+ return (*it);
+ }
+ }
+
+ return NULL;
+}
+
+DownloadItem* DownloadsDOMHandler::GetDownloadByValue(const Value* value) {
+ int id;
+ if (ExtractIntegerValue(value, &id)) {
+ return GetDownloadById(id);
+ }
+ return NULL;
+}
+
+std::wstring DownloadsDOMHandler::GetProgressStatusText(
+ DownloadItem* download) {
+ int64 total = download->total_bytes();
+ int64 size = download->received_bytes();
+ DataUnits amount_units = GetByteDisplayUnits(size);
+ std::wstring received_size = FormatBytes(size, amount_units, true);
+ std::wstring amount = received_size;
+
+ // Adjust both strings for the locale direction since we don't yet know which
+ // string we'll end up using for constructing the final progress string.
+ std::wstring amount_localized;
+ if (l10n_util::AdjustStringForLocaleDirection(amount, &amount_localized)) {
+ amount.assign(amount_localized);
+ received_size.assign(amount_localized);
+ }
+
+ if (total) {
+ amount_units = GetByteDisplayUnits(total);
+ std::wstring total_text = FormatBytes(total, amount_units, true);
+ std::wstring total_text_localized;
+ if (l10n_util::AdjustStringForLocaleDirection(total_text,
+ &total_text_localized))
+ total_text.assign(total_text_localized);
+
+ amount = l10n_util::GetStringF(IDS_DOWNLOAD_TAB_PROGRESS_SIZE,
+ received_size,
+ total_text);
+ } else {
+ amount.assign(received_size);
+ }
+ amount_units = GetByteDisplayUnits(download->CurrentSpeed());
+ std::wstring speed_text = FormatSpeed(download->CurrentSpeed(),
+ amount_units, true);
+ std::wstring speed_text_localized;
+ if (l10n_util::AdjustStringForLocaleDirection(speed_text,
+ &speed_text_localized))
+ speed_text.assign(speed_text_localized);
+
+ return l10n_util::GetStringF(IDS_DOWNLOAD_TAB_PROGRESS_SPEED,
+ speed_text,
+ amount);
+}
diff --git a/chrome/browser/dom_ui/downloads_dom_handler.h b/chrome/browser/dom_ui/downloads_dom_handler.h
new file mode 100644
index 0000000..d8e6501
--- /dev/null
+++ b/chrome/browser/dom_ui/downloads_dom_handler.h
@@ -0,0 +1,97 @@
+// 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.
+
+#ifndef CHROME_BROWSER_DOM_UI_DOWNLOADS_DOM_HANDLER_H_
+#define CHROME_BROWSER_DOM_UI_DOWNLOADS_DOM_HANDLER_H_
+
+#include <vector>
+
+#include "base/values.h"
+#include "chrome/browser/dom_ui/dom_ui.h"
+#include "chrome/browser/download/download_manager.h"
+
+// The handler for Javascript messages related to the "downloads" view,
+// also observes changes to the download manager.
+class DownloadsDOMHandler : public DOMMessageHandler,
+ public DownloadManager::Observer,
+ public DownloadItem::Observer {
+ public:
+ explicit DownloadsDOMHandler(DOMUI* dom_ui, DownloadManager* dlm);
+ virtual ~DownloadsDOMHandler();
+
+ void Init();
+
+ // DownloadItem::Observer interface
+ virtual void OnDownloadUpdated(DownloadItem* download);
+ virtual void OnDownloadOpened(DownloadItem* download) { }
+
+ // DownloadManager::Observer interface
+ virtual void ModelChanged();
+ virtual void SetDownloads(std::vector<DownloadItem*>& downloads);
+
+ // Callback for the "getDownloads" message.
+ void HandleGetDownloads(const Value* value);
+
+ // Callback for the "openFile" message - opens the file in the shell.
+ void HandleOpenFile(const Value* value);
+
+ // Callback for the "drag" message - initiates a file object drag.
+ void HandleDrag(const Value* value);
+
+ // Callback for the "saveDangerous" message - specifies that the user
+ // wishes to save a dangerous file.
+ void HandleSaveDangerous(const Value* value);
+
+ // Callback for the "discardDangerous" message - specifies that the user
+ // wishes to discard (remove) a dangerous file.
+ void HandleDiscardDangerous(const Value* value);
+
+ // Callback for the "show" message - shows the file in explorer.
+ void HandleShow(const Value* value);
+
+ // Callback for the "pause" message - pauses the file download.
+ void HandlePause(const Value* value);
+
+ // Callback for the "cancel" message - cancels the download.
+ void HandleCancel(const Value* value);
+
+ // Callback for the "clearAll" message - clears all the downloads.
+ void HandleClearAll(const Value* value);
+
+ private:
+ // Send the current list of downloads to the page.
+ void SendCurrentDownloads();
+
+ // Creates a representation of a download in a format that the downloads
+ // HTML page can understand.
+ DictionaryValue* CreateDownloadItemValue(DownloadItem* download, int id);
+
+ // Clear all download items and their observers.
+ void ClearDownloadItems();
+
+ // Return the download that corresponds to a given id.
+ DownloadItem* GetDownloadById(int id);
+
+ // Return the download that is referred to in a given value.
+ DownloadItem* GetDownloadByValue(const Value* value);
+
+ // Get the localized status text for an in-progress download.
+ std::wstring GetProgressStatusText(DownloadItem* download);
+
+ // Current search text.
+ std::wstring search_text_;
+
+ // Our model
+ DownloadManager* download_manager_;
+
+ // The current set of visible DownloadItems for this view received from the
+ // DownloadManager. DownloadManager owns the DownloadItems. The vector is
+ // kept in order, sorted by ascending start time.
+ typedef std::vector<DownloadItem*> OrderedDownloads;
+ OrderedDownloads download_items_;
+
+ DISALLOW_COPY_AND_ASSIGN(DownloadsDOMHandler);
+};
+
+#endif // CHROME_BROWSER_DOM_UI_DOWNLOADS_DOM_HANDLER_H_
diff --git a/chrome/browser/dom_ui/downloads_ui.cc b/chrome/browser/dom_ui/downloads_ui.cc
index c8c7a06..76eb9074 100644
--- a/chrome/browser/dom_ui/downloads_ui.cc
+++ b/chrome/browser/dom_ui/downloads_ui.cc
@@ -5,32 +5,22 @@
#include "chrome/browser/dom_ui/downloads_ui.h"
#include "app/l10n_util.h"
-#include "base/gfx/png_encoder.h"
+#include "app/resource_bundle.h"
#include "base/string_piece.h"
#include "base/thread.h"
-#include "base/time_format.h"
+#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/dom_ui/chrome_url_data_manager.h"
-#include "chrome/browser/dom_ui/fileicon_source.h"
+#include "chrome/browser/dom_ui/downloads_dom_handler.h"
#include "chrome/browser/download/download_manager.h"
-#if defined(OS_WIN)
-// TODO(port): re-enable when download_util is ported
-#include "chrome/browser/download/download_util.h"
-#endif
-#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/profile.h"
#include "chrome/common/jstemplate_builder.h"
-#include "chrome/common/time_format.h"
#include "chrome/common/url_constants.h"
#include "grit/browser_resources.h"
#include "grit/generated_resources.h"
namespace {
-// Maximum number of downloads to show. TODO(glen): Remove this and instead
-// stuff the downloads down the pipe slowly.
-static const int kMaxDownloads = 150;
-
///////////////////////////////////////////////////////////////////////////////
//
// DownloadsHTMLSource
@@ -111,396 +101,6 @@ void DownloadsUIHTMLSource::StartDataRequest(const std::string& path,
SendResponse(request_id, html_bytes);
}
-///////////////////////////////////////////////////////////////////////////////
-//
-// DownloadsDOMHandler
-//
-///////////////////////////////////////////////////////////////////////////////
-
-// The handler for Javascript messages related to the "downloads" view,
-// also observes changes to the download manager.
-class DownloadsDOMHandler : public DOMMessageHandler,
- public DownloadManager::Observer,
- public DownloadItem::Observer {
- public:
- explicit DownloadsDOMHandler(DOMUI* dom_ui, DownloadManager* dlm);
- virtual ~DownloadsDOMHandler();
-
- void Init();
-
- // DownloadItem::Observer interface
- virtual void OnDownloadUpdated(DownloadItem* download);
- virtual void OnDownloadOpened(DownloadItem* download) { }
-
- // DownloadManager::Observer interface
- virtual void ModelChanged();
- virtual void SetDownloads(std::vector<DownloadItem*>& downloads);
-
- // Callback for the "getDownloads" message.
- void HandleGetDownloads(const Value* value);
-
- // Callback for the "openFile" message - opens the file in the shell.
- void HandleOpenFile(const Value* value);
-
- // Callback for the "drag" message - initiates a file object drag.
- void HandleDrag(const Value* value);
-
- // Callback for the "saveDangerous" message - specifies that the user
- // wishes to save a dangerous file.
- void HandleSaveDangerous(const Value* value);
-
- // Callback for the "discardDangerous" message - specifies that the user
- // wishes to discard (remove) a dangerous file.
- void HandleDiscardDangerous(const Value* value);
-
- // Callback for the "show" message - shows the file in explorer.
- void HandleShow(const Value* value);
-
- // Callback for the "pause" message - pauses the file download.
- void HandlePause(const Value* value);
-
- // Callback for the "cancel" message - cancels the download.
- void HandleCancel(const Value* value);
-
- // Callback for the "clearAll" message - clears all the downloads.
- void HandleClearAll(const Value* value);
-
- private:
- // Send the current list of downloads to the page.
- void SendCurrentDownloads();
-
- // Creates a representation of a download in a format that the downloads
- // HTML page can understand.
- DictionaryValue* CreateDownloadItemValue(DownloadItem* download, int id);
-
- // Clear all download items and their observers.
- void ClearDownloadItems();
-
- // Return the download that corresponds to a given id.
- DownloadItem* GetDownloadById(int id);
-
- // Return the download that is referred to in a given value.
- DownloadItem* GetDownloadByValue(const Value* value);
-
- // Get the localized status text for an in-progress download.
- std::wstring GetProgressStatusText(DownloadItem* download);
-
- // Current search text.
- std::wstring search_text_;
-
- // Our model
- DownloadManager* download_manager_;
-
- // The current set of visible DownloadItems for this view received from the
- // DownloadManager. DownloadManager owns the DownloadItems. The vector is
- // kept in order, sorted by ascending start time.
- typedef std::vector<DownloadItem*> OrderedDownloads;
- OrderedDownloads download_items_;
-
- DISALLOW_COPY_AND_ASSIGN(DownloadsDOMHandler);
-};
-
-// Sort DownloadItems into descending order by their start time.
-class DownloadItemSorter : public std::binary_function<DownloadItem*,
- DownloadItem*,
- bool> {
- public:
- bool operator()(const DownloadItem* lhs, const DownloadItem* rhs) {
- return lhs->start_time() > rhs->start_time();
- }
-};
-
-DownloadsDOMHandler::DownloadsDOMHandler(DOMUI* dom_ui, DownloadManager* dlm)
- : DOMMessageHandler(dom_ui),
- search_text_(),
- download_manager_(dlm) {
- dom_ui_->RegisterMessageCallback("getDownloads",
- NewCallback(this, &DownloadsDOMHandler::HandleGetDownloads));
- dom_ui_->RegisterMessageCallback("openFile",
- NewCallback(this, &DownloadsDOMHandler::HandleOpenFile));
-
- dom_ui_->RegisterMessageCallback("drag",
- NewCallback(this, &DownloadsDOMHandler::HandleDrag));
-
- dom_ui_->RegisterMessageCallback("saveDangerous",
- NewCallback(this, &DownloadsDOMHandler::HandleSaveDangerous));
- dom_ui_->RegisterMessageCallback("discardDangerous",
- NewCallback(this, &DownloadsDOMHandler::HandleDiscardDangerous));
- dom_ui_->RegisterMessageCallback("show",
- NewCallback(this, &DownloadsDOMHandler::HandleShow));
- dom_ui_->RegisterMessageCallback("togglepause",
- NewCallback(this, &DownloadsDOMHandler::HandlePause));
- dom_ui_->RegisterMessageCallback("resume",
- NewCallback(this, &DownloadsDOMHandler::HandlePause));
- dom_ui_->RegisterMessageCallback("cancel",
- NewCallback(this, &DownloadsDOMHandler::HandleCancel));
- dom_ui_->RegisterMessageCallback("clearAll",
- NewCallback(this, &DownloadsDOMHandler::HandleClearAll));
-
-
- // Create our fileicon data source.
- g_browser_process->io_thread()->message_loop()->PostTask(FROM_HERE,
- NewRunnableMethod(&chrome_url_data_manager,
- &ChromeURLDataManager::AddDataSource,
- new FileIconSource()));
-}
-
-DownloadsDOMHandler::~DownloadsDOMHandler() {
- ClearDownloadItems();
- download_manager_->RemoveObserver(this);
-}
-
-// DownloadsDOMHandler, public: -----------------------------------------------
-
-void DownloadsDOMHandler::Init() {
- download_manager_->AddObserver(this);
-}
-
-void DownloadsDOMHandler::OnDownloadUpdated(DownloadItem* download) {
- // Get the id for the download. Our downloads are sorted latest to first,
- // and the id is the index into that list. We should be careful of sync
- // errors between the UI and the download_items_ list (we may wish to use
- // something other than 'id').
- OrderedDownloads::iterator it = find(download_items_.begin(),
- download_items_.end(),
- download);
- if (it == download_items_.end())
- return;
- const int id = static_cast<int>(it - download_items_.begin());
-
- ListValue results_value;
- results_value.Append(CreateDownloadItemValue(download, id));
- dom_ui_->CallJavascriptFunction(L"downloadUpdated", results_value);
-}
-
-// A download has started or been deleted. Query our DownloadManager for the
-// current set of downloads, which will call us back in SetDownloads once it
-// has retrieved them.
-void DownloadsDOMHandler::ModelChanged() {
- ClearDownloadItems();
- download_manager_->GetDownloads(this, search_text_);
-}
-
-void DownloadsDOMHandler::SetDownloads(
- std::vector<DownloadItem*>& downloads) {
- ClearDownloadItems();
-
- // Swap new downloads in.
- download_items_.swap(downloads);
- sort(download_items_.begin(), download_items_.end(), DownloadItemSorter());
-
- // Scan for any in progress downloads and add ourself to them as an observer.
- for (OrderedDownloads::iterator it = download_items_.begin();
- it != download_items_.end(); ++it) {
- if (static_cast<int>(it - download_items_.begin()) > kMaxDownloads)
- break;
-
- DownloadItem* download = *it;
- if (download->state() == DownloadItem::IN_PROGRESS) {
- // We want to know what happens as the download progresses.
- download->AddObserver(this);
- } else if (download->safety_state() == DownloadItem::DANGEROUS) {
- // We need to be notified when the user validates the dangerous download.
- download->AddObserver(this);
- }
- }
-
- SendCurrentDownloads();
-}
-
-void DownloadsDOMHandler::HandleGetDownloads(const Value* value) {
- std::wstring new_search = ExtractStringValue(value);
- if (search_text_.compare(new_search) != 0) {
- search_text_ = new_search;
- ClearDownloadItems();
- download_manager_->GetDownloads(this, search_text_);
- } else {
- SendCurrentDownloads();
- }
-}
-
-void DownloadsDOMHandler::HandleOpenFile(const Value* value) {
- DownloadItem* file = GetDownloadByValue(value);
- if (file)
- download_manager_->OpenDownload(file, NULL);
-}
-
-void DownloadsDOMHandler::HandleDrag(const Value* value) {
- DownloadItem* file = GetDownloadByValue(value);
- if (file) {
- IconManager* im = g_browser_process->icon_manager();
- SkBitmap* icon = im->LookupIcon(file->full_path(), IconLoader::NORMAL);
- download_util::DragDownload(file, icon);
- }
-}
-
-void DownloadsDOMHandler::HandleSaveDangerous(const Value* value) {
- DownloadItem* file = GetDownloadByValue(value);
- if (file)
- download_manager_->DangerousDownloadValidated(file);
-}
-
-void DownloadsDOMHandler::HandleDiscardDangerous(const Value* value) {
- DownloadItem* file = GetDownloadByValue(value);
- if (file)
- file->Remove(true);
-}
-
-void DownloadsDOMHandler::HandleShow(const Value* value) {
- DownloadItem* file = GetDownloadByValue(value);
- if (file)
- download_manager_->ShowDownloadInShell(file);
-}
-
-void DownloadsDOMHandler::HandlePause(const Value* value) {
- DownloadItem* file = GetDownloadByValue(value);
- if (file)
- file->TogglePause();
-}
-
-void DownloadsDOMHandler::HandleCancel(const Value* value) {
- DownloadItem* file = GetDownloadByValue(value);
- if (file)
- file->Cancel(true);
-}
-
-void DownloadsDOMHandler::HandleClearAll(const Value* value) {
- download_manager_->RemoveAllDownloads();
-}
-
-// DownloadsDOMHandler, private: ----------------------------------------------
-
-void DownloadsDOMHandler::SendCurrentDownloads() {
- ListValue results_value;
- for (OrderedDownloads::iterator it = download_items_.begin();
- it != download_items_.end(); ++it) {
- int index = static_cast<int>(it - download_items_.begin());
- if (index > kMaxDownloads)
- break;
- results_value.Append(CreateDownloadItemValue(*it,index));
- }
-
- dom_ui_->CallJavascriptFunction(L"downloadsList", results_value);
-}
-
-DictionaryValue* DownloadsDOMHandler::CreateDownloadItemValue(
- DownloadItem* download, int id) {
- DictionaryValue* file_value = new DictionaryValue();
-
- file_value->SetInteger(L"started",
- static_cast<int>(download->start_time().ToTimeT()));
- file_value->SetString(L"since_string",
- TimeFormat::RelativeDate(download->start_time(), NULL));
- file_value->SetString(L"date_string",
- base::TimeFormatShortDate(download->start_time()));
- file_value->SetInteger(L"id", id);
- file_value->SetString(L"file_path", download->full_path().ToWStringHack());
- file_value->SetString(L"file_name", download->GetFileName().ToWStringHack());
- file_value->SetString(L"url", download->url().spec());
-
- if (download->state() == DownloadItem::IN_PROGRESS) {
- if (download->safety_state() == DownloadItem::DANGEROUS) {
- file_value->SetString(L"state", L"DANGEROUS");
- } else if (download->is_paused()) {
- file_value->SetString(L"state", L"PAUSED");
- } else {
- file_value->SetString(L"state", L"IN_PROGRESS");
- }
-
- file_value->SetString(L"progress_status_text",
- GetProgressStatusText(download));
-
- file_value->SetInteger(L"percent",
- static_cast<int>(download->PercentComplete()));
- file_value->SetInteger(L"received",
- static_cast<int>(download->received_bytes()));
- } else if (download->state() == DownloadItem::CANCELLED) {
- file_value->SetString(L"state", L"CANCELLED");
- } else if (download->state() == DownloadItem::COMPLETE) {
- if (download->safety_state() == DownloadItem::DANGEROUS) {
- file_value->SetString(L"state", L"DANGEROUS");
- } else {
- file_value->SetString(L"state", L"COMPLETE");
- }
- }
-
- file_value->SetInteger(L"total",
- static_cast<int>(download->total_bytes()));
-
- return file_value;
-}
-
-void DownloadsDOMHandler::ClearDownloadItems() {
- // Clear out old state and remove self as observer for each download.
- for (OrderedDownloads::iterator it = download_items_.begin();
- it != download_items_.end(); ++it) {
- (*it)->RemoveObserver(this);
- }
- download_items_.clear();
-}
-
-DownloadItem* DownloadsDOMHandler::GetDownloadById(int id) {
- for (OrderedDownloads::iterator it = download_items_.begin();
- it != download_items_.end(); ++it) {
- if (static_cast<int>(it - download_items_.begin() == id)) {
- return (*it);
- }
- }
-
- return NULL;
-}
-
-DownloadItem* DownloadsDOMHandler::GetDownloadByValue(const Value* value) {
- int id;
- if (ExtractIntegerValue(value, &id)) {
- return GetDownloadById(id);
- }
- return NULL;
-}
-
-std::wstring DownloadsDOMHandler::GetProgressStatusText(
- DownloadItem* download) {
- int64 total = download->total_bytes();
- int64 size = download->received_bytes();
- DataUnits amount_units = GetByteDisplayUnits(size);
- std::wstring received_size = FormatBytes(size, amount_units, true);
- std::wstring amount = received_size;
-
- // Adjust both strings for the locale direction since we don't yet know which
- // string we'll end up using for constructing the final progress string.
- std::wstring amount_localized;
- if (l10n_util::AdjustStringForLocaleDirection(amount, &amount_localized)) {
- amount.assign(amount_localized);
- received_size.assign(amount_localized);
- }
-
- if (total) {
- amount_units = GetByteDisplayUnits(total);
- std::wstring total_text = FormatBytes(total, amount_units, true);
- std::wstring total_text_localized;
- if (l10n_util::AdjustStringForLocaleDirection(total_text,
- &total_text_localized))
- total_text.assign(total_text_localized);
-
- amount = l10n_util::GetStringF(IDS_DOWNLOAD_TAB_PROGRESS_SIZE,
- received_size,
- total_text);
- } else {
- amount.assign(received_size);
- }
- amount_units = GetByteDisplayUnits(download->CurrentSpeed());
- std::wstring speed_text = FormatSpeed(download->CurrentSpeed(),
- amount_units, true);
- std::wstring speed_text_localized;
- if (l10n_util::AdjustStringForLocaleDirection(speed_text,
- &speed_text_localized))
- speed_text.assign(speed_text_localized);
-
- return l10n_util::GetStringF(IDS_DOWNLOAD_TAB_PROGRESS_SPEED,
- speed_text,
- amount);
-}
-
} // namespace
///////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/dom_ui/new_tab_ui.cc b/chrome/browser/dom_ui/new_tab_ui.cc
index 449c605..e64f02e 100644
--- a/chrome/browser/dom_ui/new_tab_ui.cc
+++ b/chrome/browser/dom_ui/new_tab_ui.cc
@@ -8,6 +8,7 @@
#include "app/l10n_util.h"
#include "app/resource_bundle.h"
+#include "base/command_line.h"
#include "base/histogram.h"
#include "base/string_piece.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
@@ -17,6 +18,7 @@
#include "chrome/browser/dom_ui/dom_ui_favicon_source.h"
#include "chrome/browser/dom_ui/dom_ui_thumbnail_source.h"
#include "chrome/browser/dom_ui/dom_ui_theme_source.h"
+#include "chrome/browser/dom_ui/downloads_dom_handler.h"
#include "chrome/browser/dom_ui/history_ui.h"
#include "chrome/browser/history/page_usage_data.h"
#include "chrome/browser/metrics/user_metrics.h"
@@ -29,6 +31,7 @@
#include "chrome/browser/search_engines/template_url.h"
#include "chrome/browser/sessions/tab_restore_service.h"
#include "chrome/browser/user_data_manager.h"
+#include "chrome/common/chrome_switches.h"
#include "chrome/common/jstemplate_builder.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/pref_names.h"
@@ -242,6 +245,8 @@ void NewTabHTMLSource::StartDataRequest(const std::string& path,
l10n_util::GetString(IDS_NEW_TAB_SEARCHES));
localized_strings.SetString(L"bookmarks",
l10n_util::GetString(IDS_NEW_TAB_BOOKMARKS));
+ localized_strings.SetString(L"recent",
+ l10n_util::GetString(IDS_NEW_TAB_RECENT));
localized_strings.SetString(L"showhistory",
l10n_util::GetString(IDS_NEW_TAB_HISTORY_SHOW));
localized_strings.SetString(L"showhistoryurl",
@@ -278,10 +283,10 @@ void NewTabHTMLSource::StartDataRequest(const std::string& path,
#ifdef CHROME_PERSONALIZATION
localized_strings.SetString(L"p13nsrc", Personalization::GetNewTabSource());
#endif
-
static const StringPiece new_tab_html(
ResourceBundle::GetSharedInstance().GetRawDataResource(
- IDR_NEW_TAB_HTML));
+ NewTabUI::EnableNewNewTabPage() ?
+ IDR_NEW_NEW_TAB_HTML : IDR_NEW_TAB_HTML));
const std::string full_html = jstemplate_builder::GetTemplateHtml(
new_tab_html, &localized_strings, "t" /* template root node id */);
@@ -805,6 +810,8 @@ void RecentlyBookmarkedHandler::SendBookmarksToPage() {
DictionaryValue* entry_value = new DictionaryValue;
SetURLTitleAndDirection(entry_value,
WideToUTF16(node->GetTitle()), node->GetURL());
+ entry_value->SetInteger(L"time",
+ static_cast<int>(node->date_added().ToTimeT()));
list_value.Append(entry_value);
}
dom_ui_->CallJavascriptFunction(L"recentlyBookmarked", list_value);
@@ -1146,6 +1153,18 @@ NewTabUI::NewTabUI(TabContents* contents)
&ChromeURLDataManager::AddDataSource,
html_source));
} else {
+
+ if (EnableNewNewTabPage()) {
+ DownloadManager* dlm = GetProfile()->GetDownloadManager();
+ DownloadsDOMHandler* downloads_handler =
+ new DownloadsDOMHandler(this, dlm);
+
+ AddMessageHandler(downloads_handler);
+ AddMessageHandler(new BrowsingHistoryHandler(this));
+
+ downloads_handler->Init();
+ }
+
AddMessageHandler(new TemplateURLHandler(this));
AddMessageHandler(new MostVisitedHandler(this));
AddMessageHandler(new RecentlyBookmarkedHandler(this));
@@ -1197,3 +1216,9 @@ void NewTabUI::Observe(NotificationType type,
void NewTabUI::RegisterUserPrefs(PrefService* prefs) {
MostVisitedHandler::RegisterUserPrefs(prefs);
}
+
+// static
+bool NewTabUI::EnableNewNewTabPage() {
+ const CommandLine* command_line = CommandLine::ForCurrentProcess();
+ return command_line->HasSwitch(switches::kNewNewTabPage);
+}
diff --git a/chrome/browser/dom_ui/new_tab_ui.h b/chrome/browser/dom_ui/new_tab_ui.h
index f233b0f..1efb8dd 100644
--- a/chrome/browser/dom_ui/new_tab_ui.h
+++ b/chrome/browser/dom_ui/new_tab_ui.h
@@ -12,6 +12,10 @@ class GURL;
class PrefService;
class Profile;
+namespace {
+ class NewTabHTMLSource;
+}
+
// The TabContents used for the New Tab page.
class NewTabUI : public DOMUI,
public NotificationObserver {
@@ -21,6 +25,9 @@ class NewTabUI : public DOMUI,
static void RegisterUserPrefs(PrefService* prefs);
+ // Whether we should use the prototype new tab page.
+ static bool EnableNewNewTabPage();
+
private:
void Observe(NotificationType type,
const NotificationSource& source,
diff --git a/chrome/browser/resources/new_new_tab.html b/chrome/browser/resources/new_new_tab.html
new file mode 100644
index 0000000..a61ddd8
--- /dev/null
+++ b/chrome/browser/resources/new_new_tab.html
@@ -0,0 +1,1401 @@
+<!DOCTYPE HTML>
+<html id="t" jsvalues="dir:textdirection;firstview:firstview">
+<!--
+ This page is optimized for perceived performance. Our enemies are the time
+ taken for the backend to generate our data, and the time taken to parse
+ and render the starting HTML/CSS content of the page. This page is
+ designed to let Chrome do both of those things in parallel.
+
+ 1. Defines temporary content callback functions
+ 2. Fires off requests for content (these can come back 20-150ms later)
+ 3. Defines basic functions (handlers)
+ 4. Renders a fast-parse hard-coded version of itself (this can take 20-50ms)
+ 5. Defines the full content-rendering functions
+
+ If the requests for content come back before the content-rendering functions
+ are defined, the data is held until those functions are defined.
+-->
+<script src="local_strings.js"></script>
+<script>
+
+// Logging info for benchmarking purposes.
+var log = [];
+function logEvent(name) {
+ log.push([name, Date.now()]);
+}
+
+// Basic functions to send, receive, store and process the data from our
+// backend.
+var unprocessedData = {
+ mostVisitedPages: false,
+ searchURLs: false
+};
+
+var recent = {
+ MAX_ITEMS: 20,
+ entries: [],
+ entriesById: {},
+ dirty: true,
+ appendData: function(data, constr) {
+ var self = this;
+ data.forEach(function(d) {
+ self.add(new constr(d));
+ });
+ },
+
+ add: function(entry) {
+ if (!(entry instanceof Entry)) {
+ alert('Not an instance of Entry:\n\n' + JSON.stringify(entry));
+ }
+ // Don't add if already in there.
+ var id = entry.id;
+ if (!(id in this.entriesById)) {
+ this.entries.push(entry);
+ this.entriesById[id] = entry;
+ this.dirty = true;
+ }
+ },
+
+ remove: function(entry) {
+ var id = entry.id;
+ if (id in this.entriesById) {
+ entry.remove();
+ delete this.entriesById[id];
+ this.dirty = true;
+ }
+ },
+
+ sortEntries: function() {
+ this.entries.sort(function(e1, e2) {
+ return e2.time - e1.time;
+ });
+ }
+};
+
+var renderFunctionsDefined = false;
+
+var localStrings;
+
+// The list of URLs that have been blacklisted (so that thumbnails are not
+// shown) in the last set of change. Used to revert changes when the Cancel
+// button is pressed.
+var blacklistedURLs = [];
+
+function $(o) {return document.getElementById(o);}
+
+///////////////////////////////////////////////////////////////////////////////
+
+
+function Entry(data) {
+ this.data = data;
+}
+
+Entry.prototype = {
+ get url() {
+ return this.data.url;
+ },
+ get title() {
+ return this.data.title;
+ },
+ get icon() {
+ return 'chrome://favicon/' + this.data.url;
+ },
+ get time() {
+ return this.data.time;
+ },
+ get id() {
+ return this.type + '-' + this.url + '-' + this.time;
+ },
+ createDom: function() {
+ var data = this.data;
+ var link = DOM('a', {
+ title: this.title,
+ href: data.url,
+ textContent: this.title
+ });
+ var div = DOM('div', {
+ id: this.id,
+ className: 'recent-item ' + this.type
+ });
+
+ link.style.backgroundImage = 'url("' + this.icon + '")';
+
+ // Set the title's directionality independently of the page, see comment
+ // about setting div_title.style.direction above for details.
+ link.style.direction = data.direction;
+
+ // The following if statement is a temporary workaround for
+ // http://crbug.com/7252 and http://crbug.com/7697. It should be removed
+ // before closing these bugs.
+ if (data.direction == 'rtl') {
+ link.style.textOverflow = 'clip';
+ }
+
+ this.addListeners(div, link);
+ div.appendChild(link);
+
+ return this.element = div;
+ },
+ addListeners: function(div, link) {
+ },
+ remove: function() {
+ this.removed = true;
+ var div = $(this.id);
+ if (!div || !div.parentNode) return;
+ div.parentNode.removeChild(div);
+ }
+};
+
+function DownloadEntry(data) {
+ Entry.call(this, data);
+}
+
+DownloadEntry.prototype = {
+ __proto__: Entry.prototype,
+ type: 'download',
+ typeImageUrl: '',
+ get title() {
+ return this.data.file_name;
+ },
+ get icon() {
+ return 'chrome://fileicon/' + this.data.file_path;
+ },
+ get time() {
+ return this.data.started;
+ },
+ addListeners: function(div, link) {
+ // TODO(arv): What about non safe files?
+ // TODO(arv): What about non finished files?
+ var id = this.data.id;
+ link.onclick = function(e) {
+ chrome.send("openFile", [String(id)]);
+ e.preventDefault();
+ }
+ }
+};
+
+function HistoryEntry(data) {
+ Entry.call(this, data);
+}
+HistoryEntry.prototype = {
+ __proto__: Entry.prototype,
+ type: 'history'
+};
+
+function BookmarkEntry(data) {
+ Entry.call(this, data);
+}
+BookmarkEntry.prototype = {
+ __proto__: Entry.prototype,
+ type: 'bookmark',
+ addListeners: function(div, link) {
+ var index = this.data.index;
+ link.addEventListener('mousedown', function(event) {
+ chrome.send('metrics', ['NTP_Bookmark' + index]);
+ }, false);
+ }
+};
+
+function TabEntry(data) {
+ if (data.type == 'window') {
+ return new WindowEntry(data);
+ }
+ Entry.call(this, data);
+}
+TabEntry.prototype = {
+ __proto__: Entry.prototype,
+ type: 'tab',
+ typeImageUrl: '',
+
+ // Neither closed tabs or windows have a time stamp. Use now as the time
+ get time() {
+ return this.time_ || (this.time_ = Math.floor(Date.now() / 1000));
+ },
+ addListeners: function(div, link) {
+ var sessionId = this.data.sessionId;
+ var index = this.data.index;
+ link.onclick = function(e) {
+ chrome.send('metrics', ['NTP_TabRestored' + index]);
+ // This is a hack because chrome.send is hardcoded to only
+ // accept arrays of strings.
+ chrome.send('reopenTab', [String(sessionId)]);
+ e.preventDefault();
+ };
+ },
+ get id() {
+ return this.type + '-' + this.data.sessionId;
+ }
+};
+
+function WindowEntry(data) {
+ // This actually extends TabEntry but the tab entry constructor redirects to
+ // this constructor so don't call it here because that would cause an infinite
+ // loop.
+ Entry.call(this, data);
+}
+WindowEntry.prototype = {
+ __proto__: TabEntry.prototype,
+ type: 'window',
+ url: '',
+ createDom: function() {
+ var entry = this.data;
+ var div = DOM('div', {
+ id: this.id,
+ className: 'recent-window-container'
+ });
+
+ var linkSpan = DOM('span', {
+ textContent: ' '
+ });
+ for (var windowIndex = 0; windowIndex < entry.tabs.length; windowIndex++) {
+ var tab = entry.tabs[windowIndex];
+ var tabImg = DOM('img', {
+ src: 'url("chrome://favicon/' + tab.url + '")',
+ width: 16,
+ height: 16,
+ onmousedown: function() {
+ return false;
+ }
+ });
+ linkSpan.appendChild(tabImg);
+ }
+
+ var link = DOM('span', {
+ className: 'recently-closed-window-link',
+ tabIndex: 0
+ });
+ var windowSpan = DOM('span', {
+ className: 'recently-close-window-text',
+ textContent: recentlyClosedWindowText(entry.tabs.length)
+ });
+ link.appendChild(windowSpan);
+ link.appendChild(linkSpan);
+ div.appendChild(link);
+
+
+ // The card takes care of appending itself to the DOM, so no need to
+ // keep a reference to it.
+ new RecentlyClosedHoverCard(link, entry);
+
+ this.addListeners(div, link);
+
+ return this.element = div;
+ }
+};
+
+/**
+ * If the functions that can render content are defined, render
+ * the content for any data we've received so far.
+ */
+function processData() {
+ // This is ugly. Can we refactor this?
+ if (renderFunctionsDefined) {
+ if (unprocessedData.mostVisitedPages) {
+ renderMostVisitedPages(unprocessedData.mostVisitedPages);
+ unprocessedData.mostVisitedPages = false;
+ }
+ if (unprocessedData.searchURLs) {
+ //renderSearchURLs(unprocessedData.searchURLs);
+ renderRecentItems();
+ unprocessedData.searchURLs = false;
+ }
+
+ if (recent.dirty && recent.entries.length) {
+ renderRecentItems();
+ }
+ }
+}
+
+function mostVisitedPages(data) {
+ logEvent('received most visited pages');
+ unprocessedData.mostVisitedPages = data;
+ processData();
+}
+
+function searchURLs(data) {
+ logEvent('received search URLs');
+ unprocessedData.searchURLs = data;
+ processData();
+}
+
+function recentlyBookmarked(data) {
+ logEvent('received recently bookmarked data');
+ replaceData(data, BookmarkEntry);
+}
+
+function replaceData(data, ctor) {
+ var oldEntries = {};
+ recent.entries.forEach(function(entry) {
+ if (entry instanceof ctor) {
+ oldEntries[entry.id] = entry;
+ }
+ });
+
+ var entriesToAdd = {};
+ data.map(function(d) {
+ var newEntry = new ctor(d);
+ var id = newEntry.id;
+ if (id in oldEntries && !oldEntries[id].removed) {
+ // Already present. No need to add or remove.
+ delete oldEntries[id];
+ } else {
+ recent.add(newEntry);
+ }
+ });
+
+ for (var id in oldEntries) {
+ recent.remove(oldEntries[id]);
+ }
+
+ processData();
+}
+
+function recentlyClosedTabs(data) {
+ logEvent('received recently closed tabs');
+ // Add index to the data.
+ var i = 0;
+ data.forEach(function(d) {
+ d.index = i++;
+ });
+ replaceData(data, TabEntry);
+}
+
+function historyResult(info, results) {
+ logEvent('received recent history');
+ // TODO(arv): If we have more history and we want to show more recent items
+ // we should fetch more items from the history.
+ recent.appendData(results, HistoryEntry);
+ if (!info.finished && results.length < recent.MAX_ITEMS) {
+ getMoreHistory();
+ }
+ processData();
+}
+
+function downloadsList(data) {
+ logEvent('received downloads');
+ recent.appendData(data, DownloadEntry);
+ processData();
+}
+
+function resizeP13N(new_height) {
+ var childf = $('p13n');
+ if (new_height < 1) {
+ childf.style.display = "none";
+ return;
+ }
+ childf.height = new_height;
+ childf.style.display = "block";
+}
+
+var currentHistoryDay = 0;
+function getMoreHistory() {
+ chrome.send('getHistory', [String(currentHistoryDay++)]);
+}
+
+function handleWindowResize() {
+ var body = document.body;
+ if (!body || body.clientWidth < 10) {
+ // We're probably a background tab, so don't do anything.
+ return;
+ }
+
+ if (body.className == 'small' && body.clientWidth >= 885) {
+ body.className = '';
+ } else if (body.className == '' && body.clientWidth <= 865) {
+ body.className = 'small';
+ }
+}
+
+function handleDOMContentLoaded() {
+ logEvent('domcontentloaded fired');
+}
+
+chrome.send("getMostVisited");
+//chrome.send("getMostSearched");
+chrome.send("getRecentlyBookmarked");
+chrome.send("getRecentlyClosedTabs");
+getMoreHistory();
+chrome.send('getDownloads', ['']);
+
+logEvent('log start');
+</script>
+<head>
+<meta charset="utf-8">
+<title jscontent="title"></title>
+<style>
+body {
+ background-color:white;
+ margin:0px;
+ background-image:url(chrome://theme/theme_newtab_background);
+ background-repeat:repeat-x;
+ background-attachment: fixed;
+}
+html[firstview='true'] #main {
+ opacity:0.0;
+ -webkit-transition:all 0.4s;
+}
+html[firstview='true'] #main.visible {
+ opacity:1.0;
+}
+#main {
+ margin-left:auto;
+ margin-right:auto;
+ margin-top:10px;
+ width: 838px;
+ -webkit-transition:width .12s;
+}
+.small #main {
+ width: 658px; /* 4 * 217 */
+
+}
+form {
+ padding: 0;
+ margin: 0;
+}
+.section {
+ padding:3px 0px 5px 0px;
+ margin-bottom:30px;
+}
+.section-title {
+ color:#000;
+ line-height:19pt;
+ font-size:110%;
+ font-weight:bold;
+ margin-bottom:4px;
+}
+#recent-section {
+ margin-top: 1.5em;
+}
+#recent-section > .section-title {
+ margin-bottom: 1em;
+}
+#mostvisitedsection {
+ margin:0px 5px 0px 0px;
+}
+#mostvisited td {
+ padding:0px 10px 10px 0px;
+}
+html[dir='rtl'] #mostvisited td {
+ padding:0px 0px 10px 10px;
+}
+.most-visited-text {
+ position:absolute;
+ left:100px;
+ right:100px;
+ padding:20px;
+ margin:15px;
+ background-color:white;
+ -webkit-box-shadow: 5px 5px 10px #ccc;
+ -webkit-transition:all .12s;
+ z-index: 1;
+}
+.thumbnail-title {
+ display:block;
+ width:195px; /* thumbnail */
+ margin-top:6px; /* line up favicons with search favicons */
+ padding:1px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ text-decoration:none;
+ -webkit-transition:all .12s;
+ -webkit-box-sizing:border-box;
+}
+html[dir='rtl'] .thumbnail-title {
+ background-position:right;
+ padding-left:0px;
+ padding-right:22px;
+ text-align:right;
+}
+.thumbnail {
+ width:195px;
+ height:136px;
+ border:1px solid #ccc;
+ background-color:#eee;
+ -webkit-transition:all .12s;
+ position: relative;
+ -webkit-box-shadow:#ccc 1px 1px 5px;
+}
+a.thumbnail {
+ border:1px solid #abe;
+}
+
+.thumbnail-icon {
+ right: -5px;
+ bottom: -5px;
+ width: 32px;
+ height: 32px;
+ position: absolute;
+ /* a shadow (non box shadow) might make the icon stand out more
+ -webkit-box-shadow: 3px 3px 5px #ccc;
+ */
+}
+
+.small .thumbnail-title {
+ width:127px;
+}
+.small .thumbnail {
+ width:150px;
+ height:113px;
+}
+.small .most-visited-text {
+ width:430px;
+ padding:15px;
+ margin:12px;
+}
+.recent-item {
+ margin:3px 0px 3px 0px;
+ height:16pt;
+ line-height:16px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ background: no-repeat center left;
+ padding-left: 22px;
+}
+.recent-item.history {
+ background-image: url('../../app/theme/o2_history.png');
+}
+.recent-item.bookmark {
+ background-image: url('../../app/theme/o2_star.png');
+}
+.recent-item > a {
+ background-repeat:no-repeat;
+ -webkit-background-size:16px;
+ background-position:center left;
+ padding:1px 0px 0px 22px;
+}
+
+.recent-bookmark {
+ display:block;
+ background-repeat:no-repeat;
+ background-size:16px;
+ background-position:0px 1px;
+ padding:1px 0px 0px 22px;
+ margin:3px 0px 3px 0px;
+ min-height:16pt;
+ line-height:16px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ text-decoration:underline;
+}
+.recent-window-container {
+ line-height:16px;
+ display:block;
+ position: relative;
+ margin:0 3px;
+ padding-left: 22px;
+ margin-left: 22px;
+
+ /* TODO(arv): Remove hard coded URL */
+ background-image: url('../../app/theme/closed_window.png') !important;
+}
+.recent-window-container img {
+ margin:0 3px -2px 3px;
+}
+.recent-window-hover-container {
+ position:absolute;
+ border:1px solid #999;
+ -webkit-box-shadow: 5px 5px 10px #ccc;
+ background-color:white;
+ width: 157px;
+ left: 20px;
+ white-space:nowrap;
+ opacity:.9;
+}
+.recent-window-hover-container .recent-bookmark {
+ text-decoration:none;
+ text-overflow:ellipsis;
+ overflow:hidden;
+ margin: 3px 0 0 5px;
+}
+.recently-closed-window-link {
+ 'text-decoration:none';
+}
+.recently-closed-window-link:hover {
+ cursor:pointer;
+}
+.recently-close-window-text {
+ text-decoration:underline;
+ color: #0000cc;
+}
+
+html[dir='rtl'] .recent-bookmark {
+ background-position:right;
+ padding-left:0px;
+ padding-right:22px;
+}
+a {
+ color:#0000cc;
+ text-decoration:underline;
+ white-space: nowrap;
+}
+a.manage {
+ color:#77c;
+ margin-left: 5px;
+ margin-right: 5px;
+ line-height:19pt;
+ text-decoration:underline;
+}
+html[dir='rtl'] #managesearcheslink {
+ float: left;
+}
+.sidebar {
+ width: 207px;
+ padding:3px 10px 3px 9px;
+ -webkit-border-radius:5px 5px;
+ margin-bottom:10px;
+}
+#searches {
+ background-color:#e1ecfe;
+}
+#recentlyBookmarked {
+ background-color:#e1ecfe;
+}
+html[dir='rtl'] #recentlyBookmarkedContainer {
+ text-align:right;
+}
+#recentlyClosedContainer {
+ position:relative;
+}
+html[dir='rtl'] #recentlyClosedContainer {
+ text-align:right;
+}
+#searches input {
+ border:1px solid #7f9db9;
+ background-repeat: no-repeat;
+ background-position:4px center;
+ padding-left: 23px;
+ min-height:24px;
+ width:182px;
+ margin-bottom:8px;
+ display:block;
+}
+html[dir='rtl'] #searches input {
+ background-position: right;
+ padding-left:0px;
+ padding-right: 23px;
+}
+#searches input:-webkit-input-placeholder-mode {
+ color: #aaa;
+}
+.footer {
+ border-top:1px solid #ccc;
+ padding-top:4px;
+ font-size:8pt;
+}
+.edit-visible {
+ display: none;
+}
+.edit-mode .edit-visible {
+ display: inline;
+}
+.non-edit-visible {
+ display: inline;
+}
+.edit-mode .non-edit-visible {
+ display: none;
+}
+.most-visited-container {
+ position: relative;
+ left: 0px;
+ top: 0px;
+}
+.edit-mode .disabled-on-edit {
+ opacity: 0.5;
+ pointer-events: none; /* Disable clicks */
+}
+</style>
+</head>
+<body onload="logEvent('body onload fired');"
+ jsvalues=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<div id="l10n" style="display:none;">
+ <span id="closedwindowsingle" jscontent="closedwindowsingle">1 Tab</span>
+ <span id="closedwindowmultiple" jscontent="closedwindowmultiple">% Tabs</span>
+</div>
+<script>
+// We apply the size class here so that we don't trigger layout animations onload.
+handleWindowResize();
+window.addEventListener('resize', handleWindowResize, true);
+document.addEventListener('DOMContentLoaded', handleDOMContentLoaded);
+</script>
+
+<div id="main">
+ <div id="mostvisitedsection" class="section">
+ <div id="mostvisited" style="position:relative;">
+ <div>
+ <span class="section-title non-edit-visible" jscontent="mostvisited"></span>
+ <span class="section-title edit-visible" jseval="this.innerHTML = $this.editmodeheading;"></span>
+ </div>
+ <div id="mostvisitedintro" style="display:none;">
+ <div class="most-visited-text" jseval="this.innerHTML = $this.mostvisitedintro;"></div>
+ <table>
+ <tr>
+ <td><div class="thumbnail">&nbsp;</div></td>
+ <td><div class="thumbnail"></div></td>
+ <td><div class="thumbnail">&nbsp;</div></td>
+ <td><div class="thumbnail"></div></td>
+ </tr>
+ <tr>
+ <td><div class="thumbnail">&nbsp;</div></td>
+ <td><div class="thumbnail"></div></td>
+ <td><div class="thumbnail">&nbsp;</div></td>
+ <td><div class="thumbnail"></div></td>
+ </tr>
+ </table>
+ </div>
+ <table id="mostvisitedtable">
+ <!-- This content forces the view to the correct width and provides a
+ preview of what's to load to reduce white-flash. Users who get
+ the mostvisitedintro will see a brief flash of this content. We
+ only use one row so that we may avoid flashing extra rows when
+ the user has only one row of items -->
+ <tr>
+ <td>
+ <div class="thumbnail-title">&nbsp;</div>
+ <div class="thumbnail"></div>
+ </td>
+ <td>
+ <div class="thumbnail-title">&nbsp;</div>
+ <div class="thumbnail"></div>
+ </td>
+ <td>
+ <div class="thumbnail-title">&nbsp;</div>
+ <div class="thumbnail"></div>
+ </td>
+ <td>
+ <div class="thumbnail-title">&nbsp;</div>
+ <div class="thumbnail"></div>
+ </td>
+ </tr>
+ </table>
+ </div>
+ <a href="#"
+ class="manage non-edit-visible"
+ onClick="enterEditMode(); return false">
+ <span jscontent="editthumbnails"></span></a>
+ <button type="button" class="edit-visible" onClick="exitEditMode();"
+ jscontent="doneediting"></button>
+ <button type="button" class="edit-visible" onClick="cancelEdits();"
+ jscontent="cancelediting"></button>
+ <a href="#"
+ class="manage edit-visible"
+ onClick="restoreThumbnails(); return false">
+ <span jscontent="restorethumbnails"></span></a>
+ <a href="#"
+ jsvalues="href:showhistoryurl"
+ class="manage non-edit-visible">
+ <span jscontent="showhistory"></span> &raquo;</a>
+ </div>
+
+ <div id="recent-section">
+ <div class="section-title" jscontent="recent"></div>
+ <div id="recent-items"></div>
+ </div>
+
+ <div style="display:none">
+ <div align="right">
+ <img src="../../app/theme/%DISTRIBUTION%/product_logo.png"
+ width="145" height="52" style="padding-bottom:8px;" />
+ </div>
+ <iframe id="p13n" frameborder="0" width="100%" scrolling="no" height="0"
+ jsdisplay="p13nsrc" style="display:none;"
+ jsvalues="src:p13nsrc"></iframe>
+ <div id="searches" class="sidebar">
+ <div class="section-title" jscontent="searches"></div>
+ <form onsubmit="chrome.send('searchHistoryPage', [this.search.value]); return false;">
+ <input type="text" class="hint"
+ name="search"
+ style="background-image:url(chrome://favicon/);"
+ jsvalues="placeholder:searchhistory">
+ </form>
+ <div id='searches-entries'></div>
+ </div>
+
+ <div id="recentlyBookmarked" class="sidebar" style="display:none">
+ <span class="section-title" jscontent="bookmarks"></span>
+ <div id="recentlyBookmarkedContainer"></div>
+ </div>
+
+ <div id="recentlyClosedTabs" class="sidebar" style="display:none">
+ <div class="section-title" jscontent="recentlyclosed"></div>
+ <div id="recentlyClosedContainer"></div>
+ </div>
+
+ </div>
+
+</div>
+
+<script>
+logEvent('start of second script block');
+
+/* Return a DOM element with tag name |elem| and attributes |attrs|. */
+function DOM(elem, attrs) {
+ var elem = document.createElement(elem);
+ for (var attr in attrs) {
+ elem[attr] = attrs[attr];
+ }
+ return elem;
+}
+
+/**
+ * Partially applies this function to a particular 'this object' and zero or
+ * more arguments. The result is a new function with some arguments of the first
+ * function pre-filled and the value of |this| 'pre-specified'.<br><br>
+ *
+ * Remaining arguments specified at call-time are appended to the pre-
+ * specified ones.<br><br>
+ *
+ * @param {Function} fn A function to partially apply.
+ * @param {Object} selfObj Specifies the object which |this| should point to
+ * when the function is run.
+ * @param {Object} var_args Additional arguments that are partially
+ * applied to the function.
+ *
+ * @return {!Function} A partially-applied form of the function bind() was
+ * invoked as a method of.
+ */
+function bind(fn, selfObj, var_args) {
+ var boundArgs = Array.prototype.slice.call(arguments, 2);
+ return function() {
+ var args = Array.prototype.slice.call(arguments);
+ args.unshift.apply(args, boundArgs);
+ return fn.apply(selfObj, args);
+ }
+}
+
+/* Return the DOM element for a "most visited" entry.
+ |page| should be an object with "title", "url", and "direction" fields. */
+function makeMostVisitedDOM(page, number) {
+ /* The HTML we want looks like this:
+ <a class="disabled-on-edit" href="URL" title="gmail.com">
+ <div class="thumbnail-title disabled-on-edit"
+ style="direction:ltr">gmail.com</div>
+ <img class="thumbnail disabled-on-edit"
+ style="background-image:url(thumbnailurl);">
+ <img class="thumbnail-icon disabled-on-edit" src="chrome://faviconurl">
+ </a>
+ */
+ var root;
+ if (page.url) {
+ root = DOM('a', {className:'disabled-on-edit',
+ href:page.url,
+ title:page.title});
+ root.addEventListener("mousedown", function(event) {
+ chrome.send("metrics", ["NTP_MostVisited" + number])
+ }, false);
+ } else {
+ // Something went wrong; don't make it clickable.
+ root = DOM('span');
+ }
+
+ /* Create the thumbnail */
+ var img_thumbnail = DOM('img', {className:'thumbnail disabled-on-edit'});
+ img_thumbnail.setAttribute('onload', "logEvent('image loaded');");
+ img_thumbnail.src = 'chrome://thumb/' + page.url;
+
+ /* Create the title */
+ var div_title = DOM('div', {className:'thumbnail-title disabled-on-edit'});
+ /* Set the title's directionality independently of the overall page
+ directionality. We need to do this since a purely LTR title should always
+ have it's direction set as ltr. We only set the title direction to rtl if
+ it contains a strong RTL character. Please refer to http://crbug.com/5926
+ for more information.
+ */
+ div_title.style.direction = page.direction;
+ /* The following if statement is a temporary workaround for
+ http://crbug.com/7252 and http://crbug.com/7697. It should be removed
+ before closing these bugs.
+ */
+ if (page.direction == 'rtl') {
+ div_title.style.textOverflow = 'clip';
+ }
+ if (page.title) {
+ div_title.appendChild(document.createTextNode(page.title));
+ } else {
+ // Make the empty title at least push down the icon.
+ div_title.innerHTML = '&nbsp;';
+ }
+
+ // Create icon
+ var img_icon = DOM('img', {
+ className: 'thumbnail-icon disabled-on-edit',
+ src: 'chrome://favicon/' + page.url
+ });
+
+ root.appendChild(div_title);
+ root.appendChild(img_thumbnail);
+ root.appendChild(img_icon);
+
+ return root;
+}
+
+/* Return the DOM element for the cross that should be displayed in edit-mode
+ over a "most visited" entry. */
+function makeCrossImageDOM(url) {
+ var cross = DOM('div', {className:'edit-cross'});
+ cross.addEventListener("mousedown",
+ function(event) {
+ if (event.which == 1) // Left click only.
+ blacklistURL(url); },
+ false);
+ return cross;
+}
+
+/* This function is called by the browser with the most visited pages list.
+ |pages| is a list of page objects, which have url, title, and direction
+ attributes. */
+function renderMostVisitedPages(pages) {
+ logEvent('renderMostVisitedPages called: ' + pages.length);
+
+ // TODO(arv): Only send 8 pages
+ pages = pages.slice(0, 8);
+
+ var table = $("main");
+ // If we were in edit-mode, stay in that mode as this means this is a
+ // refresh triggered by thumbnails editing.
+ if (table.className.indexOf('edit-mode') != -1)
+ table.className = 'visible edit-mode';
+ else
+ table.className = 'visible';
+ var table = $("mostvisitedtable");
+ table.innerHTML = '';
+
+ // Show the most visited helptext if most visited is still useless. This is
+ // a crappy heuristic.
+ if (pages.length < 4) {
+ $("mostvisitedintro").style.display = "block";
+ return;
+ }
+
+ $('mostvisitedintro').style.display = 'none';
+
+ // Create the items and add them to rows.
+ var rows = [];
+ var rowNum = -1;
+ for (var i = 0, page; page = pages[i]; ++i) {
+ if (i % 4 == 0) {
+ rowNum += 1;
+ rows[rowNum] = DOM('tr', {});
+ }
+
+ var cell = DOM('td');
+ var container = DOM('div', { className: "most-visited-container"});
+ container.appendChild(makeCrossImageDOM(page.url));
+ container.appendChild(makeMostVisitedDOM(page, i));
+ cell.appendChild(container);
+
+ rows[rowNum].appendChild(cell);
+
+ logEvent('mostVisitedPage : ' + i);
+ }
+
+ // Add the rows to the table.
+ for (var i = 0, row; row = rows[i]; i++) {
+ table.appendChild(row);
+ }
+
+ logEvent('renderMostVisitedPages done');
+}
+
+function makeSearchURL(url) {
+ /* The HTML we want looks like this:
+ <form>
+ <input type="text" class="hint"
+ style="background-image:url(chrome://favicon/"+url+");"
+ placeholder="Search Wikipedia">
+ </form>
+ */
+ var input = DOM('input', {type:'text',
+ className: 'hint'});
+ // There is no DOM property for placeholder.
+ input.setAttribute('placeholder', url.short_name);
+ input.keyword = url.keyword;
+
+ if (url.favIconURL) {
+ input.style.backgroundImage =
+ 'url("chrome://favicon/iconurl/' + url.favIconURL + '")';
+ } else {
+ input.style.backgroundImage =
+ 'url("chrome://favicon/http://' + url.short_name + '")';
+ }
+
+ var form = DOM('form');
+ form.onsubmit = function() {
+ chrome.send('doSearch', [input.keyword, input.value]);
+ return false;
+ };
+ form.appendChild(input);
+
+ return form;
+}
+
+/* This function is called by the browser when the list of search URLs is
+ available. |urls| is a list of objects with |name| attributes. */
+function renderSearchURLs(urls) {
+ logEvent('renderSearchURLs called: ' + urls.length);
+ var container = $('searches-entries');
+ container.innerHTML = ''; // Clear out any previous contents.
+ if (urls.length > 0) {
+ $('searches').style.display = 'block';
+ for (var i = 0; i < urls.length; ++i) {
+ container.appendChild(makeSearchURL(urls[i]));
+ }
+ }
+
+ logEvent('renderSearchURLs done');
+}
+
+/**
+ * Renders recent history items, bookmarks, searches and tabs.
+ */
+function renderRecentItems() {
+ var recentSection = $('recent-section');
+ var containerEl = $('recent-items');
+
+ // Filter out subsequent entries from the same domain as well as remove exact
+ // duplicates from earlier (based on url).
+ var lastDomain;
+ var seenUrls = {};
+ recent.entries = recent.entries.filter(function(entry) {
+ // Mark the item as hidden if we have seen the domain before
+ var url = entry.url;
+ // Closed windows don't have an URL but we never filter them out.
+ if (!url) {
+ return true;
+ }
+
+ var m = url.match(/^[^:]+:\/*[^\/]+/);
+ var previousDomain = lastDomain;
+ lastDomain = m && m[0];
+ return previousDomain != lastDomain;
+ });
+
+ recent.sortEntries();
+
+ recentSection.style.display = recent.entries.length ? 'block' : 'none';
+
+ containerEl.innerHTML = '';
+
+ var last;
+ for (var i = 0; i < recent.entries.length; i++) {
+ var entry = recent.entries[i];
+
+ if (i > recent.MAX_ITEMS) {
+ entry.remove();
+ continue;
+ }
+
+ if (entry.removed) {
+ continue;
+ }
+
+ last = entry.time;
+
+ if (!entry.element) {
+ entry.createDom();
+ }
+
+ containerEl.appendChild(entry.element);
+ }
+
+ recent.entries = recent.entries.filter(function(d) {
+ return !d.removed;
+ });
+}
+
+/**
+ * This function adds incoming information about tabs to the new tab UI.
+ */
+function renderRecentlyClosedTabs(entries) {
+ logEvent('renderRecentlyClosedTabs begin');
+ var section = $('recentlyClosedTabs');
+ var container = $('recentlyClosedContainer');
+
+ // recentlyClosedTabs is called on every internal event which
+ // affects tab history to make sure things are up to
+ // date. Therefore, reset the recentlyClosedTabs state on every
+ // call.
+ section.style.display = 'none';
+ container.innerHTML = '';
+
+ if (entries.length > 0) {
+ section.style.display = 'block';
+
+ for (var i = 0; entry = entries[i]; ++i) {
+ var link;
+
+ if (entry.type == "tab") {
+ // Closed tab.
+ link = createRecentBookmark('a', entry);
+ container.appendChild(link);
+ } else {
+ // Closed window.
+ var linkSpanContainer = DOM('div', {className: 'recent-window-container'});
+
+ var linkSpan = DOM('span');
+ linkSpan.textContent = ' ';
+ for (var windowIndex = 0; windowIndex < entry.tabs.length; windowIndex++) {
+ var tab = entry.tabs[windowIndex];
+ var tabImg = DOM('img', {
+ src:'url("chrome://favicon/' + tab.url + '")',
+ width:16,
+ height:16});
+ tabImg.onmousedown = function() { return false; }
+ linkSpan.appendChild(tabImg);
+ }
+
+ link = DOM('span', { className: 'recently-closed-window-link' } );
+ windowSpan = DOM('span', {className: 'recently-close-window-text'});
+ windowSpan.appendChild(document.createTextNode(
+ recentlyClosedWindowText(entry.tabs.length)));
+ link.appendChild(windowSpan);
+ link.appendChild(linkSpan);
+ linkSpanContainer.appendChild(link);
+ container.appendChild(linkSpanContainer);
+
+ // The card takes care of appending itself to the DOM, so no need to
+ // keep a reference to it.
+ new RecentlyClosedHoverCard(linkSpanContainer, entry);
+ }
+
+ link.onclick = function(sessionId) {
+ return function() {
+ chrome.send("metrics", ["NTP_TabRestored" + i]);
+ // This is a hack because chrome.send is hardcoded to only
+ // accept arrays of strings.
+ chrome.send('reopenTab', [sessionId.toString()]);
+ return false;
+ }
+ }(entry.sessionId);
+ }
+ }
+
+ logEvent('renderRecentlyClosedTabs done');
+}
+
+/**
+ * Returns the text used for a recently closed window.
+ *
+ * @param numTabs number of tabs in the window
+ *
+ * @return the text to use
+ */
+function recentlyClosedWindowText(numTabs) {
+ if (numTabs == 1)
+ return localStrings.getString('closedwindowsingle');
+ return localStrings.getString('closedwindowmultiple').replace('%', numTabs);
+}
+
+/**
+ * Creates an item to go in the recent bookmarks or recently closed lists.
+ *
+ * @param {String} tagName Tagname for the DOM element to create.
+ * @param {Object} data Object with title, url, and direction to popuplate the element.
+ *
+ * @return {Node} The element containing the bookmark.
+ */
+function createRecentBookmark(tagName, data) {
+ var link = DOM(tagName, {className:'recent-bookmark', title:data.title});
+ if (tagName == 'a')
+ link.href = data.url;
+ link.style.backgroundImage = 'url("chrome://favicon/' + data.url + '")';
+ // Set the title's directionality independently of the page, see comment
+ // about setting div_title.style.direction above for details.
+ link.style.direction = data.direction;
+ // The following if statement is a temporary workaround for
+ // http://crbug.com/7252 and http://crbug.com/7697. It should be removed
+ // before closing these bugs.
+ if (data.direction == 'rtl') {
+ link.style.textOverflow = 'clip';
+ }
+
+ link.appendChild(document.createTextNode(data.title));
+ return link;
+}
+
+/**
+ * A hover card for windows in the recently closed list to show more details.
+ *
+ * @param {Node} target The element the hover card is for.
+ * @param {Object} data Object containing all the data for the card.
+ */
+function RecentlyClosedHoverCard(target, data) {
+ this.target_ = target;
+ this.data_ = data;
+ this.target_.onmouseover = bind(this.setShowTimeout_, this);
+ this.target_.onmouseout = bind(this.setHideTimeout_, this);
+}
+
+/** Timeout set when closing the card. */
+RecentlyClosedHoverCard.closeTimeout_;
+
+/** Timeout set when opening the card. */
+RecentlyClosedHoverCard.openTimeout_;
+
+/**
+ * Clears the timer for hiding the card.
+ */
+RecentlyClosedHoverCard.clearHideTimeout_ = function() {
+ clearTimeout(RecentlyClosedHoverCard.closeTimeout_);
+};
+
+/**
+ * Clears the timer for opening the card.
+ */
+RecentlyClosedHoverCard.clearOpenTimeout_ = function() {
+ clearTimeout(RecentlyClosedHoverCard.openTimeout_);
+};
+
+/**
+ * Creates and shows the card.
+ */
+RecentlyClosedHoverCard.prototype.show_ = function() {
+ if (!this.container_) {
+ this.container_ = DOM('div', {className: 'recent-window-hover-container'});
+ for (var i = 0; i < this.data_.tabs.length; i++) {
+ var tab = this.data_.tabs[i];
+ var item = createRecentBookmark('span', tab);
+ this.container_.appendChild(item);
+ }
+ this.target_.parentNode.insertBefore(this.container_,
+ this.target_.nextSibling);
+ this.container_.onmouseover = RecentlyClosedHoverCard.clearHideTimeout_;
+ this.container_.onmouseout = bind(this.setHideTimeout_, this);
+ }
+ this.container_.style.display = '';
+};
+
+/**
+ * Hides the card.
+ */
+RecentlyClosedHoverCard.prototype.hide_ = function() {
+ this.container_.style.display = 'none';
+};
+
+/**
+ * Clears any open timers and sets the open timer.
+ * If the card is already showing then we only need to clear
+ * the hide timer.
+ */
+RecentlyClosedHoverCard.prototype.setShowTimeout_ = function() {
+ if (this.container && this.container_.style.display != 'none') {
+ // If we're already showing the hovercard, make sure we don't hide it again
+ // onmouseover.
+ RecentlyClosedHoverCard.clearHideTimeout_();
+ return;
+ }
+
+ RecentlyClosedHoverCard.clearOpenTimeout_();
+ RecentlyClosedHoverCard.openTimeout_ =
+ setTimeout(bind(this.show_, this), 200);
+};
+
+/**
+ * Clears the open timer and sets the close one.
+ */
+RecentlyClosedHoverCard.prototype.setHideTimeout_ = function() {
+ RecentlyClosedHoverCard.clearOpenTimeout_();
+ RecentlyClosedHoverCard.closeTimeout_ =
+ setTimeout(bind(this.hide_, this), 200);
+};
+
+/**
+ * Switches to thumbnails editing mode.
+ */
+function enterEditMode() {
+ // If the cross-image in the heading has not been added yet, do it.
+ // Note that we have to insert the image node explicitly because the
+ // heading is localized and therefore set at run-time, and we need
+ // the image to be static so that it can be inlined at build-time.
+ var crossImageDiv = $('cross-image-container');
+ if (crossImageDiv && !crossImageDiv.hasChildNodes()) {
+ var image = $('small-cross-image');
+ image.parentNode.removeChild(image);
+ crossImageDiv.appendChild(image);
+ image.style.display = 'inline';
+ crossImageDiv.style.display = 'inline';
+ }
+ $('main').className = 'visible edit-mode';
+}
+
+function exitEditMode() {
+ $('main').className = 'visible';
+ blacklistedURLs = [];
+}
+
+function cancelEdits() {
+ if (blacklistedURLs.length > 0)
+ chrome.send("removeURLsFromMostVisitedBlacklist", blacklistedURLs);
+ exitEditMode();
+}
+
+function blacklistURL(url) {
+ blacklistedURLs.push(url);
+ chrome.send("blacklistURLFromMostVisited", [url]);
+}
+
+function restoreThumbnails() {
+ exitEditMode();
+ chrome.send('clearMostVisitedURLsBlacklist');
+}
+
+function viewLog() {
+ var lines = [];
+ var start = log[0][1];
+
+ for (var i = 0; i < log.length; i++) {
+ lines.push((log[i][1] - start) + ': ' + log[i][0]);
+ }
+
+ var lognode = document.createElement('pre');
+ lognode.appendChild(document.createTextNode(lines.join("\n")));
+ document.body.appendChild(lognode);
+}
+
+logEvent('end of second script block');
+
+localStrings = new LocalStrings($('l10n'));
+
+// We've got all the JS we need, render any unprocessed data.
+renderFunctionsDefined = true;
+processData();
+
+// In case renderMostVisitedItems doesn't come back quickly enough, begin
+// the first-run fade-in. If it has started or if this is not a first
+// run new tab, this will be a no-op.
+setTimeout(function(){$('main').className = 'visible'},
+ 1000);
+</script>
+
+<img id="small-cross-image" style="display: none; vertical-align:middle;" alt="X"
+ src="../../app/theme/ntp_x_icon_small.png"/>
+</body>
+
+<style>
+/* This CSS code is located at the end of file so it does not slow-down the page
+ loading, as it contains inlined images.
+*/
+.edit-mode div.edit-cross {
+ position: absolute;
+ z-index: 10;
+ width: 81px;
+ height: 81px;
+ left: 60px;
+ top: 47px;
+ background: url('../../app/theme/ntp_x_icon.png');
+}
+.edit-mode div.edit-cross:hover {
+ background: url('../../app/theme/ntp_x_icon_hover.png');
+}
+.edit-mode div.edit-cross:active {
+ background: url('../../app/theme/ntp_x_icon_active.png');
+}
+.recent-window-container {
+ background: url('../../app/theme/closed_window.png');
+ background-repeat: no-repeat;
+}
+html[dir='rtl'] .recent-window-container {
+ background-position: right;
+ padding-right: 22px;
+}
+</style>
+</html>