summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
authorrvargas@google.com <rvargas@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-27 00:35:56 +0000
committerrvargas@google.com <rvargas@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-27 00:35:56 +0000
commited95ffbea8f0558c75b99b312b917a71b088a4be (patch)
treee2c7c2f0089726be5b2ac05545cdfff3185d452d /chrome/browser
parenta4fe3a78551184e580ea94cee4c25628b0bb2a23 (diff)
downloadchromium_src-ed95ffbea8f0558c75b99b312b917a71b088a4be.zip
chromium_src-ed95ffbea8f0558c75b99b312b917a71b088a4be.tar.gz
chromium_src-ed95ffbea8f0558c75b99b312b917a71b088a4be.tar.bz2
revert cl 19438
BUG=none TEST=none Review URL: http://codereview.chromium.org/149116 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@19444 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-rw-r--r--chrome/browser/browser_resources.grd7
-rw-r--r--chrome/browser/dom_ui/new_tab_ui.cc384
-rw-r--r--chrome/browser/dom_ui/new_tab_ui.h3
-rw-r--r--chrome/browser/resources/new_new_tab.css (renamed from chrome/browser/resources/new_tab.css)0
-rw-r--r--chrome/browser/resources/new_new_tab.html191
-rw-r--r--chrome/browser/resources/new_new_tab.js (renamed from chrome/browser/resources/new_tab.js)0
-rw-r--r--chrome/browser/resources/new_tab.html1153
7 files changed, 1554 insertions, 184 deletions
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 8b72c0c..e8938c4 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -17,10 +17,11 @@ without changes to the corresponding grd file. -->
<include name="IDR_ABOUT_STATS_HTML" file="resources\about_stats.html" type="BINDATA" />
<include name="IDR_SSL_ROAD_BLOCK_HTML" file="security\resources\ssl_roadblock.html" flattenhtml="true" type="BINDATA" />
<include name="IDR_SSL_ERROR_HTML" file="security\resources\ssl_error.html" flattenhtml="true" type="BINDATA" />
- <include name="IDR_NEW_TAB_THEME_CSS" file="resources\new_tab_theme.css" type="BINDATA" />
<include name="IDR_NEW_TAB_HTML" file="resources\new_tab.html" flattenhtml="true" type="BINDATA" />
- <include name="IDR_NEW_TAB_CSS" file="resources\new_tab.css" type="BINDATA" />
- <include name="IDR_NEW_TAB_JS" file="resources\new_tab.js" 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_NEW_NEW_TAB_CSS" file="resources\new_new_tab.css" type="BINDATA" />
+ <include name="IDR_NEW_NEW_TAB_JS" file="resources\new_new_tab.js" 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/new_tab_ui.cc b/chrome/browser/dom_ui/new_tab_ui.cc
index f229296..9be3e72 100644
--- a/chrome/browser/dom_ui/new_tab_ui.cc
+++ b/chrome/browser/dom_ui/new_tab_ui.cc
@@ -203,10 +203,10 @@ class NewTabHTMLSource : public ChromeURLDataManager::DataSource {
static bool first_view() { return first_view_; }
private:
- // In case a file path to the new tab page was provided this tries to load
+ // In case a file path to the new new tab page was provided this tries to load
// the file and returns the file content if successful. This returns an empty
// string in case of failure.
- static std::string GetNewTabPageFromCommandLine();
+ static std::string GetNewNewTabFromCommandLine();
// Whether this is the is the first viewing of the new tab page and
// we think it is the user's startup page.
@@ -349,18 +349,22 @@ void NewTabHTMLSource::StartDataRequest(const std::string& path,
localized_strings.SetString(L"p13nsrc", Personalization::GetNewTabSource());
#endif
- // In case we have a custom new tab page enabled we first try to read the
- // file provided on the command line. If that fails we just get the default
- // resource from the resource bundle.
+ // In case we have the new new tab page enabled we first try to read the file
+ // provided on the command line. If that fails we just get the resource from
+ // the resource bundle.
StringPiece new_tab_html;
- std::string new_tab_html_str = GetNewTabPageFromCommandLine();
+ std::string new_tab_html_str;
+ if (NewTabUI::EnableNewNewTabPage()) {
+ new_tab_html_str = GetNewNewTabFromCommandLine();
- if (!new_tab_html_str.empty()) {
- new_tab_html = StringPiece(new_tab_html_str);
- }
-
- // No custom new tab page or the file was empty.
- if (new_tab_html.empty()) {
+ if (!new_tab_html_str.empty()) {
+ new_tab_html = StringPiece(new_tab_html_str);
+ } else {
+ // Use the new new tab page from the resource bundle.
+ new_tab_html = ResourceBundle::GetSharedInstance().GetRawDataResource(
+ IDR_NEW_NEW_TAB_HTML);
+ }
+ } else {
// Use the default new tab page resource.
new_tab_html = ResourceBundle::GetSharedInstance().GetRawDataResource(
IDR_NEW_TAB_HTML);
@@ -377,10 +381,10 @@ void NewTabHTMLSource::StartDataRequest(const std::string& path,
}
// static
-std::string NewTabHTMLSource::GetNewTabPageFromCommandLine() {
+std::string NewTabHTMLSource::GetNewNewTabFromCommandLine() {
const CommandLine* command_line = CommandLine::ForCurrentProcess();
const std::wstring file_path_wstring = command_line->GetSwitchValue(
- switches::kNewTabPage);
+ switches::kNewNewTabPage);
#if defined(OS_WIN)
const FilePath::StringType file_path = file_path_wstring;
@@ -818,6 +822,279 @@ void MostVisitedHandler::RegisterUserPrefs(PrefService* prefs) {
}
///////////////////////////////////////////////////////////////////////////////
+// TemplateURLHandler
+
+// The handler for Javascript messages related to the "common searches" view.
+class TemplateURLHandler : public DOMMessageHandler,
+ public TemplateURLModelObserver {
+ public:
+ explicit TemplateURLHandler(DOMUI* dom_ui);
+ virtual ~TemplateURLHandler();
+
+ // Callback for the "getMostSearched" message, sent when the page requests
+ // the list of available searches.
+ void HandleGetMostSearched(const Value* content);
+ // Callback for the "doSearch" message, sent when the user wants to
+ // run a search. Content of the message is an array containing
+ // [<the search keyword>, <the search term>].
+ void HandleDoSearch(const Value* content);
+
+ // TemplateURLModelObserver implementation.
+ virtual void OnTemplateURLModelChanged();
+
+ private:
+ DOMUI* dom_ui_;
+ TemplateURLModel* template_url_model_; // Owned by profile.
+
+ DISALLOW_COPY_AND_ASSIGN(TemplateURLHandler);
+};
+
+TemplateURLHandler::TemplateURLHandler(DOMUI* dom_ui)
+ : DOMMessageHandler(dom_ui),
+ dom_ui_(dom_ui),
+ template_url_model_(NULL) {
+ dom_ui->RegisterMessageCallback("getMostSearched",
+ NewCallback(this, &TemplateURLHandler::HandleGetMostSearched));
+ dom_ui->RegisterMessageCallback("doSearch",
+ NewCallback(this, &TemplateURLHandler::HandleDoSearch));
+}
+
+TemplateURLHandler::~TemplateURLHandler() {
+ if (template_url_model_)
+ template_url_model_->RemoveObserver(this);
+}
+
+void TemplateURLHandler::HandleGetMostSearched(const Value* content) {
+ // The page Javascript has requested the list of keyword searches.
+ // Start loading them from the template URL backend.
+ if (!template_url_model_) {
+ template_url_model_ = dom_ui_->GetProfile()->GetTemplateURLModel();
+ template_url_model_->AddObserver(this);
+ }
+ if (template_url_model_->loaded()) {
+ OnTemplateURLModelChanged();
+ } else {
+ template_url_model_->Load();
+ }
+}
+
+// A helper function for sorting TemplateURLs where the most used ones show up
+// first.
+static bool TemplateURLSortByUsage(const TemplateURL* a,
+ const TemplateURL* b) {
+ return a->usage_count() > b->usage_count();
+}
+
+void TemplateURLHandler::HandleDoSearch(const Value* content) {
+ // Extract the parameters out of the input list.
+ if (!content || !content->IsType(Value::TYPE_LIST)) {
+ NOTREACHED();
+ return;
+ }
+ const ListValue* args = static_cast<const ListValue*>(content);
+ if (args->GetSize() != 2) {
+ NOTREACHED();
+ return;
+ }
+ std::wstring keyword, search;
+ Value* value = NULL;
+ if (!args->Get(0, &value) || !value->GetAsString(&keyword)) {
+ NOTREACHED();
+ return;
+ }
+ if (!args->Get(1, &value) || !value->GetAsString(&search)) {
+ NOTREACHED();
+ return;
+ }
+
+ // Combine the keyword and search into a URL.
+ const TemplateURL* template_url =
+ template_url_model_->GetTemplateURLForKeyword(keyword);
+ if (!template_url) {
+ // The keyword seems to have changed out from under us.
+ // Not an error, but nothing we can do...
+ return;
+ }
+ const TemplateURLRef* url_ref = template_url->url();
+ if (!url_ref || !url_ref->SupportsReplacement()) {
+ NOTREACHED();
+ return;
+ }
+ GURL url = GURL(WideToUTF8(url_ref->ReplaceSearchTerms(*template_url, search,
+ TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, std::wstring())));
+
+ if (url.is_valid()) {
+ // Record the user action
+ std::vector<const TemplateURL*> urls =
+ template_url_model_->GetTemplateURLs();
+ sort(urls.begin(), urls.end(), TemplateURLSortByUsage);
+ ListValue urls_value;
+ int item_number = 0;
+ for (size_t i = 0;
+ i < std::min<size_t>(urls.size(), kSearchURLs); ++i) {
+ if (urls[i]->usage_count() == 0)
+ break; // The remainder would be no good.
+
+ const TemplateURLRef* urlref = urls[i]->url();
+ if (!urlref)
+ continue;
+
+ if (urls[i] == template_url) {
+ UserMetrics::RecordComputedAction(
+ StringPrintf(L"NTP_SearchURL%d", item_number),
+ dom_ui_->GetProfile());
+ break;
+ }
+
+ item_number++;
+ }
+
+ // Load the URL.
+ dom_ui_->tab_contents()->OpenURL(url, GURL(), CURRENT_TAB,
+ PageTransition::LINK);
+ // We've been deleted.
+ return;
+ }
+}
+
+void TemplateURLHandler::OnTemplateURLModelChanged() {
+ // We've loaded some template URLs. Send them to the page.
+ std::vector<const TemplateURL*> urls = template_url_model_->GetTemplateURLs();
+ sort(urls.begin(), urls.end(), TemplateURLSortByUsage);
+ ListValue urls_value;
+ for (size_t i = 0; i < std::min<size_t>(urls.size(), kSearchURLs); ++i) {
+ if (urls[i]->usage_count() == 0)
+ break; // urls is sorted by usage count; the remainder would be no good.
+
+ const TemplateURLRef* urlref = urls[i]->url();
+ if (!urlref)
+ continue;
+ DictionaryValue* entry_value = new DictionaryValue;
+ entry_value->SetString(L"short_name", urls[i]->short_name());
+ entry_value->SetString(L"keyword", urls[i]->keyword());
+
+ const GURL& url = urls[i]->GetFavIconURL();
+ if (url.is_valid())
+ entry_value->SetString(L"favIconURL", UTF8ToWide(url.spec()));
+
+ urls_value.Append(entry_value);
+ }
+ UMA_HISTOGRAM_COUNTS("NewTabPage.SearchURLs.Total", urls_value.GetSize());
+ dom_ui_->CallJavascriptFunction(L"searchURLs", urls_value);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// RecentlyBookmarkedHandler
+
+class RecentlyBookmarkedHandler : public DOMMessageHandler,
+ public BookmarkModelObserver {
+ public:
+ explicit RecentlyBookmarkedHandler(DOMUI* dom_ui);
+ ~RecentlyBookmarkedHandler();
+
+ // Callback which navigates to the bookmarks page.
+ void HandleShowBookmarkPage(const Value*);
+
+ // Callback for the "getRecentlyBookmarked" message.
+ // It takes no arguments.
+ void HandleGetRecentlyBookmarked(const Value*);
+
+ private:
+ void SendBookmarksToPage();
+
+ // BookmarkModelObserver methods. These invoke SendBookmarksToPage.
+ virtual void Loaded(BookmarkModel* model);
+ virtual void BookmarkNodeAdded(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int index);
+ virtual void BookmarkNodeRemoved(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int index);
+ virtual void BookmarkNodeChanged(BookmarkModel* model,
+ const BookmarkNode* node);
+
+ // These won't effect what is shown, so they do nothing.
+ virtual void BookmarkNodeMoved(BookmarkModel* model,
+ const BookmarkNode* old_parent,
+ int old_index,
+ const BookmarkNode* new_parent,
+ int new_index) {}
+ virtual void BookmarkNodeChildrenReordered(BookmarkModel* model,
+ const BookmarkNode* node) {}
+ virtual void BookmarkNodeFavIconLoaded(BookmarkModel* model,
+ const BookmarkNode* node) {}
+
+ DOMUI* dom_ui_;
+ // The model we're getting bookmarks from. The model is owned by the Profile.
+ BookmarkModel* model_;
+
+ DISALLOW_COPY_AND_ASSIGN(RecentlyBookmarkedHandler);
+};
+
+RecentlyBookmarkedHandler::RecentlyBookmarkedHandler(DOMUI* dom_ui)
+ : DOMMessageHandler(dom_ui),
+ dom_ui_(dom_ui),
+ model_(NULL) {
+ dom_ui->RegisterMessageCallback("getRecentlyBookmarked",
+ NewCallback(this,
+ &RecentlyBookmarkedHandler::HandleGetRecentlyBookmarked));
+}
+
+RecentlyBookmarkedHandler::~RecentlyBookmarkedHandler() {
+ if (model_)
+ model_->RemoveObserver(this);
+}
+
+void RecentlyBookmarkedHandler::HandleGetRecentlyBookmarked(const Value*) {
+ if (!model_) {
+ model_ = dom_ui_->GetProfile()->GetBookmarkModel();
+ model_->AddObserver(this);
+ }
+ // If the model is loaded, synchronously send the bookmarks down. Otherwise
+ // when the model loads we'll send the bookmarks down.
+ if (model_->IsLoaded())
+ SendBookmarksToPage();
+}
+
+void RecentlyBookmarkedHandler::SendBookmarksToPage() {
+ std::vector<const BookmarkNode*> recently_bookmarked;
+ bookmark_utils::GetMostRecentlyAddedEntries(
+ model_, kRecentBookmarks, &recently_bookmarked);
+ ListValue list_value;
+ for (size_t i = 0; i < recently_bookmarked.size(); ++i) {
+ const BookmarkNode* node = recently_bookmarked[i];
+ 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);
+}
+
+void RecentlyBookmarkedHandler::Loaded(BookmarkModel* model) {
+ SendBookmarksToPage();
+}
+
+void RecentlyBookmarkedHandler::BookmarkNodeAdded(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int index) {
+ SendBookmarksToPage();
+}
+
+void RecentlyBookmarkedHandler::BookmarkNodeRemoved(BookmarkModel* model,
+ const BookmarkNode* parent,
+ int index) {
+ SendBookmarksToPage();
+}
+
+void RecentlyBookmarkedHandler::BookmarkNodeChanged(BookmarkModel* model,
+ const BookmarkNode* node) {
+ SendBookmarksToPage();
+}
+
+///////////////////////////////////////////////////////////////////////////////
// RecentlyClosedTabsHandler
class RecentlyClosedTabsHandler : public DOMMessageHandler,
@@ -1005,6 +1282,50 @@ bool RecentlyClosedTabsHandler::WindowToValue(
}
///////////////////////////////////////////////////////////////////////////////
+// HistoryHandler
+
+class HistoryHandler : public DOMMessageHandler {
+ public:
+ explicit HistoryHandler(DOMUI* dom_ui);
+
+ // Callback which navigates to the history page and performs a search.
+ void HandleSearchHistoryPage(const Value* content);
+
+ private:
+ DOMUI* dom_ui_;
+
+ DISALLOW_COPY_AND_ASSIGN(HistoryHandler);
+};
+
+HistoryHandler::HistoryHandler(DOMUI* dom_ui)
+ : DOMMessageHandler(dom_ui),
+ dom_ui_(dom_ui) {
+ dom_ui->RegisterMessageCallback("searchHistoryPage",
+ NewCallback(this, &HistoryHandler::HandleSearchHistoryPage));
+}
+
+void HistoryHandler::HandleSearchHistoryPage(const Value* content) {
+ if (content && content->GetType() == Value::TYPE_LIST) {
+ const ListValue* list_value = static_cast<const ListValue*>(content);
+ Value* list_member;
+ if (list_value->Get(0, &list_member) &&
+ list_member->GetType() == Value::TYPE_STRING) {
+ const StringValue* string_value =
+ static_cast<const StringValue*>(list_member);
+ std::wstring wstring_value;
+ if (string_value->GetAsString(&wstring_value)) {
+ UserMetrics::RecordAction(L"NTP_SearchHistory", dom_ui_->GetProfile());
+ dom_ui_->tab_contents()->controller().LoadURL(
+ HistoryUI::GetHistoryURLWithSearchText(wstring_value),
+ GURL(),
+ PageTransition::LINK);
+ // We are deleted by LoadURL, so do not call anything else.
+ }
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
// MetricsHandler
// Let the page contents record UMA actions. Only use when you can't do it from
@@ -1088,20 +1409,26 @@ NewTabUI::NewTabUI(TabContents* contents)
&ChromeURLDataManager::AddDataSource,
html_source));
} else {
- AddMessageHandler(new MostVisitedHandler(this));
- AddMessageHandler(new ShownSectionsHandler(this));
- AddMessageHandler(new RecentlyClosedTabsHandler(this));
- AddMessageHandler(new MetricsHandler(this));
- // TODO(arv): What if this is not enabled?
+ if (EnableNewNewTabPage()) {
+ DownloadManager* dlm = GetProfile()->GetDownloadManager();
+ DownloadsDOMHandler* downloads_handler =
+ new DownloadsDOMHandler(this, dlm);
+ AddMessageHandler(downloads_handler);
+ downloads_handler->Init();
+
+ AddMessageHandler(new ShownSectionsHandler(this));
+ }
+
if (EnableWebResources())
AddMessageHandler(new TipsHandler(this));
- DownloadManager* dlm = GetProfile()->GetDownloadManager();
- DownloadsDOMHandler* downloads_handler =
- new DownloadsDOMHandler(this, dlm);
- AddMessageHandler(downloads_handler);
- downloads_handler->Init();
+ AddMessageHandler(new TemplateURLHandler(this));
+ AddMessageHandler(new MostVisitedHandler(this));
+ AddMessageHandler(new RecentlyBookmarkedHandler(this));
+ AddMessageHandler(new RecentlyClosedTabsHandler(this));
+ AddMessageHandler(new HistoryHandler(this));
+ AddMessageHandler(new MetricsHandler(this));
#ifdef CHROME_PERSONALIZATION
if (!Personalization::IsP13NDisabled(GetProfile())) {
AddMessageHandler(Personalization::CreateNewTabPageHandler(this));
@@ -1151,9 +1478,16 @@ void NewTabUI::Observe(NotificationType type,
// static
void NewTabUI::RegisterUserPrefs(PrefService* prefs) {
MostVisitedHandler::RegisterUserPrefs(prefs);
- ShownSectionsHandler::RegisterUserPrefs(prefs);
if (NewTabUI::EnableWebResources())
TipsHandler::RegisterUserPrefs(prefs);
+ if (NewTabUI::EnableNewNewTabPage())
+ ShownSectionsHandler::RegisterUserPrefs(prefs);
+}
+
+// static
+bool NewTabUI::EnableNewNewTabPage() {
+ const CommandLine* command_line = CommandLine::ForCurrentProcess();
+ return command_line->HasSwitch(switches::kNewNewTabPage);
}
bool NewTabUI::EnableWebResources() {
diff --git a/chrome/browser/dom_ui/new_tab_ui.h b/chrome/browser/dom_ui/new_tab_ui.h
index b51c408..851dbfa 100644
--- a/chrome/browser/dom_ui/new_tab_ui.h
+++ b/chrome/browser/dom_ui/new_tab_ui.h
@@ -25,6 +25,9 @@ class NewTabUI : public DOMUI,
static void RegisterUserPrefs(PrefService* prefs);
+ // Whether we should use the prototype new tab page.
+ static bool EnableNewNewTabPage();
+
// Whether we should enable the web resources backend service
static bool EnableWebResources();
diff --git a/chrome/browser/resources/new_tab.css b/chrome/browser/resources/new_new_tab.css
index 352417d..352417d 100644
--- a/chrome/browser/resources/new_tab.css
+++ b/chrome/browser/resources/new_new_tab.css
diff --git a/chrome/browser/resources/new_new_tab.html b/chrome/browser/resources/new_new_tab.html
new file mode 100644
index 0000000..d3a2d22
--- /dev/null
+++ b/chrome/browser/resources/new_new_tab.html
@@ -0,0 +1,191 @@
+<!DOCTYPE html>
+<html id="t" jsvalues="dir:textdirection;firstview:firstview;bookmarkbarattached:bookmarkbarattached;hasattribution:hasattribution;anim:anim">
+
+<meta charset="utf-8">
+<title jscontent="title"></title>
+<script>
+// Logging info for benchmarking purposes.
+var log = [];
+function logEvent(name) {
+ log.push([name, Date.now()]);
+}
+
+var global = this;
+
+/**
+ * Registers a callback function so that if the backend calls it too early it
+ * will get delayed until DOMContentLoaded is fired.
+ * @param {string} name The name of the global function that the backend calls.
+ */
+function registerCallback(name) {
+ var f = function(var_args) {
+ var args = Array.prototype.slice.call(arguments);
+ // If we still have the temporary function we delay until the dom is ready.
+ if (global[name] == f) {
+ logEvent(name + ' is not yet ready. Waiting for DOMContentLoaded');
+ document.addEventListener('DOMContentLoaded', function() {
+ logEvent('Calling the new ' + name);
+ global[name].apply(null, args);
+ });
+ }
+ };
+ global[name] = f;
+}
+
+chrome.send('getShownSections');
+chrome.send('getMostVisited');
+chrome.send('getDownloads');
+chrome.send('getRecentlyClosedTabs');
+chrome.send('getTips');
+
+registerCallback('onShownSections');
+registerCallback('mostVisitedPages');
+registerCallback('downloadsList');
+registerCallback('recentlyClosedTabs');
+registerCallback('tips');
+
+logEvent('log start');
+
+</script>
+<link rel="stylesheet" href="new_new_tab.css">
+<link id="themecss" rel="stylesheet" href="chrome://theme/css/newtab.css">
+</head>
+<body class="loading"
+ jsvalues=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+
+<div id="main">
+
+ <div id="view-toolbar"><input type=checkbox id="thumb-checkbox"
+ ><input type=checkbox id="list-checkbox"
+ ><input type="button" id="option-button"></div>
+
+
+<div id="option-menu" class="window-menu">
+ <div section="THUMB" show="true" jscontent="showthumbnails"></div>
+ <div section="THUMB" show="false" jscontent="hidethumbnails"></div>
+ <div section="LIST" show="true" jscontent="showlist"></div>
+ <div section="LIST" show="false" jscontent="hidelist"></div>
+ <div section="RECENT" show="true" jscontent="showrecent"></div>
+ <div section="RECENT" show="false" jscontent="hiderecent"></div>
+ <div section="TIPS" show="true"
+ jscontent="showtips"></div>
+ <div section="TIPS" show="false"
+ jscontent="hidetips"></div>
+</div>
+
+ <div id="notification">
+ <span>&nbsp;</span>
+ <span><span class="link" tabindex="0"></span></span>
+ </div>
+
+ <div id="most-visited" jsskip="!processing">
+ <a class="thumbnail-container" style="display:none" id="thumbnail-template">
+ <div class="edit-mode-border">
+ <div class="edit-bar">
+ <div class="pin"></div>
+ <div class="spacer"></div>
+ <div class="remove"></div>
+ </div>
+ <span class="thumbnail-wrapper">
+ <span class="thumbnail"></span>
+ </span>
+ </div>
+ <div class="title">
+ <div></div>
+ </div>
+ </a>
+
+ </div>
+
+ <div id="lower-sections">
+
+ <div id="recent-activities" class="section">
+ <h2 jscontent="recentactivities"></h2>
+
+ <div class="hbox">
+
+ <div id="recent-tabs">
+ <h3 jscontent="recentlyclosed"></h3>
+
+ <div class="item-container">
+ <div id="tab-items" jsskip="!processing">
+ <div jsselect="$this">
+ <a class="item"
+ jsdisplay="type == 'tab'"
+ jsvalues="href:url;
+ title:title;
+ .style.backgroundImage:'url(chrome://favicon/' + url +
+ ')';
+ dir:direction;
+ .sessionId:sessionId"
+ jscontent="title"></a>
+ <div jsdisplay="type == 'window'"
+ class="item link window"
+ jsvalues=".sessionId:sessionId;
+ .tabItems:tabs"
+ tabindex="0">
+ <span jscontent="formatTabsText($this.tabs.length)"></span>
+ </div>
+ </div>
+ </div>
+
+ <div>
+ <a href="chrome://history/" class="item nav"
+ jscontent="viewfullhistory"></a>
+ </div>
+
+ </div>
+ </div>
+
+ <div id="downloads">
+ <h3 jscontent="downloads"></h3>
+ <div class="item-container">
+ <div id="download-items" jsskip="!processing">
+ <a class="item" jsselect="$this"
+ jsdisplay="state != 'CANCELLED'"
+ jsvalues="href:file_path;title:url;
+ .style.backgroundImage:'url(chrome://fileicon/' +
+ file_path + ')';
+ dir:direction;
+ .fileId:id"
+ jscontent="file_name"></a>
+ </div>
+
+ <div>
+ <a href="chrome://downloads/" class="item nav"
+ jscontent="viewalldownloads"></a>
+ </div>
+
+ </div>
+ </div>
+
+ </div>
+
+ </div><div class="spacer">
+
+ </div><div id="tips" class="section">
+ <h2>Tips and Suggestions</h2>
+ <div class="item-container">
+ <div id="tip-items" jsskip="!processing">
+ <a class="item" jsselect="$this"
+ jsvalues="href:url;title:title"
+ jscontent="title"></a>
+ </div>
+ </div>
+ </div>
+ </div>
+
+</div> <!-- main -->
+
+<div jsskip="true">
+ <div class="window-menu" id="window-menu">
+ <span class="item" jsselect="$this"
+ jsvalues=".style.backgroundImage:'url(chrome://favicon/' + url + ')';
+ dir:direction;"
+ jscontent="title"></span>
+ </div>
+</div>
+
+<script src="local_strings.js"></script>
+<script src="new_new_tab.js"></script>
+</html>
diff --git a/chrome/browser/resources/new_tab.js b/chrome/browser/resources/new_new_tab.js
index ccd6355..ccd6355 100644
--- a/chrome/browser/resources/new_tab.js
+++ b/chrome/browser/resources/new_new_tab.js
diff --git a/chrome/browser/resources/new_tab.html b/chrome/browser/resources/new_tab.html
index 65a059a..e480c66 100644
--- a/chrome/browser/resources/new_tab.html
+++ b/chrome/browser/resources/new_tab.html
@@ -1,191 +1,1032 @@
-<!DOCTYPE html>
+<!DOCTYPE HTML>
<html id="t" jsvalues="dir:textdirection;firstview:firstview;bookmarkbarattached:bookmarkbarattached;hasattribution:hasattribution;anim:anim">
+<!--
+ 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.
-<meta charset="utf-8">
-<title jscontent="title"></title>
+ 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.
+/* Logging info for benchmarking purposes. */
var log = [];
function logEvent(name) {
log.push([name, Date.now()]);
}
-var global = this;
+// Basic functions to send, receive, store and process the data from our
+// backend.
+var unprocessedData = {
+ mostVisitedPages : false,
+ searchURLs : false,
+ recentlyBookmarked : false,
+ recentlyClosedTabs : false
+}
+
+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);}
/**
- * Registers a callback function so that if the backend calls it too early it
- * will get delayed until DOMContentLoaded is fired.
- * @param {string} name The name of the global function that the backend calls.
+ * If the functions that can render content are defined, render
+ * the content for any data we've received so far.
*/
-function registerCallback(name) {
- var f = function(var_args) {
- var args = Array.prototype.slice.call(arguments);
- // If we still have the temporary function we delay until the dom is ready.
- if (global[name] == f) {
- logEvent(name + ' is not yet ready. Waiting for DOMContentLoaded');
- document.addEventListener('DOMContentLoaded', function() {
- logEvent('Calling the new ' + name);
- global[name].apply(null, args);
- });
+function processData() {
+ if (renderFunctionsDefined) {
+ if (unprocessedData.mostVisitedPages) {
+ renderMostVisitedPages(unprocessedData.mostVisitedPages);
+ unprocessedData.mostVisitedPages = false;
}
- };
- global[name] = f;
+ if (unprocessedData.searchURLs) {
+ renderSearchURLs(unprocessedData.searchURLs);
+ unprocessedData.searchURLs = false;
+ }
+ if (unprocessedData.recentlyBookmarked) {
+ renderRecentlyBookmarked(unprocessedData.recentlyBookmarked);
+ unprocessedData.recentlyBookmarked = false;
+ }
+ if (unprocessedData.recentlyClosedTabs) {
+ renderRecentlyClosedTabs(unprocessedData.recentlyClosedTabs);
+ unprocessedData.recentlyClosedTabs = false;
+ }
+ }
+}
+
+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');
+ unprocessedData.recentlyBookmarked = data;
+ processData();
+}
+
+function recentlyClosedTabs(data) {
+ logEvent('received recently closed tabs');
+ unprocessedData.recentlyClosedTabs = data;
+ processData();
+}
+
+function resizeP13N(new_height) {
+ var childf = document.getElementById('p13n');
+ if (new_height < 1) {
+ childf.style.display = "none";
+ return;
+ }
+ childf.height = new_height;
+ childf.style.display = "block";
}
-chrome.send('getShownSections');
chrome.send('getMostVisited');
-chrome.send('getDownloads');
+chrome.send('getMostSearched');
+chrome.send('getRecentlyBookmarked');
chrome.send('getRecentlyClosedTabs');
-chrome.send('getTips');
-registerCallback('onShownSections');
-registerCallback('mostVisitedPages');
-registerCallback('downloadsList');
-registerCallback('recentlyClosedTabs');
-registerCallback('tips');
+function handleWindowResize() {
+ if (!document.body || document.body.clientWidth < 10) {
+ // We're probably a background tab, so don't do anything.
+ return;
+ }
-logEvent('log start');
+ if (document.body.className == 'small' && document.body.clientWidth >= 885) {
+ document.body.className = '';
+ } else if (document.body.className == '' && document.body.clientWidth <= 865) {
+ document.body.className = 'small';
+ }
+}
+
+function handleDOMContentLoaded() {
+ logEvent('domcontentloaded fired');
+}
+logEvent('log start');
</script>
-<link rel="stylesheet" href="new_tab.css">
-<link id="themecss" rel="stylesheet" href="chrome://theme/css/newtab.css">
+<head>
+<meta charset="utf-8">
+<title jscontent="title"></title>
+<style>
+html {
+ height:100%;
+}
+body {
+ margin:0px;
+}
+html[firstview='true'] #main {
+ opacity:0.0;
+ -webkit-transition:all 0.4s;
+}
+html[firstview='true'] #main.visible {
+ opacity:1.0;
+}
+html[anim='false'] * {
+ -webkit-transition-property: none !important
+}
+#main {
+ margin-left:auto;
+ margin-right:auto;
+ margin-top:10px;
+}
+form {
+ padding: 0;
+ margin: 0;
+}
+.section {
+ padding:3px 0px 5px 0px;
+ margin-bottom:30px;
+}
+.section-title {
+ line-height:19pt;
+ font-size:110%;
+ font-weight:bold;
+ margin-bottom:4px;
+}
+#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 {
+ width:548px; /* thumbnail + td * 3 - 2*padding - 2*margin */
+ padding:20px;
+ margin:15px;
+ background-color:white;
+ -webkit-box-shadow: 5px 5px 10px #ccc;
+ -webkit-transition:all 0.12s;
+}
+.thumbnail-title {
+ background-image:url(chrome://favicon/);
+ display:block;
+ background-repeat:no-repeat;
+ background-size:16px;
+ background-position:0px 1px;
+ width:172px; /* thumbnail - padding */
+ margin-top:6px; /* line up favicons with search favicons */
+ padding:1px 0px 4px 22px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ text-decoration:none;
+ -webkit-transition:all 0.12s;
+}
+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 0.12s;
+}
+a.thumbnail {
+ border:1px solid #abe;
+}
+
+.small .thumbnail-title {
+ width:127px;
+}
+.small .thumbnail {
+ width:150px;
+ height:113px;
+}
+.small .most-visited-text {
+ width:430px;
+ padding:15px;
+ margin:12px;
+}
+.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;
+ margin:3px 0px 3px 0px;
+ padding-left:22px;
+}
+.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;
+}
+
+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;
+}
+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;
+}
+html[hasattribution='false'] .attribution {
+ display:none;
+}
+.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>
+<link id="themecss" rel="stylesheet" href="chrome://theme/css/newtab.css" />
</head>
-<body class="loading"
+<body onload="updateAttribution(); logEvent('body onload fired');"
jsvalues=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
+<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="view-toolbar"><input type=checkbox id="thumb-checkbox"
- ><input type=checkbox id="list-checkbox"
- ><input type="button" id="option-button"></div>
-
-
-<div id="option-menu" class="window-menu">
- <div section="THUMB" show="true" jscontent="showthumbnails"></div>
- <div section="THUMB" show="false" jscontent="hidethumbnails"></div>
- <div section="LIST" show="true" jscontent="showlist"></div>
- <div section="LIST" show="false" jscontent="hidelist"></div>
- <div section="RECENT" show="true" jscontent="showrecent"></div>
- <div section="RECENT" show="false" jscontent="hiderecent"></div>
- <div section="TIPS" show="true"
- jscontent="showtips"></div>
- <div section="TIPS" show="false"
- jscontent="hidetips"></div>
-</div>
-
- <div id="notification">
- <span>&nbsp;</span>
- <span><span class="link" tabindex="0"></span></span>
- </div>
-
- <div id="most-visited" jsskip="!processing">
- <a class="thumbnail-container" style="display:none" id="thumbnail-template">
- <div class="edit-mode-border">
- <div class="edit-bar">
- <div class="pin"></div>
- <div class="spacer"></div>
- <div class="remove"></div>
- </div>
- <span class="thumbnail-wrapper">
- <span class="thumbnail"></span>
- </span>
+<table id="main" cellpadding="0" cellspacing="0" border="0">
+ <tr>
+ <td valign="top">
+ <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 class="title">
- <div></div>
+ <div id="mostvisitedintro" style="display:none;">
+ <div class="most-visited-text" style="position:absolute;" 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>
+ </tr>
+ <tr>
+ <td><div class="thumbnail">&nbsp;</div></td>
+ <td><div class="thumbnail"></div></td>
+ <td><div class="thumbnail">&nbsp;</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>
+ </tr>
+ </table>
+ </div>
+ <a href="#"
+ class="manage non-edit-visible"
+ onclick="enterEditMode(); return false"
+ id="editthumbnails">
+ <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>
+ </td>
+ <td valign="top" width="230">
+ <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 themed">
+ <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 themed" 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 id="attribution" class="sidebar attribution">
+ <span jscontent="attributionintro"></span><br />
+ <img id="attribution-img" />
+ </div>
+ </td>
+ </tr>
+</table>
+
+<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="background-image:url(faviconurl);direction:ltr">gmail.com</div>
+ <img class="thumbnail disabled-on-edit"
+ style="background-image:url(thumbnailurl);" />
</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');
+ }
- </div>
+ /* 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;
- <div id="lower-sections">
-
- <div id="recent-activities" class="section">
- <h2 jscontent="recentactivities"></h2>
-
- <div class="hbox">
-
- <div id="recent-tabs">
- <h3 jscontent="recentlyclosed"></h3>
-
- <div class="item-container">
- <div id="tab-items" jsskip="!processing">
- <div jsselect="$this">
- <a class="item"
- jsdisplay="type == 'tab'"
- jsvalues="href:url;
- title:title;
- .style.backgroundImage:'url(chrome://favicon/' + url +
- ')';
- dir:direction;
- .sessionId:sessionId"
- jscontent="title"></a>
- <div jsdisplay="type == 'window'"
- class="item link window"
- jsvalues=".sessionId:sessionId;
- .tabItems:tabs"
- tabindex="0">
- <span jscontent="formatTabsText($this.tabs.length)"></span>
- </div>
- </div>
- </div>
-
- <div>
- <a href="chrome://history/" class="item nav"
- jscontent="viewfullhistory"></a>
- </div>
-
- </div>
- </div>
-
- <div id="downloads">
- <h3 jscontent="downloads"></h3>
- <div class="item-container">
- <div id="download-items" jsskip="!processing">
- <a class="item" jsselect="$this"
- jsdisplay="state != 'CANCELLED'"
- jsvalues="href:file_path;title:url;
- .style.backgroundImage:'url(chrome://fileicon/' +
- file_path + ')';
- dir:direction;
- .fileId:id"
- jscontent="file_name"></a>
- </div>
-
- <div>
- <a href="chrome://downloads/" class="item nav"
- jscontent="viewalldownloads"></a>
- </div>
-
- </div>
- </div>
+ /* Create the title */
+ var div_title = DOM('div', {className:'thumbnail-title disabled-on-edit'});
+ div_title.style.backgroundImage =
+ 'url("chrome://favicon/' + page.url + '")';
+ /* 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;
+ 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;';
+ }
- </div>
+ root.appendChild(div_title);
+ root.appendChild(img_thumbnail);
- </div><div class="spacer">
+ return root;
+}
- </div><div id="tips" class="section">
- <h2>Tips and Suggestions</h2>
- <div class="item-container">
- <div id="tip-items" jsskip="!processing">
- <a class="item" jsselect="$this"
- jsvalues="href:url;title:title"
- jscontent="title"></a>
- </div>
- </div>
- </div>
- </div>
+/* 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;
+}
-</div> <!-- main -->
+/* 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);
-<div jsskip="true">
- <div class="window-menu" id="window-menu">
- <span class="item" jsselect="$this"
- jsvalues=".style.backgroundImage:'url(chrome://favicon/' + url + ')';
- dir:direction;"
- jscontent="title"></span>
- </div>
-</div>
+ var table = document.getElementById("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 = document.getElementById("mostvisitedtable");
+ table.innerHTML = '';
+
+ // Show the most visited helptext if most visited is still useless. This is
+ // a crappy heuristic.
+ if (pages.length < 3) {
+ $('mostvisitedintro').style.display = 'block';
+ $('editthumbnails').style.display = 'none';
+ 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 % 3 == 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 = document.getElementById('searches-entries');
+ container.innerHTML = ''; // Clear out any previous contents.
+ if (urls.length > 0) {
+ document.getElementById('searches').style.display = 'block';
+ for (var i = 0; i < urls.length; ++i) {
+ container.appendChild(makeSearchURL(urls[i]));
+ }
+ }
+
+ logEvent('renderSearchURLs done');
+}
+
+/* This function is called by the browser when the list of recently bookmarked
+ URLs is available. |entries| is a list of objects with title, url, and
+ direction attributes. */
+function renderRecentlyBookmarked(entries) {
+ logEvent('renderRecentlyBookmarked called: ' + entries.length);
+ var section = document.getElementById('recentlyBookmarked');
+ var container = document.getElementById('recentlyBookmarkedContainer');
+
+ /* recentlyBookmarked is called any time the bookmarks change. Remove existing
+ entries before adding new ones. */
+ section.style.display = 'none';
+ container.innerHTML = '';
+
+ if (entries.length > 0) {
+ section.style.display = 'block';
+ for (var i = 0, entry = entries[0]; entry = entries[i]; ++i) {
+ var link = DOM('a', {href: entry.url,
+ className:'recent-bookmark',
+ title:entry.title});
+ link.addEventListener("mousedown", function(event) {
+ chrome.send("metrics", ["NTP_Bookmark" + i])
+ }, false);
+ link.style.backgroundImage =
+ 'url("chrome://favicon/' + entry.url + '")';
+ /* Set the bookmark title's directionality independently of the page, see
+ comment about setting div_title.style.direction above for details.
+ */
+ link.style.direction = entry.direction;
+ link.appendChild(document.createTextNode(entry.title));
+ container.appendChild(link);
+ }
+ }
+
+ logEvent('renderRecentlyBookmarked done');
+}
+
+/* This function adds incoming information about tabs to the new tab UI. */
+function renderRecentlyClosedTabs(entries) {
+ logEvent('renderRecentlyClosedTabs begin');
+ var section = document.getElementById('recentlyClosedTabs');
+ var container = document.getElementById('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.appendChild(document.createTextNode(" "));
+ 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.formatString('closedwindowmultiple', 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;
+ 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 = document.getElementById('cross-image-container');
+ if (crossImageDiv && !crossImageDiv.hasChildNodes()) {
+ var image = document.getElementById('small-cross-image');
+ image.parentNode.removeChild(image);
+ crossImageDiv.appendChild(image);
+ image.style.display = 'inline';
+ crossImageDiv.style.display = 'inline';
+ }
+ document.getElementById('main').className = 'visible edit-mode';
+ window.onunload = cancelEdits;
+}
+
+function exitEditMode() {
+ document.getElementById('main').className = 'visible';
+ blacklistedURLs = [];
+ window.onunload = null;
+}
+
+function cancelEdits() {
+ if (blacklistedURLs.length > 0) {
+ chrome.send('removeURLsFromMostVisitedBlacklist', blacklistedURLs);
+ // NOTE(arv): According to evanm the order of messages should be the order
+ // they are called in. However, he wasn't sure and glen wasn't sure either.
+ // We should keep our eyes open for weird issues and if the order is not
+ // guaranteed we can call 'getMostVisited' in a timeout.
+ chrome.send('getMostVisited');
+ }
+ exitEditMode();
+}
+
+function blacklistURL(url) {
+ blacklistedURLs.push(url);
+ chrome.send('blacklistURLFromMostVisited', [url]);
+ // NOTE(arv): See note in cancelEdits.
+ chrome.send('getMostVisited');
+}
+
+function restoreThumbnails() {
+ exitEditMode();
+ chrome.send('clearMostVisitedURLsBlacklist');
+ // NOTE(arv): See note in cancelEdits.
+ chrome.send('getMostVisited');
+}
+
+function themeChanged() {
+ $('themecss').href = 'chrome://theme/css/newtab.css?' + Date.now();
+ updateAttribution();
+}
+
+function updateAttribution() {
+ $('attribution-img').src = 'chrome://theme/theme_ntp_attribution?' +
+ Date.now();
+}
+
+function bookmarkBarAttached() {
+ document.documentElement.setAttribute("bookmarkbarattached", "true");
+}
+
+function bookmarkBarDetached() {
+ document.documentElement.setAttribute("bookmarkbarattached", "false");
+}
+
+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();
+
+// 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(){document.getElementById('main').className = 'visible'},
+ 1000);
+</script>
+
+<img id="small-cross-image" style="display: none; vertical-align:middle;" alt="X"
+ src="chrome://theme/newtab_remove_icon"/>
+</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(chrome://theme/newtab_remove_thumbnail);
+}
+.edit-mode div.edit-cross:hover {
+ background: url(chrome://theme/newtab_remove_thumbnail_hover);
+}
+.edit-mode div.edit-cross:active {
+ background: url(chrome://theme/newtab_remove_thumbnail_active);
+}
+.recent-window-container {
+ background: url(chrome://theme/newtab_closed_window);
+ background-repeat: no-repeat;
+}
+html[dir='rtl'] .recent-window-container {
+ background-position: right;
+ padding-right: 22px;
+}
+</style>
-<script src="local_strings.js"></script>
-<script src="new_tab.js"></script>
</html>