Commit 55ecd206 authored by Moe Ahmadi's avatar Moe Ahmadi Committed by Commit Bot

[NTP][Realbox] Displays suggestion headers

- Propagates the suggestions' group information, i.e., group IDs,
  associated headers, and current visibilities (determined by the prefs)
  to JS.
- Renders grouped matches under their associated header.
- Allows showing/hiding grouped suggestions and updates the prefs.

screenshot/zHMaQioPKVF
screenshot/6uiibJ6BLhm

Bug: 1052514
Change-Id: I8982ca0f3ce4d84121fffc8054e9190742af73e1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2145954
Commit-Queue: Moe Ahmadi <mahmadi@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarTommy Li <tommycli@chromium.org>
Cr-Commit-Position: refs/heads/master@{#758950}
parent 12b4a561
<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
......@@ -413,6 +413,8 @@ window.chrome.embeddedSearch.newTabPage.openExtensionsPage;
window.chrome.embeddedSearch.searchBox;
/** @param {number} line */
window.chrome.embeddedSearch.searchBox.deleteAutocompleteMatch;
/** @param {number} suggestionGroupId */
window.chrome.embeddedSearch.searchBox.toggleSuggestionGroupIdVisibility;
window.chrome.embeddedSearch.searchBox.isKeyCaptureEnabled;
/** @param {number} latencyMs */
window.chrome.embeddedSearch.searchBox.logCharTypedToRepaintLatency;
......@@ -440,6 +442,7 @@ let ACMatchClassification;
* descriptionClass: !Array<!ACMatchClassification>,
* destinationUrl: string,
* fillIntoEdit: string,
* suggestionGroupId: number,
* iconUrl: string,
* imageDominantColor: string,
* imageUrl: string,
......@@ -452,9 +455,18 @@ let ACMatchClassification;
*/
let AutocompleteMatch;
/**
* @typedef {{
* header: string,
* hidden: boolean,
* }}
*/
let SuggestionGroup;
/**
* @typedef {{
* input: string,
* suggestionGroupsMap: !Object<!SuggestionGroup>,
* matches: !Array<!AutocompleteMatch>,
* }}
*/
......
......@@ -246,6 +246,7 @@ body.hide-fakebox #fakebox {
display: none;
left: 0;
overflow: hidden;
padding-bottom: 8px;
padding-top: var(--searchbox-height);
position: absolute;
right: 0;
......@@ -264,6 +265,7 @@ body.hide-fakebox #fakebox {
}
#realbox-matches a {
display: block;
font-size: 16px;
line-height: 1;
outline: none;
......@@ -278,6 +280,29 @@ body.hide-fakebox #fakebox {
white-space: nowrap;
}
#realbox-matches .header {
color: rgb(var(--GG700-rgb));
font-size: 13px;
font-weight: 500;
line-height: 16px;
padding-inline-start: 12px;
text-transform: uppercase;
}
#realbox-matches .header .remove-icon {
-webkit-mask-image: url(chevron.svg);
-webkit-transform: rotate(180deg);
background-color: var(--search-box-icon, rgb(var(--GG900-rgb)));
}
#realbox-matches .collapsed .header .remove-icon {
-webkit-transform: none;
}
#realbox-matches .collapsed a:not(.header) {
display: none;
}
[dir=rtl] #realbox-matches a {
background-position-x: calc(100% - 16px);
}
......@@ -304,10 +329,6 @@ body.hide-fakebox #fakebox {
padding-inline-end: 48px;
}
#realbox-matches a:last-of-type {
margin-bottom: 8px; /* Last result is tight with border-radius. */
}
#realbox-matches a:-webkit-any(:focus-within, .selected) .match-icon {
background-color: var(--search-box-icon-selected, rgb(117, 117, 117));
}
......
// Copyright 2015 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.
......@@ -63,6 +62,7 @@ let RealboxOutputUpdate;
*/
const CLASSES = {
ALTERNATE_LOGO: 'alternate-logo', // Shows white logo if required by theme
COLLAPSED: 'collapsed',
// Applies styles to dialogs used in customization.
CUSTOMIZE_DIALOG: 'customize-dialog',
DELAYED_HIDE_NOTIFICATION: 'mv-notice-delayed-hide',
......@@ -82,6 +82,7 @@ const CLASSES = {
HAS_IMAGE: 'has-image', // A realbox match with an image.
// Applies a different style to the error notification if a link is present.
HAS_LINK: 'has-link',
HEADER: 'header',
HIDE_FAKEBOX: 'hide-fakebox',
HIDE_NOTIFICATION: 'notice-hide',
// Contains the image next to a realbox match. Displays a placeholder color
......@@ -245,6 +246,14 @@ const REALBOX_KEYDOWN_HANDLED_KEYS = [
/** @type {?AutocompleteResult} */
let autocompleteResult = null;
/**
* The time of the first character insert operation that has not yet been
* painted in floating point milliseconds. Used to measure the realbox
* responsiveness with a histogram.
* @type {number}
*/
let charTypedTime = 0;
/**
* The currently visible notification element. Null if no notification is
* present.
......@@ -273,14 +282,6 @@ let enterWasPressed = false;
*/
const faviconOrImageUrlToDataUrlCache = {};
/**
* The time of the first character insert operation that has not yet been
* painted in floating point milliseconds. Used to measure the realbox
* responsiveness with a histogram.
* @type {number}
*/
let charTypedTime = 0;
/**
* True if dark mode is enabled.
* @type {boolean}
......@@ -323,6 +324,12 @@ let lastOutput = {text: '', inline: ''};
/** @type {?number} */
let lastRealboxFocusTime = null;
/**
* Current realbox match elements.
* @type {!Array<!Element>}
*/
let matchEls = [];
/**
* The browser embeddedSearch.newTabPage object.
* @type {Object}
......@@ -335,6 +342,12 @@ let ntpApiHandle;
*/
let pastedInRealbox = false;
/**
* A map from a suggestion Group ID to the group element for that group ID.
* @type {!Object<!Element>}
*/
let suggestionGroupElsMap = {};
// Helper methods.
/** @return {boolean} */
......@@ -349,7 +362,7 @@ function autocompleteResultChanged(result) {
return; // Stale result; ignore.
}
renderAutocompleteMatches(result.matches);
renderAutocompleteMatches(result.matches, result.suggestionGroupsMap);
autocompleteResult = result;
......@@ -360,9 +373,10 @@ function autocompleteResultChanged(result) {
text: lastQueriedInput || '',
});
assert(autocompleteResult.matches.length === matchEls.length);
const first = result.matches[0];
if (first && first.allowedToBeDefaultMatch) {
selectMatchEl(assert($(IDS.REALBOX_MATCHES).firstElementChild));
selectMatchEl(matchEls[0]);
updateRealboxOutput({inline: first.inlineAutocompletion});
if (enterWasPressed) {
......@@ -395,7 +409,6 @@ function autocompleteMatchImageAvailable(matchIndex, url, dataUrl) {
}
faviconOrImageUrlToDataUrlCache[url] = dataUrl;
const matchEls = Array.from($(IDS.REALBOX_MATCHES).children);
assert(autocompleteResult.matches.length === matchEls.length);
// Update the match image/favicon.
......@@ -1326,7 +1339,6 @@ function onRealboxCutCopy(e) {
return;
}
const matchEls = Array.from($(IDS.REALBOX_MATCHES).children);
const selected = matchEls.findIndex(matchEl => {
return matchEl.classList.contains(CLASSES.SELECTED);
});
......@@ -1380,16 +1392,21 @@ function onRealboxPaste() {
/** @param {!Event} e */
function onRealboxMatchesFocusIn(e) {
const target = /** @type {Element} */ (e.target);
const link = findAncestor(target, el => el.nodeName === 'A');
if (!link) {
const matchEl = findAncestor(target, el => el.nodeName === 'A');
if (!matchEl) {
return;
}
const selectedIndex = selectMatchEl(link);
const selectedIndex = selectMatchEl(matchEl);
const selectedMatch = autocompleteResult.matches[selectedIndex];
if (!selectedMatch) {
return;
}
// It doesn't really make sense to use fillFromMatch() here as the focus
// change drops the selection (and is probably just noisy to
// screenreaders).
const newFill = autocompleteResult.matches[selectedIndex].fillIntoEdit;
updateRealboxOutput({moveCursorToEnd: true, inline: '', text: newFill});
updateRealboxOutput(
{moveCursorToEnd: true, inline: '', text: selectedMatch.fillIntoEdit});
}
/** @param {Event} e */
......@@ -1467,15 +1484,11 @@ function onRealboxWrapperKeydown(e) {
return;
}
const realboxMatchesEl = $(IDS.REALBOX_MATCHES);
const matchEls = Array.from(realboxMatchesEl.children);
assert(matchEls.length > 0);
assert(autocompleteResult.matches.length === matchEls.length);
const selected = matchEls.findIndex(matchEl => {
return matchEl.classList.contains(CLASSES.SELECTED);
});
assert(autocompleteResult.matches.length === matchEls.length);
if (key === 'Enter') {
if (matchEls.concat(realboxEl).includes(e.target)) {
if (lastQueriedInput === autocompleteResult.input) {
......@@ -1518,19 +1531,23 @@ function onRealboxWrapperKeydown(e) {
return;
}
const visibleMatchEls = matchEls.filter((matchEl) => {
return window.getComputedStyle(matchEl).display !== 'none';
});
/** @type {number} */ let newSelected;
if (key === 'ArrowDown') {
newSelected = selected + 1 < matchEls.length ? selected + 1 : 0;
newSelected = selected + 1 < visibleMatchEls.length ? selected + 1 : 0;
} else if (key === 'ArrowUp') {
newSelected = selected - 1 >= 0 ? selected - 1 : matchEls.length - 1;
newSelected = selected - 1 >= 0 ? selected - 1 : visibleMatchEls.length - 1;
} else if (key === 'Escape' || key === 'PageUp') {
newSelected = 0;
} else if (key === 'PageDown') {
newSelected = matchEls.length - 1;
newSelected = visibleMatchEls.length - 1;
}
assert(selectMatchEl(assert(matchEls[newSelected])) >= 0);
assert(selectMatchEl(assert(visibleMatchEls[newSelected])) >= 0);
e.preventDefault();
const realboxMatchesEl = $(IDS.REALBOX_MATCHES);
if (realboxMatchesEl.contains(document.activeElement)) {
// Selection should match focus if focus is currently in the matches.
matchEls[newSelected].focus();
......@@ -1673,11 +1690,75 @@ function reloadTiles() {
/**
* @param {!Array<!AutocompleteMatch>} matches
* @param {!Object<!SuggestionGroup>} suggestionGroupsMap
*/
function renderAutocompleteMatches(matches) {
function renderAutocompleteMatches(matches, suggestionGroupsMap) {
const realboxMatchesEl = document.createElement('div');
realboxMatchesEl.setAttribute('role', 'listbox');
const newMatchEls = [];
suggestionGroupElsMap = {};
/**
* Creates and returns a remove button that once clicked invokes |callback|.
* @param {!function()} callback
*/
function createRemoveButton(callback) {
const icon = document.createElement('button');
icon.title = configData.translatedStrings.removeSuggestion;
icon.classList.add(CLASSES.REMOVE_ICON);
icon.onmousedown = e => {
e.preventDefault(); // Stops default browser action (focus)
};
icon.onauxclick = e => {
if (e.button == 1) {
// Middle click on delete should just noop for now (matches omnibox).
e.preventDefault();
}
};
icon.onclick = e => {
callback();
e.preventDefault(); // Stops default browser action (navigation)
};
const remove = document.createElement('div');
remove.classList.add(CLASSES.REMOVE_MATCH);
remove.appendChild(icon);
return remove;
}
/**
* Creates and returns an element to contain the header as well as the matches
* belonging to |suggestionGroupId|.
* @param {number} suggestionGroupId
*/
function createSuggestionGroupEl(suggestionGroupId) {
if (suggestionGroupElsMap[suggestionGroupId]) {
return suggestionGroupElsMap[suggestionGroupId];
}
const suggestionGroup = assert(suggestionGroupsMap[suggestionGroupId]);
const groupEl = document.createElement('div');
groupEl.classList.toggle(CLASSES.COLLAPSED, suggestionGroup.hidden);
const headerEl = document.createElement('a');
headerEl.classList.add(CLASSES.HEADER);
headerEl.append(document.createTextNode(suggestionGroup.header));
if (configData.suggestionTransparencyEnabled) {
const remove = createRemoveButton(() => {
groupEl.classList.toggle(CLASSES.COLLAPSED);
window.chrome.embeddedSearch.searchBox
.toggleSuggestionGroupIdVisibility(suggestionGroupId);
});
headerEl.appendChild(remove);
realboxMatchesEl.classList.add(CLASSES.REMOVABLE);
}
groupEl.appendChild(headerEl);
realboxMatchesEl.appendChild(groupEl);
suggestionGroupElsMap[suggestionGroupId] = groupEl;
return groupEl;
}
for (let i = 0; i < matches.length; ++i) {
const match = matches[i];
const matchEl = document.createElement('a');
......@@ -1766,33 +1847,21 @@ function renderAutocompleteMatches(matches) {
}
if (match.supportsDeletion && configData.suggestionTransparencyEnabled) {
const icon = document.createElement('button');
icon.title = configData.translatedStrings.removeSuggestion;
icon.classList.add(CLASSES.REMOVE_ICON);
icon.onmousedown = e => {
e.preventDefault(); // Stops default browser action (focus)
};
icon.onauxclick = e => {
if (e.button == 1) {
// Middle click on delete should just noop for now (matches omnibox).
e.preventDefault();
}
};
icon.onclick = e => {
const remove = createRemoveButton(() => {
window.chrome.embeddedSearch.searchBox.deleteAutocompleteMatch(i);
e.preventDefault(); // Stops default browser action (navigation)
};
const remove = document.createElement('div');
remove.classList.add(CLASSES.REMOVE_MATCH);
remove.appendChild(icon);
});
matchEl.appendChild(remove);
realboxMatchesEl.classList.add(CLASSES.REMOVABLE);
}
if (match.suggestionGroupId) {
const groupEl = createSuggestionGroupEl(match.suggestionGroupId);
groupEl.append(matchEl);
} else {
realboxMatchesEl.append(matchEl);
}
newMatchEls.push(matchEl);
}
if (charTypedTime) {
window.chrome.embeddedSearch.searchBox.logCharTypedToRepaintLatency(
......@@ -1812,6 +1881,7 @@ function renderAutocompleteMatches(matches) {
realboxMatchesEl.addEventListener('focusin', onRealboxMatchesFocusIn);
realboxWrapper.appendChild(realboxMatchesEl);
matchEls = newMatchEls;
realboxWrapper.addEventListener('focusout', onRealboxWrapperFocusOut);
......@@ -1834,8 +1904,8 @@ function renderMatchClassifications(text, classifications) {
return classes.length ? spanWithClasses(classifiedText, classes) :
document.createTextNode(classifiedText);
})
.reduce((container, currentElement) => {
container.appendChild(currentElement);
.reduce((container, currentEl) => {
container.appendChild(currentEl);
return container;
}, document.createElement('span'));
}
......@@ -2037,13 +2107,13 @@ function requestAndInsertGoogleResources() {
}
/**
* @param {!EventTarget} elToSelect
* @param {!EventTarget} matchElToSelect
* @return {number} The selected index (if found); else -1.
*/
function selectMatchEl(elToSelect) {
function selectMatchEl(matchElToSelect) {
let selectedIndex = -1;
Array.from($(IDS.REALBOX_MATCHES).children).forEach((matchEl, i) => {
const found = matchEl === elToSelect;
Array.from(matchEls).forEach((matchEl, i) => {
const found = matchEl === matchElToSelect;
matchEl.classList.toggle(CLASSES.SELECTED, found);
matchEl.setAttribute('aria-selected', found);
if (found) {
......
......@@ -547,6 +547,15 @@ void SearchIPCRouter::DeleteAutocompleteMatch(uint8_t line) {
delegate_->DeleteAutocompleteMatch(line);
}
void SearchIPCRouter::ToggleSuggestionGroupIdVisibility(
int32_t suggestion_group_id) {
if (!policy_->ShouldProcessToggleSuggestionGroupIdVisibility()) {
return;
}
delegate_->ToggleSuggestionGroupIdVisibility(suggestion_group_id);
}
void SearchIPCRouter::set_delegate_for_testing(Delegate* delegate) {
DCHECK(delegate);
delegate_ = delegate;
......
......@@ -182,6 +182,9 @@ class SearchIPCRouter : public content::WebContentsObserver,
bool shift_key) = 0;
virtual void DeleteAutocompleteMatch(uint8_t line) = 0;
virtual void ToggleSuggestionGroupIdVisibility(
int32_t suggestion_group_id) = 0;
};
// An interface to be implemented by consumers of SearchIPCRouter objects to
......@@ -230,6 +233,7 @@ class SearchIPCRouter : public content::WebContentsObserver,
virtual bool ShouldProcessOpenExtensionsPage() = 0;
virtual bool ShouldProcessOpenAutocompleteMatch(bool is_active_tab) = 0;
virtual bool ShouldProcessDeleteAutocompleteMatch() = 0;
virtual bool ShouldProcessToggleSuggestionGroupIdVisibility() = 0;
};
// Creates chrome::mojom::EmbeddedSearchClient connections on request.
......@@ -363,6 +367,7 @@ class SearchIPCRouter : public content::WebContentsObserver,
bool meta_key,
bool shift_key) override;
void DeleteAutocompleteMatch(uint8_t line) override;
void ToggleSuggestionGroupIdVisibility(int32_t suggestion_group_id) override;
void set_embedded_search_client_factory_for_testing(
std::unique_ptr<EmbeddedSearchClientFactory> factory) {
embedded_search_client_factory_ = std::move(factory);
......
......@@ -171,3 +171,8 @@ bool SearchIPCRouterPolicyImpl::ShouldProcessOpenAutocompleteMatch(
bool SearchIPCRouterPolicyImpl::ShouldProcessDeleteAutocompleteMatch() {
return !is_incognito_ && search::IsInstantNTP(web_contents_);
}
bool SearchIPCRouterPolicyImpl::
ShouldProcessToggleSuggestionGroupIdVisibility() {
return !is_incognito_ && search::IsInstantNTP(web_contents_);
}
......@@ -64,6 +64,7 @@ class SearchIPCRouterPolicyImpl : public SearchIPCRouter::Policy {
bool ShouldProcessOpenExtensionsPage() override;
bool ShouldProcessOpenAutocompleteMatch(bool is_active_tab) override;
bool ShouldProcessDeleteAutocompleteMatch() override;
bool ShouldProcessToggleSuggestionGroupIdVisibility() override;
// Used by unit tests.
void set_is_incognito(bool is_incognito) {
......
......@@ -126,6 +126,8 @@ class MockSearchIPCRouterDelegate : public SearchIPCRouter::Delegate {
bool ctrl_key,
bool meta_key,
bool shift_key));
MOCK_METHOD1(ToggleSuggestionGroupIdVisibility,
void(int32_t suggestion_group_id));
};
class MockSearchIPCRouterPolicy : public SearchIPCRouter::Policy {
......@@ -169,6 +171,7 @@ class MockSearchIPCRouterPolicy : public SearchIPCRouter::Policy {
MOCK_METHOD0(ShouldProcessOpenExtensionsPage, bool());
MOCK_METHOD1(ShouldProcessOpenAutocompleteMatch, bool(bool));
MOCK_METHOD0(ShouldProcessDeleteAutocompleteMatch, bool());
MOCK_METHOD0(ShouldProcessToggleSuggestionGroupIdVisibility, bool());
};
class MockEmbeddedSearchClientFactory
......@@ -1104,6 +1107,7 @@ TEST_F(SearchIPCRouterTest, SendAutocompleteResultChanged) {
GetSearchIPCRouter().AutocompleteResultChanged(
chrome::mojom::AutocompleteResult::New(
base::string16(),
base::flat_map<int32_t, chrome::mojom::SuggestionGroupPtr>(),
std::vector<chrome::mojom::AutocompleteMatchPtr>()));
}
......@@ -1120,6 +1124,7 @@ TEST_F(SearchIPCRouterTest, IgnoreAutocompleteResultChanged) {
GetSearchIPCRouter().AutocompleteResultChanged(
chrome::mojom::AutocompleteResult::New(
base::string16(),
base::flat_map<int32_t, chrome::mojom::SuggestionGroupPtr>(),
std::vector<chrome::mojom::AutocompleteMatchPtr>()));
}
......@@ -1204,6 +1209,30 @@ TEST_F(SearchIPCRouterTest, IgnoreDeleteAutocompleteMatch) {
GetSearchIPCRouter().DeleteAutocompleteMatch(0u);
}
TEST_F(SearchIPCRouterTest, SendToggleSuggestionGroupIdVisibility) {
NavigateAndCommitActiveTab(GURL("chrome-search://foo/bar"));
SetupMockDelegateAndPolicy();
MockSearchIPCRouterPolicy* policy = GetSearchIPCRouterPolicy();
EXPECT_CALL(*mock_delegate(), ToggleSuggestionGroupIdVisibility(_)).Times(1);
EXPECT_CALL(*policy, ShouldProcessToggleSuggestionGroupIdVisibility())
.Times(1)
.WillOnce(Return(true));
GetSearchIPCRouter().ToggleSuggestionGroupIdVisibility(1u);
}
TEST_F(SearchIPCRouterTest, IgnoreToggleSuggestionGroupIdVisibility) {
NavigateAndCommitActiveTab(GURL("chrome-search://foo/bar"));
SetupMockDelegateAndPolicy();
MockSearchIPCRouterPolicy* policy = GetSearchIPCRouterPolicy();
EXPECT_CALL(*mock_delegate(), ToggleSuggestionGroupIdVisibility(_)).Times(0);
EXPECT_CALL(*policy, ShouldProcessToggleSuggestionGroupIdVisibility())
.Times(1)
.WillOnce(Return(false));
GetSearchIPCRouter().ToggleSuggestionGroupIdVisibility(1u);
}
TEST_F(SearchIPCRouterTest, IgnoreStopAutoComplete) {
NavigateAndCommitActiveTab(GURL("chrome-search://foo/bar"));
SetupMockDelegateAndPolicy();
......
......@@ -7,6 +7,7 @@
#include <memory>
#include "base/base64.h"
#include "base/containers/flat_map.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
......@@ -68,7 +69,9 @@
#include "components/omnibox/browser/omnibox_event_global_tracker.h"
#include "components/omnibox/browser/omnibox_log.h"
#include "components/omnibox/browser/omnibox_popup_model.h"
#include "components/omnibox/browser/omnibox_prefs.h"
#include "components/omnibox/browser/omnibox_view.h"
#include "components/omnibox/browser/search_suggestion_parser.h"
#include "components/omnibox/browser/suggestion_answer.h"
#include "components/omnibox/browser/vector_icons.h"
#include "components/omnibox/common/omnibox_features.h"
......@@ -97,6 +100,22 @@
namespace {
base::flat_map<int32_t, chrome::mojom::SuggestionGroupPtr>
CreateSuggestionGroupsMap(
PrefService* prefs,
const SearchSuggestionParser::HeadersMap& headers_map) {
base::flat_map<int32_t, chrome::mojom::SuggestionGroupPtr> result_map;
for (const auto& pair : headers_map) {
chrome::mojom::SuggestionGroupPtr suggestion_group =
chrome::mojom::SuggestionGroup::New();
suggestion_group->header = pair.second;
suggestion_group->hidden =
omnibox::IsSuggestionGroupIdHidden(prefs, pair.first);
result_map.emplace(pair.first, std::move(suggestion_group));
}
return result_map;
}
std::vector<chrome::mojom::AutocompleteMatchPtr> CreateAutocompleteMatches(
const AutocompleteResult& result) {
std::vector<chrome::mojom::AutocompleteMatchPtr> matches;
......@@ -118,6 +137,7 @@ std::vector<chrome::mojom::AutocompleteMatchPtr> CreateAutocompleteMatches(
description_class.style));
}
mojom_match->destination_url = match.destination_url.spec();
mojom_match->suggestion_group_id = match.suggestion_group_id.value_or(0);
mojom_match->icon_url =
SearchTabHelper::AutocompleteMatchVectorIconToResourceName(
match.GetVectorIcon(false));
......@@ -533,6 +553,9 @@ void SearchTabHelper::OnResultChanged(AutocompleteController* controller,
ipc_router_.AutocompleteResultChanged(chrome::mojom::AutocompleteResult::New(
autocomplete_controller_->input().text(),
CreateSuggestionGroupsMap(
profile()->GetPrefs(),
autocomplete_controller_->result().headers_map()),
CreateAutocompleteMatches(autocomplete_controller_->result())));
BitmapFetcherService* bitmap_fetcher_service =
......@@ -825,6 +848,15 @@ void SearchTabHelper::StopAutocomplete(bool clear_result) {
time_of_first_autocomplete_query_ = base::TimeTicks();
}
void SearchTabHelper::ToggleSuggestionGroupIdVisibility(
int32_t suggestion_group_id) {
if (!autocomplete_controller_)
return;
omnibox::ToggleSuggestionGroupIdVisibility(profile()->GetPrefs(),
suggestion_group_id);
}
void SearchTabHelper::LogCharTypedToRepaintLatency(uint32_t latency_ms) {
UMA_HISTOGRAM_TIMES("NewTabPage.Realbox.CharTypedToRepaintLatency.ToPaint",
base::TimeDelta::FromMillisecondsD(latency_ms));
......
......@@ -147,6 +147,7 @@ class SearchTabHelper : public content::WebContentsObserver,
bool prevent_inline_autocomplete) override;
void DeleteAutocompleteMatch(uint8_t line) override;
void StopAutocomplete(bool clear_result) override;
void ToggleSuggestionGroupIdVisibility(int32_t suggestion_group_id) override;
void LogCharTypedToRepaintLatency(uint32_t latency_ms) override;
void BlocklistPromo(const std::string& promo_id) override;
void OpenExtensionsPage(double button,
......
......@@ -66,11 +66,20 @@ struct AutocompleteMatch {
bool is_search_type; // Result of AutocompleteMatch::IsSearchType().
string type; // Result of AutocompleteMatchType::ToString().
bool swap_contents_and_description;
// ID of the group the suggestion belongs to. 0 if it does not belong to any.
int32 suggestion_group_id;
bool supports_deletion;
};
struct SuggestionGroup {
mojo_base.mojom.String16 header; // Header for suggestion group.
bool hidden; // Whether suggestion group is allowed to appear in the results.
};
struct AutocompleteResult {
mojo_base.mojom.String16 input;
// Map of suggestion group IDs to their respective info.
map<int32, SuggestionGroup> suggestion_groups_map;
array<AutocompleteMatch> matches;
};
......@@ -225,6 +234,11 @@ interface EmbeddedSearch {
bool ctrl_key,
bool meta_key,
bool shift_key);
// Tells the browser to allow suggestions with the given suggestion group ID
// to appear in the results if they currently are not allowed to or to prevent
// them from appearing in the results if they are currently permitted to.
ToggleSuggestionGroupIdVisibility(int32 suggestion_group_id);
};
[Native]
......
......@@ -482,6 +482,11 @@ void SearchBox::OpenAutocompleteMatch(uint8_t line,
alt_key, ctrl_key, meta_key, shift_key);
}
void SearchBox::ToggleSuggestionGroupIdVisibility(int32_t suggestion_group_id) {
embedded_search_service_->ToggleSuggestionGroupIdVisibility(
suggestion_group_id);
}
void SearchBox::SetPageSequenceNumber(int page_seq_no) {
page_seq_no_ = page_seq_no;
}
......
......@@ -231,6 +231,11 @@ class SearchBox : public content::RenderFrameObserver,
bool meta_key,
bool shift_key);
// Tells the browser to allow suggestions with the given suggestion group ID
// to appear in the results if they currently are not allowed to or to prevent
// them from appearing in the results if they are currently permitted to.
void ToggleSuggestionGroupIdVisibility(int32_t suggestion_group_id);
bool is_focused() const { return is_focused_; }
bool is_input_in_progress() const { return is_input_in_progress_; }
bool is_key_capture_enabled() const { return is_key_capture_enabled_; }
......
......@@ -9,6 +9,7 @@
#include <string>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/i18n/rtl.h"
#include "base/json/json_writer.h"
#include "base/json/string_escape.h"
......@@ -452,6 +453,7 @@ base::Value CreateAutocompleteMatches(
}
dict.SetKey("descriptionClass", std::move(description_class));
dict.SetStringKey("destinationUrl", match->destination_url);
dict.SetIntKey("suggestionGroupId", match->suggestion_group_id);
dict.SetStringKey("inlineAutocompletion", match->inline_autocompletion);
dict.SetBoolKey("isSearchType", match->is_search_type);
dict.SetStringKey("fillIntoEdit", match->fill_into_edit);
......@@ -467,6 +469,20 @@ base::Value CreateAutocompleteMatches(
return list;
}
base::Value CreateSuggestionGroupsMap(
const base::flat_map<int32_t, chrome::mojom::SuggestionGroupPtr>&
suggestion_groups_map) {
base::Value result_map(base::Value::Type::DICTIONARY);
for (const auto& pair : suggestion_groups_map) {
base::Value suggestion_group(base::Value::Type::DICTIONARY);
suggestion_group.SetStringKey("header", pair.second->header);
suggestion_group.SetBoolKey("hidden", pair.second->hidden);
result_map.SetPath(base::NumberToString(pair.first),
std::move(suggestion_group));
}
return result_map;
}
static const char kDispatchFocusChangedScript[] =
"if (window.chrome &&"
" window.chrome.embeddedSearch &&"
......@@ -640,6 +656,7 @@ class SearchBoxBindings : public gin::Wrappable<SearchBoxBindings> {
bool ctrl_key,
bool meta_key,
bool shift_key);
static void ToggleSuggestionGroupIdVisibility(int32_t suggestion_group_id);
DISALLOW_COPY_AND_ASSIGN(SearchBoxBindings);
};
......@@ -659,17 +676,19 @@ gin::ObjectTemplateBuilder SearchBoxBindings::GetObjectTemplateBuilder(
&SearchBoxBindings::IsKeyCaptureEnabled)
.SetMethod("deleteAutocompleteMatch",
&SearchBoxBindings::DeleteAutocompleteMatch)
.SetMethod("logCharTypedToRepaintLatency",
&SearchBoxBindings::LogCharTypedToRepaintLatency)
.SetMethod("openAutocompleteMatch",
&SearchBoxBindings::OpenAutocompleteMatch)
.SetMethod("paste", &SearchBoxBindings::Paste)
.SetMethod("queryAutocomplete", &SearchBoxBindings::QueryAutocomplete)
.SetMethod("stopAutocomplete", &SearchBoxBindings::StopAutocomplete)
.SetMethod("logCharTypedToRepaintLatency",
&SearchBoxBindings::LogCharTypedToRepaintLatency)
.SetMethod("startCapturingKeyStrokes",
&SearchBoxBindings::StartCapturingKeyStrokes)
.SetMethod("stopCapturingKeyStrokes",
&SearchBoxBindings::StopCapturingKeyStrokes);
&SearchBoxBindings::StopCapturingKeyStrokes)
.SetMethod("toggleSuggestionGroupIdVisibility",
&SearchBoxBindings::ToggleSuggestionGroupIdVisibility);
}
// static
......@@ -698,6 +717,15 @@ void SearchBoxBindings::DeleteAutocompleteMatch(int line) {
search_box->DeleteAutocompleteMatch(line);
}
// static
void SearchBoxBindings::ToggleSuggestionGroupIdVisibility(
int32_t suggestion_group_id) {
SearchBox* search_box = GetSearchBoxForCurrentContext();
if (!search_box)
return;
search_box->ToggleSuggestionGroupIdVisibility(suggestion_group_id);
}
// static
void SearchBoxBindings::OpenAutocompleteMatch(
int line,
......@@ -1502,6 +1530,8 @@ void SearchBoxExtension::DispatchAutocompleteResultChanged(
base::Value dict(base::Value::Type::DICTIONARY);
dict.SetStringKey("input", result->input);
dict.SetKey("matches", CreateAutocompleteMatches(result->matches));
dict.SetKey("suggestionGroupsMap",
CreateSuggestionGroupsMap(result->suggestion_groups_map));
std::string json;
base::JSONWriter::Write(dict, &json);
......
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