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 $('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
);
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.
43 var progressiveAutocompleteResults
= [];
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.
51 var cursorPositionUsed
= -1;
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.
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
,
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();
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
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.
93 function PresentationInfoRecord(header
, url
, propertyName
, displayAlways
,
96 this.urlLabelForHeader
= url
;
97 this.propertyName
= propertyName
;
98 this.displayAlways
= displayAlways
;
99 this.tooltip
= tooltip
;
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
107 * @type {Array.<Object>}
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 ' +
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 ' +
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(
146 'http://code.google.com/codesearch#OAMlx_jo-ck/src/content/public/' +
147 'common/page_transition_types.h&exact_package=chromium&l=24',
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 ' +
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.')
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.
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
);
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
;
192 row
.appendChild(headerCell
);
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.
206 function createCellForPropertyAndRemoveProperty(autocompleteSuggestion
,
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
213 var additionalInfoTable
= document
.createElement('table');
214 for (var additionalInfoKey
in autocompleteSuggestion
[propertyName
]) {
215 var additionalInfoRow
= document
.createElement('tr');
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
);
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
);
230 additionalInfoTable
.appendChild(additionalInfoRow
);
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
= '✔';
240 cell
.className
= 'x-mark';
241 cell
.textContent
= '✗';
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):\/\//;
248 var aCell
= document
.createElement('a');
249 aCell
.textContent
= text
;
251 cell
.appendChild(aCell
);
253 // All other data types (integer, strings, etc.) display their
254 // normal toString() output.
255 cell
.textContent
= autocompleteSuggestion
[propertyName
];
258 } // else: if propertyName is undefined, we leave the cell blank
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.
267 function handleNewAutocompleteResult(result
) {
268 progressiveAutocompleteResults
.push(result
);
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:
284 * 'time_since_omnibox_started_ms': 15,
286 * 'is_typed_host': false,
287 * 'combined_results' : {
290 * 'destination_url': 'http://mail.google.com',
291 * 'provider_name': 'HistoryURL',
300 * 'results_by_provider': {
314 * For more information on how the result is packed, see the
315 * corresponding code in chrome/browser/ui/webui/omnibox_ui.cc
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
;
323 // Always output cursor position.
324 var p
= document
.createElement('p');
325 p
.textContent
= 'cursor position = ' + cursorPositionUsed
;
326 output
.appendChild(p
);
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
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
;
348 output
.appendChild(p3
);
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
);
357 // Add combined/merged result table.
358 var p
= document
.createElement('p');
359 p
.appendChild(addResultTableToOutput(result
.combined_results
));
360 output
.appendChild(p
);
362 // Move forward only if you want to display per provider results.
363 if (!showPerProviderResults
) {
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
);
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
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) {
382 var p
= document
.createElement('p');
383 p
.appendChild(addResultTableToOutput(results
));
384 output
.appendChild(p
);
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.
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;
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
);
436 table
.appendChild(row
);
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/
448 // Erase whatever is currently being displayed.
449 var output
= $('omnibox-debug-text');
450 output
.innerHTML
= '';
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
]);
464 initialize
: initialize
,
465 startOmniboxQuery
: startOmniboxQuery
,
466 handleNewAutocompleteResult
: handleNewAutocompleteResult
470 document
.addEventListener('DOMContentLoaded', omniboxDebug
.initialize
);