Chromium Code Reviews| Index: chrome/browser/resources/ntp_search/new_tab.js |
| diff --git a/chrome/browser/resources/ntp_search/new_tab.js b/chrome/browser/resources/ntp_search/new_tab.js |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..529bac88ce9a69bcaeeee09b0260b0dd0f5a2c24 |
| --- /dev/null |
| +++ b/chrome/browser/resources/ntp_search/new_tab.js |
| @@ -0,0 +1,606 @@ |
| +// Copyright (c) 2012 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. |
| + |
| +/** |
| + * @fileoverview New tab page |
| + * This is the main code for the new tab page used by touch-enabled Chrome |
| + * browsers. For now this is still a prototype. |
| + */ |
| + |
| +// Use an anonymous function to enable strict mode just for this file (which |
| +// will be concatenated with other files when embedded in Chrome |
| +cr.define('ntp', function() { |
| + 'use strict'; |
| + |
| + /** |
| + * NewTabView instance. |
| + * @type {!Object|undefined} |
| + */ |
| + var newTabView; |
| + |
| + /** |
| + * The 'notification-container' element. |
| + * @type {!Element|undefined} |
| + */ |
| + var notificationContainer; |
| + |
| + /** |
| + * If non-null, an info bubble for showing messages to the user. It points at |
| + * the Most Visited label, and is used to draw more attention to the |
| + * navigation dot UI. |
| + * @type {!Element|undefined} |
| + */ |
| + var infoBubble; |
| + |
| + /** |
| + * If non-null, an bubble confirming that the user has signed into sync. It |
| + * points at the login status at the top of the page. |
| + * @type {!Element|undefined} |
| + */ |
| + var loginBubble; |
| + |
| + /** |
| + * true if |loginBubble| should be shown. |
| + * @type {Boolean} |
| + */ |
| + var shouldShowLoginBubble = false; |
| + |
| + /** |
| + * The 'other-sessions-menu-button' element. |
| + * @type {!Element|undefined} |
| + */ |
| + var otherSessionsButton; |
| + |
| + /** |
| + * The time in milliseconds for most transitions. This should match what's |
| + * in new_tab.css. Unfortunately there's no better way to try to time |
| + * something to occur until after a transition has completed. |
| + * @type {number} |
| + * @const |
| + */ |
| + var DEFAULT_TRANSITION_TIME = 500; |
| + |
| + /** |
| + * See description for these values in ntp_stats.h. |
| + * @enum {number} |
| + */ |
| + var NtpFollowAction = { |
| + CLICKED_TILE: 11, |
| + CLICKED_OTHER_NTP_PANE: 12, |
| + OTHER: 13 |
| + }; |
| + |
| + /** |
| + * Creates a NewTabView object. NewTabView extends PageListView with |
| + * new tab UI specific logics. |
| + * @constructor |
| + * @extends {PageListView} |
| + */ |
| + function NewTabView() { |
| + var pageSwitcherStart = null; |
| + var pageSwitcherEnd = null; |
| + if (loadTimeData.getValue('showApps')) { |
| + pageSwitcherStart = getRequiredElement('page-switcher-start'); |
| + pageSwitcherEnd = getRequiredElement('page-switcher-end'); |
| + } |
| + this.initialize(getRequiredElement('page-list'), |
| + getRequiredElement('dot-list'), |
| + getRequiredElement('card-slider-frame'), |
| + getRequiredElement('trash'), |
| + pageSwitcherStart, pageSwitcherEnd); |
| + } |
| + |
| + NewTabView.prototype = { |
| + __proto__: ntp.PageListView.prototype, |
| + |
| + /** @inheritDoc */ |
| + appendTilePage: function(page, title, titleIsEditable, opt_refNode) { |
| + ntp.PageListView.prototype.appendTilePage.apply(this, arguments); |
| + |
| + if (infoBubble) |
| + window.setTimeout(infoBubble.reposition.bind(infoBubble), 0); |
| + } |
| + }; |
| + |
| + /** |
| + * Invoked at startup once the DOM is available to initialize the app. |
| + */ |
| + function onLoad() { |
| + sectionsToWaitFor = loadTimeData.getBoolean('showApps') ? 2 : 1; |
| + if (loadTimeData.getBoolean('isSuggestionsPageEnabled')) |
| + sectionsToWaitFor++; |
| + measureNavDots(); |
| + |
| + // Load the current theme colors. |
| + themeChanged(); |
| + |
| + newTabView = new NewTabView(); |
| + |
| + notificationContainer = getRequiredElement('notification-container'); |
| + notificationContainer.addEventListener( |
| + 'webkitTransitionEnd', onNotificationTransitionEnd); |
| + |
| + cr.ui.decorate($('recently-closed-menu-button'), ntp.RecentMenuButton); |
| + chrome.send('getRecentlyClosedTabs'); |
| + |
| + if (loadTimeData.getBoolean('showOtherSessionsMenu')) { |
| + otherSessionsButton = getRequiredElement('other-sessions-menu-button'); |
| + cr.ui.decorate(otherSessionsButton, ntp.OtherSessionsMenuButton); |
| + otherSessionsButton.initialize(loadTimeData.getBoolean('isUserSignedIn')); |
| + } |
| + |
| + var mostVisited = new ntp.MostVisitedPage(); |
| + // Move the footer into the most visited page if we are in "bare minimum" |
| + // mode. |
| + if (document.body.classList.contains('bare-minimum')) |
| + mostVisited.appendFooter(getRequiredElement('footer')); |
| + newTabView.appendTilePage(mostVisited, |
| + loadTimeData.getString('mostvisited'), |
| + false); |
| + chrome.send('getMostVisited'); |
| + |
| + if (loadTimeData.getBoolean('isSuggestionsPageEnabled')) { |
| + var suggestions_script = document.createElement('script'); |
| + suggestions_script.src = 'suggestions_page.js'; |
| + suggestions_script.onload = function() { |
| + newTabView.appendTilePage(new ntp.SuggestionsPage(), |
| + loadTimeData.getString('suggestions'), |
| + false, |
| + (newTabView.appsPages.length > 0) ? |
| + newTabView.appsPages[0] : null); |
| + chrome.send('getSuggestions'); |
| + cr.dispatchSimpleEvent(document, 'sectionready', true, true); |
| + }; |
| + document.querySelector('head').appendChild(suggestions_script); |
| + } |
| + |
| + var webStoreLink = loadTimeData.getString('webStoreLink'); |
| + var url = appendParam(webStoreLink, 'utm_source', 'chrome-ntp-launcher'); |
| + $('chrome-web-store-link').href = url; |
| + $('chrome-web-store-link').addEventListener('click', |
| + onChromeWebStoreButtonClick); |
| + |
| + if (loadTimeData.getString('login_status_message')) { |
| + loginBubble = new cr.ui.Bubble; |
| + loginBubble.anchorNode = $('login-container'); |
| + loginBubble.setArrowLocation(cr.ui.ArrowLocation.TOP_END); |
| + loginBubble.bubbleAlignment = |
| + cr.ui.BubbleAlignment.BUBBLE_EDGE_TO_ANCHOR_EDGE; |
| + loginBubble.deactivateToDismissDelay = 2000; |
| + loginBubble.setCloseButtonVisible(false); |
| + |
| + $('login-status-advanced').onclick = function() { |
| + chrome.send('showAdvancedLoginUI'); |
| + }; |
| + $('login-status-dismiss').onclick = loginBubble.hide.bind(loginBubble); |
| + |
| + var bubbleContent = $('login-status-bubble-contents'); |
| + loginBubble.content = bubbleContent; |
| + |
| + // The anchor node won't be updated until updateLogin is called so don't |
| + // show the bubble yet. |
| + shouldShowLoginBubble = true; |
| + } |
| + |
| + var loginContainer = getRequiredElement('login-container'); |
| + loginContainer.addEventListener('click', showSyncLoginUI); |
| + chrome.send('initializeSyncLogin'); |
| + |
| + doWhenAllSectionsReady(function() { |
| + |
| + // TODO(xci) new! |
| + // TODO(pedrosimonetti): find a better place to put this code. Every, |
| + // card needs to call layout the first time is shown. |
| + newTabView.cardSlider.currentCardValue.layout_(); |
| + |
| + // Tell the slider about the pages. |
| + newTabView.updateSliderCards(); |
| + // Mark the current page. |
| + newTabView.cardSlider.currentCardValue.navigationDot.classList.add( |
| + 'selected'); |
| + |
| + if (loadTimeData.valueExists('serverpromo')) { |
| + var promo = loadTimeData.getString('serverpromo'); |
| + var tags = ['IMG']; |
| + var attrs = { |
| + src: function(node, value) { |
| + return node.tagName == 'IMG' && |
| + /^data\:image\/(?:png|gif|jpe?g)/.test(value); |
| + }, |
| + }; |
| + showNotification(parseHtmlSubset(promo, tags, attrs), [], function() { |
| + chrome.send('closeNotificationPromo'); |
| + }, 60000); |
| + chrome.send('notificationPromoViewed'); |
| + } |
| + |
| + cr.dispatchSimpleEvent(document, 'ntpLoaded', true, true); |
| + document.documentElement.classList.remove('starting-up'); |
| + }); |
| + } |
| + |
| + /** |
| + * Launches the chrome web store app with the chrome-ntp-launcher |
| + * source. |
| + * @param {Event} e The click event. |
| + */ |
| + function onChromeWebStoreButtonClick(e) { |
| + chrome.send('recordAppLaunchByURL', |
| + [encodeURIComponent(this.href), |
| + ntp.APP_LAUNCH.NTP_WEBSTORE_FOOTER]); |
| + } |
| + |
| + /* |
| + * The number of sections to wait on. |
| + * @type {number} |
| + */ |
| + var sectionsToWaitFor = -1; |
| + |
| + /** |
| + * Queued callbacks which lie in wait for all sections to be ready. |
| + * @type {array} |
| + */ |
| + var readyCallbacks = []; |
| + |
| + /** |
| + * Fired as each section of pages becomes ready. |
| + * @param {Event} e Each page's synthetic DOM event. |
| + */ |
| + document.addEventListener('sectionready', function(e) { |
| + if (--sectionsToWaitFor <= 0) { |
| + while (readyCallbacks.length) { |
| + readyCallbacks.shift()(); |
| + } |
| + } |
| + }); |
| + |
| + /** |
| + * This is used to simulate a fire-once event (i.e. $(document).ready() in |
| + * jQuery or Y.on('domready') in YUI. If all sections are ready, the callback |
| + * is fired right away. If all pages are not ready yet, the function is queued |
| + * for later execution. |
| + * @param {function} callback The work to be done when ready. |
| + */ |
| + function doWhenAllSectionsReady(callback) { |
| + assert(typeof callback == 'function'); |
| + if (sectionsToWaitFor > 0) |
| + readyCallbacks.push(callback); |
| + else |
| + window.setTimeout(callback, 0); // Do soon after, but asynchronously. |
| + } |
| + |
| + /** |
| + * Fills in an invisible div with the 'Most Visited' string so that |
| + * its length may be measured and the nav dots sized accordingly. |
| + */ |
| + function measureNavDots() { |
| + var measuringDiv = $('fontMeasuringDiv'); |
| + measuringDiv.textContent = loadTimeData.getString('mostvisited'); |
| + // The 4 is for border and padding. |
| + var pxWidth = Math.max(measuringDiv.clientWidth * 1.15 + 4, 80); |
| + |
| + var styleElement = document.createElement('style'); |
| + styleElement.type = 'text/css'; |
| + // max-width is used because if we run out of space, the nav dots will be |
| + // shrunk. |
| + styleElement.textContent = '.dot { max-width: ' + pxWidth + 'px; }'; |
| + document.querySelector('head').appendChild(styleElement); |
| + } |
| + |
| + function themeChanged(opt_hasAttribution) { |
| + $('themecss').href = 'chrome://theme/css/new_tab_theme.css?' + Date.now(); |
| + |
| + if (typeof opt_hasAttribution != 'undefined') { |
| + document.documentElement.setAttribute('hasattribution', |
| + opt_hasAttribution); |
| + } |
| + |
| + updateAttribution(); |
| + } |
| + |
| + function setBookmarkBarAttached(attached) { |
| + document.documentElement.setAttribute('bookmarkbarattached', attached); |
| + } |
| + |
| + /** |
| + * Attributes the attribution image at the bottom left. |
| + */ |
| + function updateAttribution() { |
| + var attribution = $('attribution'); |
| + if (document.documentElement.getAttribute('hasattribution') == 'true') { |
| + $('attribution-img').src = |
| + 'chrome://theme/IDR_THEME_NTP_ATTRIBUTION?' + Date.now(); |
| + attribution.hidden = false; |
| + } else { |
| + attribution.hidden = true; |
| + } |
| + } |
| + |
| + /** |
| + * Timeout ID. |
| + * @type {number} |
| + */ |
| + var notificationTimeout = 0; |
| + |
| + /** |
| + * Shows the notification bubble. |
| + * @param {string|Node} message The notification message or node to use as |
| + * message. |
| + * @param {Array.<{text: string, action: function()}>} links An array of |
| + * records describing the links in the notification. Each record should |
| + * have a 'text' attribute (the display string) and an 'action' attribute |
| + * (a function to run when the link is activated). |
| + * @param {Function} opt_closeHandler The callback invoked if the user |
| + * manually dismisses the notification. |
| + */ |
| + function showNotification(message, links, opt_closeHandler, opt_timeout) { |
| + window.clearTimeout(notificationTimeout); |
| + |
| + var span = document.querySelector('#notification > span'); |
| + if (typeof message == 'string') { |
| + span.textContent = message; |
| + } else { |
| + span.textContent = ''; // Remove all children. |
| + span.appendChild(message); |
| + } |
| + |
| + var linksBin = $('notificationLinks'); |
| + linksBin.textContent = ''; |
| + for (var i = 0; i < links.length; i++) { |
| + var link = linksBin.ownerDocument.createElement('div'); |
| + link.textContent = links[i].text; |
| + link.action = links[i].action; |
| + link.onclick = function() { |
| + this.action(); |
| + hideNotification(); |
| + }; |
| + link.setAttribute('role', 'button'); |
| + link.setAttribute('tabindex', 0); |
| + link.className = 'link-button'; |
| + linksBin.appendChild(link); |
| + } |
| + |
| + function closeFunc(e) { |
| + if (opt_closeHandler) |
| + opt_closeHandler(); |
| + hideNotification(); |
| + } |
| + |
| + document.querySelector('#notification button').onclick = closeFunc; |
| + document.addEventListener('dragstart', closeFunc); |
| + |
| + notificationContainer.hidden = false; |
| + showNotificationOnCurrentPage(); |
| + |
| + newTabView.cardSlider.frame.addEventListener( |
| + 'cardSlider:card_change_ended', onCardChangeEnded); |
| + |
| + var timeout = opt_timeout || 10000; |
| + notificationTimeout = window.setTimeout(hideNotification, timeout); |
| + } |
| + |
| + /** |
| + * Hide the notification bubble. |
| + */ |
| + function hideNotification() { |
| + notificationContainer.classList.add('inactive'); |
| + |
| + newTabView.cardSlider.frame.removeEventListener( |
| + 'cardSlider:card_change_ended', onCardChangeEnded); |
| + } |
| + |
| + /** |
| + * Happens when 1 or more consecutive card changes end. |
| + * @param {Event} e The cardSlider:card_change_ended event. |
| + */ |
| + function onCardChangeEnded(e) { |
| + // If we ended on the same page as we started, ignore. |
| + if (newTabView.cardSlider.currentCardValue.notification) |
| + return; |
| + |
| + // Hide the notification the old page. |
| + notificationContainer.classList.add('card-changed'); |
| + |
| + showNotificationOnCurrentPage(); |
| + } |
| + |
| + /** |
| + * Move and show the notification on the current page. |
| + */ |
| + function showNotificationOnCurrentPage() { |
| + var page = newTabView.cardSlider.currentCardValue; |
| + doWhenAllSectionsReady(function() { |
| + if (page != newTabView.cardSlider.currentCardValue) |
| + return; |
| + |
| + // NOTE: This moves the notification to inside of the current page. |
| + page.notification = notificationContainer; |
| + |
| + // Reveal the notification and instruct it to hide itself if ignored. |
| + notificationContainer.classList.remove('inactive'); |
| + |
| + // Gives the browser time to apply this rule before we remove it (causing |
| + // a transition). |
| + window.setTimeout(function() { |
| + notificationContainer.classList.remove('card-changed'); |
| + }, 0); |
| + }); |
| + } |
| + |
| + /** |
| + * When done fading out, set hidden to true so the notification can't be |
| + * tabbed to or clicked. |
| + * @param {Event} e The webkitTransitionEnd event. |
| + */ |
| + function onNotificationTransitionEnd(e) { |
| + if (notificationContainer.classList.contains('inactive')) |
| + notificationContainer.hidden = true; |
| + } |
| + |
| + function setRecentlyClosedTabs(dataItems) { |
| + $('recently-closed-menu-button').dataItems = dataItems; |
| + } |
| + |
| + function setMostVisitedPages(data, hasBlacklistedUrls) { |
| + newTabView.mostVisitedPage.data = data; |
| + cr.dispatchSimpleEvent(document, 'sectionready', true, true); |
| + } |
| + |
| + function setSuggestionsPages(data, hasBlacklistedUrls) { |
| + newTabView.suggestionsPage.data = data; |
| + } |
| + |
| + function getThumbnailUrl(url) { |
| + return 'chrome://thumb/' + url; |
| + } |
| + |
| + /** |
| + * Set the dominant color for a node. This will be called in response to |
| + * getFaviconDominantColor. The node represented by |id| better have a setter |
| + * for stripeColor. |
| + * @param {string} id The ID of a node. |
| + * @param {string} color The color represented as a CSS string. |
| + */ |
| + function setStripeColor(id, color) { |
| + var node = $(id); |
| + if (node) |
| + node.stripeColor = color; |
| + } |
| + |
| + /** |
| + * Updates the text displayed in the login container. If there is no text then |
| + * the login container is hidden. |
| + * @param {string} loginHeader The first line of text. |
| + * @param {string} loginSubHeader The second line of text. |
| + * @param {string} iconURL The url for the login status icon. If this is null |
| + then the login status icon is hidden. |
| + * @param {boolean} isUserSignedIn Indicates if the user is signed in or not. |
| + */ |
| + function updateLogin(loginHeader, loginSubHeader, iconURL, isUserSignedIn) { |
| + if (loginHeader || loginSubHeader) { |
| + $('login-container').hidden = false; |
| + $('login-status-header').innerHTML = loginHeader; |
| + $('login-status-sub-header').innerHTML = loginSubHeader; |
| + $('card-slider-frame').classList.add('showing-login-area'); |
| + |
| + if (iconURL) { |
| + $('login-status-header-container').style.backgroundImage = url(iconURL); |
| + $('login-status-header-container').classList.add('login-status-icon'); |
| + } else { |
| + $('login-status-header-container').style.backgroundImage = 'none'; |
| + $('login-status-header-container').classList.remove( |
| + 'login-status-icon'); |
| + } |
| + } else { |
| + $('login-container').hidden = true; |
| + $('card-slider-frame').classList.remove('showing-login-area'); |
| + } |
| + if (shouldShowLoginBubble) { |
| + window.setTimeout(loginBubble.show.bind(loginBubble), 0); |
| + chrome.send('loginMessageSeen'); |
| + shouldShowLoginBubble = false; |
| + } else if (loginBubble) { |
| + loginBubble.reposition(); |
| + } |
| + if (otherSessionsButton) |
| + otherSessionsButton.updateSignInState(isUserSignedIn); |
| + } |
| + |
| + /** |
| + * Show the sync login UI. |
| + * @param {Event} e The click event. |
| + */ |
| + function showSyncLoginUI(e) { |
| + var rect = e.currentTarget.getBoundingClientRect(); |
| + chrome.send('showSyncLoginUI', |
| + [rect.left, rect.top, rect.width, rect.height]); |
| + } |
| + |
| + /** |
| + * Wrappers to forward the callback to corresponding PageListView member. |
| + */ |
| + function appAdded() { |
| + return newTabView.appAdded.apply(newTabView, arguments); |
| + } |
| + |
| + function appMoved() { |
| + return newTabView.appMoved.apply(newTabView, arguments); |
| + } |
| + |
| + function appRemoved() { |
| + return newTabView.appRemoved.apply(newTabView, arguments); |
| + } |
| + |
| + function appsPrefChangeCallback() { |
| + return newTabView.appsPrefChangedCallback.apply(newTabView, arguments); |
| + } |
| + |
| + function appsReordered() { |
| + return newTabView.appsReordered.apply(newTabView, arguments); |
| + } |
| + |
| + function enterRearrangeMode() { |
| + return newTabView.enterRearrangeMode.apply(newTabView, arguments); |
| + } |
| + |
| + function setForeignSessions(sessionList, isTabSyncEnabled) { |
| + if (otherSessionsButton) |
| + otherSessionsButton.setForeignSessions(sessionList, isTabSyncEnabled); |
| + } |
| + |
| + function getAppsCallback() { |
| + return newTabView.getAppsCallback.apply(newTabView, arguments); |
| + } |
| + |
| + function getAppsPageIndex() { |
| + return newTabView.getAppsPageIndex.apply(newTabView, arguments); |
| + } |
| + |
| + function getCardSlider() { |
| + return newTabView.cardSlider; |
| + } |
| + |
| + function leaveRearrangeMode() { |
| + return newTabView.leaveRearrangeMode.apply(newTabView, arguments); |
| + } |
| + |
| + function saveAppPageName() { |
| + return newTabView.saveAppPageName.apply(newTabView, arguments); |
| + } |
| + |
| + function setAppToBeHighlighted(appId) { |
| + newTabView.highlightAppId = appId; |
| + } |
| + |
| + // Return an object with all the exports |
| + return { |
| + appAdded: appAdded, |
| + appMoved: appMoved, |
| + appRemoved: appRemoved, |
| + appsPrefChangeCallback: appsPrefChangeCallback, |
| + enterRearrangeMode: enterRearrangeMode, |
| + getAppsCallback: getAppsCallback, |
| + getAppsPageIndex: getAppsPageIndex, |
| + getCardSlider: getCardSlider, |
| + onLoad: onLoad, |
| + leaveRearrangeMode: leaveRearrangeMode, |
| + NtpFollowAction: NtpFollowAction, |
| + saveAppPageName: saveAppPageName, |
| + setAppToBeHighlighted: setAppToBeHighlighted, |
| + setBookmarkBarAttached: setBookmarkBarAttached, |
| + setForeignSessions: setForeignSessions, |
| + setMostVisitedPages: setMostVisitedPages, |
| + setSuggestionsPages: setSuggestionsPages, |
| + setRecentlyClosedTabs: setRecentlyClosedTabs, |
| + getThumbnailUrl: getThumbnailUrl, |
|
jeremycho_google
2012/07/31 03:09:16
Alphabetize.
pedrosimonetti2
2012/08/03 18:14:01
Done.
|
| + setStripeColor: setStripeColor, |
| + showNotification: showNotification, |
| + themeChanged: themeChanged, |
| + updateLogin: updateLogin |
| + }; |
| +}); |
| + |
| +document.addEventListener('DOMContentLoaded', ntp.onLoad); |
| + |
| +var toCssPx = cr.ui.toCssPx; |