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

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

Issue 13375003: Fixing iframe jank in the local omnibox popup. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: virtual 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 | Annotate | Revision Log
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 <include src="../../../../ui/webui/resources/js/assert.js">
6 7
7 /** 8 /**
8 * True if this a Google page and not some other search provider. Used to 9 * True if this a Google page and not some other search provider. Used to
9 * determine whether to show the logo and fakebox. 10 * determine whether to show the logo and fakebox.
10 * @type {boolean} 11 * @type {boolean}
11 * @const 12 * @const
12 */ 13 */
13 var isGooglePage = location.href.indexOf('isGoogle') != -1; 14 var isGooglePage = location.href.indexOf('isGoogle') != -1;
14 15
15 // ========================================================== 16 // ==========================================================
16 // Enums 17 // Enums
17 // ========================================================== 18 // ==========================================================
18 19
19 /** 20 /**
20 * Enum for classnames. 21 * Enum for classnames.
21 * @enum {string} 22 * @enum {string}
22 * @const 23 * @const
23 */ 24 */
24 var CLASSES = { 25 var CLASSES = {
26 ACTIVE_SUGGESTIONS_CONTAINER: 'active-suggestions-container',
25 BLACKLIST: 'mv-blacklist', // triggers tile blacklist animation 27 BLACKLIST: 'mv-blacklist', // triggers tile blacklist animation
26 BLACKLIST_BUTTON: 'mv-x', 28 BLACKLIST_BUTTON: 'mv-x',
27 CUSTOM_THEME: 'custom-theme', 29 CUSTOM_THEME: 'custom-theme',
28 DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide', 30 DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide',
29 DOMAIN: 'mv-domain', 31 DOMAIN: 'mv-domain',
30 FAKEBOX_ANIMATE: 'fakebox-animate', // triggers fakebox animation 32 FAKEBOX_ANIMATE: 'fakebox-animate', // triggers fakebox animation
31 FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox 33 FAKEBOX_FOCUS: 'fakebox-focused', // Applies focus styles to the fakebox
32 FAVICON: 'mv-favicon', 34 FAVICON: 'mv-favicon',
33 FILLER: 'mv-filler', // filler tiles 35 FILLER: 'mv-filler', // filler tiles
34 GOOGLE_PAGE: 'google-page', // shows the Google logo and fakebox 36 GOOGLE_PAGE: 'google-page', // shows the Google logo and fakebox
35 HIDE_BLACKLIST_BUTTON: 'mv-x-hide', // hides blacklist button during animation 37 HIDE_BLACKLIST_BUTTON: 'mv-x-hide', // hides blacklist button during animation
36 HIDE_NOTIFICATION: 'mv-notice-hide', 38 HIDE_NOTIFICATION: 'mv-notice-hide',
37 HIDE_TILE: 'mv-tile-hide', // hides tiles on small browser width 39 HIDE_TILE: 'mv-tile-hide', // hides tiles on small browser width
40 HOVERED: 'hovered',
41 PENDING_SUGGESTIONS_CONTAINER: 'pending-suggestions-container',
38 PAGE: 'mv-page', // page tiles 42 PAGE: 'mv-page', // page tiles
43 SEARCH: 'search',
39 SELECTED: 'selected', // a selected suggestion (if any) 44 SELECTED: 'selected', // a selected suggestion (if any)
45 SUGGESTION: 'suggestion',
46 SUGGESTION_CONTENTS: 'suggestion-contents',
47 SUGGESTIONS_BOX: 'suggestions-box',
40 THUMBNAIL: 'mv-thumb', 48 THUMBNAIL: 'mv-thumb',
41 TILE: 'mv-tile', 49 TILE: 'mv-tile',
42 TITLE: 'mv-title' 50 TITLE: 'mv-title'
43 }; 51 };
44 52
45 /** 53 /**
46 * Enum for HTML element ids. 54 * Enum for HTML element ids.
47 * @enum {string} 55 * @enum {string}
48 * @const 56 * @const
49 */ 57 */
50 var IDS = { 58 var IDS = {
51 ATTRIBUTION: 'attribution', 59 ATTRIBUTION: 'attribution',
52 CURSOR: 'cursor', 60 CURSOR: 'cursor',
53 FAKEBOX: 'fakebox', 61 FAKEBOX: 'fakebox',
54 LOGO: 'logo', 62 LOGO: 'logo',
55 NOTIFICATION: 'mv-notice', 63 NOTIFICATION: 'mv-notice',
56 NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x', 64 NOTIFICATION_CLOSE_BUTTON: 'mv-notice-x',
57 NOTIFICATION_MESSAGE: 'mv-msg', 65 NOTIFICATION_MESSAGE: 'mv-msg',
58 NTP_CONTENTS: 'ntp-contents', 66 NTP_CONTENTS: 'ntp-contents',
59 RESTORE_ALL_LINK: 'mv-restore', 67 RESTORE_ALL_LINK: 'mv-restore',
60 SUGGESTIONS_BOX: 'suggestions-box', 68 SUGGESTION_LOADER: 'suggestion-loader',
61 SUGGESTIONS_CONTAINER: 'suggestions-box-container',
62 SUGGESTION_STYLE: 'suggestion-style', 69 SUGGESTION_STYLE: 'suggestion-style',
70 SUGGESTION_TEXT_PREFIX: 'suggestion-text-',
63 TILES: 'mv-tiles', 71 TILES: 'mv-tiles',
64 TOP_MARGIN: 'mv-top-margin', 72 TOP_MARGIN: 'mv-top-margin',
65 UNDO_LINK: 'mv-undo' 73 UNDO_LINK: 'mv-undo'
66 }; 74 };
67 75
68 // ============================================================================= 76 // =============================================================================
69 // NTP implementation 77 // NTP implementation
70 // ============================================================================= 78 // =============================================================================
71 79
72 /** 80 /**
(...skipping 545 matching lines...) Expand 10 before | Expand all | Expand 10 after
618 626
619 /** 627 /**
620 * Assume any native suggestion with a score higher than this value has been 628 * Assume any native suggestion with a score higher than this value has been
621 * inlined by the browser. 629 * inlined by the browser.
622 * @type {number} 630 * @type {number}
623 * @const 631 * @const
624 */ 632 */
625 var INLINE_SUGGESTION_THRESHOLD = 1200; 633 var INLINE_SUGGESTION_THRESHOLD = 1200;
626 634
627 /** 635 /**
636 * The color code for a query.
637 * @type {number}
638 * @const
639 */
640 var SUGGESTION_QUERY_COLOR = 0x000000;
641
642 /**
643 * The color code for a suggestion display URL.
644 * @type {number}
645 * @const
646 */
647 var SUGGESTION_URL_COLOR = 0x009933;
648
649 /**
650 * The color code for a suggestion title.
651 * @type {number}
652 * @const
653 */
654 var SUGGESTION_TITLE_COLOR = 0x666666;
655
656 /**
657 * A top position which is off-screen.
658 * @type {string}
659 * @const
660 */
661 var OFF_SCREEN = '-1000px';
662
663 /**
664 * The expected origin of a suggestion iframe.
665 * @type {string}
666 * @const
667 */
668 var SUGGESTION_ORIGIN = 'chrome-search://suggestion';
669
670 /**
628 * Suggestion provider type corresponding to a verbatim URL suggestion. 671 * Suggestion provider type corresponding to a verbatim URL suggestion.
629 * @type {string} 672 * @type {string}
630 * @const 673 * @const
631 */ 674 */
632 var VERBATIM_URL_TYPE = 'url-what-you-typed'; 675 var VERBATIM_URL_TYPE = 'url-what-you-typed';
633 676
634 /** 677 /**
635 * Suggestion provider type corresponding to a verbatim search suggestion. 678 * Suggestion provider type corresponding to a verbatim search suggestion.
636 * @type {string} 679 * @type {string}
637 * @const 680 * @const
638 */ 681 */
639 var VERBATIM_SEARCH_TYPE = 'search-what-you-typed'; 682 var VERBATIM_SEARCH_TYPE = 'search-what-you-typed';
640 683
641 /** 684 /**
642 * The omnibox input value during the last onnativesuggestions event. 685 * "Up" arrow keycode.
643 * @type {string}
644 */
645 var lastInputValue = '';
646
647 /**
648 * The ordered restricted ids of the currently displayed suggestions. Since the
649 * suggestions contain the user's personal data (browser history) the searchBox
650 * API embeds the content of the suggestion in a shadow dom, and assigns a
651 * random restricted id to each suggestion which is accessible to the JS.
652 * @type {!Array.<number>}
653 */
654
655 var restrictedIds = [];
656
657 /**
658 * The index of the currently selected suggestion or -1 if none are selected.
659 * @type {number} 686 * @type {number}
660 */ 687 * @const
661 var selectedIndex = -1; 688 */
662 689 var KEY_UP_ARROW = 38;
663 /** 690
664 * The browser embeddedSearch.searchBox object. 691 /**
665 * @type {Object} 692 * "Down" arrow keycode.
666 */ 693 * @type {number}
667 var searchboxApiHandle; 694 * @const
668 695 */
669 /** 696 var KEY_DOWN_ARROW = 40;
670 * Displays a suggestion. 697
671 * @param {Object} suggestion The suggestion to render. 698 /**
672 * @param {HTMLElement} box The html element to add the suggestion to. 699 * Pixels of padding inside a suggestion div for displaying its icon.
673 * @param {boolean} select True to select the selection. 700 * @type {number}
674 */ 701 * @const
675 function addSuggestionToBox(suggestion, box, select) { 702 */
676 var suggestionDiv = document.createElement('div'); 703 var SUGGESTION_ICON_PADDING = 26;
677 suggestionDiv.classList.add('suggestion'); 704
678 suggestionDiv.classList.toggle(CLASSES.SELECTED, select); 705 /**
679 suggestionDiv.classList.toggle('search', suggestion.is_search); 706 * Pixels by which iframes should be moved down relative to their wrapping
680 707 * suggestion div.
681 if (suggestion.destination_url) { // iframes. 708 */
682 var suggestionIframe = document.createElement('iframe'); 709 var SUGGESTION_TOP_OFFSET = 4;
683 suggestionIframe.className = 'contents'; 710
684 suggestionIframe.src = suggestion.destination_url; 711 /**
685 suggestionIframe.id = suggestion.rid; 712 * The displayed suggestions.
686 suggestionDiv.appendChild(suggestionIframe); 713 * @type {SuggestionsBox}
687 } else { 714 */
688 var contentsContainer = document.createElement('div'); 715 var activeBox;
689 var contents = suggestion.combinedNode; 716
690 contents.classList.add('contents'); 717 /**
691 contentsContainer.appendChild(contents); 718 * The suggestions being rendered.
692 suggestionDiv.appendChild(contentsContainer); 719 * @type {SuggestionsBox}
693 suggestionDiv.onclick = function(event) { 720 */
694 handleSuggestionClick(suggestion.rid, event.button); 721 var pendingBox;
695 }; 722
696 } 723 /**
697 724 * A pool of iframes to display suggestions.
698 restrictedIds.push(suggestion.rid); 725 * @type {IframePool}
699 box.appendChild(suggestionDiv); 726 */
700 } 727 var iframePool;
701 728
702 /** 729 /**
703 * Renders the input suggestions. 730 * A serial number for the next suggestions rendered.
704 * @param {!Array} nativeSuggestions An array of native suggestions to render. 731 * @type {number}
705 */ 732 */
706 function renderSuggestions(nativeSuggestions) { 733 var nextRequestId = 0;
707 for (var i = 0, length = nativeSuggestions.length; 734
708 i < Math.min(MAX_SUGGESTIONS_TO_SHOW, length); ++i) { 735 /**
709 // Don't add the search-what-you-typed suggestion if it's the top match. 736 * @param {Object} suggestion A suggestion.
710 if (i > 0 || nativeSuggestions[i].type != VERBATIM_SEARCH_TYPE) {
711 var box = $(IDS.SUGGESTIONS_BOX);
712 if (!box) {
713 box = document.createElement('div');
714 box.id = IDS.SUGGESTIONS_BOX;
715 $(IDS.SUGGESTIONS_CONTAINER).appendChild(box);
716 }
717 addSuggestionToBox(nativeSuggestions[i], box, i == selectedIndex);
718 }
719 }
720 }
721
722 /**
723 * Clears the suggestions being displayed.
724 */
725 function clearSuggestions() {
726 $(IDS.SUGGESTIONS_CONTAINER).innerHTML = '';
727 restrictedIds = [];
728 selectedIndex = -1;
729 }
730
731 /**
732 * @return {number} The height of the dropdown.
733 */
734 function getDropdownHeight() {
735 return $(IDS.SUGGESTIONS_CONTAINER).offsetHeight;
736 }
737
738 /**
739 * @param {!Object} suggestion A suggestion.
740 * @param {boolean} inVerbatimMode Are we in verbatim mode? 737 * @param {boolean} inVerbatimMode Are we in verbatim mode?
741 * @return {boolean} True if the suggestion should be selected. 738 * @return {boolean} True if the suggestion should be selected.
742 */ 739 */
743 function shouldSelectSuggestion(suggestion, inVerbatimMode) { 740 function shouldSelectSuggestion(suggestion, inVerbatimMode) {
744 var isVerbatimUrl = suggestion.type == VERBATIM_URL_TYPE; 741 var isVerbatimUrl = suggestion.type == VERBATIM_URL_TYPE;
745 var inlinableSuggestion = suggestion.type != VERBATIM_SEARCH_TYPE && 742 var inlineableSuggestion = suggestion.type != VERBATIM_SEARCH_TYPE &&
746 suggestion.rankingData.relevance > INLINE_SUGGESTION_THRESHOLD; 743 suggestion.rankingData.relevance > INLINE_SUGGESTION_THRESHOLD;
747 // Verbatim URLs should always be selected. Otherwise, select suggestions 744 // Verbatim URLs should always be selected. Otherwise, select suggestions
748 // with a high enough score unless we are in verbatim mode (e.g. backspacing 745 // with a high enough score unless we are in verbatim mode (e.g. backspacing
749 // away). 746 // away).
750 return isVerbatimUrl || (!inVerbatimMode && inlinableSuggestion); 747 return isVerbatimUrl || (!inVerbatimMode && inlineableSuggestion);
751 } 748 }
752 749
753 /** 750 /**
754 * Updates selectedIndex, bounding it between -1 and the total number of 751 * Extract the desired navigation behavior from a click button.
755 * of suggestions - 1 (looping as necessary), and selects the corresponding 752 * @param {number} button The Event#button property of a click event.
756 * suggestion. 753 * @return {WindowOpenDisposition} The desired behavior for
757 * @param {boolean} increment True to increment the selected suggestion, false 754 * navigateContentWindow.
758 * to decrement. 755 */
759 */ 756 function getDispositionFromClickButton(button) {
760 function updateSelectedSuggestion(increment) { 757 if (button == MIDDLE_MOUSE_BUTTON)
761 var numSuggestions = restrictedIds.length; 758 return WindowOpenDisposition.NEW_BACKGROUND_TAB;
762 if (!numSuggestions) 759 return WindowOpenDisposition.CURRENT_TAB;
763 return; 760 }
764 761
765 var oldSelection = $(IDS.SUGGESTIONS_BOX).querySelector('.selected'); 762
766 if (oldSelection) 763 /**
767 oldSelection.classList.remove(CLASSES.SELECTED); 764 * Manages a pool of chrome-search suggestion result iframes.
768 if (increment) { 765 * @constructor
769 if (selectedIndex > numSuggestions) 766 */
770 selectedIndex = -1; 767 function IframePool() {
771 else 768 }
772 ++selectedIndex; 769
770 IframePool.prototype = {
771 /**
772 * HTML iframe elements.
773 * @type {Array.<Element>}
774 * @private
775 */
776 iframes_: [],
777
778 /**
779 * Initializes the pool with blank result template iframes, positioned off
780 * screen.
781 */
782 init: function() {
783 for (var i = 0; i < 2 * MAX_SUGGESTIONS_TO_SHOW; ++i) {
784 var iframe = document.createElement('iframe');
785 iframe.className = CLASSES.SUGGESTION_CONTENTS;
786 iframe.id = IDS.SUGGESTION_TEXT_PREFIX + i;
787 iframe.src = 'chrome-search://suggestion/result.html';
788 iframe.style.top = OFF_SCREEN;
789 iframe.addEventListener('mouseover', function(e) {
790 if (activeBox)
791 activeBox.hover(e.currentTarget.id);
792 }, false);
793 iframe.addEventListener('mouseout', function(e) {
794 if (activeBox)
795 activeBox.unhover(e.currentTarget.id);
796 }, false);
797 document.body.appendChild(iframe);
798 this.iframes_.push(iframe);
799 }
800 },
801
802 /**
803 * Reserves a free suggestion iframe from the pool.
804 * @return {Element} An iframe suitable for holding a suggestion.
805 */
806 reserve: function() {
807 return this.iframes_.pop();
808 },
809
810 /**
811 * Releases a suggestion iframe back into the pool.
812 * @param {Element} iframe The iframe to return to the pool.
813 */
814 release: function(iframe) {
815 this.iframes_.push(iframe);
816 iframe.style.top = OFF_SCREEN;
817 },
818 };
819
820
821 /**
822 * An individual suggestion.
823 * @param {!Object} data Autocomplete fields for this suggestion.
824 * @constructor
825 */
826 function Suggestion(data) {
827 assert(data);
828 /**
829 * Autocomplete fields for this suggestion.
830 * @type {!Object}
831 * @private
832 */
833 this.data_ = data;
834 }
835
836 Suggestion.prototype = {
837 /**
838 * Releases the iframe reserved for this suggestion.
839 */
840 destroy: function() {
841 if (this.iframe_)
842 iframePool.release(this.iframe_);
843 },
844
845 /**
846 * Creates and appends the placeholder div for this suggestion to box.
847 * @param {Element} box A suggestions box.
848 * @param {boolean} selected True if the suggestion should be drawn as
849 * selected and false otherwise.
850 */
851 appendToBox: function(box, selected) {
852 var div = document.createElement('div');
853 div.classList.add(CLASSES.SUGGESTION);
854 div.classList.toggle(CLASSES.SELECTED, selected);
855 div.classList.toggle(CLASSES.SEARCH, this.data_.is_search);
856 box.appendChild(div);
857 this.div_ = div;
858 },
859
860 /**
861 * Repositions the suggestion iframe to align with its expected dropdown
862 * position.
863 * @param {boolean} isRtl True if rendering right-to-left and false if not.
864 * @param {number} startMargin Leading space before suggestion.
865 * @param {number} totalMargin Total non-content space on suggestion line.
866 */
867 reposition: function(isRtl, startMargin, totalMargin) {
868 // Add in the expected parent offset and the top margin.
869 this.iframe_.style.top = this.div_.offsetTop + SUGGESTION_TOP_OFFSET + 'px';
870 // Call parseInt to enforce that startMargin and totalMargin are really
871 // numbers since we're interpolating CSS.
872 startMargin = parseInt(startMargin, 10);
873 totalMargin = parseInt(totalMargin, 10);
874 if (isFinite(startMargin) && isFinite(totalMargin)) {
875 this.iframe_.style[isRtl ? 'right' : 'left'] = startMargin + 'px';
876 this.iframe_.style.width = '-webkit-calc(100% - ' +
877 (totalMargin + SUGGESTION_ICON_PADDING) + 'px)';
878 }
879 },
880
881 /**
882 * Updates the suggestion selection state.
883 * @param {boolean} selected True if drawn selected or false if not.
884 */
885 select: function(selected) {
886 this.div_.classList.toggle(CLASSES.SELECTED, selected);
887 },
888
889 /**
890 * Updates the suggestion hover state.
891 * @param {boolean} hovered True if drawn hovered or false if not.
892 */
893 hover: function(hovered) {
894 this.div_.classList.toggle(CLASSES.HOVERED, hovered);
895 },
896
897 /**
898 * @param {Window} iframeWindow The content window of an iframe.
899 * @return {boolean} True if this suggestion's iframe has the specified
900 * window and false if not.
901 */
902 hasIframeWindow: function(iframeWindow) {
903 return this.iframe_.contentWindow == iframeWindow;
904 },
905
906 /**
907 * @param {string} id An element id.
908 * @return {boolean} True if this suggestion's iframe has the specified id
909 * and false if not.
910 */
911 hasIframeId: function(id) {
912 return this.iframe_.id == id;
913 },
914
915 /**
916 * The iframe element for this suggestion.
917 * @type {Element}
918 */
919 set iframe(iframe) {
920 this.iframe_ = iframe;
921 },
922
923 /**
924 * The restricted id associated with this suggestion.
925 * @type {number}
926 */
927 get restrictedId() {
928 return this.data_.rid;
929 },
930 };
931
932
933 /**
934 * Displays a suggestions box.
935 * @param {string} inputValue The user text that prompted these suggestions.
936 * @param {!Array.<!Object>} suggestionData Suggestion data to display.
937 * @param {number} selectedIndex The index of the suggestion selected.
938 * @constructor
939 */
940 function SuggestionsBox(inputValue, suggestionData, selectedIndex) {
941 /**
942 * The user text that prompted these suggestions.
943 * @type {string}
944 * @private
945 */
946 this.inputValue_ = inputValue;
947
948 /**
949 * The index of the suggestion currently selected, whether by default or
950 * because the user arrowed down to it.
951 * @type {number}
952 * @private
953 */
954 this.selectedIndex_ = selectedIndex;
955
956 /**
957 * The index of the suggestion currently under the mouse pointer.
958 * @type {number}
959 * @private
960 */
961 this.hoveredIndex_ = -1;
962
963 /**
964 * A stamp to distinguish this suggestions box from others.
965 * @type {number}
966 * @private
967 */
968 this.requestId_ = nextRequestId++;
969
970 /**
971 * The ordered suggestions this box is displaying.
972 * @type {Array.<Suggestion>}
973 * @private
974 */
975 this.suggestions_ = [];
976 for (var i = 0; i < suggestionData.length; ++i) {
977 this.suggestions_.push(new Suggestion(suggestionData[i]));
978 }
979
980 /**
981 * The container for this suggestions box. div.pending-suggestion-container
982 * if inactive and div.active-suggestion-container if active.
983 * @type {Element}
984 * @private
985 */
986 this.container_ = $qs('.' + CLASSES.PENDING_SUGGESTIONS_CONTAINER);
987 assert(this.container_);
988 }
989
990 SuggestionsBox.prototype = {
991 /**
992 * Releases suggestion iframes and ignores any load done message for the
993 * current suggestions.
994 */
995 destroy: function() {
996 while (this.suggestions_.length > 0) {
997 this.suggestions_.pop().destroy();
998 }
999 this.responseId = -1;
1000 },
1001
1002 /**
1003 * Starts rendering new suggestions.
1004 */
1005 loadSuggestions: function() {
1006 // Create a placeholder DOM in the invisible container.
1007 this.container_.innerHTML = '';
1008
1009 var box = document.createElement('div');
1010 box.className = CLASSES.SUGGESTIONS_BOX;
1011 this.container_.appendChild(box);
1012
1013 var iframesToLoad = {};
1014 for (var i = 0; i < this.suggestions_.length; ++i) {
1015 var suggestion = this.suggestions_[i];
1016 suggestion.appendToBox(box, i == this.selectedIndex_);
1017 var iframe = iframePool.reserve();
1018 suggestion.iframe = iframe;
1019 iframesToLoad[iframe.id] = suggestion.restrictedId;
1020 }
1021
1022 // Ask the loader iframe to populate the iframes just reserved.
1023 var loadRequest = {
1024 load: iframesToLoad,
1025 requestId: this.requestId_,
1026 style: {
1027 queryColor: SUGGESTION_QUERY_COLOR,
1028 urlColor: SUGGESTION_URL_COLOR,
1029 titleColor: SUGGESTION_TITLE_COLOR
1030 }
1031 };
1032 $(IDS.SUGGESTION_LOADER).contentWindow.postMessage(loadRequest,
1033 SUGGESTION_ORIGIN);
1034 },
1035
1036 /**
1037 * @param {number} responseId The id of a request that just finished
1038 * rendering.
1039 * @return {boolean} Whether the request is for the suggestions in this box.
1040 */
1041 isResponseCurrent: function(responseId) {
1042 return responseId == this.requestId_;
1043 },
1044
1045 /**
1046 * Moves suggestion iframes into position.
1047 */
1048 repositionSuggestions: function() {
1049 // Note: This may be called before margins are ready. In that case,
1050 // suggestion iframes will initially be too large and then size down
1051 // onresize.
1052 var startMargin = searchboxApiHandle.startMargin;
1053 var totalMargin = window.innerWidth - searchboxApiHandle.width;
1054 var isRtl = searchboxApiHandle.isRtl;
1055 for (var i = 0; i < this.suggestions_.length; ++i) {
1056 this.suggestions_[i].reposition(isRtl, startMargin, totalMargin);
1057 }
1058 },
1059
1060 /**
1061 * Selects the suggestion before the current selection.
1062 */
1063 selectPrevious: function() {
1064 this.changeSelection_(this.selectedIndex_ - 1);
1065 },
1066
1067 /**
1068 * Selects the suggestion after the current selection.
1069 */
1070 selectNext: function() {
1071 this.changeSelection_(this.selectedIndex_ + 1);
1072 },
1073
1074 /**
1075 * Changes the current selected suggestion index.
1076 * @param {number} index The new selection to suggest.
1077 * @private
1078 */
1079 changeSelection_: function(index) {
1080 var numSuggestions = this.suggestions_.length;
1081 this.selectedIndex_ = Math.min(numSuggestions - 1, Math.max(-1, index));
1082
1083 this.redrawSelection_();
1084 this.redrawHover_();
1085 },
1086
1087 /**
1088 * Redraws the selected suggestion.
1089 * @private
1090 */
1091 redrawSelection_: function() {
1092 var oldSelection = this.container_.querySelector('.' + CLASSES.SELECTED);
1093 if (oldSelection)
1094 oldSelection.classList.remove(CLASSES.SELECTED);
1095 if (this.selectedIndex_ == -1) {
1096 searchboxApiHandle.setValue(this.inputValue_);
1097 } else {
1098 this.suggestions_[this.selectedIndex_].select(true);
1099 searchboxApiHandle.setRestrictedValue(
1100 this.suggestions_[this.selectedIndex_].restrictedId);
1101 }
1102 },
1103
1104 /**
1105 * @param {!Window} iframeWindow The window of the iframe that was clicked.
1106 * @return {?number} The restricted ID of the iframe that was clicked, or
1107 * null if there was none.
1108 */
1109 getClickTarget: function(iframeWindow) {
1110 for (var i = 0; i < this.suggestions_.length; ++i) {
1111 if (this.suggestions_[i].hasIframeWindow(iframeWindow))
1112 return this.suggestions_[i].restrictedId;
1113 }
1114 return null;
1115 },
1116
1117 /**
1118 * Called when the user hovers on the specified iframe to update hoveredIndex_
1119 * and draw a hover background.
1120 * @param {string} iframeId The id of the iframe hovered.
1121 */
1122 hover: function(iframeId) {
1123 this.hoveredIndex_ = -1;
1124 for (var i = 0; i < this.suggestions_.length; ++i) {
1125 if (this.suggestions_[i].hasIframeId(iframeId)) {
1126 this.hoveredIndex_ = i;
1127 break;
1128 }
1129 }
1130 this.redrawHover_();
1131 },
1132
1133 /**
1134 * Called when the user unhovers the specified iframe to clear the current
1135 * hover.
1136 * @param {string} iframeId The id of the iframe hovered.
1137 */
1138 unhover: function(iframeId) {
1139 if (this.suggestions_[this.hoveredIndex_] &&
1140 this.suggestions_[this.hoveredIndex_].hasIframeId(iframeId)) {
1141 this.clearHover();
1142 }
1143 },
1144
1145 /**
1146 * Clears the current hover.
1147 */
1148 clearHover: function() {
1149 this.hoveredIndex_ = -1;
1150 this.redrawHover_();
1151 },
1152
1153 /**
1154 * Redraws the mouse hover background.
1155 * @private
1156 */
1157 redrawHover_: function() {
1158 var oldHover = this.container_.querySelector('.' + CLASSES.HOVERED);
1159 if (oldHover)
1160 oldHover.classList.remove(CLASSES.HOVERED);
1161 if (this.hoveredIndex_ != -1 && this.hoveredIndex_ != this.selectedIndex_)
1162 this.suggestions_[this.hoveredIndex_].hover(true);
1163 },
1164
1165 /**
1166 * Marks the suggestions container as active.
1167 */
1168 activate: function() {
1169 this.container_.className = CLASSES.ACTIVE_SUGGESTIONS_CONTAINER;
1170 },
1171
1172 /**
1173 * Marks the suggestions container as inactive.
1174 */
1175 deactivate: function() {
1176 this.container_.className = CLASSES.PENDING_SUGGESTIONS_CONTAINER;
1177 this.container_.innerHTML = '';
1178 },
1179
1180 /**
1181 * The height of the suggestions container.
1182 * @type {number}
1183 */
1184 get height() {
1185 return this.container_.offsetHeight;
1186 },
1187 };
1188
1189
1190 /**
1191 * Clears the currently active suggestions and shows pending suggestions.
1192 */
1193 function makePendingSuggestionsActive() {
1194 if (activeBox) {
1195 activeBox.deactivate();
1196 activeBox.destroy();
773 } else { 1197 } else {
774 if (selectedIndex < 0) 1198 // Initially there will be no active suggestions, but we still want to use
775 selectedIndex = numSuggestions - 1; 1199 // div.active-container to load the next suggestions.
776 else 1200 $qs('.' + CLASSES.ACTIVE_SUGGESTIONS_CONTAINER).className =
777 --selectedIndex; 1201 CLASSES.PENDING_SUGGESTIONS_CONTAINER;
778 } 1202 }
779 1203 pendingBox.activate();
780 if (selectedIndex == -1) { 1204 activeBox = pendingBox;
781 searchboxApiHandle.setValue(lastInputValue); 1205 pendingBox = null;
782 } else { 1206 activeBox.repositionSuggestions();
783 var newSelection = $(IDS.SUGGESTIONS_BOX).querySelector( 1207 searchboxApiHandle.showOverlay(activeBox.height);
784 '.suggestion:nth-of-type(' + (selectedIndex + 1) + ')'); 1208 }
785 newSelection.classList.add(CLASSES.SELECTED); 1209
786 searchboxApiHandle.setRestrictedValue(restrictedIds[selectedIndex]); 1210 /**
1211 * Hides the active suggestions box.
1212 */
1213 function hideActiveSuggestions() {
1214 searchboxApiHandle.showOverlay(0);
1215 if (activeBox) {
1216 $qs('.' + CLASSES.ACTIVE_SUGGESTIONS_CONTAINER).innerHTML = '';
1217 activeBox.destroy();
787 } 1218 }
788 } 1219 activeBox = null;
789 1220 }
790 /** 1221
1222 /**
791 * Updates suggestions in response to a onchange or onnativesuggestions call. 1223 * Updates suggestions in response to a onchange or onnativesuggestions call.
792 */ 1224 */
793 function updateSuggestions() { 1225 function updateSuggestions() {
794 appendSuggestionStyles(); 1226 appendSuggestionStyles();
795 lastInputValue = searchboxApiHandle.value; 1227 if (pendingBox)
796 1228 pendingBox.destroy();
797 // Hide the NTP if input has made it into the omnibox. 1229 pendingBox = null;
798 var showNTP = lastInputValue == ''; 1230 var suggestions = searchboxApiHandle.nativeSuggestions;
799 updateNtpVisibility(showNTP); 1231 if (suggestions.length) {
800 1232 suggestions.sort(function(a, b) {
801 clearSuggestions(); 1233 return b.rankingData.relevance - a.rankingData.relevance;
802 if (showNTP) { 1234 });
803 searchboxApiHandle.showBars(); 1235 var selectedIndex = -1;
1236 if (shouldSelectSuggestion(suggestions[0], searchboxApiHandle.verbatim))
1237 selectedIndex = 0;
1238 // Don't display a search-what-you-typed suggestion if it's the top match.
1239 if (suggestions[0].type == VERBATIM_SEARCH_TYPE)
1240 suggestions.shift();
1241 }
1242 var inputValue = searchboxApiHandle.value;
1243 if (!!inputValue && suggestions.length) {
1244 pendingBox = new SuggestionsBox(inputValue,
1245 suggestions.slice(0, MAX_SUGGESTIONS_TO_SHOW), selectedIndex);
1246 pendingBox.loadSuggestions();
804 } else { 1247 } else {
805 var nativeSuggestions = searchboxApiHandle.nativeSuggestions; 1248 hideActiveSuggestions();
806 if (nativeSuggestions.length) {
807 nativeSuggestions.sort(function(a, b) {
808 return b.rankingData.relevance - a.rankingData.relevance;
809 });
810 if (shouldSelectSuggestion(
811 nativeSuggestions[0], searchboxApiHandle.verbatim)) {
812 selectedIndex = 0;
813 }
814
815 renderSuggestions(nativeSuggestions);
816 searchboxApiHandle.hideBars();
817 } else {
818 searchboxApiHandle.showBars();
819 }
820 } 1249 }
821
822 var height = getDropdownHeight();
823 searchboxApiHandle.showOverlay(height);
824 } 1250 }
825 1251
826 /** 1252 /**
827 * Appends a style node for suggestion properties that depend on apiHandle. 1253 * Appends a style node for suggestion properties that depend on apiHandle.
828 */ 1254 */
829 function appendSuggestionStyles() { 1255 function appendSuggestionStyles() {
830 if ($(IDS.SUGGESTION_STYLE)) 1256 if ($(IDS.SUGGESTION_STYLE))
831 return; 1257 return;
832 1258
833 var isRtl = searchboxApiHandle.rtl; 1259 var isRtl = searchboxApiHandle.rtl;
834 var startMargin = searchboxApiHandle.startMargin; 1260 var startMargin = searchboxApiHandle.startMargin;
835 var style = document.createElement('style'); 1261 var style = document.createElement('style');
836 style.type = 'text/css'; 1262 style.type = 'text/css';
837 style.id = IDS.SUGGESTION_STYLE; 1263 style.id = IDS.SUGGESTION_STYLE;
838 style.textContent = 1264 style.textContent =
839 '.suggestion, ' + 1265 '.suggestion, ' +
840 '.suggestion.search {' + 1266 '.suggestion.search {' +
841 ' background-position: ' + 1267 ' background-position: ' +
842 (isRtl ? '-webkit-calc(100% - 5px)' : '5px') + ' 4px;' + 1268 (isRtl ? '-webkit-calc(100% - 5px)' : '5px') + ' 4px;' +
843 ' -webkit-margin-start: ' + startMargin + 'px;' + 1269 ' -webkit-margin-start: ' + startMargin + 'px;' +
844 ' -webkit-margin-end: ' + 1270 ' -webkit-margin-end: ' +
845 (window.innerWidth - searchboxApiHandle.width - startMargin) + 'px;' + 1271 (window.innerWidth - searchboxApiHandle.width - startMargin) + 'px;' +
846 ' font: ' + searchboxApiHandle.fontSize + 'px "' + 1272 ' font: ' + searchboxApiHandle.fontSize + 'px "' +
847 searchboxApiHandle.font + '";' + 1273 searchboxApiHandle.font + '";' +
848 '}'; 1274 '}';
849 document.querySelector('head').appendChild(style); 1275 document.querySelector('head').appendChild(style);
850 } 1276 }
851 1277
852 /** 1278 /**
853 * Extract the desired navigation behavior from a click button. 1279 * Makes keys navigate through suggestions.
854 * @param {number} button The Event#button property of a click event. 1280 * @param {Object} e The key being pressed.
855 * @return {!WindowOpenDisposition} The desired behavior for
856 * navigateContentWindow.
857 */
858 function getDispositionFromClickButton(button) {
859 if (button == MIDDLE_MOUSE_BUTTON)
860 return WindowOpenDisposition.NEW_BACKGROUND_TAB;
861 return WindowOpenDisposition.CURRENT_TAB;
862 }
863
864 /**
865 * Handles suggestion clicks.
866 * @param {number} restrictedId The restricted id of the suggestion being
867 * clicked.
868 * @param {number} button The Event#button property of a click event.
869 *
870 */
871 function handleSuggestionClick(restrictedId, button) {
872 clearSuggestions();
873 searchboxApiHandle.navigateContentWindow(
874 restrictedId, getDispositionFromClickButton(button));
875 }
876
877 /**
878 * chrome.searchBox.onkeypress implementation.
879 * @param {!Event} e The key being pressed.
880 */ 1281 */
881 function handleKeyPress(e) { 1282 function handleKeyPress(e) {
1283 if (!activeBox)
1284 return;
1285
882 switch (e.keyCode) { 1286 switch (e.keyCode) {
883 case 38: // Up arrow. 1287 case KEY_UP_ARROW:
884 updateSelectedSuggestion(false); 1288 activeBox.selectPrevious();
885 break; 1289 break;
886 case 40: // Down arrow. 1290 case KEY_DOWN_ARROW:
887 updateSelectedSuggestion(true); 1291 activeBox.selectNext();
888 break; 1292 break;
889 } 1293 }
890 } 1294 }
891 1295
892 /** 1296 /**
893 * Handles the postMessage calls from the result iframes. 1297 * Handles postMessage calls from suggestion iframes.
894 * @param {Object} message The message containing details of clicks the iframes. 1298 * @param {Object} message A notification that all iframes are done loading or
1299 * that an iframe was clicked.
895 */ 1300 */
896 function handleMessage(message) { 1301 function handleMessage(message) {
897 if (message.origin != 'null' || !message.data || 1302 if (message.origin != SUGGESTION_ORIGIN)
898 message.data.eventType != 'click') {
899 return; 1303 return;
900 }
901 1304
902 var iframes = document.getElementsByClassName('contents'); 1305 if ('loaded' in message.data) {
903 for (var i = 0; i < iframes.length; ++i) { 1306 if (pendingBox && pendingBox.isResponseCurrent(message.data.loaded))
904 if (iframes[i].contentWindow == message.source) { 1307 makePendingSuggestionsActive();
905 handleSuggestionClick(parseInt(iframes[i].id, 10), 1308 } else if ('click' in message.data) {
906 message.data.button); 1309 if (activeBox) {
907 break; 1310 var restrictedId = activeBox.getClickTarget(message.source);
1311 if (restrictedId != null) {
1312 hideActiveSuggestions();
1313 searchboxApiHandle.navigateContentWindow(restrictedId,
1314 getDispositionFromClickButton(message.data.click));
1315 }
908 } 1316 }
909 } 1317 }
910 } 1318 }
911 1319
912 // ============================================================================= 1320 // =============================================================================
913 // Utils 1321 // Utils
914 // ============================================================================= 1322 // =============================================================================
915 1323
916 /** 1324 /**
917 * Shortcut for document.getElementById. 1325 * Shortcut for document.getElementById.
918 * @param {string} id of the element. 1326 * @param {string} id of the element.
919 * @return {HTMLElement} with the id. 1327 * @return {HTMLElement} with the id.
920 */ 1328 */
921 function $(id) { 1329 function $(id) {
922 return document.getElementById(id); 1330 return document.getElementById(id);
923 } 1331 }
924 1332
925 /** 1333 /**
1334 * Shortcut for document.querySelector.
1335 * @param {string} selector A selector to query the desired element.
1336 * @return {HTMLElement} The first element to match |selector| or null.
1337 */
1338 function $qs(selector) {
1339 return document.querySelector(selector);
1340 }
1341
1342 /**
926 * Utility function which creates an element with an optional classname and 1343 * Utility function which creates an element with an optional classname and
927 * appends it to the specified parent. 1344 * appends it to the specified parent.
928 * @param {Element} parent The parent to append the new element. 1345 * @param {Element} parent The parent to append the new element.
929 * @param {string} name The name of the new element. 1346 * @param {string} name The name of the new element.
930 * @param {string=} opt_class The optional classname of the new element. 1347 * @param {string=} opt_class The optional classname of the new element.
931 * @return {Element} The new element. 1348 * @return {Element} The new element.
932 */ 1349 */
933 function createAndAppendElement(parent, name, opt_class) { 1350 function createAndAppendElement(parent, name, opt_class) {
934 var child = document.createElement(name); 1351 var child = document.createElement(name);
935 if (opt_class) 1352 if (opt_class)
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
968 // ============================================================================= 1385 // =============================================================================
969 // Initialization 1386 // Initialization
970 // ============================================================================= 1387 // =============================================================================
971 1388
972 /** 1389 /**
973 * Prepares the New Tab Page by adding listeners, rendering the current 1390 * Prepares the New Tab Page by adding listeners, rendering the current
974 * theme, the most visited pages section, and Google-specific elements for a 1391 * theme, the most visited pages section, and Google-specific elements for a
975 * Google-provided page. 1392 * Google-provided page.
976 */ 1393 */
977 function init() { 1394 function init() {
1395 iframePool = new IframePool();
1396 iframePool.init();
978 topMarginElement = $(IDS.TOP_MARGIN); 1397 topMarginElement = $(IDS.TOP_MARGIN);
979 tilesContainer = $(IDS.TILES); 1398 tilesContainer = $(IDS.TILES);
980 notification = $(IDS.NOTIFICATION); 1399 notification = $(IDS.NOTIFICATION);
981 attribution = $(IDS.ATTRIBUTION); 1400 attribution = $(IDS.ATTRIBUTION);
982 ntpContents = $(IDS.NTP_CONTENTS); 1401 ntpContents = $(IDS.NTP_CONTENTS);
983 1402
984 if (isGooglePage) { 1403 if (isGooglePage) {
985 document.body.classList.add(CLASSES.GOOGLE_PAGE); 1404 document.body.classList.add(CLASSES.GOOGLE_PAGE);
986 var logo = document.createElement('div'); 1405 var logo = document.createElement('div');
987 logo.id = IDS.LOGO; 1406 logo.id = IDS.LOGO;
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
1024 onMostVisitedChange(); 1443 onMostVisitedChange();
1025 1444
1026 searchboxApiHandle = topLevelHandle.searchBox; 1445 searchboxApiHandle = topLevelHandle.searchBox;
1027 searchboxApiHandle.onnativesuggestions = updateSuggestions; 1446 searchboxApiHandle.onnativesuggestions = updateSuggestions;
1028 searchboxApiHandle.onchange = updateSuggestions; 1447 searchboxApiHandle.onchange = updateSuggestions;
1029 searchboxApiHandle.onkeypress = handleKeyPress; 1448 searchboxApiHandle.onkeypress = handleKeyPress;
1030 searchboxApiHandle.onsubmit = function() { 1449 searchboxApiHandle.onsubmit = function() {
1031 var value = searchboxApiHandle.value; 1450 var value = searchboxApiHandle.value;
1032 if (!value) { 1451 if (!value) {
1033 // Interpret onsubmit with an empty query as an ESC key press. 1452 // Interpret onsubmit with an empty query as an ESC key press.
1034 clearSuggestions(); 1453 hideActiveSuggestions();
1035 updateNtpVisibility(true); 1454 updateNtpVisibility(true);
1036 } 1455 }
1037 }; 1456 };
1038 1457 $qs('.' + CLASSES.ACTIVE_SUGGESTIONS_CONTAINER).dir =
1039 $(IDS.SUGGESTIONS_CONTAINER).dir = searchboxApiHandle.rtl ? 'rtl' : 'ltr'; 1458 searchboxApiHandle.rtl ? 'rtl' : 'ltr';
1459 $qs('.' + CLASSES.PENDING_SUGGESTIONS_CONTAINER).dir =
1460 searchboxApiHandle.rtl ? 'rtl' : 'ltr';
1040 1461
1041 if (!document.webkitHidden) 1462 if (!document.webkitHidden)
1042 window.addEventListener('resize', addDelayedTransitions); 1463 window.addEventListener('resize', addDelayedTransitions);
1043 else 1464 else
1044 document.addEventListener('webkitvisibilitychange', addDelayedTransitions); 1465 document.addEventListener('webkitvisibilitychange', addDelayedTransitions);
1045 1466
1046 if (fakebox) { 1467 if (fakebox) {
1047 // Listener for updating the fakebox focus. 1468 // Listener for updating the fakebox focus.
1048 document.body.onclick = function(event) { 1469 document.body.onclick = function(event) {
1049 if (isFakeboxClick(event)) { 1470 if (isFakeboxClick(event)) {
(...skipping 21 matching lines...) Expand all
1071 '-webkit-transform 100ms linear, width 200ms ease'; 1492 '-webkit-transform 100ms linear, width 200ms ease';
1072 } 1493 }
1073 1494
1074 tilesContainer.style.webkitTransition = 'width 200ms'; 1495 tilesContainer.style.webkitTransition = 'width 200ms';
1075 window.removeEventListener('resize', addDelayedTransitions); 1496 window.removeEventListener('resize', addDelayedTransitions);
1076 document.removeEventListener('webkitvisibilitychange', addDelayedTransitions); 1497 document.removeEventListener('webkitvisibilitychange', addDelayedTransitions);
1077 } 1498 }
1078 1499
1079 document.addEventListener('DOMContentLoaded', init); 1500 document.addEventListener('DOMContentLoaded', init);
1080 window.addEventListener('message', handleMessage, false); 1501 window.addEventListener('message', handleMessage, false);
1502 window.addEventListener('blur', function() {
1503 if (activeBox)
1504 activeBox.clearHover();
1505 }, false);
1081 })(); 1506 })();
OLDNEW
« no previous file with comments | « chrome/browser/resources/local_ntp/local_ntp.html ('k') | chrome/browser/resources/omnibox_result.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698