summaryrefslogtreecommitdiffstats
path: root/chromium/chrome/browser/resources/ntp_android/ntp_android.js
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/chrome/browser/resources/ntp_android/ntp_android.js')
-rw-r--r--chromium/chrome/browser/resources/ntp_android/ntp_android.js2744
1 files changed, 0 insertions, 2744 deletions
diff --git a/chromium/chrome/browser/resources/ntp_android/ntp_android.js b/chromium/chrome/browser/resources/ntp_android/ntp_android.js
deleted file mode 100644
index 5de0633c7b6..00000000000
--- a/chromium/chrome/browser/resources/ntp_android/ntp_android.js
+++ /dev/null
@@ -1,2744 +0,0 @@
-// Copyright (c) 2011 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.
-
-// File Description:
-// Contains all the necessary functions for rendering the NTP on mobile
-// devices.
-
-/**
- * The event type used to determine when a touch starts.
- * @type {string}
- */
-var PRESS_START_EVT = 'touchstart';
-
-/**
- * The event type used to determine when a touch finishes.
- * @type {string}
- */
-var PRESS_STOP_EVT = 'touchend';
-
-/**
- * The event type used to determine when a touch moves.
- * @type {string}
- */
-var PRESS_MOVE_EVT = 'touchmove';
-
-cr.define('ntp', function() {
- /**
- * Constant for the localStorage key used to specify the default bookmark
- * folder to be selected when navigating to the bookmark tab for the first
- * time of a new NTP instance.
- * @type {string}
- */
- var DEFAULT_BOOKMARK_FOLDER_KEY = 'defaultBookmarkFolder';
-
- /**
- * Constant for the localStorage key used to store whether or not sync was
- * enabled on the last call to syncEnabled().
- * @type {string}
- */
- var SYNC_ENABLED_KEY = 'syncEnabled';
-
- /**
- * The time before and item gets marked as active (in milliseconds). This
- * prevents an item from being marked as active when the user is scrolling
- * the page.
- * @type {number}
- */
- var ACTIVE_ITEM_DELAY_MS = 100;
-
- /**
- * The CSS class identifier for grid layouts.
- * @type {string}
- */
- var GRID_CSS_CLASS = 'icon-grid';
-
- /**
- * The element to center when centering a GRID_CSS_CLASS.
- */
- var GRID_CENTER_CSS_CLASS = 'center-icon-grid';
-
- /**
- * Attribute used to specify the number of columns to use in a grid. If
- * left unspecified, the grid will fill the container.
- */
- var GRID_COLUMNS = 'grid-columns';
-
- /**
- * Attribute used to specify whether the top margin should be set to match
- * the left margin of the grid.
- */
- var GRID_SET_TOP_MARGIN_CLASS = 'grid-set-top-margin';
-
- /**
- * Attribute used to specify whether the margins of individual items within
- * the grid should be adjusted to better fill the space.
- */
- var GRID_SET_ITEM_MARGINS = 'grid-set-item-margins';
-
- /**
- * The CSS class identifier for centered empty section containers.
- */
- var CENTER_EMPTY_CONTAINER_CSS_CLASS = 'center-empty-container';
-
- /**
- * The CSS class identifier for marking list items as active.
- * @type {string}
- */
- var ACTIVE_LIST_ITEM_CSS_CLASS = 'list-item-active';
-
- /**
- * Attributes set on elements representing data in a section, specifying
- * which section that element belongs to. Used for context menus.
- * @type {string}
- */
- var SECTION_KEY = 'sectionType';
-
- /**
- * Attribute set on an element that has a context menu. Specifies the URL for
- * which the context menu action should apply.
- * @type {string}
- */
- var CONTEXT_MENU_URL_KEY = 'url';
-
- /**
- * The list of main section panes added.
- * @type {Array.<Element>}
- */
- var panes = [];
-
- /**
- * The list of section prefixes, which are used to append to the hash of the
- * page to allow the native toolbar to see url changes when the pane is
- * switched.
- */
- var sectionPrefixes = [];
-
- /**
- * The next available index for new favicons. Users must increment this
- * value once assigning this index to a favicon.
- * @type {number}
- */
- var faviconIndex = 0;
-
- /**
- * The currently selected pane DOM element.
- * @type {Element}
- */
- var currentPane = null;
-
- /**
- * The index of the currently selected top level pane. The index corresponds
- * to the elements defined in {@see #panes}.
- * @type {number}
- */
- var currentPaneIndex;
-
- /**
- * The ID of the bookmark folder currently selected.
- * @type {string|number}
- */
- var bookmarkFolderId = null;
-
- /**
- * The current element active item.
- * @type {?Element}
- */
- var activeItem;
-
- /**
- * The element to be marked as active if no actions cancel it.
- * @type {?Element}
- */
- var pendingActiveItem;
-
- /**
- * The timer ID to mark an element as active.
- * @type {number}
- */
- var activeItemDelayTimerId;
-
- /**
- * Enum for the different load states based on the initialization of the NTP.
- * @enum {number}
- */
- var LoadStatusType = {
- LOAD_NOT_DONE: 0,
- LOAD_IMAGES_COMPLETE: 1,
- LOAD_BOOKMARKS_FINISHED: 2,
- LOAD_COMPLETE: 3 // An OR'd combination of all necessary states.
- };
-
- /**
- * The current loading status for the NTP.
- * @type {LoadStatusType}
- */
- var loadStatus_ = LoadStatusType.LOAD_NOT_DONE;
-
- /**
- * Whether the loading complete notification has been sent.
- * @type {boolean}
- */
- var finishedLoadingNotificationSent_ = false;
-
- /**
- * Whether the page title has been loaded.
- * @type {boolean}
- */
- var titleLoadedStatus_ = false;
-
- /**
- * Whether the NTP is in incognito mode or not.
- * @type {boolean}
- */
- var isIncognito = false;
-
- /**
- * Whether incognito mode is enabled. (It can be blocked e.g. with a policy.)
- * @type {boolean}
- */
- var isIncognitoEnabled = true;
-
- /**
- * Whether the initial history state has been replaced. The state will be
- * replaced once the bookmark data has loaded to ensure the proper folder
- * id is persisted.
- * @type {boolean}
- */
- var replacedInitialState = false;
-
- /**
- * Stores number of most visited pages.
- * @type {number}
- */
- var numberOfMostVisitedPages = 0;
-
- /**
- * Whether there are any recently closed tabs.
- * @type {boolean}
- */
- var hasRecentlyClosedTabs = false;
-
- /**
- * Whether promo is not allowed or not (external to NTP).
- * @type {boolean}
- */
- var promoIsAllowed = false;
-
- /**
- * Whether promo should be shown on Most Visited page (externally set).
- * @type {boolean}
- */
- var promoIsAllowedOnMostVisited = false;
-
- /**
- * Whether promo should be shown on Open Tabs page (externally set).
- * @type {boolean}
- */
- var promoIsAllowedOnOpenTabs = false;
-
- /**
- * Whether promo should show a virtual computer on Open Tabs (externally set).
- * @type {boolean}
- */
- var promoIsAllowedAsVirtualComputer = false;
-
- /**
- * Promo-injected title of a virtual computer on an open tabs pane.
- * @type {string}
- */
- var promoInjectedComputerTitleText = '';
-
- /**
- * Promo-injected last synced text of a virtual computer on an open tabs pane.
- * @type {string}
- */
- var promoInjectedComputerLastSyncedText = '';
-
- /**
- * The different sections that are displayed.
- * @enum {number}
- */
- var SectionType = {
- BOOKMARKS: 'bookmarks',
- FOREIGN_SESSION: 'foreign_session',
- FOREIGN_SESSION_HEADER: 'foreign_session_header',
- MOST_VISITED: 'most_visited',
- PROMO_VC_SESSION_HEADER: 'promo_vc_session_header',
- RECENTLY_CLOSED: 'recently_closed',
- SNAPSHOTS: 'snapshots',
- UNKNOWN: 'unknown',
- };
-
- /**
- * The different ids used of our custom context menu. Sent to the ChromeView
- * and sent back when a menu is selected.
- * @enum {number}
- */
- var ContextMenuItemIds = {
- BOOKMARK_EDIT: 0,
- BOOKMARK_DELETE: 1,
- BOOKMARK_OPEN_IN_NEW_TAB: 2,
- BOOKMARK_OPEN_IN_INCOGNITO_TAB: 3,
- BOOKMARK_SHORTCUT: 4,
-
- MOST_VISITED_OPEN_IN_NEW_TAB: 10,
- MOST_VISITED_OPEN_IN_INCOGNITO_TAB: 11,
- MOST_VISITED_REMOVE: 12,
-
- RECENTLY_CLOSED_OPEN_IN_NEW_TAB: 20,
- RECENTLY_CLOSED_OPEN_IN_INCOGNITO_TAB: 21,
- RECENTLY_CLOSED_REMOVE: 22,
-
- FOREIGN_SESSIONS_REMOVE: 30,
-
- PROMO_VC_SESSION_REMOVE: 40,
- };
-
- /**
- * The URL of the element for the context menu.
- * @type {string}
- */
- var contextMenuUrl = null;
-
- var contextMenuItem = null;
-
- var currentSnapshots = null;
-
- var currentSessions = null;
-
- /**
- * The possible states of the sync section
- * @enum {number}
- */
- var SyncState = {
- INITIAL: 0,
- WAITING_FOR_DATA: 1,
- DISPLAYING_LOADING: 2,
- DISPLAYED_LOADING: 3,
- LOADED: 4,
- };
-
- /**
- * The current state of the sync section.
- */
- var syncState = SyncState.INITIAL;
-
- /**
- * Whether or not sync is enabled. It will be undefined until
- * setSyncEnabled() is called.
- * @type {?boolean}
- */
- var syncEnabled = undefined;
-
- /**
- * The current most visited data being displayed.
- * @type {Array.<Object>}
- */
- var mostVisitedData_ = [];
-
- /**
- * The current bookmark data being displayed. Keep a reference to this data
- * in case the sync enabled state changes. In this case, the bookmark data
- * will need to be refiltered.
- * @type {?Object}
- */
- var bookmarkData;
-
- /**
- * Keep track of any outstanding timers related to updating the sync section.
- */
- var syncTimerId = -1;
-
- /**
- * The minimum amount of time that 'Loading...' can be displayed. This is to
- * prevent flashing.
- */
- var SYNC_LOADING_TIMEOUT = 1000;
-
- /**
- * How long to wait for sync data to load before displaying the 'Loading...'
- * text to the user.
- */
- var SYNC_INITIAL_LOAD_TIMEOUT = 1000;
-
- /**
- * An array of images that are currently in loading state. Once an image
- * loads it is removed from this array.
- */
- var imagesBeingLoaded = new Array();
-
- /**
- * Flag indicating if we are on bookmark shortcut mode.
- * In this mode, only the bookmark section is available and selecting
- * a non-folder bookmark adds it to the home screen.
- * Context menu is disabled.
- */
- var bookmarkShortcutMode = false;
-
- function setIncognitoMode(incognito) {
- isIncognito = incognito;
- if (!isIncognito) {
- chrome.send('getMostVisited');
- chrome.send('getRecentlyClosedTabs');
- chrome.send('getForeignSessions');
- chrome.send('getPromotions');
- chrome.send('getIncognitoDisabled');
- }
- }
-
- function setIncognitoEnabled(item) {
- isIncognitoEnabled = item.incognitoEnabled;
- }
-
- /**
- * Flag set to true when the page is loading its initial set of images. This
- * is set to false after all the initial images have loaded.
- */
- function onInitialImageLoaded(event) {
- var url = event.target.src;
- for (var i = 0; i < imagesBeingLoaded.length; ++i) {
- if (imagesBeingLoaded[i].src == url) {
- imagesBeingLoaded.splice(i, 1);
- if (imagesBeingLoaded.length == 0) {
- // To send out the NTP loading complete notification.
- loadStatus_ |= LoadStatusType.LOAD_IMAGES_COMPLETE;
- sendNTPNotification();
- }
- }
- }
- }
-
- /**
- * Marks the given image as currently being loaded. Once all such images load
- * we inform the browser via a hash change.
- */
- function trackImageLoad(url) {
- if (finishedLoadingNotificationSent_)
- return;
-
- for (var i = 0; i < imagesBeingLoaded.length; ++i) {
- if (imagesBeingLoaded[i].src == url)
- return;
- }
-
- loadStatus_ &= (~LoadStatusType.LOAD_IMAGES_COMPLETE);
-
- var image = new Image();
- image.onload = onInitialImageLoaded;
- image.onerror = onInitialImageLoaded;
- image.src = url;
- imagesBeingLoaded.push(image);
- }
-
- /**
- * Initializes all the UI once the page has loaded.
- */
- function init() {
- // Special case to handle NTP caching.
- if (window.location.hash == '#cached_ntp')
- document.location.hash = '#most_visited';
- // Special case to show a specific bookmarks folder.
- // Used to show the mobile bookmarks folder after importing.
- var bookmarkIdMatch = window.location.hash.match(/#bookmarks:(\d+)/);
- if (bookmarkIdMatch && bookmarkIdMatch.length == 2) {
- localStorage.setItem(DEFAULT_BOOKMARK_FOLDER_KEY, bookmarkIdMatch[1]);
- document.location.hash = '#bookmarks';
- }
- // Special case to choose a bookmark for adding a shortcut.
- // See the doc of bookmarkShortcutMode for details.
- if (window.location.hash == '#bookmark_shortcut')
- bookmarkShortcutMode = true;
- // Make sure a valid section is always displayed. Both normal and
- // incognito NTPs have a bookmarks section.
- if (getPaneIndexFromHash() < 0)
- document.location.hash = '#bookmarks';
-
- // Initialize common widgets.
- var titleScrollers =
- document.getElementsByClassName('section-title-wrapper');
- for (var i = 0, len = titleScrollers.length; i < len; i++)
- initializeTitleScroller(titleScrollers[i]);
-
- // Initialize virtual computers for the sync promo.
- createPromoVirtualComputers();
-
- setCurrentBookmarkFolderData(
- localStorage.getItem(DEFAULT_BOOKMARK_FOLDER_KEY));
-
- addMainSection('incognito');
- addMainSection('most_visited');
- addMainSection('bookmarks');
- addMainSection('open_tabs');
-
- computeDynamicLayout();
-
- scrollToPane(getPaneIndexFromHash());
- updateSyncEmptyState();
-
- window.onpopstate = onPopStateHandler;
- window.addEventListener('hashchange', updatePaneOnHash);
- window.addEventListener('resize', windowResizeHandler);
-
- if (!bookmarkShortcutMode)
- window.addEventListener('contextmenu', contextMenuHandler);
- }
-
- function sendNTPTitleLoadedNotification() {
- if (!titleLoadedStatus_) {
- titleLoadedStatus_ = true;
- chrome.send('notifyNTPTitleLoaded');
- }
- }
-
- /**
- * Notifies the chrome process of the status of the NTP.
- */
- function sendNTPNotification() {
- if (loadStatus_ != LoadStatusType.LOAD_COMPLETE)
- return;
-
- if (!finishedLoadingNotificationSent_) {
- finishedLoadingNotificationSent_ = true;
- chrome.send('notifyNTPReady');
- } else {
- // Navigating after the loading complete notification has been sent
- // might break tests.
- chrome.send('NTPUnexpectedNavigation');
- }
- }
-
- /**
- * The default click handler for created item shortcuts.
- *
- * @param {Object} item The item specification.
- * @param {function} evt The browser click event triggered.
- */
- function itemShortcutClickHandler(item, evt) {
- // Handle the touch callback
- if (item['folder']) {
- browseToBookmarkFolder(item.id);
- } else {
- if (bookmarkShortcutMode) {
- chrome.send('createHomeScreenBookmarkShortcut', [item.id]);
- } else if (!!item.url) {
- window.location = item.url;
- }
- }
- }
-
- /**
- * Opens a recently closed tab.
- *
- * @param {Object} item An object containing the necessary information to
- * reopen a tab.
- */
- function openRecentlyClosedTab(item, evt) {
- chrome.send('openedRecentlyClosed');
- chrome.send('reopenTab', [item.sessionId]);
- }
-
- /**
- * Creates a 'div' DOM element.
- *
- * @param {string} className The CSS class name for the DIV.
- * @param {string=} opt_backgroundUrl The background URL to be applied to the
- * DIV if required.
- * @return {Element} The newly created DIV element.
- */
- function createDiv(className, opt_backgroundUrl) {
- var div = document.createElement('div');
- div.className = className;
- if (opt_backgroundUrl)
- div.style.backgroundImage = 'url(' + opt_backgroundUrl + ')';
- return div;
- }
-
- /**
- * Helper for creating new DOM elements.
- *
- * @param {string} type The type of Element to be created (i.e. 'div',
- * 'span').
- * @param {Object} params A mapping of element attribute key and values that
- * should be applied to the new element.
- * @return {Element} The newly created DOM element.
- */
- function createElement(type, params) {
- var el = document.createElement(type);
- if (typeof params === 'string') {
- el.className = params;
- } else {
- for (attr in params) {
- el[attr] = params[attr];
- }
- }
- return el;
- }
-
- /**
- * Adds a click listener to a specified element with the ability to override
- * the default value of itemShortcutClickHandler.
- *
- * @param {Element} el The element the click listener should be added to.
- * @param {Object} item The item data represented by the element.
- * @param {function(Object, string, BrowserEvent)=} opt_clickCallback The
- * click callback to be triggered upon selection.
- */
- function wrapClickHandler(el, item, opt_clickCallback) {
- el.addEventListener('click', function(evt) {
- var clickCallback =
- opt_clickCallback ? opt_clickCallback : itemShortcutClickHandler;
- clickCallback(item, evt);
- });
- }
-
- /**
- * Create a DOM element to contain a recently closed item for a tablet
- * device.
- *
- * @param {Object} item The data of the item used to generate the shortcut.
- * @param {function(Object, string, BrowserEvent)=} opt_clickCallback The
- * click callback to be triggered upon selection (if not provided it will
- * use the default -- itemShortcutClickHandler).
- * @return {Element} The shortcut element created.
- */
- function makeRecentlyClosedTabletItem(item, opt_clickCallback) {
- var cell = createDiv('cell');
-
- cell.setAttribute(CONTEXT_MENU_URL_KEY, item.url);
-
- var iconUrl = item.icon;
- if (!iconUrl) {
- iconUrl = 'chrome://touch-icon/size/16@' + window.devicePixelRatio +
- 'x/' + item.url;
- }
- var icon = createDiv('icon', iconUrl);
- trackImageLoad(iconUrl);
- cell.appendChild(icon);
-
- var title = createDiv('title');
- title.textContent = item.title;
- cell.appendChild(title);
-
- wrapClickHandler(cell, item, opt_clickCallback);
-
- return cell;
- }
-
- /**
- * Creates a shortcut DOM element based on the item specified item
- * configuration using the thumbnail layout used for most visited. Other
- * data types should not use this as they won't have a thumbnail.
- *
- * @param {Object} item The data of the item used to generate the shortcut.
- * @param {function(Object, string, BrowserEvent)=} opt_clickCallback The
- * click callback to be triggered upon selection (if not provided it will
- * use the default -- itemShortcutClickHandler).
- * @return {Element} The shortcut element created.
- */
- function makeMostVisitedItem(item, opt_clickCallback) {
- // thumbnail-cell -- main outer container
- // thumbnail-container -- container for the thumbnail
- // thumbnail -- the actual thumbnail image; outer border
- // inner-border -- inner border
- // title -- container for the title
- // img -- hack align title text baseline with bottom
- // title text -- the actual text of the title
- var thumbnailCell = createDiv('thumbnail-cell');
- var thumbnailContainer = createDiv('thumbnail-container');
- var backgroundUrl = item.thumbnailUrl || 'chrome://thumb/' + item.url;
- if (backgroundUrl == 'chrome://thumb/chrome://welcome/') {
- // Ideally, it would be nice to use the URL as is. However, as of now
- // theme support has been removed from Chrome. Instead, load the image
- // URL from a style and use it. Don't just use the style because
- // trackImageLoad(...) must be called with the background URL.
- var welcomeStyle = findCssRule('.welcome-to-chrome').style;
- var backgroundImage = welcomeStyle.backgroundImage;
- // trim the "url(" prefix and ")" suffix
- backgroundUrl = backgroundImage.substring(4, backgroundImage.length - 1);
- }
- trackImageLoad(backgroundUrl);
- var thumbnail = createDiv('thumbnail');
- // Use an Image object to ensure the thumbnail image actually exists. If
- // not, this will allow the default to show instead.
- var thumbnailImg = new Image();
- thumbnailImg.onload = function() {
- thumbnail.style.backgroundImage = 'url(' + backgroundUrl + ')';
- };
- thumbnailImg.src = backgroundUrl;
-
- thumbnailContainer.appendChild(thumbnail);
- var innerBorder = createDiv('inner-border');
- thumbnailContainer.appendChild(innerBorder);
- thumbnailCell.appendChild(thumbnailContainer);
- var title = createDiv('title');
- title.textContent = item.title;
- var spacerImg = createElement('img', 'title-spacer');
- spacerImg.alt = '';
- title.insertBefore(spacerImg, title.firstChild);
- thumbnailCell.appendChild(title);
-
- var shade = createDiv('thumbnail-cell-shade');
- thumbnailContainer.appendChild(shade);
- addActiveTouchListener(shade, 'thumbnail-cell-shade-active');
-
- wrapClickHandler(thumbnailCell, item, opt_clickCallback);
-
- thumbnailCell.setAttribute(CONTEXT_MENU_URL_KEY, item.url);
- thumbnailCell.contextMenuItem = item;
- return thumbnailCell;
- }
-
- /**
- * Creates a shortcut DOM element based on the item specified item
- * configuration using the favicon layout used for bookmarks.
- *
- * @param {Object} item The data of the item used to generate the shortcut.
- * @param {function(Object, string, BrowserEvent)=} opt_clickCallback The
- * click callback to be triggered upon selection (if not provided it will
- * use the default -- itemShortcutClickHandler).
- * @return {Element} The shortcut element created.
- */
- function makeBookmarkItem(item, opt_clickCallback) {
- var holder = createDiv('favicon-cell');
- addActiveTouchListener(holder, 'favicon-cell-active');
-
- holder.setAttribute(CONTEXT_MENU_URL_KEY, item.url);
- holder.contextMenuItem = item;
- var faviconBox = createDiv('favicon-box');
- if (item.folder) {
- faviconBox.classList.add('folder');
- } else {
- var iconUrl = item.icon || 'chrome://touch-icon/largest/' + item.url;
- var faviconIcon = createDiv('favicon-icon');
- faviconIcon.style.backgroundImage = 'url(' + iconUrl + ')';
- trackImageLoad(iconUrl);
-
- var image = new Image();
- image.src = iconUrl;
- image.onload = function() {
- var w = image.width;
- var h = image.height;
- if (Math.floor(w) <= 16 || Math.floor(h) <= 16) {
- // it's a standard favicon (or at least it's small).
- faviconBox.classList.add('document');
-
- faviconBox.appendChild(
- createDiv('color-strip colorstrip-' + faviconIndex));
- faviconBox.appendChild(createDiv('bookmark-border'));
- var foldDiv = createDiv('fold');
- foldDiv.id = 'fold_' + faviconIndex;
- foldDiv.style['background'] =
- '-webkit-canvas(fold_' + faviconIndex + ')';
-
- // Use a container so that the fold it self can be zoomed without
- // changing the positioning of the fold.
- var foldContainer = createDiv('fold-container');
- foldContainer.appendChild(foldDiv);
- faviconBox.appendChild(foldContainer);
-
- // FaviconWebUIHandler::HandleGetFaviconDominantColor expects
- // an URL that starts with chrome://favicon/size/.
- // The handler always loads 16x16 1x favicon and assumes that
- // the dominant color for all scale factors is the same.
- chrome.send('getFaviconDominantColor',
- [('chrome://favicon/size/16@1x/' + item.url), '' + faviconIndex]);
- faviconIndex++;
- } else if ((w == 57 && h == 57) || (w == 114 && h == 114)) {
- // it's a touch icon for 1x or 2x.
- faviconIcon.classList.add('touch-icon');
- } else {
- // It's an html5 icon (or at least it's larger).
- // Rescale it to be no bigger than 64x64 dip.
- var max = 64;
- if (w > max || h > max) {
- var scale = (w > h) ? (max / w) : (max / h);
- w *= scale;
- h *= scale;
- }
- faviconIcon.style.backgroundSize = w + 'px ' + h + 'px';
- }
- };
- faviconBox.appendChild(faviconIcon);
- }
- holder.appendChild(faviconBox);
-
- var title = createDiv('title');
- title.textContent = item.title;
- holder.appendChild(title);
-
- wrapClickHandler(holder, item, opt_clickCallback);
-
- return holder;
- }
-
- /**
- * Adds touch listeners to the specified element to apply a class when it is
- * selected (removing the class when no longer pressed).
- *
- * @param {Element} el The element to apply the class to when touched.
- * @param {string} activeClass The CSS class name to be applied when active.
- */
- function addActiveTouchListener(el, activeClass) {
- if (!window.touchCancelListener) {
- window.touchCancelListener = function(evt) {
- if (activeItemDelayTimerId) {
- clearTimeout(activeItemDelayTimerId);
- activeItemDelayTimerId = undefined;
- }
- if (!activeItem) {
- return;
- }
- activeItem.classList.remove(activeItem.dataset.activeClass);
- activeItem = null;
- };
- document.addEventListener('touchcancel', window.touchCancelListener);
- }
- el.dataset.activeClass = activeClass;
- el.addEventListener(PRESS_START_EVT, function(evt) {
- if (activeItemDelayTimerId) {
- clearTimeout(activeItemDelayTimerId);
- activeItemDelayTimerId = undefined;
- }
- activeItemDelayTimerId = setTimeout(function() {
- el.classList.add(activeClass);
- activeItem = el;
- }, ACTIVE_ITEM_DELAY_MS);
- });
- el.addEventListener(PRESS_STOP_EVT, function(evt) {
- if (activeItemDelayTimerId) {
- clearTimeout(activeItemDelayTimerId);
- activeItemDelayTimerId = undefined;
- }
- // Add the active class to ensure the pressed state is visible when
- // quickly tapping, which can happen if the start and stop events are
- // received before the active item delay timer has been executed.
- el.classList.add(activeClass);
- el.classList.add('no-active-delay');
- setTimeout(function() {
- el.classList.remove(activeClass);
- el.classList.remove('no-active-delay');
- }, 0);
- activeItem = null;
- });
- }
-
- /**
- * Creates a shortcut DOM element based on the item specified in the list
- * format.
- *
- * @param {Object} item The data of the item used to generate the shortcut.
- * @param {function(Object, string, BrowserEvent)=} opt_clickCallback The
- * click callback to be triggered upon selection (if not provided it will
- * use the default -- itemShortcutClickHandler).
- * @return {Element} The shortcut element created.
- */
- function makeListEntryItem(item, opt_clickCallback) {
- var listItem = createDiv('list-item');
- addActiveTouchListener(listItem, ACTIVE_LIST_ITEM_CSS_CLASS);
- listItem.setAttribute(CONTEXT_MENU_URL_KEY, item.url);
- var iconSize = item.iconSize || 64;
- var iconUrl = item.icon ||
- 'chrome://touch-icon/size/' + iconSize + '@1x/' + item.url;
- listItem.appendChild(createDiv('icon', iconUrl));
- trackImageLoad(iconUrl);
- var title = createElement('div', {
- textContent: item.title,
- className: 'title session_title'
- });
- listItem.appendChild(title);
-
- listItem.addEventListener('click', function(evt) {
- var clickCallback =
- opt_clickCallback ? opt_clickCallback : itemShortcutClickHandler;
- clickCallback(item, evt);
- });
- if (item.divider == 'section') {
- // Add a child div because the section divider has a gradient and
- // webkit doesn't seem to currently support borders with gradients.
- listItem.appendChild(createDiv('section-divider'));
- } else {
- listItem.classList.add('standard-divider');
- }
- return listItem;
- }
-
- /**
- * Creates a DOM list entry for a remote session or tab.
- *
- * @param {Object} item The data of the item used to generate the shortcut.
- * @param {function(Object, string, BrowserEvent)=} opt_clickCallback The
- * click callback to be triggered upon selection (if not provided it will
- * use the default -- itemShortcutClickHandler).
- * @return {Element} The shortcut element created.
- */
- function makeForeignSessionListEntry(item, opt_clickCallback) {
- // Session item
- var sessionOuterDiv = createDiv('list-item standard-divider');
- addActiveTouchListener(sessionOuterDiv, ACTIVE_LIST_ITEM_CSS_CLASS);
- sessionOuterDiv.contextMenuItem = item;
-
- var icon = createDiv('session-icon ' + item.iconStyle);
- sessionOuterDiv.appendChild(icon);
-
- var titleContainer = createElement('div', 'title');
- sessionOuterDiv.appendChild(titleContainer);
-
- // Extra container to allow title & last-sync time to stack vertically.
- var sessionInnerDiv = createDiv('session_container');
- titleContainer.appendChild(sessionInnerDiv);
-
- var title = createDiv('session-name');
- title.textContent = item.title;
- title.id = item.titleId || '';
- sessionInnerDiv.appendChild(title);
-
- var lastSynced = createDiv('session-last-synced');
- lastSynced.textContent =
- templateData.opentabslastsynced + ': ' + item.userVisibleTimestamp;
- lastSynced.id = item.userVisibleTimestampId || '';
- sessionInnerDiv.appendChild(lastSynced);
-
- sessionOuterDiv.addEventListener('click', function(evt) {
- var clickCallback =
- opt_clickCallback ? opt_clickCallback : itemShortcutClickHandler;
- clickCallback(item, evt);
- });
- return sessionOuterDiv;
- }
-
- /**
- * Saves the number of most visited pages and updates promo visibility.
- * @param {number} n Number of most visited pages.
- */
- function setNumberOfMostVisitedPages(n) {
- numberOfMostVisitedPages = n;
- updatePromoVisibility();
- }
-
- /**
- * Saves the recently closed tabs flag and updates promo visibility.
- * @param {boolean} anyTabs Whether there are any recently closed tabs.
- */
- function setHasRecentlyClosedTabs(anyTabs) {
- hasRecentlyClosedTabs = anyTabs;
- updatePromoVisibility();
- }
-
- /**
- * Updates the most visited pages.
- *
- * @param {Array.<Object>} List of data for displaying the list of most
- * visited pages (see C++ handler for model description).
- * @param {boolean} hasBlacklistedUrls Whether any blacklisted URLs are
- * present.
- */
- function setMostVisitedPages(data, hasBlacklistedUrls) {
- setNumberOfMostVisitedPages(data.length);
- // limit the number of most visited items to display
- if (isPhone() && data.length > 6) {
- data.splice(6, data.length - 6);
- } else if (isTablet() && data.length > 8) {
- data.splice(8, data.length - 8);
- }
-
- data.forEach(function(item, index) {
- item.mostVisitedIndex = index;
- });
-
- if (equals(data, mostVisitedData_))
- return;
-
- var clickFunction = function(item) {
- chrome.send('openedMostVisited');
- chrome.send('metricsHandler:recordInHistogram',
- ['NewTabPage.MostVisited', item.mostVisitedIndex, 8]);
- window.location = item.url;
- };
- populateData(findList('most_visited'), SectionType.MOST_VISITED, data,
- makeMostVisitedItem, clickFunction);
- computeDynamicLayout();
-
- mostVisitedData_ = data;
- }
-
- /**
- * Updates the recently closed tabs.
- *
- * @param {Array.<Object>} List of data for displaying the list of recently
- * closed tabs (see C++ handler for model description).
- */
- function setRecentlyClosedTabs(data) {
- var container = $('recently_closed_container');
- if (!data || data.length == 0) {
- // hide the recently closed section if it is empty.
- container.style.display = 'none';
- setHasRecentlyClosedTabs(false);
- } else {
- container.style.display = 'block';
- setHasRecentlyClosedTabs(true);
- var decoratorFunc = isPhone() ? makeListEntryItem :
- makeRecentlyClosedTabletItem;
- populateData(findList('recently_closed'), SectionType.RECENTLY_CLOSED,
- data, decoratorFunc, openRecentlyClosedTab);
- }
- computeDynamicLayout();
- }
-
- /**
- * Updates the bookmarks.
- *
- * @param {Array.<Object>} List of data for displaying the bookmarks (see
- * C++ handler for model description).
- */
- function bookmarks(data) {
- bookmarkFolderId = data.id;
- if (!replacedInitialState) {
- history.replaceState(
- {folderId: bookmarkFolderId, selectedPaneIndex: currentPaneIndex},
- null, null);
- replacedInitialState = true;
- }
- if (syncEnabled == undefined) {
- // Wait till we know whether or not sync is enabled before displaying any
- // bookmarks (since they may need to be filtered below)
- bookmarkData = data;
- return;
- }
-
- var titleWrapper = $('bookmarks_title_wrapper');
- setBookmarkTitleHierarchy(
- titleWrapper, data, data['hierarchy']);
-
- var filteredBookmarks = data.bookmarks;
- if (!syncEnabled) {
- filteredBookmarks = filteredBookmarks.filter(function(val) {
- return (val.type != 'BOOKMARK_BAR' && val.type != 'OTHER_NODE');
- });
- }
- if (bookmarkShortcutMode) {
- populateData(findList('bookmarks'), SectionType.BOOKMARKS,
- filteredBookmarks, makeBookmarkItem);
- } else {
- var clickFunction = function(item) {
- if (item['folder']) {
- browseToBookmarkFolder(item.id);
- } else if (!!item.url) {
- chrome.send('openedBookmark');
- window.location = item.url;
- }
- };
- populateData(findList('bookmarks'), SectionType.BOOKMARKS,
- filteredBookmarks, makeBookmarkItem, clickFunction);
- }
-
- var bookmarkContainer = $('bookmarks_container');
-
- // update the shadows on the breadcrumb bar
- computeDynamicLayout();
-
- if ((loadStatus_ & LoadStatusType.LOAD_BOOKMARKS_FINISHED) !=
- LoadStatusType.LOAD_BOOKMARKS_FINISHED) {
- loadStatus_ |= LoadStatusType.LOAD_BOOKMARKS_FINISHED;
- sendNTPNotification();
- }
- }
-
- /**
- * Checks if promo is allowed and MostVisited requirements are satisfied.
- * @return {boolean} Whether the promo should be shown on most_visited.
- */
- function shouldPromoBeShownOnMostVisited() {
- return promoIsAllowed && promoIsAllowedOnMostVisited &&
- numberOfMostVisitedPages >= 2 && !hasRecentlyClosedTabs;
- }
-
- /**
- * Checks if promo is allowed and OpenTabs requirements are satisfied.
- * @return {boolean} Whether the promo should be shown on open_tabs.
- */
- function shouldPromoBeShownOnOpenTabs() {
- var snapshotsCount =
- currentSnapshots == null ? 0 : currentSnapshots.length;
- var sessionsCount = currentSessions == null ? 0 : currentSessions.length;
- return promoIsAllowed && promoIsAllowedOnOpenTabs &&
- (snapshotsCount + sessionsCount != 0);
- }
-
- /**
- * Checks if promo is allowed and SyncPromo requirements are satisfied.
- * @return {boolean} Whether the promo should be shown on sync_promo.
- */
- function shouldPromoBeShownOnSync() {
- var snapshotsCount =
- currentSnapshots == null ? 0 : currentSnapshots.length;
- var sessionsCount = currentSessions == null ? 0 : currentSessions.length;
- return promoIsAllowed && promoIsAllowedOnOpenTabs &&
- (snapshotsCount + sessionsCount == 0);
- }
-
- /**
- * Records a promo impression on a given section if necessary.
- * @param {string} section Active section name to check.
- */
- function promoUpdateImpressions(section) {
- if (section == 'most_visited' && shouldPromoBeShownOnMostVisited())
- chrome.send('recordImpression', ['most_visited']);
- else if (section == 'open_tabs' && shouldPromoBeShownOnOpenTabs())
- chrome.send('recordImpression', ['open_tabs']);
- else if (section == 'open_tabs' && shouldPromoBeShownOnSync())
- chrome.send('recordImpression', ['sync_promo']);
- }
-
- /**
- * Updates the visibility on all promo-related items as necessary.
- */
- function updatePromoVisibility() {
- var mostVisitedEl = $('promo_message_on_most_visited');
- var openTabsVCEl = $('promo_vc_list');
- var syncPromoLegacyEl = $('promo_message_on_sync_promo_legacy');
- var syncPromoReceivedEl = $('promo_message_on_sync_promo_received');
- mostVisitedEl.style.display =
- shouldPromoBeShownOnMostVisited() ? 'block' : 'none';
- syncPromoReceivedEl.style.display =
- shouldPromoBeShownOnSync() ? 'block' : 'none';
- syncPromoLegacyEl.style.display =
- shouldPromoBeShownOnSync() ? 'none' : 'block';
- openTabsVCEl.style.display =
- (shouldPromoBeShownOnOpenTabs() && promoIsAllowedAsVirtualComputer) ?
- 'block' : 'none';
- }
-
- /**
- * Called from native.
- * Clears the promotion.
- */
- function clearPromotions() {
- setPromotions({});
- }
-
- /**
- * Set the element to a parsed and sanitized promotion HTML string.
- * @param {Element} el The element to set the promotion string to.
- * @param {string} html The promotion HTML string.
- * @throws {Error} In case of non supported markup.
- */
- function setPromotionHtml(el, html) {
- if (!el) return;
- el.innerHTML = '';
- if (!html) return;
- var tags = ['BR', 'DIV', 'BUTTON', 'SPAN'];
- var attrs = {
- class: function(node, value) { return true; },
- style: function(node, value) { return true; },
- };
- try {
- var fragment = parseHtmlSubset(html, tags, attrs);
- el.appendChild(fragment);
- } catch (err) {
- console.error(err.toString());
- // Ignore all errors while parsing or setting the element.
- }
- }
-
- /**
- * Called from native.
- * Sets the text for all promo-related items, updates
- * promo-send-email-target items to send email on click and
- * updates the visibility of items.
- * @param {Object} promotions Dictionary used to fill-in the text.
- */
- function setPromotions(promotions) {
- var mostVisitedEl = $('promo_message_on_most_visited');
- var openTabsEl = $('promo_message_on_open_tabs');
- var syncPromoReceivedEl = $('promo_message_on_sync_promo_received');
-
- promoIsAllowed = !!promotions.promoIsAllowed;
- promoIsAllowedOnMostVisited = !!promotions.promoIsAllowedOnMostVisited;
- promoIsAllowedOnOpenTabs = !!promotions.promoIsAllowedOnOpenTabs;
- promoIsAllowedAsVirtualComputer = !!promotions.promoIsAllowedAsVC;
-
- setPromotionHtml(mostVisitedEl, promotions.promoMessage);
- setPromotionHtml(openTabsEl, promotions.promoMessage);
- setPromotionHtml(syncPromoReceivedEl, promotions.promoMessageLong);
-
- promoInjectedComputerTitleText = promotions.promoVCTitle || '';
- promoInjectedComputerLastSyncedText = promotions.promoVCLastSynced || '';
- var openTabsVCTitleEl = $('promo_vc_title');
- if (openTabsVCTitleEl)
- openTabsVCTitleEl.textContent = promoInjectedComputerTitleText;
- var openTabsVCLastSyncEl = $('promo_vc_lastsync');
- if (openTabsVCLastSyncEl)
- openTabsVCLastSyncEl.textContent = promoInjectedComputerLastSyncedText;
-
- if (promoIsAllowed) {
- var promoButtonEls =
- document.getElementsByClassName('promo-button');
- for (var i = 0, len = promoButtonEls.length; i < len; i++) {
- promoButtonEls[i].onclick = executePromoAction;
- addActiveTouchListener(promoButtonEls[i], 'promo-button-active');
- }
- }
- updatePromoVisibility();
- }
-
- /**
- * On-click handler for promo email targets.
- * Performs the promo action "send email".
- * @param {Object} evt User interface event that triggered the action.
- */
- function executePromoAction(evt) {
- evt.preventDefault();
- chrome.send('promoActionTriggered');
- }
-
- /**
- * Called by the browser when a context menu has been selected.
- *
- * @param {number} itemId The id of the item that was selected, as specified
- * when chrome.send('showContextMenu') was called.
- */
- function onCustomMenuSelected(itemId) {
- if (contextMenuUrl != null) {
- switch (itemId) {
- case ContextMenuItemIds.BOOKMARK_OPEN_IN_NEW_TAB:
- case ContextMenuItemIds.BOOKMARK_OPEN_IN_INCOGNITO_TAB:
- chrome.send('openedBookmark');
- break;
-
- case ContextMenuItemIds.MOST_VISITED_OPEN_IN_NEW_TAB:
- case ContextMenuItemIds.MOST_VISITED_OPEN_IN_INCOGNITO_TAB:
- chrome.send('openedMostVisited');
- if (contextMenuItem) {
- chrome.send('metricsHandler:recordInHistogram',
- ['NewTabPage.MostVisited',
- contextMenuItem.mostVisitedIndex,
- 8]);
- }
- break;
-
- case ContextMenuItemIds.RECENTLY_CLOSED_OPEN_IN_NEW_TAB:
- case ContextMenuItemIds.RECENTLY_CLOSED_OPEN_IN_INCOGNITO_TAB:
- chrome.send('openedRecentlyClosed');
- break;
- }
- }
-
- switch (itemId) {
- case ContextMenuItemIds.BOOKMARK_OPEN_IN_NEW_TAB:
- case ContextMenuItemIds.MOST_VISITED_OPEN_IN_NEW_TAB:
- case ContextMenuItemIds.RECENTLY_CLOSED_OPEN_IN_NEW_TAB:
- if (contextMenuUrl != null)
- chrome.send('openInNewTab', [contextMenuUrl]);
- break;
-
- case ContextMenuItemIds.BOOKMARK_OPEN_IN_INCOGNITO_TAB:
- case ContextMenuItemIds.MOST_VISITED_OPEN_IN_INCOGNITO_TAB:
- case ContextMenuItemIds.RECENTLY_CLOSED_OPEN_IN_INCOGNITO_TAB:
- if (contextMenuUrl != null)
- chrome.send('openInIncognitoTab', [contextMenuUrl]);
- break;
-
- case ContextMenuItemIds.BOOKMARK_EDIT:
- if (contextMenuItem != null)
- chrome.send('editBookmark', [contextMenuItem.id]);
- break;
-
- case ContextMenuItemIds.BOOKMARK_DELETE:
- if (contextMenuUrl != null)
- chrome.send('deleteBookmark', [contextMenuItem.id]);
- break;
-
- case ContextMenuItemIds.MOST_VISITED_REMOVE:
- if (contextMenuUrl != null)
- chrome.send('blacklistURLFromMostVisited', [contextMenuUrl]);
- break;
-
- case ContextMenuItemIds.BOOKMARK_SHORTCUT:
- if (contextMenuUrl != null)
- chrome.send('createHomeScreenBookmarkShortcut', [contextMenuItem.id]);
- break;
-
- case ContextMenuItemIds.RECENTLY_CLOSED_REMOVE:
- chrome.send('clearRecentlyClosed');
- break;
-
- case ContextMenuItemIds.FOREIGN_SESSIONS_REMOVE:
- if (contextMenuItem != null) {
- chrome.send(
- 'deleteForeignSession', [contextMenuItem.sessionTag]);
- chrome.send('getForeignSessions');
- }
- break;
-
- case ContextMenuItemIds.PROMO_VC_SESSION_REMOVE:
- chrome.send('promoDisabled');
- break;
-
- default:
- log.error('Unknown context menu selected id=' + itemId);
- break;
- }
- }
-
- /**
- * Generates the full bookmark folder hierarchy and populates the scrollable
- * title element.
- *
- * @param {Element} wrapperEl The wrapper element containing the scrollable
- * title.
- * @param {string} data The current bookmark folder node.
- * @param {Array.<Object>=} opt_ancestry The folder ancestry of the current
- * bookmark folder. The list is ordered in order of closest descendant
- * (the root will always be the last node). The definition of each
- * element is:
- * - id {number}: Unique ID of the folder (N/A for root node).
- * - name {string}: Name of the folder (N/A for root node).
- * - root {boolean}: Whether this is the root node.
- */
- function setBookmarkTitleHierarchy(wrapperEl, data, opt_ancestry) {
- var title = wrapperEl.getElementsByClassName('section-title')[0];
- title.innerHTML = '';
- if (opt_ancestry) {
- for (var i = opt_ancestry.length - 1; i >= 0; i--) {
- var titleCrumb = createBookmarkTitleCrumb_(opt_ancestry[i]);
- title.appendChild(titleCrumb);
- title.appendChild(createDiv('bookmark-separator'));
- }
- }
- var titleCrumb = createBookmarkTitleCrumb_(data);
- titleCrumb.classList.add('title-crumb-active');
- title.appendChild(titleCrumb);
-
- // Ensure the last crumb is as visible as possible.
- var windowWidth =
- wrapperEl.getElementsByClassName('section-title-mask')[0].offsetWidth;
- var crumbWidth = titleCrumb.offsetWidth;
- var leftOffset = titleCrumb.offsetLeft;
-
- var shiftLeft = windowWidth - crumbWidth - leftOffset;
- if (shiftLeft < 0) {
- if (crumbWidth > windowWidth)
- shifLeft = -leftOffset;
-
- // Queue up the scrolling initially to allow for the mask element to
- // be placed into the dom and it's size correctly calculated.
- setTimeout(function() {
- handleTitleScroll(wrapperEl, shiftLeft);
- }, 0);
- } else {
- handleTitleScroll(wrapperEl, 0);
- }
- }
-
- /**
- * Creates a clickable bookmark title crumb.
- * @param {Object} data The crumb data (see setBookmarkTitleHierarchy for
- * definition of the data object).
- * @return {Element} The clickable title crumb element.
- * @private
- */
- function createBookmarkTitleCrumb_(data) {
- var titleCrumb = createDiv('title-crumb');
- if (data.root) {
- titleCrumb.innerText = templateData.bookmarkstitle;
- } else {
- titleCrumb.innerText = data.title;
- }
- titleCrumb.addEventListener('click', function(evt) {
- browseToBookmarkFolder(data.root ? '0' : data.id);
- });
- return titleCrumb;
- }
-
- /**
- * Handles scrolling a title element.
- * @param {Element} wrapperEl The wrapper element containing the scrollable
- * title.
- * @param {number} scrollPosition The position to be scrolled to.
- */
- function handleTitleScroll(wrapperEl, scrollPosition) {
- var overflowLeftMask =
- wrapperEl.getElementsByClassName('overflow-left-mask')[0];
- var overflowRightMask =
- wrapperEl.getElementsByClassName('overflow-right-mask')[0];
- var title = wrapperEl.getElementsByClassName('section-title')[0];
- var titleMask = wrapperEl.getElementsByClassName('section-title-mask')[0];
- var titleWidth = title.scrollWidth;
- var containerWidth = titleMask.offsetWidth;
-
- var maxRightScroll = containerWidth - titleWidth;
- var boundedScrollPosition =
- Math.max(maxRightScroll, Math.min(scrollPosition, 0));
-
- overflowLeftMask.style.opacity =
- Math.min(
- 1,
- (Math.max(0, -boundedScrollPosition)) + 10 / 30);
-
- overflowRightMask.style.opacity =
- Math.min(
- 1,
- (Math.max(0, boundedScrollPosition - maxRightScroll) + 10) / 30);
-
- // Set the position of the title.
- if (titleWidth < containerWidth) {
- // left-align on LTR and right-align on RTL.
- title.style.left = '';
- } else {
- title.style.left = boundedScrollPosition + 'px';
- }
- }
-
- /**
- * Initializes a scrolling title element.
- * @param {Element} wrapperEl The wrapper element of the scrolling title.
- */
- function initializeTitleScroller(wrapperEl) {
- var title = wrapperEl.getElementsByClassName('section-title')[0];
-
- var inTitleScroll = false;
- var startingScrollPosition;
- var startingOffset;
- wrapperEl.addEventListener(PRESS_START_EVT, function(evt) {
- inTitleScroll = true;
- startingScrollPosition = getTouchEventX(evt);
- startingOffset = title.offsetLeft;
- });
- document.body.addEventListener(PRESS_STOP_EVT, function(evt) {
- if (!inTitleScroll)
- return;
- inTitleScroll = false;
- });
- document.body.addEventListener(PRESS_MOVE_EVT, function(evt) {
- if (!inTitleScroll)
- return;
- handleTitleScroll(
- wrapperEl,
- startingOffset - (startingScrollPosition - getTouchEventX(evt)));
- evt.stopPropagation();
- });
- }
-
- /**
- * Handles updates from the underlying bookmark model (calls originate
- * in the WebUI handler for bookmarks).
- *
- * @param {Object} status Describes the type of change that occurred. Can
- * contain the following fields:
- * - parent_id {string}: Unique id of the parent that was affected by
- * the change. If the parent is the bookmark
- * bar, then the ID will be 'root'.
- * - node_id {string}: The unique ID of the node that was affected.
- */
- function bookmarkChanged(status) {
- if (status) {
- var affectedParentNode = status['parent_id'];
- var affectedNodeId = status['node_id'];
- var shouldUpdate = (bookmarkFolderId == affectedParentNode ||
- bookmarkFolderId == affectedNodeId);
- if (shouldUpdate)
- setCurrentBookmarkFolderData(bookmarkFolderId);
- } else {
- // This typically happens when extensive changes could have happened to
- // the model, such as initial load, import and sync.
- setCurrentBookmarkFolderData(bookmarkFolderId);
- }
- }
-
- /**
- * Loads the bookarks data for a given folder.
- *
- * @param {string|number} folderId The ID of the folder to load (or null if
- * it should load the root folder).
- */
- function setCurrentBookmarkFolderData(folderId) {
- if (folderId != null) {
- chrome.send('getBookmarks', [folderId]);
- } else {
- chrome.send('getBookmarks');
- }
- try {
- if (folderId == null) {
- localStorage.removeItem(DEFAULT_BOOKMARK_FOLDER_KEY);
- } else {
- localStorage.setItem(DEFAULT_BOOKMARK_FOLDER_KEY, folderId);
- }
- } catch (e) {}
- }
-
- /**
- * Navigates to the specified folder and handles loading the required data.
- * Ensures the current folder can be navigated back to using the browser
- * controls.
- *
- * @param {string|number} folderId The ID of the folder to navigate to.
- */
- function browseToBookmarkFolder(folderId) {
- history.pushState(
- {folderId: folderId, selectedPaneIndex: currentPaneIndex},
- null, null);
- setCurrentBookmarkFolderData(folderId);
- }
-
- /**
- * Called to inform the page of the current sync status. If the state has
- * changed from disabled to enabled, it changes the current and default
- * bookmark section to the root directory. This makes desktop bookmarks are
- * visible.
- */
- function setSyncEnabled(enabled) {
- try {
- if (syncEnabled != undefined && syncEnabled == enabled) {
- // The value didn't change
- return;
- }
- syncEnabled = enabled;
-
- if (enabled) {
- if (!localStorage.getItem(SYNC_ENABLED_KEY)) {
- localStorage.setItem(SYNC_ENABLED_KEY, 'true');
- setCurrentBookmarkFolderData('0');
- }
- } else {
- localStorage.removeItem(SYNC_ENABLED_KEY);
- }
- updatePromoVisibility();
-
- if (bookmarkData) {
- // Bookmark data can now be displayed (or needs to be refiltered)
- bookmarks(bookmarkData);
- }
-
- updateSyncEmptyState();
- } catch (e) {}
- }
-
- /**
- * Handles adding or removing the 'nothing to see here' text from the session
- * list depending on the state of snapshots and sessions.
- *
- * @param {boolean} Whether the call is occuring because of a schedule
- * timeout.
- */
- function updateSyncEmptyState(timeout) {
- if (syncState == SyncState.DISPLAYING_LOADING && !timeout) {
- // Make sure 'Loading...' is displayed long enough
- return;
- }
-
- var openTabsList = findList('open_tabs');
- var snapshotsList = findList('snapshots');
- var syncPromo = $('sync_promo');
- var syncLoading = $('sync_loading');
- var syncEnableSync = $('sync_enable_sync');
-
- if (syncEnabled == undefined ||
- currentSnapshots == null ||
- currentSessions == null) {
- if (syncState == SyncState.INITIAL) {
- // Wait one second for sync data to come in before displaying loading
- // text.
- syncState = SyncState.WAITING_FOR_DATA;
- syncTimerId = setTimeout(function() { updateSyncEmptyState(true); },
- SYNC_INITIAL_LOAD_TIMEOUT);
- } else if (syncState == SyncState.WAITING_FOR_DATA && timeout) {
- // We've waited for the initial info timeout to pass and still don't
- // have data. So, display loading text so the user knows something is
- // happening.
- syncState = SyncState.DISPLAYING_LOADING;
- syncLoading.style.display = '-webkit-box';
- centerEmptySections(syncLoading);
- syncTimerId = setTimeout(function() { updateSyncEmptyState(true); },
- SYNC_LOADING_TIMEOUT);
- } else if (syncState == SyncState.DISPLAYING_LOADING) {
- // Allow the Loading... text to go away once data comes in
- syncState = SyncState.DISPLAYED_LOADING;
- }
- return;
- }
-
- if (syncTimerId != -1) {
- clearTimeout(syncTimerId);
- syncTimerId = -1;
- }
- syncState = SyncState.LOADED;
-
- // Hide everything by default, display selectively below
- syncEnableSync.style.display = 'none';
- syncLoading.style.display = 'none';
- syncPromo.style.display = 'none';
-
- var snapshotsCount =
- currentSnapshots == null ? 0 : currentSnapshots.length;
- var sessionsCount = currentSessions == null ? 0 : currentSessions.length;
-
- if (!syncEnabled) {
- syncEnableSync.style.display = '-webkit-box';
- centerEmptySections(syncEnableSync);
- } else if (sessionsCount + snapshotsCount == 0) {
- syncPromo.style.display = '-webkit-box';
- centerEmptySections(syncPromo);
- } else {
- openTabsList.style.display = sessionsCount == 0 ? 'none' : 'block';
- snapshotsList.style.display = snapshotsCount == 0 ? 'none' : 'block';
- }
- updatePromoVisibility();
- }
-
- /**
- * Called externally when updated snapshot data is available.
- *
- * @param {Object} data The snapshot data
- */
- function snapshots(data) {
- var list = findList('snapshots');
- list.innerHTML = '';
-
- currentSnapshots = data;
- updateSyncEmptyState();
-
- if (!data || data.length == 0)
- return;
-
- data.sort(function(a, b) {
- return b.createTime - a.createTime;
- });
-
- // Create the main container
- var snapshotsEl = createElement('div');
- list.appendChild(snapshotsEl);
-
- // Create the header container
- var headerEl = createDiv('session-header');
- snapshotsEl.appendChild(headerEl);
-
- // Create the documents container
- var docsEl = createDiv('session-children-container');
- snapshotsEl.appendChild(docsEl);
-
- // Create the container for the title & icon
- var headerInnerEl = createDiv('list-item standard-divider');
- addActiveTouchListener(headerInnerEl, ACTIVE_LIST_ITEM_CSS_CLASS);
- headerEl.appendChild(headerInnerEl);
-
- // Create the header icon
- headerInnerEl.appendChild(createDiv('session-icon documents'));
-
- // Create the header title
- var titleContainer = createElement('span', 'title');
- headerInnerEl.appendChild(titleContainer);
- var title = createDiv('session-name');
- title.textContent = templateData.receivedDocuments;
- titleContainer.appendChild(title);
-
- // Add support for expanding and collapsing the children
- var expando = createDiv();
- var expandoFunction = createExpandoFunction(expando, docsEl);
- headerInnerEl.addEventListener('click', expandoFunction);
- headerEl.appendChild(expando);
-
- // Support for actually opening the document
- var snapshotClickCallback = function(item) {
- if (!item)
- return;
- if (item.snapshotId) {
- window.location = 'chrome://snapshot/' + item.snapshotId;
- } else if (item.printJobId) {
- window.location = 'chrome://printjob/' + item.printJobId;
- } else {
- window.location = item.url;
- }
- }
-
- // Finally, add the list of documents
- populateData(docsEl, SectionType.SNAPSHOTS, data,
- makeListEntryItem, snapshotClickCallback);
- }
-
- /**
- * Create a function to handle expanding and collapsing a section
- *
- * @param {Element} expando The expando div
- * @param {Element} element The element to expand and collapse
- * @return {function()} A callback function that should be invoked when the
- * expando is clicked
- */
- function createExpandoFunction(expando, element) {
- expando.className = 'expando open';
- return function() {
- if (element.style.height != '0px') {
- // It seems that '-webkit-transition' only works when explicit pixel
- // values are used.
- setTimeout(function() {
- // If this is the first time to collapse the list, store off the
- // expanded height and also set the height explicitly on the style.
- if (!element.expandedHeight) {
- element.expandedHeight =
- element.clientHeight + 'px';
- element.style.height = element.expandedHeight;
- }
- // Now set the height to 0. Note, this is also done in a callback to
- // give the layout engine a chance to run after possibly setting the
- // height above.
- setTimeout(function() {
- element.style.height = '0px';
- }, 0);
- }, 0);
- expando.className = 'expando closed';
- } else {
- element.style.height = element.expandedHeight;
- expando.className = 'expando open';
- }
- }
- }
-
- /**
- * Initializes the promo_vc_list div to look like a foreign session
- * with a desktop.
- */
- function createPromoVirtualComputers() {
- var list = findList('promo_vc');
- list.innerHTML = '';
-
- // Set up the container and the "virtual computer" session header.
- var sessionEl = createDiv();
- list.appendChild(sessionEl);
- var sessionHeader = createDiv('session-header');
- sessionEl.appendChild(sessionHeader);
-
- // Set up the session children container and the promo as a child.
- var sessionChildren = createDiv('session-children-container');
- var promoMessage = createDiv('promo-message');
- promoMessage.id = 'promo_message_on_open_tabs';
- sessionChildren.appendChild(promoMessage);
- sessionEl.appendChild(sessionChildren);
-
- // Add support for expanding and collapsing the children.
- var expando = createDiv();
- var expandoFunction = createExpandoFunction(expando, sessionChildren);
-
- // Fill-in the contents of the "virtual computer" session header.
- var headerList = [{
- 'title': promoInjectedComputerTitleText,
- 'titleId': 'promo_vc_title',
- 'userVisibleTimestamp': promoInjectedComputerLastSyncedText,
- 'userVisibleTimestampId': 'promo_vc_lastsync',
- 'iconStyle': 'laptop'
- }];
-
- populateData(sessionHeader, SectionType.PROMO_VC_SESSION_HEADER, headerList,
- makeForeignSessionListEntry, expandoFunction);
- sessionHeader.appendChild(expando);
- }
-
- /**
- * Called externally when updated synced sessions data is available.
- *
- * @param {Object} data The snapshot data
- */
- function setForeignSessions(data, tabSyncEnabled) {
- var list = findList('open_tabs');
- list.innerHTML = '';
-
- currentSessions = data;
- updateSyncEmptyState();
-
- // Sort the windows within each client such that more recently
- // modified windows appear first.
- data.forEach(function(client) {
- if (client.windows != null) {
- client.windows.sort(function(a, b) {
- if (b.timestamp == null) {
- return -1;
- } else if (a.timestamp == null) {
- return 1;
- } else {
- return b.timestamp - a.timestamp;
- }
- });
- }
- });
-
- // Sort so more recently modified clients appear first.
- data.sort(function(aClient, bClient) {
- var aWindows = aClient.windows;
- var bWindows = bClient.windows;
- if (bWindows == null || bWindows.length == 0 ||
- bWindows[0].timestamp == null) {
- return -1;
- } else if (aWindows == null || aWindows.length == 0 ||
- aWindows[0].timestamp == null) {
- return 1;
- } else {
- return bWindows[0].timestamp - aWindows[0].timestamp;
- }
- });
-
- data.forEach(function(client, clientNum) {
-
- var windows = client.windows;
- if (windows == null || windows.length == 0)
- return;
-
- // Set up the container for the session header
- var sessionEl = createElement('div');
- list.appendChild(sessionEl);
- var sessionHeader = createDiv('session-header');
- sessionEl.appendChild(sessionHeader);
-
- // Set up the container for the session children
- var sessionChildren = createDiv('session-children-container');
- sessionEl.appendChild(sessionChildren);
-
- var clientName = 'Client ' + clientNum;
- if (client.name)
- clientName = client.name;
-
- var iconStyle;
- var deviceType = client.deviceType;
- if (deviceType == 'win' ||
- deviceType == 'macosx' ||
- deviceType == 'linux' ||
- deviceType == 'chromeos' ||
- deviceType == 'other') {
- iconStyle = 'laptop';
- } else if (deviceType == 'phone') {
- iconStyle = 'phone';
- } else if (deviceType == 'tablet') {
- iconStyle = 'tablet';
- } else {
- console.error('Unknown sync device type found: ', deviceType);
- iconStyle = 'laptop';
- }
- var headerList = [{
- 'title': clientName,
- 'userVisibleTimestamp': windows[0].userVisibleTimestamp,
- 'iconStyle': iconStyle,
- 'sessionTag': client.tag,
- }];
-
- var expando = createDiv();
- var expandoFunction = createExpandoFunction(expando, sessionChildren);
- populateData(sessionHeader, SectionType.FOREIGN_SESSION_HEADER,
- headerList, makeForeignSessionListEntry, expandoFunction);
- sessionHeader.appendChild(expando);
-
- // Populate the session children container
- var openTabsList = new Array();
- for (var winNum = 0; winNum < windows.length; winNum++) {
- win = windows[winNum];
- var tabs = win.tabs;
- for (var tabNum = 0; tabNum < tabs.length; tabNum++) {
- var tab = tabs[tabNum];
- // If this is the last tab in the window and there are more windows,
- // use a section divider.
- var needSectionDivider =
- (tabNum + 1 == tabs.length) && (winNum + 1 < windows.length);
- tab.icon = tab.icon || 'chrome://favicon/size/16@1x/' + tab.url;
-
- openTabsList.push({
- timestamp: tab.timestamp,
- title: tab.title,
- url: tab.url,
- sessionTag: client.tag,
- winNum: winNum,
- sessionId: tab.sessionId,
- icon: tab.icon,
- iconSize: 16,
- divider: needSectionDivider ? 'section' : 'standard',
- });
- }
- }
- var tabCallback = function(item, evt) {
- var buttonIndex = 0;
- var altKeyPressed = false;
- var ctrlKeyPressed = false;
- var metaKeyPressed = false;
- var shiftKeyPressed = false;
- if (evt instanceof MouseEvent) {
- buttonIndex = evt.button;
- altKeyPressed = evt.altKey;
- ctrlKeyPressed = evt.ctrlKey;
- metaKeyPressed = evt.metaKey;
- shiftKeyPressed = evt.shiftKey;
- }
- chrome.send('openedForeignSession');
- chrome.send('openForeignSession', [String(item.sessionTag),
- String(item.winNum), String(item.sessionId), buttonIndex,
- altKeyPressed, ctrlKeyPressed, metaKeyPressed, shiftKeyPressed]);
- };
- populateData(sessionChildren, SectionType.FOREIGN_SESSION, openTabsList,
- makeListEntryItem, tabCallback);
- });
- }
-
- /**
- * Updates the dominant favicon color for a given index.
- *
- * @param {number} index The index of the favicon whose dominant color is
- * being specified.
- * @param {string} color The string encoded color.
- */
- function setFaviconDominantColor(index, color) {
- var colorstrips = document.getElementsByClassName('colorstrip-' + index);
- for (var i = 0; i < colorstrips.length; i++)
- colorstrips[i].style.background = color;
-
- var id = 'fold_' + index;
- var fold = $(id);
- if (!fold)
- return;
- var zoom = window.getComputedStyle(fold).zoom;
- var scale = 1 / window.getComputedStyle(fold).zoom;
-
- // The width/height of the canvas. Set to 24 so it looks good across all
- // resolutions.
- var cw = 24;
- var ch = 24;
-
- // Get the fold canvas and create a path for the fold shape
- var ctx = document.getCSSCanvasContext(
- '2d', 'fold_' + index, cw * scale, ch * scale);
- ctx.beginPath();
- ctx.moveTo(0, 0);
- ctx.lineTo(0, ch * 0.75 * scale);
- ctx.quadraticCurveTo(
- 0, ch * scale,
- cw * .25 * scale, ch * scale);
- ctx.lineTo(cw * scale, ch * scale);
- ctx.closePath();
-
- // Create a gradient for the fold and fill it
- var gradient = ctx.createLinearGradient(cw * scale, 0, 0, ch * scale);
- if (color.indexOf('#') == 0) {
- var r = parseInt(color.substring(1, 3), 16);
- var g = parseInt(color.substring(3, 5), 16);
- var b = parseInt(color.substring(5, 7), 16);
- gradient.addColorStop(0, 'rgba(' + r + ', ' + g + ', ' + b + ', 0.6)');
- } else {
- // assume the color is in the 'rgb(#, #, #)' format
- var rgbBase = color.substring(4, color.length - 1);
- gradient.addColorStop(0, 'rgba(' + rgbBase + ', 0.6)');
- }
- gradient.addColorStop(1, color);
- ctx.fillStyle = gradient;
- ctx.fill();
-
- // Stroke the fold
- ctx.lineWidth = Math.floor(scale);
- ctx.strokeStyle = color;
- ctx.stroke();
- ctx.strokeStyle = 'rgba(0, 0, 0, 0.1)';
- ctx.stroke();
-
- }
-
- /**
- * Finds the list element corresponding to the given name.
- * @param {string} name The name prefix of the DOM element (<prefix>_list).
- * @return {Element} The list element corresponding with the name.
- */
- function findList(name) {
- return $(name + '_list');
- }
-
- /**
- * Render the given data into the given list, and hide or show the entire
- * container based on whether there are any elements. The decorator function
- * is used to create the element to be inserted based on the given data
- * object.
- *
- * @param {holder} The dom element that the generated list items will be put
- * into.
- * @param {SectionType} section The section that data is for.
- * @param {Object} data The data to be populated.
- * @param {function(Object, boolean)} decorator The function that will
- * handle decorating each item in the data.
- * @param {function(Object, Object)} opt_clickCallback The function that is
- * called when the item is clicked.
- */
- function populateData(holder, section, data, decorator,
- opt_clickCallback) {
- // Empty other items in the list, if present.
- holder.innerHTML = '';
- var fragment = document.createDocumentFragment();
- if (!data || data.length == 0) {
- fragment.innerHTML = '';
- } else {
- data.forEach(function(item) {
- var el = decorator(item, opt_clickCallback);
- el.setAttribute(SECTION_KEY, section);
- el.id = section + fragment.childNodes.length;
- fragment.appendChild(el);
- });
- }
- holder.appendChild(fragment);
- if (holder.classList.contains(GRID_CSS_CLASS))
- centerGrid(holder);
- centerEmptySections(holder);
- }
-
- /**
- * Given an element containing a list of child nodes arranged in
- * a grid, this will center the grid in the window based on the
- * remaining space.
- * @param {Element} el Container holding the grid cell items.
- */
- function centerGrid(el) {
- var childEl = el.firstChild;
- if (!childEl)
- return;
-
- // Find the element to actually set the margins on.
- var toCenter = el;
- var curEl = toCenter;
- while (curEl && curEl.classList) {
- if (curEl.classList.contains(GRID_CENTER_CSS_CLASS)) {
- toCenter = curEl;
- break;
- }
- curEl = curEl.parentNode;
- }
- var setItemMargins = el.classList.contains(GRID_SET_ITEM_MARGINS);
- var itemWidth = getItemWidth(childEl, setItemMargins);
- var windowWidth = document.documentElement.offsetWidth;
- if (itemWidth >= windowWidth) {
- toCenter.style.paddingLeft = '0';
- toCenter.style.paddingRight = '0';
- } else {
- var numColumns = el.getAttribute(GRID_COLUMNS);
- if (numColumns) {
- numColumns = parseInt(numColumns);
- } else {
- numColumns = Math.floor(windowWidth / itemWidth);
- }
-
- if (setItemMargins) {
- // In this case, try to size each item to fill as much space as
- // possible.
- var gutterSize =
- (windowWidth - itemWidth * numColumns) / (numColumns + 1);
- var childLeftMargin = Math.round(gutterSize / 2);
- var childRightMargin = Math.floor(gutterSize - childLeftMargin);
- var children = el.childNodes;
- for (var i = 0; i < children.length; i++) {
- children[i].style.marginLeft = childLeftMargin + 'px';
- children[i].style.marginRight = childRightMargin + 'px';
- }
- itemWidth += childLeftMargin + childRightMargin;
- }
-
- var remainder = windowWidth - itemWidth * numColumns;
- var leftPadding = Math.round(remainder / 2);
- var rightPadding = Math.floor(remainder - leftPadding);
- toCenter.style.paddingLeft = leftPadding + 'px';
- toCenter.style.paddingRight = rightPadding + 'px';
-
- if (toCenter.classList.contains(GRID_SET_TOP_MARGIN_CLASS)) {
- var childStyle = window.getComputedStyle(childEl);
- var childLeftPadding = parseInt(
- childStyle.getPropertyValue('padding-left'));
- toCenter.style.paddingTop =
- (childLeftMargin + childLeftPadding + leftPadding) + 'px';
- }
- }
- }
-
- /**
- * Finds and centers all child grid elements for a given node (the grids
- * do not need to be direct descendants and can reside anywhere in the node
- * hierarchy).
- * @param {Element} el The node containing the grid child nodes.
- */
- function centerChildGrids(el) {
- var grids = el.getElementsByClassName(GRID_CSS_CLASS);
- for (var i = 0; i < grids.length; i++)
- centerGrid(grids[i]);
- }
-
- /**
- * Finds and vertically centers all 'empty' elements for a given node (the
- * 'empty' elements do not need to be direct descendants and can reside
- * anywhere in the node hierarchy).
- * @param {Element} el The node containing the 'empty' child nodes.
- */
- function centerEmptySections(el) {
- if (el.classList &&
- el.classList.contains(CENTER_EMPTY_CONTAINER_CSS_CLASS)) {
- centerEmptySection(el);
- }
- var empties = el.getElementsByClassName(CENTER_EMPTY_CONTAINER_CSS_CLASS);
- for (var i = 0; i < empties.length; i++) {
- centerEmptySection(empties[i]);
- }
- }
-
- /**
- * Set the top of the given element to the top of the parent and set the
- * height to (bottom of document - top).
- *
- * @param {Element} el Container holding the centered content.
- */
- function centerEmptySection(el) {
- var parent = el.parentNode;
- var top = parent.offsetTop;
- var bottom = (
- document.documentElement.offsetHeight - getButtonBarPadding());
- el.style.height = (bottom - top) + 'px';
- el.style.top = top + 'px';
- }
-
- /**
- * Finds the index of the panel specified by its prefix.
- * @param {string} The string prefix for the panel.
- * @return {number} The index of the panel.
- */
- function getPaneIndex(panePrefix) {
- var pane = $(panePrefix + '_container');
-
- if (pane != null) {
- var index = panes.indexOf(pane);
-
- if (index >= 0)
- return index;
- }
- return 0;
- }
-
- /**
- * Finds the index of the panel specified by location hash.
- * @return {number} The index of the panel.
- */
- function getPaneIndexFromHash() {
- var paneIndex;
- if (window.location.hash == '#bookmarks') {
- paneIndex = getPaneIndex('bookmarks');
- } else if (window.location.hash == '#bookmark_shortcut') {
- paneIndex = getPaneIndex('bookmarks');
- } else if (window.location.hash == '#most_visited') {
- paneIndex = getPaneIndex('most_visited');
- } else if (window.location.hash == '#open_tabs') {
- paneIndex = getPaneIndex('open_tabs');
- } else if (window.location.hash == '#incognito') {
- paneIndex = getPaneIndex('incognito');
- } else {
- // Couldn't find a good section
- paneIndex = -1;
- }
- return paneIndex;
- }
-
- /**
- * Selects a pane from the top level list (Most Visited, Bookmarks, etc...).
- * @param {number} paneIndex The index of the pane to be selected.
- * @return {boolean} Whether the selected pane has changed.
- */
- function scrollToPane(paneIndex) {
- var pane = panes[paneIndex];
-
- if (pane == currentPane)
- return false;
-
- var newHash = '#' + sectionPrefixes[paneIndex];
- // If updated hash matches the current one in the URL, we need to call
- // updatePaneOnHash directly as updating the hash to the same value will
- // not trigger the 'hashchange' event.
- if (bookmarkShortcutMode || newHash == document.location.hash)
- updatePaneOnHash();
- computeDynamicLayout();
- promoUpdateImpressions(sectionPrefixes[paneIndex]);
- return true;
- }
-
- /**
- * Updates the pane based on the current hash.
- */
- function updatePaneOnHash() {
- var paneIndex = getPaneIndexFromHash();
- var pane = panes[paneIndex];
-
- if (currentPane)
- currentPane.classList.remove('selected');
- pane.classList.add('selected');
- currentPane = pane;
- currentPaneIndex = paneIndex;
-
- setScrollTopForDocument(document, 0);
-
- var panelPrefix = sectionPrefixes[paneIndex];
- var title = templateData[panelPrefix + '_document_title'];
- if (!title)
- title = templateData['title'];
- document.title = title;
-
- sendNTPTitleLoadedNotification();
-
- // TODO (dtrainor): Could potentially add logic to reset the bookmark state
- // if they are moving to that pane. This logic was in there before, but
- // was removed due to the fact that we have to go to this pane as part of
- // the history navigation.
- }
-
- /**
- * Adds a top level section to the NTP.
- * @param {string} panelPrefix The prefix of the element IDs corresponding
- * to the container of the content.
- * @param {boolean=} opt_canBeDefault Whether this section can be marked as
- * the default starting point for subsequent instances of the NTP. The
- * default value for this is true.
- */
- function addMainSection(panelPrefix) {
- var paneEl = $(panelPrefix + '_container');
- var paneIndex = panes.push(paneEl) - 1;
- sectionPrefixes.push(panelPrefix);
- }
-
- /**
- * Handles the dynamic layout of the components on the new tab page. Only
- * layouts that require calculation based on the screen size should go in
- * this function as it will be called during all resize changes
- * (orientation, keyword being displayed).
- */
- function computeDynamicLayout() {
- // Update the scrolling titles to ensure they are not in a now invalid
- // scroll position.
- var titleScrollers =
- document.getElementsByClassName('section-title-wrapper');
- for (var i = 0, len = titleScrollers.length; i < len; i++) {
- var titleEl =
- titleScrollers[i].getElementsByClassName('section-title')[0];
- handleTitleScroll(
- titleScrollers[i],
- titleEl.offsetLeft);
- }
-
- updateMostVisitedStyle();
- updateMostVisitedHeight();
- }
-
- /**
- * The centering of the 'recently closed' section is different depending on
- * the orientation of the device. In landscape, it should be left-aligned
- * with the 'most used' section. In portrait, it should be centered in the
- * screen.
- */
- function updateMostVisitedStyle() {
- if (isTablet()) {
- updateMostVisitedStyleTablet();
- } else {
- updateMostVisitedStylePhone();
- }
- }
-
- /**
- * Updates the style of the most visited pane for the phone.
- */
- function updateMostVisitedStylePhone() {
- var mostVisitedList = $('most_visited_list');
- var childEl = mostVisitedList.firstChild;
- if (!childEl)
- return;
-
- // 'natural' height and width of the thumbnail
- var thumbHeight = 72;
- var thumbWidth = 108;
- var labelHeight = 25;
- var labelWidth = thumbWidth + 20;
- var labelLeft = (thumbWidth - labelWidth) / 2;
- var itemHeight = thumbHeight + labelHeight;
-
- // default vertical margin between items
- var itemMarginTop = 0;
- var itemMarginBottom = 0;
- var itemMarginLeft = 20;
- var itemMarginRight = 20;
-
- var listHeight = 0;
-
- var screenHeight =
- document.documentElement.offsetHeight -
- getButtonBarPadding();
-
- if (isPortrait()) {
- mostVisitedList.setAttribute(GRID_COLUMNS, '2');
- listHeight = screenHeight * .85;
- // Ensure that listHeight is not too small and not too big.
- listHeight = Math.max(listHeight, (itemHeight * 3) + 20);
- listHeight = Math.min(listHeight, 420);
- // Size for 3 rows (4 gutters)
- itemMarginTop = (listHeight - (itemHeight * 3)) / 4;
- } else {
- mostVisitedList.setAttribute(GRID_COLUMNS, '3');
- listHeight = screenHeight;
-
- // If the screen height is less than targetHeight, scale the size of the
- // thumbnails such that the margin between the thumbnails remains
- // constant.
- var targetHeight = 220;
- if (screenHeight < targetHeight) {
- var targetRemainder = targetHeight - 2 * (thumbHeight + labelHeight);
- var scale = (screenHeight - 2 * labelHeight -
- targetRemainder) / (2 * thumbHeight);
- // update values based on scale
- thumbWidth = Math.round(thumbWidth * scale);
- thumbHeight = Math.round(thumbHeight * scale);
- labelWidth = thumbWidth + 20;
- itemHeight = thumbHeight + labelHeight;
- }
-
- // scale the vertical margin such that the items fit perfectly on the
- // screen
- var remainder = screenHeight - (2 * itemHeight);
- var margin = (remainder / 2);
- margin = margin > 24 ? 24 : margin;
- itemMarginTop = Math.round(margin / 2);
- itemMarginBottom = Math.round(margin - itemMarginTop);
- }
-
- mostVisitedList.style.minHeight = listHeight + 'px';
-
- modifyCssRule('body[device="phone"] .thumbnail-cell',
- 'height', itemHeight + 'px');
- modifyCssRule('body[device="phone"] #most_visited_list .thumbnail',
- 'height', thumbHeight + 'px');
- modifyCssRule('body[device="phone"] #most_visited_list .thumbnail',
- 'width', thumbWidth + 'px');
- modifyCssRule(
- 'body[device="phone"] #most_visited_list .thumbnail-container',
- 'height', thumbHeight + 'px');
- modifyCssRule(
- 'body[device="phone"] #most_visited_list .thumbnail-container',
- 'width', thumbWidth + 'px');
- modifyCssRule('body[device="phone"] #most_visited_list .title',
- 'width', labelWidth + 'px');
- modifyCssRule('body[device="phone"] #most_visited_list .title',
- 'left', labelLeft + 'px');
- modifyCssRule('body[device="phone"] #most_visited_list .inner-border',
- 'height', thumbHeight - 2 + 'px');
- modifyCssRule('body[device="phone"] #most_visited_list .inner-border',
- 'width', thumbWidth - 2 + 'px');
-
- modifyCssRule('body[device="phone"] .thumbnail-cell',
- 'margin-left', itemMarginLeft + 'px');
- modifyCssRule('body[device="phone"] .thumbnail-cell',
- 'margin-right', itemMarginRight + 'px');
- modifyCssRule('body[device="phone"] .thumbnail-cell',
- 'margin-top', itemMarginTop + 'px');
- modifyCssRule('body[device="phone"] .thumbnail-cell',
- 'margin-bottom', itemMarginBottom + 'px');
-
- centerChildGrids($('most_visited_container'));
- }
-
- /**
- * Updates the style of the most visited pane for the tablet.
- */
- function updateMostVisitedStyleTablet() {
- function setCenterIconGrid(el, set) {
- if (set) {
- el.classList.add(GRID_CENTER_CSS_CLASS);
- } else {
- el.classList.remove(GRID_CENTER_CSS_CLASS);
- el.style.paddingLeft = '0px';
- el.style.paddingRight = '0px';
- }
- }
- var isPortrait = document.documentElement.offsetWidth <
- document.documentElement.offsetHeight;
- var mostVisitedContainer = $('most_visited_container');
- var mostVisitedList = $('most_visited_list');
- var recentlyClosedContainer = $('recently_closed_container');
- var recentlyClosedList = $('recently_closed_list');
-
- setCenterIconGrid(mostVisitedContainer, !isPortrait);
- setCenterIconGrid(mostVisitedList, isPortrait);
- setCenterIconGrid(recentlyClosedContainer, isPortrait);
- if (isPortrait) {
- recentlyClosedList.classList.add(GRID_CSS_CLASS);
- } else {
- recentlyClosedList.classList.remove(GRID_CSS_CLASS);
- }
-
- // Make the recently closed list visually left align with the most recently
- // closed items in landscape mode. It will be reset by the grid centering
- // in portrait mode.
- if (!isPortrait)
- recentlyClosedContainer.style.paddingLeft = '14px';
- }
-
- /**
- * This handles updating some of the spacing to make the 'recently closed'
- * section appear at the bottom of the page.
- */
- function updateMostVisitedHeight() {
- if (!isTablet())
- return;
- // subtract away height of button bar
- var windowHeight = document.documentElement.offsetHeight;
- var padding = parseInt(window.getComputedStyle(document.body)
- .getPropertyValue('padding-bottom'));
- $('most_visited_container').style.minHeight =
- (windowHeight - padding) + 'px';
- }
-
- /**
- * Called by the native toolbar to open a different section. This handles
- * updating the hash url which in turns makes a history entry.
- *
- * @param {string} section The section to switch to.
- */
- var openSection = function(section) {
- if (!scrollToPane(getPaneIndex(section)))
- return;
- // Update the url so the native toolbar knows the pane has changed and
- // to create a history entry.
- document.location.hash = '#' + section;
- }
-
- /////////////////////////////////////////////////////////////////////////////
- // NTP Scoped Window Event Listeners.
- /////////////////////////////////////////////////////////////////////////////
-
- /**
- * Handles history on pop state changes.
- */
- function onPopStateHandler(event) {
- if (event.state != null) {
- var evtState = event.state;
- // Navigate back to the previously selected panel and ensure the same
- // bookmarks are loaded.
- var selectedPaneIndex = evtState.selectedPaneIndex == undefined ?
- 0 : evtState.selectedPaneIndex;
-
- scrollToPane(selectedPaneIndex);
- setCurrentBookmarkFolderData(evtState.folderId);
- } else {
- // When loading the page, replace the default state with one that
- // specifies the default panel loaded via localStorage as well as the
- // default bookmark folder.
- history.replaceState(
- {folderId: bookmarkFolderId, selectedPaneIndex: currentPaneIndex},
- null, null);
- }
- }
-
- /**
- * Handles window resize events.
- */
- function windowResizeHandler() {
- // Scroll to the current pane to refactor all the margins and offset.
- scrollToPane(currentPaneIndex);
- computeDynamicLayout();
- // Center the padding for each of the grid views.
- centerChildGrids(document);
- centerEmptySections(document);
- }
-
- /*
- * We implement the context menu ourselves.
- */
- function contextMenuHandler(evt) {
- var section = SectionType.UNKNOWN;
- contextMenuUrl = null;
- contextMenuItem = null;
- // The node with a menu have been tagged with their section and url.
- // Let's find these tags.
- var node = evt.target;
- while (node) {
- if (section == SectionType.UNKNOWN &&
- node.getAttribute &&
- node.getAttribute(SECTION_KEY) != null) {
- section = node.getAttribute(SECTION_KEY);
- if (contextMenuUrl != null)
- break;
- }
- if (contextMenuUrl == null) {
- contextMenuUrl = node.getAttribute(CONTEXT_MENU_URL_KEY);
- contextMenuItem = node.contextMenuItem;
- if (section != SectionType.UNKNOWN)
- break;
- }
- node = node.parentNode;
- }
-
- var menuOptions;
-
- if (section == SectionType.BOOKMARKS &&
- !contextMenuItem.folder && !isIncognito) {
- menuOptions = [
- [
- ContextMenuItemIds.BOOKMARK_OPEN_IN_NEW_TAB,
- templateData.elementopeninnewtab
- ]
- ];
- if (isIncognitoEnabled) {
- menuOptions.push([
- ContextMenuItemIds.BOOKMARK_OPEN_IN_INCOGNITO_TAB,
- templateData.elementopeninincognitotab
- ]);
- }
- if (contextMenuItem.editable) {
- menuOptions.push(
- [ContextMenuItemIds.BOOKMARK_EDIT, templateData.bookmarkedit],
- [ContextMenuItemIds.BOOKMARK_DELETE, templateData.bookmarkdelete]);
- }
- if (contextMenuUrl.search('chrome://') == -1 &&
- contextMenuUrl.search('about://') == -1 &&
- document.body.getAttribute('shortcut_item_enabled') == 'true') {
- menuOptions.push([
- ContextMenuItemIds.BOOKMARK_SHORTCUT,
- templateData.bookmarkshortcut
- ]);
- }
- } else if (section == SectionType.BOOKMARKS &&
- !contextMenuItem.folder &&
- isIncognito) {
- menuOptions = [
- [
- ContextMenuItemIds.BOOKMARK_OPEN_IN_INCOGNITO_TAB,
- templateData.elementopeninincognitotab
- ]
- ];
- } else if (section == SectionType.BOOKMARKS &&
- contextMenuItem.folder &&
- contextMenuItem.editable &&
- !isIncognito) {
- menuOptions = [
- [ContextMenuItemIds.BOOKMARK_EDIT, templateData.editfolder],
- [ContextMenuItemIds.BOOKMARK_DELETE, templateData.deletefolder]
- ];
- } else if (section == SectionType.MOST_VISITED) {
- menuOptions = [
- [
- ContextMenuItemIds.MOST_VISITED_OPEN_IN_NEW_TAB,
- templateData.elementopeninnewtab
- ],
- ];
- if (isIncognitoEnabled) {
- menuOptions.push([
- ContextMenuItemIds.MOST_VISITED_OPEN_IN_INCOGNITO_TAB,
- templateData.elementopeninincognitotab
- ]);
- }
- menuOptions.push(
- [ContextMenuItemIds.MOST_VISITED_REMOVE, templateData.elementremove]);
- } else if (section == SectionType.RECENTLY_CLOSED) {
- menuOptions = [
- [
- ContextMenuItemIds.RECENTLY_CLOSED_OPEN_IN_NEW_TAB,
- templateData.elementopeninnewtab
- ],
- ];
- if (isIncognitoEnabled) {
- menuOptions.push([
- ContextMenuItemIds.RECENTLY_CLOSED_OPEN_IN_INCOGNITO_TAB,
- templateData.elementopeninincognitotab
- ]);
- }
- menuOptions.push(
- [ContextMenuItemIds.RECENTLY_CLOSED_REMOVE, templateData.removeall]);
- } else if (section == SectionType.FOREIGN_SESSION_HEADER) {
- menuOptions = [
- [
- ContextMenuItemIds.FOREIGN_SESSIONS_REMOVE,
- templateData.elementremove
- ]
- ];
- } else if (section == SectionType.PROMO_VC_SESSION_HEADER) {
- menuOptions = [
- [
- ContextMenuItemIds.PROMO_VC_SESSION_REMOVE,
- templateData.elementremove
- ]
- ];
- }
-
- if (menuOptions)
- chrome.send('showContextMenu', menuOptions);
-
- return false;
- }
-
- // Return an object with all the exports
- return {
- bookmarks: bookmarks,
- bookmarkChanged: bookmarkChanged,
- clearPromotions: clearPromotions,
- init: init,
- setIncognitoEnabled: setIncognitoEnabled,
- onCustomMenuSelected: onCustomMenuSelected,
- openSection: openSection,
- setFaviconDominantColor: setFaviconDominantColor,
- setForeignSessions: setForeignSessions,
- setIncognitoMode: setIncognitoMode,
- setMostVisitedPages: setMostVisitedPages,
- setPromotions: setPromotions,
- setRecentlyClosedTabs: setRecentlyClosedTabs,
- setSyncEnabled: setSyncEnabled,
- snapshots: snapshots
- };
-});
-
-/////////////////////////////////////////////////////////////////////////////
-//Utility Functions.
-/////////////////////////////////////////////////////////////////////////////
-
-/**
- * A best effort approach for checking simple data object equality.
- * @param {?} val1 The first value to check equality for.
- * @param {?} val2 The second value to check equality for.
- * @return {boolean} Whether the two objects are equal(ish).
- */
-function equals(val1, val2) {
- if (typeof val1 != 'object' || typeof val2 != 'object')
- return val1 === val2;
-
- // Object and array equality checks.
- var keyCountVal1 = 0;
- for (var key in val1) {
- if (!(key in val2) || !equals(val1[key], val2[key]))
- return false;
- keyCountVal1++;
- }
- var keyCountVal2 = 0;
- for (var key in val2)
- keyCountVal2++;
- if (keyCountVal1 != keyCountVal2)
- return false;
- return true;
-}
-
-/**
- * Alias for document.getElementById.
- * @param {string} id The ID of the element to find.
- * @return {HTMLElement} The found element or null if not found.
- */
-function $(id) {
- return document.getElementById(id);
-}
-
-/**
- * @return {boolean} Whether the device is currently in portrait mode.
- */
-function isPortrait() {
- return document.documentElement.offsetWidth <
- document.documentElement.offsetHeight;
-}
-
-/**
- * Determine if the page should be formatted for tablets.
- * @return {boolean} true if the device is a tablet, false otherwise.
- */
-function isTablet() {
- return document.body.getAttribute('device') == 'tablet';
-}
-
-/**
- * Determine if the page should be formatted for phones.
- * @return {boolean} true if the device is a phone, false otherwise.
- */
-function isPhone() {
- return document.body.getAttribute('device') == 'phone';
-}
-
-/**
- * Get the page X coordinate of a touch event.
- * @param {TouchEvent} evt The touch event triggered by the browser.
- * @return {number} The page X coordinate of the touch event.
- */
-function getTouchEventX(evt) {
- return (evt.touches[0] || e.changedTouches[0]).pageX;
-}
-
-/**
- * Get the page Y coordinate of a touch event.
- * @param {TouchEvent} evt The touch event triggered by the browser.
- * @return {number} The page Y coordinate of the touch event.
- */
-function getTouchEventY(evt) {
- return (evt.touches[0] || e.changedTouches[0]).pageY;
-}
-
-/**
- * @param {Element} el The item to get the width of.
- * @param {boolean} excludeMargin If true, exclude the width of the margin.
- * @return {number} The total width of a given item.
- */
-function getItemWidth(el, excludeMargin) {
- var elStyle = window.getComputedStyle(el);
- var width = el.offsetWidth;
- if (!width || width == 0) {
- width = parseInt(elStyle.getPropertyValue('width'));
- width +=
- parseInt(elStyle.getPropertyValue('border-left-width')) +
- parseInt(elStyle.getPropertyValue('border-right-width'));
- width +=
- parseInt(elStyle.getPropertyValue('padding-left')) +
- parseInt(elStyle.getPropertyValue('padding-right'));
- }
- if (!excludeMargin) {
- width += parseInt(elStyle.getPropertyValue('margin-left')) +
- parseInt(elStyle.getPropertyValue('margin-right'));
- }
- return width;
-}
-
-/**
- * @return {number} The padding height of the body due to the button bar
- */
-function getButtonBarPadding() {
- var body = document.getElementsByTagName('body')[0];
- var style = window.getComputedStyle(body);
- return parseInt(style.getPropertyValue('padding-bottom'));
-}
-
-/**
- * Modify a css rule
- * @param {string} selector The selector for the rule (passed to findCssRule())
- * @param {string} property The property to update
- * @param {string} value The value to update the property to
- * @return {boolean} true if the rule was updated, false otherwise.
- */
-function modifyCssRule(selector, property, value) {
- var rule = findCssRule(selector);
- if (!rule)
- return false;
- rule.style[property] = value;
- return true;
-}
-
-/**
- * Find a particular CSS rule. The stylesheets attached to the document
- * are traversed in reverse order. The rules in each stylesheet are also
- * traversed in reverse order. The first rule found to match the selector
- * is returned.
- * @param {string} selector The selector for the rule.
- * @return {Object} The rule if one was found, null otherwise
- */
-function findCssRule(selector) {
- var styleSheets = document.styleSheets;
- for (i = styleSheets.length - 1; i >= 0; i--) {
- var styleSheet = styleSheets[i];
- var rules = styleSheet.cssRules;
- if (rules == null)
- continue;
- for (j = rules.length - 1; j >= 0; j--) {
- if (rules[j].selectorText == selector)
- return rules[j];
- }
- }
-}
-
-/////////////////////////////////////////////////////////////////////////////
-// NTP Entry point.
-/////////////////////////////////////////////////////////////////////////////
-
-/*
- * Handles initializing the UI when the page has finished loading.
- */
-window.addEventListener('DOMContentLoaded', function(evt) {
- ntp.init();
- $('content-area').style.display = 'block';
-});