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

Side by Side 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: Fix compile. 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 (function() {
6 /**
7 * The element used to vertically position the most visited section on
8 * window resize.
9 * @type {Element}
10 */
11 var topMarginElement;
12
13 /**
14 * The container for the tile elements.
15 * @type {Element}
16 */
17 var tilesContainer;
18
19 /**
20 * The notification displayed when a page is blacklisted.
21 * @type {Element}
22 */
23 var notification;
24
25 /**
26 * The array of rendered tiles, ordered by appearance.
27 * @type {Array.<Tile>}
28 */
29 var tiles = [];
30
31 /**
32 * The last blacklisted tile if any, which by definition should not be filler.
33 * @type {?Tile}
34 */
35 var lastBlacklistedTile = null;
36
37 /**
38 * The index of the last blacklisted tile, if any. Used to determine where to
39 * re-insert a tile on undo.
40 * @type {number}
41 */
42 var lastBlacklistedIndex = -1;
43
44 /**
45 * True if a page has been blacklisted and we're waiting on the
46 * onmostvisitedchange callback. See onMostVisitedChange() for how this
47 * is used.
48 * @type {boolean}
49 */
50 var isBlacklisting = false;
51
52 /**
53 * True if a blacklist has been undone and we're waiting on the
54 * onmostvisitedchange callback. See onMostVisitedChange() for how this
55 * is used.
56 * @type {boolean}
57 */
58 var isUndoing = false;
59
60 /**
61 * Current number of tiles shown based on the window width, including filler.
62 * @type {number}
63 */
64 var numTilesShown = 0;
65
66 /**
67 * The browser embeddedSearch.newTabPage object.
68 * @type {Object}
69 */
70 var apiHandle;
71
72 /**
73 * Possible background-colors of a non-custom theme. Used to determine whether
74 * the homepage should be updated to support custom or non-custom themes.
75 * @type {!Array.<string>}
76 * @const
77 */
78 var WHITE = ['rgba(255,255,255,1)', 'rgba(0,0,0,0)'];
79
80 /**
81 * Should be equal to mv-tile's -webkit-margin-start + width.
82 * @type {number}
83 * @const
84 */
85 var TILE_WIDTH = 160;
86
87 /**
88 * The height of the most visited section.
89 * @type {number}
90 * @const
91 */
92 var MOST_VISITED_HEIGHT = 156;
93
94 /** @type {number} @const */
95 var MAX_NUM_TILES_TO_SHOW = 4;
96
97 /** @type {number} @const */
98 var MIN_NUM_TILES_TO_SHOW = 2;
99
100 /**
101 * Minimum total padding to give to the left and right of the most visited
102 * section. Used to determine how many tiles to show.
103 * @type {number}
104 * @const
105 */
106 var MIN_TOTAL_HORIZONTAL_PADDING = 188;
107
108 /**
109 * Enum for classnames.
110 * @enum {string}
111 * @const
112 */
113 var CLASSES = {
114 TILE: 'mv-tile',
115 PAGE: 'mv-page', // page tiles
116 TITLE: 'mv-title',
117 THUMBNAIL: 'mv-thumb',
118 DOMAIN: 'mv-domain',
119 BLACKLIST_BUTTON: 'mv-x',
120 FAVICON: 'mv-favicon',
121 FILLER: 'mv-filler', // filler tiles
122 BLACKLIST: 'mv-blacklist', // triggers tile blacklist animation
123 HIDE_TILE: 'mv-tile-hide', // hides tiles on small browser width
124 HIDE_BLACKLIST_BUTTON: 'mv-x-hide', // hides blacklist button during animation
125 DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide',
126 HIDE_NOTIFICATION: 'mv-notice-hide'
127 };
128
129 /**
130 * Enum for ids.
131 * @enum {string}
132 * @const
133 */
134 var IDS = {
135 TOP_MARGIN: 'mv-top-margin',
136 TILES: 'mv-tiles',
137 NOTIFICATION: 'mv-notice',
138 NOTIFICATION_MESSAGE: 'mv-msg',
139 UNDO_LINK: 'mv-undo',
140 RESTORE_ALL_LINK: 'mv-restore',
141 NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x'
142 };
143
144 /**
145 * A Tile is either a rendering of a Most Visited page or "filler" used to
146 * pad out the section when not enough pages exist.
147 *
148 * @param {Element} elem The element for rendering the tile.
149 * @param {number=} opt_rid The RID for the corresponding Most Visited page.
150 * Should only be left unspecified when creating a filler tile.
151 * @constructor
152 */
153 function Tile(elem, opt_rid) {
154 /** @type {Element} */
155 this.elem = elem;
156
157 /** @type {number|undefined} */
158 this.rid = opt_rid;
159 }
160
161 /**
162 * Updates the NTP based on the current theme.
163 * @private
164 */
165 function onThemeChange() {
166 var info = apiHandle.themeBackgroundInfo;
167 if (!info)
168 return;
169 var background = [info.colorRgba,
170 info.imageUrl,
171 info.imageTiling,
172 info.imageHorizontalAlignment,
173 info.imageVerticalAlignment].join(' ').trim();
174 document.body.style.background = background;
175 var isCustom = !!background && WHITE.indexOf(background) == -1;
176 document.body.classList.toggle('custom-theme', isCustom);
177 }
178
179 /**
180 * Handles a new set of Most Visited page data.
181 */
182 function onMostVisitedChange() {
183 var pages = apiHandle.mostVisited;
184
185 if (isBlacklisting) {
186 // If this was called as a result of a blacklist, add a new replacement
187 // (possibly filler) tile at the end and trigger the blacklist animation.
188 var replacementTile = createTile(pages[MAX_NUM_TILES_TO_SHOW - 1]);
189
190 tiles.push(replacementTile);
191 tilesContainer.appendChild(replacementTile.elem);
192
193 var lastBlacklistedTileElement = lastBlacklistedTile.elem;
194 lastBlacklistedTileElement.addEventListener(
195 'webkitTransitionEnd', blacklistAnimationDone);
196 lastBlacklistedTileElement.classList.add(CLASSES.BLACKLIST);
197 // In order to animate the replacement tile sliding into place, it must
198 // be made visible.
199 updateTileVisibility(numTilesShown + 1);
200
201 } else if (isUndoing) {
202 // If this was called as a result of an undo, re-insert the last blacklisted
203 // tile in its old location and trigger the undo animation.
204 tiles.splice(
205 lastBlacklistedIndex, 0, lastBlacklistedTile);
206 var lastBlacklistedTileElement = lastBlacklistedTile.elem;
207 tilesContainer.insertBefore(
208 lastBlacklistedTileElement,
209 tilesContainer.childNodes[lastBlacklistedIndex]);
210 lastBlacklistedTileElement.addEventListener(
211 'webkitTransitionEnd', undoAnimationDone);
212 // Force the removal to happen synchronously.
213 lastBlacklistedTileElement.scrollTop;
214 lastBlacklistedTileElement.classList.remove(CLASSES.BLACKLIST);
215 } else {
216 // Otherwise render the tiles using the new data without animation.
217 tiles = [];
218 for (var i = 0; i < MAX_NUM_TILES_TO_SHOW; ++i) {
219 tiles.push(createTile(pages[i]));
220 }
221 renderTiles();
222 }
223 }
224
225 /**
226 * Renders the current set of tiles without animation.
227 */
228 function renderTiles() {
229 removeChildren(tilesContainer);
230 for (var i = 0, length = tiles.length; i < length; ++i) {
231 tilesContainer.appendChild(tiles[i].elem);
232 }
233 }
234
235 /**
236 * Creates a Tile with the specified page data. If no data is provided, a
237 * filler Tile is created.
238 * @param {Object} page The page data.
239 * @return {Tile} The new Tile.
240 */
241 function createTile(page) {
242 var tileElement = document.createElement('div');
243 tileElement.classList.add(CLASSES.TILE);
244
245 if (page) {
246 var rid = page.rid;
247 tileElement.classList.add(CLASSES.PAGE);
248
249 // The click handler for navigating to the page identified by the RID.
250 tileElement.addEventListener('click', function() {
251 apiHandle.navigateContentWindow(rid);
252 });
253
254 // The shadow DOM which renders the page title.
255 var titleElement = page.titleElement;
256 if (titleElement) {
257 titleElement.classList.add(CLASSES.TITLE);
258 tileElement.appendChild(titleElement);
259 }
260
261 // Render the thumbnail if present. Otherwise, fall back to a shadow DOM
262 // which renders the domain.
263 var thumbnailUrl = page.thumbnailUrl;
264
265 var showDomainElement = function() {
266 var domainElement = page.domainElement;
267 if (domainElement) {
268 domainElement.classList.add(CLASSES.DOMAIN);
269 tileElement.appendChild(domainElement);
270 }
271 };
272 if (thumbnailUrl) {
273 var image = new Image();
274 image.onload = function() {
275 var thumbnailElement = createAndAppendElement(
276 tileElement, 'div', CLASSES.THUMBNAIL);
277 thumbnailElement.style.backgroundImage = 'url(' + thumbnailUrl + ')';
278 };
279
280 image.onerror = showDomainElement;
281 image.src = thumbnailUrl;
282 } else {
283 showDomainElement();
284 }
285
286 // The button used to blacklist this page.
287 var blacklistButton = createAndAppendElement(
288 tileElement, 'div', CLASSES.BLACKLIST_BUTTON);
289 blacklistButton.addEventListener('click', generateBlacklistFunction(rid));
290 // TODO(jeremycho): i18n. See http://crbug.com/190223.
291 blacklistButton.title = "Don't show on this page";
292
293 // The page favicon, if any.
294 var faviconUrl = page.faviconUrl;
295 if (faviconUrl) {
296 var favicon = createAndAppendElement(
297 tileElement, 'div', CLASSES.FAVICON);
298 favicon.style.backgroundImage = 'url(' + faviconUrl + ')';
299 }
300 return new Tile(tileElement, rid);
301 } else {
302 tileElement.classList.add(CLASSES.FILLER);
303 return new Tile(tileElement);
304 }
305 }
306
307 /**
308 * Generates a function to be called when the page with the corresponding RID
309 * is blacklisted.
310 * @param {number} rid The RID of the page being blacklisted.
311 * @return {function(Event)} A function which handles the blacklisting of the
312 * page by displaying the notification, updating state variables, and
313 * notifying Chrome.
314 */
315 function generateBlacklistFunction(rid) {
316 return function(e) {
317 // Prevent navigation when the page is being blacklisted.
318 e.stopPropagation();
319
320 showNotification();
321 isBlacklisting = true;
322 tilesContainer.classList.add(CLASSES.HIDE_BLACKLIST_BUTTON);
323 lastBlacklistedTile = getTileByRid(rid);
324 lastBlacklistedIndex = tiles.indexOf(lastBlacklistedTile);
325 apiHandle.deleteMostVisitedItem(rid);
326 };
327 }
328
329 /**
330 * Shows the blacklist notification and triggers a delay to hide it.
331 */
332 function showNotification() {
333 notification.classList.remove(CLASSES.HIDE_NOTIFICATION);
334 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION);
335 notification.scrollTop;
336 notification.classList.add(CLASSES.DELAYED_HIDE_NOTIFICATION);
337 }
338
339 /**
340 * Hides the blacklist notification.
341 */
342 function hideNotification() {
343 notification.classList.add(CLASSES.HIDE_NOTIFICATION);
344 }
345
346 /**
347 * Handles the end of the blacklist animation by removing the blacklisted tile.
348 */
349 function blacklistAnimationDone() {
350 tiles.splice(lastBlacklistedIndex, 1);
351 removeNode(lastBlacklistedTile.elem);
352 updateTileVisibility(numTilesShown);
353 isBlacklisting = false;
354 tilesContainer.classList.remove(CLASSES.HIDE_BLACKLIST_BUTTON);
355 lastBlacklistedTile.elem.removeEventListener(
356 'webkitTransitionEnd', blacklistAnimationDone);
357 }
358
359 /**
360 * Handles a click on the notification undo link by hiding the notification and
361 * informing Chrome.
362 */
363 function onUndo() {
364 hideNotification();
365 var lastBlacklistedRID = lastBlacklistedTile.rid;
366 if (typeof lastBlacklistedRID != 'undefined') {
367 isUndoing = true;
368 apiHandle.undoMostVisitedDeletion(lastBlacklistedRID);
369 }
370 }
371
372 /**
373 * Handles the end of the undo animation by removing the extraneous end tile.
374 */
375 function undoAnimationDone() {
376 isUndoing = false;
377 tiles.splice(tiles.length - 1, 1);
378 removeNode(tilesContainer.lastElementChild);
379 updateTileVisibility(numTilesShown);
380 lastBlacklistedTile.elem.removeEventListener(
381 'webkitTransitionEnd', undoAnimationDone);
382 }
383
384 /**
385 * Handles a click on the restore all notification link by hiding the
386 * notification and informing Chrome.
387 */
388 function onRestoreAll() {
389 hideNotification();
390 apiHandle.undoAllMostVisitedDeletions();
391 }
392
393 /**
394 * Handles a resize by vertically centering the most visited section
395 * and triggering the tile show/hide animation if necessary.
396 */
397 function onResize() {
398 var clientHeight = document.documentElement.clientHeight;
399 topMarginElement.style.marginTop =
400 Math.max(0, (clientHeight - MOST_VISITED_HEIGHT) / 2) + 'px';
401
402 var clientWidth = document.documentElement.clientWidth;
403 var numTilesToShow = Math.floor(
404 (clientWidth - MIN_TOTAL_HORIZONTAL_PADDING) / TILE_WIDTH);
405 numTilesToShow = Math.max(MIN_NUM_TILES_TO_SHOW, numTilesToShow);
406 if (numTilesToShow != numTilesShown) {
407 updateTileVisibility(numTilesToShow);
408 numTilesShown = numTilesToShow;
409 }
410 }
411
412 /**
413 * Triggers an animation to show the first numTilesToShow tiles and hide the
414 * remaining.
415 * @param {number} numTilesToShow The number of tiles to show.
416 */
417 function updateTileVisibility(numTilesToShow) {
418 for (var i = 0, length = tiles.length; i < length; ++i) {
419 tiles[i].elem.classList.toggle(CLASSES.HIDE_TILE, i >= numTilesToShow);
420 }
421 }
422
423 /**
424 * Returns the tile corresponding to the specified page RID.
425 * @param {number} rid The page RID being looked up.
426 * @return {Tile} The corresponding tile.
427 */
428 function getTileByRid(rid) {
429 for (var i = 0, length = tiles.length; i < length; ++i) {
430 var tile = tiles[i];
431 if (tile.rid == rid)
432 return tile;
433 }
434 return null;
435 }
436
437 /**
438 * Utility function which creates an element with an optional classname and
439 * appends it to the specified parent.
440 * @param {Element} parent The parent to append the new element.
441 * @param {string} name The name of the new element.
442 * @param {string=} opt_class The optional classname of the new element.
443 * @return {Element} The new element.
444 */
445 function createAndAppendElement(parent, name, opt_class) {
446 var child = document.createElement(name);
447 if (opt_class)
448 child.classList.add(opt_class);
449 parent.appendChild(child);
450 return child;
451 }
452
453 /**
454 * Removes a node from its parent.
455 * @param {Node} node The node to remove.
456 */
457 function removeNode(node) {
458 node.parentNode.removeChild(node);
459 }
460
461 /**
462 * Removes all the child nodes on a DOM node.
463 * @param {Node} node Node to remove children from.
464 */
465 function removeChildren(node) {
466 node.innerHTML = '';
467 }
468
469 /**
470 * @return {Object} the handle to the embeddedSearch API.
471 */
472 function getEmbeddedSearchApiHandle() {
473 if (window.cideb)
474 return window.cideb;
475 if (window.chrome && window.chrome.embeddedSearch)
476 return window.chrome.embeddedSearch;
477 return null;
478 }
479
480 /**
481 * Prepares the New Tab Page by adding listeners, rendering the current
482 * theme, and the most visited pages section.
483 */
484 function init() {
485 topMarginElement = document.getElementById(IDS.TOP_MARGIN);
486 tilesContainer = document.getElementById(IDS.TILES);
487 notification = document.getElementById(IDS.NOTIFICATION);
488
489 // TODO(jeremycho): i18n.
490 var notificationMessage = document.getElementById(IDS.NOTIFICATION_MESSAGE);
491 notificationMessage.innerText = 'Thumbnail removed.';
492 var undoLink = document.getElementById(IDS.UNDO_LINK);
493 undoLink.addEventListener('click', onUndo);
494 undoLink.innerText = 'Undo';
495 var restoreAllLink = document.getElementById(IDS.RESTORE_ALL_LINK);
496 restoreAllLink.addEventListener('click', onRestoreAll);
497 restoreAllLink.innerText = 'Restore all';
498 var notificationCloseButton =
499 document.getElementById(IDS.NOTIFICATION_CLOSE_BUTTON);
500 notificationCloseButton.addEventListener('click', hideNotification);
501
502 window.addEventListener('resize', onResize);
503 onResize();
504
505 var topLevelHandle = getEmbeddedSearchApiHandle();
506 // This is to inform Chrome that the NTP is instant-extended capable i.e.
507 // it should fire events like onmostvisitedchange.
508 topLevelHandle.searchBox.onsubmit = function() {};
509
510 apiHandle = topLevelHandle.newTabPage;
511 apiHandle.onthemechange = onThemeChange;
512 apiHandle.onmostvisitedchange = onMostVisitedChange;
513
514 onThemeChange();
515 onMostVisitedChange();
516 }
517
518 document.addEventListener('DOMContentLoaded', init);
519 })();
OLDNEW
« no previous file with comments | « chrome/browser/resources/local_ntp/local_ntp.html ('k') | chrome/browser/search/instant_service.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698