| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * Javascript for omnibox.html, served from chrome://omnibox/ |
| * This is used to debug omnibox ranking. The user enters some text |
| * into a box, submits it, and then sees lots of debug information |
| * from the autocompleter that shows what omnibox would do with that |
| * input. |
| * |
| * The simple object defined in this javascript file listens for |
| * certain events on omnibox.html, sends (when appropriate) the |
| * input text to C++ code to start the omnibox autcomplete controller |
| * working, and listens from callbacks from the C++ code saying that |
| * results are available. When results (possibly intermediate ones) |
| * are available, the Javascript formats them and displays them. |
| */ |
| |
| (function () { |
| /** |
| * @type {number} the value for cursor position we sent with the most |
| * recent request. We need to remember this in order to display it |
| * in the output; otherwise it's hard or impossible to determine |
| * from screen captures or print-to-PDFs. |
| */ |
| let cursorPosition = -1; |
| |
| /** |
| * Tracks and aggregates responses from the C++ autocomplete controller. |
| * Typically, the C++ controller returns 3 sets of results per query, unless |
| * a new query is submitted before all 3 responses. OutputController also |
| * triggers appending to and clearing of OmniboxOutput when appropriate (e.g., |
| * upon receiving a new response or a change in display inputs). |
| */ |
| class OutputController { |
| constructor() { |
| /** @private {!Array<mojom.OmniboxResult>} */ |
| this.outputResultsGroups_ = []; |
| } |
| |
| clear() { |
| this.outputResultsGroups_ = []; |
| omniboxOutput.clearOutput(); |
| } |
| |
| /* |
| * Adds a new response to the page. If we're not displaying incomplete |
| * results, we clear the page and display only the new result. If we are |
| * displaying incomplete results, then this is more efficient than refresh, |
| * as there's no need to clear and re-add previous results. |
| */ |
| /** @param {!mojom.OmniboxResult} response A response from C++ autocomplete controller */ |
| add(response) { |
| this.outputResultsGroups_.push(response); |
| if (!omniboxInputs.$$('show-incomplete-results').checked) |
| omniboxOutput.clearOutput(); |
| addResultToOutput( |
| this.outputResultsGroups_[this.outputResultsGroups_.length - 1]); |
| } |
| |
| /* |
| * Refreshes all results. We only display the last (most recent) entry |
| * unless incomplete results is enabled. |
| */ |
| refresh() { |
| omniboxOutput.clearOutput(); |
| if (omniboxInputs.$$('show-incomplete-results').checked) { |
| this.outputResultsGroups_.forEach(addResultToOutput); |
| } else if (this.outputResultsGroups_.length) { |
| addResultToOutput( |
| this.outputResultsGroups_[this.outputResultsGroups_.length - 1]); |
| } |
| } |
| } |
| |
| /** |
| * Appends some human-readable information about the provided |
| * autocomplete result to the HTML node with id omnibox-debug-text. |
| * The current human-readable form is a few lines about general |
| * autocomplete result statistics followed by a table with one line |
| * for each autocomplete match. The input parameter is an OmniboxResultMojo. |
| */ |
| function addResultToOutput(result) { |
| // Output the result-level features in detailed mode and in |
| // show incomplete results mode. We do the latter because without |
| // these result-level features, one can't make sense of each |
| // batch of results. |
| if (omniboxInputs.$$('show-details').checked |
| || omniboxInputs.$$('show-incomplete-results').checked) { |
| addParagraph(`cursor position = ${cursorPosition}`); |
| addParagraph(`inferred input type = ${result.type}`); |
| addParagraph(`elapsed time = ${result.timeSinceOmniboxStartedMs}ms`); |
| addParagraph(`all providers done = ${result.done}`); |
| const p = document.createElement('p'); |
| p.textContent = `host = ${result.host}`; |
| // The field isn't actually optional in the mojo object; instead it assumes |
| // failed lookups are not typed hosts. Fix this to make it optional. |
| // http://crbug.com/863201 |
| if ('isTypedHost' in result) { |
| // Only output the isTypedHost information if available. (It may |
| // be missing if the history database lookup failed.) |
| p.textContent = |
| p.textContent + ` has isTypedHost = ${result.isTypedHost}`; |
| } |
| omniboxOutput.addOutput(p); |
| } |
| |
| // Combined results go after the lines below. |
| const group = document.createElement('a'); |
| group.className = 'group-separator'; |
| group.textContent = 'Combined results.'; |
| omniboxOutput.addOutput(group); |
| |
| // Add combined/merged result table. |
| const p = document.createElement('p'); |
| p.appendChild(addResultTableToOutput(result.combinedResults)); |
| omniboxOutput.addOutput(p); |
| |
| // Move forward only if you want to display per provider results. |
| if (!omniboxInputs.$$('show-all-providers').checked) { |
| return; |
| } |
| |
| // Individual results go after the lines below. |
| const individualGroup = document.createElement('a'); |
| individualGroup.className = 'group-separator'; |
| individualGroup.textContent = 'Results for individual providers.'; |
| omniboxOutput.addOutput(individualGroup); |
| |
| // Add the per-provider result tables with labels. We do not append the |
| // combined/merged result table since we already have the per provider |
| // results. |
| result.resultsByProvider.forEach(providerResults => { |
| // If we have no results we do not display anything. |
| if (providerResults.results.length === 0) |
| return; |
| const p = document.createElement('p'); |
| p.appendChild(addResultTableToOutput(providerResults.results)); |
| omniboxOutput.addOutput(p); |
| }); |
| } |
| |
| /** |
| * @param {Object} result an array of AutocompleteMatchMojos. |
| * @return {Element} that is a user-readable HTML |
| * representation of this object. |
| */ |
| function addResultTableToOutput(result) { |
| const inDetailedMode = omniboxInputs.$$('show-details').checked; |
| // Create a table to hold all the autocomplete items. |
| const table = document.createElement('table'); |
| table.className = 'autocomplete-results-table'; |
| table.appendChild(createAutocompleteResultTableHeader()); |
| // Loop over every autocomplete item and add it as a row in the table. |
| result.forEach(autocompleteSuggestion => { |
| const row = new omnibox_output.OutputMatch(autocompleteSuggestion) |
| .render(inDetailedMode); |
| table.appendChild(row); |
| }); |
| return table; |
| } |
| |
| /** |
| * Returns an HTML Element of type table row that contains the |
| * headers we'll use for labeling the columns. If we're in |
| * detailedMode, we use all the headers. If not, we only use ones |
| * marked displayAlways. |
| */ |
| function createAutocompleteResultTableHeader() { |
| const row = document.createElement('tr'); |
| const inDetailedMode = omniboxInputs.$$('show-details').checked; |
| omnibox_output.PROPERTY_OUTPUT_ORDER.forEach(property => { |
| if (inDetailedMode || property.displayAlways) { |
| const headerCell = document.createElement('th'); |
| if (property.url !== '') { |
| // Wrap header text in URL. |
| const linkNode = document.createElement('a'); |
| linkNode.href = property.url; |
| linkNode.textContent = property.header; |
| headerCell.appendChild(linkNode); |
| } else { |
| // Output header text without a URL. |
| headerCell.textContent = property.header; |
| headerCell.className = 'table-header'; |
| headerCell.title = property.tooltip; |
| } |
| row.appendChild(headerCell); |
| } |
| }); |
| return row; |
| } |
| |
| /** |
| * Appends a paragraph node containing text to the parent node. |
| */ |
| function addParagraph(text) { |
| const p = document.createElement('p'); |
| p.textContent = text; |
| omniboxOutput.addOutput(p); |
| } |
| |
| class BrowserProxy { |
| constructor() { |
| /** @private {!mojom.OmniboxPageHandlerPtr} */ |
| this.pagehandlePtr_ = new mojom.OmniboxPageHandlerPtr; |
| Mojo.bindInterface( |
| mojom.OmniboxPageHandler.name, |
| mojo.makeRequest(this.pagehandlePtr_).handle); |
| const client = new mojom.OmniboxPagePtr; |
| // NOTE: Need to keep a global reference to the |binding_| such that it is |
| // not garbage collected, which causes the pipe to close and future calls |
| // from C++ to JS to get dropped. |
| /** @private {!mojo.Binding} */ |
| this.binding_ = |
| new mojo.Binding(mojom.OmniboxPage, this, mojo.makeRequest(client)); |
| this.pagehandlePtr_.setClientPage(client); |
| } |
| |
| /** |
| * Extracts the input text from the text field and sends it to the |
| * C++ portion of chrome to handle. The C++ code will iteratively |
| * call handleNewAutocompleteResult as results come in. |
| */ |
| makeRequest(inputString, |
| cursorPosition, |
| preventInlineAutocomplete, |
| preferKeyword, |
| pageClassification) { |
| outputController.clear(); |
| // Then, call chrome with a five-element list: |
| // - first element: the value in the text box |
| // - second element: the location of the cursor in the text box |
| // - third element: the value of prevent-inline-autocomplete |
| // - forth element: the value of prefer-keyword |
| // - fifth element: the value of page-classification |
| this.pagehandlePtr_.startOmniboxQuery( |
| inputString, |
| cursorPosition, |
| preventInlineAutocomplete, |
| preferKeyword, |
| pageClassification); |
| } |
| |
| handleNewAutocompleteResult(response) { |
| outputController.add(response); |
| } |
| } |
| |
| /** @type {BrowserProxy} */ |
| const browserProxy = new BrowserProxy(); |
| /** @type {OmniboxInputs} */ |
| let omniboxInputs; |
| /** @type {omnibox_output.OmniboxOutput} */ |
| let omniboxOutput; |
| /** @type {OutputController} */ |
| const outputController = new OutputController(); |
| |
| document.addEventListener('DOMContentLoaded', () => { |
| omniboxInputs = /** @type {!OmniboxInputs} */ ($('omnibox-inputs')); |
| omniboxOutput = |
| /** @type {!omnibox_output.OmniboxOutput} */ ($('omnibox-output')); |
| omniboxInputs.addEventListener('query-inputs-changed', event => |
| browserProxy.makeRequest( |
| event.detail.inputText, |
| event.detail.cursorPosition, |
| event.detail.preventInlineAutocomplete, |
| event.detail.preferKeyword, |
| event.detail.pageClassification |
| )); |
| omniboxInputs.addEventListener('display-inputs-changed', |
| outputController.refresh.bind(outputController)); |
| }); |
| })(); |