| Index: chrome/browser/resources/ntp_android/ntp_android.js
|
| diff --git a/chrome/browser/resources/ntp_android/ntp_android.js b/chrome/browser/resources/ntp_android/ntp_android.js
|
| deleted file mode 100644
|
| index c44298cfb3ed78b2807b9f3a6d3c7fbf7aef3f07..0000000000000000000000000000000000000000
|
| --- a/chrome/browser/resources/ntp_android/ntp_android.js
|
| +++ /dev/null
|
| @@ -1,2736 +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]);
|
| - }
|
| - } 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';
|
| -});
|
|
|