- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / omnibox / omnibox.js
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.
4
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';
21
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   }
35
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 = [];
43
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;
51
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   }
75
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   }
98
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('Starred', '', 'starred', false,
117         'A green checkmark indicates that the result has been bookmarked.'),
118     new PresentationInfoRecord(
119         'HWYT', '', 'is_history_what_you_typed_match', false,
120         'A green checkmark indicates that the result is an History What You ' +
121         'Typed Match'),
122     new PresentationInfoRecord('Description', '', 'description', false,
123         'The page title of the result.'),
124     new PresentationInfoRecord('URL', '', 'destination_url', true,
125         'The URL for the result.'),
126     new PresentationInfoRecord('Fill Into Edit', '', 'fill_into_edit', false,
127         'The text shown in the omnibox when the result is selected.'),
128     new PresentationInfoRecord(
129         'Inline Autocompletion', '', 'inline_autocompletion', false,
130         'The text shown in the omnibox as a blue highlight selection ' +
131         'following the cursor, if this match is shown inline.'),
132     new PresentationInfoRecord('Del', '', 'deletable', false,
133         'A green checkmark indicates that the results can be deleted from ' +
134         'the visit history.'),
135     new PresentationInfoRecord('Prev', '', 'from_previous', false, ''),
136     new PresentationInfoRecord(
137         'Tran',
138         'http://code.google.com/codesearch#OAMlx_jo-ck/src/content/public/' +
139         'common/page_transition_types.h&exact_package=chromium&l=24',
140         'transition', false,
141         'How the user got to the result.'),
142     new PresentationInfoRecord(
143         'Done', '', 'provider_done', false,
144         'A green checkmark indicates that the provider is done looking for ' +
145         'more results.'),
146     new PresentationInfoRecord(
147         'Template URL', '', 'template_url', false, ''),
148     new PresentationInfoRecord(
149         'Associated Keyword', '', 'associated_keyword', false,
150         'If non-empty, a "press tab to search" hint will be shown and will ' +
151         'engage this keyword.'),
152     new PresentationInfoRecord(
153         'Keyword', '', 'keyword', false,
154         'The keyword of the search engine to be used.'),
155     new PresentationInfoRecord(
156         'Additional Info', '', 'additional_info', false,
157         'Provider-specific information about the result.')
158   ];
159
160   /**
161    * Returns an HTML Element of type table row that contains the
162    * headers we'll use for labeling the columns.  If we're in
163    * detailed_mode, we use all the headers.  If not, we only use ones
164    * marked displayAlways.
165    */
166   function createAutocompleteResultTableHeader() {
167     var row = document.createElement('tr');
168     var inDetailedMode = $('show-details').checked;
169     for (var i = 0; i < PROPERTY_OUTPUT_ORDER.length; i++) {
170       if (inDetailedMode || PROPERTY_OUTPUT_ORDER[i].displayAlways) {
171         var headerCell = document.createElement('th');
172         if (PROPERTY_OUTPUT_ORDER[i].urlLabelForHeader != '') {
173           // Wrap header text in URL.
174           var linkNode = document.createElement('a');
175           linkNode.href = PROPERTY_OUTPUT_ORDER[i].urlLabelForHeader;
176           linkNode.textContent = PROPERTY_OUTPUT_ORDER[i].header;
177           headerCell.appendChild(linkNode);
178         } else {
179           // Output header text without a URL.
180           headerCell.textContent = PROPERTY_OUTPUT_ORDER[i].header;
181           headerCell.className = 'table-header';
182           headerCell.title = PROPERTY_OUTPUT_ORDER[i].tooltip;
183         }
184         row.appendChild(headerCell);
185       }
186     }
187     return row;
188   }
189
190   /**
191    * @param {Object} autocompleteSuggestion the particular autocomplete
192    *     suggestion we're in the process of displaying.
193    * @param {string} propertyName the particular property of the autocomplete
194    *     suggestion that should go in this cell.
195    * @return {HTMLTableCellElement} that contains the value within this
196    *     autocompleteSuggestion associated with propertyName.
197    */
198   function createCellForPropertyAndRemoveProperty(autocompleteSuggestion,
199                                                   propertyName) {
200     var cell = document.createElement('td');
201     if (propertyName in autocompleteSuggestion) {
202       if (propertyName == 'additional_info') {
203         // |additional_info| embeds a two-column table of provider-specific data
204         // within this cell.
205         var additionalInfoTable = document.createElement('table');
206         for (var additionalInfoKey in autocompleteSuggestion[propertyName]) {
207           var additionalInfoRow = document.createElement('tr');
208
209           // Set the title (name of property) cell text.
210           var propertyCell = document.createElement('td');
211           propertyCell.textContent = additionalInfoKey + ':';
212           propertyCell.className = 'additional-info-property';
213           additionalInfoRow.appendChild(propertyCell);
214
215           // Set the value of the property cell text.
216           var valueCell = document.createElement('td');
217           valueCell.textContent =
218               autocompleteSuggestion[propertyName][additionalInfoKey];
219           valueCell.className = 'additional-info-value';
220           additionalInfoRow.appendChild(valueCell);
221
222           additionalInfoTable.appendChild(additionalInfoRow);
223         }
224         cell.appendChild(additionalInfoTable);
225       } else if (typeof autocompleteSuggestion[propertyName] == 'boolean') {
226         // If this is a boolean, display a checkmark or an X instead of
227         // the strings true or false.
228         if (autocompleteSuggestion[propertyName]) {
229           cell.className = 'check-mark';
230           cell.textContent = '✔';
231         } else {
232           cell.className = 'x-mark';
233           cell.textContent = '✗';
234         }
235       } else {
236         var text = String(autocompleteSuggestion[propertyName]);
237         // If it's a URL wrap it in an href.
238         var re = /^(http|https|ftp|chrome|file):\/\//;
239         if (re.test(text)) {
240           var aCell = document.createElement('a');
241           aCell.textContent = text;
242           aCell.href = text;
243           cell.appendChild(aCell);
244         } else {
245           // All other data types (integer, strings, etc.) display their
246           // normal toString() output.
247           cell.textContent = autocompleteSuggestion[propertyName];
248         }
249       }
250     }  // else: if propertyName is undefined, we leave the cell blank
251     return cell;
252   }
253
254   /**
255    * Called by C++ code when we get an update from the
256    * AutocompleteController.  We simply append the result to
257    * progressiveAutocompleteResults and refresh the page.
258    */
259   function handleNewAutocompleteResult(result) {
260     progressiveAutocompleteResults.push(result);
261     refresh();
262   }
263
264   /**
265    * Appends some human-readable information about the provided
266    * autocomplete result to the HTML node with id omnibox-debug-text.
267    * The current human-readable form is a few lines about general
268    * autocomplete result statistics followed by a table with one line
269    * for each autocomplete match.  The input parameter result is a
270    * complex Object with lots of information about various
271    * autocomplete matches.  Here's an example of what it looks like:
272    * <pre>
273    * {@code
274    * {
275    *   'done': false,
276    *   'time_since_omnibox_started_ms': 15,
277    *   'host': 'mai',
278    *   'is_typed_host': false,
279    *   'combined_results' : {
280    *     'num_items': 4,
281    *     'item_0': {
282    *       'destination_url': 'http://mail.google.com',
283    *       'provider_name': 'HistoryURL',
284    *       'relevance': 1410,
285    *       ...
286    *     }
287    *     'item_1: {
288    *       ...
289    *     }
290    *     ...
291    *   }
292    *   'results_by_provider': {
293    *     'HistoryURL' : {
294    *       'num_items': 3,
295    *         ...
296    *       }
297    *     'Search' : {
298    *       'num_items': 1,
299    *       ...
300    *     }
301    *     ...
302    *   }
303    * }
304    * }
305    * </pre>
306    * For more information on how the result is packed, see the
307    * corresponding code in chrome/browser/ui/webui/omnibox_ui.cc
308    */
309   function addResultToOutput(result) {
310     var output = $('omnibox-debug-text');
311     var inDetailedMode = $('show-details').checked;
312     var showIncompleteResults = $('show-incomplete-results').checked;
313     var showPerProviderResults = $('show-all-providers').checked;
314
315     // Always output cursor position.
316     var p = document.createElement('p');
317     p.textContent = 'cursor position = ' + cursorPositionUsed;
318     output.appendChild(p);
319
320     // Output the result-level features in detailed mode and in
321     // show incomplete results mode.  We do the latter because without
322     // these result-level features, one can't make sense of each
323     // batch of results.
324     if (inDetailedMode || showIncompleteResults) {
325       var p1 = document.createElement('p');
326       p1.textContent = 'elapsed time = ' +
327           result.time_since_omnibox_started_ms + 'ms';
328       output.appendChild(p1);
329       var p2 = document.createElement('p');
330       p2.textContent = 'all providers done = ' + result.done;
331       output.appendChild(p2);
332       var p3 = document.createElement('p');
333       p3.textContent = 'host = ' + result.host;
334       if ('is_typed_host' in result) {
335         // Only output the is_typed_host information if available.  (It may
336         // be missing if the history database lookup failed.)
337         p3.textContent = p3.textContent + ' has is_typed_host = ' +
338             result.is_typed_host;
339       }
340       output.appendChild(p3);
341     }
342
343     // Combined results go after the lines below.
344     var group = document.createElement('a');
345     group.className = 'group-separator';
346     group.textContent = 'Combined results.';
347     output.appendChild(group);
348
349     // Add combined/merged result table.
350     var p = document.createElement('p');
351     p.appendChild(addResultTableToOutput(result.combined_results));
352     output.appendChild(p);
353
354     // Move forward only if you want to display per provider results.
355     if (!showPerProviderResults) {
356       return;
357     }
358
359     // Individual results go after the lines below.
360     var group = document.createElement('a');
361     group.className = 'group-separator';
362     group.textContent = 'Results for individual providers.';
363     output.appendChild(group);
364
365     // Add the per-provider result tables with labels. We do not append the
366     // combined/merged result table since we already have the per provider
367     // results.
368     for (var provider in result.results_by_provider) {
369       var results = result.results_by_provider[provider];
370       // If we have no results we do not display anything.
371       if (results.num_items == 0) {
372         continue;
373       }
374       var p = document.createElement('p');
375       p.appendChild(addResultTableToOutput(results));
376       output.appendChild(p);
377     }
378   }
379
380   /**
381    * @param {Object} result either the combined_results component of
382    *     the structure described in the comment by addResultToOutput()
383    *     above or one of the per-provider results in the structure.
384    *     (Both have the same format).
385    * @return {HTMLTableCellElement} that is a user-readable HTML
386    *     representation of this object.
387    */
388   function addResultTableToOutput(result) {
389     var inDetailedMode = $('show-details').checked;
390     // Create a table to hold all the autocomplete items.
391     var table = document.createElement('table');
392     table.className = 'autocomplete-results-table';
393     table.appendChild(createAutocompleteResultTableHeader());
394     // Loop over every autocomplete item and add it as a row in the table.
395     for (var i = 0; i < result.num_items; i++) {
396       var autocompleteSuggestion = result['item_' + i];
397       var row = document.createElement('tr');
398       // Loop over all the columns/properties and output either them
399       // all (if we're in detailed mode) or only the ones marked displayAlways.
400       // Keep track of which properties we displayed.
401       var displayedProperties = {};
402       for (var j = 0; j < PROPERTY_OUTPUT_ORDER.length; j++) {
403         if (inDetailedMode || PROPERTY_OUTPUT_ORDER[j].displayAlways) {
404           row.appendChild(createCellForPropertyAndRemoveProperty(
405               autocompleteSuggestion, PROPERTY_OUTPUT_ORDER[j].propertyName));
406           displayedProperties[PROPERTY_OUTPUT_ORDER[j].propertyName] = true;
407         }
408       }
409
410       // Now, if we're in detailed mode, add all the properties that
411       // haven't already been output.  (We know which properties have
412       // already been output because we delete the property when we output
413       // it.  The only way we have properties left at this point if
414       // we're in detailed mode and we're getting back properties
415       // not listed in PROPERTY_OUTPUT_ORDER.  Perhaps someone added
416       // something to the C++ code but didn't bother to update this
417       // Javascript?  In any case, we want to display them.)
418       if (inDetailedMode) {
419         for (var key in autocompleteSuggestion) {
420           if (!displayedProperties[key]) {
421             var cell = document.createElement('td');
422             cell.textContent = key + '=' + autocompleteSuggestion[key];
423             row.appendChild(cell);
424           }
425         }
426       }
427
428       table.appendChild(row);
429     }
430     return table;
431   }
432
433   /* Repaints the page based on the contents of the array
434    * progressiveAutocompleteResults, which represents consecutive
435    * autocomplete results.  We only display the last (most recent)
436    * entry unless we're asked to display incomplete results.  For an
437    * example of the output, play with chrome://omnibox/
438    */
439   function refresh() {
440     // Erase whatever is currently being displayed.
441     var output = $('omnibox-debug-text');
442     output.innerHTML = '';
443
444     if (progressiveAutocompleteResults.length > 0) {  // if we have results
445       // Display the results.
446       var showIncompleteResults = $('show-incomplete-results').checked;
447       var startIndex = showIncompleteResults ? 0 :
448           progressiveAutocompleteResults.length - 1;
449       for (var i = startIndex; i < progressiveAutocompleteResults.length; i++) {
450         addResultToOutput(progressiveAutocompleteResults[i]);
451       }
452     }
453   }
454
455   return {
456     initialize: initialize,
457     startOmniboxQuery: startOmniboxQuery,
458     handleNewAutocompleteResult: handleNewAutocompleteResult
459   };
460 });
461
462 document.addEventListener('DOMContentLoaded', omniboxDebug.initialize);