Commit b5c5ea54 authored by Moe Ahmadi's avatar Moe Ahmadi Committed by Commit Bot

[Local NTP][Realbox] Triggers zero-prefix suggestions on explicit gestures

Rather than querying the autocomplete system on the NTP realbox getting
focus, this CL makes it such that the autocomplete system is queriesd on
explicit user gestures such left moust button click, tap and tab/shift+tab
into the NTP realbox.

This fixes the bug where opening a new tab or committing a navigation gives
the realbox input automatic focus causing zero-prefix suggestions to show.

Bug: 1026948
Change-Id: I0a5e5fdb5e158a26a7a2ea1cbac9958ebd2c2886
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1963332
Commit-Queue: Moe Ahmadi <mahmadi@chromium.org>
Reviewed-by: default avatarDan Beam <dbeam@chromium.org>
Cr-Commit-Position: refs/heads/master@{#728042}
parent 3c332fe2
......@@ -864,14 +864,18 @@ function init() {
if (configData.realboxEnabled) {
const realboxEl = $(IDS.REALBOX);
realboxEl.placeholder = configData.translatedStrings.searchboxPlaceholder;
// Using .onclick instead of addEventListener('click') to support tests.
realboxEl.onclick = onRealboxClick;
realboxEl.addEventListener('copy', onRealboxCutCopy);
realboxEl.addEventListener('cut', onRealboxCutCopy);
realboxEl.addEventListener('focus', onRealboxFocus);
realboxEl.addEventListener('input', onRealboxInput);
realboxEl.addEventListener('keyup', onRealboxKeyup);
realboxEl.addEventListener('paste', onRealboxPaste);
const realboxWrapper = $(IDS.REALBOX_INPUT_WRAPPER);
realboxWrapper.addEventListener('focusin', onRealboxWrapperFocusIn);
setRealboxWrapperListenForFocusOut(true);
realboxWrapper.addEventListener('focusout', onRealboxWrapperFocusOut);
realboxWrapper.addEventListener('keydown', onRealboxWrapperKeydown);
searchboxApiHandle.autocompleteresultchanged = autocompleteResultChanged;
......@@ -1245,6 +1249,18 @@ function onMostVisitedChange() {
reloadTiles();
}
/** @param {Event} e */
function onRealboxClick(e) {
if (!e.isTrusted || e.button !== 0) {
// Only handle main (generally left) button presses generated by a user
// action.
return;
}
if (!$(IDS.REALBOX).value) {
queryAutocomplete('');
}
}
/** @param {!Event} e */
function onRealboxCutCopy(e) {
const realboxEl = $(IDS.REALBOX);
......@@ -1270,6 +1286,10 @@ function onRealboxCutCopy(e) {
}
}
function onRealboxFocus() {
lastRealboxFocusTime = Date.now();
}
function onRealboxInput() {
const realboxValue = $(IDS.REALBOX).value;
......@@ -1279,37 +1299,36 @@ function onRealboxInput() {
queryAutocomplete(realboxValue);
} else {
setRealboxMatchesVisible(false);
setRealboxWrapperListenForKeydown(false);
clearAutocompleteMatches();
}
pastedInRealbox = false;
}
/** @param {!Event} e */
function onRealboxKeyup(e) {
if (e.key === 'Tab' && !$(IDS.REALBOX).value) {
queryAutocomplete('');
}
}
function onRealboxPaste() {
pastedInRealbox = true;
}
/** @param {Event} e */
function onRealboxWrapperFocusIn(e) {
if (e.target.matches(`#${IDS.REALBOX}`)) {
if (!$(IDS.REALBOX).value) {
queryAutocomplete('');
}
lastRealboxFocusTime = Date.now();
} else if (e.target.matches(`#${IDS.REALBOX_MATCHES} *`)) {
const target = /** @type {Element} */ (e.target);
const link = findAncestor(target, el => el.nodeName === 'A');
if (!link) {
return;
}
const selectedIndex = selectMatchEl(link);
// 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});
/** @param {!Event} e */
function onRealboxMatchesFocusIn(e) {
const target = /** @type {Element} */ (e.target);
const link = findAncestor(target, el => el.nodeName === 'A');
if (!link) {
return;
}
const selectedIndex = selectMatchEl(link);
// 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});
}
/** @param {Event} e */
......@@ -1329,9 +1348,7 @@ function onRealboxWrapperFocusOut(e) {
// listening for key presses. These stale results should never be shown, but
// correspond to the potentially stale suggestion left in the realbox when
// blurred. That stale result may be navigated to by focusing and pressing
// Enter, and that match may be privileged, so we need to keep the data
// around in order to ascertain this. If matches are reshown, fresh
// autocomplete data will be fetched.
// Enter.
window.chrome.embeddedSearch.searchBox.stopAutocomplete(
/*clearResult=*/ false);
}
......@@ -1367,6 +1384,21 @@ function onRealboxWrapperKeydown(e) {
return;
}
if (!areRealboxMatchesVisible()) {
if (key === 'ArrowUp' || key === 'ArrowDown') {
const realboxValue = $(IDS.REALBOX).value;
if (realboxValue.trim() || !realboxValue) {
queryAutocomplete(realboxValue);
}
e.preventDefault();
return;
}
}
if (!autocompleteResult || autocompleteResult.matches.length === 0) {
return;
}
const realboxMatchesEl = $(IDS.REALBOX_MATCHES);
const matchEls = Array.from(realboxMatchesEl.children);
assert(matchEls.length > 0);
......@@ -1394,17 +1426,6 @@ function onRealboxWrapperKeydown(e) {
return;
}
if (!areRealboxMatchesVisible()) {
if (key === 'ArrowUp' || key === 'ArrowDown') {
const realboxValue = $(IDS.REALBOX).value;
if (realboxValue.trim()) {
queryAutocomplete(realboxValue);
}
e.preventDefault();
}
return;
}
if (key === 'Delete') {
if (e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey) {
const selectedMatch = autocompleteResult.matches[selected];
......@@ -1424,7 +1445,6 @@ function onRealboxWrapperKeydown(e) {
if (key === 'Escape' && selected === 0) {
updateRealboxOutput({inline: '', text: ''});
setRealboxMatchesVisible(false);
setRealboxWrapperListenForKeydown(false);
clearAutocompleteMatches();
e.preventDefault();
return;
......@@ -1680,18 +1700,19 @@ function renderAutocompleteMatches(matches) {
// focused element is being deleted from the DOM. Stop listening to 'focusout'
// event and retore it immediately after since we don't want to stop
// autocomplete in those cases.
setRealboxWrapperListenForFocusOut(false);
const realboxWrapper = $(IDS.REALBOX_INPUT_WRAPPER);
realboxWrapper.removeEventListener('focusout', onRealboxWrapperFocusOut);
$(IDS.REALBOX_MATCHES).remove();
realboxMatchesEl.id = IDS.REALBOX_MATCHES;
realboxMatchesEl.addEventListener('focusin', onRealboxMatchesFocusIn);
$(IDS.REALBOX_INPUT_WRAPPER).appendChild(realboxMatchesEl);
realboxWrapper.appendChild(realboxMatchesEl);
setRealboxWrapperListenForFocusOut(true);
realboxWrapper.addEventListener('focusout', onRealboxWrapperFocusOut);
const hasMatches = matches.length > 0;
setRealboxMatchesVisible(hasMatches);
setRealboxWrapperListenForKeydown(hasMatches);
}
/**
......@@ -2003,26 +2024,6 @@ function setRealboxMatchesVisible(visible) {
$(IDS.REALBOX_INPUT_WRAPPER).classList.toggle(CLASSES.SHOW_MATCHES, visible);
}
/** @param {boolean} listen */
function setRealboxWrapperListenForFocusOut(listen) {
const realboxWrapper = $(IDS.REALBOX_INPUT_WRAPPER);
if (listen) {
realboxWrapper.addEventListener('focusout', onRealboxWrapperFocusOut);
} else {
realboxWrapper.removeEventListener('focusout', onRealboxWrapperFocusOut);
}
}
/** @param {boolean} listen */
function setRealboxWrapperListenForKeydown(listen) {
const realboxWrapper = $(IDS.REALBOX_INPUT_WRAPPER);
if (listen) {
realboxWrapper.addEventListener('keydown', onRealboxWrapperKeydown);
} else {
realboxWrapper.removeEventListener('keydown', onRealboxWrapperKeydown);
}
}
/**
* Shows the error pop-up notification and triggers a delay to hide it. The
* message will be set to |msg|. If |linkName| and |linkOnClick| are present,
......
......@@ -172,12 +172,74 @@ test.realbox1.testEmptyValueDoesntQueryAutocomplete = function() {
assertEquals(test.realbox.queries.length, 0);
};
test.realbox1.testFocusDoesntQueryAutocomplete = function() {
test.realbox.realboxEl.dispatchEvent(new Event('focus'));
assertEquals(0, test.realbox.queries.length);
};
test.realbox1.testTabbingWhenNonEmptyDoesntQueryAutocomplete = function() {
test.realbox.realboxEl.value = ' ';
// Give the realbox focus programmatically.
test.realbox.realboxEl.focus();
test.realbox.realboxEl.dispatchEvent(new KeyboardEvent(
'keyup', {bubbles: true, cancelable: true, key: 'Tab'}));
assertEquals(0, test.realbox.queries.length);
};
test.realbox1.testSpacesDontQueryAutocomplete = function() {
test.realbox.realboxEl.value = ' ';
test.realbox.realboxEl.dispatchEvent(new CustomEvent('input'));
assertEquals(test.realbox.queries.length, 0);
};
test.realbox1.testTabbingWhenEmptyQueriesAutocomplete = function() {
// Give the realbox focus programmatically.
test.realbox.realboxEl.focus();
test.realbox.realboxEl.dispatchEvent(new KeyboardEvent(
'keyup', {bubbles: true, cancelable: true, key: 'Tab'}));
assertEquals(1, test.realbox.queries.length);
assertEquals('', test.realbox.queries[0].input);
};
test.realbox1.testArrowUpDownQueryAutocomplete = function() {
test.realbox.realboxEl.dispatchEvent(new KeyboardEvent('keydown', {
bubbles: true,
cancelable: true,
key: 'ArrowUp',
}));
assertEquals(1, test.realbox.queries.length);
assertEquals('', test.realbox.queries[0].input);
test.realbox.realboxEl.value = 'hello';
test.realbox.realboxEl.dispatchEvent(new KeyboardEvent('keydown', {
bubbles: true,
cancelable: true,
key: 'ArrowDown',
}));
assertEquals(2, test.realbox.queries.length);
assertEquals(test.realbox.realboxEl.value, test.realbox.queries[1].input);
};
test.realbox1.testLeftClickWhenEmptyQueriesAutocomplete = function() {
test.realbox.realboxEl.onclick(test.realbox.trustedEventFacade(
'click', {button: 1, target: test.realbox.realboxEl}));
assertEquals(0, test.realbox.queries.length);
test.realbox.realboxEl.value = ' ';
test.realbox.realboxEl.onclick(test.realbox.trustedEventFacade(
'click', {button: 0, target: test.realbox.realboxEl}));
assertEquals(0, test.realbox.queries.length);
test.realbox.realboxEl.value = '';
test.realbox.realboxEl.onclick(test.realbox.trustedEventFacade(
'click', {button: 0, target: test.realbox.realboxEl}));
assertEquals(1, test.realbox.queries.length);
assertEquals('', test.realbox.queries[0].input);
};
test.realbox1.testInputSentAsQuery = function() {
test.realbox.realboxEl.value = 'hello realbox';
test.realbox.realboxEl.dispatchEvent(new CustomEvent('input'));
......@@ -755,7 +817,7 @@ test.realbox2.testRemoveIcon = function() {
};
test.realbox2.testPressEnterOnSelectedMatch = function() {
test.realbox.realboxEl.dispatchEvent(new Event('focusin', {bubbles: true}));
test.realbox.realboxEl.dispatchEvent(new Event('focus'));
test.realbox.realboxEl.value = 'hello world';
test.realbox.realboxEl.dispatchEvent(new CustomEvent('input'));
......@@ -787,7 +849,7 @@ test.realbox2.testPressEnterOnSelectedMatch = function() {
};
test.realbox2.testPressEnterTooQuickly = function() {
test.realbox.realboxEl.dispatchEvent(new Event('focusin', {bubbles: true}));
test.realbox.realboxEl.dispatchEvent(new Event('focus'));
test.realbox.realboxEl.value = 'hello';
test.realbox.realboxEl.dispatchEvent(new CustomEvent('input'));
......@@ -945,11 +1007,7 @@ test.realbox2.testPressEnterAfterFocusout = function() {
relatedTarget: document.body,
}));
test.realbox.realboxEl.dispatchEvent(new Event('focusin', {
bubbles: true,
cancelable: true,
target: test.realbox.realboxEl,
}));
test.realbox.realboxEl.dispatchEvent(new Event('focus'));
let clicked = false;
matchEls[1].onclick = () => clicked = true;
......@@ -1002,15 +1060,9 @@ test.realbox2.testInputAfterFocusoutPrefixMatches = function() {
};
test.realbox2.testInputAfterFocusoutZeroPrefixMatches = function() {
test.realbox.realboxEl.value = '';
test.realbox.realboxEl.dispatchEvent(new CustomEvent('input'));
// Empty input doesn't query autocomplete.
test.realbox.realboxEl.dispatchEvent(new Event('focusin', {
bubbles: true,
cancelable: true,
target: test.realbox.realboxEl,
}));
// Trigger zero suggest querying autocomplete.
test.realbox.realboxEl.onclick(test.realbox.trustedEventFacade(
'click', {button: 0, target: test.realbox.realboxEl}));
assertEquals(1, test.realbox.queries.length);
chrome.embeddedSearch.searchBox.autocompleteresultchanged({
......@@ -1061,11 +1113,7 @@ test.realbox2.testArrowUpDownShowsMatchesWhenHidden = function() {
assertFalse(test.realbox.areMatchesShowing());
test.realbox.realboxEl.dispatchEvent(new Event('focusin', {
bubbles: true,
cancelable: true,
target: test.realbox.realboxEl,
}));
test.realbox.realboxEl.dispatchEvent(new Event('focus'));
const arrowDown = new KeyboardEvent('keydown', {
bubbles: true,
......@@ -1088,7 +1136,7 @@ test.realbox2.testArrowUpDownShowsMatchesWhenHidden = function() {
// Test that trying to open e.g. chrome:// links goes through the mojo API.
test.realbox2.testPrivilegedDestinationUrls = function() {
test.realbox.realboxEl.dispatchEvent(new Event('focusin', {bubbles: true}));
test.realbox.realboxEl.dispatchEvent(new Event('focus'));
test.realbox.realboxEl.value = 'about';
test.realbox.realboxEl.dispatchEvent(new CustomEvent('input'));
......@@ -1148,10 +1196,8 @@ test.realbox2.testRealboxIconZeroSuggest = function() {
assertFalse(!!realboxIcon.style.backgroundImage);
// Trigger zero suggest querying autocomplete.
test.realbox.realboxEl.dispatchEvent(new Event('focusin', {
bubbles: true,
target: test.realbox.realboxEl,
}));
test.realbox.realboxEl.onclick(test.realbox.trustedEventFacade(
'click', {button: 0, target: test.realbox.realboxEl}));
assertEquals(1, test.realbox.queries.length);
chrome.embeddedSearch.searchBox.autocompleteresultchanged({
......
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