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