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