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.
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
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.
19 cr.define('omniboxDebug', function() {
23 * Register our event handlers.
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);
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.
42 var progressiveAutocompleteResults = [];
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.
50 var cursorPositionUsed = -1;
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.
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,
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();
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
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.
90 function PresentationInfoRecord(header, url, propertyName, displayAlways,
93 this.urlLabelForHeader = url;
94 this.propertyName = propertyName;
95 this.displayAlways = displayAlways;
96 this.tooltip = tooltip;
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
104 * @type {Array.<Object>}
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 ' +
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(
138 'http://code.google.com/codesearch#OAMlx_jo-ck/src/content/public/' +
139 'common/page_transition_types.h&exact_package=chromium&l=24',
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 ' +
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.')
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.
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);
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;
184 row.appendChild(headerCell);
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.
198 function createCellForPropertyAndRemoveProperty(autocompleteSuggestion,
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
205 var additionalInfoTable = document.createElement('table');
206 for (var additionalInfoKey in autocompleteSuggestion[propertyName]) {
207 var additionalInfoRow = document.createElement('tr');
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);
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);
222 additionalInfoTable.appendChild(additionalInfoRow);
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 = '✔';
232 cell.className = 'x-mark';
233 cell.textContent = '✗';
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):\/\//;
240 var aCell = document.createElement('a');
241 aCell.textContent = text;
243 cell.appendChild(aCell);
245 // All other data types (integer, strings, etc.) display their
246 // normal toString() output.
247 cell.textContent = autocompleteSuggestion[propertyName];
250 } // else: if propertyName is undefined, we leave the cell blank
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.
259 function handleNewAutocompleteResult(result) {
260 progressiveAutocompleteResults.push(result);
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:
276 * 'time_since_omnibox_started_ms': 15,
278 * 'is_typed_host': false,
279 * 'combined_results' : {
282 * 'destination_url': 'http://mail.google.com',
283 * 'provider_name': 'HistoryURL',
292 * 'results_by_provider': {
306 * For more information on how the result is packed, see the
307 * corresponding code in chrome/browser/ui/webui/omnibox_ui.cc
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;
315 // Always output cursor position.
316 var p = document.createElement('p');
317 p.textContent = 'cursor position = ' + cursorPositionUsed;
318 output.appendChild(p);
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
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;
340 output.appendChild(p3);
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);
349 // Add combined/merged result table.
350 var p = document.createElement('p');
351 p.appendChild(addResultTableToOutput(result.combined_results));
352 output.appendChild(p);
354 // Move forward only if you want to display per provider results.
355 if (!showPerProviderResults) {
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);
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
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) {
374 var p = document.createElement('p');
375 p.appendChild(addResultTableToOutput(results));
376 output.appendChild(p);
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.
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;
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);
428 table.appendChild(row);
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/
440 // Erase whatever is currently being displayed.
441 var output = $('omnibox-debug-text');
442 output.innerHTML = '';
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]);
456 initialize: initialize,
457 startOmniboxQuery: startOmniboxQuery,
458 handleNewAutocompleteResult: handleNewAutocompleteResult
462 document.addEventListener('DOMContentLoaded', omniboxDebug.initialize);