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

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

Powered by Google App Engine
This is Rietveld 408576698