Commit 892ef7a4 authored by Moe Ahmadi's avatar Moe Ahmadi Committed by Commit Bot

Local NTP, Realbox: Remember pressing 'Enter' when results are not ready

This CL adds logic to "remember" the user pressing 'Enter' when
autocomplete matches are stale. This happens if the user types too fast
and presses 'Enter' immediately. Currently, the realbox either does nothing
or navigates to a stale result. After this change, the realbox does nothing
until up-to-date results have arrived. it then navigates to the default
match, if one exists, assuming that navigation to the default match was the
user intent, signaled by typing too fast and pressing 'Enter'.

Bug: 1024109
Change-Id: I67e434419e586d3488b1239b9fa37da8aaa4fec4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1928214
Commit-Queue: Moe Ahmadi <mahmadi@chromium.org>
Reviewed-by: default avatarDan Beam <dbeam@chromium.org>
Cr-Commit-Position: refs/heads/master@{#718302}
parent 4917d1d7
......@@ -41,9 +41,6 @@ const ACMatchClassificationStyle = {
DIM: 1 << 2,
};
/** @type {string} */
let lastInput;
/** @typedef {{inline: string, text: string}} */
let RealboxOutput;
......@@ -239,8 +236,8 @@ const REALBOX_KEYDOWN_HANDLED_KEYS = [
// Local statics.
/** @type {!Array<!AutocompleteMatch>} */
let autocompleteMatches = [];
/** @type {?AutocompleteResult} */
let autocompleteResult = null;
/**
* The currently visible notification element. Null if no notification is
......@@ -256,6 +253,13 @@ let currNotification = null;
*/
let delayedHideNotification = null;
/**
* Whether 'Enter' was pressed but did not navigate to a match due to matches
* being stale.
* @type {boolean}
*/
let enterWasPressed = false;
/**
* True if dark mode is enabled.
* @type {boolean}
......@@ -275,8 +279,18 @@ let isDeletingInput = false;
*/
let lastBlacklistedTile = null;
/** @type {?number} */
let lastRealboxFocusTime = null;
/**
* The 'Enter' event that was ignored due to matches being stale. Will be used
* to navigate to the default match once up-to-date matches arrive.
* @type {?Event}
*/
let lastEnterEvent = null;
/**
* The last queried input.
* @type {string|undefined}
*/
let lastInput;
/**
* Last text/inline autocompletion shown in the realbox (either by user input or
......@@ -285,6 +299,9 @@ let lastRealboxFocusTime = null;
*/
let lastOutput = {text: '', inline: ''};
/** @type {?number} */
let lastRealboxFocusTime = null;
/**
* The browser embeddedSearch.newTabPage object.
* @type {Object}
......@@ -1083,7 +1100,7 @@ function listen() {
* @param {!Event} e
*/
function navigateToMatch(match, e) {
const line = autocompleteMatches.indexOf(match);
const line = autocompleteResult.matches.indexOf(match);
assert(line >= 0);
assert(lastRealboxFocusTime);
window.chrome.embeddedSearch.searchBox.openAutocompleteMatch(
......@@ -1158,6 +1175,7 @@ function autocompleteResultChanged(result) {
}
populateAutocompleteMatches(result.matches);
autocompleteResult = result;
$(IDS.REALBOX).focus();
......@@ -1170,6 +1188,11 @@ function autocompleteResultChanged(result) {
if (first && first.allowedToBeDefaultMatch) {
selectMatchEl(assert($(IDS.REALBOX_MATCHES).firstElementChild));
updateRealboxOutput({inline: first.inlineAutocompletion});
if (enterWasPressed) {
assert(lastEnterEvent);
navigateToMatch(first, lastEnterEvent);
}
}
}
......@@ -1178,7 +1201,7 @@ function onRealboxCutCopy(e) {
const realboxEl = $(IDS.REALBOX);
if (!realboxEl.value || realboxEl.selectionStart !== 0 ||
realboxEl.selectionEnd !== realboxEl.value.length ||
autocompleteMatches.length === 0) {
!autocompleteResult || autocompleteResult.matches.length === 0) {
// Only handle cut/copy when realbox has content and it's all selected.
return;
}
......@@ -1188,7 +1211,7 @@ function onRealboxCutCopy(e) {
return matchEl.classList.contains(CLASSES.SELECTED);
});
const selectedMatch = autocompleteMatches[selected];
const selectedMatch = autocompleteResult.matches[selected];
if (selectedMatch && !selectedMatch.isSearchType) {
e.clipboardData.setData('text/plain', selectedMatch.destinationUrl);
e.preventDefault();
......@@ -1235,7 +1258,7 @@ function onRealboxWrapperFocusIn(e) {
// 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 = autocompleteMatches[selectedIndex].fillIntoEdit;
const newFill = autocompleteResult.matches[selectedIndex].fillIntoEdit;
updateRealboxOutput({moveCursorToEnd: true, inline: '', text: newFill});
}
}
......@@ -1302,11 +1325,22 @@ function onRealboxWrapperKeydown(e) {
return matchEl.classList.contains(CLASSES.SELECTED);
});
assert(autocompleteMatches.length === matchEls.length);
assert(autocompleteResult.matches.length === matchEls.length);
if (key === 'Enter') {
if (matchEls[selected] && matchEls.concat(realboxEl).includes(e.target)) {
navigateToMatch(autocompleteMatches[selected], e);
if (matchEls.concat(realboxEl).includes(e.target)) {
if (lastInput === autocompleteResult.input) {
if (autocompleteResult.matches[selected]) {
navigateToMatch(autocompleteResult.matches[selected], e);
}
} else {
// User typed and pressed 'Enter' too quickly. Ignore this for now
// because the matches are stale. Navigate to the default match (if one
// exists) once the up-to-date results arrive.
enterWasPressed = true;
lastEnterEvent = e;
e.preventDefault();
}
}
return;
}
......@@ -1324,7 +1358,7 @@ function onRealboxWrapperKeydown(e) {
if (key === 'Delete') {
if (e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey) {
const selectedMatch = autocompleteMatches[selected];
const selectedMatch = autocompleteResult.matches[selected];
if (selectedMatch && selectedMatch.supportsDeletion) {
window.chrome.embeddedSearch.searchBox.deleteAutocompleteMatch(
selected);
......@@ -1365,7 +1399,7 @@ function onRealboxWrapperKeydown(e) {
matchEls[newSelected].focus();
}
const newMatch = autocompleteMatches[newSelected];
const newMatch = autocompleteResult.matches[newSelected];
const newFill = newMatch.fillIntoEdit;
let newInline = '';
if (newMatch.allowedToBeDefaultMatch) {
......@@ -1557,7 +1591,6 @@ function populateAutocompleteMatches(matches) {
const hasMatches = matches.length > 0;
setRealboxMatchesVisible(hasMatches);
setRealboxWrapperListenForKeydown(hasMatches);
autocompleteMatches = matches;
}
/**
......@@ -1866,9 +1899,8 @@ function setAttributionVisibility(show) {
$(IDS.ATTRIBUTION).style.display = show ? '' : 'none';
}
/** @suppress {checkTypes} */
function clearAutocompleteMatches() {
autocompleteMatches = [];
autocompleteResult = null;
window.chrome.embeddedSearch.searchBox.stopAutocomplete(
/*clearResult=*/ true);
// Autocomplete sends updates once it is stopped. Invalidate those results
......
......@@ -785,6 +785,53 @@ test.realbox2.testPressEnterOnSelectedMatch = function() {
assertEquals(1, test.realbox.opens.length);
};
test.realbox2.testPressEnterTooQuickly = function() {
test.realbox.realboxEl.dispatchEvent(new Event('focusin', {bubbles: true}));
test.realbox.realboxEl.value = 'hello';
test.realbox.realboxEl.dispatchEvent(new CustomEvent('input'));
chrome.embeddedSearch.searchBox.autocompleteresultchanged({
input: test.realbox.realboxEl.value,
matches: [test.realbox.getSearchMatch({supportsDeletion: true})]
});
const matchEls1 = $(test.realbox.IDS.REALBOX_MATCHES).children;
assertEquals(1, matchEls1.length);
// First match is selected.
assertTrue(matchEls1[0].classList.contains(test.realbox.CLASSES.SELECTED));
test.realbox.realboxEl.value = 'hello world';
test.realbox.realboxEl.dispatchEvent(new CustomEvent('input'));
const shiftEnter = new KeyboardEvent('keydown', {
bubbles: true,
cancelable: true,
key: 'Enter',
target: test.realbox.realboxEl,
shiftKey: true,
});
test.realbox.realboxEl.dispatchEvent(shiftEnter);
// Did not navigate to the first match as it is stale.
assertEquals(0, test.realbox.opens.length);
const matches = [test.realbox.getUrlMatch({supportsDeletion: true})];
chrome.embeddedSearch.searchBox.autocompleteresultchanged(
{input: test.realbox.realboxEl.value, matches});
const matchEls2 = $(test.realbox.IDS.REALBOX_MATCHES).children;
assertEquals(1, matchEls2.length);
// First match is selected.
assertTrue(matchEls2[0].classList.contains(test.realbox.CLASSES.SELECTED));
// Navigated to the first new match.
assertEquals(1, test.realbox.opens.length);
assertEquals(0, test.realbox.opens[0].index);
assertEquals(matches[0].destinationUrl, test.realbox.opens[0].url);
};
test.realbox2.testPressEnterNoSelectedMatch = function() {
test.realbox.realboxEl.value = 'hello world';
test.realbox.realboxEl.dispatchEvent(new CustomEvent('input'));
......
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