blob: 7e9d9b13d6f9317e941e3261420e9480f64a9f16 [file] [log] [blame]
// 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() {
/**
* @typedef {{
* queryInputs: QueryInputs,
* displayInputs: DisplayInputs,
* responsesHistory: !Array<!Array<!mojom.OmniboxResponse>>,
* }}
*/
let OmniboxExport;
/** @type {!BrowserProxy} */
let browserProxy;
/** @type {!OmniboxInput} */
let omniboxInput;
/** @type {!omnibox_output.OmniboxOutput} */
let omniboxOutput;
/** @type {!ExportDelegate} */
let exportDelegate;
class BrowserProxy {
/** @param {!omnibox_output.OmniboxOutput} omniboxOutput */
constructor(omniboxOutput) {
/** @private {!mojom.OmniboxPageCallbackRouter} */
this.callbackRouter_ = new mojom.OmniboxPageCallbackRouter;
this.callbackRouter_.handleNewAutocompleteResponse.addListener(
(response, isPageController) => {
// When unfocusing the browser omnibox, the autocomplete controller
// sends a response with no combined results. This response is ignored
// in order to prevent the previous non-empty response from being
// hidden and because these results wouldn't normally be displayed by
// the browser window omnibox.
if (isPageController ||
(omniboxInput.connectWindowOmnibox &&
response.combinedResults.length)) {
omniboxOutput.addAutocompleteResponse(response);
}
});
this.callbackRouter_.handleNewAutocompleteQuery.addListener(
isPageController => {
if (isPageController || omniboxInput.connectWindowOmnibox) {
omniboxOutput.prepareNewQuery();
}
});
this.callbackRouter_.handleAnswerImageData.addListener(
omniboxOutput.updateAnswerImage.bind(omniboxOutput));
/** @private {!mojom.OmniboxPageHandlerProxy} */
this.handler_ = mojom.OmniboxPageHandler.getProxy();
this.handler_.setClientPage(this.callbackRouter_.createProxy());
/**
* @type {function(string, boolean, number, boolean, boolean, boolean,
* string, number)}
*/
this.makeRequest = this.handler_.startOmniboxQuery.bind(this.handler_);
}
}
document.addEventListener('DOMContentLoaded', () => {
omniboxInput = /** @type {!OmniboxInput} */ ($('omnibox-input'));
omniboxOutput =
/** @type {!omnibox_output.OmniboxOutput} */ ($('omnibox-output'));
browserProxy = new BrowserProxy(omniboxOutput);
exportDelegate = new ExportDelegate(omniboxOutput, omniboxInput);
omniboxInput.addEventListener('query-inputs-changed', event => {
omniboxOutput.updateQueryInputs(event.detail);
browserProxy.makeRequest(
event.detail.inputText, event.detail.resetAutocompleteController,
event.detail.cursorPosition, event.detail.zeroSuggest,
event.detail.preventInlineAutocomplete, event.detail.preferKeyword,
event.detail.currentUrl, event.detail.pageClassification);
});
omniboxInput.addEventListener(
'display-inputs-changed',
event => omniboxOutput.updateDisplayInputs(event.detail));
omniboxInput.addEventListener(
'filter-input-changed',
event => omniboxOutput.updateFilterText(event.detail));
omniboxInput.addEventListener(
'import-json', event => exportDelegate.importJson(event.detail));
omniboxInput.addEventListener('copy-text', () => exportDelegate.copyText());
omniboxInput.addEventListener(
'download-json', () => exportDelegate.downloadJson());
omniboxInput.addEventListener(
'response-select',
event => omniboxOutput.updateSelectedResponseIndex(event.detail));
omniboxOutput.addEventListener(
'responses-count-changed',
event => omniboxInput.responsesCount = event.detail);
});
class ExportDelegate {
/**
* @param {!omnibox_output.OmniboxOutput} omniboxOutput
* @param {!OmniboxInput} omniboxInput
*/
constructor(omniboxOutput, omniboxInput) {
/** @private {!OmniboxInput} */
this.omniboxInput_ = omniboxInput;
/** @private {!omnibox_output.OmniboxOutput} */
this.omniboxOutput_ = omniboxOutput;
}
/** @param {OmniboxExport} importData */
importJson(importData) {
// This is the minimum validation required to ensure no console errors.
// Invalid importData that passes validation will be processed with a
// best-attempt; e.g. if responses are missing 'relevance' values, then
// those cells will be left blank.
const valid = importData && importData.queryInputs &&
importData.displayInputs &&
Array.isArray(importData.responsesHistory) &&
importData.responsesHistory.every(
responses => Array.isArray(responses) &&
responses.every(
response => Array.isArray(response.combinedResults) &&
Array.isArray(response.resultsByProvider)));
if (!valid) {
return console.error(
'invalid import format:',
'expected {queryInputs: {}, displayInputs: {}, responsesHistory: []}');
}
this.omniboxInput_.queryInputs = importData.queryInputs;
this.omniboxInput_.displayInputs = importData.displayInputs;
this.omniboxOutput_.updateQueryInputs(importData.queryInputs);
this.omniboxOutput_.updateDisplayInputs(importData.displayInputs);
this.omniboxOutput_.setResponsesHistory(importData.responsesHistory);
}
copyText() {
ExportDelegate.copy_(this.omniboxOutput_.visibleTableText);
}
downloadJson() {
/** @type {OmniboxExport} */
const exportObj = {
queryInputs: this.omniboxInput_.queryInputs,
displayInputs: this.omniboxInput_.displayInputs,
responsesHistory: this.omniboxOutput_.responsesHistory,
};
const fileName = `omnibox_debug_export_${exportObj.queryInputs.inputText}_${
new Date().toISOString()}.json`;
ExportDelegate.download_(exportObj, fileName);
}
/** @private @param {string} value */
static copy_(value) {
navigator.clipboard.writeText(value).catch(
error => console.error('unable to copy to clipboard:', error));
}
/**
* @private
* @param {Object} object
* @param {string} fileName
*/
static download_(object, fileName) {
const content = JSON.stringify(object, null, 2);
const blob = new Blob([content], {type: 'application/json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName;
a.click();
}
}
})();