Updated drag and drop thumbnails.
[chromium-blink-merge.git] / chrome / browser / resources / ntp4 / new_tab.js
blob483446d59822bc3dd377f5a183dd760f29f29b67
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /**
6 * @fileoverview New tab page
7 * This is the main code for the new tab page used by touch-enabled Chrome
8 * browsers. For now this is still a prototype.
9 */
11 // Use an anonymous function to enable strict mode just for this file (which
12 // will be concatenated with other files when embedded in Chrome
13 cr.define('ntp', function() {
14 'use strict';
16 /**
17 * NewTabView instance.
18 * @type {!Object|undefined}
20 var newTabView;
22 /**
23 * The 'notification-container' element.
24 * @type {!Element|undefined}
26 var notificationContainer;
28 /**
29 * If non-null, an info bubble for showing messages to the user. It points at
30 * the Most Visited label, and is used to draw more attention to the
31 * navigation dot UI.
32 * @type {!Element|undefined}
34 var promoBubble;
36 /**
37 * If non-null, an bubble confirming that the user has signed into sync. It
38 * points at the login status at the top of the page.
39 * @type {!Element|undefined}
41 var loginBubble;
43 /**
44 * true if |loginBubble| should be shown.
45 * @type {boolean}
47 var shouldShowLoginBubble = false;
49 /**
50 * The 'other-sessions-menu-button' element.
51 * @type {!Element|undefined}
53 var otherSessionsButton;
55 /**
56 * The time when all sections are ready.
57 * @type {number|undefined}
58 * @private
60 var startTime;
62 /**
63 * The time in milliseconds for most transitions. This should match what's
64 * in new_tab.css. Unfortunately there's no better way to try to time
65 * something to occur until after a transition has completed.
66 * @type {number}
67 * @const
69 var DEFAULT_TRANSITION_TIME = 500;
71 /**
72 * See description for these values in ntp_stats.h.
73 * @enum {number}
75 var NtpFollowAction = {
76 CLICKED_TILE: 11,
77 CLICKED_OTHER_NTP_PANE: 12,
78 OTHER: 13
81 /**
82 * Creates a NewTabView object. NewTabView extends PageListView with
83 * new tab UI specific logics.
84 * @constructor
85 * @extends {PageListView}
87 function NewTabView() {
88 var pageSwitcherStart = null;
89 var pageSwitcherEnd = null;
90 if (loadTimeData.getValue('showApps')) {
91 pageSwitcherStart = getRequiredElement('page-switcher-start');
92 pageSwitcherEnd = getRequiredElement('page-switcher-end');
94 this.initialize(getRequiredElement('page-list'),
95 getRequiredElement('dot-list'),
96 getRequiredElement('card-slider-frame'),
97 getRequiredElement('trash'),
98 pageSwitcherStart, pageSwitcherEnd);
101 NewTabView.prototype = {
102 __proto__: ntp.PageListView.prototype,
104 /** @override */
105 appendTilePage: function(page, title, titleIsEditable, opt_refNode) {
106 ntp.PageListView.prototype.appendTilePage.apply(this, arguments);
108 if (promoBubble)
109 window.setTimeout(promoBubble.reposition.bind(promoBubble), 0);
114 * Invoked at startup once the DOM is available to initialize the app.
116 function onLoad() {
117 sectionsToWaitFor = loadTimeData.getBoolean('showApps') ? 2 : 1;
118 if (loadTimeData.getBoolean('isDiscoveryInNTPEnabled'))
119 sectionsToWaitFor++;
120 measureNavDots();
122 // Load the current theme colors.
123 themeChanged();
125 newTabView = new NewTabView();
127 notificationContainer = getRequiredElement('notification-container');
128 notificationContainer.addEventListener(
129 'webkitTransitionEnd', onNotificationTransitionEnd);
131 cr.ui.decorate($('recently-closed-menu-button'), ntp.RecentMenuButton);
132 chrome.send('getRecentlyClosedTabs');
134 if (loadTimeData.getBoolean('showOtherSessionsMenu')) {
135 otherSessionsButton = getRequiredElement('other-sessions-menu-button');
136 cr.ui.decorate(otherSessionsButton, ntp.OtherSessionsMenuButton);
137 otherSessionsButton.initialize(loadTimeData.getBoolean('isUserSignedIn'));
140 var mostVisited = new ntp.MostVisitedPage();
141 // Move the footer into the most visited page if we are in "bare minimum"
142 // mode.
143 if (document.body.classList.contains('bare-minimum'))
144 mostVisited.appendFooter(getRequiredElement('footer'));
145 newTabView.appendTilePage(mostVisited,
146 loadTimeData.getString('mostvisited'),
147 false);
148 chrome.send('getMostVisited');
150 if (loadTimeData.getBoolean('isDiscoveryInNTPEnabled')) {
151 var suggestions_script = document.createElement('script');
152 suggestions_script.src = 'suggestions_page.js';
153 suggestions_script.onload = function() {
154 newTabView.appendTilePage(new ntp.SuggestionsPage(),
155 loadTimeData.getString('suggestions'),
156 false,
157 (newTabView.appsPages.length > 0) ?
158 newTabView.appsPages[0] : null);
159 chrome.send('getSuggestions');
160 cr.dispatchSimpleEvent(document, 'sectionready', true, true);
162 document.querySelector('head').appendChild(suggestions_script);
165 var webStoreLink = loadTimeData.getString('webStoreLink');
166 var url = appendParam(webStoreLink, 'utm_source', 'chrome-ntp-launcher');
167 $('chrome-web-store-link').href = url;
168 $('chrome-web-store-link').addEventListener('click',
169 onChromeWebStoreButtonClick);
171 if (loadTimeData.getString('login_status_message')) {
172 loginBubble = new cr.ui.Bubble;
173 loginBubble.anchorNode = $('login-container');
174 loginBubble.arrowLocation = cr.ui.ArrowLocation.TOP_END;
175 loginBubble.bubbleAlignment =
176 cr.ui.BubbleAlignment.BUBBLE_EDGE_TO_ANCHOR_EDGE;
177 loginBubble.deactivateToDismissDelay = 2000;
178 loginBubble.closeButtonVisible = false;
180 $('login-status-advanced').onclick = function() {
181 chrome.send('showAdvancedLoginUI');
183 $('login-status-dismiss').onclick = loginBubble.hide.bind(loginBubble);
185 var bubbleContent = $('login-status-bubble-contents');
186 loginBubble.content = bubbleContent;
188 // The anchor node won't be updated until updateLogin is called so don't
189 // show the bubble yet.
190 shouldShowLoginBubble = true;
193 if (loadTimeData.valueExists('bubblePromoText')) {
194 promoBubble = new cr.ui.Bubble;
195 promoBubble.anchorNode = getRequiredElement('promo-bubble-anchor');
196 promoBubble.arrowLocation = cr.ui.ArrowLocation.BOTTOM_START;
197 promoBubble.bubbleAlignment = cr.ui.BubbleAlignment.ENTIRELY_VISIBLE;
198 promoBubble.deactivateToDismissDelay = 2000;
199 promoBubble.content = parseHtmlSubset(
200 loadTimeData.getString('bubblePromoText'), ['BR']);
202 var bubbleLink = promoBubble.querySelector('a');
203 if (bubbleLink) {
204 bubbleLink.addEventListener('click', function(e) {
205 chrome.send('bubblePromoLinkClicked');
209 promoBubble.handleCloseEvent = function() {
210 promoBubble.hide();
211 chrome.send('bubblePromoClosed');
213 promoBubble.show();
214 chrome.send('bubblePromoViewed');
217 var loginContainer = getRequiredElement('login-container');
218 loginContainer.addEventListener('click', showSyncLoginUI);
219 chrome.send('initializeSyncLogin');
221 doWhenAllSectionsReady(function() {
222 // Tell the slider about the pages.
223 newTabView.updateSliderCards();
224 // Mark the current page.
225 newTabView.cardSlider.currentCardValue.navigationDot.classList.add(
226 'selected');
228 if (loadTimeData.valueExists('notificationPromoText')) {
229 var promoText = loadTimeData.getString('notificationPromoText');
230 var tags = ['IMG'];
231 var attrs = {
232 src: function(node, value) {
233 return node.tagName == 'IMG' &&
234 /^data\:image\/(?:png|gif|jpe?g)/.test(value);
238 var promo = parseHtmlSubset(promoText, tags, attrs);
239 var promoLink = promo.querySelector('a');
240 if (promoLink) {
241 promoLink.addEventListener('click', function(e) {
242 chrome.send('notificationPromoLinkClicked');
246 showNotification(promo, [], function() {
247 chrome.send('notificationPromoClosed');
248 }, 60000);
249 chrome.send('notificationPromoViewed');
252 cr.dispatchSimpleEvent(document, 'ntpLoaded', true, true);
253 document.documentElement.classList.remove('starting-up');
255 startTime = Date.now();
258 preventDefaultOnPoundLinkClicks(); // From shared/js/util.js.
262 * Launches the chrome web store app with the chrome-ntp-launcher
263 * source.
264 * @param {Event} e The click event.
266 function onChromeWebStoreButtonClick(e) {
267 chrome.send('recordAppLaunchByURL',
268 [encodeURIComponent(this.href),
269 ntp.APP_LAUNCH.NTP_WEBSTORE_FOOTER]);
273 * The number of sections to wait on.
274 * @type {number}
276 var sectionsToWaitFor = -1;
279 * Queued callbacks which lie in wait for all sections to be ready.
280 * @type {array}
282 var readyCallbacks = [];
285 * Fired as each section of pages becomes ready.
286 * @param {Event} e Each page's synthetic DOM event.
288 document.addEventListener('sectionready', function(e) {
289 if (--sectionsToWaitFor <= 0) {
290 while (readyCallbacks.length) {
291 readyCallbacks.shift()();
297 * This is used to simulate a fire-once event (i.e. $(document).ready() in
298 * jQuery or Y.on('domready') in YUI. If all sections are ready, the callback
299 * is fired right away. If all pages are not ready yet, the function is queued
300 * for later execution.
301 * @param {function} callback The work to be done when ready.
303 function doWhenAllSectionsReady(callback) {
304 assert(typeof callback == 'function');
305 if (sectionsToWaitFor > 0)
306 readyCallbacks.push(callback);
307 else
308 window.setTimeout(callback, 0); // Do soon after, but asynchronously.
312 * Fills in an invisible div with the 'Most Visited' string so that
313 * its length may be measured and the nav dots sized accordingly.
315 function measureNavDots() {
316 var measuringDiv = $('fontMeasuringDiv');
317 measuringDiv.textContent = loadTimeData.getString('mostvisited');
318 // The 4 is for border and padding.
319 var pxWidth = Math.max(measuringDiv.clientWidth * 1.15 + 4, 80);
321 var styleElement = document.createElement('style');
322 styleElement.type = 'text/css';
323 // max-width is used because if we run out of space, the nav dots will be
324 // shrunk.
325 styleElement.textContent = '.dot { max-width: ' + pxWidth + 'px; }';
326 document.querySelector('head').appendChild(styleElement);
329 function themeChanged(opt_hasAttribution) {
330 $('themecss').href = 'chrome://theme/css/new_tab_theme.css?' + Date.now();
332 if (typeof opt_hasAttribution != 'undefined') {
333 document.documentElement.setAttribute('hasattribution',
334 opt_hasAttribution);
337 updateAttribution();
340 function setBookmarkBarAttached(attached) {
341 document.documentElement.setAttribute('bookmarkbarattached', attached);
345 * Attributes the attribution image at the bottom left.
347 function updateAttribution() {
348 var attribution = $('attribution');
349 if (document.documentElement.getAttribute('hasattribution') == 'true') {
350 $('attribution-img').src =
351 'chrome://theme/IDR_THEME_NTP_ATTRIBUTION?' + Date.now();
352 attribution.hidden = false;
353 } else {
354 attribution.hidden = true;
359 * Timeout ID.
360 * @type {number}
362 var notificationTimeout = 0;
365 * Shows the notification bubble.
366 * @param {string|Node} message The notification message or node to use as
367 * message.
368 * @param {Array.<{text: string, action: function()}>} links An array of
369 * records describing the links in the notification. Each record should
370 * have a 'text' attribute (the display string) and an 'action' attribute
371 * (a function to run when the link is activated).
372 * @param {Function} opt_closeHandler The callback invoked if the user
373 * manually dismisses the notification.
375 function showNotification(message, links, opt_closeHandler, opt_timeout) {
376 window.clearTimeout(notificationTimeout);
378 var span = document.querySelector('#notification > span');
379 if (typeof message == 'string') {
380 span.textContent = message;
381 } else {
382 span.textContent = ''; // Remove all children.
383 span.appendChild(message);
386 var linksBin = $('notificationLinks');
387 linksBin.textContent = '';
388 for (var i = 0; i < links.length; i++) {
389 var link = linksBin.ownerDocument.createElement('div');
390 link.textContent = links[i].text;
391 link.action = links[i].action;
392 link.onclick = function() {
393 this.action();
394 hideNotification();
396 link.setAttribute('role', 'button');
397 link.setAttribute('tabindex', 0);
398 link.className = 'link-button';
399 linksBin.appendChild(link);
402 function closeFunc(e) {
403 if (opt_closeHandler)
404 opt_closeHandler();
405 hideNotification();
408 document.querySelector('#notification button').onclick = closeFunc;
409 document.addEventListener('dragstart', closeFunc);
411 notificationContainer.hidden = false;
412 showNotificationOnCurrentPage();
414 newTabView.cardSlider.frame.addEventListener(
415 'cardSlider:card_change_ended', onCardChangeEnded);
417 var timeout = opt_timeout || 10000;
418 notificationTimeout = window.setTimeout(hideNotification, timeout);
422 * Hide the notification bubble.
424 function hideNotification() {
425 notificationContainer.classList.add('inactive');
427 newTabView.cardSlider.frame.removeEventListener(
428 'cardSlider:card_change_ended', onCardChangeEnded);
432 * Happens when 1 or more consecutive card changes end.
433 * @param {Event} e The cardSlider:card_change_ended event.
435 function onCardChangeEnded(e) {
436 // If we ended on the same page as we started, ignore.
437 if (newTabView.cardSlider.currentCardValue.notification)
438 return;
440 // Hide the notification the old page.
441 notificationContainer.classList.add('card-changed');
443 showNotificationOnCurrentPage();
447 * Move and show the notification on the current page.
449 function showNotificationOnCurrentPage() {
450 var page = newTabView.cardSlider.currentCardValue;
451 doWhenAllSectionsReady(function() {
452 if (page != newTabView.cardSlider.currentCardValue)
453 return;
455 // NOTE: This moves the notification to inside of the current page.
456 page.notification = notificationContainer;
458 // Reveal the notification and instruct it to hide itself if ignored.
459 notificationContainer.classList.remove('inactive');
461 // Gives the browser time to apply this rule before we remove it (causing
462 // a transition).
463 window.setTimeout(function() {
464 notificationContainer.classList.remove('card-changed');
465 }, 0);
470 * When done fading out, set hidden to true so the notification can't be
471 * tabbed to or clicked.
472 * @param {Event} e The webkitTransitionEnd event.
474 function onNotificationTransitionEnd(e) {
475 if (notificationContainer.classList.contains('inactive'))
476 notificationContainer.hidden = true;
479 function setRecentlyClosedTabs(dataItems) {
480 $('recently-closed-menu-button').dataItems = dataItems;
483 function setMostVisitedPages(data, hasBlacklistedUrls) {
484 newTabView.mostVisitedPage.data = data;
485 cr.dispatchSimpleEvent(document, 'sectionready', true, true);
488 function setSuggestionsPages(data, hasBlacklistedUrls) {
489 newTabView.suggestionsPage.data = data;
493 * Set the dominant color for a node. This will be called in response to
494 * getFaviconDominantColor. The node represented by |id| better have a setter
495 * for stripeColor.
496 * @param {string} id The ID of a node.
497 * @param {string} color The color represented as a CSS string.
499 function setFaviconDominantColor(id, color) {
500 var node = $(id);
501 if (node)
502 node.stripeColor = color;
506 * Updates the text displayed in the login container. If there is no text then
507 * the login container is hidden.
508 * @param {string} loginHeader The first line of text.
509 * @param {string} loginSubHeader The second line of text.
510 * @param {string} iconURL The url for the login status icon. If this is null
511 then the login status icon is hidden.
512 * @param {boolean} isUserSignedIn Indicates if the user is signed in or not.
514 function updateLogin(loginHeader, loginSubHeader, iconURL, isUserSignedIn) {
515 if (loginHeader || loginSubHeader) {
516 $('login-container').hidden = false;
517 $('login-status-header').innerHTML = loginHeader;
518 $('login-status-sub-header').innerHTML = loginSubHeader;
519 $('card-slider-frame').classList.add('showing-login-area');
521 if (iconURL) {
522 $('login-status-header-container').style.backgroundImage = url(iconURL);
523 $('login-status-header-container').classList.add('login-status-icon');
524 } else {
525 $('login-status-header-container').style.backgroundImage = 'none';
526 $('login-status-header-container').classList.remove(
527 'login-status-icon');
529 } else {
530 $('login-container').hidden = true;
531 $('card-slider-frame').classList.remove('showing-login-area');
533 if (shouldShowLoginBubble) {
534 window.setTimeout(loginBubble.show.bind(loginBubble), 0);
535 chrome.send('loginMessageSeen');
536 shouldShowLoginBubble = false;
537 } else if (loginBubble) {
538 loginBubble.reposition();
540 if (otherSessionsButton)
541 otherSessionsButton.updateSignInState(isUserSignedIn);
545 * Show the sync login UI.
546 * @param {Event} e The click event.
548 function showSyncLoginUI(e) {
549 var rect = e.currentTarget.getBoundingClientRect();
550 chrome.send('showSyncLoginUI',
551 [rect.left, rect.top, rect.width, rect.height]);
555 * Logs the time to click for the specified item.
556 * @param {string} item The item to log the time-to-click.
558 function logTimeToClick(item) {
559 var timeToClick = Date.now() - startTime;
560 chrome.send('logTimeToClick',
561 ['NewTabPage.TimeToClick' + item, timeToClick]);
565 * Wrappers to forward the callback to corresponding PageListView member.
567 function appAdded() {
568 return newTabView.appAdded.apply(newTabView, arguments);
571 function appMoved() {
572 return newTabView.appMoved.apply(newTabView, arguments);
575 function appRemoved() {
576 return newTabView.appRemoved.apply(newTabView, arguments);
579 function appsPrefChangeCallback() {
580 return newTabView.appsPrefChangedCallback.apply(newTabView, arguments);
583 function appsReordered() {
584 return newTabView.appsReordered.apply(newTabView, arguments);
587 function enterRearrangeMode() {
588 return newTabView.enterRearrangeMode.apply(newTabView, arguments);
591 function setForeignSessions(sessionList, isTabSyncEnabled) {
592 if (otherSessionsButton)
593 otherSessionsButton.setForeignSessions(sessionList, isTabSyncEnabled);
596 function getAppsCallback() {
597 return newTabView.getAppsCallback.apply(newTabView, arguments);
600 function getAppsPageIndex() {
601 return newTabView.getAppsPageIndex.apply(newTabView, arguments);
604 function getCardSlider() {
605 return newTabView.cardSlider;
608 function leaveRearrangeMode() {
609 return newTabView.leaveRearrangeMode.apply(newTabView, arguments);
612 function saveAppPageName() {
613 return newTabView.saveAppPageName.apply(newTabView, arguments);
616 function setAppToBeHighlighted(appId) {
617 newTabView.highlightAppId = appId;
620 // Return an object with all the exports
621 return {
622 appAdded: appAdded,
623 appMoved: appMoved,
624 appRemoved: appRemoved,
625 appsPrefChangeCallback: appsPrefChangeCallback,
626 enterRearrangeMode: enterRearrangeMode,
627 getAppsCallback: getAppsCallback,
628 getAppsPageIndex: getAppsPageIndex,
629 getCardSlider: getCardSlider,
630 onLoad: onLoad,
631 leaveRearrangeMode: leaveRearrangeMode,
632 logTimeToClick: logTimeToClick,
633 NtpFollowAction: NtpFollowAction,
634 saveAppPageName: saveAppPageName,
635 setAppToBeHighlighted: setAppToBeHighlighted,
636 setBookmarkBarAttached: setBookmarkBarAttached,
637 setForeignSessions: setForeignSessions,
638 setMostVisitedPages: setMostVisitedPages,
639 setSuggestionsPages: setSuggestionsPages,
640 setRecentlyClosedTabs: setRecentlyClosedTabs,
641 setFaviconDominantColor: setFaviconDominantColor,
642 showNotification: showNotification,
643 themeChanged: themeChanged,
644 updateLogin: updateLogin
648 document.addEventListener('DOMContentLoaded', ntp.onLoad);
650 var toCssPx = cr.ui.toCssPx;