blob: 7873e821afe241891ddd4b09cebdbca62cb4314f [file] [log] [blame]
// Copyright 2018 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.
cr.define('omnibox_output', function() {
/**
* @typedef {{
* cursorPosition: number,
* time: number,
* done: boolean,
* host: string,
* isTypedHost: boolean,
* }}
*/
let ResultsDetails;
/** @param {!Element} element*/
function clearChildren(element) {
while (element.firstChild) {
element.firstChild.remove();
}
}
class OmniboxOutput extends OmniboxElement {
constructor() {
super('omnibox-output-template');
/** @private {number} */
this.selectedResponseIndex_ = 0;
/** @type {!Array<!Array<!mojom.OmniboxResponse>>} */
this.responsesHistory = [];
/** @private {!Array<!OutputResultsGroup>} */
this.resultsGroups_ = [];
/** @private {!QueryInputs} */
this.queryInputs_ = /** @type {!QueryInputs} */ ({});
/** @private {!DisplayInputs} */
this.displayInputs_ = OmniboxInput.defaultDisplayInputs;
/** @private {string} */
this.filterText_ = '';
}
/** @param {!QueryInputs} queryInputs */
updateQueryInputs(queryInputs) {
this.queryInputs_ = queryInputs;
}
/** @param {!DisplayInputs} displayInputs */
updateDisplayInputs(displayInputs) {
this.displayInputs_ = displayInputs;
this.updateVisibility_();
this.updateEliding_();
}
/** @param {string} filterText */
updateFilterText(filterText) {
this.filterText_ = filterText;
this.updateFilterHighlights_();
}
/** @param {!Array<!Array<!mojom.OmniboxResponse>>} responsesHistory */
setResponsesHistory(responsesHistory) {
this.responsesHistory = responsesHistory;
this.dispatchEvent(new CustomEvent(
'responses-count-changed', {detail: responsesHistory.length}));
this.updateSelectedResponseIndex(this.selectedResponseIndex_);
}
/** @param {number} selection */
updateSelectedResponseIndex(selection) {
if (selection >= 0 && selection < this.responsesHistory.length) {
this.selectedResponseIndex_ = selection;
this.clearResultsGroups_();
this.responsesHistory[selection].forEach(
this.createResultsGroup_.bind(this));
}
}
prepareNewQuery() {
this.responsesHistory.push([]);
this.dispatchEvent(new CustomEvent(
'responses-count-changed', {detail: this.responsesHistory.length}));
}
/** @param {!mojom.OmniboxResponse} response */
addAutocompleteResponse(response) {
const lastIndex = this.responsesHistory.length - 1;
this.responsesHistory[lastIndex].push(response);
if (lastIndex === this.selectedResponseIndex_) {
this.createResultsGroup_(response);
}
}
/**
* Clears result groups from the UI.
* @private
*/
clearResultsGroups_() {
this.resultsGroups_ = [];
clearChildren(this.$$('#contents'));
}
/**
* Creates and adds a result group to the UI.
* @private @param {!mojom.OmniboxResponse} response
*/
createResultsGroup_(response) {
const resultsGroup =
OutputResultsGroup.create(response, this.queryInputs_.cursorPosition);
this.resultsGroups_.push(resultsGroup);
this.$$('#contents').appendChild(resultsGroup);
this.updateVisibility_();
this.updateFilterHighlights_();
}
/**
* @param {string} url
* @param {string} data
*/
updateAnswerImage(url, data) {
this.autocompleteMatches.forEach(
match => match.updateAnswerImage(url, data));
}
/**
* Show or hide various output elements depending on display inputs.
* 1) Show non-last result groups only if showIncompleteResults is true.
* 2) Show the details section above each table if showDetails or
* showIncompleteResults are true.
* 3) Show individual results when showAllProviders is true.
* 4) Show certain columns and headers only if they showDetails is true.
* @private
*/
updateVisibility_() {
// Show non-last result groups only if showIncompleteResults is true.
this.resultsGroups_.forEach((resultsGroup, index) => {
resultsGroup.hidden = !this.displayInputs_.showIncompleteResults &&
index !== this.resultsGroups_.length - 1;
});
this.resultsGroups_.forEach(resultsGroup => {
resultsGroup.updateVisibility(
this.displayInputs_.showIncompleteResults,
this.displayInputs_.showDetails,
this.displayInputs_.showAllProviders);
});
}
/** @private */
updateEliding_() {
this.resultsGroups_.forEach(
resultsGroup =>
resultsGroup.updateEliding(this.displayInputs_.elideCells));
}
/** @private */
updateFilterHighlights_() {
this.autocompleteMatches.forEach(match => match.filter(this.filterText_));
}
/** @return {!Array<!OutputMatch>} */
get autocompleteMatches() {
return this.resultsGroups_.flatMap(
resultsGroup => resultsGroup.autocompleteMatches);
}
/** @return {string} */
get visibleTableText() {
return this.resultsGroups_
.flatMap(resultsGroup => resultsGroup.visibleText)
.join('\n');
}
}
/**
* Helps track and render a results group. C++ Autocomplete typically returns
* 3 result groups per query. It may return less if the next query is
* submitted before all 3 have been returned. Each result group contains
* top level information (e.g., how long the result took to generate), as well
* as a single list of combined results and multiple lists of individual
* results. Each of these lists is tracked and rendered by OutputResultsTable
* below.
*/
class OutputResultsGroup extends OmniboxElement {
/**
* @param {!mojom.OmniboxResponse} resultsGroup
* @param {number} cursorPosition
* @return {!OutputResultsGroup}
*/
static create(resultsGroup, cursorPosition) {
const outputResultsGroup = new OutputResultsGroup();
outputResultsGroup.setResultsGroup(resultsGroup, cursorPosition);
return outputResultsGroup;
}
constructor() {
super('output-results-group-template');
}
/**
* @param {!mojom.OmniboxResponse} resultsGroup
* @param {number} cursorPosition
*/
setResultsGroup(resultsGroup, cursorPosition) {
/** @private {ResultsDetails} */
this.details_ = {
cursorPosition: cursorPosition,
time: resultsGroup.timeSinceOmniboxStartedMs,
done: resultsGroup.done,
host: resultsGroup.host,
isTypedHost: resultsGroup.isTypedHost
};
/** @type {!Array<!OutputHeader>} */
this.headers = COLUMNS.map(OutputHeader.create);
/** @type {!OutputResultsTable} */
this.combinedResults =
OutputResultsTable.create(resultsGroup.combinedResults);
/** @type {!Array<!OutputResultsTable>} */
this.individualResultsList =
resultsGroup.resultsByProvider
.map(resultsWrapper => resultsWrapper.results)
.filter(results => results.length > 0)
.map(OutputResultsTable.create);
if (this.hasAdditionalProperties) {
this.headers.push(OutputHeader.create(ADDITIONAL_PROPERTIES_COLUMN));
}
this.render_();
}
/**
* Creates a HTML Node representing this data.
* @private
*/
render_() {
clearChildren(this);
/** @private {!Array<!Element>} */
this.innerHeaders_ = [];
customElements.whenDefined(this.$$('output-results-details').localName)
.then(
() =>
this.$$('output-results-details').setDetails(this.details_));
this.$$('#table').appendChild(this.renderHeader_());
this.$$('#table').appendChild(this.combinedResults);
this.individualResultsList.forEach(results => {
const innerHeader = this.renderInnerHeader_(results);
this.innerHeaders_.push(innerHeader);
this.$$('#table').appendChild(innerHeader);
this.$$('#table').appendChild(results);
});
}
/** @private @return {!Element} */
renderHeader_() {
const head = document.createElement('thead');
head.classList.add('head');
const row = document.createElement('tr');
this.headers.forEach(cell => row.appendChild(cell));
head.appendChild(row);
return head;
}
/**
* @private
* @param {!OutputResultsTable} results
* @return {!Element}
*/
renderInnerHeader_(results) {
const head = document.createElement('tbody');
head.classList.add('head');
const row = document.createElement('tr');
const cell = document.createElement('th');
// Reserve 1 more column for showing the additional properties column.
cell.colSpan = COLUMNS.length + 1;
cell.textContent = results.innerHeaderText;
row.appendChild(cell);
head.appendChild(row);
return head;
}
/**
* @param {boolean} showIncompleteResults
* @param {boolean} showDetails
* @param {boolean} showAllProviders
*/
updateVisibility(showIncompleteResults, showDetails, showAllProviders) {
// Show the details section above each table if showDetails or
// showIncompleteResults are true.
this.$$('output-results-details').hidden =
!showDetails && !showIncompleteResults;
// Show individual results when showAllProviders is true.
this.individualResultsList.forEach(
individualResults => individualResults.hidden = !showAllProviders);
this.innerHeaders_.forEach(
innerHeader => innerHeader.hidden = !showAllProviders);
// Show certain column headers only if they showDetails is true.
COLUMNS.forEach(({displayAlways}, index) => {
this.headers[index].hidden = !showDetails && !displayAlways;
});
// Show certain columns only if they showDetails is true.
this.autocompleteMatches.forEach(
match => match.updateVisibility(showDetails));
}
/** @param {boolean} elideCells */
updateEliding(elideCells) {
this.autocompleteMatches.forEach(
match => match.updateEliding(elideCells));
}
/**
* @private
* @return {boolean}
*/
get hasAdditionalProperties() {
return this.combinedResults.hasAdditionalProperties ||
this.individualResultsList.some(
results => results.hasAdditionalProperties);
}
/** @return {!Array<!OutputMatch>} */
get autocompleteMatches() {
return [this.combinedResults]
.concat(this.individualResultsList)
.flatMap(results => results.autocompleteMatches);
}
/** @return {!Array<string>} */
get visibleText() {
return Array.from(this.shadowRoot.querySelectorAll(':host > :not(style)'))
.map(child => child.innerText);
}
}
class OutputResultsDetails extends OmniboxElement {
constructor() {
super('output-results-details-template');
}
/** @param {ResultsDetails} details */
setDetails(details) {
this.$$('#cursor-position').textContent = details.cursorPosition;
this.$$('#time').textContent = details.time;
this.$$('#done').textContent = details.done;
this.$$('#host').textContent = details.host;
this.$$('#is-typed-host').textContent = details.isTypedHost;
}
}
/**
* Helps track and render a list of results. Each result is tracked and
* rendered by OutputMatch below.
*/
class OutputResultsTable extends HTMLTableSectionElement {
/**
* @param {!Array<!mojom.AutocompleteMatch>} results
* @return {!OutputResultsTable}
*/
static create(results) {
const resultsTable = new OutputResultsTable();
resultsTable.results = results;
return resultsTable;
}
constructor() {
super();
this.classList.add('body');
/** @type {!Array<!OutputMatch>} */
this.autocompleteMatches = [];
}
/** @param {!Array<!mojom.AutocompleteMatch>} results */
set results(results) {
this.autocompleteMatches.forEach(match => match.remove());
this.autocompleteMatches = results.map(OutputMatch.create);
this.autocompleteMatches.forEach(this.appendChild.bind(this));
}
/** @return {?string} */
get innerHeaderText() {
return this.autocompleteMatches[0].providerName;
}
/** @return {boolean} */
get hasAdditionalProperties() {
return this.autocompleteMatches.some(
match => match.hasAdditionalProperties);
}
}
/** Helps track and render a single match. */
class OutputMatch extends HTMLTableRowElement {
/**
* @param {!mojom.AutocompleteMatch} match
* @return {!OutputMatch}
*/
static create(match) {
/** @suppress {checkTypes} */
const outputMatch = new OutputMatch();
outputMatch.match = match;
return outputMatch;
}
/** @param {!mojom.AutocompleteMatch} match */
set match(match) {
/** @type {!Object<string, !OutputProperty>} */
this.properties = {};
/** @type {!OutputProperty} */
this.properties.contentsAndDescription;
/** @type {?string} */
this.providerName = match.providerName || null;
COLUMNS.forEach(column => {
const values = column.sourceProperties.map(
propertyName => /** @type {Object} */ (match)[propertyName]);
this.properties[column.matchKey] =
OutputProperty.create(column, values);
});
const unconsumedProperties = {};
Object.entries(match)
.filter(([name]) => !CONSUMED_SOURCE_PROPERTIES.includes(name))
.forEach(([name, value]) => unconsumedProperties[name] = value);
/** @type {!OutputProperty} */
this.additionalProperties = OutputProperty.create(
ADDITIONAL_PROPERTIES_COLUMN, [unconsumedProperties]);
this.render_();
}
/** @private */
render_() {
clearChildren(this);
COLUMNS.map(column => this.properties[column.matchKey])
.forEach(cell => this.appendChild(cell));
if (this.hasAdditionalProperties) {
this.appendChild(this.additionalProperties);
}
}
/**
* @param {string} url
* @param {string} data
*/
updateAnswerImage(url, data) {
if (this.properties.contentsAndDescription.value === url) {
this.properties.contentsAndDescription.setAnswerImageData(data);
}
}
/** @param {boolean} showDetails */
updateVisibility(showDetails) {
// Show certain columns only if they showDetails is true.
COLUMNS.forEach(({matchKey, displayAlways}) => {
this.properties[matchKey].hidden = !showDetails && !displayAlways;
});
}
/** @param {boolean} elideCells */
updateEliding(elideCells) {
Object.values(this.properties)
.forEach(property => property.classList.toggle('elided', elideCells));
}
/** @param {string} filterText */
filter(filterText) {
this.classList.remove('filtered-highlighted');
this.allProperties_.forEach(
property => property.classList.remove('filtered-highlighted-nested'));
if (!filterText) {
return;
}
const matchedProperties = this.allProperties_.filter(
property => FilterUtil.filterText(property.text, filterText));
const isMatch = matchedProperties.length > 0;
this.classList.toggle('filtered-highlighted', isMatch);
matchedProperties.forEach(
property => property.classList.add('filtered-highlighted-nested'));
}
/**
* @return {boolean} Used to determine if the additional properties column
* needs to be displayed for this match.
*/
get hasAdditionalProperties() {
return Object
.keys(/** @type {!Object} */ (this.additionalProperties.value))
.length > 0;
}
/** @private @return {!Array<!OutputProperty>} */
get allProperties_() {
return Object.values(this.properties).concat(this.additionalProperties);
}
}
class OutputHeader extends HTMLTableCellElement {
/**
* @param {Column} column
* @return {!OutputHeader}
*/
static create(column) {
const header = new OutputHeader();
header.classList.add(column.headerClassName);
header.setContents(column.headerText, column.url);
header.title = column.tooltip;
return header;
}
/**
* @param {!Array<string>} texts
* @param {string=} url
*/
setContents(texts, url) {
clearChildren(this);
let container;
if (url) {
container = document.createElement('a');
container.href = url;
} else {
container = document.createElement('div');
}
container.classList.add('header-container');
texts.forEach(text => {
const part = document.createElement('span');
part.textContent = text;
container.appendChild(part);
});
this.appendChild(container);
}
}
class OutputProperty extends HTMLTableCellElement {
constructor() {
super();
/** @type {string} */
this.filterName;
}
/**
* @param {Column} column
* @param {!Array<*>} values
* @return {!OutputProperty}
*/
static create(column, values) {
const outputProperty = new column.outputClass();
outputProperty.classList.add(column.cellClassName);
outputProperty.filterName = column.tooltip.split('\n', 1)[0];
outputProperty.values = values;
return outputProperty;
}
/** @param {!Array<*>} values */
set values(values) {
/** @type {*} */
this.value = values[0];
/** @private {!Array<*>} */
this.values_ = values;
/** @override */
this.render_();
}
/** @private */
render_() {}
/** @return {string} */
get text() {
return this.value + '';
}
}
class OutputPairProperty extends OutputProperty {
constructor() {
super();
this.container_ = document.createElement('div');
this.container_.classList.add('pair-container');
this.appendChild(this.container_);
/** @type {!Element} */
this.first_ = document.createElement('div');
this.first_.classList.add('pair-item');
this.container_.appendChild(this.first_);
/** @type {!Element} */
this.second_ = document.createElement('div');
this.second_.classList.add('pair-item');
this.container_.appendChild(this.second_);
}
/** @private @override */
render_() {
[this.first_.textContent, this.second_.textContent] = this.values_;
}
/** @override @return {string} */
get text() {
return `${this.values_[0]}.${this.values_[1]}`;
}
}
class OutputOverlappingPairProperty extends OutputPairProperty {
constructor() {
super();
this.notOverlapWarning_ = document.createElement('div');
this.notOverlapWarning_.classList.add('overlap-warning');
this.container_.appendChild(this.notOverlapWarning_);
}
/** @private @override */
render_() {
const overlap = this.values_[0].endsWith(this.values_[1]);
const firstText = this.values_[1] && overlap ?
this.values_[0].slice(0, -this.values_[1].length) :
this.values_[0];
this.first_.textContent = firstText;
this.second_.textContent = this.values_[1];
this.notOverlapWarning_.textContent = overlap ?
'' :
`btw, these texts do not overlap; '${
this.values_[1]}' was expected to be a suffix of '${
this.values_[0]}'`;
}
}
class OutputAnswerProperty extends OutputProperty {
constructor() {
super();
/** @private {!Element} */
this.container_ = document.createElement('div');
this.container_.classList.add('pair-container');
this.appendChild(this.container_);
/** @type {!Element} */
this.image_ = document.createElement('img');
this.image_.classList.add('pair-item');
this.container_.appendChild(this.image_);
/** @type {!Element} */
this.contents_ = document.createElement('div');
this.contents_.classList.add('pair-item', 'contents');
this.container_.appendChild(this.contents_);
/** @type {!Element} */
this.description_ = document.createElement('div');
this.description_.classList.add('pair-item', 'description');
this.container_.appendChild(this.description_);
/** @type {!Element} */
this.answer_ = document.createElement('div');
this.answer_.classList.add('pair-item', 'answer');
this.container_.appendChild(this.answer_);
/** @type {!Element} */
this.imageUrl_ = document.createElement('a');
this.imageUrl_.classList.add('pair-item', 'image-url');
this.container_.appendChild(this.imageUrl_);
}
/** @param {string} imageData */
setAnswerImageData(imageData) {
this.image_.src = imageData;
}
/** @private @override */
render_() {
this.contents_.textContent = this.values_[1];
this.description_.textContent = this.values_[2];
this.answer_.textContent = this.values_[3];
this.imageUrl_.textContent = this.values_[0];
this.imageUrl_.href = this.values_[0];
}
/** @override @return {string} */
get text() {
return this.values_.join('.');
}
}
class OutputBooleanProperty extends OutputProperty {
constructor() {
super();
/** @private {!Element} */
this.icon_ = document.createElement('div');
this.appendChild(this.icon_);
}
/** @private @override */
render_() {
this.icon_.classList.toggle('check-mark', !!this.value);
this.icon_.classList.toggle('x-mark', !this.value);
this.icon_.textContent = this.value;
}
get text() {
return (this.value ? 'is: ' : 'not: ') + this.filterName;
}
}
class OutputJsonProperty extends OutputProperty {
constructor() {
super();
/** @private {!Element} */
this.pre_ = document.createElement('pre');
this.pre_.classList.add('json');
this.appendChild(this.pre_);
}
/** @private @override */
render_() {
clearChildren(this.pre_);
this.text.split(/("(?:[^"\\]|\\.)*":?|\w+)/)
.map(word => {
return OutputJsonProperty.renderJsonWord(
word, OutputJsonProperty.classifyJsonWord(word));
})
.forEach(jsonSpan => this.pre_.appendChild(jsonSpan));
}
/** @override @return {string} */
get text() {
return JSON.stringify(this.value, null, 2);
}
/**
* @param {string} word
* @param {string|undefined} cls
* @return {!Element}
*/
static renderJsonWord(word, cls) {
const span = document.createElement('span');
if (cls) {
span.classList.add(cls);
}
span.textContent = word;
return span;
}
/**
* @param {string} word
* @return {string|undefined}
*/
static classifyJsonWord(word) {
if (/^\d+$/.test(word)) {
return 'number';
}
if (/^"[^]*":$/.test(word)) {
return 'key';
}
if (/^"[^]*"$/.test(word)) {
return 'string';
}
if (/true|false/.test(word)) {
return 'boolean';
}
if (/null/.test(word)) {
return 'null';
}
}
}
class OutputKeyValueTuplesProperty extends OutputJsonProperty {
/** @private @override */
render_() {
clearChildren(this.pre_);
this.value.forEach(({key, value}) => {
this.pre_.appendChild(
OutputJsonProperty.renderJsonWord(key + ': ', 'key'));
this.pre_.appendChild(
OutputJsonProperty.renderJsonWord(value + '\n', 'number'));
});
}
/** @override @return {string} */
get text() {
return this.value.reduce(
(prev, {key, value}) => `${prev}${key}: ${value}\n`, '');
}
}
class OutputUrlProperty extends OutputProperty {
constructor() {
super();
/** @private {!Element} */
this.container_ = document.createElement('div');
this.container_.classList.add('pair-container');
this.appendChild(this.container_);
/** @private {!Element} */
this.iconAndUrlContainer_ = document.createElement('div');
this.iconAndUrlContainer_.classList.add('pair-item');
this.container_.appendChild(this.iconAndUrlContainer_);
/** @private {!Element} */
this.icon_ = document.createElement('img');
this.iconAndUrlContainer_.appendChild(this.icon_);
/** @private {!Element} */
this.urlLink_ = document.createElement('a');
this.iconAndUrlContainer_.appendChild(this.urlLink_);
/** @private {!Element} */
this.strippedUrlLink_ = document.createElement('a');
this.strippedUrlLink_.classList.add('pair-item');
this.container_.appendChild(this.strippedUrlLink_);
}
/** @private @override */
render_() {
const [destinationUrl, isSearchType, strippedDestinationUrl] =
this.values_;
if (isSearchType) {
this.icon_.removeAttribute('src');
} else {
this.icon_.src = `chrome://favicon/${destinationUrl}`;
}
this.urlLink_.textContent = destinationUrl;
this.urlLink_.href = destinationUrl;
this.strippedUrlLink_.textContent = strippedDestinationUrl;
this.strippedUrlLink_.href = strippedDestinationUrl;
}
}
class OutputTextProperty extends OutputProperty {
constructor() {
super();
/** @private {!Element} */
this.div_ = document.createElement('div');
this.appendChild(this.div_);
}
/** @private @override */
render_() {
this.div_.textContent = this.value;
}
}
/** Responsible for highlighting and hiding rows using filter text. */
class FilterUtil {
/**
* Checks if a string fuzzy-matches a filter string. Each character
* of filterText must be present in the search text, either adjacent to the
* previous matched character, or at the start of a new word (see
* textToWords_).
* E.g. `abc` matches `abc`, `a big cat`, `a-bigCat`, `a very big cat`, and
* `an amBer cat`; but does not match `abigcat` or `an amber cat`.
* `green rainbow` is matched by `gre rain`, but not by `gre bow`.
* One exception is the first character, which may be matched mid-word.
* E.g. `een rain` can also match `green rainbow`.
* @param {string} searchText
* @param {string} filterText
* @return {boolean}
*/
static filterText(searchText, filterText) {
const regexFilter =
Array.from(filterText)
.map(word => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
.join('(.*\\.)?');
const words = FilterUtil.textToWords_(searchText).join('.');
return words.match(new RegExp(regexFilter, 'i')) !== null;
}
/**
* Splits a string into words, delimited by either capital letters, groups
* of digits, or non alpha characters.
* E.g., `https://google.com/the-dog-ate-134pies` will be split to:
* https, :, /, /, google, ., com, /, the, -, dog, -, ate, -, 134, pies
* We don't use `Array.split`, because we want to group digits, e.g. 134.
* @private
* @param {string} text
* @return {!Array<string>}
*/
static textToWords_(text) {
const MAX_TEXT_LENGTH = 200;
if (text.length > MAX_TEXT_LENGTH) {
text = text.slice(0, MAX_TEXT_LENGTH);
console.warn(`text to be filtered too long, truncatd; max length: ${
MAX_TEXT_LENGTH}, truncated text: ${text}`);
}
return text.match(/[a-z]+|[A-Z][a-z]*|\d+|./g) || [];
}
}
class Column {
/**
* @param {!Array<string>} headerText
* @param {string} url
* @param {string} matchKey
* @param {boolean} displayAlways
* @param {string} tooltip
* @param {function(new:OutputProperty)} outputClass
* @param {!Array<string>} sourceProperties
*/
constructor(
headerText, url, matchKey, displayAlways, tooltip, sourceProperties,
outputClass) {
/** @type {!Array<string>} split per span container to support styling. */
this.headerText = headerText;
/** @type {string} header link href or blank if non-hyperlink header. */
this.url = url;
/** @type {string} the field name used in the Match.properties object. */
this.matchKey = matchKey;
/** @type {boolean} if shown when showDetails option is false. */
this.displayAlways = displayAlways;
/** @type {string} header tooltip. */
this.tooltip = tooltip;
/** @type {!Array<string>} related mojo AutocompleteMatch properties. */
this.sourceProperties = sourceProperties;
/** @type {function(new:OutputProperty)} */
this.outputClass = outputClass;
const hyphenatedName =
matchKey.replace(/[A-Z]/g, c => '-' + c.toLowerCase());
/** @type {string} */
this.cellClassName = 'cell-' + hyphenatedName;
/** @type {string} */
this.headerClassName = 'header-' + hyphenatedName;
}
}
/**
* A constant that's used to decide what autocomplete result
* properties to output in what order.
* @type {!Array<!Column>}
*/
const COLUMNS = [
new Column(
['Provider', 'Type'], '', 'providerAndType', true,
'The AutocompleteProvider suggesting this result. / The type of the ' +
'result.',
['providerName', 'type'], OutputPairProperty),
new Column(
['Relevance'], '', 'relevance', true,
'The result score. Higher is more relevant.', ['relevance'],
OutputTextProperty),
new Column(
['Contents', 'Description', 'Answer'], '', 'contentsAndDescription',
true,
'The text that is presented identifying the result. / The page title ' +
'of the result.',
['image', 'contents', 'description', 'answer'], OutputAnswerProperty),
new Column(
['D'], '', 'allowedToBeDefaultMatch', true,
'Can be Default\nA green checkmark indicates that the result can be ' +
'the default match (i.e., can be the match that pressing enter ' +
'in the omnibox navigates to).',
['allowedToBeDefaultMatch'], OutputBooleanProperty),
new Column(
['S'], '', 'starred', false,
'Starred\nA green checkmark indicates that the result has been ' +
'bookmarked.',
['starred'], OutputBooleanProperty),
new Column(
['T'], '', 'hasTabMatch', false,
'Has Tab Match\nA green checkmark indicates that the result URL ' +
'matches an open tab.',
['hasTabMatch'], OutputBooleanProperty),
new Column(
['URL', 'Stripped URL'], '', 'destinationUrl', true,
'The URL for the result.',
['destinationUrl', 'isSearchType', 'strippedDestinationUrl'],
OutputUrlProperty),
new Column(
['Fill', 'Inline'], '', 'fillAndInline', false,
'The text shown in the omnibox when the result is selected. / The ' +
'text shown in the omnibox as a blue highlight selection ' +
'following the cursor, if this match is shown inline.',
['fillIntoEdit', 'inlineAutocompletion'],
OutputOverlappingPairProperty),
new Column(
['D'], '', 'deletable', false,
'Deletable\nA green checkmark indicates that the result can be ' +
'deleted from the visit history.',
['deletable'], OutputBooleanProperty),
new Column(
['P'], '', 'fromPrevious', false,
'From Previous\nTrue if this match is from a previous result.',
['fromPrevious'], OutputBooleanProperty),
new Column(
['Tran'],
'https://cs.chromium.org/chromium/src/ui/base/page_transition_types.h' +
'?q=page_transition_types.h&sq=package:chromium&dr=CSs&l=14',
'transition', false, 'How the user got to the result.', ['transition'],
OutputTextProperty),
new Column(
['D'], '', 'providerDone', false,
'Done\nA green checkmark indicates that the provider is done looking ' +
'for more results.',
['providerDone'], OutputBooleanProperty),
new Column(
['Associated Keyword'], '', 'associatedKeyword', false,
'If non-empty, a "press tab to search" hint will be shown and will ' +
'engage this keyword.',
['associatedKeyword'], OutputTextProperty),
new Column(
['Keyword'], '', 'keyword', false,
'The keyword of the search engine to be used.', ['keyword'],
OutputTextProperty),
new Column(
['D'], '', 'duplicates', false,
'Duplicates\nThe number of matches that have been marked as ' +
'duplicates of this match.',
['duplicates'], OutputTextProperty),
new Column(
['Additional Info'], '', 'additionalInfo', false,
'Provider-specific information about the result.', ['additionalInfo'],
OutputKeyValueTuplesProperty)
];
/** @type {!Column} */
const ADDITIONAL_PROPERTIES_COLUMN = new Column(
['Additional Properties'], '', 'additionalProperties', false,
'Properties not accounted for.', [], OutputJsonProperty);
const CONSUMED_SOURCE_PROPERTIES =
COLUMNS.flatMap(column => column.sourceProperties);
customElements.define('omnibox-output', OmniboxOutput);
customElements.define('output-results-group', OutputResultsGroup);
customElements.define('output-results-details', OutputResultsDetails);
customElements.define(
'output-results-table', OutputResultsTable, {extends: 'tbody'});
customElements.define('output-match', OutputMatch, {extends: 'tr'});
customElements.define('output-header', OutputHeader, {extends: 'th'});
customElements.define(
'output-pair-property', OutputPairProperty, {extends: 'td'});
customElements.define(
'output-overlapping-pair-property', OutputOverlappingPairProperty,
{extends: 'td'});
customElements.define(
'output-answer-property', OutputAnswerProperty, {extends: 'td'});
customElements.define(
'output-boolean-property', OutputBooleanProperty, {extends: 'td'});
customElements.define(
'output-json-property', OutputJsonProperty, {extends: 'td'});
customElements.define(
'output-key-value-tuple-property', OutputKeyValueTuplesProperty,
{extends: 'td'});
customElements.define(
'output-url-property', OutputUrlProperty, {extends: 'td'});
customElements.define(
'output-text-property', OutputTextProperty, {extends: 'td'});
return {OmniboxOutput: OmniboxOutput};
});