// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import type {Signals} from './omnibox.mojom-webui.js';
export function clearChildren(element: Element) {
while (element.firstChild) {
element.firstChild.remove();
}
}
export function createEl<K extends keyof HTMLElementTagNameMap>(
tagName: K, parentEl: Element|null = null, classes: string[] = [],
text: string = ''): HTMLElementTagNameMap[K] {
const el = document.createElement(tagName);
el.classList.add(...classes);
el.textContent = text;
if (parentEl) {
parentEl.appendChild(el);
}
return el;
}
// Keep consistent:
// - omnibox_event.proto `ScoringSignals`
// - omnibox_scoring_signals.proto `OmniboxScoringSignals`
// - autocomplete_scoring_model_handler.cc
// `AutocompleteScoringModelHandler::ExtractInputFromScoringSignals()`
// - autocomplete_match.cc `AutocompleteMatch::MergeScoringSignals()`
// - autocomplete_controller.cc `RecordScoringSignalCoverageForProvider()`
// - omnibox_metrics_provider.cc `GetScoringSignalsForLogging()`
// - omnibox.mojom `struct Signals`
// - omnibox_page_handler.cc
// `TypeConverter<AutocompleteMatch::ScoringSignals, mojom::SignalsPtr>`
// - omnibox_page_handler.cc `TypeConverter<mojom::SignalsPtr,
// AutocompleteMatch::ScoringSignals>`
// - omnibox_util.ts `signalNames`
// - omnibox/histograms.xml
// `Omnibox.URLScoringModelExecuted.ScoringSignalCoverage`
export const signalNames: Array<keyof Signals> = [
'typedCount',
'visitCount',
'elapsedTimeLastVisitSecs',
'shortcutVisitCount',
'shortestShortcutLen',
'elapsedTimeLastShortcutVisitSec',
'isHostOnly',
'numBookmarksOfUrl',
'firstBookmarkTitleMatchPosition',
'totalBookmarkTitleMatchLength',
'numInputTermsMatchedByBookmarkTitle',
'firstUrlMatchPosition',
'totalUrlMatchLength',
'hostMatchAtWordBoundary',
'totalHostMatchLength',
'totalPathMatchLength',
'totalQueryOrRefMatchLength',
'totalTitleMatchLength',
'hasNonSchemeWwwMatch',
'numInputTermsMatchedByTitle',
'numInputTermsMatchedByUrl',
'lengthOfUrl',
'siteEngagement',
'allowedToBeDefaultMatch',
'searchSuggestRelevance',
'isSearchSuggestEntity',
'isVerbatim',
'isNavsuggest',
'isSearchSuggestTail',
'isAnswerSuggest',
'isCalculatorSuggest',
];
export function clamp(value: number, min: number, max: number) {
return Math.min(Math.max(value, min), max);
}
export class MlVersionObj {
version: number;
string: string;
url: string;
constructor(version: number) {
this.version = version;
this.string = version === -1 ?
String(version) :
`${version} (${new Date(version * 1000).toLocaleDateString()})`;
const codeSearchPrefix =
'https://source.corp.google.com/search?q=file:google3/googledata/chrome/breve/cacao/models/data/omnibox/url_scoring/';
this.url = `${codeSearchPrefix} ${version}`;
}
}
function setFormattedClipboard(text: string) {
let styleCount = 0;
const addStyle = (style: string) => {
styleCount++;
return `<span style="${style}">`;
};
const clearStyles = () => {
const n = styleCount;
styleCount = 0;
return '</span>'.repeat(n);
};
let linkStep = 0;
const addLink = () => {
linkStep = (linkStep + 1) % 3;
switch (linkStep) {
case 1:
return '<a href="';
case 2:
return '">';
case 3:
default:
return '</a>';
}
};
type StyleMap = Record<string, () => string>;
const htmlMap: StyleMap = {
$$: () => '$',
$n: () => '<br>',
$h: () => addStyle('font-weight:bold'),
$r: () => addStyle('color:red'),
$g: () => addStyle('color:green'),
$b: () => addStyle('color:blue'),
$p: () => addStyle('color:purple'),
$0: clearStyles,
$l: addLink,
};
const plainMap: StyleMap = {
$$: () => '$',
$n: () => '\n',
};
const applyMap = (map: StyleMap) => {
return text.split(/(\$.)/g)
.map((part, i) => i % 2 ? map[part]?.() ?? '' : part)
.join('');
};
const clipboardEntries: Array<[string, StyleMap]> = [
['text/html', htmlMap],
['text/plain', plainMap],
];
const clipboardItem =
new ClipboardItem(Object.fromEntries(clipboardEntries.map(
([type, map]) => [type, new Blob([applyMap(map)], {type})])));
return navigator.clipboard.write([clipboardItem]);
}
export function setFormattedClipboardForMl(
matchDetails: Record<string, any>, signals: Signals, shareUrl: string,
version: MlVersionObj) {
return setFormattedClipboard([
// clang-format off
...Object.entries(matchDetails)
.flatMap(([k, v]) => ['$h$g', k, ': $0$b', v, '$0$n']),
...Object.entries(signals)
.filter(([, v]) => v)
.flatMap(([k, v]) => ['$h$p', k, ': $0$b', v, '$0$n']),
...shareUrl ? ['$r', shareUrl, '$0$n'] : [],
'$h$r', 'Version: ', '$0', '$l', version.url, '$l', version.string, '$l$n',
// clang-format on
].join(''));
}