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