summaryrefslogtreecommitdiffstats
path: root/chrome/browser
diff options
context:
space:
mode:
authorarv@google.com <arv@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-22 23:53:09 +0000
committerarv@google.com <arv@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-22 23:53:09 +0000
commit60287ffd30023b51d7f183abb6948a63f9f1eb90 (patch)
treea640367374681d389b091b4443d9b43ff4822983 /chrome/browser
parent930d1f1a0dd0bdfa65822af55d4717c6ab62515e (diff)
downloadchromium_src-60287ffd30023b51d7f183abb6948a63f9f1eb90.zip
chromium_src-60287ffd30023b51d7f183abb6948a63f9f1eb90.tar.gz
chromium_src-60287ffd30023b51d7f183abb6948a63f9f1eb90.tar.bz2
Update the New New Tab Page. There are still a lot of things that are
not implemented but it is time to get this submitted and iron out the remaining issues. BUG=13362 TEST=Start chrome with --new-new-tab-page and look at play arouind with the new tab page. Review URL: http://codereview.chromium.org/132027 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@18989 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
-rw-r--r--chrome/browser/browser_resources.grd2
-rw-r--r--chrome/browser/dom_ui/new_tab_ui.cc40
-rw-r--r--chrome/browser/dom_ui/shown_sections_handler.cc54
-rw-r--r--chrome/browser/dom_ui/shown_sections_handler.h40
-rw-r--r--chrome/browser/resources/new_new_tab.css665
-rw-r--r--chrome/browser/resources/new_new_tab.html1516
-rw-r--r--chrome/browser/resources/new_new_tab.js891
7 files changed, 1838 insertions, 1370 deletions
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 69733b7..e8938c4 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -20,6 +20,8 @@ without changes to the corresponding grd file. -->
<include name="IDR_NEW_TAB_HTML" file="resources\new_tab.html" flattenhtml="true" type="BINDATA" />
<include name="IDR_NEW_TAB_THEME_CSS" file="resources\new_tab_theme.css" flattenhtml="true" type="BINDATA" />
<include name="IDR_NEW_NEW_TAB_HTML" file="resources\new_new_tab.html" flattenhtml="true" type="BINDATA" />
+ <include name="IDR_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 e1dd96e..3bdb738 100644
--- a/chrome/browser/dom_ui/new_tab_ui.cc
+++ b/chrome/browser/dom_ui/new_tab_ui.cc
@@ -22,6 +22,7 @@
#include "chrome/browser/dom_ui/dom_ui_theme_source.h"
#include "chrome/browser/dom_ui/downloads_dom_handler.h"
#include "chrome/browser/dom_ui/history_ui.h"
+#include "chrome/browser/dom_ui/shown_sections_handler.h"
#include "chrome/browser/dom_ui/web_resource_handler.h"
#include "chrome/browser/history/page_usage_data.h"
#include "chrome/browser/metrics/user_metrics.h"
@@ -294,6 +295,38 @@ void NewTabHTMLSource::StartDataRequest(const std::string& path,
l10n_util::GetString(IDS_NEW_TAB_ATTRIBUTION_INTRO));
localized_strings.SetString(L"resourcecache",
l10n_util::GetString(IDS_NEW_TAB_WEB_RESOURCE_CACHE));
+ localized_strings.SetString(L"editthumbnail",
+ l10n_util::GetString(IDS_NEW_TAB_EDIT_THUMBNAIL));
+ localized_strings.SetString(L"recentactivities",
+ l10n_util::GetString(IDS_NEW_TAB_RECENT_ACTIVITIES));
+ localized_strings.SetString(L"downloads",
+ l10n_util::GetString(IDS_NEW_TAB_DOWNLOADS));
+ localized_strings.SetString(L"viewfullhistory",
+ l10n_util::GetString(IDS_NEW_TAB_VIEW_FULL_HISTORY));
+ localized_strings.SetString(L"viewalldownloads",
+ l10n_util::GetString(IDS_NEW_TAB_VIEW_ALL_DOWNLOADS));
+ localized_strings.SetString(L"showthumbnails",
+ l10n_util::GetString(IDS_NEW_TAB_SHOW_THUMBNAILS));
+ localized_strings.SetString(L"hidethumbnails",
+ l10n_util::GetString(IDS_NEW_TAB_HIDE_THUMBNAILS));
+ localized_strings.SetString(L"showlist",
+ l10n_util::GetString(IDS_NEW_TAB_SHOW_LIST));
+ localized_strings.SetString(L"hidelist",
+ l10n_util::GetString(IDS_NEW_TAB_HIDE_LIST));
+ localized_strings.SetString(L"showrecent",
+ l10n_util::GetString(IDS_NEW_TAB_SHOW_RECENT));
+ localized_strings.SetString(L"hiderecent",
+ l10n_util::GetString(IDS_NEW_TAB_HIDE_RECENT));
+ localized_strings.SetString(L"showrecommendations",
+ l10n_util::GetString(IDS_NEW_TAB_SHOW_RECOMMENDATIONS));
+ localized_strings.SetString(L"hiderecommendations",
+ l10n_util::GetString(IDS_NEW_TAB_HIDE_RECOMMENDATIONS));
+ localized_strings.SetString(L"thumbnailremovednotification",
+ l10n_util::GetString(IDS_NEW_TAB_THUMBNAIL_REMOVED_NOTIFICATION));
+ localized_strings.SetString(L"undothumbnailremove",
+ l10n_util::GetString(IDS_NEW_TAB_UNDO_THUMBNAIL_REMOVE));
+ localized_strings.SetString(L"otrmessage",
+ l10n_util::GetString(IDS_NEW_TAB_OTR_MESSAGE));
SetFontAndTextDirection(&localized_strings);
@@ -1376,11 +1409,10 @@ NewTabUI::NewTabUI(TabContents* contents)
DownloadManager* dlm = GetProfile()->GetDownloadManager();
DownloadsDOMHandler* downloads_handler =
new DownloadsDOMHandler(this, dlm);
-
AddMessageHandler(downloads_handler);
- AddMessageHandler(new BrowsingHistoryHandler(this));
-
downloads_handler->Init();
+
+ AddMessageHandler(new ShownSectionsHandler(this));
}
if (EnableWebResources())
@@ -1443,6 +1475,8 @@ void NewTabUI::RegisterUserPrefs(PrefService* prefs) {
MostVisitedHandler::RegisterUserPrefs(prefs);
if (NewTabUI::EnableWebResources())
WebResourceHandler::RegisterUserPrefs(prefs);
+ if (NewTabUI::EnableNewNewTabPage())
+ ShownSectionsHandler::RegisterUserPrefs(prefs);
}
// static
diff --git a/chrome/browser/dom_ui/shown_sections_handler.cc b/chrome/browser/dom_ui/shown_sections_handler.cc
new file mode 100644
index 0000000..d9c3488
--- /dev/null
+++ b/chrome/browser/dom_ui/shown_sections_handler.cc
@@ -0,0 +1,54 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/dom_ui/shown_sections_handler.h"
+
+#include "base/string_util.h"
+#include "base/values.h"
+#include "chrome/browser/profile.h"
+#include "chrome/common/pref_names.h"
+
+ShownSectionsHandler::ShownSectionsHandler(DOMUI* dom_ui)
+ : DOMMessageHandler(dom_ui),
+ dom_ui_(dom_ui) {
+ dom_ui->RegisterMessageCallback("getShownSections",
+ NewCallback(this, &ShownSectionsHandler::HandleGetShownSections));
+ dom_ui->RegisterMessageCallback("setShownSections",
+ NewCallback(this, &ShownSectionsHandler::HandleSetShownSections));
+}
+
+void ShownSectionsHandler::HandleGetShownSections(const Value* value) {
+ const int mode = dom_ui_->GetProfile()->GetPrefs()->
+ GetInteger(prefs::kNTPShownSections);
+ FundamentalValue* mode_value = new FundamentalValue(mode);
+ dom_ui_->CallJavascriptFunction(L"onShownSections", *mode_value);
+}
+
+void ShownSectionsHandler::HandleSetShownSections(const Value* value) {
+ if (!value->IsType(Value::TYPE_LIST)) {
+ NOTREACHED();
+ return;
+ }
+
+ const ListValue* list = static_cast<const ListValue*>(value);
+ std::string mode_string;
+
+ if (list->GetSize() < 1) {
+ NOTREACHED() << "setShownSections called with too few arguments";
+ return;
+ }
+
+ bool r = list->GetString(0, &mode_string);
+ DCHECK(r) << "Missing value in setShownSections from the NTP Most Visited.";
+
+ dom_ui_->GetProfile()->GetPrefs()->SetInteger(
+ prefs::kNTPShownSections, StringToInt(mode_string));
+}
+
+// static
+void ShownSectionsHandler::RegisterUserPrefs(PrefService* prefs) {
+ prefs->RegisterIntegerPref(prefs::kNTPShownSections,
+ THUMB | RECENT | RECOMMENDATIONS);
+}
+
diff --git a/chrome/browser/dom_ui/shown_sections_handler.h b/chrome/browser/dom_ui/shown_sections_handler.h
new file mode 100644
index 0000000..d70ba1c
--- /dev/null
+++ b/chrome/browser/dom_ui/shown_sections_handler.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_DOM_UI_SHOWN_SECTIONS_HANDLER_H_
+#define CHROME_BROWSER_DOM_UI_SHOWN_SECTIONS_HANDLER_H_
+
+#include "chrome/browser/dom_ui/dom_ui.h"
+
+class DOMUI;
+class Value;
+class PrefService;
+
+// Use for the shown sections bitmask.
+enum Section {
+ THUMB = 1,
+ LIST = 2,
+ RECENT = 4,
+ RECOMMENDATIONS = 8
+};
+
+class ShownSectionsHandler : public DOMMessageHandler {
+ public:
+ explicit ShownSectionsHandler(DOMUI* dom_ui);
+
+ // Callback for "getShownSections" message.
+ void HandleGetShownSections(const Value* value);
+
+ // Callback for "setShownSections" message.
+ void HandleSetShownSections(const Value* value);
+
+ static void RegisterUserPrefs(PrefService* prefs);
+
+ private:
+ DOMUI* dom_ui_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShownSectionsHandler);
+};
+
+#endif // CHROME_BROWSER_DOM_UI_SHOWN_SECTIONS_HANDLER_H_
diff --git a/chrome/browser/resources/new_new_tab.css b/chrome/browser/resources/new_new_tab.css
new file mode 100644
index 0000000..9aae5b4
--- /dev/null
+++ b/chrome/browser/resources/new_new_tab.css
@@ -0,0 +1,665 @@
+html {
+ /* This is needed because of chrome://theme/css/new_tab.css */
+ height: 100%;
+}
+
+body {
+ margin: 0;
+}
+
+#main {
+ background: url(chrome://theme/product_logo) no-repeat 0 0px;
+ position: relative;
+ margin: 0 auto;
+ width: 940px;
+ -webkit-transition: width .5s;
+}
+
+html[dir='rtl'] #main {
+ background-position-x: 100%;
+}
+
+.small #main {
+ width: 692px;
+}
+
+html[anim='false'],
+.no-anim, .no-anim *,
+.loading * {
+ -webkit-transition: none !important;
+ -webkit-animation: none !important;
+}
+
+/* Most Visited */
+
+#most-visited {
+ position: relative;
+ padding: 0;
+ margin-bottom: 34px;
+ -webkit-user-select: none;
+ -webkit-transition: height .5s, opacity .5s;
+}
+
+@-webkit-keyframes 'thumbnail-enter' {
+ /* 2.5s */
+ 0%, 20% {
+ -webkit-box-shadow: 0px 2px 2px hsla(0, 0%, 0%, .5);
+ -webkit-border-radius: 4px;
+ }
+
+ 100% {
+ -webkit-border-top-left-radius: 0;
+ -webkit-border-top-right-radius: 0;
+ -webkit-box-shadow: 0px 2px 2px hsla(0, 0%, 0%, 0);
+ }
+}
+
+@-webkit-keyframes 'edit-mode-border-enter' {
+ /* 2.5s */
+ 0%, 20% {
+ background-color: hsla(213, 66%, 57%, 0);
+ -webkit-box-shadow: 0px 2px 2px hsla(0, 0%, 0%, 0);
+ }
+
+ 100% {
+ background-color: hsla(213, 66%, 57%, 1);
+ -webkit-box-shadow: 0px 2px 2px hsla(0, 0%, 0%, .5);
+ }
+}
+
+@-webkit-keyframes 'edit-bar-enter' {
+ /* 2.5s */
+ 0%, 20% {
+ opacity: 0;
+ pointer-events: none;
+ }
+
+ 100% {
+ opacity: 1;
+ pointer-events: inherit;
+ }
+}
+
+.thumbnail-container {
+ position: absolute;
+ -webkit-transition: top .5s, left .5s;
+ color: black;
+ text-decoration: none;
+ -webkit-transition: left .5s, top .5s;
+}
+
+.thumbnail,
+.thumbnail-container .title {
+ width: 212px; /* natural size is 196 */
+ height: 132px; /* 136 */
+ -webkit-transition: width .5s, height .5s;
+}
+
+.small .thumbnail,
+.small .thumbnail-container .title {
+ width: 150px;
+ height: 93px;
+}
+
+.thumbnail-wrapper {
+ display: block;
+ -webkit-background-size: 212px 132px;
+ background: no-repeat 4px 4px;
+ background-color: white;
+ -webkit-border-radius: 5px;
+ -webkit-transition: -webkit-background-size .5s;
+}
+
+.small .thumbnail-wrapper {
+ -webkit-background-size: 150px 93px;
+}
+
+.filler * {
+ visibility: hidden;
+}
+
+.filler {
+ pointer-events: none;
+}
+
+.filler .thumbnail-wrapper {
+ visibility: inherit;
+ border: 1px solid hsl(0, 0%, 90%);
+ background-color: hsl(0, 0%, 95%);
+ -webkit-border-radius: 2px;
+ display: block;
+}
+
+.filler .thumbnail-wrapper .thumbnail {
+ visibility: visible;
+ border-color: hsl(0, 0%, 90%);
+ background-color: hsl(0, 0%, 95%);
+}
+
+.edit-bar {
+ opacity: 0;
+ pointer-events: none;
+ display: -webkit-box;
+ -webkit-box-orient: horizontal;
+ -webkit-box-align: stretch;
+ padding: 3px;
+ padding-bottom: 0;
+ height: 17px; /* 23 - 2 * 3 */
+ -webkit-transition: opacity .5s;
+ cursor: move;
+ font-size: 100%;
+ line-height: 17px;
+ background-image: -webkit-gradient(linear, left top, left bottom,
+ from(hsl(213, 87%, 67%)),
+ to(hsl(213, 66%, 57%)));
+ -webkit-border-top-left-radius: 4px;
+ -webkit-border-top-right-radius: 4px;
+}
+
+.edit-bar > * {
+ display: block;
+ position: relative;
+}
+
+.thumbnail-container:hover .edit-bar {
+ -webkit-animation: 'edit-bar-enter' 2.5s;
+ opacity: 1;
+ pointer-events: inherit;
+}
+
+.edit-bar {
+
+}
+
+.edit-bar > .edit-link {
+ padding: 0 5px;
+ color: hsl(213, 88%, 94%);
+ font-weight: bold;
+}
+
+.edit-bar > .spacer {
+ -webkit-box-flex: 1;
+}
+
+.edit-bar > .pin,
+.edit-bar > .remove {
+ width: 16px;
+ height: 16px;
+ cursor: pointer;
+ background-image: no-repeat 50% 50%;
+}
+
+.edit-bar > .pin {
+ background-image: url(chrome://theme/newtab_pin_off);
+}
+
+.edit-bar > .pin:hover {
+ background-image: url(chrome://theme/newtab_pin_off_h);
+}
+
+.edit-bar > .pin:active {
+ background-image: url(chrome://theme/newtab_pin_off_p);
+}
+
+.pinned .edit-bar > .pin {
+ background-image: url(chrome://theme/newtab_pin_on);
+}
+
+.pinned .edit-bar > .pin:hover {
+ background-image: url(chrome://theme/newtab_pin_on_h);
+}
+
+.pinned .edit-bar > .pin:active {
+ background-image: url(chrome://theme/newtab_pin_on_p);
+}
+
+.edit-bar > .remove {
+ background-image: url(chrome://theme/newtab_close);
+}
+
+.edit-bar > .remove:hover {
+ background-image: url(chrome://theme/newtab_close_h);
+}
+
+.edit-bar > .remove:active {
+ background-image: url(chrome://theme/newtab_close_p);
+}
+
+:link,
+:visited,
+.link,
+.thumbnail-container a {
+ cursor: pointer;
+ text-decoration: underline;
+ color: hsl(213, 90%, 24%);
+}
+
+.thumbnail-container .title,
+.small .thumbnail-container .title {
+ line-height: 16px;
+ height: 16px;
+ margin: 0;
+ margin-top: 4px;
+ font-size: 100%;
+ font-weight:normal;
+ padding: 0 3px;
+ opacity: 1;
+ -webkit-transition: opacity .5s;
+ color: black;
+}
+
+.thumbnail-container .title div {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ background: no-repeat 0 50%;
+ -webkit-background-size: 16px;
+ padding-left: 20px; /* we cannot use padding start here because even if we set
+ the direction we always want the icon on the same side
+ */
+ padding-right: 0;
+}
+
+html[dir=rtl] .thumbnail-container .title div {
+ background-position-x: 100%;
+ padding-left: 0;
+ padding-right: 20px;
+ text-align: right;
+}
+
+.thumbnail {
+ border: 3px solid hsla(213, 63%, 93%, 1);
+ padding: 1px;
+ -webkit-border-radius: 5px;
+ display: block;
+ -webkit-box-shadow: 0px 2px 2px hsla(0, 0%, 0%, 0);
+ -webkit-transition: width .5s, height .5s, border-color .5s,
+ -webkit-border-radius .5s, -webkit-box-shadow .5s;
+}
+
+.edit-mode-border {
+ -webkit-border-radius: 4px;
+ background-color: hsla(213, 54%, 95%, 0);
+}
+
+.thumbnail-container:hover .thumbnail {
+ -webkit-animation: 'thumbnail-enter' 2.5s;
+ border-color: hsla(213, 66%, 57%, 1);
+ -webkit-box-shadow: 0px 2px 2px hsla(0, 0%, 0%, 0);
+ -webkit-border-top-left-radius: 0;
+ -webkit-border-top-right-radius: 0;
+
+ background-image: -webkit-gradient(linear, left top, left bottom,
+ from(hsla(0, 0%, 0%, 0)),
+ color-stop(0.85, hsla(0, 0%, 47%, 0)),
+ to(hsla(0, 0%, 47%, 0.2))
+ );
+}
+
+.thumbnail-container:hover .edit-mode-border {
+ -webkit-animation: 'edit-mode-border-enter' 2.5s;
+ background-color: hsla(213, 66%, 57%, 1);
+ -webkit-box-shadow: 0px 2px 2px hsla(0, 0%, 0%, .5);
+}
+
+.dragging {
+ -webkit-transition: none;
+}
+
+.dragging .title {
+ opacity: 0;
+}
+
+.list .dragging .title {
+ opacity: 1;
+}
+
+.hide {
+ opacity: 0 !important;
+ visibility: hidden !important;
+}
+
+@-webkit-keyframes 'fade-in' {
+ 0% {
+ opacity: 0;
+ }
+
+ 100% {
+ opacity: 1;
+ }
+}
+
+.fade-in {
+ -webkit-animation: 'fade-in' 2.5s;
+}
+
+/* Notification */
+
+#notification {
+ position: relative;
+ background-color: hsl(52, 100%, 80%);
+ -webkit-border-radius: 4px;
+ padding: 8px 16px;
+ white-space: nowrap;
+ display: table;
+ margin: 10px auto;
+ font-weight: bold;
+ opacity: 0;
+ visibility: hidden;
+ -webkit-transition: opacity 1s;
+ z-index: 1;
+}
+
+#notification > * {
+ display: table-cell;
+ max-width: 500px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.small #notification > * {
+ max-width: 300px;
+}
+
+#notification:hover,
+#notification.show {
+ opacity: 1;
+ visibility: inherit;
+}
+
+#notification .link {
+ -webkit-margin-start: 20px;
+}
+
+/* List mode */
+
+.list .thumbnail,
+.list .edit-bar {
+ display: none;
+}
+
+.list .title,
+.small .list .title {
+ width: auto;
+}
+
+.list .thumbnail-container {
+ -webkit-box-sizing: border-box;
+}
+
+.list .title,
+.small .list .title {
+ font-size: 140%;
+ line-height: 40px;
+ height: 40px;
+ color: hsl(213, 27%, 68%);
+ text-decoration: underline;
+}
+
+.list .title div {
+ color: rgb(6, 45, 117);
+}
+
+.section {
+ background: white;
+ -webkit-border-radius: 4px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ border: 1px solid hsl(213, 65%, 92%);
+ padding-bottom: 5px;
+ overflow: hidden;
+ min-height: 175px;
+ vertical-align: top;
+ margin: 0;
+}
+
+.section > h2 {
+ background-image: -webkit-gradient(linear, left top, left bottom,
+ from(hsl(213, 87%, 67%)),
+ to(hsl(213, 66%, 57%)));
+ -webkit-border-top-left-radius: 2px;
+ -webkit-border-top-right-radius: 2px;
+ padding: 4px 8px;
+ color: white;
+ font-size: 100%;
+ margin: 0px;
+}
+
+.item {
+ background: no-repeat 0% 50%;
+ padding: 2px;
+ -webkit-padding-start: 18px;
+ -webkit-background-size: 16px;
+ background-color: hsla(213, 63%, 93%, 0);
+ display: block;
+ line-height: 20px;
+ -webkit-box-sizing: border-box;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ text-decoration: none;
+ font-size: 100%;
+}
+
+html[dir='rtl'] .item {
+ background-position-x: 100%;
+}
+
+.window {
+ position: relative;
+ overflow: visible; /* We use visible so that the menu can be a child and shown
+ on :hover. To get this to work we have to set visibility
+ to visible which unfortunately breaks the ellipsis for t
+ he window items */
+ background-image: url(chrome://theme/newtab_closed_window);
+}
+
+.window-menu {
+ position: absolute;
+ display: none;
+ border: 1px solid #999;
+ -webkit-box-shadow: 5px 5px 10px hsla(0, 0%, 0%, .3);
+ background-color: white;
+ width: 157px;
+ left: 0;
+ white-space: nowrap;
+ opacity: .9;
+ z-index: 1;
+ pointer-events: none;
+}
+
+.item:hover > .window-menu,
+.item:focus > .window-menu {
+ display: block;
+}
+
+.item:hover .window-menu .item,
+.item:focus .window-menu .item {
+ background-color: transparent;
+ text-decoration: none;
+ margin: 0;
+ font-size: 100%;
+}
+
+.hbox {
+ display: -webkit-box;
+ -webkit-box-orient: horizontal;
+}
+
+#recent-tabs,
+#downloads {
+ -webkit-box-flex: 1;
+ width: 50%;
+}
+
+.section h3 {
+ padding: 8px 5px;
+ padding-bottom: 0;
+ font-size: 80%;
+ font-weight: normal;
+ margin: 0;
+ -webkit-margin-start: 2px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.item-container {
+ margin: 0 5px;
+ text-decoration: underline;
+ color: hsl(213, 27%, 68%);
+}
+
+.item.nav:after {
+ content: '\00bb'; /* raque gets flipped automatically in rtl */
+ font-size: 120%;
+ -webkit-padding-start: 2px;
+}
+
+#recent-activities {
+ -webkit-transition: width .5s, opacity .5s;
+}
+
+.section {
+ -webkit-box-flex: 1;
+ width: 50%;
+ display: inline-block;
+}
+
+#view-toolbar {
+ -webkit-user-select: none;
+ text-align: right;
+ position: relative;
+ top: 35px;
+}
+
+html[dir='rtl'] #view-toolbar {
+ text-align: left;
+}
+
+#view-toolbar input {
+ -webkit-appearance: none;
+ background-color: transparent;
+ width: 19px;
+ height: 17px;
+ margin: 0;
+ border: 0;
+ padding: 0;
+ vertical-align: top;
+ -webkit-margin-start: 5px;
+}
+
+#lower-sections {
+ position: relative;
+ overflow: hidden;
+ -webkit-transition: height .5s, width .5s, opacity .5s;
+ white-space: nowrap;
+}
+
+#lower-sections .section {
+ -webkit-transition: width .5s, opacity .5s, left .5s;
+ -webkit-box-sizing: border-box;
+}
+
+#lower-sections .spacer {
+ width: 20px;
+ -webkit-transition: width .5s;
+ display: inline-block;
+}
+
+.loading * {
+ -webkit-transition: none;
+}
+
+#thumb-checkbox {
+ background-image: url(chrome://theme/newtab_thumb_off);
+}
+
+#thumb-checkbox:hover {
+ background-image: url(chrome://theme/newtab_thumb_off_h);
+}
+
+#thumb-checkbox:active {
+ background-image: url(chrome://theme/newtab_thumb_off_p);
+}
+
+#thumb-checkbox:checked {
+ background-image: url(chrome://theme/newtab_thumb_on);
+}
+
+#thumb-checkbox:checked:hover {
+ background-image: url(chrome://theme/newtab_thumb_on_h);
+}
+
+#thumb-checkbox:checked:active {
+ background-image: url(chrome://theme/newtab_thumb_on_p);
+}
+
+#list-checkbox {
+ background-image: url(chrome://theme/newtab_list_off);
+}
+
+#list-checkbox:hover {
+ background-image: url(chrome://theme/newtab_list_off_h);
+}
+
+#list-checkbox:active {
+ background-image: url(chrome://theme/newtab_list_off_p);
+}
+
+#list-checkbox:checked {
+ background-image: url(chrome://theme/newtab_list_on);
+}
+
+#list-checkbox:checked:hover {
+ background-image: url(chrome://theme/newtab_list_on_h);
+}
+
+#list-checkbox:checked:active {
+ background-image: url(chrome://theme/newtab_list_on_p);
+}
+
+#option-button {
+ background-image: url(chrome://theme/newtab_option);
+}
+
+#option-button:hover {
+ background-image: url(chrome://theme/newtab_option_h);
+}
+
+#option-button:active {
+ background-image: url(chrome://theme/newtab_option_p);
+}
+
+#option-menu {
+ -webkit-user-select: none;
+ position: absolute;
+ right: 0;
+ left: auto;
+ top: 53px; /* Position this below the option button. The option button
+ is positioned 35px from the top and has a height of 17px. We add
+ one to get some spacing there as well: 35 + 17 + 1 = 53 */
+ cursor: default;
+ pointer-events: all;
+ min-width: 175px;
+ outline: 0;
+}
+
+html[dir='rtl'] #option-menu {
+ right: auto;
+ left: 0;
+}
+
+#option-menu > div {
+ padding: 3px 8px;
+ margin: 1px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+#option-menu > div:hover,
+#option-menu > div:focus {
+ background-color: hsla(213, 66%, 57%, 1);
+ color: white;
+ outline: none;
+}
diff --git a/chrome/browser/resources/new_new_tab.html b/chrome/browser/resources/new_new_tab.html
index 7897c44..0372260 100644
--- a/chrome/browser/resources/new_new_tab.html
+++ b/chrome/browser/resources/new_new_tab.html
@@ -1,1402 +1,184 @@
-<!DOCTYPE HTML>
-<html id="t" jsvalues="dir:textdirection;firstview:firstview;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.
+<!DOCTYPE html>
+<html id="t" jsvalues="dir:textdirection;firstview:firstview;bookmarkbarattached:bookmarkbarattached;hasattribution:hasattribution;anim:anim">
- 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>
+<meta charset="utf-8">
+<title jscontent="title"></title>
<script>
-
// Logging info for benchmarking purposes.
var log = [];
function logEvent(name) {
log.push([name, Date.now()]);
}
-// Basic functions to send, receive, store and process the data from our
-// backend.
-var unprocessedData = {
- mostVisitedPages: false,
- searchURLs: false
-};
-
-var recent = {
- MAX_ITEMS: 20,
- entries: [],
- entriesById: {},
- dirty: true,
- appendData: function(data, constr) {
- var self = this;
- data.forEach(function(d) {
- self.add(new constr(d));
- });
- },
-
- add: function(entry) {
- if (!(entry instanceof Entry)) {
- alert('Not an instance of Entry:\n\n' + JSON.stringify(entry));
- }
- // Don't add if already in there.
- var id = entry.id;
- if (!(id in this.entriesById)) {
- this.entries.push(entry);
- this.entriesById[id] = entry;
- this.dirty = true;
- }
- },
-
- remove: function(entry) {
- var id = entry.id;
- if (id in this.entriesById) {
- entry.remove();
- delete this.entriesById[id];
- this.dirty = true;
- }
- },
-
- sortEntries: function() {
- this.entries.sort(function(e1, e2) {
- return e2.time - e1.time;
- });
- }
-};
-
-var renderFunctionsDefined = false;
-
-var localStrings;
-
-// The list of URLs that have been blacklisted (so that thumbnails are not
-// shown) in the last set of change. Used to revert changes when the Cancel
-// button is pressed.
-var blacklistedURLs = [];
-
-function $(o) {return document.getElementById(o);}
-
-///////////////////////////////////////////////////////////////////////////////
-
-
-function Entry(data) {
- this.data = data;
-}
-
-Entry.prototype = {
- get url() {
- return this.data.url;
- },
- get title() {
- return this.data.title;
- },
- get icon() {
- return 'chrome://favicon/' + this.data.url;
- },
- get time() {
- return this.data.time;
- },
- get id() {
- return this.type + '-' + this.url + '-' + this.time;
- },
- createDom: function() {
- var data = this.data;
- var link = DOM('a', {
- title: this.title,
- href: data.url,
- textContent: this.title
- });
- var div = DOM('div', {
- id: this.id,
- className: 'recent-item ' + this.type
- });
-
- link.style.backgroundImage = 'url("' + this.icon + '")';
-
- // Set the title's directionality independently of the page, see comment
- // about setting div_title.style.direction above for details.
- link.style.direction = data.direction;
-
- // The following if statement is a temporary workaround for
- // http://crbug.com/7252 and http://crbug.com/7697. It should be removed
- // before closing these bugs.
- if (data.direction == 'rtl') {
- link.style.textOverflow = 'clip';
- }
-
- this.addListeners(div, link);
- div.appendChild(link);
-
- return this.element = div;
- },
- addListeners: function(div, link) {
- },
- remove: function() {
- this.removed = true;
- var div = $(this.id);
- if (!div || !div.parentNode) return;
- div.parentNode.removeChild(div);
- }
-};
-
-function DownloadEntry(data) {
- Entry.call(this, data);
-}
-
-DownloadEntry.prototype = {
- __proto__: Entry.prototype,
- type: 'download',
- typeImageUrl: '',
- get title() {
- return this.data.file_name;
- },
- get icon() {
- return 'chrome://fileicon/' + this.data.file_path;
- },
- get time() {
- return this.data.started;
- },
- addListeners: function(div, link) {
- // TODO(arv): What about non safe files?
- // TODO(arv): What about non finished files?
- var id = this.data.id;
- link.onclick = function(e) {
- chrome.send("openFile", [String(id)]);
- e.preventDefault();
- }
- }
-};
-
-function HistoryEntry(data) {
- Entry.call(this, data);
-}
-HistoryEntry.prototype = {
- __proto__: Entry.prototype,
- type: 'history'
-};
-
-function BookmarkEntry(data) {
- Entry.call(this, data);
-}
-BookmarkEntry.prototype = {
- __proto__: Entry.prototype,
- type: 'bookmark',
- addListeners: function(div, link) {
- var index = this.data.index;
- link.addEventListener('mousedown', function(event) {
- chrome.send('metrics', ['NTP_Bookmark' + index]);
- }, false);
- }
-};
-
-function TabEntry(data) {
- if (data.type == 'window') {
- return new WindowEntry(data);
- }
- Entry.call(this, data);
-}
-TabEntry.prototype = {
- __proto__: Entry.prototype,
- type: 'tab',
- typeImageUrl: '',
-
- // Neither closed tabs or windows have a time stamp. Use now as the time
- get time() {
- return this.time_ || (this.time_ = Math.floor(Date.now() / 1000));
- },
- addListeners: function(div, link) {
- var sessionId = this.data.sessionId;
- var index = this.data.index;
- link.onclick = function(e) {
- chrome.send('metrics', ['NTP_TabRestored' + index]);
- // This is a hack because chrome.send is hardcoded to only
- // accept arrays of strings.
- chrome.send('reopenTab', [String(sessionId)]);
- e.preventDefault();
- };
- },
- get id() {
- return this.type + '-' + this.data.sessionId;
- }
-};
-
-function WindowEntry(data) {
- // This actually extends TabEntry but the tab entry constructor redirects to
- // this constructor so don't call it here because that would cause an infinite
- // loop.
- Entry.call(this, data);
-}
-WindowEntry.prototype = {
- __proto__: TabEntry.prototype,
- type: 'window',
- url: '',
- createDom: function() {
- var entry = this.data;
- var div = DOM('div', {
- id: this.id,
- className: 'recent-window-container'
- });
-
- var linkSpan = DOM('span', {
- textContent: ' '
- });
- for (var windowIndex = 0; windowIndex < entry.tabs.length; windowIndex++) {
- var tab = entry.tabs[windowIndex];
- var tabImg = DOM('img', {
- src: 'url("chrome://favicon/' + tab.url + '")',
- width: 16,
- height: 16,
- onmousedown: function() {
- return false;
- }
- });
- linkSpan.appendChild(tabImg);
- }
-
- var link = DOM('span', {
- className: 'recently-closed-window-link',
- tabIndex: 0
- });
- var windowSpan = DOM('span', {
- className: 'recently-close-window-text',
- textContent: recentlyClosedWindowText(entry.tabs.length)
- });
- link.appendChild(windowSpan);
- link.appendChild(linkSpan);
- div.appendChild(link);
-
-
- // The card takes care of appending itself to the DOM, so no need to
- // keep a reference to it.
- new RecentlyClosedHoverCard(link, entry);
-
- this.addListeners(div, link);
-
- return this.element = div;
- }
-};
+var global = this;
/**
- * If the functions that can render content are defined, render
- * the content for any data we've received so far.
+ * 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 processData() {
- // This is ugly. Can we refactor this?
- if (renderFunctionsDefined) {
- if (unprocessedData.mostVisitedPages) {
- renderMostVisitedPages(unprocessedData.mostVisitedPages);
- unprocessedData.mostVisitedPages = false;
- }
- if (unprocessedData.searchURLs) {
- //renderSearchURLs(unprocessedData.searchURLs);
- renderRecentItems();
- unprocessedData.searchURLs = false;
- }
-
- if (recent.dirty && recent.entries.length) {
- renderRecentItems();
- }
- }
-}
-
-function mostVisitedPages(data) {
- logEvent('received most visited pages');
- unprocessedData.mostVisitedPages = data;
- processData();
-}
-
-function searchURLs(data) {
- logEvent('received search URLs');
- unprocessedData.searchURLs = data;
- processData();
-}
-
-function recentlyBookmarked(data) {
- logEvent('received recently bookmarked data');
- replaceData(data, BookmarkEntry);
-}
-
-function replaceData(data, ctor) {
- var oldEntries = {};
- recent.entries.forEach(function(entry) {
- if (entry instanceof ctor) {
- oldEntries[entry.id] = entry;
- }
- });
-
- var entriesToAdd = {};
- data.map(function(d) {
- var newEntry = new ctor(d);
- var id = newEntry.id;
- if (id in oldEntries && !oldEntries[id].removed) {
- // Already present. No need to add or remove.
- delete oldEntries[id];
- } else {
- recent.add(newEntry);
+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);
+ });
}
- });
-
- for (var id in oldEntries) {
- recent.remove(oldEntries[id]);
- }
-
- processData();
-}
-
-function recentlyClosedTabs(data) {
- logEvent('received recently closed tabs');
- // Add index to the data.
- var i = 0;
- data.forEach(function(d) {
- d.index = i++;
- });
- replaceData(data, TabEntry);
-}
-
-function historyResult(info, results) {
- logEvent('received recent history');
- // TODO(arv): If we have more history and we want to show more recent items
- // we should fetch more items from the history.
- recent.appendData(results, HistoryEntry);
- if (!info.finished && results.length < recent.MAX_ITEMS) {
- getMoreHistory();
- }
- processData();
-}
-
-function downloadsList(data) {
- logEvent('received downloads');
- recent.appendData(data, DownloadEntry);
- processData();
-}
-
-function resizeP13N(new_height) {
- var childf = $('p13n');
- if (new_height < 1) {
- childf.style.display = "none";
- return;
- }
- childf.height = new_height;
- childf.style.display = "block";
-}
-
-var currentHistoryDay = 0;
-function getMoreHistory() {
- chrome.send('getHistory', [String(currentHistoryDay++)]);
-}
-
-function handleWindowResize() {
- var body = document.body;
- if (!body || body.clientWidth < 10) {
- // We're probably a background tab, so don't do anything.
- return;
- }
-
- if (body.className == 'small' && body.clientWidth >= 885) {
- body.className = '';
- } else if (body.className == '' && body.clientWidth <= 865) {
- body.className = 'small';
- }
+ };
+ global[name] = f;
}
-function handleDOMContentLoaded() {
- logEvent('domcontentloaded fired');
-}
+chrome.send('getShownSections');
+chrome.send('getMostVisited');
+chrome.send('getDownloads');
+chrome.send('getRecentlyClosedTabs');
-chrome.send("getMostVisited");
-//chrome.send("getMostSearched");
-chrome.send("getRecentlyBookmarked");
-chrome.send("getRecentlyClosedTabs");
-getMoreHistory();
-chrome.send('getDownloads', ['']);
+registerCallback('onShownSections');
+registerCallback('mostVisitedPages');
+registerCallback('downloadsList');
+registerCallback('recentlyClosedTabs');
logEvent('log start');
-</script>
-<head>
-<meta charset="utf-8">
-<title jscontent="title"></title>
-<style>
-body {
- background-color:white;
- margin:0px;
- background-image:url(chrome://theme/theme_newtab_background);
- background-repeat:repeat-x;
- background-attachment: fixed;
-}
-html[firstview='true'] #main {
- opacity:0.0;
- -webkit-transition:all 0.4s;
-}
-html[firstview='true'] #main.visible {
- opacity:1.0;
-}
-html[anim='false'] * {
- -webkit-transition-property: none !important
-}
-#main {
- margin-left:auto;
- margin-right:auto;
- margin-top:10px;
- width: 838px;
- -webkit-transition:width .12s;
-}
-.small #main {
- width: 658px; /* 4 * 217 */
-
-}
-form {
- padding: 0;
- margin: 0;
-}
-.section {
- padding:3px 0px 5px 0px;
- margin-bottom:30px;
-}
-.section-title {
- color:#000;
- line-height:19pt;
- font-size:110%;
- font-weight:bold;
- margin-bottom:4px;
-}
-#recent-section {
- margin-top: 1.5em;
-}
-#recent-section > .section-title {
- margin-bottom: 1em;
-}
-#mostvisitedsection {
- margin:0px 5px 0px 0px;
-}
-#mostvisited td {
- padding:0px 10px 10px 0px;
-}
-html[dir='rtl'] #mostvisited td {
- padding:0px 0px 10px 10px;
-}
-.most-visited-text {
- position:absolute;
- left:100px;
- right:100px;
- padding:20px;
- margin:15px;
- background-color:white;
- -webkit-box-shadow: 5px 5px 10px #ccc;
- -webkit-transition:all .12s;
- z-index: 1;
-}
-.thumbnail-title {
- display:block;
- width:195px; /* thumbnail */
- margin-top:6px; /* line up favicons with search favicons */
- padding:1px;
- overflow: hidden;
- text-overflow: ellipsis;
- text-decoration:none;
- -webkit-transition:all .12s;
- -webkit-box-sizing:border-box;
-}
-html[dir='rtl'] .thumbnail-title {
- background-position:right;
- padding-left:0px;
- padding-right:22px;
- text-align:right;
-}
-.thumbnail {
- width:195px;
- height:136px;
- border:1px solid #ccc;
- background-color:#eee;
- -webkit-transition:all .12s;
- position: relative;
- -webkit-box-shadow:#ccc 1px 1px 5px;
-}
-a.thumbnail {
- border:1px solid #abe;
-}
-.thumbnail-icon {
- right: -5px;
- bottom: -5px;
- width: 32px;
- height: 32px;
- position: absolute;
- /* a shadow (non box shadow) might make the icon stand out more
- -webkit-box-shadow: 3px 3px 5px #ccc;
- */
-}
-
-.small .thumbnail-title {
- width:127px;
-}
-.small .thumbnail {
- width:150px;
- height:113px;
-}
-.small .most-visited-text {
- width:430px;
- padding:15px;
- margin:12px;
-}
-.recent-item {
- margin:3px 0px 3px 0px;
- height:16pt;
- line-height:16px;
- overflow: hidden;
- text-overflow: ellipsis;
- background: no-repeat center left;
- padding-left: 22px;
-}
-.recent-item.history {
- background-image: url('../../app/theme/o2_history.png');
-}
-.recent-item.bookmark {
- background-image: url('../../app/theme/o2_star.png');
-}
-.recent-item > a {
- background-repeat:no-repeat;
- -webkit-background-size:16px;
- background-position:center left;
- padding:1px 0px 0px 22px;
-}
-
-.recent-bookmark {
- display:block;
- background-repeat:no-repeat;
- background-size:16px;
- background-position:0px 1px;
- padding:1px 0px 0px 22px;
- margin:3px 0px 3px 0px;
- min-height:16pt;
- line-height:16px;
- overflow: hidden;
- text-overflow: ellipsis;
- text-decoration:underline;
-}
-.recent-window-container {
- line-height:16px;
- display:block;
- position: relative;
- margin:0 3px;
- padding-left: 22px;
- margin-left: 22px;
-
- /* TODO(arv): Remove hard coded URL */
- background-image: url('../../app/theme/closed_window.png') !important;
-}
-.recent-window-container img {
- margin:0 3px -2px 3px;
-}
-.recent-window-hover-container {
- position:absolute;
- border:1px solid #999;
- -webkit-box-shadow: 5px 5px 10px #ccc;
- background-color:white;
- width: 157px;
- left: 20px;
- white-space:nowrap;
- opacity:.9;
-}
-.recent-window-hover-container .recent-bookmark {
- text-decoration:none;
- text-overflow:ellipsis;
- overflow:hidden;
- margin: 3px 0 0 5px;
-}
-.recently-closed-window-link {
- 'text-decoration:none';
-}
-.recently-closed-window-link:hover {
- cursor:pointer;
-}
-.recently-close-window-text {
- text-decoration:underline;
- color: #0000cc;
-}
-
-html[dir='rtl'] .recent-bookmark {
- background-position:right;
- padding-left:0px;
- padding-right:22px;
-}
-a {
- color:#0000cc;
- text-decoration:underline;
- white-space: nowrap;
-}
-a.manage {
- color:#77c;
- margin-left: 5px;
- margin-right: 5px;
- line-height:19pt;
- text-decoration:underline;
-}
-html[dir='rtl'] #managesearcheslink {
- float: left;
-}
-.sidebar {
- width: 207px;
- padding:3px 10px 3px 9px;
- -webkit-border-radius:5px 5px;
- margin-bottom:10px;
-}
-#searches {
- background-color:#e1ecfe;
-}
-#recentlyBookmarked {
- background-color:#e1ecfe;
-}
-html[dir='rtl'] #recentlyBookmarkedContainer {
- text-align:right;
-}
-#recentlyClosedContainer {
- position:relative;
-}
-html[dir='rtl'] #recentlyClosedContainer {
- text-align:right;
-}
-#searches input {
- border:1px solid #7f9db9;
- background-repeat: no-repeat;
- background-position:4px center;
- padding-left: 23px;
- min-height:24px;
- width:182px;
- margin-bottom:8px;
- display:block;
-}
-html[dir='rtl'] #searches input {
- background-position: right;
- padding-left:0px;
- padding-right: 23px;
-}
-#searches input:-webkit-input-placeholder-mode {
- color: #aaa;
-}
-.footer {
- border-top:1px solid #ccc;
- padding-top:4px;
- font-size:8pt;
-}
-.edit-visible {
- display: none;
-}
-.edit-mode .edit-visible {
- display: inline;
-}
-.non-edit-visible {
- display: inline;
-}
-.edit-mode .non-edit-visible {
- display: none;
-}
-.most-visited-container {
- position: relative;
- left: 0px;
- top: 0px;
-}
-.edit-mode .disabled-on-edit {
- opacity: 0.5;
- pointer-events: none; /* Disable clicks */
-}
-</style>
+</script>
+<link rel="stylesheet" href="new_new_tab.css">
+<link id="themecss" rel="stylesheet" href="chrome://theme/css/newtab.css">
</head>
-<body onload="logEvent('body onload fired');"
+<body class="loading"
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="mostvisitedsection" class="section">
- <div id="mostvisited" style="position:relative;">
- <div>
- <span class="section-title non-edit-visible" jscontent="mostvisited"></span>
- <span class="section-title edit-visible" jseval="this.innerHTML = $this.editmodeheading;"></span>
- </div>
- <div id="mostvisitedintro" style="display:none;">
- <div class="most-visited-text" jseval="this.innerHTML = $this.mostvisitedintro;"></div>
- <table>
- <tr>
- <td><div class="thumbnail">&nbsp;</div></td>
- <td><div class="thumbnail"></div></td>
- <td><div class="thumbnail">&nbsp;</div></td>
- <td><div class="thumbnail"></div></td>
- </tr>
- <tr>
- <td><div class="thumbnail">&nbsp;</div></td>
- <td><div class="thumbnail"></div></td>
- <td><div class="thumbnail">&nbsp;</div></td>
- <td><div class="thumbnail"></div></td>
- </tr>
- </table>
- </div>
- <table id="mostvisitedtable">
- <!-- This content forces the view to the correct width and provides a
- preview of what's to load to reduce white-flash. Users who get
- the mostvisitedintro will see a brief flash of this content. We
- only use one row so that we may avoid flashing extra rows when
- the user has only one row of items -->
- <tr>
- <td>
- <div class="thumbnail-title">&nbsp;</div>
- <div class="thumbnail"></div>
- </td>
- <td>
- <div class="thumbnail-title">&nbsp;</div>
- <div class="thumbnail"></div>
- </td>
- <td>
- <div class="thumbnail-title">&nbsp;</div>
- <div class="thumbnail"></div>
- </td>
- <td>
- <div class="thumbnail-title">&nbsp;</div>
- <div class="thumbnail"></div>
- </td>
- </tr>
- </table>
- </div>
- <a href="#"
- class="manage non-edit-visible"
- onClick="enterEditMode(); return false">
- <span jscontent="editthumbnails"></span></a>
- <button type="button" class="edit-visible" onClick="exitEditMode();"
- jscontent="doneediting"></button>
- <button type="button" class="edit-visible" onClick="cancelEdits();"
- jscontent="cancelediting"></button>
- <a href="#"
- class="manage edit-visible"
- onClick="restoreThumbnails(); return false">
- <span jscontent="restorethumbnails"></span></a>
- <a href="#"
- jsvalues="href:showhistoryurl"
- class="manage non-edit-visible">
- <span jscontent="showhistory"></span> &raquo;</a>
- </div>
-
- <div id="recent-section">
- <div class="section-title" jscontent="recent"></div>
- <div id="recent-items"></div>
- </div>
-
- <div style="display:none">
- <div align="right">
- <img src="../../app/theme/%DISTRIBUTION%/product_logo.png"
- width="145" height="52" style="padding-bottom:8px;" />
- </div>
- <iframe id="p13n" frameborder="0" width="100%" scrolling="no" height="0"
- jsdisplay="p13nsrc" style="display:none;"
- jsvalues="src:p13nsrc"></iframe>
- <div id="searches" class="sidebar">
- <div class="section-title" jscontent="searches"></div>
- <form onsubmit="chrome.send('searchHistoryPage', [this.search.value]); return false;">
- <input type="text" class="hint"
- name="search"
- style="background-image:url(chrome://favicon/);"
- jsvalues="placeholder:searchhistory">
- </form>
- <div id='searches-entries'></div>
- </div>
-
- <div id="recentlyBookmarked" class="sidebar" style="display:none">
- <span class="section-title" jscontent="bookmarks"></span>
- <div id="recentlyBookmarkedContainer"></div>
- </div>
-
- <div id="recentlyClosedTabs" class="sidebar" style="display:none">
- <div class="section-title" jscontent="recentlyclosed"></div>
- <div id="recentlyClosedContainer"></div>
- </div>
-
- </div>
+ <div 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" tabindex="0">
+ <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="RECOMMENDATIONS" show="true"
+ jscontent="showrecommendations"></div>
+ <div section="RECOMMENDATIONS" show="false"
+ jscontent="hiderecommendations"></div>
</div>
-<script>
-logEvent('start of second script block');
-
-/* Return a DOM element with tag name |elem| and attributes |attrs|. */
-function DOM(elem, attrs) {
- var elem = document.createElement(elem);
- for (var attr in attrs) {
- elem[attr] = attrs[attr];
- }
- return elem;
-}
-
-/**
- * Partially applies this function to a particular 'this object' and zero or
- * more arguments. The result is a new function with some arguments of the first
- * function pre-filled and the value of |this| 'pre-specified'.<br><br>
- *
- * Remaining arguments specified at call-time are appended to the pre-
- * specified ones.<br><br>
- *
- * @param {Function} fn A function to partially apply.
- * @param {Object} selfObj Specifies the object which |this| should point to
- * when the function is run.
- * @param {Object} var_args Additional arguments that are partially
- * applied to the function.
- *
- * @return {!Function} A partially-applied form of the function bind() was
- * invoked as a method of.
- */
-function bind(fn, selfObj, var_args) {
- var boundArgs = Array.prototype.slice.call(arguments, 2);
- return function() {
- var args = Array.prototype.slice.call(arguments);
- args.unshift.apply(args, boundArgs);
- return fn.apply(selfObj, args);
- }
-}
+ <div id="notification">
+ <span>&nbsp;</span>
+ <span><span class="link" tabindex="0"></span></span>
+ </div>
-/* Return the DOM element for a "most visited" entry.
- |page| should be an object with "title", "url", and "direction" fields. */
-function makeMostVisitedDOM(page, number) {
- /* The HTML we want looks like this:
- <a class="disabled-on-edit" href="URL" title="gmail.com">
- <div class="thumbnail-title disabled-on-edit"
- style="direction:ltr">gmail.com</div>
- <img class="thumbnail disabled-on-edit"
- style="background-image:url(thumbnailurl);">
- <img class="thumbnail-icon disabled-on-edit" src="chrome://faviconurl">
+ <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="edit-link link"></div>
+ <div class="remove"></div>
+ </div>
+ <span class="thumbnail-wrapper">
+ <span class="thumbnail"></span>
+ </span>
+ </div>
+ <div class="title">
+ <div></div>
+ </div>
</a>
- */
- var root;
- if (page.url) {
- root = DOM('a', {className:'disabled-on-edit',
- href:page.url,
- title:page.title});
- root.addEventListener("mousedown", function(event) {
- chrome.send("metrics", ["NTP_MostVisited" + number])
- }, false);
- } else {
- // Something went wrong; don't make it clickable.
- root = DOM('span');
- }
-
- /* Create the thumbnail */
- var img_thumbnail = DOM('img', {className:'thumbnail disabled-on-edit'});
- img_thumbnail.setAttribute('onload', "logEvent('image loaded');");
- img_thumbnail.src = 'chrome://thumb/' + page.url;
-
- /* Create the title */
- var div_title = DOM('div', {className:'thumbnail-title disabled-on-edit'});
- /* Set the title's directionality independently of the overall page
- directionality. We need to do this since a purely LTR title should always
- have it's direction set as ltr. We only set the title direction to rtl if
- it contains a strong RTL character. Please refer to http://crbug.com/5926
- for more information.
- */
- div_title.style.direction = page.direction;
- /* The following if statement is a temporary workaround for
- http://crbug.com/7252 and http://crbug.com/7697. It should be removed
- before closing these bugs.
- */
- if (page.direction == 'rtl') {
- div_title.style.textOverflow = 'clip';
- }
- if (page.title) {
- div_title.appendChild(document.createTextNode(page.title));
- } else {
- // Make the empty title at least push down the icon.
- div_title.innerHTML = '&nbsp;';
- }
-
- // Create icon
- var img_icon = DOM('img', {
- className: 'thumbnail-icon disabled-on-edit',
- src: 'chrome://favicon/' + page.url
- });
-
- root.appendChild(div_title);
- root.appendChild(img_thumbnail);
- root.appendChild(img_icon);
-
- return root;
-}
-
-/* Return the DOM element for the cross that should be displayed in edit-mode
- over a "most visited" entry. */
-function makeCrossImageDOM(url) {
- var cross = DOM('div', {className:'edit-cross'});
- cross.addEventListener("mousedown",
- function(event) {
- if (event.which == 1) // Left click only.
- blacklistURL(url); },
- false);
- return cross;
-}
-
-/* This function is called by the browser with the most visited pages list.
- |pages| is a list of page objects, which have url, title, and direction
- attributes. */
-function renderMostVisitedPages(pages) {
- logEvent('renderMostVisitedPages called: ' + pages.length);
-
- // TODO(arv): Only send 8 pages
- pages = pages.slice(0, 8);
-
- var table = $("main");
- // If we were in edit-mode, stay in that mode as this means this is a
- // refresh triggered by thumbnails editing.
- if (table.className.indexOf('edit-mode') != -1)
- table.className = 'visible edit-mode';
- else
- table.className = 'visible';
- var table = $("mostvisitedtable");
- table.innerHTML = '';
-
- // Show the most visited helptext if most visited is still useless. This is
- // a crappy heuristic.
- if (pages.length < 4) {
- $("mostvisitedintro").style.display = "block";
- return;
- }
-
- $('mostvisitedintro').style.display = 'none';
-
- // Create the items and add them to rows.
- var rows = [];
- var rowNum = -1;
- for (var i = 0, page; page = pages[i]; ++i) {
- if (i % 4 == 0) {
- rowNum += 1;
- rows[rowNum] = DOM('tr', {});
- }
- var cell = DOM('td');
- var container = DOM('div', { className: "most-visited-container"});
- container.appendChild(makeCrossImageDOM(page.url));
- container.appendChild(makeMostVisitedDOM(page, i));
- cell.appendChild(container);
-
- rows[rowNum].appendChild(cell);
-
- logEvent('mostVisitedPage : ' + i);
- }
-
- // Add the rows to the table.
- for (var i = 0, row; row = rows[i]; i++) {
- table.appendChild(row);
- }
-
- logEvent('renderMostVisitedPages done');
-}
-
-function makeSearchURL(url) {
- /* The HTML we want looks like this:
- <form>
- <input type="text" class="hint"
- style="background-image:url(chrome://favicon/"+url+");"
- placeholder="Search Wikipedia">
- </form>
- */
- var input = DOM('input', {type:'text',
- className: 'hint'});
- // There is no DOM property for placeholder.
- input.setAttribute('placeholder', url.short_name);
- input.keyword = url.keyword;
-
- if (url.favIconURL) {
- input.style.backgroundImage =
- 'url("chrome://favicon/iconurl/' + url.favIconURL + '")';
- } else {
- input.style.backgroundImage =
- 'url("chrome://favicon/http://' + url.short_name + '")';
- }
-
- var form = DOM('form');
- form.onsubmit = function() {
- chrome.send('doSearch', [input.keyword, input.value]);
- return false;
- };
- form.appendChild(input);
-
- return form;
-}
-
-/* This function is called by the browser when the list of search URLs is
- available. |urls| is a list of objects with |name| attributes. */
-function renderSearchURLs(urls) {
- logEvent('renderSearchURLs called: ' + urls.length);
- var container = $('searches-entries');
- container.innerHTML = ''; // Clear out any previous contents.
- if (urls.length > 0) {
- $('searches').style.display = 'block';
- for (var i = 0; i < urls.length; ++i) {
- container.appendChild(makeSearchURL(urls[i]));
- }
- }
-
- logEvent('renderSearchURLs done');
-}
-
-/**
- * Renders recent history items, bookmarks, searches and tabs.
- */
-function renderRecentItems() {
- var recentSection = $('recent-section');
- var containerEl = $('recent-items');
-
- // Filter out subsequent entries from the same domain as well as remove exact
- // duplicates from earlier (based on url).
- var lastDomain;
- var seenUrls = {};
- recent.entries = recent.entries.filter(function(entry) {
- // Mark the item as hidden if we have seen the domain before
- var url = entry.url;
- // Closed windows don't have an URL but we never filter them out.
- if (!url) {
- return true;
- }
-
- var m = url.match(/^[^:]+:\/*[^\/]+/);
- var previousDomain = lastDomain;
- lastDomain = m && m[0];
- return previousDomain != lastDomain;
- });
-
- recent.sortEntries();
-
- recentSection.style.display = recent.entries.length ? 'block' : 'none';
-
- containerEl.innerHTML = '';
-
- var last;
- for (var i = 0; i < recent.entries.length; i++) {
- var entry = recent.entries[i];
-
- if (i > recent.MAX_ITEMS) {
- entry.remove();
- continue;
- }
-
- if (entry.removed) {
- continue;
- }
-
- last = entry.time;
-
- if (!entry.element) {
- entry.createDom();
- }
-
- containerEl.appendChild(entry.element);
- }
-
- recent.entries = recent.entries.filter(function(d) {
- return !d.removed;
- });
-}
-
-/**
- * This function adds incoming information about tabs to the new tab UI.
- */
-function renderRecentlyClosedTabs(entries) {
- logEvent('renderRecentlyClosedTabs begin');
- var section = $('recentlyClosedTabs');
- var container = $('recentlyClosedContainer');
-
- // recentlyClosedTabs is called on every internal event which
- // affects tab history to make sure things are up to
- // date. Therefore, reset the recentlyClosedTabs state on every
- // call.
- section.style.display = 'none';
- container.innerHTML = '';
-
- if (entries.length > 0) {
- section.style.display = 'block';
-
- for (var i = 0; entry = entries[i]; ++i) {
- var link;
-
- if (entry.type == "tab") {
- // Closed tab.
- link = createRecentBookmark('a', entry);
- container.appendChild(link);
- } else {
- // Closed window.
- var linkSpanContainer = DOM('div', {className: 'recent-window-container'});
-
- var linkSpan = DOM('span');
- linkSpan.textContent = ' ';
- for (var windowIndex = 0; windowIndex < entry.tabs.length; windowIndex++) {
- var tab = entry.tabs[windowIndex];
- var tabImg = DOM('img', {
- src:'url("chrome://favicon/' + tab.url + '")',
- width:16,
- height:16});
- tabImg.onmousedown = function() { return false; }
- linkSpan.appendChild(tabImg);
- }
-
- link = DOM('span', { className: 'recently-closed-window-link' } );
- windowSpan = DOM('span', {className: 'recently-close-window-text'});
- windowSpan.appendChild(document.createTextNode(
- recentlyClosedWindowText(entry.tabs.length)));
- link.appendChild(windowSpan);
- link.appendChild(linkSpan);
- linkSpanContainer.appendChild(link);
- container.appendChild(linkSpanContainer);
-
- // The card takes care of appending itself to the DOM, so no need to
- // keep a reference to it.
- new RecentlyClosedHoverCard(linkSpanContainer, entry);
- }
-
- link.onclick = function(sessionId) {
- return function() {
- chrome.send("metrics", ["NTP_TabRestored" + i]);
- // This is a hack because chrome.send is hardcoded to only
- // accept arrays of strings.
- chrome.send('reopenTab', [sessionId.toString()]);
- return false;
- }
- }(entry.sessionId);
- }
- }
-
- logEvent('renderRecentlyClosedTabs done');
-}
-
-/**
- * Returns the text used for a recently closed window.
- *
- * @param numTabs number of tabs in the window
- *
- * @return the text to use
- */
-function recentlyClosedWindowText(numTabs) {
- if (numTabs == 1)
- return localStrings.getString('closedwindowsingle');
- // TODO(arv): This should use localStrings.formatString but we need to update
- // the grd file for that.
- return localStrings.getString('closedwindowmultiple').replace('%', numTabs);
-}
-
-/**
- * Creates an item to go in the recent bookmarks or recently closed lists.
- *
- * @param {String} tagName Tagname for the DOM element to create.
- * @param {Object} data Object with title, url, and direction to popuplate the element.
- *
- * @return {Node} The element containing the bookmark.
- */
-function createRecentBookmark(tagName, data) {
- var link = DOM(tagName, {className:'recent-bookmark', title:data.title});
- if (tagName == 'a')
- link.href = data.url;
- link.style.backgroundImage = 'url("chrome://favicon/' + data.url + '")';
- // Set the title's directionality independently of the page, see comment
- // about setting div_title.style.direction above for details.
- link.style.direction = data.direction;
- // The following if statement is a temporary workaround for
- // http://crbug.com/7252 and http://crbug.com/7697. It should be removed
- // before closing these bugs.
- if (data.direction == 'rtl') {
- link.style.textOverflow = 'clip';
- }
-
- link.appendChild(document.createTextNode(data.title));
- return link;
-}
-
-/**
- * A hover card for windows in the recently closed list to show more details.
- *
- * @param {Node} target The element the hover card is for.
- * @param {Object} data Object containing all the data for the card.
- */
-function RecentlyClosedHoverCard(target, data) {
- this.target_ = target;
- this.data_ = data;
- this.target_.onmouseover = bind(this.setShowTimeout_, this);
- this.target_.onmouseout = bind(this.setHideTimeout_, this);
-}
-
-/** Timeout set when closing the card. */
-RecentlyClosedHoverCard.closeTimeout_;
-
-/** Timeout set when opening the card. */
-RecentlyClosedHoverCard.openTimeout_;
-
-/**
- * Clears the timer for hiding the card.
- */
-RecentlyClosedHoverCard.clearHideTimeout_ = function() {
- clearTimeout(RecentlyClosedHoverCard.closeTimeout_);
-};
-
-/**
- * Clears the timer for opening the card.
- */
-RecentlyClosedHoverCard.clearOpenTimeout_ = function() {
- clearTimeout(RecentlyClosedHoverCard.openTimeout_);
-};
-
-/**
- * Creates and shows the card.
- */
-RecentlyClosedHoverCard.prototype.show_ = function() {
- if (!this.container_) {
- this.container_ = DOM('div', {className: 'recent-window-hover-container'});
- for (var i = 0; i < this.data_.tabs.length; i++) {
- var tab = this.data_.tabs[i];
- var item = createRecentBookmark('span', tab);
- this.container_.appendChild(item);
- }
- this.target_.parentNode.insertBefore(this.container_,
- this.target_.nextSibling);
- this.container_.onmouseover = RecentlyClosedHoverCard.clearHideTimeout_;
- this.container_.onmouseout = bind(this.setHideTimeout_, this);
- }
- this.container_.style.display = '';
-};
-
-/**
- * Hides the card.
- */
-RecentlyClosedHoverCard.prototype.hide_ = function() {
- this.container_.style.display = 'none';
-};
-
-/**
- * Clears any open timers and sets the open timer.
- * If the card is already showing then we only need to clear
- * the hide timer.
- */
-RecentlyClosedHoverCard.prototype.setShowTimeout_ = function() {
- if (this.container && this.container_.style.display != 'none') {
- // If we're already showing the hovercard, make sure we don't hide it again
- // onmouseover.
- RecentlyClosedHoverCard.clearHideTimeout_();
- return;
- }
-
- RecentlyClosedHoverCard.clearOpenTimeout_();
- RecentlyClosedHoverCard.openTimeout_ =
- setTimeout(bind(this.show_, this), 200);
-};
-
-/**
- * Clears the open timer and sets the close one.
- */
-RecentlyClosedHoverCard.prototype.setHideTimeout_ = function() {
- RecentlyClosedHoverCard.clearOpenTimeout_();
- RecentlyClosedHoverCard.closeTimeout_ =
- setTimeout(bind(this.hide_, this), 200);
-};
-
-/**
- * Switches to thumbnails editing mode.
- */
-function enterEditMode() {
- // If the cross-image in the heading has not been added yet, do it.
- // Note that we have to insert the image node explicitly because the
- // heading is localized and therefore set at run-time, and we need
- // the image to be static so that it can be inlined at build-time.
- var crossImageDiv = $('cross-image-container');
- if (crossImageDiv && !crossImageDiv.hasChildNodes()) {
- var image = $('small-cross-image');
- image.parentNode.removeChild(image);
- crossImageDiv.appendChild(image);
- image.style.display = 'inline';
- crossImageDiv.style.display = 'inline';
- }
- $('main').className = 'visible edit-mode';
-}
-
-function exitEditMode() {
- $('main').className = 'visible';
- blacklistedURLs = [];
-}
-
-function cancelEdits() {
- if (blacklistedURLs.length > 0)
- chrome.send("removeURLsFromMostVisitedBlacklist", blacklistedURLs);
- exitEditMode();
-}
-
-function blacklistURL(url) {
- blacklistedURLs.push(url);
- chrome.send("blacklistURLFromMostVisited", [url]);
-}
-
-function restoreThumbnails() {
- exitEditMode();
- chrome.send('clearMostVisitedURLsBlacklist');
-}
-
-function viewLog() {
- var lines = [];
- var start = log[0][1];
-
- for (var i = 0; i < log.length; i++) {
- lines.push((log[i][1] - start) + ': ' + log[i][0]);
- }
-
- var lognode = document.createElement('pre');
- lognode.appendChild(document.createTextNode(lines.join("\n")));
- document.body.appendChild(lognode);
-}
+ </div>
-logEvent('end of second script block');
+ <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 +
+ ')';
+ .style.direction:direction;
+ .sessionId:sessionId"
+ jscontent="title"></a>
+ <div jsdisplay="type == 'window'"
+ class="item link window"
+ jsvalues=".sessionId:sessionId"
+ tabindex="0">
+ <span jscontent="formatTabsText($this.tabs.length)"></span>
+ <div class="window-menu">
+ <span jsselect="tabs">
+ <span class="item"
+ jsdisplay="type == 'tab'"
+ jsvalues="title:title;
+ .style.backgroundImage:'url(chrome://favicon/' +
+ url + ')';
+ .style.direction:direction;"
+ jscontent="title"></span>
+ </span>
+ </div>
+ </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 + ')';
+ .fileId:id"
+ jscontent="file_name"></a>
+ </div>
+
+ <div>
+ <a href="chrome://downloads/" class="item nav"
+ jscontent="viewalldownloads"></a>
+ </div>
+
+ </div>
+ </div>
-localStrings = new LocalStrings();
+ </div>
-// We've got all the JS we need, render any unprocessed data.
-renderFunctionsDefined = true;
-processData();
+ </div><div class="spacer">
-// In case renderMostVisitedItems doesn't come back quickly enough, begin
-// the first-run fade-in. If it has started or if this is not a first
-// run new tab, this will be a no-op.
-setTimeout(function(){$('main').className = 'visible'},
- 1000);
-</script>
+ </div><div id="recommendations" class="section">
+ <h2>Even more</h2>
+ <p style="margin:10px">What will we put here?
+ </div>
+ </div>
-<img id="small-cross-image" style="display: none; vertical-align:middle;" alt="X"
- src="../../app/theme/ntp_x_icon_small.png"/>
-</body>
+</div> <!-- main -->
-<style>
-/* This CSS code is located at the end of file so it does not slow-down the page
- loading, as it contains inlined images.
-*/
-.edit-mode div.edit-cross {
- position: absolute;
- z-index: 10;
- width: 81px;
- height: 81px;
- left: 60px;
- top: 47px;
- background: url('../../app/theme/ntp_x_icon.png');
-}
-.edit-mode div.edit-cross:hover {
- background: url('../../app/theme/ntp_x_icon_hover.png');
-}
-.edit-mode div.edit-cross:active {
- background: url('../../app/theme/ntp_x_icon_active.png');
-}
-.recent-window-container {
- background: url('../../app/theme/closed_window.png');
- background-repeat: no-repeat;
-}
-html[dir='rtl'] .recent-window-container {
- background-position: right;
- padding-right: 22px;
-}
-</style>
+<script src="local_strings.js"></script>
+<script src="new_new_tab.js"></script>
</html>
diff --git a/chrome/browser/resources/new_new_tab.js b/chrome/browser/resources/new_new_tab.js
new file mode 100644
index 0000000..b62aefc
--- /dev/null
+++ b/chrome/browser/resources/new_new_tab.js
@@ -0,0 +1,891 @@
+
+// Helpers
+
+function $(id) {
+ return document.getElementById(id);
+}
+
+// TODO(arv): Remove these when classList is available in HTML5.
+// https://bugs.webkit.org/show_bug.cgi?id=20709
+function hasClass(el, name) {
+ return el.nodeType == 1 && el.className.split(/\s+/).indexOf(name) != -1;
+}
+
+function addClass(el, name) {
+ el.className += ' ' + name;
+}
+
+function removeClass(el, name) {
+ var names = el.className.split(/\s+/);
+ el.className = names.filter(function(n) {
+ return name != n;
+ }).join(' ');
+}
+
+function findAncestorByClass(el, className) {
+ return findAncestor(el, function(el) {
+ return hasClass(el, className);
+ });
+}
+
+function findAncestor(el, predicate) {
+ while (el != null && !predicate(el)) {
+ el = el.parentNode;
+ }
+ return el;
+}
+
+// WebKit does not have Node.prototype.swapNode
+// https://bugs.webkit.org/show_bug.cgi?id=26525
+function swapDomNodes(a, b) {
+ var afterA = a.nextSibling;
+ if (afterA == b) {
+ swapDomNodes(b, a);
+ return;
+ }
+ var aParent = a.parentNode;
+ b.parentNode.replaceChild(a, b);
+ aParent.insertBefore(b, afterA);
+}
+
+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);
+ }
+}
+
+var mostVisitedData = [];
+var gotMostVisited = false;
+var gotShownSections = false;
+
+function mostVisitedPages(data) {
+ logEvent('received most visited pages');
+
+ // We append the class name with the "filler" so that we can style fillers
+ // differently.
+ var maxItems = 8;
+ data.length = Math.min(maxItems, data.length);
+ var len = data.length;
+ for (var i = len; i < maxItems; i++) {
+ data[i] = {filler: true};
+ }
+
+ mostVisitedData = data;
+ renderMostVisited(data);
+ layoutMostVisited();
+
+ gotMostVisited = true;
+ onDataLoaded();
+}
+
+function downloadsList(data) {
+ logEvent('received downloads');
+ data.length = Math.min(data.length, 5);
+ processData('#download-items', data);
+}
+
+function recentlyClosedTabs(data) {
+ logEvent('received recently closed tabs');
+ data.length = Math.min(data.length, 5);
+ processData('#tab-items', data);
+}
+
+function onShownSections(m) {
+ logEvent('received shown sections');
+ setShownSections(m);
+ gotShownSections = true;
+ onDataLoaded();
+}
+
+function saveShownSections() {
+ chrome.send('setShownSections', [String(shownSections)]);
+}
+
+function layoutMostVisited() {
+ var d0 = Date.now();
+ var mostVisitedElement = $('most-visited');
+ var thumbnails = mostVisitedElement.querySelectorAll('.thumbnail-container');
+
+ if (thumbnails.length < 8) {
+ return;
+ }
+
+ var small = useSmallGrid();
+
+ var cols = 4;
+ var rows = 2;
+ var marginWidth = 10;
+ var marginHeight = 7;
+ var borderWidth = 4;
+ var thumbWidth = small ? 150 : 212;
+ var thumbHeight = small ? 93 : 132;
+ var w = thumbWidth + 2 * borderWidth + 2 * marginWidth;
+ var h = thumbHeight + 40 + 2 * marginHeight;
+ var sumWidth = cols * w - 2 * marginWidth;
+ var sumHeight = rows * h;
+ var opacity = 1;
+
+ if (shownSections & Section.LIST) {
+ w = (sumWidth + 2 * marginWidth) / 2;
+ h = 45;
+ rows = 4;
+ cols = 2;
+ sumHeight = rows * h;
+ addClass(mostVisitedElement, 'list');
+ } else if (shownSections & Section.THUMB) {
+ removeClass(mostVisitedElement, 'list');
+ } else {
+ sumHeight = 0;
+ opacity = 0;
+ }
+
+ mostVisitedElement.style.height = sumHeight + 'px';
+ mostVisitedElement.style.opacity = opacity;
+ // We set overflow to hidden so that the most visited element does not
+ // "leak" when we hide and show it.
+ mostVisitedElement.style.overflow = 'hidden';
+
+ var rtl = document.documentElement.dir == 'rtl';
+
+ if (shownSections & Section.THUMB || shownSections & Section.LIST) {
+ for (var i = 0; i < thumbnails.length; i++) {
+ var t = thumbnails[i];
+
+ var row, col;
+ if (shownSections & Section.THUMB) {
+ row = Math.floor(i / cols);
+ col = i % cols;
+ } else {
+ col = Math.floor(i / rows);
+ row = i % rows;
+ }
+
+ if (shownSections & Section.THUMB) {
+ t.style.left = (rtl ?
+ sumWidth - col * w - thumbWidth - 2 * borderWidth :
+ col * w) + 'px';
+ } else {
+ t.style.left = (rtl ?
+ sumWidth - col * w - w + 2 * marginWidth :
+ col * w) + 'px';
+ }
+ t.style.top = row * h + 'px';
+
+ if (shownSections & Section.LIST) {
+ t.style.width = w - 2 * marginWidth + 'px';
+ } else {
+ t.style.width = '';
+ }
+ }
+ }
+
+ afterTransition(function() {
+ mostVisitedElement.style.overflow = '';
+ });
+
+ logEvent('layoutMostVisited: ' + (Date.now() - d0));
+}
+
+// This global variable is used to skip parts of the DOM tree for the global
+// jst processing done by the i18n.
+var processing = false;
+
+function processData(selector, data) {
+ var output = document.querySelector(selector);
+
+ // Wait until ready
+ if (typeof JsEvalContext !== 'function' || !output) {
+ logEvent('JsEvalContext is not yet available, ' + selector);
+ document.addEventListener('DOMContentLoaded', function() {
+ processData(selector, data);
+ });
+ } else {
+ var d0 = Date.now();
+ var input = new JsEvalContext(data);
+ processing = true;
+ jstProcess(input, output);
+ processing = false;
+ logEvent('processData: ' + selector + ', ' + (Date.now() - d0));
+ }
+}
+
+var thumbnailTemplate;
+
+function getThumbnailClassName(data) {
+ return 'thumbnail-container' +
+ (data.pinned ? ' pinned' : '') +
+ (data.filler ? ' filler' : '');
+}
+
+function renderMostVisited(data) {
+ var parent = $('most-visited');
+ if (!thumbnailTemplate) {
+ thumbnailTemplate = $('thumbnail-template');
+ thumbnailTemplate.parentNode.removeChild(thumbnailTemplate);
+ }
+
+ var children = parent.children;
+ for (var i = 0; i < data.length; i++) {
+ var d = data[i];
+ var reuse = !!children[i];
+ var t = children[i] || thumbnailTemplate.cloneNode(true);
+ t.style.display = '';
+ t.className = getThumbnailClassName(d);
+ t.title = d.title;
+ t.href = d.url;
+ t.querySelector('.edit-link').textContent =
+ localStrings.getString('editthumbnail');
+
+ // There was some concern that a malformed malicious URL could cause an XSS
+ // attack but setting style.backgroundImage = 'url(javascript:...)' does
+ // not execute the JavaScript in WebKit.
+ t.querySelector('.thumbnail-wrapper').style.backgroundImage =
+ 'url(chrome://thumb/' + d.url + ')';
+ var titleDiv = t.querySelector('.title > div');
+ titleDiv.textContent = d.title;
+ titleDiv.style.backgroundImage = 'url(chrome://favicon/' + d.url + ')';
+ titleDiv.style.direction = d.direction;
+ if (!reuse) {
+ parent.appendChild(t);
+ }
+ }
+}
+
+/**
+ * Calls chrome.send with a callback and restores the original afterwards.
+ */
+function chromeSend(name, params, callbackName, callback) {
+ var old = global[callbackName];
+ global[callbackName] = function() {
+ // restore
+ global[callbackName] = old;
+
+ var args = Array.prototype.slice.call(arguments);
+ return callback.apply(global, args);
+ };
+ chrome.send(name, params);
+}
+
+function useSmallGrid() {
+ return document.body.clientWidth <= 940;
+}
+
+function handleWindowResize(e, opt_noUpdate) {
+ var body = document.body;
+ if (!body || body.clientWidth < 10) {
+ // We're probably a background tab, so don't do anything.
+ return;
+ }
+
+ var hasSmallClass = hasClass(body, 'small');
+ if (hasSmallClass && !useSmallGrid()) {
+ removeClass(body, 'small');
+ if (!opt_noUpdate) {
+ layoutMostVisited();
+ layoutLowerSections();
+ }
+ } else if (!hasSmallClass && useSmallGrid()) {
+ addClass(body, 'small');
+ if (!opt_noUpdate) {
+ layoutMostVisited();
+ layoutLowerSections();
+ }
+ }
+}
+
+/**
+ * Bitmask for the different UI sections.
+ * This matches the Section enum in ../dom_ui/shown_sections_handler.h
+ * @enum {number}
+ */
+var Section = {
+ THUMB: 1,
+ LIST: 2,
+ RECENT: 4,
+ RECOMMENDATIONS: 8
+};
+
+var shownSections = Section.RECENT | Section.RECOMMENDATIONS;
+
+function showSection(section) {
+ if (!(section & shownSections)) {
+ // THUMBS and LIST are mutually exclusive.
+ if (section == Section.THUMB) {
+ hideSection(Section.LIST);
+ } else if (section == Section.LIST) {
+ hideSection(Section.THUMB);
+ }
+
+ shownSections |= section;
+ notifyLowerSectionForChange(section, false);
+
+ mostVisited.updateDisplayMode();
+ layoutMostVisited();
+ updateOptionMenu();
+ layoutLowerSections();
+ }
+}
+
+function hideSection(section) {
+ if (section & shownSections) {
+ shownSections &= ~section;
+ notifyLowerSectionForChange(section, true);
+
+ mostVisited.updateDisplayMode();
+ layoutMostVisited();
+ updateOptionMenu();
+ layoutLowerSections();
+ }
+}
+
+function notifyLowerSectionForChange(section, large) {
+ // Notify recent and recommendations if they need to display more data.
+ if (section == Section.RECENT || section == Section.RECOMMENDATIONS) {
+ // we are hiding one of them so if the other one is visible it is now
+ // {@code large}.
+ if (shownSections & Section.RECENT) {
+ recentChangedSize(large);
+ } else if (shownSections & Section.RECOMMENDATIONS) {
+ recommendationsChangedSize(large);
+ }
+ }
+}
+
+/**
+ * This is called when we get the shown sections pref from the backend.
+ */
+function setShownSections(mask) {
+ if (mask != shownSections) {
+ shownSections = mask;
+ mostVisited.updateDisplayMode();
+ layoutMostVisited();
+ layoutLowerSections();
+ updateOptionMenu();
+ }
+}
+
+var mostVisited = {
+ getItem: function(el) {
+ return findAncestorByClass(el, 'thumbnail-container');
+ },
+
+ getHref: function(el) {
+ return el.href;
+ },
+
+ togglePinned: function(el) {
+ var index = this.getThumbnailIndex(el);
+ var data = mostVisitedData[index];
+ if (data.pinned) {
+ removeClass(el, 'pinned');
+ chrome.send('removePinnedURL', [data.url]);
+ } else {
+ addClass(el, 'pinned');
+ chrome.send('addPinnedURL', [data.url, data.title, String(index)]);
+ }
+ data.pinned = !data.pinned;
+ },
+
+ getThumbnailIndex: function(el) {
+ var nodes = el.parentNode.querySelectorAll('.thumbnail-container');
+ return Array.prototype.indexOf.call(nodes, el);
+ },
+
+ swapPosition: function(source, destination) {
+ var nodes = source.parentNode.querySelectorAll('.thumbnail-container');
+ var sourceIndex = this.getThumbnailIndex(source);
+ var destinationIndex = this.getThumbnailIndex(destination);
+ swapDomNodes(source, destination);
+
+ var sourceData = mostVisitedData[sourceIndex];
+ chrome.send('addPinnedURL', [sourceData.url, sourceData.title,
+ String(destinationIndex)]);
+ sourceData.pinned = true;
+ addClass(source, 'pinned');
+ var destinationData = mostVisitedData[destinationIndex];
+ // Only update the destination if it was pinned before.
+ if (destinationData.pinned) {
+ chrome.send('addPinnedURL', [destinationData.url, destinationData.title,
+ String(sourceIndex)]);
+ }
+ mostVisitedData[destinationIndex] = sourceData;
+ mostVisitedData[sourceIndex] = destinationData;
+ },
+
+ blacklist: function(el) {
+ var self = this;
+ var url = this.getHref(el);
+ chrome.send('blacklistURLFromMostVisited', [url]);
+
+ addClass(el, 'hide');
+
+ // Find the old item.
+ var oldUrls = {};
+ var oldIndex = -1;
+ var oldItem;
+ for (var i = 0; i < mostVisitedData.length; i++) {
+ if (mostVisitedData[i].url == url) {
+ oldItem = mostVisitedData[i];
+ oldIndex = i;
+ }
+ oldUrls[mostVisitedData[i].url] = true;
+ }
+
+ // Send 'getMostVisitedPages' with a callback since we want to find the new
+ // page and add that in the place of the removed page.
+ chromeSend('getMostVisited', [], 'mostVisitedPages', function(data) {
+ // Find new item.
+ var newItem;
+ for (var i = 0; i < data.length; i++) {
+ if (!(data[i].url in oldUrls)) {
+ newItem = data[i];
+ break;
+ }
+ }
+
+ if (!newItem) {
+ newItem = {filler: true};
+ }
+
+ // Replace old item with new item in the mostVisitedData array.
+ if (oldIndex != -1) {
+ mostVisitedData.splice(oldIndex, 1, newItem);
+ mostVisitedPages(mostVisitedData);
+ addClass(el, 'fade-in');
+ }
+
+ var text = localStrings.formatString('thumbnailremovednotification',
+ oldItem.title);
+ var actionText = localStrings.getString('undothumbnailremove');
+
+ // Show notification and add undo callback function.
+ var wasPinned = oldItem.pinned;
+ showNotification(text, actionText, function() {
+ self.removeFromBlackList(url);
+ if (wasPinned) {
+ chromeSend('addPinnedURL', [url, oldItem.title, String(oldIndex)]);
+ }
+ chrome.send('getMostVisited');
+ });
+ });
+ },
+
+ removeFromBlackList: function(url) {
+ chrome.send('removeURLsFromMostVisitedBlacklist', [url]);
+ },
+
+ clearAllBlacklisted: function() {
+ chrome.send('clearMostVisitedURLsBlacklist', []);
+ },
+
+ updateDisplayMode: function() {
+ var thumbCheckbox = $('thumb-checkbox');
+ var listCheckbox = $('list-checkbox');
+ var mostVisitedElement = $('most-visited');
+
+ if (shownSections & Section.THUMB) {
+ thumbCheckbox.checked = true;
+ listCheckbox.checked = false;
+ removeClass(mostVisitedElement, 'list');
+ } else if (shownSections & Section.LIST) {
+ thumbCheckbox.checked = false;
+ listCheckbox.checked = true;
+ addClass(mostVisitedElement, 'list');
+ } else {
+ thumbCheckbox.checked = false;
+ listCheckbox.checked = false;
+ }
+ }
+};
+
+function recentChangedSize(large) {
+ // TODO(arv): Implement
+}
+
+function recommendationsChangedSize(large) {
+ // TODO(arv): Implement
+}
+
+// Recent activities
+
+function layoutLowerSections() {
+ // This lower sections are inline blocks so all we need to do is to set the
+ // width and opacity.
+ var lowerSectionsElement = $('lower-sections');
+ var recentElement = $('recent-activities');
+ var recommendationsElement = $('recommendations');
+ var spacer = recentElement.nextElementSibling;
+
+ var totalWidth = useSmallGrid() ? 692 : 940;
+ var spacing = 20;
+ var rtl = document.documentElement.dir == 'rtl';
+
+ var recentShown = shownSections & Section.RECENT;
+ var recommendationsShown = shownSections & Section.RECOMMENDATIONS;
+
+ if (recentShown || recommendationsShown) {
+ lowerSectionsElement.style.height = '175px'
+ lowerSectionsElement.style.opacity = '';
+ } else {
+ lowerSectionsElement.style.height = lowerSectionsElement.style.opacity = 0;
+ }
+
+ if (recentShown && recommendationsShown) {
+ var w = (totalWidth - spacing) / 2;
+ recentElement.style.width = recommendationsElement.style.width = w + 'px'
+ recentElement.style.opacity = recommendationsElement.style.opacity = '';
+ spacer.style.width = spacing + 'px';
+ } else if (recentShown) {
+ recentElement.style.width = totalWidth + 'px';
+ recentElement.style.opacity = '';
+ recommendationsElement.style.width =
+ recommendationsElement.style.opacity = 0;
+ spacer.style.width = 0;
+ } else if (recommendationsShown) {
+ recommendationsElement.style.width = totalWidth + 'px';
+ recommendationsElement.style.opacity = '';
+ recentElement.style.width = recentElement.style.opacity = 0;
+ spacer.style.width = 0;
+ }
+}
+
+/**
+ * Returns the text used for a recently closed window.
+ * @param {number} numTabs Number of tabs in the window.
+ * @return {string} The text to use.
+ */
+function formatTabsText(numTabs) {
+ if (numTabs == 1)
+ return localStrings.getString('closedwindowsingle');
+ // TODO(arv): Update grd file to use %s so we can use formatString
+ // http://crbug.com/14878
+ // return localStrings.formatString('closedwindowmultiple', numTabs);
+ return localStrings.getString('closedwindowmultiple').replace(/%/, numTabs);
+}
+
+/**
+ * We need both most visited and the shown sections to be considered loaded.
+ * @return {boolean}
+ */
+function onDataLoaded() {
+ if (gotMostVisited && gotShownSections) {
+ // Remove class name in a timeout so that changes done in this JS thread are
+ // not animated.
+ window.setTimeout(function() {
+ removeClass(document.body, 'loading');
+ }, 10);
+ }
+}
+
+// Theme related
+
+function themeChanged() {
+ $('themecss').href = 'chrome://theme/css/newtab.css?' + Date.now();
+ updateAttribution();
+}
+
+function updateAttribution() {
+ // TODO(arv): Implement
+ //$('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]);
+ }
+
+ console.log(lines.join('\n'));
+}
+
+// Updates the visibility of the menu items.
+function updateOptionMenu() {
+ var menuItems = $('option-menu').children;
+ for (var i = 0; i < menuItems.length; i++) {
+ var item = menuItems[i];
+ var section = Section[item.getAttribute('section')];
+ var show = item.getAttribute('show') == 'true';
+ // Hide show items if already shown. Hide hide items if already hidden.
+ var hideMenuItem = show == !!(shownSections & section);
+ item.style.display = hideMenuItem ? 'none' : '';
+ }
+}
+
+// We apply the size class here so that we don't trigger layout animations
+// onload.
+
+handleWindowResize(null, true);
+
+var localStrings = new LocalStrings();
+
+///////////////////////////////////////////////////////////////////////////////
+// Things we know are not needed at startup go below here
+
+// Notification
+
+function afterTransition(f) {
+ // The duration of all transitions are 500ms
+ window.setTimeout(f, 500);
+}
+
+function showNotification(text, actionText, f) {
+ var notificationElement = $('notification');
+ var actionLink = notificationElement.querySelector('.link');
+ notificationElement.firstElementChild.textContent = text;
+
+ actionLink.textContent = actionText;
+ actionLink.onclick = function() {
+ f();
+ removeClass(notificationElement, 'show');
+ // Since we have a :hover rule to not hide the notification banner when the
+ // mouse is over we need force it to hide. We remove the hide class after
+ // a short timeout to allow the banner to be shown again.
+ addClass(notificationElement, 'hide');
+ afterTransition(function() {
+ removeClass(notificationElement, 'hide');
+ })
+ };
+ addClass(notificationElement, 'show');
+ window.setTimeout(function() {
+ removeClass(notificationElement, 'show');
+ }, 10000);
+}
+
+// Options menu
+// TODO(arv): Keyboard navigation of the menu items.
+
+function showMenu(button, menu) {
+ function hide() {
+ menu.style.display = '';
+ menu.removeEventListener('blur', hide);
+ window.removeEventListener('blur', hide);
+ };
+ menu.addEventListener('blur', hide);
+ window.addEventListener('blur', hide);
+ menu.style.display = 'block';
+ menu.focus();
+}
+
+$('option-button').addEventListener('click', function(e) {
+ showMenu(this, $('option-menu'));
+});
+
+$('option-menu').addEventListener('click', function(e) {
+ var section = Section[e.target.getAttribute('section')];
+ var show = e.target.getAttribute('show') == 'true';
+ if (show) {
+ showSection(section);
+ } else {
+ hideSection(section);
+ }
+
+ // Hide menu now.
+ this.style.display = 'none';
+
+ layoutLowerSections();
+ mostVisited.updateDisplayMode();
+ layoutMostVisited();
+
+ saveShownSections();
+});
+
+$('most-visited').addEventListener('click', function(e) {
+ var target = e.target;
+ if (hasClass(target, 'pin')) {
+ mostVisited.togglePinned(mostVisited.getItem(target));
+ e.preventDefault();
+ } else if (hasClass(target, 'remove')) {
+ mostVisited.blacklist(mostVisited.getItem(target));
+ e.preventDefault();
+ } else if (hasClass(target, 'edit-link')) {
+ alert('Not implemented yet')
+ e.preventDefault();
+ }
+});
+
+$('downloads').addEventListener('click', function(e) {
+ var el = findAncestor(e.target, function(el) {
+ return el.fileId !== undefined;
+ });
+ if (el && el.fileId) {
+ chrome.send('openFile', [String(el.fileId)]);
+ e.preventDefault();
+ }
+});
+
+$('recent-tabs').addEventListener('click', function(e) {
+ var el = findAncestor(e.target, function(el) {
+ return el.sessionId !== undefined;
+ });
+ if (el && el.sessionId !== undefined) {
+ chrome.send('reopenTab', [String(el.sessionId)]);
+ e.preventDefault();
+ }
+});
+
+$('thumb-checkbox').addEventListener('change', function(e) {
+ if (e.target.checked) {
+ showSection(Section.THUMB);
+ } else {
+ hideSection(Section.THUMB);
+ }
+ mostVisited.updateDisplayMode();
+ layoutMostVisited();
+ saveShownSections();
+});
+
+$('list-checkbox').addEventListener('change', function(e) {
+ var newValue = shownSections;
+ if (e.target.checked) {
+ showSection(Section.LIST);
+ } else {
+ hideSection(Section.LIST);
+ }
+ mostVisited.updateDisplayMode();
+ layoutMostVisited();
+ saveShownSections();
+});
+
+window.addEventListener('load', bind(logEvent, global, 'onload fired'));
+window.addEventListener('load', onDataLoaded);
+window.addEventListener('resize', handleWindowResize);
+document.addEventListener('DOMContentLoaded', bind(logEvent, global,
+ 'domcontentloaded fired'));
+
+// DnD
+
+var dnd = {
+ currentOverItem: null,
+ dragItem: null,
+ startX: 0,
+ startY: 0,
+ startScreenX: 0,
+ startScreenY: 0,
+ dragEndTimer: null,
+
+ handleDragStart: function(e) {
+ var thumbnail = mostVisited.getItem(e.target);
+ if (thumbnail) {
+ e.dataTransfer.setData('text/uri-list', mostVisited.getHref(thumbnail));
+ this.dragItem = thumbnail;
+ addClass(this.dragItem, 'dragging');
+ this.dragItem.style.zIndex = 2;
+ }
+ },
+
+ handleDragEnter: function(e) {
+ this.currentOverItem = mostVisited.getItem(e.target);
+ if (this.canDropOnElement(this.currentOverItem)) {
+ e.preventDefault();
+ }
+ },
+
+ handleDragOver: function(e) {
+ var item = mostVisited.getItem(e.target);
+ if (this.canDropOnElement(item)) {
+ e.preventDefault();
+ }
+ },
+
+ handleDragLeave: function(e) {
+ var item = mostVisited.getItem(e.target);
+ if (item) {
+ e.preventDefault();
+ }
+
+ this.currentOverItem = null;
+ },
+
+ handleDrop: function(e) {
+ var dropTarget = mostVisited.getItem(e.target);
+ if (this.canDropOnElement(dropTarget)) {
+ dropTarget.style.zIndex = 1;
+ mostVisited.swapPosition(this.dragItem, dropTarget);
+ layoutMostVisited();
+ e.preventDefault();
+ if (this.dragEndTimer) {
+ window.clearTimeout(this.dragEndTimer);
+ this.dragEndTimer = null;
+ }
+ afterTransition(function() {
+ dropTarget.style.zIndex = '';
+ });
+ }
+ },
+
+ handleDragEnd: function(e) {
+ // WebKit fires dragend before drop.
+ var dragItem = this.dragItem;
+ if (dragItem) {
+ dragItem.style.pointerEvents = '';
+ removeClass(dragItem, 'dragging');
+
+ afterTransition(function() {
+ // Delay resetting zIndex to let the animation finish.
+ dragItem.style.zIndex = '';
+ // Same for overflow.
+ dragItem.parentNode.style.overflow = '';
+ });
+ var self = this;
+ this.dragEndTimer = window.setTimeout(function() {
+ // These things needto happen after the drop event.
+ layoutMostVisited();
+ self.dragItem = null;
+ }, 10);
+
+ }
+ },
+
+ handleDrag: function(e) {
+ var item = mostVisited.getItem(e.target);
+ var rect = document.querySelector('#most-visited').getBoundingClientRect();
+ item.style.pointerEvents = 'none';
+
+ item.style.left = this.startX + e.screenX - this.startScreenX + 'px';
+ item.style.top = this.startY + e.screenY - this.startScreenY + 'px';
+ },
+
+ // We listen to mousedown to get the relative position of the cursor for dnd.
+ handleMouseDown: function(e) {
+ var item = mostVisited.getItem(e.target);
+ if (item) {
+ this.startX = item.offsetLeft;
+ this.startY = item.offsetTop;
+ this.startScreenX = e.screenX;
+ this.startScreenY = e.screenY;
+ }
+ },
+
+ canDropOnElement: function(el) {
+ return this.dragItem && el && hasClass(el, 'thumbnail-container') &&
+ !hasClass(el, 'filler');
+ },
+
+ init: function() {
+ var el = $('most-visited');
+ el.addEventListener('dragstart', bind(this.handleDragStart, this));
+ el.addEventListener('dragenter', bind(this.handleDragEnter, this));
+ el.addEventListener('dragover', bind(this.handleDragOver, this));
+ el.addEventListener('dragleave', bind(this.handleDragLeave, this));
+ el.addEventListener('drop', bind(this.handleDrop, this));
+ el.addEventListener('dragend', bind(this.handleDragEnd, this));
+ el.addEventListener('drag', bind(this.handleDrag, this));
+ el.addEventListener('mousedown', bind(this.handleMouseDown, this));
+ }
+};
+
+dnd.init();