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('Description', '', 'description', false,
139 'The page title of the result.'),
140 new PresentationInfoRecord('URL', '', 'destination_url', true,
141 'The URL for the result.'),
142 new PresentationInfoRecord('Fill Into Edit', '', 'fill_into_edit', false,
143 'The text shown in the omnibox when the result is selected.'),
144 new PresentationInfoRecord(
145 'Inline Autocompletion', '', 'inline_autocompletion', false,
146 'The text shown in the omnibox as a blue highlight selection ' +
147 'following the cursor, if this match is shown inline.'),
148 new PresentationInfoRecord('Del', '', 'deletable', false,
149 'A green checkmark indicates that the result can be deleted from ' +
150 'the visit history.'),
151 new PresentationInfoRecord('Prev', '', 'from_previous', false, ''),
152 new PresentationInfoRecord(
154 'http://code.google.com/codesearch#OAMlx_jo-ck/src/content/public/' +
155 'common/page_transition_types.h&exact_package=chromium&l=24',
157 'How the user got to the result.'),
158 new PresentationInfoRecord(
159 'Done', '', 'provider_done', false,
160 'A green checkmark indicates that the provider is done looking for ' +
162 new PresentationInfoRecord(
163 'Associated Keyword', '', 'associated_keyword', false,
164 'If non-empty, a "press tab to search" hint will be shown and will ' +
165 'engage this keyword.'),
166 new PresentationInfoRecord(
167 'Keyword', '', 'keyword', false,
168 'The keyword of the search engine to be used.'),
169 new PresentationInfoRecord(
170 'Duplicates', '', 'duplicates', false,
171 'The number of matches that have been marked as duplicates of this ' +
173 new PresentationInfoRecord(
174 'Additional Info', '', 'additional_info', false,
175 'Provider-specific information about the result.')
179 * Returns an HTML Element of type table row that contains the
180 * headers we'll use for labeling the columns. If we're in
181 * detailed_mode, we use all the headers. If not, we only use ones
182 * marked displayAlways.
184 function createAutocompleteResultTableHeader() {
185 var row = document.createElement('tr');
186 var inDetailedMode = $('show-details').checked;
187 for (var i = 0; i < PROPERTY_OUTPUT_ORDER.length; i++) {
188 if (inDetailedMode || PROPERTY_OUTPUT_ORDER[i].displayAlways) {
189 var headerCell = document.createElement('th');
190 if (PROPERTY_OUTPUT_ORDER[i].urlLabelForHeader != '') {
191 // Wrap header text in URL.
192 var linkNode = document.createElement('a');
193 linkNode.href = PROPERTY_OUTPUT_ORDER[i].urlLabelForHeader;
194 linkNode.textContent = PROPERTY_OUTPUT_ORDER[i].header;
195 headerCell.appendChild(linkNode);
197 // Output header text without a URL.
198 headerCell.textContent = PROPERTY_OUTPUT_ORDER[i].header;
199 headerCell.className = 'table-header';
200 headerCell.title = PROPERTY_OUTPUT_ORDER[i].tooltip;
202 row.appendChild(headerCell);
209 * @param {AutocompleteMatchMojo} autocompleteSuggestion the particular
210 * autocomplete suggestion we're in the process of displaying.
211 * @param {string} propertyName the particular property of the autocomplete
212 * suggestion that should go in this cell.
213 * @return {HTMLTableCellElement} that contains the value within this
214 * autocompleteSuggestion associated with propertyName.
216 function createCellForPropertyAndRemoveProperty(autocompleteSuggestion,
218 var cell = document.createElement('td');
219 if (propertyName in autocompleteSuggestion) {
220 if (propertyName == 'additional_info') {
221 // |additional_info| embeds a two-column table of provider-specific data
222 // within this cell. |additional_info| is an array of
223 // AutocompleteAdditionalInfo.
224 var additionalInfoTable = document.createElement('table');
225 for (var i = 0; i < autocompleteSuggestion[propertyName].length; i++) {
226 var additionalInfo = autocompleteSuggestion[propertyName][i];
227 var additionalInfoRow = document.createElement('tr');
229 // Set the title (name of property) cell text.
230 var propertyCell = document.createElement('td');
231 propertyCell.textContent = additionalInfo.key + ':';
232 propertyCell.className = 'additional-info-property';
233 additionalInfoRow.appendChild(propertyCell);
235 // Set the value of the property cell text.
236 var valueCell = document.createElement('td');
237 valueCell.textContent = additionalInfo.value;
238 valueCell.className = 'additional-info-value';
239 additionalInfoRow.appendChild(valueCell);
241 additionalInfoTable.appendChild(additionalInfoRow);
243 cell.appendChild(additionalInfoTable);
244 } else if (typeof autocompleteSuggestion[propertyName] == 'boolean') {
245 // If this is a boolean, display a checkmark or an X instead of
246 // the strings true or false.
247 if (autocompleteSuggestion[propertyName]) {
248 cell.className = 'check-mark';
249 cell.textContent = '✔';
251 cell.className = 'x-mark';
252 cell.textContent = '✗';
255 var text = String(autocompleteSuggestion[propertyName]);
256 // If it's a URL wrap it in an href.
257 var re = /^(http|https|ftp|chrome|file):\/\//;
259 var aCell = document.createElement('a');
260 aCell.textContent = text;
262 cell.appendChild(aCell);
264 // All other data types (integer, strings, etc.) display their
265 // normal toString() output.
266 cell.textContent = autocompleteSuggestion[propertyName];
269 } // else: if propertyName is undefined, we leave the cell blank
274 * Appends some human-readable information about the provided
275 * autocomplete result to the HTML node with id omnibox-debug-text.
276 * The current human-readable form is a few lines about general
277 * autocomplete result statistics followed by a table with one line
278 * for each autocomplete match. The input parameter is an OmniboxResultMojo.
280 function addResultToOutput(result) {
281 var output = $('omnibox-debug-text');
282 var inDetailedMode = $('show-details').checked;
283 var showIncompleteResults = $('show-incomplete-results').checked;
284 var showPerProviderResults = $('show-all-providers').checked;
286 // Always output cursor position.
287 var p = document.createElement('p');
288 p.textContent = 'cursor position = ' + cursorPositionUsed;
289 output.appendChild(p);
291 // Output the result-level features in detailed mode and in
292 // show incomplete results mode. We do the latter because without
293 // these result-level features, one can't make sense of each
295 if (inDetailedMode || showIncompleteResults) {
296 var p1 = document.createElement('p');
297 p1.textContent = 'elapsed time = ' +
298 result.time_since_omnibox_started_ms + 'ms';
299 output.appendChild(p1);
300 var p2 = document.createElement('p');
301 p2.textContent = 'all providers done = ' + result.done;
302 output.appendChild(p2);
303 var p3 = document.createElement('p');
304 p3.textContent = 'host = ' + result.host;
305 if ('is_typed_host' in result) {
306 // Only output the is_typed_host information if available. (It may
307 // be missing if the history database lookup failed.)
308 p3.textContent = p3.textContent + ' has is_typed_host = ' +
309 result.is_typed_host;
311 output.appendChild(p3);
314 // Combined results go after the lines below.
315 var group = document.createElement('a');
316 group.className = 'group-separator';
317 group.textContent = 'Combined results.';
318 output.appendChild(group);
320 // Add combined/merged result table.
321 var p = document.createElement('p');
322 p.appendChild(addResultTableToOutput(result.combined_results));
323 output.appendChild(p);
325 // Move forward only if you want to display per provider results.
326 if (!showPerProviderResults) {
330 // Individual results go after the lines below.
331 var group = document.createElement('a');
332 group.className = 'group-separator';
333 group.textContent = 'Results for individual providers.';
334 output.appendChild(group);
336 // Add the per-provider result tables with labels. We do not append the
337 // combined/merged result table since we already have the per provider
339 for (var i = 0; i < result.results_by_provider.length; i++) {
340 var providerResults = result.results_by_provider[i];
341 // If we have no results we do not display anything.
342 if (providerResults.results.length == 0) {
345 var p = document.createElement('p');
346 p.appendChild(addResultTableToOutput(providerResults.results));
347 output.appendChild(p);
352 * @param {Object} result an array of AutocompleteMatchMojos.
353 * @return {HTMLTableCellElement} that is a user-readable HTML
354 * representation of this object.
356 function addResultTableToOutput(result) {
357 var inDetailedMode = $('show-details').checked;
358 // Create a table to hold all the autocomplete items.
359 var table = document.createElement('table');
360 table.className = 'autocomplete-results-table';
361 table.appendChild(createAutocompleteResultTableHeader());
362 // Loop over every autocomplete item and add it as a row in the table.
363 for (var i = 0; i < result.length; i++) {
364 var autocompleteSuggestion = result[i];
365 var row = document.createElement('tr');
366 // Loop over all the columns/properties and output either them
367 // all (if we're in detailed mode) or only the ones marked displayAlways.
368 // Keep track of which properties we displayed.
369 var displayedProperties = {};
370 for (var j = 0; j < PROPERTY_OUTPUT_ORDER.length; j++) {
371 if (inDetailedMode || PROPERTY_OUTPUT_ORDER[j].displayAlways) {
372 row.appendChild(createCellForPropertyAndRemoveProperty(
373 autocompleteSuggestion, PROPERTY_OUTPUT_ORDER[j].propertyName));
374 displayedProperties[PROPERTY_OUTPUT_ORDER[j].propertyName] = true;
378 // Now, if we're in detailed mode, add all the properties that
379 // haven't already been output. (We know which properties have
380 // already been output because we delete the property when we output
381 // it. The only way we have properties left at this point if
382 // we're in detailed mode and we're getting back properties
383 // not listed in PROPERTY_OUTPUT_ORDER. Perhaps someone added
384 // something to the C++ code but didn't bother to update this
385 // Javascript? In any case, we want to display them.)
386 if (inDetailedMode) {
387 for (var key in autocompleteSuggestion) {
388 if (!displayedProperties[key] &&
389 typeof autocompleteSuggestion[key] != 'function') {
390 var cell = document.createElement('td');
391 cell.textContent = key + '=' + autocompleteSuggestion[key];
392 row.appendChild(cell);
397 table.appendChild(row);
402 /* Repaints the page based on the contents of the array
403 * progressiveAutocompleteResults, which represents consecutive
404 * autocomplete results. We only display the last (most recent)
405 * entry unless we're asked to display incomplete results. For an
406 * example of the output, play with chrome://omnibox/
409 // Erase whatever is currently being displayed.
410 var output = $('omnibox-debug-text');
411 output.innerHTML = '';
413 if (progressiveAutocompleteResults.length > 0) { // if we have results
414 // Display the results.
415 var showIncompleteResults = $('show-incomplete-results').checked;
416 var startIndex = showIncompleteResults ? 0 :
417 progressiveAutocompleteResults.length - 1;
418 for (var i = startIndex; i < progressiveAutocompleteResults.length; i++) {
419 addResultToOutput(progressiveAutocompleteResults[i]);
424 function OmniboxPageImpl(browser) {
425 this.browser_ = browser;
429 OmniboxPageImpl.prototype =
430 Object.create(browser.OmniboxPage.stubClass.prototype);
432 OmniboxPageImpl.prototype.handleNewAutocompleteResult = function(result) {
433 progressiveAutocompleteResults.push(result);
438 var browserProxy = connection.bindHandleToProxy(
439 serviceProvider.connectToService(
440 browser.OmniboxUIHandlerMojo.name),
441 browser.OmniboxUIHandlerMojo);
442 page = new OmniboxPageImpl(browserProxy);