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/connection',
21 'chrome/browser/ui/webui/omnibox/omnibox.mojom',
22 'content/public/renderer/service_provider',
23 ], function(connector, browser, serviceProvider) {
30 * Register our event handlers.
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);
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..
49 var progressiveAutocompleteResults = [];
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.
57 var cursorPositionUsed = -1;
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.
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,
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();
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
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.
99 function PresentationInfoRecord(header, url, propertyName, displayAlways,
101 this.header = header;
102 this.urlLabelForHeader = url;
103 this.propertyName = propertyName;
104 this.displayAlways = displayAlways;
105 this.tooltip = tooltip;
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
113 * @type {Array.<Object>}
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 ' +
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 ' +
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(
152 'http://code.google.com/codesearch#OAMlx_jo-ck/src/content/public/' +
153 'common/page_transition_types.h&exact_package=chromium&l=24',
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 ' +
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 ' +
171 new PresentationInfoRecord(
172 'Additional Info', '', 'additional_info', false,
173 'Provider-specific information about the result.')
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.
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);
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;
200 row.appendChild(headerCell);
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.
214 function createCellForPropertyAndRemoveProperty(autocompleteSuggestion,
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');
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);
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);
239 additionalInfoTable.appendChild(additionalInfoRow);
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 = '✔';
249 cell.className = 'x-mark';
250 cell.textContent = '✗';
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):\/\//;
257 var aCell = document.createElement('a');
258 aCell.textContent = text;
260 cell.appendChild(aCell);
262 // All other data types (integer, strings, etc.) display their
263 // normal toString() output.
264 cell.textContent = autocompleteSuggestion[propertyName];
267 } // else: if propertyName is undefined, we leave the cell blank
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.
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;
284 // Always output cursor position.
285 var p = document.createElement('p');
286 p.textContent = 'cursor position = ' + cursorPositionUsed;
287 output.appendChild(p);
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
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;
309 output.appendChild(p3);
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);
318 // Add combined/merged result table.
319 var p = document.createElement('p');
320 p.appendChild(addResultTableToOutput(result.combined_results));
321 output.appendChild(p);
323 // Move forward only if you want to display per provider results.
324 if (!showPerProviderResults) {
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);
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
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) {
343 var p = document.createElement('p');
344 p.appendChild(addResultTableToOutput(providerResults.results));
345 output.appendChild(p);
350 * @param {Object} result an array of AutocompleteMatchMojos.
351 * @return {HTMLTableCellElement} that is a user-readable HTML
352 * representation of this object.
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;
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);
395 table.appendChild(row);
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/
407 // Erase whatever is currently being displayed.
408 var output = $('omnibox-debug-text');
409 output.innerHTML = '';
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]);
422 function OmniboxPageImpl(browser) {
423 this.browser_ = browser;
428 OmniboxPageImpl.prototype =
429 Object.create(browser.OmniboxPageStub.prototype);
431 OmniboxPageImpl.prototype.handleNewAutocompleteResult = function(result) {
432 progressiveAutocompleteResults.push(result);
437 connection = new connector.Connection(
438 // TODO(sammc): Avoid using NAME_ directly.
439 serviceProvider.connectToService(
440 browser.OmniboxUIHandlerMojoProxy.NAME_),
442 browser.OmniboxUIHandlerMojoProxy);