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

Side by Side Diff: chrome/browser/resources/local_ntp/local_ntp_fast.js

Issue 997223003: New fast NTP that uses a single iframe (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 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
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 5
6 /** 6 /**
7 * @fileoverview The local InstantExtended NTP. 7 * @fileoverview The local InstantExtended NTP.
8 */ 8 */
9 9
10 10
11 /** 11 /**
12 * Controls rendering the new tab page for InstantExtended. 12 * Controls rendering the new tab page for InstantExtended.
13 * @return {Object} A limited interface for testing the local NTP. 13 * @return {Object} A limited interface for testing the local NTP.
14 */ 14 */
15 window.performance.mark('page.begin');
16
15 function LocalNTP() { 17 function LocalNTP() {
16 <include src="../../../../ui/webui/resources/js/assert.js">
17 <include src="local_ntp_design.js">
18 <include src="local_ntp_util.js">
19 <include src="window_disposition_util.js">
20 18
19 <include src="../../../../ui/webui/resources/js/util.js">
20 <include src='local_ntp_design.js'>
21
22 var timedLoadCount = 0;
23
24 var timedLoad = function() {
25 timedLoadCount++;
26 if (timedLoadCount == 16) {
27 window.performance.mark('page.end');
28 window.performance.measure('page', 'page.begin', 'page.end');
29 var m = window.performance.getEntriesByName('page');
30 console.log('Full page render: ' + m[0].duration);
31 }
32 };
21 33
22 /** 34 /**
23 * Enum for classnames. 35 * Enum for classnames.
24 * @enum {string} 36 * @enum {string}
25 * @const 37 * @const
26 */ 38 */
27 var CLASSES = { 39 var CLASSES = {
28 ALTERNATE_LOGO: 'alternate-logo', // Shows white logo if required by theme 40 ALTERNATE_LOGO: 'alternate-logo', // Shows white logo if required by theme
29 BLACKLIST: 'mv-blacklist', // triggers tile blacklist animation
30 BLACKLIST_BUTTON: 'mv-x',
31 BLACKLIST_BUTTON_INNER: 'mv-x-inner',
32 DARK: 'dark', 41 DARK: 'dark',
33 DEFAULT_THEME: 'default-theme', 42 DEFAULT_THEME: 'default-theme',
34 DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide', 43 DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide',
35 DOT: 'dot', 44 DOT: 'dot',
36 FAKEBOX_DISABLE: 'fakebox-disable', // Makes fakebox non-interactive 45 FAKEBOX_DISABLE: 'fakebox-disable', // Makes fakebox non-interactive
37 FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox 46 FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox
38 // Applies drag focus style to the fakebox 47 // Applies drag focus style to the fakebox
39 FAKEBOX_DRAG_FOCUS: 'fakebox-drag-focused', 48 FAKEBOX_DRAG_FOCUS: 'fakebox-drag-focused',
40 FAVICON: 'mv-favicon',
41 FAVICON_FALLBACK: 'mv-favicon-fallback',
42 FOCUSED: 'mv-focused',
43 HIDE_BLACKLIST_BUTTON: 'mv-x-hide', // hides blacklist button during animation
44 HIDE_FAKEBOX_AND_LOGO: 'hide-fakebox-logo', 49 HIDE_FAKEBOX_AND_LOGO: 'hide-fakebox-logo',
45 HIDE_NOTIFICATION: 'mv-notice-hide', 50 HIDE_NOTIFICATION: 'mv-notice-hide',
46 // Vertically centers the most visited section for a non-Google provided page. 51 // Vertically centers the most visited section for a non-Google provided page.
47 NON_GOOGLE_PAGE: 'non-google-page', 52 NON_GOOGLE_PAGE: 'non-google-page',
48 PAGE: 'mv-page', // page tiles
49 PAGE_READY: 'mv-page-ready', // page tile when ready
50 RTL: 'rtl', // Right-to-left language text. 53 RTL: 'rtl', // Right-to-left language text.
51 THUMBNAIL: 'mv-thumb',
52 THUMBNAIL_FALLBACK: 'mv-thumb-fallback',
53 THUMBNAIL_MASK: 'mv-mask',
54 TILE: 'mv-tile',
55 TILE_INNER: 'mv-tile-inner',
56 TITLE: 'mv-title'
57 }; 54 };
58 55
59 56
60 /** 57 /**
61 * Enum for HTML element ids. 58 * Enum for HTML element ids.
62 * @enum {string} 59 * @enum {string}
63 * @const 60 * @const
64 */ 61 */
65 var IDS = { 62 var IDS = {
66 ATTRIBUTION: 'attribution', 63 ATTRIBUTION: 'attribution',
67 ATTRIBUTION_TEXT: 'attribution-text', 64 ATTRIBUTION_TEXT: 'attribution-text',
68 CUSTOM_THEME_STYLE: 'ct-style', 65 CUSTOM_THEME_STYLE: 'ct-style',
69 FAKEBOX: 'fakebox', 66 FAKEBOX: 'fakebox',
70 FAKEBOX_INPUT: 'fakebox-input', 67 FAKEBOX_INPUT: 'fakebox-input',
71 FAKEBOX_TEXT: 'fakebox-text', 68 FAKEBOX_TEXT: 'fakebox-text',
72 LOGO: 'logo', 69 LOGO: 'logo',
73 NOTIFICATION: 'mv-notice', 70 NOTIFICATION: 'mv-notice',
74 NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x', 71 NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x',
75 NOTIFICATION_MESSAGE: 'mv-msg', 72 NOTIFICATION_MESSAGE: 'mv-msg',
76 NTP_CONTENTS: 'ntp-contents', 73 NTP_CONTENTS: 'ntp-contents',
77 RESTORE_ALL_LINK: 'mv-restore', 74 RESTORE_ALL_LINK: 'mv-restore',
78 TILES: 'mv-tiles',
79 UNDO_LINK: 'mv-undo' 75 UNDO_LINK: 'mv-undo'
huangs 2015/03/12 06:29:19 Keep TILES so you can do $(IDS.TILES) instead of
fserb 2015/03/12 17:06:55 Done.
80 }; 76 };
81 77
82 78
83 /** 79 /**
84 * Enum for keycodes. 80 * Enum for keycodes.
85 * @enum {number} 81 * @enum {number}
86 * @const 82 * @const
87 */ 83 */
88 var KEYCODE = { 84 var KEYCODE = {
89 ENTER: 13 85 ENTER: 13
(...skipping 14 matching lines...) Expand all
104 100
105 /** 101 /**
106 * The JavaScript button event value for a middle click. 102 * The JavaScript button event value for a middle click.
107 * @type {number} 103 * @type {number}
108 * @const 104 * @const
109 */ 105 */
110 var MIDDLE_MOUSE_BUTTON = 1; 106 var MIDDLE_MOUSE_BUTTON = 1;
111 107
112 108
113 /** 109 /**
114 * The container for the tile elements.
115 * @type {Element}
116 */
117 var tilesContainer;
118
119
120 /**
121 * The notification displayed when a page is blacklisted. 110 * The notification displayed when a page is blacklisted.
122 * @type {Element} 111 * @type {Element}
123 */ 112 */
124 var notification; 113 var notification;
125 114
126 115
127 /** 116 /**
128 * The container for the theme attribution. 117 * The container for the theme attribution.
129 * @type {Element} 118 * @type {Element}
130 */ 119 */
131 var attribution; 120 var attribution;
132 121
133 122
134 /** 123 /**
135 * The "fakebox" - an input field that looks like a regular searchbox. When it 124 * The "fakebox" - an input field that looks like a regular searchbox. When it
136 * is focused, any text the user types goes directly into the omnibox. 125 * is focused, any text the user types goes directly into the omnibox.
137 * @type {Element} 126 * @type {Element}
138 */ 127 */
139 var fakebox; 128 var fakebox;
140 129
141 130
142 /** 131 /**
143 * The container for NTP elements. 132 * The container for NTP elements.
144 * @type {Element} 133 * @type {Element}
145 */ 134 */
146 var ntpContents; 135 var ntpContents;
147 136
148 137
149 /** 138 /**
150 * The array of rendered tiles, ordered by appearance. 139 * The last blacklisted tile rid if any, which by definition should not be
151 * @type {!Array<Tile>} 140 * filler.
152 */ 141 * @type {?number}
153 var tiles = [];
154
155
156 /**
157 * The last blacklisted tile if any, which by definition should not be filler.
158 * @type {?Tile}
159 */ 142 */
160 var lastBlacklistedTile = null; 143 var lastBlacklistedTile = null;
161 144
162 145
163 /** 146 /**
164 * The iframe element which is currently keyboard focused, or null.
165 * @type {?Element}
166 */
167 var focusedIframe = null;
168
169
170 /**
171 * True if a page has been blacklisted and we're waiting on the
172 * onmostvisitedchange callback. See renderAllTiles() for how this is used.
173 * @type {boolean}
174 */
175 var isBlacklisting = false;
176
177
178 /**
179 * Current number of tiles columns shown based on the window width, including 147 * Current number of tiles columns shown based on the window width, including
180 * those that just contain filler. 148 * those that just contain filler.
181 * @type {number} 149 * @type {number}
182 */ 150 */
183 var numColumnsShown = 0; 151 var numColumnsShown = 0;
184 152
185 153
186 /** 154 /**
187 * A flag to indicate Most Visited changed caused by user action. If true, then
188 * in renderAllTiles() tiles remain visible so no flickering occurs.
189 * @type {boolean}
190 */
191 var userInitiatedMostVisitedChange = false;
192
193
194 /**
195 * The browser embeddedSearch.newTabPage object. 155 * The browser embeddedSearch.newTabPage object.
196 * @type {Object} 156 * @type {Object}
197 */ 157 */
198 var ntpApiHandle; 158 var ntpApiHandle;
199 159
200 160
201 /** 161 /**
202 * The browser embeddedSearch.searchBox object. 162 * The browser embeddedSearch.searchBox object.
203 * @type {Object} 163 * @type {Object}
204 */ 164 */
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
238 /** 198 /**
239 * Minimum total padding to give to the left and right of the most visited 199 * Minimum total padding to give to the left and right of the most visited
240 * section. Used to determine how many tiles to show. 200 * section. Used to determine how many tiles to show.
241 * @type {number} 201 * @type {number}
242 * @const 202 * @const
243 */ 203 */
244 var MIN_TOTAL_HORIZONTAL_PADDING = 200; 204 var MIN_TOTAL_HORIZONTAL_PADDING = 200;
245 205
246 206
247 /** 207 /**
248 * The filename for a most visited iframe src which shows a page title.
249 * @type {string}
250 * @const
251 */
252 var MOST_VISITED_TITLE_IFRAME = 'title.html';
253
254
255 /**
256 * The filename for a most visited iframe src which shows a thumbnail image.
257 * @type {string}
258 * @const
259 */
260 var MOST_VISITED_THUMBNAIL_IFRAME = 'thumbnail.html';
261
262
263 /**
264 * The color of the title in RRGGBBAA format. 208 * The color of the title in RRGGBBAA format.
265 * @type {?string} 209 * @type {?string}
266 */ 210 */
267 var titleColor = null; 211 var titleColor = null;
268 212
269 213
270 /** 214 /**
271 * Hide most visited tiles for at most this many milliseconds while painting.
272 * @type {number}
273 * @const
274 */
275 var MOST_VISITED_PAINT_TIMEOUT_MSEC = 500;
276
277
278 /**
279 * A Tile is either a rendering of a Most Visited page or "filler" used to
280 * pad out the section when not enough pages exist.
281 *
282 * @param {Element} elem The element for rendering the tile.
283 * @param {Element=} opt_innerElem The element for contents of tile.
284 * @param {Element=} opt_titleElem The element for rendering the title.
285 * @param {Element=} opt_thumbnailElem The element for rendering the thumbnail.
286 * @param {number=} opt_rid The RID for the corresponding Most Visited page.
287 * Should only be left unspecified when creating a filler tile.
288 * @constructor
289 */
290 function Tile(elem, opt_innerElem, opt_titleElem, opt_thumbnailElem, opt_rid) {
291 /** @type {Element} */
292 this.elem = elem;
293
294 /** @type {Element|undefined} */
295 this.innerElem = opt_innerElem;
296
297 /** @type {Element|undefined} */
298 this.titleElem = opt_titleElem;
299
300 /** @type {Element|undefined} */
301 this.thumbnailElem = opt_thumbnailElem;
302
303 /** @type {number|undefined} */
304 this.rid = opt_rid;
305 }
306
307
308 /**
309 * Heuristic to determine whether a theme should be considered to be dark, so 215 * Heuristic to determine whether a theme should be considered to be dark, so
310 * the colors of various UI elements can be adjusted. 216 * the colors of various UI elements can be adjusted.
311 * @param {ThemeBackgroundInfo|undefined} info Theme background information. 217 * @param {ThemeBackgroundInfo|undefined} info Theme background information.
312 * @return {boolean} Whether the theme is dark. 218 * @return {boolean} Whether the theme is dark.
313 * @private 219 * @private
314 */ 220 */
315 function getIsThemeDark(info) { 221 function getIsThemeDark(info) {
316 if (!info) 222 if (!info)
317 return false; 223 return false;
318 // Heuristic: light text implies dark theme. 224 // Heuristic: light text implies dark theme.
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
363 setCustomThemeStyle(info); 269 setCustomThemeStyle(info);
364 } 270 }
365 271
366 272
367 /** 273 /**
368 * Updates the NTP based on the current theme, then rerenders all tiles. 274 * Updates the NTP based on the current theme, then rerenders all tiles.
369 * @private 275 * @private
370 */ 276 */
371 function onThemeChange() { 277 function onThemeChange() {
372 renderTheme(); 278 renderTheme();
373 tilesContainer.innerHTML = '';
374 renderAllTiles();
375 } 279 }
376 280
377 281
378 /** 282 /**
379 * Updates the NTP style according to theme. 283 * Updates the NTP style according to theme.
380 * @param {Object=} opt_themeInfo The information about the theme. If it is 284 * @param {Object=} opt_themeInfo The information about the theme. If it is
381 * omitted the style will be reverted to the default. 285 * omitted the style will be reverted to the default.
382 * @private 286 * @private
383 */ 287 */
384 function setCustomThemeStyle(opt_themeInfo) { 288 function setCustomThemeStyle(opt_themeInfo) {
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after
482 function convertToRGBAColor(color) { 386 function convertToRGBAColor(color) {
483 return 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' + 387 return 'rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' +
484 color[3] / 255 + ')'; 388 color[3] / 255 + ')';
485 } 389 }
486 390
487 391
488 /** 392 /**
489 * Called when page data change. 393 * Called when page data change.
490 */ 394 */
491 function onMostVisitedChange() { 395 function onMostVisitedChange() {
492 renderAllTiles(); 396 reloadTiles();
493 } 397 }
494 398
495 399
496 /** 400 /**
huangs 2015/03/12 06:29:19 Extra /**
fserb 2015/03/12 17:06:55 Done.
497 * Rerenders all tiles based on Most Visited page data. 401 /**
402 * Fetches new data, creates, and renders tiles.
498 */ 403 */
499 function renderAllTiles() { 404 function reloadTiles() {
500 if (isBlacklisting) { 405 var pages = ntpApiHandle.mostVisited;
501 // Trigger the blacklist animation, which then triggers reloadAllTiles(). 406 var iframe = $('mv-single').contentWindow;
502 var lastBlacklistedTileElem = lastBlacklistedTile.elem; 407
503 lastBlacklistedTileElem.addEventListener( 408 tiles = [];
504 'webkitTransitionEnd', blacklistAnimationDone); 409 for (var i = 0; i < Math.min(8, pages.length); ++i) {
505 lastBlacklistedTileElem.classList.add(CLASSES.BLACKLIST); 410 iframe.postMessage({cmd: 'tile', rid: pages[i].rid}, '*');
506 } else {
507 reloadAllTiles();
508 } 411 }
412 iframe.postMessage({cmd: 'show'}, '*');
509 } 413 }
510 414
511 415
512 /**
513 * Handles the end of the blacklist animation by showing the notification and
514 * re-rendering the new set of tiles.
515 */
516 function blacklistAnimationDone() {
517 showNotification();
518 isBlacklisting = false;
519 tilesContainer.classList.remove(CLASSES.HIDE_BLACKLIST_BUTTON);
520 lastBlacklistedTile.elem.removeEventListener(
521 'webkitTransitionEnd', blacklistAnimationDone);
522 // Need to call explicitly to re-render the tiles, since the initial
523 // renderAllTiles() issued by the blacklist function only triggered the
524 // animation.
525 reloadAllTiles();
526 }
527
528
529 /**
530 * Fetches new data, creates, and renders tiles.
531 */
532 function reloadAllTiles() {
533 var pages = ntpApiHandle.mostVisited;
534
535 tiles = [];
536 for (var i = 0; i < MAX_NUM_TILES_TO_SHOW; ++i)
537 tiles.push(createTile(pages[i], i));
538
539 tilesContainer.innerHTML = '';
540 renderAndShowTiles();
541 }
542
543
544 /**
545 * Binds onload events for a tile's internal iframe elements.
546 * @param {Tile} tile The main tile to bind events to.
547 * @param {Barrier} tileVisibilityBarrier A barrier to make all tiles visible
548 * the moment all tiles are loaded.
549 */
550 function bindTileOnloadEvents(tile, tileVisibilityBarrier) {
551 if (tile.titleElem) {
552 tileVisibilityBarrier.add();
553 tile.titleElem.onload = function() {
554 tileVisibilityBarrier.remove();
555 };
556 }
557 if (tile.thumbnailElem) {
558 tileVisibilityBarrier.add();
559 tile.thumbnailElem.onload = function() {
560 tile.elem.classList.add(CLASSES.PAGE_READY);
561 tileVisibilityBarrier.remove();
562 };
563 }
564 }
565
566
567 /**
568 * Renders the current list of visible tiles to DOM, and hides tiles that are
569 * already in the DOM but should not be seen.
570 */
571 function renderAndShowTiles() {
572 var numExisting = tilesContainer.querySelectorAll('.' + CLASSES.TILE).length;
573 // Only add visible tiles to the DOM, to avoid creating invisible tiles that
574 // produce meaningless impression metrics. However, if a tile becomes
575 // invisible then we leave it in DOM to prevent reload if it's shown again.
576 var numDesired = Math.min(tiles.length, numColumnsShown * NUM_ROWS);
577
578 // If we need to render new tiles, manage the visibility to hide intermediate
579 // load states of the iframes.
580 if (numExisting < numDesired) {
581 var showAll = function() {
582 for (var i = 0; i < numDesired; ++i) {
583 if (tiles[i].titleElem || tiles[i].thumbnailElem)
584 tiles[i].elem.classList.add(CLASSES.PAGE_READY);
585 }
586 };
587 var tileVisibilityBarrier = new Barrier(showAll);
588
589 if (!userInitiatedMostVisitedChange) {
590 // Make titleContainer invisible, but still taking up space.
591 // titleContainer becomes visible again (1) on timeout, or (2) when all
592 // tiles finish loading (using tileVisibilityBarrier).
593 window.setTimeout(function() {
594 tileVisibilityBarrier.cancel();
595 showAll();
596 }, MOST_VISITED_PAINT_TIMEOUT_MSEC);
597 }
598 userInitiatedMostVisitedChange = false;
599
600 for (var i = numExisting; i < numDesired; ++i) {
601 bindTileOnloadEvents(tiles[i], tileVisibilityBarrier);
602 tilesContainer.appendChild(tiles[i].elem);
603 }
604 }
605
606 // Show only the desired tiles. Note that .hidden does not work for
607 // inline-block elements like tiles[i].elem.
608 for (var i = 0; i < numDesired; ++i)
609 tiles[i].elem.style.display = 'inline-block';
610 // If |numDesired| < |numExisting| then hide extra tiles (e.g., this occurs
611 // when window is downsized).
612 for (; i < numExisting; ++i)
613 tiles[i].elem.style.display = 'none';
614 }
615
616
617 /**
618 * Builds a URL to display a most visited tile title in an iframe.
619 * @param {number} rid The restricted ID.
620 * @param {number} position The position of the iframe in the UI.
621 * @return {string} An URL to display the most visited title in an iframe.
622 */
623 function getMostVisitedTitleIframeUrl(rid, position) {
624 var url = 'chrome-search://most-visited/' +
625 encodeURIComponent(MOST_VISITED_TITLE_IFRAME);
626 var params = [
627 'rid=' + encodeURIComponent(rid),
628 'f=' + encodeURIComponent(NTP_DESIGN.fontFamily),
629 'fs=' + encodeURIComponent(NTP_DESIGN.fontSize),
630 'c=' + encodeURIComponent(titleColor),
631 'pos=' + encodeURIComponent(position)];
632 if (NTP_DESIGN.titleTextAlign)
633 params.push('ta=' + encodeURIComponent(NTP_DESIGN.titleTextAlign));
634 if (NTP_DESIGN.titleTextFade)
635 params.push('tf=' + encodeURIComponent(NTP_DESIGN.titleTextFade));
636 return url + '?' + params.join('&');
637 }
638
639
640 /**
641 * Builds a URL to display a most visited tile thumbnail in an iframe.
642 * @param {number} rid The restricted ID.
643 * @param {number} position The position of the iframe in the UI.
644 * @return {string} An URL to display the most visited thumbnail in an iframe.
645 */
646 function getMostVisitedThumbnailIframeUrl(rid, position) {
647 var url = 'chrome-search://most-visited/' +
648 encodeURIComponent(MOST_VISITED_THUMBNAIL_IFRAME);
649 var params = [
650 'rid=' + encodeURIComponent(rid),
651 'f=' + encodeURIComponent(NTP_DESIGN.fontFamily),
652 'fs=' + encodeURIComponent(NTP_DESIGN.fontSize),
653 'c=' + encodeURIComponent(NTP_DESIGN.thumbnailTextColor),
654 'pos=' + encodeURIComponent(position)];
655 if (NTP_DESIGN.thumbnailFallback)
656 params.push('etfb=1');
657 return url + '?' + params.join('&');
658 }
659
660
661 /**
662 * Creates a Tile with the specified page data. If no data is provided, a
663 * filler Tile is created.
664 * @param {?Object} page The page data.
665 * @param {number} position The position of the tile.
666 * @return {Tile} The new Tile.
667 */
668 function createTile(page, position) {
669 var tileElem = document.createElement('div');
670 tileElem.classList.add(CLASSES.TILE);
671 // Prevent tile from being selected (and highlighted) when areas outside the
672 // iframes are clicked.
673 tileElem.addEventListener('mousedown', function(e) {
674 e.preventDefault();
675 });
676
677 if (!page) {
678 return new Tile(tileElem);
679 }
680
681 var rid = page.rid;
682 tileElem.classList.add(CLASSES.PAGE);
683
684 var navigateFunction = function(e) {
685 e.preventDefault();
686 ntpApiHandle.navigateContentWindow(rid, getDispositionFromEvent(e));
687 };
688
689 // The click handler for navigating to the page identified by the RID.
690 tileElem.addEventListener('click', navigateFunction);
691
692 // Container of tile contents.
693 var innerElem = createAndAppendElement(tileElem, 'div', CLASSES.TILE_INNER);
694
695 // The iframe which renders the page title.
696 var titleElem = document.createElement('iframe');
697 // Enable tab navigation on the iframe, which will move the selection to the
698 // link element (which also has a tabindex).
699 titleElem.tabIndex = '0';
700
701 // Make the iframe presentational for accessibility so screen readers perceive
702 // the iframe content as just part of the same page.
703 titleElem.setAttribute('role', 'presentation');
704
705 // Why iframes have IDs:
706 //
707 // On navigating back to the NTP we see several onmostvisitedchange() events
708 // in series with incrementing RIDs. After the first event, a set of iframes
709 // begins loading RIDs n, n+1, ..., n+k-1; after the second event, these get
710 // destroyed and a new set begins loading RIDs n+k, n+k+1, ..., n+2k-1.
711 // Now due to crbug.com/68841, Chrome incorrectly loads the content for the
712 // first set of iframes into the most recent set of iframes.
713 //
714 // Giving iframes distinct ids seems to cause some invalidation and prevent
715 // associating the incorrect data.
716 //
717 // TODO(jered): Find and fix the root (probably Blink) bug.
718
719 // Keep this ID here. See comment above.
720 titleElem.id = 'title-' + rid;
721 titleElem.className = CLASSES.TITLE;
722 titleElem.src = getMostVisitedTitleIframeUrl(rid, position);
723 innerElem.appendChild(titleElem);
724
725 // A fallback element for missing thumbnails.
726 if (NTP_DESIGN.thumbnailFallback) {
727 var fallbackElem = createAndAppendElement(
728 innerElem, 'div', CLASSES.THUMBNAIL_FALLBACK);
729 if (NTP_DESIGN.thumbnailFallback === THUMBNAIL_FALLBACK.DOT)
730 createAndAppendElement(fallbackElem, 'div', CLASSES.DOT);
731 }
732
733 // The iframe which renders either a thumbnail or domain element.
734 var thumbnailElem = document.createElement('iframe');
735 thumbnailElem.tabIndex = '-1';
736 thumbnailElem.setAttribute('aria-hidden', 'true');
737 // Keep this ID here. See comment above.
738 thumbnailElem.id = 'thumb-' + rid;
739 thumbnailElem.className = CLASSES.THUMBNAIL;
740 thumbnailElem.src = getMostVisitedThumbnailIframeUrl(rid, position);
741 innerElem.appendChild(thumbnailElem);
742
743 // The button used to blacklist this page.
744 var blacklistButton = createAndAppendElement(
745 innerElem, 'div', CLASSES.BLACKLIST_BUTTON);
746 createAndAppendElement(
747 blacklistButton, 'div', CLASSES.BLACKLIST_BUTTON_INNER);
748 var blacklistFunction = generateBlacklistFunction(rid);
749 blacklistButton.addEventListener('click', blacklistFunction);
750 blacklistButton.title = configData.translatedStrings.removeThumbnailTooltip;
751
752 // A helper mask on top of the tile that is used to create hover border
753 // and/or to darken the thumbnail on focus.
754 var maskElement = createAndAppendElement(
755 innerElem, 'div', CLASSES.THUMBNAIL_MASK);
756
757 // The page favicon, or a fallback.
758 var favicon = createAndAppendElement(innerElem, 'div', CLASSES.FAVICON);
759 if (page.faviconUrl) {
760 favicon.style.backgroundImage = 'url(' + page.faviconUrl + ')';
761 } else {
762 favicon.classList.add(CLASSES.FAVICON_FALLBACK);
763 }
764 return new Tile(tileElem, innerElem, titleElem, thumbnailElem, rid);
765 }
766
767
768 /**
769 * Generates a function to be called when the page with the corresponding RID
770 * is blacklisted.
771 * @param {number} rid The RID of the page being blacklisted.
772 * @return {function(Event=)} A function which handles the blacklisting of the
773 * page by updating state variables and notifying Chrome.
774 */
775 function generateBlacklistFunction(rid) {
776 return function(e) {
777 // Prevent navigation when the page is being blacklisted.
778 if (e)
779 e.stopPropagation();
780
781 userInitiatedMostVisitedChange = true;
782 isBlacklisting = true;
783 tilesContainer.classList.add(CLASSES.HIDE_BLACKLIST_BUTTON);
784 lastBlacklistedTile = getTileByRid(rid);
785 ntpApiHandle.deleteMostVisitedItem(rid);
786 };
787 }
788
789
790 /** 416 /**
791 * Shows the blacklist notification and triggers a delay to hide it. 417 * Shows the blacklist notification and triggers a delay to hide it.
792 */ 418 */
793 function showNotification() { 419 function showNotification() {
794 notification.classList.remove(CLASSES.HIDE_NOTIFICATION); 420 notification.classList.remove(CLASSES.HIDE_NOTIFICATION);
795 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION); 421 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION);
796 notification.scrollTop; 422 notification.scrollTop;
797 notification.classList.add(CLASSES.DELAYED_HIDE_NOTIFICATION); 423 notification.classList.add(CLASSES.DELAYED_HIDE_NOTIFICATION);
798 } 424 }
799 425
800 426
801 /** 427 /**
802 * Hides the blacklist notification. 428 * Hides the blacklist notification.
803 */ 429 */
804 function hideNotification() { 430 function hideNotification() {
805 notification.classList.add(CLASSES.HIDE_NOTIFICATION); 431 notification.classList.add(CLASSES.HIDE_NOTIFICATION);
806 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION); 432 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION);
807 } 433 }
808 434
809 435
810 /** 436 /**
811 * Handles a click on the notification undo link by hiding the notification and 437 * Handles a click on the notification undo link by hiding the notification and
812 * informing Chrome. 438 * informing Chrome.
813 */ 439 */
814 function onUndo() { 440 function onUndo() {
815 userInitiatedMostVisitedChange = true;
816 hideNotification(); 441 hideNotification();
817 var lastBlacklistedRID = lastBlacklistedTile.rid; 442 if (lastBlacklistedTile != null) {
818 if (typeof lastBlacklistedRID != 'undefined') 443 ntpApiHandle.undoMostVisitedDeletion(lastBlacklistedTile);
819 ntpApiHandle.undoMostVisitedDeletion(lastBlacklistedRID); 444 }
820 } 445 }
821 446
822 447
823 /** 448 /**
824 * Handles a click on the restore all notification link by hiding the 449 * Handles a click on the restore all notification link by hiding the
825 * notification and informing Chrome. 450 * notification and informing Chrome.
826 */ 451 */
827 function onRestoreAll() { 452 function onRestoreAll() {
828 userInitiatedMostVisitedChange = true;
829 hideNotification(); 453 hideNotification();
830 ntpApiHandle.undoAllMostVisitedDeletions(); 454 ntpApiHandle.undoAllMostVisitedDeletions();
831 } 455 }
832 456
833 457
834 /** 458 /**
835 * Recomputes the number of tile columns, and width of various contents based 459 * Recomputes the number of tile columns, and width of various contents based
836 * on the width of the window. 460 * on the width of the window.
837 * @return {boolean} Whether the number of tile columns has changed. 461 * @return {boolean} Whether the number of tile columns has changed.
838 */ 462 */
(...skipping 10 matching lines...) Expand all
849 if (newNumColumns < MIN_NUM_COLUMNS) 473 if (newNumColumns < MIN_NUM_COLUMNS)
850 newNumColumns = MIN_NUM_COLUMNS; 474 newNumColumns = MIN_NUM_COLUMNS;
851 else if (newNumColumns > MAX_NUM_COLUMNS) 475 else if (newNumColumns > MAX_NUM_COLUMNS)
852 newNumColumns = MAX_NUM_COLUMNS; 476 newNumColumns = MAX_NUM_COLUMNS;
853 477
854 if (numColumnsShown === newNumColumns) 478 if (numColumnsShown === newNumColumns)
855 return false; 479 return false;
856 480
857 numColumnsShown = newNumColumns; 481 numColumnsShown = newNumColumns;
858 var tilesContainerWidth = numColumnsShown * tileRequiredWidth; 482 var tilesContainerWidth = numColumnsShown * tileRequiredWidth;
859 tilesContainer.style.width = tilesContainerWidth + 'px'; 483 $('mv-tiles').style.width = tilesContainerWidth + 'px';
860 if (fakebox) { 484 if (fakebox) {
861 // -2 to account for border. 485 // -2 to account for border.
862 var fakeboxWidth = (tilesContainerWidth - NTP_DESIGN.tileMargin - 2); 486 var fakeboxWidth = (tilesContainerWidth - NTP_DESIGN.tileMargin - 2);
863 fakebox.style.width = fakeboxWidth + 'px'; 487 fakebox.style.width = fakeboxWidth + 'px';
864 } 488 }
865 return true; 489 return true;
866 } 490 }
867 491
868 492
869 /** 493 /**
870 * Resizes elements because the number of tile columns may need to change in 494 * Resizes elements because the number of tile columns may need to change in
871 * response to resizing. Also shows or hides extra tiles tiles according to the 495 * response to resizing. Also shows or hides extra tiles tiles according to the
872 * new width of the page. 496 * new width of the page.
873 */ 497 */
874 function onResize() { 498 function onResize() {
875 if (updateContentWidth()) { 499 updateContentWidth();
876 // Render without clearing tiles.
877 renderAndShowTiles();
878 }
879 } 500 }
880 501
881 502
882 /**
883 * Returns the tile corresponding to the specified page RID.
884 * @param {number} rid The page RID being looked up.
885 * @return {Tile} The corresponding tile.
886 */
887 function getTileByRid(rid) {
888 for (var i = 0, length = tiles.length; i < length; ++i) {
889 var tile = tiles[i];
890 if (tile.rid == rid)
891 return tile;
892 }
893 return null;
894 }
895
896
897 /** 503 /**
898 * Handles new input by disposing the NTP, according to where the input was 504 * Handles new input by disposing the NTP, according to where the input was
899 * entered. 505 * entered.
900 */ 506 */
901 function onInputStart() { 507 function onInputStart() {
902 if (fakebox && isFakeboxFocused()) { 508 if (fakebox && isFakeboxFocused()) {
903 setFakeboxFocus(false); 509 setFakeboxFocus(false);
904 setFakeboxDragFocus(false); 510 setFakeboxDragFocus(false);
905 disposeNtp(true); 511 disposeNtp(true);
906 } else if (!isFakeboxFocused()) { 512 } else if (!isFakeboxFocused()) {
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after
1001 function createAndAppendElement(parent, name, opt_class) { 607 function createAndAppendElement(parent, name, opt_class) {
1002 var child = document.createElement(name); 608 var child = document.createElement(name);
1003 if (opt_class) 609 if (opt_class)
1004 child.classList.add(opt_class); 610 child.classList.add(opt_class);
1005 parent.appendChild(child); 611 parent.appendChild(child);
1006 return child; 612 return child;
1007 } 613 }
1008 614
1009 615
1010 /** 616 /**
1011 * Removes a node from its parent.
1012 * @param {Node} node The node to remove.
1013 */
1014 function removeNode(node) {
1015 node.parentNode.removeChild(node);
1016 }
1017
1018
1019 /**
1020 * @param {!Element} element The element to register the handler for. 617 * @param {!Element} element The element to register the handler for.
1021 * @param {number} keycode The keycode of the key to register. 618 * @param {number} keycode The keycode of the key to register.
1022 * @param {!Function} handler The key handler to register. 619 * @param {!Function} handler The key handler to register.
1023 */ 620 */
1024 function registerKeyHandler(element, keycode, handler) { 621 function registerKeyHandler(element, keycode, handler) {
1025 element.addEventListener('keydown', function(event) { 622 element.addEventListener('keydown', function(event) {
1026 if (event.keyCode == keycode) 623 if (event.keyCode == keycode)
1027 handler(event); 624 handler(event);
1028 }); 625 });
1029 } 626 }
(...skipping 10 matching lines...) Expand all
1040 return null; 637 return null;
1041 } 638 }
1042 639
1043 640
1044 /** 641 /**
1045 * Event handler for the focus changed and blacklist messages on link elements. 642 * Event handler for the focus changed and blacklist messages on link elements.
1046 * Used to toggle visual treatment on the tiles (depending on the message). 643 * Used to toggle visual treatment on the tiles (depending on the message).
1047 * @param {Event} event Event received. 644 * @param {Event} event Event received.
1048 */ 645 */
1049 function handlePostMessage(event) { 646 function handlePostMessage(event) {
1050 if (event.origin !== 'chrome-search://most-visited') 647 // if (event.origin !== 'chrome-search://most-visited')
huangs 2015/03/12 06:29:19 Clean up?
fserb 2015/03/12 17:06:55 Done.
1051 return; 648 //return;
1052 649
1053 if (event.data === 'linkFocused') { 650 var cmd = event.data.cmd;
1054 var activeElement = document.activeElement; 651 var args = event.data;
1055 if (activeElement.classList.contains(CLASSES.TITLE)) { 652 if (cmd == 'tileBlacklisted') {
1056 activeElement.classList.add(CLASSES.FOCUSED); 653 showNotification();
1057 focusedIframe = activeElement; 654 lastBlacklistedTile = args.rid;
1058 } 655
1059 } else if (event.data === 'linkBlurred') { 656 ntpApiHandle.deleteMostVisitedItem(args.rid);
1060 if (focusedIframe) 657 } else if (cmd == 'loaded') {
1061 focusedIframe.classList.remove(CLASSES.FOCUSED); 658 window.performance.mark('page.end');
1062 focusedIframe = null; 659 window.performance.measure('page', 'page.begin', 'page.end');
1063 } else if (event.data.indexOf('tileBlacklisted') === 0) { 660 var m = window.performance.getEntriesByName('page');
1064 var tilePosition = event.data.split(',')[1]; 661 console.log('Full page render: ' + m[0].duration);
huangs 2015/03/12 06:29:19 Clean up?
fserb 2015/03/12 17:06:55 Is it ok if I leave this here for now until I add
1065 if (tilePosition)
1066 generateBlacklistFunction(tiles[parseInt(tilePosition, 10)].rid)();
1067 } 662 }
1068 } 663 }
1069 664
1070 665
1071 /** 666 /**
1072 * Prepares the New Tab Page by adding listeners, rendering the current 667 * Prepares the New Tab Page by adding listeners, rendering the current
1073 * theme, the most visited pages section, and Google-specific elements for a 668 * theme, the most visited pages section, and Google-specific elements for a
1074 * Google-provided page. 669 * Google-provided page.
1075 */ 670 */
1076 function init() { 671 function init() {
1077 tilesContainer = $(IDS.TILES);
1078 notification = $(IDS.NOTIFICATION); 672 notification = $(IDS.NOTIFICATION);
1079 attribution = $(IDS.ATTRIBUTION); 673 attribution = $(IDS.ATTRIBUTION);
1080 ntpContents = $(IDS.NTP_CONTENTS); 674 ntpContents = $(IDS.NTP_CONTENTS);
1081 675
1082 if (configData.isGooglePage) { 676 if (configData.isGooglePage) {
1083 var logo = document.createElement('div'); 677 var logo = document.createElement('div');
1084 logo.id = IDS.LOGO; 678 logo.id = IDS.LOGO;
1085 logo.title = 'Google'; 679 logo.title = 'Google';
1086 680
1087 fakebox = document.createElement('div'); 681 fakebox = document.createElement('div');
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
1134 ntpApiHandle.onthemechange = onThemeChange; 728 ntpApiHandle.onthemechange = onThemeChange;
1135 ntpApiHandle.onmostvisitedchange = onMostVisitedChange; 729 ntpApiHandle.onmostvisitedchange = onMostVisitedChange;
1136 730
1137 ntpApiHandle.oninputstart = onInputStart; 731 ntpApiHandle.oninputstart = onInputStart;
1138 ntpApiHandle.oninputcancel = restoreNtp; 732 ntpApiHandle.oninputcancel = restoreNtp;
1139 733
1140 if (ntpApiHandle.isInputInProgress) 734 if (ntpApiHandle.isInputInProgress)
1141 onInputStart(); 735 onInputStart();
1142 736
1143 renderTheme(); 737 renderTheme();
1144 renderAllTiles();
1145 738
1146 searchboxApiHandle = topLevelHandle.searchBox; 739 searchboxApiHandle = topLevelHandle.searchBox;
1147 740
1148 if (fakebox) { 741 if (fakebox) {
1149 // Listener for updating the key capture state. 742 // Listener for updating the key capture state.
1150 document.body.onmousedown = function(event) { 743 document.body.onmousedown = function(event) {
1151 if (isFakeboxClick(event)) 744 if (isFakeboxClick(event))
1152 searchboxApiHandle.startCapturingKeyStrokes(); 745 searchboxApiHandle.startCapturingKeyStrokes();
1153 else if (isFakeboxFocused()) 746 else if (isFakeboxFocused())
1154 searchboxApiHandle.stopCapturingKeyStrokes(); 747 searchboxApiHandle.stopCapturingKeyStrokes();
(...skipping 28 matching lines...) Expand all
1183 // Update the fakebox style to match the current key capturing state. 776 // Update the fakebox style to match the current key capturing state.
1184 setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled); 777 setFakeboxFocus(searchboxApiHandle.isKeyCaptureEnabled);
1185 } 778 }
1186 779
1187 if (searchboxApiHandle.rtl) { 780 if (searchboxApiHandle.rtl) {
1188 $(IDS.NOTIFICATION).dir = 'rtl'; 781 $(IDS.NOTIFICATION).dir = 'rtl';
1189 // Grabbing the root HTML element. 782 // Grabbing the root HTML element.
1190 document.documentElement.setAttribute('dir', 'rtl'); 783 document.documentElement.setAttribute('dir', 'rtl');
1191 // Add class for setting alignments based on language directionality. 784 // Add class for setting alignments based on language directionality.
1192 document.documentElement.classList.add(CLASSES.RTL); 785 document.documentElement.classList.add(CLASSES.RTL);
1193 $(IDS.TILES).dir = 'rtl';
1194 } 786 }
1195 787
788
789 var iframe = document.createElement('iframe');
790 iframe.id = 'mv-single';
791 var args = '';
huangs 2015/03/12 06:29:19 Maybe have var args = [] ... args.push('rtl=1'); .
fserb 2015/03/12 17:06:55 Done.
792
793 if (searchboxApiHandle.rtl) {
794 args += 'rtl=1&';
795 }
796
797 iframe.src = '//most-visited/single.html?' + args;
798 $('mv-tiles').appendChild(iframe);
799
800 iframe.onload = function() {
801 reloadTiles();
802 };
803
1196 window.addEventListener('message', handlePostMessage); 804 window.addEventListener('message', handlePostMessage);
1197 } 805 }
1198 806
1199 807
1200 /** 808 /**
1201 * Binds event listeners. 809 * Binds event listeners.
1202 */ 810 */
1203 function listen() { 811 function listen() {
1204 document.addEventListener('DOMContentLoaded', init); 812 document.addEventListener('DOMContentLoaded', init);
1205 } 813 }
1206 814
1207 return { 815 return {
1208 init: init, 816 init: init,
1209 listen: listen 817 listen: listen
1210 }; 818 };
1211 } 819 }
1212 820
1213 if (!window.localNTPUnitTest) { 821 if (!window.localNTPUnitTest) {
1214 LocalNTP().listen(); 822 LocalNTP().listen();
1215 } 823 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698