Commit 9e52c746 authored by Moe Ahmadi's avatar Moe Ahmadi Committed by Commit Bot

[WebUI][NTP] Renders realbox match details and handles match interactions

Adds the ability to render the *full* version of autcomplete matches
(including entities) and handle interactions with them such as:
1. showing and hiding matches in a suggestion group.
2. removing a match using the RHS remove button.
3. navigating to a match via click.
4. updating the realbox icon/favicon as the match selection changes.

Bug: 1041129
Change-Id: Iee5d8192cd0b2b122a4c9b22a7860278fa5d37be
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2199361
Commit-Queue: Moe Ahmadi <mahmadi@chromium.org>
Reviewed-by: default avatarTibor Goldschwendt <tiborg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#769000}
parent 150fb904
......@@ -18,7 +18,9 @@ js_type_check("closure_compile") {
":grid",
":logo",
":realbox",
":realbox_button",
":realbox_dropdown",
":realbox_icon",
":realbox_match",
":theme_icon",
":untrusted_iframe",
......@@ -141,6 +143,7 @@ js_library("fakebox") {
js_library("realbox") {
deps = [
":browser_proxy",
":realbox_icon",
":utils",
"//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
"//ui/webui/resources/js:assert.m",
......@@ -150,6 +153,9 @@ js_library("realbox") {
js_library("realbox_dropdown") {
deps = [
":browser_proxy",
":realbox_button",
":realbox_match",
":utils",
"//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
"//ui/webui/resources/js:assert.m",
......@@ -157,8 +163,22 @@ js_library("realbox_dropdown") {
]
}
js_library("realbox_button") {
deps = [
"//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
]
}
js_library("realbox_icon") {
deps = [
"//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
]
}
js_library("realbox_match") {
deps = [
":realbox_button",
":realbox_icon",
":utils",
"//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
"//ui/webui/resources/js:load_time_data.m",
......@@ -200,9 +220,11 @@ html_to_js("web_components") {
"logo.js",
"mini_page.js",
"most_visited.js",
"realbox.js",
"realbox_button.js",
"realbox_dropdown.js",
"realbox_icon.js",
"realbox_match.js",
"realbox.js",
"theme_icon.js",
"untrusted_iframe.js",
"voice_search_overlay.js",
......
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M12.884 16.123a1.229 1.229 0 0 1-1.768 0l-6.25-6.428a1.3 1.3 0 0 1-.366-.91c0-.709.56-1.285 1.25-1.285.345 0 .657.144.884.377L12 13.397l5.366-5.52a1.23 1.23 0 0 1 .884-.377c.69 0 1.25.576 1.25 1.286a1.3 1.3 0 0 1-.366.909l-6.25 6.428z" fill="#5F6368"/></svg>
\ No newline at end of file
......@@ -59,9 +59,15 @@
<include name="IDR_NEW_TAB_PAGE_REALBOX_JS"
file="${root_gen_dir}/chrome/browser/resources/new_tab_page/realbox.js"
use_base_dir="false" type="BINDATA" compress="false" />
<include name="IDR_NEW_TAB_PAGE_REALBOX_BUTTON_JS"
file="${root_gen_dir}/chrome/browser/resources/new_tab_page/realbox_button.js"
use_base_dir="false" type="BINDATA" compress="false" />
<include name="IDR_NEW_TAB_PAGE_REALBOX_DROPDOWN_JS"
file="${root_gen_dir}/chrome/browser/resources/new_tab_page/realbox_dropdown.js"
use_base_dir="false" type="BINDATA" compress="false" />
<include name="IDR_NEW_TAB_PAGE_REALBOX_ICON_JS"
file="${root_gen_dir}/chrome/browser/resources/new_tab_page/realbox_icon.js"
use_base_dir="false" type="BINDATA" compress="false" />
<include name="IDR_NEW_TAB_PAGE_REALBOX_MATCH_JS"
file="${root_gen_dir}/chrome/browser/resources/new_tab_page/realbox_match.js"
use_base_dir="false" type="BINDATA" compress="false" />
......
......@@ -15,6 +15,8 @@
file="icons/brush.svg" type="BINDATA" compress="gzip" />
<include name="IDR_NEW_TAB_PAGE_CHECK_CIRCLE_SVG"
file="icons/check_circle.svg" type="BINDATA" compress="gzip" />
<include name="IDR_NEW_TAB_PAGE_CHEVRON_SVG"
file="icons/chevron.svg" type="BINDATA" compress="gzip" />
<include name="IDR_NEW_TAB_PAGE_GENERIC_GLOBE_SVG"
file="icons/generic_globe.svg" type="BINDATA" compress="gzip" />
<include name="IDR_NEW_TAB_PAGE_PENCIL_ICON_SVG"
......
......@@ -32,7 +32,7 @@
background-color: var(--search-box-bg, white);
border-radius: calc(0.5 * var(--ntp-realbox-height));
border: none;
color: var(--search-box-text, var(--ntp-primary-text-color));
color: var(--search-box-text);
font-size: 16px;
height: 100%;
outline: none;
......@@ -50,62 +50,40 @@
}
input::placeholder {
color: var(--search-box-placeholder, var(--ntp-secondary-text-color));
color: var(--search-box-placeholder, var(--google-grey-refresh-700));
}
input:focus::placeholder {
color: transparent;
}
input:focus {
input:focus,
:host([matches-are-visible]) input {
background-color: var(--search-box-results-bg, white);
}
/* The realbox has a "search" icon by default. When a match with a different
* icon ("clock" for historical searches or "page" for URLs) or a favicon is
* selected, the realbox also gets that icon/favicon. Icons are rendered via
* webkit-mask-image + background-color and favicons are rendered via
* background-image set on #realboxIcon's style property in JS. */
#realboxIcon {
-webkit-mask-position: center;
-webkit-mask-repeat: no-repeat;
-webkit-mask-size: 16px;
background-color: var(--search-box-icon, var(--ntp-secondary-text-color));
background-position: center;
background-repeat: no-repeat;
background-size: 16px;
ntp-realbox-icon {
height: 100%;
left: 16px;
left: 12px;
position: absolute;
top: 0;
width: 24px;
}
:host-context([dir='rtl']) #realboxIcon {
:host-context([dir='rtl']) ntp-realbox-icon {
left: unset;
right: 16px;
}
#realboxIcon[data-icon='search.svg'] {
-webkit-mask-image: url(search.svg);
-webkit-mask-size: 20px; /* "Search" icon is larger than the other icons. */
}
#realboxIcon[data-icon='google_g.png'] {
background-color: transparent;
background-image: url(google_g.png);
background-size: 12px;
right: 12px;
}
#voiceSearchButton {
background: url(icons/googlemic_clr_24px.svg) no-repeat center;
background-size: 21px;
background-size: 21px 21px;
border: none;
border-radius: 2px;
cursor: pointer;
height: 100%;
outline: none;
padding: 0;
pointer-events: auto;
position: absolute;
right: 16px;
width: 26px;
......@@ -120,12 +98,12 @@
box-shadow: var(--ntp-focus-shadow);
}
:-webkit-any(input, #realboxIcon, #voiceSearchButton) {
:-webkit-any(input, ntp-realbox-icon, #voiceSearchButton) {
z-index: 2;
}
ntp-realbox-dropdown {
border-radius: calc(0.5 * var(--ntp-realbox-height));
border-radius: calc(0.25 * var(--ntp-realbox-height));
box-shadow: 0 1px 6px 0 rgba(32, 33, 36, .28);
left: 0;
padding-bottom: 8px;
......@@ -144,13 +122,17 @@
on-focus="onInputFocus_" on-input="onInputInput_"
on-keydown="onInputKeydown_" on-keyup="onInputKeyup_"
on-mousedown="onInputMouseDown_" on-paste="onInputPaste_">
<div id="realboxIcon" data-icon$="[[realboxIcon_]]"></div>
<ntp-realbox-icon match="[[selectedMatch_]]"
favicon-data-url="[[selectedMatch_.faviconDataUrl]]"
default-icon="[[realboxIcon_]]" in-searchbox>
</ntp-realbox-icon>
<button id="voiceSearchButton" on-click="onVoiceSearchClick_"
title="$i18n{voiceSearchButtonLabel}">
</button>
<ntp-realbox-dropdown id="matches" role="listbox" theme="[[theme]]"
result="[[result_]]" selected-match-index="{{selectedMatchIndex_}}"
on-result-repaint="onResultRepaint_"
on-match-focusin="onMatchFocusin_" hidden$="[[!matchesAreVisible]]">
on-result-repaint="onResultRepaint_" on-match-focusin="onMatchFocusin_"
on-match-click="onMatchClick_" on-match-remove="onMatchRemove_"
hidden$="[[!matchesAreVisible]]">
</ntp-realbox-dropdown>
</div>
......@@ -4,6 +4,7 @@
import './strings.m.js';
import './realbox_dropdown.js';
import './realbox_icon.js';
import {assert} from 'chrome://resources/js/assert.m.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
......@@ -138,7 +139,10 @@ class RealboxElement extends PolymerElement {
value: false,
},
/** @private */
/**
* Realbox default icon (i.e., Google G icon or the search loupe).
* @private
*/
realboxIcon_: {
type: String,
value: () => loadTimeData.getString('realboxDefaultIcon'),
......@@ -184,6 +188,8 @@ class RealboxElement extends PolymerElement {
this.callbackRouter_ = BrowserProxy.getInstance().callbackRouter;
/** @private {?number} */
this.autocompleteResultChangedListenerId_ = null;
/** @private {?number} */
this.autocompleteMatchImageAvailableListenerId_ = null;
}
/** @override */
......@@ -192,6 +198,9 @@ class RealboxElement extends PolymerElement {
this.autocompleteResultChangedListenerId_ =
this.callbackRouter_.autocompleteResultChanged.addListener(
this.onAutocompleteResultChanged_.bind(this));
this.autocompleteMatchImageAvailableListenerId_ =
this.callbackRouter_.autocompleteMatchImageAvailable.addListener(
this.onAutocompleteMatchImageAvailable_.bind(this));
}
/** @override */
......@@ -199,6 +208,8 @@ class RealboxElement extends PolymerElement {
super.disconnectedCallback();
this.callbackRouter_.removeListener(
assert(this.autocompleteResultChangedListenerId_));
this.callbackRouter_.removeListener(
assert(this.autocompleteMatchImageAvailableListenerId_));
}
/** @override */
......@@ -211,6 +222,34 @@ class RealboxElement extends PolymerElement {
// Callbacks
//============================================================================
/**
* @param {number} matchIndex match index
* @param {!url.mojom.Url} url match imageUrl or destinationUrl.
* @param {string} dataUrl match image or favicon content in in base64 encoded
* Data URL format.
* @private
*/
onAutocompleteMatchImageAvailable_(matchIndex, url, dataUrl) {
if (!this.result_) {
return;
}
const match = this.result_.matches[matchIndex];
if (!match) {
return;
}
// Set the favicon content on the match. It may become the default match.
if (match.destinationUrl.url === url.url) {
/** @suppress {checkTypes} */
match.faviconDataUrl = dataUrl;
// If the match is currently the default match, update its favicon.
if (match === this.selectedMatch_) {
this.notifyPath('selectedMatch_.faviconDataUrl');
}
}
}
/**
* @private
* @param {search.mojom.AutocompleteResult} result
......@@ -236,7 +275,7 @@ class RealboxElement extends PolymerElement {
// Navigate to the default up-to-date match if the user typed and pressed
// 'Enter' too fast.
if (this.lastIgnoredEnterEvent_) {
this.navigateToMatch_(firstMatch, this.lastIgnoredEnterEvent_);
this.navigateToMatch_(0, this.lastIgnoredEnterEvent_);
this.lastIgnoredEnterEvent_ = null;
}
} else {
......@@ -260,7 +299,7 @@ class RealboxElement extends PolymerElement {
'--search-box-placeholder': skColorToRgba(assert(this.theme.placeholder)),
'--search-box-results-bg': skColorToRgba(assert(this.theme.resultsBg)),
'--search-box-text': skColorToRgba(assert(this.theme.text)),
'--search-box-icon': skColorToRgba(assert(this.theme.icon))
'--search-box-icon': skColorToRgba(assert(this.theme.icon)),
});
}
......@@ -462,7 +501,7 @@ class RealboxElement extends PolymerElement {
if ([this.$.matches, this.$.input].includes(e.target)) {
if (this.lastQueriedInput_ === decodeString16(this.result_.input)) {
if (this.selectedMatch_) {
this.navigateToMatch_(this.selectedMatch_, e);
this.navigateToMatch_(this.selectedMatchIndex_, e);
}
} else {
// User typed and pressed 'Enter' too quickly. Ignore this for now
......@@ -527,6 +566,15 @@ class RealboxElement extends PolymerElement {
});
}
/**
* @param {!CustomEvent<{index: number, event:!MouseEvent}>} e Event
* containing index of the match that was clicked.
* @private
*/
onMatchClick_(e) {
this.navigateToMatch_(e.detail.index, e.detail.event);
}
/**
* @param {!CustomEvent<number>} e Event containing index of the match that
* received focus.
......@@ -544,6 +592,15 @@ class RealboxElement extends PolymerElement {
});
}
/**
* @param {!CustomEvent<number>} e Event containing index of the match that
* was removed.
* @private
*/
onMatchRemove_(e) {
this.pageHandler_.deleteAutocompleteMatch(e.detail);
}
/**
* @param {!CustomEvent<number>} e Event containing the result repaint time.
* @private
......@@ -589,18 +646,18 @@ class RealboxElement extends PolymerElement {
}
/**
* @param {!search.mojom.AutocompleteMatch} match
* @param {number} matchIndex
* @param {!Event} e
* @private
*/
navigateToMatch_(match, e) {
const line = this.result_.matches.indexOf(match);
assert(line >= 0);
navigateToMatch_(matchIndex, e) {
assert(matchIndex >= 0);
const match = assert(this.result_.matches[matchIndex]);
assert(this.lastInputFocusTime_);
const delta =
mojoTimeDelta(window.performance.now() - this.lastInputFocusTime_);
this.pageHandler_.openAutocompleteMatch(
line, match.destinationUrl, this.matchesAreVisible, delta,
matchIndex, match.destinationUrl, this.matchesAreVisible, delta,
e.button || 0, e.altKey, e.ctrlKey, e.metaKey, e.shiftKey);
e.preventDefault();
}
......
<style>
:host {
align-items: center;
border-radius: 50%;
display: flex;
flex-shrink: 0;
height: 24px;
justify-content: center;
width: 24px;
}
:host([hidden]) {
display: none;
}
:host(:hover) {
background-color: var(--search-box-icon-bg-hovered, rgba(var(--google-grey-900-rgb), .16));
}
:host(:focus-within) {
background-color: var(--search-box-icon-bg-focused, rgba(var(--google-grey-900-rgb), .32));
}
button {
-webkit-mask-image: url(chrome://resources/images/icon_clear.svg);
-webkit-mask-position: center;
-webkit-mask-repeat: no-repeat;
-webkit-mask-size: 16px;
background: transparent;
border: 0;
height: 100%;
margin: 0;
padding: 0;
width: 100%;
}
:host-context(.header) button {
-webkit-mask-image: url(icons/chevron.svg);
-webkit-transform: rotate(180deg);
background-color: var(--search-box-icon, var(--google-grey-900));
}
:host-context(.header[group-is-hidden]) button {
-webkit-transform: none;
}
:host-context(ntp-realbox-match:hover) button {
background-color: var(--search-box-icon, var(--google-grey-900));
}
:host-context(ntp-realbox-match:-webkit-any(:focus-within, .selected)) button,
:host-context(.header:focus-within) button {
background-color: var(--search-box-icon-selected, var(--google-grey-900));
}
</style>
<button></button>
// Copyright 2020 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.
import './strings.m.js';
import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
// The RHS action button. Used on autocomplete matches as the remove button as
// well as on suggestion group headers as the toggle button.
class RealboxButtonElement extends PolymerElement {
static get is() {
return 'ntp-realbox-button';
}
static get template() {
return html`{__html_template__}`;
}
ready() {
super.ready();
this.addEventListener('mousedown', this.onMouseDown_.bind(this));
}
//============================================================================
// Event handlers
//============================================================================
/**
* @param {!Event} e
*/
onMouseDown_(e) {
e.preventDefault(); // Prevents default browser action (focus).
}
}
customElements.define(RealboxButtonElement.is, RealboxButtonElement);
<style>
:host {
background-color: var(--search-box-results-bg, white);
color: var(--search-box-results-text, var(--ntp-primary-text-color));
overflow: hidden;
}
......@@ -17,9 +16,42 @@
}
}
ntp-realbox-match {
color: var(--search-box-results-text);
}
.header {
align-items: center;
color: rgb(var(--google-grey-refresh-700-rgb));
display: flex;
font-size: 13px;
font-weight: 500;
justify-content: space-between;
line-height: 16px;
margin-top: 8px;
outline: none;
overflow: hidden;
padding-bottom: 4px;
padding-inline-end: 16px;
padding-inline-start: 12px;
padding-top: 4px;
text-overflow: ellipsis;
text-transform: uppercase;
white-space: nowrap;
}
ntp-realbox-match:hover,
.header:hover {
background-color: var(--search-box-results-bg-hovered, rgba(var(--google-grey-900-rgb), .1));
}
ntp-realbox-match:-webkit-any(:focus-within, .selected) {
background-color: var(--search-box-results-bg-selected, rgb(219, 219, 220));
color: var(--search-box-results-text-selected, rgb(var(--GG900-rgb)));
background-color: var(--search-box-results-bg-selected, rgba(var(--google-grey-900-rgb), .16));
color: var(--search-box-results-text-selected, var(--google-grey-900));
}
.header:focus-within {
background-color: var(--search-box-results-bg-selected, rgba(var(--google-grey-900-rgb), .16));
}
</style>
<iron-selector id="selector" selectable="ntp-realbox-match"
......@@ -29,13 +61,22 @@
<template is="dom-if" if="[[groupHasHeader_(groupId)]]">
<!-- Header cannot be tabbed into but gets focus when clicked. This stops
the dropdown from losing focus and closing as a result. -->
<div tabindex="-1">[[headerForGroup_(groupId)]]</div>
<div class="header" tabindex="-1" on-focusin="onHeaderFocusin_"
group-is-hidden$="[[groupIsHidden_(hiddenGroupIds_.*, groupId)]]">
[[headerForGroup_(groupId)]]
<ntp-realbox-button data-id$="[[groupId]]"
on-click="onToggleButtonClick_">
</ntp-realbox-button>
</div>
</template>
<template is="dom-repeat" items="[[result.matches]]"
filter="[[computeMatchBelongsToGroup_(groupId)]]">
<ntp-realbox-match role="option" match="[[item]]"
on-focusin="onMatchFocusin_">
</ntp-realbox-match>
<template is="dom-if" if="[[!groupIsHidden_(hiddenGroupIds_.*, groupId)]]"
restamp>
<template is="dom-repeat" items="[[result.matches]]"
filter="[[computeMatchBelongsToGroup_(groupId)]]">
<ntp-realbox-match role="option" match="[[item]]"
match-index="[[matchIndex_(item)]]">
</ntp-realbox-match>
</template>
</template>
<template>
</iron-selector>
......@@ -3,12 +3,14 @@
// found in the LICENSE file.
import './strings.m.js';
import './realbox_button.js';
import './realbox_match.js';
import {assert} from 'chrome://resources/js/assert.m.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {BrowserProxy} from './browser_proxy.js';
import {decodeString16, skColorToRgba} from './utils.js';
/**
......@@ -75,6 +77,16 @@ class RealboxDropdownElement extends PolymerElement {
computed: `computeGroupIds_(result)`,
},
/**
* The list of suggestion group IDs whose matches should be hidden.
* @type {!Array<string>}
* @private
*/
hiddenGroupIds_: {
type: Array,
computed: `computeHiddenGroupIds_(result)`,
},
/**
* The list of selectable match elements.
* @type {!Array<!Element>}
......@@ -87,6 +99,12 @@ class RealboxDropdownElement extends PolymerElement {
};
}
constructor() {
super();
/** @private {newTabPage.mojom.PageHandlerRemote} */
this.pageHandler_ = BrowserProxy.getInstance().handler;
}
//============================================================================
// Public methods
//============================================================================
......@@ -175,14 +193,36 @@ class RealboxDropdownElement extends PolymerElement {
return;
}
const icon = assert(this.theme.icon);
// Icon's background color in a hovered context (0x29 == .16).
// TODO(crbug.com/1041129): Share this with the Omnibox.
const iconBgHovered = {value: icon.value & 0x29ffffff};
const iconSelected = assert(this.theme.iconSelected);
// Icon's background color in a focused context (0x52 == .32).
// TODO(crbug.com/1041129): Share this with the Omnibox.
const iconBgFocused = {value: iconSelected.value & 0x52ffffff};
this.updateStyles({
'--search-box-results-bg': skColorToRgba(assert(this.theme.resultsBg)),
'--search-box-icon-bg-focused': skColorToRgba(iconBgFocused),
'--search-box-icon-bg-hovered': skColorToRgba(iconBgHovered),
'--search-box-icon-selected': skColorToRgba(iconSelected),
'--search-box-icon': skColorToRgba(icon),
'--search-box-results-bg-hovered':
skColorToRgba(assert(this.theme.resultsBgHovered)),
'--search-box-results-bg-selected':
skColorToRgba(assert(this.theme.resultsBgSelected)),
'--search-box-results-text':
skColorToRgba(assert(this.theme.resultsText)),
'--search-box-results-bg': skColorToRgba(assert(this.theme.resultsBg)),
'--search-box-results-dim-selected':
skColorToRgba(assert(this.theme.resultsDimSelected)),
'--search-box-results-dim': skColorToRgba(assert(this.theme.resultsDim)),
'--search-box-results-text-selected':
skColorToRgba(assert(this.theme.resultsTextSelected)),
'--search-box-results-text':
skColorToRgba(assert(this.theme.resultsText)),
'--search-box-results-url-selected':
skColorToRgba(assert(this.theme.resultsUrlSelected)),
'--search-box-results-url': skColorToRgba(assert(this.theme.resultsUrl)),
});
}
......@@ -191,33 +231,76 @@ class RealboxDropdownElement extends PolymerElement {
//============================================================================
/**
* @param {!CustomEvent} e
* @private
*/
onMatchFocusin_(e) {
e.stopPropagation();
onHeaderFocusin_() {
// The header got focus. Unselect the selected match, if any.
this.unselect();
}
this.dispatchEvent(new CustomEvent('match-focusin', {
bubbles: true,
composed: true,
detail: this.selectableMatchElements_.indexOf(
/** @type {!Element} */ (e.target)),
}));
/**
* @param {!Event} e
* @private
*/
onToggleButtonClick_(e) {
const groupId = e.target.dataset.id;
// Tell the backend to toggle visibility of the given suggestion group ID.
this.pageHandler_.toggleSuggestionGroupIdVisibility(groupId);
// Hide/Show matches with the given suggestion group ID.
const index = this.hiddenGroupIds_.indexOf(groupId);
if (index === -1) {
this.push('hiddenGroupIds_', groupId);
} else {
this.splice('hiddenGroupIds_', index, 1);
}
}
//============================================================================
// Helpers
//============================================================================
/**
* @returns {number} Index of the match in the autocomplete result. Passed to
* the match so it knows abut its position in the list of matches.
* @private
*/
matchIndex_(match) {
if (!this.result || !this.result.matches) {
return -1;
}
return this.result.matches.indexOf(match);
}
/**
* @returns {!Array<string>}
* @private
*/
computeGroupIds_() {
if (!this.result) {
return [];
}
// Add |NO_SUGGESTION_GROUP_ID| to the list of suggestion group IDs.
return this.result ? [NO_SUGGESTION_GROUP_ID].concat(
Object.keys(this.result.suggestionGroupsMap)) :
[];
return [NO_SUGGESTION_GROUP_ID].concat(
Object.keys(this.result.suggestionGroupsMap));
}
/**
* @returns {!Array<string>}
* @private
*/
computeHiddenGroupIds_() {
if (!this.result) {
return [];
}
return Object.keys(this.result.suggestionGroupsMap)
.filter((groupId => {
return this.result.suggestionGroupsMap[groupId].hidden;
}).bind(this));
}
/**
......@@ -232,6 +315,7 @@ class RealboxDropdownElement extends PolymerElement {
return match.suggestionGroupId === Number(groupId);
};
}
/**
* @param {string} groupId
* @returns {boolean} Whether the given suggestion group ID has a header.
......@@ -241,6 +325,17 @@ class RealboxDropdownElement extends PolymerElement {
return groupId !== NO_SUGGESTION_GROUP_ID;
}
/**
* @param {!Object} hiddenGroupIdsChange
* @param {string} groupId
* @returns {boolean} Whether matches with the given suggestion group ID
* should be hidden.
* @private
*/
groupIsHidden_(hiddenGroupIdsChange, groupId) {
return this.hiddenGroupIds_.indexOf(groupId) !== -1;
}
/**
* @param {string} groupId
* @returns {string} The header for the given suggestion group ID.
......
<style>
:host {
align-items: center;
display: flex;
flex-shrink: 0;
justify-content: center;
width: 32px;
}
#imageContainer {
align-items: center;
border-radius: 8px;
display: none;
height: 32px;
justify-content: center;
overflow: hidden;
width: 32px;
}
:host-context(ntp-realbox-match[has-image]) #imageContainer {
display: flex;
}
#image {
max-height: 32px;
max-width: 32px;
}
#icon {
-webkit-mask-position: center;
-webkit-mask-repeat: no-repeat;
-webkit-mask-size: 16px;
background-color: var(--search-box-icon, var(--google-grey-refresh-700));
background-position: center center;
background-repeat: no-repeat;
background-size: 16px;
height: 24px;
width: 24px;
}
:host-context(ntp-realbox-match[has-image]) #icon {
display: none;
}
:host([in-searchbox][background-image='google_g.png']) #icon {
background-size: 12px;
}
:host([in-searchbox][mask-image='search.svg']) #icon {
-webkit-mask-size: 20px; /* Loupe in realbox is larger than in matches. */
}
</style>
<div id="imageContainer" style$="[[imageContainerStyle_]]">
<image id="image" src$="[[imageDataUrl]]"></image>
</div>
<div id="icon" style$="[[iconStyle_]]">
</div>
// Copyright 2020 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.
import './strings.m.js';
import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
const DOCUMENT_MATCH_TYPE = 'document';
// The LHS icon. Used on autocomplete matches as well as the realbox input to
// render icons, favicons, and entity images.
class RealboxIconElement extends PolymerElement {
static get is() {
return 'ntp-realbox-icon';
}
static get template() {
return html`{__html_template__}`;
}
static get properties() {
return {
//========================================================================
// Public properties
//========================================================================
/**
* Used as a background image on #icon if non-empty.
* @type {string}
*/
backgroundImage: {
type: String,
computed: `computeBackgroundImage_(faviconDataUrl, match)`,
reflectToAttribute: true,
},
/**
* The default icon to show when no match is selected and/or for
* non-navigation matches. Only set in the context of the realbox input.
* @type {string}
*/
defaultIcon: {
type: String,
value: '',
},
/**
* The match favicon content in base64 encoded Data URL format.
* @type {string}
*/
faviconDataUrl: {
type: String,
value: '',
},
/**
* The match image content in base64 encoded Data URL format.
* @type {string}
*/
imageDataUrl: {
type: String,
value: '',
},
/**
* Used as a mask image on #icon if |backgroundImage| is empty.
* @type {string}
*/
maskImage: {
type: String,
computed: `computeMaskImage_(match)`,
reflectToAttribute: true,
},
/**
* @type {!search.mojom.AutocompleteMatch}
*/
match: {
type: Object,
},
//========================================================================
// Private properties
//========================================================================
/**
* @type {string}
* @private
*/
iconStyle_: {
type: String,
computed: `computeIconStyle_(backgroundImage, maskImage)`,
},
/**
* @type {string}
* @private
*/
imageContainerStyle_: {
type: String,
computed: `computeImageContainerStyle_(imageDataUrl, match)`,
},
};
}
//============================================================================
// Helpers
//============================================================================
/**
* @returns {string}
* @private
*/
computeBackgroundImage_() {
// If the match is a navigation one and has a favicon loaded, display that
// as background image. Otherwise, display the colored SVG icon for
// 'document' matches.
// If 'google_g' is the default icon, display that as background image when
// there is no match or the match is not a navigation one. Otherwise, don't
// use a background image (use a mask image instead).
if (this.match && !this.match.isSearchType) {
if (this.faviconDataUrl) {
return this.faviconDataUrl;
} else if (this.match.type === DOCUMENT_MATCH_TYPE) {
return this.match.iconUrl;
} else {
return '';
}
} else if (this.defaultIcon === 'google_g.png') {
return this.defaultIcon;
} else {
return '';
}
}
/**
* @returns {string}
* @private
*/
computeMaskImage_() {
// Use the match icon if available. Otherwise use the default icon.
if (this.match) {
return this.match.iconUrl;
} else {
return this.defaultIcon;
}
}
/**
* @returns {string}
* @private
*/
computeIconStyle_() {
// Use a background image if applicabale. Otherwise use a mask image.
if (this.backgroundImage) {
return `background-image: url(${this.backgroundImage});` +
`background-color: transparent;`;
} else {
return `-webkit-mask-image: url(${this.maskImage});`;
}
}
/**
* @returns {string}
* @private
*/
computeImageContainerStyle_() {
// Show a background color until the image loads.
return (this.match && this.match.imageDominantColor && !this.imageDataUrl) ?
// .25 opacity matching c/b/u/views/omnibox/omnibox_match_cell_view.cc.
`background-color: ${this.match.imageDominantColor}40;` :
'background-color: transparent;';
}
}
customElements.define(RealboxIconElement.is, RealboxIconElement);
......@@ -3,25 +3,95 @@
display: block;
}
a {
#link {
align-items: center;
color: inherit;
display: block;
display: flex;
font-size: 16px;
line-height: 1;
outline: none;
overflow: hidden;
padding-bottom: 8px;
padding-bottom: 6px;
padding-inline-end: 16px;
padding-inline-start: 52px;
padding-top: 8px;
padding-inline-start: 12px;
padding-top: 6px;
position: relative;
text-decoration: none;
text-overflow: ellipsis;
white-space: nowrap;
}
#container {
align-items: center;
display: flex;
flex-grow: 1;
overflow: hidden;
padding-inline-end: 8px;
padding-inline-start: 8px;
}
#contents,
#description {
overflow: hidden;
text-overflow: ellipsis;
}
#separator {
white-space: pre;
}
:host([has-image]) #container {
align-items: flex-start;
flex-direction: column;
}
:host([has-image]) #separator {
display: none;
}
:host([has-image]) #contents {
width: 100%;
}
:host([has-image]) #description {
font-size: 14px;
margin-top: 2px;
width: 100%;
}
.match {
font-weight: 500;
}
:host([has-image]) #description,
.dim {
color: var(--search-box-results-dim, var(--google-grey-600));
}
:host-context(ntp-realbox-match:-webkit-any(:focus-within, .selected)):host([has-image]) #description,
:host-context(ntp-realbox-match:-webkit-any(:focus-within, .selected)) .dim {
color: var(--search-box-results-dim-selected, var(--google-grey-600));
}
.url {
color: var(--search-box-results-url, var(--google-grey-600));
}
:host-context(ntp-realbox-match:-webkit-any(:focus-within, .selected)) .url {
color: var(--search-box-results-url-selected, var(--google-grey-600));
}
</style>
<a id="link" href$="[[match.destinationUrl.url]]">
<span id="contents">[[decodeString16_(match.contents)]]</span>
[[separatorText_]]
<span id="description">[[decodeString16_(match.description)]]</span>
<ntp-realbox-icon match="[[match]]"
favicon-data-url="[[faviconDataUrl]]" image-data-url="[[imageDataUrl]]">
</ntp-realbox-icon>
<div id="container">
<span id="contents" inner-h-t-m-l="[[contentsHtml_]]"></span>
<span id="separator">[[separatorText_]]</span>
<span id="description" inner-h-t-m-l="[[descriptionHtml_]]"></span>
</div>
<ntp-realbox-button id="remove" on-click="onRemoveButtonClick_"
title$="[[removeButtonTitle_]]" hidden$="[[!removeButtonIsVisible_]]">
</ntp-realbox-button>
</a>
......@@ -13,7 +13,7 @@ export function skColorToRgba(skColor) {
const r = (skColor.value >> 16) & 0xff;
const g = (skColor.value >> 8) & 0xff;
const b = skColor.value & 0xff;
return `rgba(${r}, ${g}, ${b}, ${a})`;
return `rgba(${r}, ${g}, ${b}, ${(a / 255).toFixed(2)})`;
}
/**
......
......@@ -102,9 +102,9 @@ suite('NewTabPageAppTest', () => {
(await backgroundManager.whenCalled('setBackgroundColor')).value);
assertStyle(
$$(app, '#content'), '--ntp-theme-shortcut-background-color',
'rgba(0, 255, 0, 255)');
'rgba(0, 255, 0, 1)');
assertStyle(
$$(app, '#content'), '--ntp-theme-text-color', 'rgba(0, 0, 255, 255)');
$$(app, '#content'), '--ntp-theme-text-color', 'rgba(0, 0, 255, 1)');
assertEquals(1, backgroundManager.getCallCount('setShowBackgroundImage'));
assertFalse(await backgroundManager.whenCalled('setShowBackgroundImage'));
assertStyle($$(app, '#backgroundImageAttribution'), 'display', 'none');
......@@ -205,7 +205,7 @@ suite('NewTabPageAppTest', () => {
// Assert.
assertTrue($$(app, '#logo').singleColored);
assertStyle($$(app, '#logo'), '--ntp-logo-color', 'rgba(255, 0, 0, 255)');
assertStyle($$(app, '#logo'), '--ntp-logo-color', 'rgba(255, 0, 0, 1)');
});
test('preview background image', async () => {
......
......@@ -70,14 +70,13 @@ suite('NewTabPageCustomizeThemesTest', () => {
const tiles = customizeThemes.shadowRoot.querySelectorAll('ntp-theme-icon');
assertEquals(tiles.length, 4);
assertEquals(tiles[2].getAttribute('title'), 'theme_0');
assertStyle(tiles[2], '--ntp-theme-icon-frame-color', 'rgba(0, 0, 0, 255)');
assertStyle(tiles[2], '--ntp-theme-icon-frame-color', 'rgba(0, 0, 0, 1)');
assertStyle(
tiles[2], '--ntp-theme-icon-active-tab-color', 'rgba(0, 0, 255, 255)');
tiles[2], '--ntp-theme-icon-active-tab-color', 'rgba(0, 0, 255, 1)');
assertEquals(tiles[3].getAttribute('title'), 'theme_1');
assertStyle(tiles[3], '--ntp-theme-icon-frame-color', 'rgba(255, 0, 0, 1)');
assertStyle(
tiles[3], '--ntp-theme-icon-frame-color', 'rgba(255, 0, 0, 255)');
assertStyle(
tiles[3], '--ntp-theme-icon-active-tab-color', 'rgba(0, 255, 0, 255)');
tiles[3], '--ntp-theme-icon-active-tab-color', 'rgba(0, 255, 0, 1)');
});
test('clicking default theme calls applying default theme', async () => {
......@@ -133,11 +132,10 @@ suite('NewTabPageCustomizeThemesTest', () => {
assertEquals(selectedIcons.length, 1);
assertEquals(selectedIcons[0], customizeThemes.$.autogeneratedTheme);
assertStyle(
selectedIcons[0], '--ntp-theme-icon-frame-color',
'rgba(255, 0, 0, 255)');
selectedIcons[0], '--ntp-theme-icon-frame-color', 'rgba(255, 0, 0, 1)');
assertStyle(
selectedIcons[0], '--ntp-theme-icon-active-tab-color',
'rgba(0, 0, 255, 255)');
'rgba(0, 0, 255, 1)');
});
test('setting default theme selects and updates icon', async () => {
......
......@@ -97,20 +97,31 @@ suite('NewTabPageRealboxTest', () => {
realbox.theme = createTheme().searchBox;
// Assert.
assertStyle(realbox, '--search-box-bg', 'rgba(0, 0, 0, 255)');
assertStyle(realbox, '--search-box-placeholder', 'rgba(0, 0, 3, 255)');
assertStyle(realbox, '--search-box-results-bg', 'rgba(0, 0, 4, 255)');
assertStyle(realbox, '--search-box-text', 'rgba(0, 0, 13, 255)');
assertStyle(realbox, '--search-box-icon', 'rgba(0, 0, 1, 255)');
assertStyle(realbox, '--search-box-bg', 'rgba(0, 0, 0, 1)');
assertStyle(realbox, '--search-box-placeholder', 'rgba(0, 0, 3, 1)');
assertStyle(realbox, '--search-box-results-bg', 'rgba(0, 0, 4, 1)');
assertStyle(realbox, '--search-box-text', 'rgba(0, 0, 13, 1)');
assertStyle(realbox, '--search-box-icon', 'rgba(0, 0, 1, 1)');
});
test('realbox default icon', async () => {
test('realbox default loupe icon', async () => {
// Arrange.
loadTimeData.overrideValues({
realboxDefaultIcon: 'search.svg',
});
PolymerTest.clearBody();
realbox = document.createElement('ntp-realbox');
document.body.appendChild(realbox);
// Assert.
const realboxIconEl = realbox.shadowRoot.querySelector('ntp-realbox-icon');
assertStyle(
realbox.$.realboxIcon, '-webkit-mask-image',
realboxIconEl.$.icon, '-webkit-mask-image',
'url("chrome://new-tab-page/search.svg")');
assertStyle(realbox.$.realboxIcon, 'background-image', 'none');
assertStyle(realboxIconEl.$.icon, 'background-image', 'none');
});
test('realbox default Google G icon', async () => {
// Arrange.
loadTimeData.overrideValues({
realboxDefaultIcon: 'google_g.png',
......@@ -120,9 +131,10 @@ suite('NewTabPageRealboxTest', () => {
document.body.appendChild(realbox);
// Assert.
assertStyle(realbox.$.realboxIcon, '-webkit-mask-image', 'none');
const realboxIconEl = realbox.shadowRoot.querySelector('ntp-realbox-icon');
assertStyle(realboxIconEl.$.icon, '-webkit-mask-image', 'none');
assertStyle(
realbox.$.realboxIcon, 'background-image',
realboxIconEl.$.icon, 'background-image',
'url("chrome://new-tab-page/google_g.png")');
});
......
......@@ -10,19 +10,20 @@ import {waitAfterNextRender} from 'chrome://test/test_util.m.js';
suite('NewTabPageUtilsTest', () => {
test('Can convert simple SkColors to rgba strings', () => {
assertEquals(skColorToRgba({value: 0xffff0000}), 'rgba(255, 0, 0, 255)');
assertEquals(skColorToRgba({value: 0xff00ff00}), 'rgba(0, 255, 0, 255)');
assertEquals(skColorToRgba({value: 0xff0000ff}), 'rgba(0, 0, 255, 255)');
assertEquals(skColorToRgba({value: 0xffff0000}), 'rgba(255, 0, 0, 1.00)');
assertEquals(skColorToRgba({value: 0xff00ff00}), 'rgba(0, 255, 0, 1.00)');
assertEquals(skColorToRgba({value: 0xff0000ff}), 'rgba(0, 0, 255, 1.00)');
assertEquals(
skColorToRgba({value: 0xffffffff}), 'rgba(255, 255, 255, 255)');
assertEquals(skColorToRgba({value: 0xff000000}), 'rgba(0, 0, 0, 255)');
skColorToRgba({value: 0xffffffff}), 'rgba(255, 255, 255, 1.00)');
assertEquals(skColorToRgba({value: 0xff000000}), 'rgba(0, 0, 0, 1.00)');
});
test('Can convert complex SkColors to rgba strings', () => {
assertEquals(skColorToRgba({value: 0xc2a11f8f}), 'rgba(161, 31, 143, 194)');
assertEquals(skColorToRgba({value: 0xf02b6335}), 'rgba(43, 99, 53, 240)');
assertEquals(
skColorToRgba({value: 0x8ae3d2c1}), 'rgba(227, 210, 193, 138)');
skColorToRgba({value: 0xC0a11f8f}), 'rgba(161, 31, 143, 0.75)');
assertEquals(skColorToRgba({value: 0x802b6335}), 'rgba(43, 99, 53, 0.50)');
assertEquals(
skColorToRgba({value: 0x40e3d2c1}), 'rgba(227, 210, 193, 0.25)');
});
test('Can convert simple hex strings to SkColors', () => {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment