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