| Index: chrome/browser/resources/local_ntp/local_ntp.js
|
| diff --git a/chrome/browser/resources/local_ntp/local_ntp.js b/chrome/browser/resources/local_ntp/local_ntp.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..550e5e1af19cac1f173ca2df13b4f96202e08a15
|
| --- /dev/null
|
| +++ b/chrome/browser/resources/local_ntp/local_ntp.js
|
| @@ -0,0 +1,519 @@
|
| +// Copyright 2013 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.
|
| +
|
| +(function() {
|
| +/**
|
| + * The element used to vertically position the most visited section on
|
| + * window resize.
|
| + * @type {Element}
|
| + */
|
| +var topMarginElement;
|
| +
|
| +/**
|
| + * The container for the tile elements.
|
| + * @type {Element}
|
| + */
|
| +var tilesContainer;
|
| +
|
| +/**
|
| + * The notification displayed when a page is blacklisted.
|
| + * @type {Element}
|
| + */
|
| +var notification;
|
| +
|
| +/**
|
| + * The array of rendered tiles, ordered by appearance.
|
| + * @type {Array.<Tile>}
|
| + */
|
| +var tiles = [];
|
| +
|
| +/**
|
| + * The last blacklisted tile if any, which by definition should not be filler.
|
| + * @type {?Tile}
|
| + */
|
| +var lastBlacklistedTile = null;
|
| +
|
| +/**
|
| + * The index of the last blacklisted tile, if any. Used to determine where to
|
| + * re-insert a tile on undo.
|
| + * @type {number}
|
| + */
|
| +var lastBlacklistedIndex = -1;
|
| +
|
| +/**
|
| + * True if a page has been blacklisted and we're waiting on the
|
| + * onmostvisitedchange callback. See onMostVisitedChange() for how this
|
| + * is used.
|
| + * @type {boolean}
|
| + */
|
| +var isBlacklisting = false;
|
| +
|
| +/**
|
| + * True if a blacklist has been undone and we're waiting on the
|
| + * onmostvisitedchange callback. See onMostVisitedChange() for how this
|
| + * is used.
|
| + * @type {boolean}
|
| + */
|
| +var isUndoing = false;
|
| +
|
| +/**
|
| + * Current number of tiles shown based on the window width, including filler.
|
| + * @type {number}
|
| + */
|
| +var numTilesShown = 0;
|
| +
|
| +/**
|
| + * The browser embeddedSearch.newTabPage object.
|
| + * @type {Object}
|
| + */
|
| +var apiHandle;
|
| +
|
| +/**
|
| + * Possible background-colors of a non-custom theme. Used to determine whether
|
| + * the homepage should be updated to support custom or non-custom themes.
|
| + * @type {!Array.<string>}
|
| + * @const
|
| + */
|
| +var WHITE = ['rgba(255,255,255,1)', 'rgba(0,0,0,0)'];
|
| +
|
| +/**
|
| + * Should be equal to mv-tile's -webkit-margin-start + width.
|
| + * @type {number}
|
| + * @const
|
| + */
|
| +var TILE_WIDTH = 160;
|
| +
|
| +/**
|
| + * The height of the most visited section.
|
| + * @type {number}
|
| + * @const
|
| + */
|
| +var MOST_VISITED_HEIGHT = 156;
|
| +
|
| +/** @type {number} @const */
|
| +var MAX_NUM_TILES_TO_SHOW = 4;
|
| +
|
| +/** @type {number} @const */
|
| +var MIN_NUM_TILES_TO_SHOW = 2;
|
| +
|
| +/**
|
| + * Minimum total padding to give to the left and right of the most visited
|
| + * section. Used to determine how many tiles to show.
|
| + * @type {number}
|
| + * @const
|
| + */
|
| +var MIN_TOTAL_HORIZONTAL_PADDING = 188;
|
| +
|
| +/**
|
| + * Enum for classnames.
|
| + * @enum {string}
|
| + * @const
|
| + */
|
| +var CLASSES = {
|
| + TILE: 'mv-tile',
|
| + PAGE: 'mv-page', // page tiles
|
| + TITLE: 'mv-title',
|
| + THUMBNAIL: 'mv-thumb',
|
| + DOMAIN: 'mv-domain',
|
| + BLACKLIST_BUTTON: 'mv-x',
|
| + FAVICON: 'mv-favicon',
|
| + FILLER: 'mv-filler', // filler tiles
|
| + BLACKLIST: 'mv-blacklist', // triggers tile blacklist animation
|
| + HIDE_TILE: 'mv-tile-hide', // hides tiles on small browser width
|
| + HIDE_BLACKLIST_BUTTON: 'mv-x-hide', // hides blacklist button during animation
|
| + DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide',
|
| + HIDE_NOTIFICATION: 'mv-notice-hide'
|
| +};
|
| +
|
| +/**
|
| + * Enum for ids.
|
| + * @enum {string}
|
| + * @const
|
| + */
|
| +var IDS = {
|
| + TOP_MARGIN: 'mv-top-margin',
|
| + TILES: 'mv-tiles',
|
| + NOTIFICATION: 'mv-notice',
|
| + NOTIFICATION_MESSAGE: 'mv-msg',
|
| + UNDO_LINK: 'mv-undo',
|
| + RESTORE_ALL_LINK: 'mv-restore',
|
| + NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x'
|
| +};
|
| +
|
| +/**
|
| + * A Tile is either a rendering of a Most Visited page or "filler" used to
|
| + * pad out the section when not enough pages exist.
|
| + *
|
| + * @param {Element} elem The element for rendering the tile.
|
| + * @param {number=} opt_rid The RID for the corresponding Most Visited page.
|
| + * Should only be left unspecified when creating a filler tile.
|
| + * @constructor
|
| + */
|
| +function Tile(elem, opt_rid) {
|
| + /** @type {Element} */
|
| + this.elem = elem;
|
| +
|
| + /** @type {number|undefined} */
|
| + this.rid = opt_rid;
|
| +}
|
| +
|
| +/**
|
| + * Updates the NTP based on the current theme.
|
| + * @private
|
| + */
|
| +function onThemeChange() {
|
| + var info = apiHandle.themeBackgroundInfo;
|
| + if (!info)
|
| + return;
|
| + var background = [info.colorRgba,
|
| + info.imageUrl,
|
| + info.imageTiling,
|
| + info.imageHorizontalAlignment,
|
| + info.imageVerticalAlignment].join(' ').trim();
|
| + document.body.style.background = background;
|
| + var isCustom = !!background && WHITE.indexOf(background) == -1;
|
| + document.body.classList.toggle('custom-theme', isCustom);
|
| +}
|
| +
|
| +/**
|
| + * Handles a new set of Most Visited page data.
|
| + */
|
| +function onMostVisitedChange() {
|
| + var pages = apiHandle.mostVisited;
|
| +
|
| + if (isBlacklisting) {
|
| + // If this was called as a result of a blacklist, add a new replacement
|
| + // (possibly filler) tile at the end and trigger the blacklist animation.
|
| + var replacementTile = createTile(pages[MAX_NUM_TILES_TO_SHOW - 1]);
|
| +
|
| + tiles.push(replacementTile);
|
| + tilesContainer.appendChild(replacementTile.elem);
|
| +
|
| + var lastBlacklistedTileElement = lastBlacklistedTile.elem;
|
| + lastBlacklistedTileElement.addEventListener(
|
| + 'webkitTransitionEnd', blacklistAnimationDone);
|
| + lastBlacklistedTileElement.classList.add(CLASSES.BLACKLIST);
|
| + // In order to animate the replacement tile sliding into place, it must
|
| + // be made visible.
|
| + updateTileVisibility(numTilesShown + 1);
|
| +
|
| + } else if (isUndoing) {
|
| + // If this was called as a result of an undo, re-insert the last blacklisted
|
| + // tile in its old location and trigger the undo animation.
|
| + tiles.splice(
|
| + lastBlacklistedIndex, 0, lastBlacklistedTile);
|
| + var lastBlacklistedTileElement = lastBlacklistedTile.elem;
|
| + tilesContainer.insertBefore(
|
| + lastBlacklistedTileElement,
|
| + tilesContainer.childNodes[lastBlacklistedIndex]);
|
| + lastBlacklistedTileElement.addEventListener(
|
| + 'webkitTransitionEnd', undoAnimationDone);
|
| + // Force the removal to happen synchronously.
|
| + lastBlacklistedTileElement.scrollTop;
|
| + lastBlacklistedTileElement.classList.remove(CLASSES.BLACKLIST);
|
| + } else {
|
| + // Otherwise render the tiles using the new data without animation.
|
| + tiles = [];
|
| + for (var i = 0; i < MAX_NUM_TILES_TO_SHOW; ++i) {
|
| + tiles.push(createTile(pages[i]));
|
| + }
|
| + renderTiles();
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * Renders the current set of tiles without animation.
|
| + */
|
| +function renderTiles() {
|
| + removeChildren(tilesContainer);
|
| + for (var i = 0, length = tiles.length; i < length; ++i) {
|
| + tilesContainer.appendChild(tiles[i].elem);
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * Creates a Tile with the specified page data. If no data is provided, a
|
| + * filler Tile is created.
|
| + * @param {Object} page The page data.
|
| + * @return {Tile} The new Tile.
|
| + */
|
| +function createTile(page) {
|
| + var tileElement = document.createElement('div');
|
| + tileElement.classList.add(CLASSES.TILE);
|
| +
|
| + if (page) {
|
| + var rid = page.rid;
|
| + tileElement.classList.add(CLASSES.PAGE);
|
| +
|
| + // The click handler for navigating to the page identified by the RID.
|
| + tileElement.addEventListener('click', function() {
|
| + apiHandle.navigateContentWindow(rid);
|
| + });
|
| +
|
| + // The shadow DOM which renders the page title.
|
| + var titleElement = page.titleElement;
|
| + if (titleElement) {
|
| + titleElement.classList.add(CLASSES.TITLE);
|
| + tileElement.appendChild(titleElement);
|
| + }
|
| +
|
| + // Render the thumbnail if present. Otherwise, fall back to a shadow DOM
|
| + // which renders the domain.
|
| + var thumbnailUrl = page.thumbnailUrl;
|
| +
|
| + var showDomainElement = function() {
|
| + var domainElement = page.domainElement;
|
| + if (domainElement) {
|
| + domainElement.classList.add(CLASSES.DOMAIN);
|
| + tileElement.appendChild(domainElement);
|
| + }
|
| + };
|
| + if (thumbnailUrl) {
|
| + var image = new Image();
|
| + image.onload = function() {
|
| + var thumbnailElement = createAndAppendElement(
|
| + tileElement, 'div', CLASSES.THUMBNAIL);
|
| + thumbnailElement.style.backgroundImage = 'url(' + thumbnailUrl + ')';
|
| + };
|
| +
|
| + image.onerror = showDomainElement;
|
| + image.src = thumbnailUrl;
|
| + } else {
|
| + showDomainElement();
|
| + }
|
| +
|
| + // The button used to blacklist this page.
|
| + var blacklistButton = createAndAppendElement(
|
| + tileElement, 'div', CLASSES.BLACKLIST_BUTTON);
|
| + blacklistButton.addEventListener('click', generateBlacklistFunction(rid));
|
| + // TODO(jeremycho): i18n. See http://crbug.com/190223.
|
| + blacklistButton.title = "Don't show on this page";
|
| +
|
| + // The page favicon, if any.
|
| + var faviconUrl = page.faviconUrl;
|
| + if (faviconUrl) {
|
| + var favicon = createAndAppendElement(
|
| + tileElement, 'div', CLASSES.FAVICON);
|
| + favicon.style.backgroundImage = 'url(' + faviconUrl + ')';
|
| + }
|
| + return new Tile(tileElement, rid);
|
| + } else {
|
| + tileElement.classList.add(CLASSES.FILLER);
|
| + return new Tile(tileElement);
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * Generates a function to be called when the page with the corresponding RID
|
| + * is blacklisted.
|
| + * @param {number} rid The RID of the page being blacklisted.
|
| + * @return {function(Event)} A function which handles the blacklisting of the
|
| + * page by displaying the notification, updating state variables, and
|
| + * notifying Chrome.
|
| + */
|
| +function generateBlacklistFunction(rid) {
|
| + return function(e) {
|
| + // Prevent navigation when the page is being blacklisted.
|
| + e.stopPropagation();
|
| +
|
| + showNotification();
|
| + isBlacklisting = true;
|
| + tilesContainer.classList.add(CLASSES.HIDE_BLACKLIST_BUTTON);
|
| + lastBlacklistedTile = getTileByRid(rid);
|
| + lastBlacklistedIndex = tiles.indexOf(lastBlacklistedTile);
|
| + apiHandle.deleteMostVisitedItem(rid);
|
| + };
|
| +}
|
| +
|
| +/**
|
| + * Shows the blacklist notification and triggers a delay to hide it.
|
| + */
|
| +function showNotification() {
|
| + notification.classList.remove(CLASSES.HIDE_NOTIFICATION);
|
| + notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION);
|
| + notification.scrollTop;
|
| + notification.classList.add(CLASSES.DELAYED_HIDE_NOTIFICATION);
|
| +}
|
| +
|
| +/**
|
| + * Hides the blacklist notification.
|
| + */
|
| +function hideNotification() {
|
| + notification.classList.add(CLASSES.HIDE_NOTIFICATION);
|
| +}
|
| +
|
| +/**
|
| + * Handles the end of the blacklist animation by removing the blacklisted tile.
|
| + */
|
| +function blacklistAnimationDone() {
|
| + tiles.splice(lastBlacklistedIndex, 1);
|
| + removeNode(lastBlacklistedTile.elem);
|
| + updateTileVisibility(numTilesShown);
|
| + isBlacklisting = false;
|
| + tilesContainer.classList.remove(CLASSES.HIDE_BLACKLIST_BUTTON);
|
| + lastBlacklistedTile.elem.removeEventListener(
|
| + 'webkitTransitionEnd', blacklistAnimationDone);
|
| +}
|
| +
|
| +/**
|
| + * Handles a click on the notification undo link by hiding the notification and
|
| + * informing Chrome.
|
| + */
|
| +function onUndo() {
|
| + hideNotification();
|
| + var lastBlacklistedRID = lastBlacklistedTile.rid;
|
| + if (typeof lastBlacklistedRID != 'undefined') {
|
| + isUndoing = true;
|
| + apiHandle.undoMostVisitedDeletion(lastBlacklistedRID);
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * Handles the end of the undo animation by removing the extraneous end tile.
|
| + */
|
| +function undoAnimationDone() {
|
| + isUndoing = false;
|
| + tiles.splice(tiles.length - 1, 1);
|
| + removeNode(tilesContainer.lastElementChild);
|
| + updateTileVisibility(numTilesShown);
|
| + lastBlacklistedTile.elem.removeEventListener(
|
| + 'webkitTransitionEnd', undoAnimationDone);
|
| +}
|
| +
|
| +/**
|
| + * Handles a click on the restore all notification link by hiding the
|
| + * notification and informing Chrome.
|
| + */
|
| +function onRestoreAll() {
|
| + hideNotification();
|
| + apiHandle.undoAllMostVisitedDeletions();
|
| +}
|
| +
|
| +/**
|
| + * Handles a resize by vertically centering the most visited section
|
| + * and triggering the tile show/hide animation if necessary.
|
| + */
|
| +function onResize() {
|
| + var clientHeight = document.documentElement.clientHeight;
|
| + topMarginElement.style.marginTop =
|
| + Math.max(0, (clientHeight - MOST_VISITED_HEIGHT) / 2) + 'px';
|
| +
|
| + var clientWidth = document.documentElement.clientWidth;
|
| + var numTilesToShow = Math.floor(
|
| + (clientWidth - MIN_TOTAL_HORIZONTAL_PADDING) / TILE_WIDTH);
|
| + numTilesToShow = Math.max(MIN_NUM_TILES_TO_SHOW, numTilesToShow);
|
| + if (numTilesToShow != numTilesShown) {
|
| + updateTileVisibility(numTilesToShow);
|
| + numTilesShown = numTilesToShow;
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * Triggers an animation to show the first numTilesToShow tiles and hide the
|
| + * remaining.
|
| + * @param {number} numTilesToShow The number of tiles to show.
|
| + */
|
| +function updateTileVisibility(numTilesToShow) {
|
| + for (var i = 0, length = tiles.length; i < length; ++i) {
|
| + tiles[i].elem.classList.toggle(CLASSES.HIDE_TILE, i >= numTilesToShow);
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * Returns the tile corresponding to the specified page RID.
|
| + * @param {number} rid The page RID being looked up.
|
| + * @return {Tile} The corresponding tile.
|
| + */
|
| +function getTileByRid(rid) {
|
| + for (var i = 0, length = tiles.length; i < length; ++i) {
|
| + var tile = tiles[i];
|
| + if (tile.rid == rid)
|
| + return tile;
|
| + }
|
| + return null;
|
| +}
|
| +
|
| +/**
|
| + * Utility function which creates an element with an optional classname and
|
| + * appends it to the specified parent.
|
| + * @param {Element} parent The parent to append the new element.
|
| + * @param {string} name The name of the new element.
|
| + * @param {string=} opt_class The optional classname of the new element.
|
| + * @return {Element} The new element.
|
| + */
|
| +function createAndAppendElement(parent, name, opt_class) {
|
| + var child = document.createElement(name);
|
| + if (opt_class)
|
| + child.classList.add(opt_class);
|
| + parent.appendChild(child);
|
| + return child;
|
| +}
|
| +
|
| +/**
|
| + * Removes a node from its parent.
|
| + * @param {Node} node The node to remove.
|
| + */
|
| +function removeNode(node) {
|
| + node.parentNode.removeChild(node);
|
| +}
|
| +
|
| +/**
|
| + * Removes all the child nodes on a DOM node.
|
| + * @param {Node} node Node to remove children from.
|
| + */
|
| +function removeChildren(node) {
|
| + node.innerHTML = '';
|
| +}
|
| +
|
| +/**
|
| + * @return {Object} the handle to the embeddedSearch API.
|
| + */
|
| +function getEmbeddedSearchApiHandle() {
|
| + if (window.cideb)
|
| + return window.cideb;
|
| + if (window.chrome && window.chrome.embeddedSearch)
|
| + return window.chrome.embeddedSearch;
|
| + return null;
|
| +}
|
| +
|
| +/**
|
| + * Prepares the New Tab Page by adding listeners, rendering the current
|
| + * theme, and the most visited pages section.
|
| + */
|
| +function init() {
|
| + topMarginElement = document.getElementById(IDS.TOP_MARGIN);
|
| + tilesContainer = document.getElementById(IDS.TILES);
|
| + notification = document.getElementById(IDS.NOTIFICATION);
|
| +
|
| + // TODO(jeremycho): i18n.
|
| + var notificationMessage = document.getElementById(IDS.NOTIFICATION_MESSAGE);
|
| + notificationMessage.innerText = 'Thumbnail removed.';
|
| + var undoLink = document.getElementById(IDS.UNDO_LINK);
|
| + undoLink.addEventListener('click', onUndo);
|
| + undoLink.innerText = 'Undo';
|
| + var restoreAllLink = document.getElementById(IDS.RESTORE_ALL_LINK);
|
| + restoreAllLink.addEventListener('click', onRestoreAll);
|
| + restoreAllLink.innerText = 'Restore all';
|
| + var notificationCloseButton =
|
| + document.getElementById(IDS.NOTIFICATION_CLOSE_BUTTON);
|
| + notificationCloseButton.addEventListener('click', hideNotification);
|
| +
|
| + window.addEventListener('resize', onResize);
|
| + onResize();
|
| +
|
| + var topLevelHandle = getEmbeddedSearchApiHandle();
|
| + // This is to inform Chrome that the NTP is instant-extended capable i.e.
|
| + // it should fire events like onmostvisitedchange.
|
| + topLevelHandle.searchBox.onsubmit = function() {};
|
| +
|
| + apiHandle = topLevelHandle.newTabPage;
|
| + apiHandle.onthemechange = onThemeChange;
|
| + apiHandle.onmostvisitedchange = onMostVisitedChange;
|
| +
|
| + onThemeChange();
|
| + onMostVisitedChange();
|
| +}
|
| +
|
| +document.addEventListener('DOMContentLoaded', init);
|
| +})();
|
|
|