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

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

Issue 13905008: Merge local_omnibox_popup into local_ntp. Render the Google logo and fakebox if Google is the sear… (Closed) Base URL: https://git.chromium.org/chromium/src.git@master
Patch Set: Respond to Dan's comments. Created 7 years, 8 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 2013 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 (function() { 5 (function() {
6
7 /**
8 * True if this a Google page and not some other search provider. Used to
9 * determine whether to show the logo and fakebox.
10 * @type {boolean}
11 * @const
12 */
13 var isGooglePage = location.href.indexOf('isGoogle') != -1;
14
15 // ==========================================================
16 // Enums
17 // ==========================================================
18
19 /**
20 * Enum for classnames.
21 * @enum {string}
22 * @const
23 */
24 var CLASSES = {
25 BLACKLIST: 'mv-blacklist', // triggers tile blacklist animation
26 BLACKLIST_BUTTON: 'mv-x',
27 CUSTOM_THEME: 'custom-theme',
28 DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide',
29 DOMAIN: 'mv-domain',
30 FAKEBOX_ANIMATE: 'fakebox-animate', // triggers fakebox animation
31 FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox
32 FAVICON: 'mv-favicon',
33 FILLER: 'mv-filler', // filler tiles
34 GOOGLE_PAGE: 'google-page', // shows the Google logo and fakebox
35 HIDE_BLACKLIST_BUTTON: 'mv-x-hide', // hides blacklist button during animation
36 HIDE_NOTIFICATION: 'mv-notice-hide',
37 HIDE_TILE: 'mv-tile-hide', // hides tiles on small browser width
38 PAGE: 'mv-page', // page tiles
39 SELECTED: 'selected', // a selected suggestion (if any)
40 THUMBNAIL: 'mv-thumb',
41 TILE: 'mv-tile',
42 TITLE: 'mv-title'
43 };
44
45 /**
46 * Enum for HTML element ids.
47 * @enum {string}
48 * @const
49 */
50 var IDS = {
51 ATTRIBUTION: 'attribution',
52 CURSOR: 'cursor',
53 FAKEBOX: 'fakebox',
54 LOGO: 'logo',
55 NOTIFICATION: 'mv-notice',
56 NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x',
57 NOTIFICATION_MESSAGE: 'mv-msg',
58 NTP_CONTENTS: 'ntp-contents',
59 RESTORE_ALL_LINK: 'mv-restore',
60 SUGGESTIONS_BOX: 'suggestions-box',
61 SUGGESTIONS_CONTAINER: 'suggestions-box-container',
62 SUGGESTION_STYLE: 'suggestion-style',
63 TILES: 'mv-tiles',
64 TOP_MARGIN: 'mv-top-margin',
65 UNDO_LINK: 'mv-undo'
66 };
67
68 // =============================================================================
69 // NTP implementation
70 // =============================================================================
71
6 /** 72 /**
7 * The element used to vertically position the most visited section on 73 * The element used to vertically position the most visited section on
8 * window resize. 74 * window resize.
9 * @type {Element} 75 * @type {Element}
10 */ 76 */
11 var topMarginElement; 77 var topMarginElement;
12 78
13 /** 79 /**
14 * The container for the tile elements. 80 * The container for the tile elements.
15 * @type {Element} 81 * @type {Element}
16 */ 82 */
17 var tilesContainer; 83 var tilesContainer;
18 84
19 /** 85 /**
20 * The notification displayed when a page is blacklisted. 86 * The notification displayed when a page is blacklisted.
21 * @type {Element} 87 * @type {Element}
22 */ 88 */
23 var notification; 89 var notification;
24 90
25 /** 91 /**
26 * The container for the theme attribution. 92 * The container for the theme attribution.
27 * @type {Element} 93 * @type {Element}
28 */ 94 */
29 var attribution; 95 var attribution;
30 96
31 /** 97 /**
98 * The "fakebox" - an input field that looks like a regular searchbox. When it
99 * is focused, any text the user types goes directly into the omnibox.
100 * @type {Element}
101 */
102 var fakebox;
103
104 /**
105 * The container for NTP elements that should be hidden when suggestions are
106 * visible.
107 * @type {Element}
108 */
109 var ntpContents;
110
111 /**
32 * The array of rendered tiles, ordered by appearance. 112 * The array of rendered tiles, ordered by appearance.
33 * @type {Array.<Tile>} 113 * @type {!Array.<Tile>}
34 */ 114 */
35 var tiles = []; 115 var tiles = [];
36 116
37 /** 117 /**
38 * The last blacklisted tile if any, which by definition should not be filler. 118 * The last blacklisted tile if any, which by definition should not be filler.
39 * @type {?Tile} 119 * @type {?Tile}
40 */ 120 */
41 var lastBlacklistedTile = null; 121 var lastBlacklistedTile = null;
42 122
43 /** 123 /**
(...skipping 22 matching lines...) Expand all
66 /** 146 /**
67 * Current number of tiles shown based on the window width, including filler. 147 * Current number of tiles shown based on the window width, including filler.
68 * @type {number} 148 * @type {number}
69 */ 149 */
70 var numTilesShown = 0; 150 var numTilesShown = 0;
71 151
72 /** 152 /**
73 * The browser embeddedSearch.newTabPage object. 153 * The browser embeddedSearch.newTabPage object.
74 * @type {Object} 154 * @type {Object}
75 */ 155 */
76 var apiHandle; 156 var ntpApiHandle;
77 157
78 /** 158 /**
79 * Possible background-colors of a non-custom theme. Used to determine whether 159 * Possible background-colors of a non-custom theme. Used to determine whether
80 * the homepage should be updated to support custom or non-custom themes. 160 * the homepage should be updated to support custom or non-custom themes.
81 * @type {!Array.<string>} 161 * @type {!Array.<string>}
82 * @const 162 * @const
83 */ 163 */
84 var WHITE = ['rgba(255,255,255,1)', 'rgba(0,0,0,0)']; 164 var WHITE = ['rgba(255,255,255,1)', 'rgba(0,0,0,0)'];
85 165
86 /** 166 /**
(...skipping 18 matching lines...) Expand all
105 185
106 /** 186 /**
107 * Minimum total padding to give to the left and right of the most visited 187 * Minimum total padding to give to the left and right of the most visited
108 * section. Used to determine how many tiles to show. 188 * section. Used to determine how many tiles to show.
109 * @type {number} 189 * @type {number}
110 * @const 190 * @const
111 */ 191 */
112 var MIN_TOTAL_HORIZONTAL_PADDING = 188; 192 var MIN_TOTAL_HORIZONTAL_PADDING = 188;
113 193
114 /** 194 /**
115 * Enum for classnames.
116 * @enum {string}
117 * @const
118 */
119 var CLASSES = {
120 BLACKLIST: 'mv-blacklist', // triggers tile blacklist animation
121 BLACKLIST_BUTTON: 'mv-x',
122 DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide',
123 DOMAIN: 'mv-domain',
124 FAVICON: 'mv-favicon',
125 FILLER: 'mv-filler', // filler tiles
126 HIDE_BLACKLIST_BUTTON: 'mv-x-hide', // hides blacklist button during animation
127 HIDE_NOTIFICATION: 'mv-notice-hide',
128 HIDE_TILE: 'mv-tile-hide', // hides tiles on small browser width
129 PAGE: 'mv-page', // page tiles
130 THUMBNAIL: 'mv-thumb',
131 TILE: 'mv-tile',
132 TITLE: 'mv-title'
133 };
134
135 /**
136 * Enum for HTML element ids.
137 * @enum {string}
138 * @const
139 */
140 var IDS = {
141 ATTRIBUTION: 'attribution',
142 NOTIFICATION: 'mv-notice',
143 NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x',
144 NOTIFICATION_MESSAGE: 'mv-msg',
145 RESTORE_ALL_LINK: 'mv-restore',
146 TILES: 'mv-tiles',
147 TOP_MARGIN: 'mv-top-margin',
148 UNDO_LINK: 'mv-undo'
149 };
150
151 /**
152 * A Tile is either a rendering of a Most Visited page or "filler" used to 195 * A Tile is either a rendering of a Most Visited page or "filler" used to
153 * pad out the section when not enough pages exist. 196 * pad out the section when not enough pages exist.
154 * 197 *
155 * @param {Element} elem The element for rendering the tile. 198 * @param {Element} elem The element for rendering the tile.
156 * @param {number=} opt_rid The RID for the corresponding Most Visited page. 199 * @param {number=} opt_rid The RID for the corresponding Most Visited page.
157 * Should only be left unspecified when creating a filler tile. 200 * Should only be left unspecified when creating a filler tile.
158 * @constructor 201 * @constructor
159 */ 202 */
160 function Tile(elem, opt_rid) { 203 function Tile(elem, opt_rid) {
161 /** @type {Element} */ 204 /** @type {Element} */
162 this.elem = elem; 205 this.elem = elem;
163 206
164 /** @type {number|undefined} */ 207 /** @type {number|undefined} */
165 this.rid = opt_rid; 208 this.rid = opt_rid;
166 } 209 }
167 210
168 /** 211 /**
169 * Updates the NTP based on the current theme. 212 * Updates the NTP based on the current theme.
170 * @private 213 * @private
171 */ 214 */
172 function onThemeChange() { 215 function onThemeChange() {
173 var info = apiHandle.themeBackgroundInfo; 216 if (!isNtpVisible())
217 return;
218
219 var info = ntpApiHandle.themeBackgroundInfo;
174 if (!info) 220 if (!info)
175 return; 221 return;
176 var background = [info.colorRgba, 222 var background = [info.colorRgba,
177 info.imageUrl, 223 info.imageUrl,
178 info.imageTiling, 224 info.imageTiling,
179 info.imageHorizontalAlignment, 225 info.imageHorizontalAlignment,
180 info.imageVerticalAlignment].join(' ').trim(); 226 info.imageVerticalAlignment].join(' ').trim();
181 document.body.style.background = background; 227 document.body.style.background = background;
182 var isCustom = !!background && WHITE.indexOf(background) == -1; 228 var isCustom = !!background && WHITE.indexOf(background) == -1;
183 document.body.classList.toggle('custom-theme', isCustom); 229 document.body.classList.toggle(CLASSES.CUSTOM_THEME, isCustom);
184 updateAttribution(info.attributionUrl); 230 updateAttribution(info.attributionUrl);
185 } 231 }
186 232
187 /** 233 /**
188 * Renders the attribution if the image is present and loadable. Otherwise 234 * Renders the attribution if the image is present and loadable. Otherwise
189 * hides it. 235 * hides it.
190 * @param {string} url The URL of the attribution image, if any. 236 * @param {string} url The URL of the attribution image, if any.
191 * @private 237 * @private
192 */ 238 */
193 function updateAttribution(url) { 239 function updateAttribution(url) {
(...skipping 12 matching lines...) Expand all
206 attributionImage.onerror = function() { 252 attributionImage.onerror = function() {
207 attribution.hidden = true; 253 attribution.hidden = true;
208 }; 254 };
209 attributionImage.src = url; 255 attributionImage.src = url;
210 } 256 }
211 257
212 /** 258 /**
213 * Handles a new set of Most Visited page data. 259 * Handles a new set of Most Visited page data.
214 */ 260 */
215 function onMostVisitedChange() { 261 function onMostVisitedChange() {
216 var pages = apiHandle.mostVisited; 262 var pages = ntpApiHandle.mostVisited;
217 263
218 if (isBlacklisting) { 264 if (isBlacklisting) {
219 // If this was called as a result of a blacklist, add a new replacement 265 // If this was called as a result of a blacklist, add a new replacement
220 // (possibly filler) tile at the end and trigger the blacklist animation. 266 // (possibly filler) tile at the end and trigger the blacklist animation.
221 var replacementTile = createTile(pages[MAX_NUM_TILES_TO_SHOW - 1]); 267 var replacementTile = createTile(pages[MAX_NUM_TILES_TO_SHOW - 1]);
222 268
223 tiles.push(replacementTile); 269 tiles.push(replacementTile);
224 tilesContainer.appendChild(replacementTile.elem); 270 tilesContainer.appendChild(replacementTile.elem);
225 271
226 var lastBlacklistedTileElement = lastBlacklistedTile.elem; 272 var lastBlacklistedTileElement = lastBlacklistedTile.elem;
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
274 function createTile(page) { 320 function createTile(page) {
275 var tileElement = document.createElement('div'); 321 var tileElement = document.createElement('div');
276 tileElement.classList.add(CLASSES.TILE); 322 tileElement.classList.add(CLASSES.TILE);
277 323
278 if (page) { 324 if (page) {
279 var rid = page.rid; 325 var rid = page.rid;
280 tileElement.classList.add(CLASSES.PAGE); 326 tileElement.classList.add(CLASSES.PAGE);
281 327
282 // The click handler for navigating to the page identified by the RID. 328 // The click handler for navigating to the page identified by the RID.
283 tileElement.addEventListener('click', function() { 329 tileElement.addEventListener('click', function() {
284 apiHandle.navigateContentWindow(rid); 330 ntpApiHandle.navigateContentWindow(rid);
285 }); 331 });
286 332
287 // The shadow DOM which renders the page title. 333 // The shadow DOM which renders the page title.
288 var titleElement = page.titleElement; 334 var titleElement = page.titleElement;
289 if (titleElement) { 335 if (titleElement) {
290 titleElement.classList.add(CLASSES.TITLE); 336 titleElement.classList.add(CLASSES.TITLE);
291 tileElement.appendChild(titleElement); 337 tileElement.appendChild(titleElement);
292 } 338 }
293 339
294 // Render the thumbnail if present. Otherwise, fall back to a shadow DOM 340 // Render the thumbnail if present. Otherwise, fall back to a shadow DOM
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
348 function generateBlacklistFunction(rid) { 394 function generateBlacklistFunction(rid) {
349 return function(e) { 395 return function(e) {
350 // Prevent navigation when the page is being blacklisted. 396 // Prevent navigation when the page is being blacklisted.
351 e.stopPropagation(); 397 e.stopPropagation();
352 398
353 showNotification(); 399 showNotification();
354 isBlacklisting = true; 400 isBlacklisting = true;
355 tilesContainer.classList.add(CLASSES.HIDE_BLACKLIST_BUTTON); 401 tilesContainer.classList.add(CLASSES.HIDE_BLACKLIST_BUTTON);
356 lastBlacklistedTile = getTileByRid(rid); 402 lastBlacklistedTile = getTileByRid(rid);
357 lastBlacklistedIndex = tiles.indexOf(lastBlacklistedTile); 403 lastBlacklistedIndex = tiles.indexOf(lastBlacklistedTile);
358 apiHandle.deleteMostVisitedItem(rid); 404 ntpApiHandle.deleteMostVisitedItem(rid);
359 }; 405 };
360 } 406 }
361 407
362 /** 408 /**
363 * Shows the blacklist notification and triggers a delay to hide it. 409 * Shows the blacklist notification and triggers a delay to hide it.
364 */ 410 */
365 function showNotification() { 411 function showNotification() {
366 notification.classList.remove(CLASSES.HIDE_NOTIFICATION); 412 notification.classList.remove(CLASSES.HIDE_NOTIFICATION);
367 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION); 413 notification.classList.remove(CLASSES.DELAYED_HIDE_NOTIFICATION);
368 notification.scrollTop; 414 notification.scrollTop;
(...skipping 22 matching lines...) Expand all
391 437
392 /** 438 /**
393 * Handles a click on the notification undo link by hiding the notification and 439 * Handles a click on the notification undo link by hiding the notification and
394 * informing Chrome. 440 * informing Chrome.
395 */ 441 */
396 function onUndo() { 442 function onUndo() {
397 hideNotification(); 443 hideNotification();
398 var lastBlacklistedRID = lastBlacklistedTile.rid; 444 var lastBlacklistedRID = lastBlacklistedTile.rid;
399 if (typeof lastBlacklistedRID != 'undefined') { 445 if (typeof lastBlacklistedRID != 'undefined') {
400 isUndoing = true; 446 isUndoing = true;
401 apiHandle.undoMostVisitedDeletion(lastBlacklistedRID); 447 ntpApiHandle.undoMostVisitedDeletion(lastBlacklistedRID);
402 } 448 }
403 } 449 }
404 450
405 /** 451 /**
406 * Handles the end of the undo animation by removing the extraneous end tile. 452 * Handles the end of the undo animation by removing the extraneous end tile.
407 */ 453 */
408 function undoAnimationDone() { 454 function undoAnimationDone() {
409 isUndoing = false; 455 isUndoing = false;
410 tiles.splice(tiles.length - 1, 1); 456 tiles.splice(tiles.length - 1, 1);
411 removeNode(tilesContainer.lastElementChild); 457 removeNode(tilesContainer.lastElementChild);
412 updateTileVisibility(numTilesShown); 458 updateTileVisibility(numTilesShown);
413 lastBlacklistedTile.elem.removeEventListener( 459 lastBlacklistedTile.elem.removeEventListener(
414 'webkitTransitionEnd', undoAnimationDone); 460 'webkitTransitionEnd', undoAnimationDone);
415 } 461 }
416 462
417 /** 463 /**
418 * Handles a click on the restore all notification link by hiding the 464 * Handles a click on the restore all notification link by hiding the
419 * notification and informing Chrome. 465 * notification and informing Chrome.
420 */ 466 */
421 function onRestoreAll() { 467 function onRestoreAll() {
422 hideNotification(); 468 hideNotification();
423 apiHandle.undoAllMostVisitedDeletions(); 469 ntpApiHandle.undoAllMostVisitedDeletions();
424 } 470 }
425 471
426 /** 472 /**
427 * Handles a resize by vertically centering the most visited section 473 * Handles a resize by vertically centering the most visited section
428 * and triggering the tile show/hide animation if necessary. 474 * and triggering the tile show/hide animation if necessary.
429 */ 475 */
430 function onResize() { 476 function onResize() {
431 var clientHeight = document.documentElement.clientHeight; 477 // The Google page uses a fixed layout instead.
432 topMarginElement.style.marginTop = 478 if (!isGooglePage) {
433 Math.max(0, (clientHeight - MOST_VISITED_HEIGHT) / 2) + 'px'; 479 var clientHeight = document.documentElement.clientHeight;
434 480 topMarginElement.style.marginTop =
481 Math.max(0, (clientHeight - MOST_VISITED_HEIGHT) / 2) + 'px';
482 }
435 var clientWidth = document.documentElement.clientWidth; 483 var clientWidth = document.documentElement.clientWidth;
436 var numTilesToShow = Math.floor( 484 var numTilesToShow = Math.floor(
437 (clientWidth - MIN_TOTAL_HORIZONTAL_PADDING) / TILE_WIDTH); 485 (clientWidth - MIN_TOTAL_HORIZONTAL_PADDING) / TILE_WIDTH);
438 numTilesToShow = Math.max(MIN_NUM_TILES_TO_SHOW, numTilesToShow); 486 numTilesToShow = Math.max(MIN_NUM_TILES_TO_SHOW, numTilesToShow);
439 if (numTilesToShow != numTilesShown) { 487 if (numTilesToShow != numTilesShown) {
440 updateTileVisibility(numTilesToShow); 488 updateTileVisibility(numTilesToShow);
441 numTilesShown = numTilesToShow; 489 numTilesShown = numTilesToShow;
442 } 490 }
443 } 491 }
444 492
(...skipping 16 matching lines...) Expand all
461 function getTileByRid(rid) { 509 function getTileByRid(rid) {
462 for (var i = 0, length = tiles.length; i < length; ++i) { 510 for (var i = 0, length = tiles.length; i < length; ++i) {
463 var tile = tiles[i]; 511 var tile = tiles[i];
464 if (tile.rid == rid) 512 if (tile.rid == rid)
465 return tile; 513 return tile;
466 } 514 }
467 return null; 515 return null;
468 } 516 }
469 517
470 /** 518 /**
519 * @param {boolean} visible True to show the NTP.
520 */
521 function updateNtpVisibility(visible) {
522 if (visible && !isNtpVisible()) {
523 if (fakebox) {
524 // Stop any fakebox animation animation in progress and restore the NTP.
525 fakebox.removeEventListener('webkitTransitionEnd', fakeboxAnimationDone);
526 document.body.classList.remove(CLASSES.FAKEBOX_ANIMATE);
527 }
528 ntpContents.hidden = false;
529 onThemeChange();
530 } else if (!visible && isNtpVisible()) {
531 if (fakebox && isFakeboxFocused()) {
532 // The user has typed in the fakebox - initiate the fakebox animation,
533 // which upon termination will hide the NTP.
534 document.body.classList.remove(CLASSES.FAKEBOX_FOCUS);
535 // Don't show the suggestions until the animation termintes.
536 $(IDS.SUGGESTIONS_CONTAINER).hidden = true;
537 fakebox.addEventListener('webkitTransitionEnd', fakeboxAnimationDone);
538 document.body.classList.add(CLASSES.FAKEBOX_ANIMATE);
539 } else if (!fakebox ||
540 !document.body.classList.contains(CLASSES.FAKEBOX_ANIMATE)) {
541 // The user has typed in the omnibox - hide the NTP immediately.
542 ntpContents.hidden = true;
543 clearCustomTheme();
544 }
545 }
546
Dan Beam 2013/04/18 20:05:15 ^H
jeremycho 2013/04/18 20:26:18 Done.
547 }
548
549 /**
550 * Clears the custom theme (if any).
551 */
552 function clearCustomTheme() {
553 document.body.style.background = '';
554 document.body.classList.remove(CLASSES.CUSTOM_THEME);
555 }
556
557 /**
558 * @return {boolean} True if the NTP is visible.
559 */
560 function isNtpVisible() {
561 return !ntpContents.hidden;
562 }
563
564 /**
565 * @return {boolean} True if the fakebox has focus.
566 */
567 function isFakeboxFocused() {
568 return document.body.classList.contains(CLASSES.FAKEBOX_FOCUS);
569 }
570
571 /**
572 * @param {!Event} event The click event.
573 * @return {boolean} True if the click occurred in the fakebox.
574 */
575 function isFakeboxClick(event) {
576 var element = event.target;
577 while (element) {
578 if (element == fakebox)
579 return true;
580 element = element.parentNode;
581 }
582 return false;
583 }
584
585 /**
586 * Cleans up the fakebox animation, hides the NTP, and shows suggestions.
587 * @param {!Event} e The webkitTransitionEnd event.
588 */
589 function fakeboxAnimationDone(event) {
590 if (event.propertyName == '-webkit-transform') {
591 ntpContents.hidden = true;
592 $(IDS.SUGGESTIONS_CONTAINER).hidden = false;
593 clearCustomTheme();
594 document.body.classList.remove(CLASSES.FAKEBOX_ANIMATE);
595 fakebox.removeEventListener('webkitTransitionEnd', fakeboxAnimationDone);
596 }
597 }
598
599 // =============================================================================
600 // Dropdown Implementation
601 // =============================================================================
602
603 /**
604 * Possible behaviors for navigateContentWindow.
605 * @enum {number}
606 */
607 var WindowOpenDisposition = {
608 CURRENT_TAB: 1,
609 NEW_BACKGROUND_TAB: 2
610 };
611
612 /**
613 * The JavaScript button event value for a middle click.
614 * @type {number}
615 * @const
616 */
617 var MIDDLE_MOUSE_BUTTON = 1;
618
619 /**
620 * The maximum number of suggestions to show.
621 * @type {number}
622 * @const
623 */
624 var MAX_SUGGESTIONS_TO_SHOW = 5;
625
626 /**
627 * Assume any native suggestion with a score higher than this value has been
628 * inlined by the browser.
629 * @type {number}
630 * @const
631 */
632 var INLINE_SUGGESTION_THRESHOLD = 1200;
633
634 /**
635 * Suggestion provider type corresponding to a verbatim URL suggestion.
636 * @type {string}
637 * @const
638 */
639 var VERBATIM_URL_TYPE = 'url-what-you-typed';
640
641 /**
642 * Suggestion provider type corresponding to a verbatim search suggestion.
643 * @type {string}
644 * @const
645 */
646 var VERBATIM_SEARCH_TYPE = 'search-what-you-typed';
647
648 /**
649 * The omnibox input value during the last onnativesuggestions event.
650 * @type {string}
651 */
652 var lastInputValue = '';
653
654 /**
655 * The ordered restricted ids of the currently displayed suggestions. Since the
656 * suggestions contain the user's personal data (browser history) the searchBox
657 * API embeds the content of the suggestion in a shadow dom, and assigns a
658 * random restricted id to each suggestion which is accessible to the JS.
659 * @type {!Array.<number>}
660 */
661
662 var restrictedIds = [];
663
664 /**
665 * The index of the currently selected suggestion or -1 if none are selected.
666 * @type {number}
667 */
668 var selectedIndex = -1;
669
670 /**
671 * The browser embeddedSearch.searchBox object.
672 * @type {Object}
673 */
674 var searchboxApiHandle;
675
676 /**
677 * Displays a suggestion.
678 * @param {Object} suggestion The suggestion to render.
679 * @param {HTMLElement} box The html element to add the suggestion to.
680 * @param {boolean} select True to select the selection.
681 */
682 function addSuggestionToBox(suggestion, box, select) {
683 var suggestionDiv = document.createElement('div');
684 suggestionDiv.classList.add('suggestion');
685 suggestionDiv.classList.toggle(CLASSES.SELECTED, select);
686 suggestionDiv.classList.toggle('search', suggestion.is_search);
687
688 if (suggestion.destination_url) { // iframes.
689 var suggestionIframe = document.createElement('iframe');
690 suggestionIframe.className = 'contents';
691 suggestionIframe.src = suggestion.destination_url;
692 suggestionIframe.id = suggestion.rid;
693 suggestionDiv.appendChild(suggestionIframe);
694 } else {
695 var contentsContainer = document.createElement('div');
696 var contents = suggestion.combinedNode;
697 contents.classList.add('contents');
698 contentsContainer.appendChild(contents);
699 suggestionDiv.appendChild(contentsContainer);
700 suggestionDiv.onclick = function(event) {
701 handleSuggestionClick(suggestion.rid, event.button);
702 };
703 }
704
705 restrictedIds.push(suggestion.rid);
706 box.appendChild(suggestionDiv);
707 }
708
709 /**
710 * Renders the input suggestions.
711 * @param {!Array} nativeSuggestions An array of native suggestions to render.
712 */
713 function renderSuggestions(nativeSuggestions) {
714 for (var i = 0, length = nativeSuggestions.length;
715 i < Math.min(MAX_SUGGESTIONS_TO_SHOW, length); ++i) {
716 // Don't add the search-what-you-typed suggestion if it's the top match.
717 if (i > 0 || nativeSuggestions[i].type != VERBATIM_SEARCH_TYPE) {
718 var box = $(IDS.SUGGESTIONS_BOX);
719 if (!box) {
720 box = document.createElement('div');
721 box.id = IDS.SUGGESTIONS_BOX;
722 $(IDS.SUGGESTIONS_CONTAINER).appendChild(box);
723 }
724 addSuggestionToBox(nativeSuggestions[i], box, i == selectedIndex);
725 }
726 }
727 }
728
729 /**
730 * Clears the suggestions being displayed.
731 */
732 function clearSuggestions() {
733 $(IDS.SUGGESTIONS_CONTAINER).innerHTML = '';
734 restrictedIds = [];
735 selectedIndex = -1;
736 }
737
738 /**
739 * @return {number} The height of the dropdown.
740 */
741 function getDropdownHeight() {
742 return $(IDS.SUGGESTIONS_CONTAINER).offsetHeight;
743 }
744
745 /**
746 * @param {!Object} suggestion A suggestion.
747 * @param {boolean} inVerbatimMode Are we in verbatim mode?
748 * @return {boolean} True if the suggestion should be selected.
749 */
750 function shouldSelectSuggestion(suggestion, inVerbatimMode) {
751 var isVerbatimUrl = suggestion.type == VERBATIM_URL_TYPE;
752 var inlinableSuggestion = suggestion.type != VERBATIM_SEARCH_TYPE &&
753 suggestion.rankingData.relevance > INLINE_SUGGESTION_THRESHOLD;
754 // Verbatim URLs should always be selected. Otherwise, select suggestions
755 // with a high enough score unless we are in verbatim mode (e.g. backspacing
756 // away).
757 return isVerbatimUrl || (!inVerbatimMode && inlinableSuggestion);
758 }
759
760 /**
761 * Updates selectedIndex, bounding it between -1 and the total number of
762 * of suggestions - 1 (looping as necessary), and selects the corresponding
763 * suggestion.
764 * @param {boolean} increment True to increment the selected suggestion, false
765 * to decrement.
766 */
767 function updateSelectedSuggestion(increment) {
768 var numSuggestions = restrictedIds.length;
769 if (!numSuggestions)
770 return;
771
772 var oldSelection = $(IDS.SUGGESTIONS_BOX).querySelector('.selected');
773 if (oldSelection)
774 oldSelection.classList.remove(CLASSES.SELECTED);
775 if (increment) {
776 if (selectedIndex > numSuggestions)
777 selectedIndex = -1;
778 else
779 ++selectedIndex;
780 } else {
781 if (selectedIndex < 0)
782 selectedIndex = numSuggestions - 1;
783 else
784 --selectedIndex;
785 }
786
787 if (selectedIndex == -1) {
788 searchboxApiHandle.setValue(lastInputValue);
789 } else {
790 var newSelection = $(IDS.SUGGESTIONS_BOX).querySelector(
791 '.suggestion:nth-of-type(' + (selectedIndex + 1) + ')');
792 newSelection.classList.add(CLASSES.SELECTED);
793 searchboxApiHandle.setRestrictedValue(restrictedIds[selectedIndex]);
794 }
795 }
796
797 /**
798 * Updates suggestions in response to a onchange or onnativesuggestions call.
799 */
800 function updateSuggestions() {
801 appendSuggestionStyles();
802 lastInputValue = searchboxApiHandle.value;
803
804 // Hide the NTP if input has made it into the omnibox.
805 var show = lastInputValue == '';
806 updateNtpVisibility(show);
807
808 clearSuggestions();
809 if (show) {
810 searchboxApiHandle.showBars();
811 return;
812 }
813
814 var nativeSuggestions = searchboxApiHandle.nativeSuggestions;
815 if (nativeSuggestions.length) {
816 nativeSuggestions.sort(function(a, b) {
817 return b.rankingData.relevance - a.rankingData.relevance;
818 });
819 if (shouldSelectSuggestion(
820 nativeSuggestions[0], searchboxApiHandle.verbatim)) {
821 selectedIndex = 0;
822 }
823
824 renderSuggestions(nativeSuggestions);
825 searchboxApiHandle.hideBars();
826 } else {
827 searchboxApiHandle.showBars();
828 }
829
830 var height = getDropdownHeight();
831 searchboxApiHandle.showOverlay(height);
832 }
833
834 /**
835 * Appends a style node for suggestion properties that depend on apiHandle.
836 */
837 function appendSuggestionStyles() {
838 if ($(IDS.SUGGESTION_STYLE))
839 return;
840
841 var isRtl = searchboxApiHandle.rtl;
842 var startMargin = searchboxApiHandle.startMargin;
843 var style = document.createElement('style');
844 style.type = 'text/css';
845 style.id = IDS.SUGGESTION_STYLE;
846 style.textContent =
847 '.suggestion, ' +
848 '.suggestion.search {' +
849 ' background-position: ' +
850 (isRtl ? '-webkit-calc(100% - 5px)' : '5px') + ' 4px;' +
851 ' -webkit-margin-start: ' + startMargin + 'px;' +
852 ' -webkit-margin-end: ' +
853 (window.innerWidth - searchboxApiHandle.width - startMargin) + 'px;' +
854 ' font: ' + searchboxApiHandle.fontSize + 'px "' +
855 searchboxApiHandle.font + '";' +
856 '}';
857 document.querySelector('head').appendChild(style);
858 }
859
860 /**
861 * Extract the desired navigation behavior from a click button.
862 * @param {number} button The Event#button property of a click event.
863 * @return {!WindowOpenDisposition} The desired behavior for
864 * navigateContentWindow.
865 */
866 function getDispositionFromClickButton(button) {
867 if (button == MIDDLE_MOUSE_BUTTON)
868 return WindowOpenDisposition.NEW_BACKGROUND_TAB;
869 return WindowOpenDisposition.CURRENT_TAB;
870 }
871
872 /**
873 * Handles suggestion clicks.
874 * @param {number} restrictedId The restricted id of the suggestion being
875 * clicked.
876 * @param {number} button The Event#button property of a click event.
877 *
878 */
879 function handleSuggestionClick(restrictedId, button) {
880 clearSuggestions();
881 searchboxApiHandle.navigateContentWindow(
882 restrictedId, getDispositionFromClickButton(button));
883 }
884
885 /**
886 * chrome.searchBox.onkeypress implementation.
887 * @param {!Event} e The key being pressed.
888 */
889 function handleKeyPress(e) {
890 switch (e.keyCode) {
891 case 38: // Up arrow.
892 updateSelectedSuggestion(false);
893 break;
894 case 40: // Down arrow.
895 updateSelectedSuggestion(true);
896 break;
897 }
898 }
899
900 /**
901 * Handles the postMessage calls from the result iframes.
902 * @param {Object} message The message containing details of clicks the iframes.
903 */
904 function handleMessage(message) {
905 if (message.origin != 'null' || !message.data ||
906 message.data.eventType != 'click') {
907 return;
908 }
909
910 var iframes = document.getElementsByClassName('contents');
911 for (var i = 0; i < iframes.length; ++i) {
912 if (iframes[i].contentWindow == message.source) {
913 handleSuggestionClick(parseInt(iframes[i].id, 10),
914 message.data.button);
915 break;
916 }
917 }
918 }
919
920 // =============================================================================
921 // Utils
922 // =============================================================================
923
924 /**
925 * Shortcut for document.getElementById.
926 * @param {string} id of the element.
927 * @return {HTMLElement} with the id.
928 */
929 function $(id) {
930 return document.getElementById(id);
931 }
932
933 /**
471 * Utility function which creates an element with an optional classname and 934 * Utility function which creates an element with an optional classname and
472 * appends it to the specified parent. 935 * appends it to the specified parent.
473 * @param {Element} parent The parent to append the new element. 936 * @param {Element} parent The parent to append the new element.
474 * @param {string} name The name of the new element. 937 * @param {string} name The name of the new element.
475 * @param {string=} opt_class The optional classname of the new element. 938 * @param {string=} opt_class The optional classname of the new element.
476 * @return {Element} The new element. 939 * @return {Element} The new element.
477 */ 940 */
478 function createAndAppendElement(parent, name, opt_class) { 941 function createAndAppendElement(parent, name, opt_class) {
479 var child = document.createElement(name); 942 var child = document.createElement(name);
480 if (opt_class) 943 if (opt_class)
(...skipping 22 matching lines...) Expand all
503 * @return {Object} the handle to the embeddedSearch API. 966 * @return {Object} the handle to the embeddedSearch API.
504 */ 967 */
505 function getEmbeddedSearchApiHandle() { 968 function getEmbeddedSearchApiHandle() {
506 if (window.cideb) 969 if (window.cideb)
507 return window.cideb; 970 return window.cideb;
508 if (window.chrome && window.chrome.embeddedSearch) 971 if (window.chrome && window.chrome.embeddedSearch)
509 return window.chrome.embeddedSearch; 972 return window.chrome.embeddedSearch;
510 return null; 973 return null;
511 } 974 }
512 975
976 // =============================================================================
977 // Initialization
978 // =============================================================================
979
513 /** 980 /**
514 * Prepares the New Tab Page by adding listeners, rendering the current 981 * Prepares the New Tab Page by adding listeners, rendering the current
515 * theme, and the most visited pages section. 982 * theme, the most visited pages section, and Google-specific elements for a
983 * Google-provided page.
516 */ 984 */
517 function init() { 985 function init() {
518 topMarginElement = document.getElementById(IDS.TOP_MARGIN); 986 topMarginElement = $(IDS.TOP_MARGIN);
519 tilesContainer = document.getElementById(IDS.TILES); 987 tilesContainer = $(IDS.TILES);
520 notification = document.getElementById(IDS.NOTIFICATION); 988 notification = $(IDS.NOTIFICATION);
521 attribution = document.getElementById(IDS.ATTRIBUTION); 989 attribution = $(IDS.ATTRIBUTION);
990 ntpContents = $(IDS.NTP_CONTENTS);
991
992 if (isGooglePage) {
993 document.body.classList.add(CLASSES.GOOGLE_PAGE);
994 var logo = document.createElement('div');
995 logo.id = IDS.LOGO;
996
997 fakebox = document.createElement('div');
998 fakebox.id = IDS.FAKEBOX;
999 fakebox.innerHTML =
1000 '<input autocomplete="off" tabindex="-1" aria-hidden="true">' +
1001 '<div id=cursor></div>';
1002
1003 ntpContents.insertBefore(fakebox, ntpContents.firstChild);
1004 ntpContents.insertBefore(logo, ntpContents.firstChild);
1005 }
1006
522 1007
523 // TODO(jeremycho): i18n. 1008 // TODO(jeremycho): i18n.
524 var notificationMessage = document.getElementById(IDS.NOTIFICATION_MESSAGE); 1009 var notificationMessage = $(IDS.NOTIFICATION_MESSAGE);
525 notificationMessage.innerText = 'Thumbnail removed.'; 1010 notificationMessage.innerText = 'Thumbnail removed.';
526 var undoLink = document.getElementById(IDS.UNDO_LINK); 1011 var undoLink = $(IDS.UNDO_LINK);
527 undoLink.addEventListener('click', onUndo); 1012 undoLink.addEventListener('click', onUndo);
528 undoLink.innerText = 'Undo'; 1013 undoLink.innerText = 'Undo';
529 var restoreAllLink = document.getElementById(IDS.RESTORE_ALL_LINK); 1014 var restoreAllLink = $(IDS.RESTORE_ALL_LINK);
530 restoreAllLink.addEventListener('click', onRestoreAll); 1015 restoreAllLink.addEventListener('click', onRestoreAll);
531 restoreAllLink.innerText = 'Restore all'; 1016 restoreAllLink.innerText = 'Restore all';
532 attribution.innerText = 'Theme created by'; 1017 attribution.innerText = 'Theme created by';
533 1018
534 var notificationCloseButton = 1019 var notificationCloseButton = $(IDS.NOTIFICATION_CLOSE_BUTTON);
535 document.getElementById(IDS.NOTIFICATION_CLOSE_BUTTON);
536 notificationCloseButton.addEventListener('click', hideNotification); 1020 notificationCloseButton.addEventListener('click', hideNotification);
537 1021
538 window.addEventListener('resize', onResize); 1022 window.addEventListener('resize', onResize);
539 onResize(); 1023 onResize();
540 1024
541 var topLevelHandle = getEmbeddedSearchApiHandle(); 1025 var topLevelHandle = getEmbeddedSearchApiHandle();
542 // This is to inform Chrome that the NTP is instant-extended capable i.e.
543 // it should fire events like onmostvisitedchange.
544 topLevelHandle.searchBox.onsubmit = function() {};
545 1026
546 apiHandle = topLevelHandle.newTabPage; 1027 ntpApiHandle = topLevelHandle.newTabPage;
547 apiHandle.onthemechange = onThemeChange; 1028 ntpApiHandle.onthemechange = onThemeChange;
548 apiHandle.onmostvisitedchange = onMostVisitedChange; 1029 ntpApiHandle.onmostvisitedchange = onMostVisitedChange;
549 1030
550 onThemeChange(); 1031 onThemeChange();
551 onMostVisitedChange(); 1032 onMostVisitedChange();
1033
1034 searchboxApiHandle = topLevelHandle.searchBox;
1035 searchboxApiHandle.onnativesuggestions = updateSuggestions;
1036 searchboxApiHandle.onchange = updateSuggestions;
1037 searchboxApiHandle.onkeypress = handleKeyPress;
1038 searchboxApiHandle.onsubmit = function() {
1039 var value = searchboxApiHandle.value;
1040 if (!value) {
1041 // Interpret onsubmit with an empty query as an ESC key press.
1042 clearSuggestions();
1043 updateNtpVisibility(true);
1044 }
1045 };
1046
1047 $(IDS.SUGGESTIONS_CONTAINER).dir = searchboxApiHandle.rtl ? 'rtl' : 'ltr';
1048
1049 if (!document.webkitHidden)
1050 window.addEventListener('resize', addDelayedTransitions);
1051 else
1052 document.addEventListener('webkitvisibilitychange', addDelayedTransitions);
1053
1054 if (fakebox) {
1055 // Listener for updating the fakebox focus.
1056 document.body.onclick = function(event) {
1057 if (isFakeboxClick(event)) {
1058 document.body.classList.add(CLASSES.FAKEBOX_FOCUS);
1059 searchboxApiHandle.startCapturingKeyStrokes();
1060 } else {
1061 document.body.classList.remove(CLASSES.FAKEBOX_FOCUS);
1062 searchboxApiHandle.stopCapturingKeyStrokes();
1063 }
1064 };
1065
1066 // Set the cursor alignment based on language directionality.
1067 $(IDS.CURSOR).style[searchboxApiHandle.rtl ? 'right' : 'left'] = '9px';
1068 }
1069 }
1070
1071 /**
1072 * Applies webkit transitions to NTP elements which need to be delayed until
1073 * after the page is made visible and any initial resize has occurred. This is
1074 * to prevent animations from triggering when the NTP is shown.
1075 */
1076 function addDelayedTransitions() {
1077 if (fakebox) {
1078 fakebox.style.webkitTransition =
1079 '-webkit-transform 100ms linear, width 200ms ease';
1080 }
1081
1082 tilesContainer.style.webkitTransition = 'width 200ms';
1083 window.removeEventListener('resize', addDelayedTransitions);
1084 document.removeEventListener('webkitvisibilitychange', addDelayedTransitions);
552 } 1085 }
553 1086
554 document.addEventListener('DOMContentLoaded', init); 1087 document.addEventListener('DOMContentLoaded', init);
1088 window.addEventListener('message', handleMessage, false);
555 })(); 1089 })();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698