| // 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 4 |
| * This is the main code for a previous version of the Chrome NTP ("NTP4"). |
| * Some parts of this are still used for the chrome://apps page. |
| */ |
| |
| // 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} |
| */ |
| let newTabView; |
| |
| /** |
| * 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 {!cr.ui.Bubble|undefined} |
| */ |
| let loginBubble; |
| |
| /** |
| * true if |loginBubble| should be shown. |
| * @type {boolean} |
| */ |
| let shouldShowLoginBubble = false; |
| |
| /** |
| * The time when all sections are ready. |
| * @type {number|undefined} |
| * @private |
| */ |
| let startTime; |
| |
| /** |
| * The number of sections to wait on. |
| * @type {number} |
| */ |
| let sectionsToWaitFor = -1; |
| |
| /** |
| * 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 |
| */ |
| const DEFAULT_TRANSITION_TIME = 500; |
| |
| /** |
| * Creates a NewTabView object. NewTabView extends PageListView with |
| * new tab UI specific logics. |
| * @constructor |
| * @extends {ntp.PageListView} |
| */ |
| function NewTabView() { |
| const pageSwitcherStart = /** @type {!ntp.PageSwitcher} */ ( |
| getRequiredElement('page-switcher-start')); |
| const pageSwitcherEnd = /** @type {!ntp.PageSwitcher} */ ( |
| getRequiredElement('page-switcher-end')); |
| this.initialize( |
| getRequiredElement('page-list'), getRequiredElement('dot-list'), |
| getRequiredElement('card-slider-frame'), getRequiredElement('trash'), |
| pageSwitcherStart, pageSwitcherEnd); |
| } |
| |
| // TODO(dbeam): NewTabView is now the only extender of PageListView; these |
| // classes should be merged. |
| NewTabView.prototype = {__proto__: ntp.PageListView.prototype}; |
| |
| /** |
| * Invoked at startup once the DOM is available to initialize the app. |
| */ |
| function onLoad() { |
| sectionsToWaitFor = 1; |
| measureNavDots(); |
| |
| newTabView = new NewTabView(); |
| |
| if (!loadTimeData.getBoolean('showWebStoreIcon')) { |
| const webStoreIcon = $('chrome-web-store-link'); |
| // Not all versions of the NTP have a footer, so this may not exist. |
| if (webStoreIcon) |
| webStoreIcon.hidden = true; |
| } else { |
| const webStoreLink = loadTimeData.getString('webStoreLink'); |
| const url = |
| appendParam(webStoreLink, 'utm_source', 'chrome-ntp-launcher'); |
| $('chrome-web-store-link').href = url; |
| $('chrome-web-store-link') |
| .addEventListener('auxclick', onChromeWebStoreButtonClick); |
| $('chrome-web-store-link') |
| .addEventListener('click', onChromeWebStoreButtonClick); |
| } |
| |
| // We need to wait for all the footer menu setup to be completed before |
| // we can compute its layout. |
| layoutFooter(); |
| |
| if (loadTimeData.getString('login_status_message')) { |
| loginBubble = new cr.ui.Bubble; |
| loginBubble.anchorNode = $('login-container'); |
| loginBubble.arrowLocation = cr.ui.ArrowLocation.TOP_END; |
| loginBubble.bubbleAlignment = |
| cr.ui.BubbleAlignment.BUBBLE_EDGE_TO_ANCHOR_EDGE; |
| loginBubble.deactivateToDismissDelay = 2000; |
| loginBubble.closeButtonVisible = false; |
| |
| $('login-status-advanced').onclick = function() { |
| chrome.send('showAdvancedLoginUI'); |
| }; |
| $('login-status-dismiss').onclick = loginBubble.hide.bind(loginBubble); |
| |
| const 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; |
| } |
| |
| $('login-container').addEventListener('click', showSyncLoginUI); |
| if (loadTimeData.getBoolean('shouldShowSyncLogin')) |
| chrome.send('initializeSyncLogin'); |
| |
| doWhenAllSectionsReady(function() { |
| // Tell the slider about the pages. |
| newTabView.updateSliderCards(); |
| // Mark the current page. |
| newTabView.cardSlider.currentCardValue.navigationDot.classList.add( |
| 'selected'); |
| |
| cr.dispatchSimpleEvent(document, 'ntpLoaded', true, true); |
| document.documentElement.classList.remove('starting-up'); |
| |
| startTime = Date.now(); |
| }); |
| } |
| |
| /** |
| * Launches the chrome web store app with the chrome-ntp-launcher |
| * source. |
| * @param {Event} e The click/auxclick event. |
| */ |
| function onChromeWebStoreButtonClick(e) { |
| if (e.button > 1) |
| return; // Ignore buttons other than left and middle. |
| chrome.send( |
| 'recordAppLaunchByURL', |
| [encodeURIComponent(this.href), ntp.APP_LAUNCH.NTP_WEBSTORE_FOOTER]); |
| } |
| |
| /** |
| * Queued callbacks which lie in wait for all sections to be ready. |
| * @type {Array} |
| */ |
| const readyCallbacks = []; |
| |
| /** |
| * Fired as each section of pages becomes ready. |
| */ |
| 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. |
| } |
| |
| /** |
| * Measure the width of a nav dot with a given title. |
| * @param {string} id The loadTimeData ID of the desired title. |
| * @return {number} The width of the nav dot. |
| */ |
| function measureNavDot(id) { |
| const measuringDiv = $('fontMeasuringDiv'); |
| measuringDiv.textContent = loadTimeData.getString(id); |
| // The 4 is for border and padding. |
| return Math.max(measuringDiv.clientWidth * 1.15 + 4, 80); |
| } |
| |
| /** |
| * Fills in an invisible div with the longest dot title string so that |
| * its length may be measured and the nav dots sized accordingly. |
| */ |
| function measureNavDots() { |
| const 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. |
| const pxWidth = measureNavDot('appDefaultPageName'); |
| styleElement.textContent = '.dot { max-width: ' + pxWidth + 'px; }'; |
| document.querySelector('head').appendChild(styleElement); |
| } |
| |
| /** |
| * Layout the footer so that the nav dots stay centered. |
| */ |
| function layoutFooter() { |
| // We need the image to be loaded. |
| const logo = $('logo-img'); |
| const logoImg = logo.querySelector('img'); |
| |
| // Only compare the width after the footer image successfully loaded. |
| if (!logoImg.complete || logoImg.width === 0) { |
| logoImg.onload = layoutFooter; |
| return; |
| } |
| |
| const menu = $('footer-menu-container'); |
| if (menu.clientWidth > logoImg.width) |
| logo.style.WebkitFlex = '0 1 ' + menu.clientWidth + 'px'; |
| else |
| menu.style.WebkitFlex = '0 1 ' + logoImg.width + 'px'; |
| } |
| |
| /** |
| * Called when the theme has changed. |
| * @param {Object=} opt_themeData Not used; only exists to match equivalent |
| * function in incognito NTP. |
| */ |
| function themeChanged(opt_themeData) { |
| $('themecss').href = 'chrome://theme/css/new_tab_theme.css?' + Date.now(); |
| } |
| |
| function setBookmarkBarAttached(attached) { |
| document.documentElement.setAttribute('bookmarkbarattached', attached); |
| } |
| |
| /** |
| * 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 setFaviconDominantColor(id, color) { |
| const 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) { |
| /** @const */ const showLogin = loginHeader || loginSubHeader; |
| |
| $('login-container').hidden = !showLogin; |
| $('login-container').classList.toggle('signed-in', isUserSignedIn); |
| $('card-slider-frame').classList.toggle('showing-login-area', !!showLogin); |
| |
| if (showLogin) { |
| // TODO(dbeam): we should use .textContent instead to mitigate XSS. |
| $('login-status-header').innerHTML = loginHeader; |
| $('login-status-sub-header').innerHTML = loginSubHeader; |
| |
| const headerContainer = $('login-status-header-container'); |
| headerContainer.classList.toggle('login-status-icon', !!iconURL); |
| headerContainer.style.backgroundImage = |
| iconURL ? getUrlForCss(iconURL) : 'none'; |
| } |
| |
| if (shouldShowLoginBubble) { |
| window.setTimeout(loginBubble.show.bind(loginBubble), 0); |
| chrome.send('loginMessageSeen'); |
| shouldShowLoginBubble = false; |
| } else if (loginBubble) { |
| loginBubble.reposition(); |
| } |
| } |
| |
| /** |
| * Show the sync login UI. |
| * @param {Event} e The click event. |
| */ |
| function showSyncLoginUI(e) { |
| const rect = e.currentTarget.getBoundingClientRect(); |
| chrome.send( |
| 'showSyncLoginUI', [rect.left, rect.top, rect.width, rect.height]); |
| } |
| |
| /** |
| * Wrappers to forward the callback to corresponding PageListView member. |
| */ |
| |
| /** |
| * Called by chrome when a new app has been added to chrome or has been |
| * enabled if previously disabled. |
| * @param {Object} appData A data structure full of relevant information for |
| * the app. |
| * @param {boolean=} opt_highlight Whether the app about to be added should |
| * be highlighted. |
| */ |
| function appAdded(appData, opt_highlight) { |
| newTabView.appAdded(appData, opt_highlight); |
| } |
| |
| /** |
| * Called by chrome when an app has changed positions. |
| * @param {Object} appData The data for the app. This contains page and |
| * position indices. |
| */ |
| function appMoved(appData) { |
| newTabView.appMoved(appData); |
| } |
| |
| /** |
| * Called by chrome when an existing app has been disabled or |
| * removed/uninstalled from chrome. |
| * @param {Object} appData A data structure full of relevant information for |
| * the app. |
| * @param {boolean} isUninstall True if the app is being uninstalled; |
| * false if the app is being disabled. |
| * @param {boolean} fromPage True if the removal was from the current page. |
| */ |
| function appRemoved(appData, isUninstall, fromPage) { |
| newTabView.appRemoved(appData, isUninstall, fromPage); |
| } |
| |
| /** |
| * Callback invoked by chrome whenever an app preference changes. |
| * @param {Object} data An object with all the data on available |
| * applications. |
| */ |
| function appsPrefChangeCallback(data) { |
| newTabView.appsPrefChangedCallback(data); |
| } |
| |
| /** |
| * Callback invoked by chrome whenever the app launcher promo pref changes. |
| * @param {boolean} show Identifies if we should show or hide the promo. |
| */ |
| function appLauncherPromoPrefChangeCallback(show) { |
| newTabView.appLauncherPromoPrefChangeCallback(show); |
| } |
| |
| /** |
| * Called whenever tiles should be re-arranging themselves out of the way |
| * of a moving or insert tile. |
| */ |
| function enterRearrangeMode() { |
| newTabView.enterRearrangeMode(); |
| } |
| |
| /** |
| * Callback invoked by chrome with the apps available. |
| * |
| * Note that calls to this function can occur at any time, not just in |
| * response to a getApps request. For example, when a user |
| * installs/uninstalls an app on another synchronized devices. |
| * @param {Object} data An object with all the data on available |
| * applications. |
| */ |
| function getAppsCallback(data) { |
| newTabView.getAppsCallback(data); |
| } |
| |
| /** |
| * Return the index of the given apps page. |
| * @param {ntp.AppsPage} page The AppsPage we wish to find. |
| * @return {number} The index of |page| or -1 if it is not in the collection. |
| */ |
| function getAppsPageIndex(page) { |
| return newTabView.getAppsPageIndex(page); |
| } |
| |
| function getCardSlider() { |
| return newTabView.cardSlider; |
| } |
| |
| /** |
| * Invoked whenever some app is released |
| */ |
| function leaveRearrangeMode() { |
| newTabView.leaveRearrangeMode(); |
| } |
| |
| /** |
| * Save the name of an apps page. |
| * Store the apps page name into the preferences store. |
| * @param {ntp.AppsPage} appPage The app page for which we wish to save. |
| * @param {string} name The name of the page. |
| */ |
| function saveAppPageName(appPage, name) { |
| newTabView.saveAppPageName(appPage, name); |
| } |
| |
| function setAppToBeHighlighted(appId) { |
| newTabView.highlightAppId = appId; |
| } |
| |
| // Return an object with all the exports |
| return { |
| appAdded: appAdded, |
| appMoved: appMoved, |
| appRemoved: appRemoved, |
| appsPrefChangeCallback: appsPrefChangeCallback, |
| appLauncherPromoPrefChangeCallback: appLauncherPromoPrefChangeCallback, |
| enterRearrangeMode: enterRearrangeMode, |
| getAppsCallback: getAppsCallback, |
| getAppsPageIndex: getAppsPageIndex, |
| getCardSlider: getCardSlider, |
| onLoad: onLoad, |
| leaveRearrangeMode: leaveRearrangeMode, |
| saveAppPageName: saveAppPageName, |
| setAppToBeHighlighted: setAppToBeHighlighted, |
| setBookmarkBarAttached: setBookmarkBarAttached, |
| setFaviconDominantColor: setFaviconDominantColor, |
| themeChanged: themeChanged, |
| updateLogin: updateLogin |
| }; |
| }); |
| |
| document.addEventListener('DOMContentLoaded', ntp.onLoad); |
| |
| const toCssPx = cr.ui.toCssPx; |