diff options
author | rvargas@google.com <rvargas@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-27 00:35:56 +0000 |
---|---|---|
committer | rvargas@google.com <rvargas@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-27 00:35:56 +0000 |
commit | ed95ffbea8f0558c75b99b312b917a71b088a4be (patch) | |
tree | e2c7c2f0089726be5b2ac05545cdfff3185d452d | |
parent | a4fe3a78551184e580ea94cee4c25628b0bb2a23 (diff) | |
download | chromium_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
-rw-r--r-- | chrome/browser/browser_resources.grd | 7 | ||||
-rw-r--r-- | chrome/browser/dom_ui/new_tab_ui.cc | 384 | ||||
-rw-r--r-- | chrome/browser/dom_ui/new_tab_ui.h | 3 | ||||
-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.html | 191 | ||||
-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.html | 1153 | ||||
-rw-r--r-- | chrome/common/chrome_switches.cc | 21 | ||||
-rw-r--r-- | chrome/common/chrome_switches.h | 2 |
9 files changed, 1566 insertions, 195 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> </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> </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"> </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> + </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> + </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> »</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 = ' '; + } - </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> diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc index c3c2275..f45ca34 100644 --- a/chrome/common/chrome_switches.cc +++ b/chrome/common/chrome_switches.cc @@ -493,13 +493,14 @@ const wchar_t kSimpleDataSource[] = L"simple-data-source"; // the selected outcome to have the indicated text value. const wchar_t kForceFieldTestNameAndValue[] = L"force-fieldtest"; -// Allow a custom file to be used as the New Tab page. This needs to be a file -// path, in which case the file will be used as the new tab page. -const wchar_t kNewTabPage[] = L"new-tab-page"; +// Enables the prototype of the next version of the New Tab page. +// This can be empty in which case the default prototype resource is used or it +// can be a file path, in which case the file will be used as the new tab page. +const wchar_t kNewNewTabPage[] = L"new-new-tab-page"; // Enables the prototype of the backend service for web resources, used in the // new new tab page for loading tips and recommendations from a JSON feed. -const wchar_t kWebResources[] = L"enable-web-resources"; +const wchar_t kWebResources[] = L"enable-web-resources"; // Disables the default browser check. Useful for UI/browser tests where we want // to avoid having the default browser info-bar displayed. @@ -507,13 +508,13 @@ const wchar_t kNoDefaultBrowserCheck[] = L"no-default-browser-check"; // Enables the Privacy Blacklist with the specified data file. // The file contains data from all imported blacklists. -const wchar_t kPrivacyBlacklist[] = L"privacy-blacklist"; +const wchar_t kPrivacyBlacklist[] = L"privacy-blacklist"; // Enables the benchmarking extensions. -const wchar_t kEnableBenchmarking[] = L"enable-benchmarking"; +const wchar_t kEnableBenchmarking[] = L"enable-benchmarking"; // The prefix used when starting the zygote process. (i.e. 'gdb --args') -const wchar_t kZygoteCmdPrefix[] = L"zygote-cmd-prefix"; +const wchar_t kZygoteCmdPrefix[] = L"zygote-cmd-prefix"; // Enables using ThumbnailStore instead of ThumbnailDatabase for setting and // getting thumbnails for the new tab page. @@ -521,7 +522,7 @@ const wchar_t kThumbnailStore[] = L"thumbnail-store"; // Experimental. Shows a dialog asking the user to try chrome. This flag // is to be used only by the upgrade process. -const wchar_t kTryChromeAgain[] = L"try-chrome-again"; +const wchar_t kTryChromeAgain[] = L"try-chrome-again"; // The file descriptor limit is set to the value of this switch, subject to the // OS hard limits. Useful for testing that file descriptor exhaustion is handled @@ -531,10 +532,10 @@ const wchar_t kFileDescriptorLimit[] = L"file-descriptor-limit"; // On Windows, converts the page to the currently-installed monitor profile. // This does NOT enable color management for images. The source is still assumed // to be sRGB. -const wchar_t kEnableMonitorProfile[] = L"enable-monitor-profile"; +const wchar_t kEnableMonitorProfile[] = L"enable-monitor-profile"; // Enable WebKit's XSSAuditor to mitigate reflective XSS. The XSSAuditor is // still experimental. -const wchar_t kEnableXSSAuditor[] = L"enable-xss-auditor"; +const wchar_t kEnableXSSAuditor[] = L"enable-xss-auditor"; } // namespace switches diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h index 3946774..f18e56b 100644 --- a/chrome/common/chrome_switches.h +++ b/chrome/common/chrome_switches.h @@ -182,7 +182,7 @@ extern const wchar_t kSimpleDataSource[]; extern const wchar_t kForceFieldTestNameAndValue[]; -extern const wchar_t kNewTabPage[]; +extern const wchar_t kNewNewTabPage[]; extern const wchar_t kWebResources[]; |