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