Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(26)

Unified Diff: chrome/browser/resources/local_ntp/local_ntp.js

Issue 12840003: Implement local NTP for fallback. (Closed) Base URL: https://git.chromium.org/chromium/src.git@master
Patch Set: Respond to Samarth's comments. Created 7 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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..3f6d2d31fe424e470348bc7f356d3faadf9df5ad
--- /dev/null
+++ b/chrome/browser/resources/local_ntp/local_ntp.js
@@ -0,0 +1,583 @@
+// 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.
+
+/**
+ * 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 handle for the timer used to hide the notification.
+ * @type {?number}
+ */
+var notificationTimer = null;
+
+/**
+ * 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
Dan Beam 2013/03/21 02:45:08 1 \s between sentences in comments.
jeremycho 2013/03/21 05:28:42 Done.
+ * 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)'];
Dan Beam 2013/03/21 02:45:08 uh, the second color here is transparent black?
jeremycho 2013/03/21 05:28:42 This gets passed on occasion when a default theme
+
+/**
+ * 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}
Dan Beam 2013/03/21 02:45:08 @const
jeremycho 2013/03/21 05:28:42 Done.
+ */
+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
Dan Beam 2013/03/21 02:45:08 @const should be on next line if the comment is mu
jeremycho 2013/03/21 05:28:42 Done.
+ */
+var MIN_TOTAL_HORIZONTAL_PADDING = 188;
+
+/** @type {string} @const */
+var TILE_CLASS = 'mv-tile';
+
+/**
+ * Class applied to page tiles.
+ * @type {string} @const
+ */
+var PAGE_CLASS = 'mv-page';
+
+/**
+ * Class applied to a page tile's title.
+ * @type {string} @const
+ */
+var TITLE_CLASS = 'mv-title';
+
+/**
+ * Class applied to a page tile's thumbnail.
+ * @type {string} @const
+ */
+var THUMBNAIL_CLASS = 'mv-thumb';
+
+/**
+ * Class applied to a page tile's domain.
+ * @type {string} @const
+ */
+var DOMAIN_CLASS = 'mv-domain';
+
+/**
+ * Class applied to a page tile's blacklist button.
+ * @type {string} @const
+ */
+var BLACKLIST_BUTTON_CLASS = 'mv-x';
+
+/**
+ * Class applied to a page tile's favicon.
+ * @type {string} @const
+ */
+var FAVICON_CLASS = 'mv-fav';
+
+/**
+ * Class applied to filler tiles.
+ * @type {string} @const
+ */
+var FILLER_CLASS = 'mv-filler';
+
+/**
+ * Class applied to tiles to trigger the blacklist animation.
+ * @type {string} @const
+ */
+var BLACKLIST_CLASS = 'mv-bl';
+
+/**
+ * Class applied to tiles to hide them on small browser width.
+ * @type {string} @const
+ */
+var HIDE_TILE_CLASS = 'mv-tile-hide';
+
+/**
+ * Class applied to hide the blacklist buttons during the blacklist animation.
+ * @type {string} @const
+ */
+var HIDE_BLACKLIST_BUTTON_CLASS = 'mv-x-hide';
+
+/**
+ * Class applied to the notification to hide it.
+ * @type {string} @const
+ */
+var HIDE_NOTIFICATION_CLASS = 'mv-noti-hide';
Dan Beam 2013/03/21 02:45:08 var CLASSES = { HIDE_NOTIFICATION: 'mv-noti-hide
jeremycho 2013/03/21 05:28:42 Done.
+
+/**
+ * Time (in milliseconds) to show the notification.
+ * @type {number} @const
+ */
+var NOTIFICATION_TIMEOUT = 10000;
+
+/**
+ * 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
+ */
+Tile = function(elem, opt_rid) {
Dan Beam 2013/03/21 02:45:08 s/Tile = function(/function Tile(/
jeremycho 2013/03/21 05:28:42 Done.
+ /** @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;
+ enable(document.body, 'custom-theme', isCustom);
+}
+
+/**
+ * Handles a new set of Most Visited page data.
+ */
+function onMostVisitedChange() {
+ var pages = apiHandle.mostVisited;
+
+ // 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.
+ if (isBlacklisting) {
+ 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(BLACKLIST_CLASS);
+ // In order to animate the replacement tile sliding into place, it must
+ // be made visible.
+ updateTileVisibility(numTilesShown + 1);
+
+ // 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.
+ } else if (isUndoing) {
+ tiles.splice(
+ lastBlacklistedIndex, 0, lastBlacklistedTile);
+ var lastBlacklistedTileElement = lastBlacklistedTile.elem;
+ tilesContainer.insertBefore(
+ lastBlacklistedTileElement,
+ tilesContainer.childNodes[lastBlacklistedIndex]);
+ lastBlacklistedTileElement.addEventListener(
+ 'webkitTransitionEnd', undoAnimationDone);
+ // Yield to ensure the tile is added to the DOM before removing the class.
+ // Without this, the webkit transition doesn't reliably trigger.
+ window.setTimeout(function() {
+ lastBlacklistedTileElement.classList.remove(BLACKLIST_CLASS);
+ }, 0);
+ // Otherwise render the tiles using the new data without animation.
+ } else {
+ 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(TILE_CLASS);
+
+ if (page) {
+ var rid = page.rid;
+ tileElement.classList.add(PAGE_CLASS);
+
+ // 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(TITLE_CLASS);
+ 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(DOMAIN_CLASS);
+ tileElement.appendChild(domainElement);
+ }
+ };
+ if (thumbnailUrl) {
+ var image = new Image();
+ image.onload = function() {
+ var thumbnailElement = createAndAppendElement(
+ tileElement, 'div', THUMBNAIL_CLASS);
+ thumbnailElement.style['background-image'] =
+ 'url(' + thumbnailUrl + ')';
+ };
+
+ image.onerror = showDomainElement;
+ image.src = thumbnailUrl;
+ } else {
+ showDomainElement();
+ }
+
+ // The button used to blacklist this page.
+ var blacklistButton = createAndAppendElement(
+ tileElement, 'div', BLACKLIST_BUTTON_CLASS);
+ blacklistButton.addEventListener('click', generateBlacklistFunction(rid));
+ // TODO(jeremycho): i18n.
+ blacklistButton.title = "Don't show on this page";
+
+ // The page favicon, if any.
+ var faviconUrl = page.faviconUrl;
+ if (faviconUrl) {
+ var favicon = createAndAppendElement(
+ tileElement, 'div', FAVICON_CLASS);
+ favicon.style['background-image'] = 'url(' + faviconUrl + ')';
+ }
+ return new Tile(tileElement, rid);
+ } else {
+ tileElement.classList.add(FILLER_CLASS);
+ 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(HIDE_BLACKLIST_BUTTON_CLASS);
+ lastBlacklistedTile = getTileByRid(rid);
+ lastBlacklistedIndex = tiles.indexOf(lastBlacklistedTile);
+ apiHandle.deleteMostVisitedItem(rid);
+ };
+}
+
+/**
+ * Shows the blacklist notification and refreshes the timer to hide it.
+ */
+function showNotification() {
+ notification.classList.remove(HIDE_NOTIFICATION_CLASS);
+ if (notificationTimer)
+ window.clearTimeout(notificationTimer);
+ notificationTimer = window.setTimeout(
+ hideNotification, NOTIFICATION_TIMEOUT);
+}
+
+
+/**
+ * Hides the blacklist notification.
+ */
+function hideNotification() {
+ notification.classList.add(HIDE_NOTIFICATION_CLASS);
+}
+
+/**
+ * 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(HIDE_BLACKLIST_BUTTON_CLASS);
+ 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 (lastBlacklistedRID != null) {
+ 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['margin-top'] =
+ 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) {
+ enable(tiles[i].elem, HIDE_TILE_CLASS, 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 && node.parentNode && node.parentNode.removeChild(node);
+}
+
+/**
+ * Removes all the child nodes on a DOM node.
+ * @param {Node} node Node to remove children from.
+ */
+function removeChildren(node) {
+ var child;
+ while ((child = node.firstChild)) {
+ node.removeChild(child);
+ }
+}
+
+/**
+ * Adds or removes a class depending on the enabled argument.
+ * @param {Element} element DOM node to add or remove the class on.
+ * @param {string} className Class name to add or remove.
+ * @param {boolean} enabled Whether to add or remove the class (true adds,
+ * false removes).
+ */
+function enable(element, className, enabled) {
+ if (enabled)
+ element.classList.add(className);
+ else
+ element.classList.remove(className);
+}
+
+/**
+ * @return {Object} the handle to the embeddedSearch API.
+ */
+function getEmbeddedSearchApiHandle() {
+ if (window.cideb)
+ return window.cideb;
+ if (window.navigator && window.navigator.embeddedSearch)
+ return window.navigator.embeddedSearch;
+ 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('top-margin');
+ tilesContainer = document.getElementById('mv-tiles');
+ notification = document.getElementById('mv-noti');
+
+ // TODO(jeremycho): i18n.
+ var notificationMessage = document.getElementById('mv-msg');
+ notificationMessage.innerText = 'Thumbnail removed.';
+ var undoLink = document.getElementById('mv-undo');
+ undoLink.addEventListener('click', onUndo);
+ undoLink.innerText = 'Undo';
+ var restoreAllLink = document.getElementById('mv-restore');
+ restoreAllLink.addEventListener('click', onRestoreAll);
+ restoreAllLink.innerText = 'Restore all';
+ var notificationCloseButton = document.getElementById('mv-noti-x');
+ notificationCloseButton.addEventListener('click', hideNotification);
+
+ window.addEventListener('resize', onResize);
+ onResize();
+
+ var topLevelHandle = getEmbeddedSearchApiHandle();
+ // This is to inform Chrome that the NTP is instant-extended capable.
+ topLevelHandle.searchBox.onsubmit = function() {};
+
+ apiHandle = topLevelHandle.newTabPage;
+ apiHandle.onthemechange = onThemeChange;
+ apiHandle.onmostvisitedchange = onMostVisitedChange;
+
+ onThemeChange();
+ onMostVisitedChange();
+}
+
+document.addEventListener('DOMContentLoaded', init);

Powered by Google App Engine
This is Rietveld 408576698