diff options
author | arv@chromium.org <arv@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-19 00:55:50 +0000 |
---|---|---|
committer | arv@chromium.org <arv@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-19 00:55:50 +0000 |
commit | 5ed4ba5f65ce49c736bb16ddff31948c17c5938e (patch) | |
tree | 35f65274447876453c22ad46f7b2ef6ab53e4830 /chrome/browser | |
parent | abcfb02cc5b7605049855bf0a1c53e445f51be4b (diff) | |
download | chromium_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.vcproj | 8 | ||||
-rw-r--r-- | chrome/browser/browser_resources.grd | 1 | ||||
-rw-r--r-- | chrome/browser/dom_ui/downloads_dom_handler.cc | 334 | ||||
-rw-r--r-- | chrome/browser/dom_ui/downloads_dom_handler.h | 97 | ||||
-rw-r--r-- | chrome/browser/dom_ui/downloads_ui.cc | 406 | ||||
-rw-r--r-- | chrome/browser/dom_ui/new_tab_ui.cc | 29 | ||||
-rw-r--r-- | chrome/browser/dom_ui/new_tab_ui.h | 7 | ||||
-rw-r--r-- | chrome/browser/resources/new_new_tab.html | 1401 |
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"> </div></td> + <td><div class="thumbnail"></div></td> + <td><div class="thumbnail"> </div></td> + <td><div class="thumbnail"></div></td> + </tr> + <tr> + <td><div class="thumbnail"> </div></td> + <td><div class="thumbnail"></div></td> + <td><div class="thumbnail"> </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"> </div> + <div class="thumbnail"></div> + </td> + <td> + <div class="thumbnail-title"> </div> + <div class="thumbnail"></div> + </td> + <td> + <div class="thumbnail-title"> </div> + <div class="thumbnail"></div> + </td> + <td> + <div class="thumbnail-title"> </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> »</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 = ' '; + } + + // 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> |