Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / resources / omnibox / omnibox.js
blob242de52e75879a53153c5a96f67f731feb34348a
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /**
6  * Javascript for omnibox.html, served from chrome://omnibox/
7  * This is used to debug omnibox ranking.  The user enters some text
8  * into a box, submits it, and then sees lots of debug information
9  * from the autocompleter that shows what omnibox would do with that
10  * input.
11  *
12  * The simple object defined in this javascript file listens for
13  * certain events on omnibox.html, sends (when appropriate) the
14  * input text to C++ code to start the omnibox autcomplete controller
15  * working, and listens from callbacks from the C++ code saying that
16  * results are available.  When results (possibly intermediate ones)
17  * are available, the Javascript formats them and displays them.
18  */
19 cr.define('omniboxDebug', function() {
20   'use strict';
22   /**
23    * Register our event handlers.
24    */
25   function initialize() {
26     $('omnibox-input-form').addEventListener(
27         'submit', startOmniboxQuery, false);
28     $('prevent-inline-autocomplete').addEventListener(
29         'change', startOmniboxQuery);
30     $('prefer-keyword').addEventListener('change', startOmniboxQuery);
31     $('show-details').addEventListener('change', refresh);
32     $('show-incomplete-results').addEventListener('change', refresh);
33     $('show-all-providers').addEventListener('change', refresh);
34   }
36   /**
37    * @type {Array.<Object>} an array of all autocomplete results we've seen
38    *     for this query.  We append to this list once for every call to
39    *     handleNewAutocompleteResult.  For details on the structure of
40    *     the object inside, see the comments by addResultToOutput.
41    */
42   var progressiveAutocompleteResults = [];
44   /**
45    * @type {number} the value for cursor position we sent with the most
46    *     recent request.  We need to remember this in order to display it
47    *     in the output; otherwise it's hard or impossible to determine
48    *     from screen captures or print-to-PDFs.
49    */
50   var cursorPositionUsed = -1;
52   /**
53    * Extracts the input text from the text field and sends it to the
54    * C++ portion of chrome to handle.  The C++ code will iteratively
55    * call handleNewAutocompleteResult as results come in.
56    */
57   function startOmniboxQuery(event) {
58     // First, clear the results of past calls (if any).
59     progressiveAutocompleteResults = [];
60     // Then, call chrome with a four-element list:
61     // - first element: the value in the text box
62     // - second element: the location of the cursor in the text box
63     // - third element: the value of prevent-inline-autocomplete
64     // - forth element: the value of prefer-keyword
65     cursorPositionUsed = $('input-text').selectionEnd;
66     chrome.send('startOmniboxQuery', [
67         $('input-text').value,
68         cursorPositionUsed,
69         $('prevent-inline-autocomplete').checked,
70         $('prefer-keyword').checked]);
71     // Cancel the submit action.  i.e., don't submit the form.  (We handle
72     // display the results solely with Javascript.)
73     event.preventDefault();
74   }
76   /**
77    * Returns a simple object with information about how to display an
78    * autocomplete result data field.
79    * @param {string} header the label for the top of the column/table.
80    * @param {string} urlLabelForHeader the URL that the header should point
81    *     to (if non-empty).
82    * @param {string} propertyName the name of the property in the autocomplete
83    *     result record that we lookup.
84    * @param {boolean} displayAlways whether the property should be displayed
85    *     regardless of whether we're in detailed more.
86    * @param {string} tooltip a description of the property that will be
87    *     presented as a tooltip when the mouse is hovered over the column title.
88    * @constructor
89    */
90   function PresentationInfoRecord(header, url, propertyName, displayAlways,
91                                   tooltip) {
92     this.header = header;
93     this.urlLabelForHeader = url;
94     this.propertyName = propertyName;
95     this.displayAlways = displayAlways;
96     this.tooltip = tooltip;
97   }
99   /**
100    * A constant that's used to decide what autocomplete result
101    * properties to output in what order.  This is an array of
102    * PresentationInfoRecord() objects; for details see that
103    * function.
104    * @type {Array.<Object>}
105    * @const
106    */
107   var PROPERTY_OUTPUT_ORDER = [
108     new PresentationInfoRecord('Provider', '', 'provider_name', true,
109         'The AutocompleteProvider suggesting this result.'),
110     new PresentationInfoRecord('Type', '', 'type', true,
111         'The type of the result.'),
112     new PresentationInfoRecord('Relevance', '', 'relevance', true,
113         'The result score. Higher is more relevant.'),
114     new PresentationInfoRecord('Contents', '', 'contents', true,
115         'The text that is presented identifying the result.'),
116     new PresentationInfoRecord(
117         'Can Be Default', '', 'allowed_to_be_default_match', false,
118         'A green checkmark indicates that the result can be the default ' +
119         'match (i.e., can be the match that pressing enter in the omnibox ' +
120         'navigates to).'),
121     new PresentationInfoRecord('Starred', '', 'starred', false,
122         'A green checkmark indicates that the result has been bookmarked.'),
123     new PresentationInfoRecord(
124         'HWYT', '', 'is_history_what_you_typed_match', false,
125         'A green checkmark indicates that the result is an History What You ' +
126         'Typed Match'),
127     new PresentationInfoRecord('Description', '', 'description', false,
128         'The page title of the result.'),
129     new PresentationInfoRecord('URL', '', 'destination_url', true,
130         'The URL for the result.'),
131     new PresentationInfoRecord('Fill Into Edit', '', 'fill_into_edit', false,
132         'The text shown in the omnibox when the result is selected.'),
133     new PresentationInfoRecord(
134         'Inline Autocompletion', '', 'inline_autocompletion', false,
135         'The text shown in the omnibox as a blue highlight selection ' +
136         'following the cursor, if this match is shown inline.'),
137     new PresentationInfoRecord('Del', '', 'deletable', false,
138         'A green checkmark indicates that the result can be deleted from ' +
139         'the visit history.'),
140     new PresentationInfoRecord('Prev', '', 'from_previous', false, ''),
141     new PresentationInfoRecord(
142         'Tran',
143         'http://code.google.com/codesearch#OAMlx_jo-ck/src/content/public/' +
144         'common/page_transition_types.h&exact_package=chromium&l=24',
145         'transition', false,
146         'How the user got to the result.'),
147     new PresentationInfoRecord(
148         'Done', '', 'provider_done', false,
149         'A green checkmark indicates that the provider is done looking for ' +
150         'more results.'),
151     new PresentationInfoRecord(
152         'Template URL', '', 'template_url', false, ''),
153     new PresentationInfoRecord(
154         'Associated Keyword', '', 'associated_keyword', false,
155         'If non-empty, a "press tab to search" hint will be shown and will ' +
156         'engage this keyword.'),
157     new PresentationInfoRecord(
158         'Keyword', '', 'keyword', false,
159         'The keyword of the search engine to be used.'),
160     new PresentationInfoRecord(
161         'Additional Info', '', 'additional_info', false,
162         'Provider-specific information about the result.')
163   ];
165   /**
166    * Returns an HTML Element of type table row that contains the
167    * headers we'll use for labeling the columns.  If we're in
168    * detailed_mode, we use all the headers.  If not, we only use ones
169    * marked displayAlways.
170    */
171   function createAutocompleteResultTableHeader() {
172     var row = document.createElement('tr');
173     var inDetailedMode = $('show-details').checked;
174     for (var i = 0; i < PROPERTY_OUTPUT_ORDER.length; i++) {
175       if (inDetailedMode || PROPERTY_OUTPUT_ORDER[i].displayAlways) {
176         var headerCell = document.createElement('th');
177         if (PROPERTY_OUTPUT_ORDER[i].urlLabelForHeader != '') {
178           // Wrap header text in URL.
179           var linkNode = document.createElement('a');
180           linkNode.href = PROPERTY_OUTPUT_ORDER[i].urlLabelForHeader;
181           linkNode.textContent = PROPERTY_OUTPUT_ORDER[i].header;
182           headerCell.appendChild(linkNode);
183         } else {
184           // Output header text without a URL.
185           headerCell.textContent = PROPERTY_OUTPUT_ORDER[i].header;
186           headerCell.className = 'table-header';
187           headerCell.title = PROPERTY_OUTPUT_ORDER[i].tooltip;
188         }
189         row.appendChild(headerCell);
190       }
191     }
192     return row;
193   }
195   /**
196    * @param {Object} autocompleteSuggestion the particular autocomplete
197    *     suggestion we're in the process of displaying.
198    * @param {string} propertyName the particular property of the autocomplete
199    *     suggestion that should go in this cell.
200    * @return {HTMLTableCellElement} that contains the value within this
201    *     autocompleteSuggestion associated with propertyName.
202    */
203   function createCellForPropertyAndRemoveProperty(autocompleteSuggestion,
204                                                   propertyName) {
205     var cell = document.createElement('td');
206     if (propertyName in autocompleteSuggestion) {
207       if (propertyName == 'additional_info') {
208         // |additional_info| embeds a two-column table of provider-specific data
209         // within this cell.
210         var additionalInfoTable = document.createElement('table');
211         for (var additionalInfoKey in autocompleteSuggestion[propertyName]) {
212           var additionalInfoRow = document.createElement('tr');
214           // Set the title (name of property) cell text.
215           var propertyCell = document.createElement('td');
216           propertyCell.textContent = additionalInfoKey + ':';
217           propertyCell.className = 'additional-info-property';
218           additionalInfoRow.appendChild(propertyCell);
220           // Set the value of the property cell text.
221           var valueCell = document.createElement('td');
222           valueCell.textContent =
223               autocompleteSuggestion[propertyName][additionalInfoKey];
224           valueCell.className = 'additional-info-value';
225           additionalInfoRow.appendChild(valueCell);
227           additionalInfoTable.appendChild(additionalInfoRow);
228         }
229         cell.appendChild(additionalInfoTable);
230       } else if (typeof autocompleteSuggestion[propertyName] == 'boolean') {
231         // If this is a boolean, display a checkmark or an X instead of
232         // the strings true or false.
233         if (autocompleteSuggestion[propertyName]) {
234           cell.className = 'check-mark';
235           cell.textContent = '✔';
236         } else {
237           cell.className = 'x-mark';
238           cell.textContent = '✗';
239         }
240       } else {
241         var text = String(autocompleteSuggestion[propertyName]);
242         // If it's a URL wrap it in an href.
243         var re = /^(http|https|ftp|chrome|file):\/\//;
244         if (re.test(text)) {
245           var aCell = document.createElement('a');
246           aCell.textContent = text;
247           aCell.href = text;
248           cell.appendChild(aCell);
249         } else {
250           // All other data types (integer, strings, etc.) display their
251           // normal toString() output.
252           cell.textContent = autocompleteSuggestion[propertyName];
253         }
254       }
255     }  // else: if propertyName is undefined, we leave the cell blank
256     return cell;
257   }
259   /**
260    * Called by C++ code when we get an update from the
261    * AutocompleteController.  We simply append the result to
262    * progressiveAutocompleteResults and refresh the page.
263    */
264   function handleNewAutocompleteResult(result) {
265     progressiveAutocompleteResults.push(result);
266     refresh();
267   }
269   /**
270    * Appends some human-readable information about the provided
271    * autocomplete result to the HTML node with id omnibox-debug-text.
272    * The current human-readable form is a few lines about general
273    * autocomplete result statistics followed by a table with one line
274    * for each autocomplete match.  The input parameter result is a
275    * complex Object with lots of information about various
276    * autocomplete matches.  Here's an example of what it looks like:
277    * <pre>
278    * {@code
279    * {
280    *   'done': false,
281    *   'time_since_omnibox_started_ms': 15,
282    *   'host': 'mai',
283    *   'is_typed_host': false,
284    *   'combined_results' : {
285    *     'num_items': 4,
286    *     'item_0': {
287    *       'destination_url': 'http://mail.google.com',
288    *       'provider_name': 'HistoryURL',
289    *       'relevance': 1410,
290    *       ...
291    *     }
292    *     'item_1: {
293    *       ...
294    *     }
295    *     ...
296    *   }
297    *   'results_by_provider': {
298    *     'HistoryURL' : {
299    *       'num_items': 3,
300    *         ...
301    *       }
302    *     'Search' : {
303    *       'num_items': 1,
304    *       ...
305    *     }
306    *     ...
307    *   }
308    * }
309    * }
310    * </pre>
311    * For more information on how the result is packed, see the
312    * corresponding code in chrome/browser/ui/webui/omnibox_ui.cc
313    */
314   function addResultToOutput(result) {
315     var output = $('omnibox-debug-text');
316     var inDetailedMode = $('show-details').checked;
317     var showIncompleteResults = $('show-incomplete-results').checked;
318     var showPerProviderResults = $('show-all-providers').checked;
320     // Always output cursor position.
321     var p = document.createElement('p');
322     p.textContent = 'cursor position = ' + cursorPositionUsed;
323     output.appendChild(p);
325     // Output the result-level features in detailed mode and in
326     // show incomplete results mode.  We do the latter because without
327     // these result-level features, one can't make sense of each
328     // batch of results.
329     if (inDetailedMode || showIncompleteResults) {
330       var p1 = document.createElement('p');
331       p1.textContent = 'elapsed time = ' +
332           result.time_since_omnibox_started_ms + 'ms';
333       output.appendChild(p1);
334       var p2 = document.createElement('p');
335       p2.textContent = 'all providers done = ' + result.done;
336       output.appendChild(p2);
337       var p3 = document.createElement('p');
338       p3.textContent = 'host = ' + result.host;
339       if ('is_typed_host' in result) {
340         // Only output the is_typed_host information if available.  (It may
341         // be missing if the history database lookup failed.)
342         p3.textContent = p3.textContent + ' has is_typed_host = ' +
343             result.is_typed_host;
344       }
345       output.appendChild(p3);
346     }
348     // Combined results go after the lines below.
349     var group = document.createElement('a');
350     group.className = 'group-separator';
351     group.textContent = 'Combined results.';
352     output.appendChild(group);
354     // Add combined/merged result table.
355     var p = document.createElement('p');
356     p.appendChild(addResultTableToOutput(result.combined_results));
357     output.appendChild(p);
359     // Move forward only if you want to display per provider results.
360     if (!showPerProviderResults) {
361       return;
362     }
364     // Individual results go after the lines below.
365     var group = document.createElement('a');
366     group.className = 'group-separator';
367     group.textContent = 'Results for individual providers.';
368     output.appendChild(group);
370     // Add the per-provider result tables with labels. We do not append the
371     // combined/merged result table since we already have the per provider
372     // results.
373     for (var provider in result.results_by_provider) {
374       var results = result.results_by_provider[provider];
375       // If we have no results we do not display anything.
376       if (results.num_items == 0) {
377         continue;
378       }
379       var p = document.createElement('p');
380       p.appendChild(addResultTableToOutput(results));
381       output.appendChild(p);
382     }
383   }
385   /**
386    * @param {Object} result either the combined_results component of
387    *     the structure described in the comment by addResultToOutput()
388    *     above or one of the per-provider results in the structure.
389    *     (Both have the same format).
390    * @return {HTMLTableCellElement} that is a user-readable HTML
391    *     representation of this object.
392    */
393   function addResultTableToOutput(result) {
394     var inDetailedMode = $('show-details').checked;
395     // Create a table to hold all the autocomplete items.
396     var table = document.createElement('table');
397     table.className = 'autocomplete-results-table';
398     table.appendChild(createAutocompleteResultTableHeader());
399     // Loop over every autocomplete item and add it as a row in the table.
400     for (var i = 0; i < result.num_items; i++) {
401       var autocompleteSuggestion = result['item_' + i];
402       var row = document.createElement('tr');
403       // Loop over all the columns/properties and output either them
404       // all (if we're in detailed mode) or only the ones marked displayAlways.
405       // Keep track of which properties we displayed.
406       var displayedProperties = {};
407       for (var j = 0; j < PROPERTY_OUTPUT_ORDER.length; j++) {
408         if (inDetailedMode || PROPERTY_OUTPUT_ORDER[j].displayAlways) {
409           row.appendChild(createCellForPropertyAndRemoveProperty(
410               autocompleteSuggestion, PROPERTY_OUTPUT_ORDER[j].propertyName));
411           displayedProperties[PROPERTY_OUTPUT_ORDER[j].propertyName] = true;
412         }
413       }
415       // Now, if we're in detailed mode, add all the properties that
416       // haven't already been output.  (We know which properties have
417       // already been output because we delete the property when we output
418       // it.  The only way we have properties left at this point if
419       // we're in detailed mode and we're getting back properties
420       // not listed in PROPERTY_OUTPUT_ORDER.  Perhaps someone added
421       // something to the C++ code but didn't bother to update this
422       // Javascript?  In any case, we want to display them.)
423       if (inDetailedMode) {
424         for (var key in autocompleteSuggestion) {
425           if (!displayedProperties[key]) {
426             var cell = document.createElement('td');
427             cell.textContent = key + '=' + autocompleteSuggestion[key];
428             row.appendChild(cell);
429           }
430         }
431       }
433       table.appendChild(row);
434     }
435     return table;
436   }
438   /* Repaints the page based on the contents of the array
439    * progressiveAutocompleteResults, which represents consecutive
440    * autocomplete results.  We only display the last (most recent)
441    * entry unless we're asked to display incomplete results.  For an
442    * example of the output, play with chrome://omnibox/
443    */
444   function refresh() {
445     // Erase whatever is currently being displayed.
446     var output = $('omnibox-debug-text');
447     output.innerHTML = '';
449     if (progressiveAutocompleteResults.length > 0) {  // if we have results
450       // Display the results.
451       var showIncompleteResults = $('show-incomplete-results').checked;
452       var startIndex = showIncompleteResults ? 0 :
453           progressiveAutocompleteResults.length - 1;
454       for (var i = startIndex; i < progressiveAutocompleteResults.length; i++) {
455         addResultToOutput(progressiveAutocompleteResults[i]);
456       }
457     }
458   }
460   return {
461     initialize: initialize,
462     startOmniboxQuery: startOmniboxQuery,
463     handleNewAutocompleteResult: handleNewAutocompleteResult
464   };
467 document.addEventListener('DOMContentLoaded', omniboxDebug.initialize);