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; |