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

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

Powered by Google App Engine
This is Rietveld 408576698