Commit 9b89d615 authored by Dan Beam's avatar Dan Beam Committed by Commit Bot

NTP Realbox: fix some renderer<->browser communication issues

* DCHECK() about not calling mojo callbacks before they're destroyed

This happens when the user types more quickly than we can respond

* Holding backspace accidentally shows matches for last deleted char

This happens because when input is emptied, the UI immediately hides
the matches UI, but an asynchronous result from the autocomplete
"backend" returns right after, which renders a new result.

R=mahmadi@chromium.org

Bug: 1001761, 1001762
Change-Id: Ic18dd8c63e79d4fe2d1563134920725dc19daf97
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1790434Reviewed-by: default avatarTommy Li <tommycli@chromium.org>
Reviewed-by: default avatarNasko Oskov <nasko@chromium.org>
Reviewed-by: default avatarMoe Ahmadi <mahmadi@chromium.org>
Commit-Queue: Dan Beam <dbeam@chromium.org>
Auto-Submit: Dan Beam <dbeam@chromium.org>
Cr-Commit-Position: refs/heads/master@{#695816}
parent af1dd5c0
...@@ -411,7 +411,19 @@ let ACMatchClassification; ...@@ -411,7 +411,19 @@ let ACMatchClassification;
*/ */
let AutocompleteMatch; let AutocompleteMatch;
/** @type {function(!Array<!AutocompleteMatch>):void} */ /** @enum {number} */
let AutocompleteResultStatus;
/**
* @typedef {{
* input: string,
* matches: !Array<!AutocompleteMatch>,
* status: !AutocompleteResultStatus
* }}
*/
let AutocompleteResult;
/** @type {function(!AutocompleteResult):void} */
window.chrome.embeddedSearch.searchBox.onqueryautocompletedone; window.chrome.embeddedSearch.searchBox.onqueryautocompletedone;
/**************************** Translated Strings *****************************/ /**************************** Translated Strings *****************************/
......
...@@ -41,6 +41,12 @@ const ACMatchClassificationStyle = { ...@@ -41,6 +41,12 @@ const ACMatchClassificationStyle = {
DIM: 1 << 2, DIM: 1 << 2,
}; };
/** @enum {number} */
const AutocompleteResultStatus = {
SUCCESS: 0,
SKIPPED: 1,
};
/** @typedef {{inline: string, text: string}} */ /** @typedef {{inline: string, text: string}} */
let RealboxOutput; let RealboxOutput;
...@@ -1025,11 +1031,16 @@ function onMostVisitedChange() { ...@@ -1025,11 +1031,16 @@ function onMostVisitedChange() {
reloadTiles(); reloadTiles();
} }
/** @param {!Array<!AutocompleteMatch>} matches */ /** @param {!AutocompleteResult} result */
function onQueryAutocompleteDone(matches) { function onQueryAutocompleteDone(result) {
if (result.status === AutocompleteResultStatus.SKIPPED ||
result.input !== lastOutput.text) {
return; // Stale or skipped result; ignore.
}
const realboxMatchesEl = document.createElement('div'); const realboxMatchesEl = document.createElement('div');
for (const [i, match] of matches.entries()) { for (const [i, match] of result.matches.entries()) {
const matchEl = document.createElement('a'); const matchEl = document.createElement('a');
matchEl.href = match.destinationUrl; matchEl.href = match.destinationUrl;
...@@ -1068,10 +1079,11 @@ function onQueryAutocompleteDone(matches) { ...@@ -1068,10 +1079,11 @@ function onQueryAutocompleteDone(matches) {
realboxMatchesEl.append(matchEl); realboxMatchesEl.append(matchEl);
} }
// TODO(crbug.com/996516): this should probably not select the first match by
// default in the case of zero-suggest. One easy (but possibly imperfect) way const hasMatches = result.matches.length > 0;
// would be to check if ($(IDS.REALBOX).value) {...}. if (hasMatches) {
realboxMatchesEl.firstElementChild.classList.add(CLASSES.SELECTED); realboxMatchesEl.firstElementChild.classList.add(CLASSES.SELECTED);
}
$(IDS.REALBOX_MATCHES).remove(); $(IDS.REALBOX_MATCHES).remove();
realboxMatchesEl.id = IDS.REALBOX_MATCHES; realboxMatchesEl.id = IDS.REALBOX_MATCHES;
...@@ -1079,7 +1091,6 @@ function onQueryAutocompleteDone(matches) { ...@@ -1079,7 +1091,6 @@ function onQueryAutocompleteDone(matches) {
const realboxWrapper = $(IDS.REALBOX_INPUT_WRAPPER); const realboxWrapper = $(IDS.REALBOX_INPUT_WRAPPER);
realboxWrapper.appendChild(realboxMatchesEl); realboxWrapper.appendChild(realboxMatchesEl);
const hasMatches = matches.length > 0;
realboxWrapper.classList.toggle(CLASSES.SHOW_MATCHES, hasMatches); realboxWrapper.classList.toggle(CLASSES.SHOW_MATCHES, hasMatches);
if (hasMatches) { if (hasMatches) {
...@@ -1088,14 +1099,14 @@ function onQueryAutocompleteDone(matches) { ...@@ -1088,14 +1099,14 @@ function onQueryAutocompleteDone(matches) {
// If the user is deleting content, don't quickly re-suggest the same // If the user is deleting content, don't quickly re-suggest the same
// output. // output.
if (!isDeleting) { if (!isDeleting) {
const first = matches[0]; const first = result.matches[0];
if (first.allowedToBeDefaultMatch && first.inlineAutocompletion) { if (first.allowedToBeDefaultMatch && first.inlineAutocompletion) {
updateRealboxOutput({inline: first.inlineAutocompletion}); updateRealboxOutput({inline: first.inlineAutocompletion});
} }
} }
} }
autocompleteMatches = matches; autocompleteMatches = result.matches;
} }
/** @param {!Event} e */ /** @param {!Event} e */
......
...@@ -441,10 +441,12 @@ void SearchIPCRouter::ConfirmThemeChanges() { ...@@ -441,10 +441,12 @@ void SearchIPCRouter::ConfirmThemeChanges() {
} }
void SearchIPCRouter::QueryAutocomplete( void SearchIPCRouter::QueryAutocomplete(
const std::string& input, const base::string16& input,
chrome::mojom::EmbeddedSearch::QueryAutocompleteCallback callback) { chrome::mojom::EmbeddedSearch::QueryAutocompleteCallback callback) {
if (!policy_->ShouldProcessQueryAutocomplete(is_active_tab_)) { if (!policy_->ShouldProcessQueryAutocomplete(is_active_tab_)) {
std::move(callback).Run(std::vector<chrome::mojom::AutocompleteMatchPtr>()); std::move(callback).Run(chrome::mojom::AutocompleteResult::New(
input, std::vector<chrome::mojom::AutocompleteMatchPtr>(),
chrome::mojom::AutocompleteResultStatus::SKIPPED));
return; return;
} }
......
...@@ -157,7 +157,7 @@ class SearchIPCRouter : public content::WebContentsObserver, ...@@ -157,7 +157,7 @@ class SearchIPCRouter : public content::WebContentsObserver,
virtual void OnConfirmThemeChanges() = 0; virtual void OnConfirmThemeChanges() = 0;
virtual void QueryAutocomplete( virtual void QueryAutocomplete(
const std::string& input, const base::string16& input,
chrome::mojom::EmbeddedSearch::QueryAutocompleteCallback callback) = 0; chrome::mojom::EmbeddedSearch::QueryAutocompleteCallback callback) = 0;
virtual void StopAutocomplete(bool clear_result) = 0; virtual void StopAutocomplete(bool clear_result) = 0;
...@@ -307,7 +307,7 @@ class SearchIPCRouter : public content::WebContentsObserver, ...@@ -307,7 +307,7 @@ class SearchIPCRouter : public content::WebContentsObserver,
void RevertThemeChanges() override; void RevertThemeChanges() override;
void ConfirmThemeChanges() override; void ConfirmThemeChanges() override;
void QueryAutocomplete( void QueryAutocomplete(
const std::string& input, const base::string16& input,
chrome::mojom::EmbeddedSearch::QueryAutocompleteCallback callback) chrome::mojom::EmbeddedSearch::QueryAutocompleteCallback callback)
override; override;
void StopAutocomplete(bool clear_result) override; void StopAutocomplete(bool clear_result) override;
......
...@@ -105,7 +105,7 @@ class MockSearchIPCRouterDelegate : public SearchIPCRouter::Delegate { ...@@ -105,7 +105,7 @@ class MockSearchIPCRouterDelegate : public SearchIPCRouter::Delegate {
MOCK_METHOD0(OnConfirmThemeChanges, void()); MOCK_METHOD0(OnConfirmThemeChanges, void());
MOCK_METHOD2( MOCK_METHOD2(
QueryAutocomplete, QueryAutocomplete,
void(const std::string& input, void(const base::string16& input,
chrome::mojom::EmbeddedSearch::QueryAutocompleteCallback callback)); chrome::mojom::EmbeddedSearch::QueryAutocompleteCallback callback));
MOCK_METHOD1(StopAutocomplete, void(bool clear_result)); MOCK_METHOD1(StopAutocomplete, void(bool clear_result));
}; };
...@@ -1070,3 +1070,17 @@ TEST_F(SearchIPCRouterTest, IgnoreOptOutOfSearchSuggestions) { ...@@ -1070,3 +1070,17 @@ TEST_F(SearchIPCRouterTest, IgnoreOptOutOfSearchSuggestions) {
GetSearchIPCRouter().OptOutOfSearchSuggestions(); GetSearchIPCRouter().OptOutOfSearchSuggestions();
} }
TEST_F(SearchIPCRouterTest, IgnoreQueryAutocomplete) {
NavigateAndCommitActiveTab(GURL("chrome-search://foo/bar"));
SetupMockDelegateAndPolicy();
MockSearchIPCRouterPolicy* policy = GetSearchIPCRouterPolicy();
EXPECT_CALL(*mock_delegate(), QueryAutocomplete(_, _)).Times(0);
EXPECT_CALL(*policy, ShouldProcessQueryAutocomplete(_))
.Times(1)
.WillOnce(Return(false));
GetSearchIPCRouter().QueryAutocomplete(
base::string16(),
base::Bind([](chrome::mojom::AutocompleteResultPtr result) {}));
}
...@@ -125,6 +125,13 @@ SearchTabHelper::SearchTabHelper(content::WebContents* web_contents) ...@@ -125,6 +125,13 @@ SearchTabHelper::SearchTabHelper(content::WebContents* web_contents)
SearchTabHelper::~SearchTabHelper() { SearchTabHelper::~SearchTabHelper() {
if (instant_service_) if (instant_service_)
instant_service_->RemoveObserver(this); instant_service_->RemoveObserver(this);
if (query_autocomplete_callback_) {
std::move(query_autocomplete_callback_)
.Run(chrome::mojom::AutocompleteResult::New(
autocomplete_controller_->input().text(),
std::vector<chrome::mojom::AutocompleteMatchPtr>(),
chrome::mojom::AutocompleteResultStatus::SKIPPED));
}
} }
void SearchTabHelper::OmniboxInputStateChanged() { void SearchTabHelper::OmniboxInputStateChanged() {
...@@ -423,11 +430,14 @@ void SearchTabHelper::FileSelectionCanceled(void* params) { ...@@ -423,11 +430,14 @@ void SearchTabHelper::FileSelectionCanceled(void* params) {
} }
void SearchTabHelper::OnResultChanged(bool default_result_changed) { void SearchTabHelper::OnResultChanged(bool default_result_changed) {
if (!autocomplete_controller_ || !autocomplete_controller_->done() || if (!autocomplete_controller_) {
!query_autocomplete_callback_) { NOTREACHED();
return; return;
} }
if (!autocomplete_controller_->done() || !query_autocomplete_callback_)
return;
std::vector<chrome::mojom::AutocompleteMatchPtr> matches; std::vector<chrome::mojom::AutocompleteMatchPtr> matches;
for (const AutocompleteMatch& match : autocomplete_controller_->result()) { for (const AutocompleteMatch& match : autocomplete_controller_->result()) {
chrome::mojom::AutocompleteMatchPtr mojom_match = chrome::mojom::AutocompleteMatchPtr mojom_match =
...@@ -455,7 +465,11 @@ void SearchTabHelper::OnResultChanged(bool default_result_changed) { ...@@ -455,7 +465,11 @@ void SearchTabHelper::OnResultChanged(bool default_result_changed) {
mojom_match->type = AutocompleteMatchType::ToString(match.type); mojom_match->type = AutocompleteMatchType::ToString(match.type);
matches.push_back(std::move(mojom_match)); matches.push_back(std::move(mojom_match));
} }
std::move(query_autocomplete_callback_).Run(std::move(matches));
std::move(query_autocomplete_callback_)
.Run(chrome::mojom::AutocompleteResult::New(
autocomplete_controller_->input().text(), std::move(matches),
chrome::mojom::AutocompleteResultStatus::SUCCESS));
} }
void SearchTabHelper::OnSelectLocalBackgroundImage() { void SearchTabHelper::OnSelectLocalBackgroundImage() {
...@@ -540,10 +554,12 @@ void SearchTabHelper::OnConfirmThemeChanges() { ...@@ -540,10 +554,12 @@ void SearchTabHelper::OnConfirmThemeChanges() {
} }
void SearchTabHelper::QueryAutocomplete( void SearchTabHelper::QueryAutocomplete(
const std::string& input, const base::string16& input,
chrome::mojom::EmbeddedSearch::QueryAutocompleteCallback callback) { chrome::mojom::EmbeddedSearch::QueryAutocompleteCallback callback) {
if (!search::DefaultSearchProviderIsGoogle(profile())) { if (!search::DefaultSearchProviderIsGoogle(profile())) {
std::move(callback).Run(std::vector<chrome::mojom::AutocompleteMatchPtr>()); std::move(callback).Run(chrome::mojom::AutocompleteResult::New(
input, std::vector<chrome::mojom::AutocompleteMatchPtr>(),
chrome::mojom::AutocompleteResultStatus::SKIPPED));
return; return;
} }
...@@ -559,10 +575,17 @@ void SearchTabHelper::QueryAutocomplete( ...@@ -559,10 +575,17 @@ void SearchTabHelper::QueryAutocomplete(
providers); providers);
} }
if (query_autocomplete_callback_) {
std::move(query_autocomplete_callback_)
.Run(chrome::mojom::AutocompleteResult::New(
input, std::vector<chrome::mojom::AutocompleteMatchPtr>(),
chrome::mojom::AutocompleteResultStatus::SKIPPED));
autocomplete_controller_->Stop(/*clear_results=*/false);
}
query_autocomplete_callback_ = std::move(callback); query_autocomplete_callback_ = std::move(callback);
AutocompleteInput autocomplete_input( AutocompleteInput autocomplete_input(
base::UTF8ToUTF16(input), metrics::OmniboxEventProto::NTP_REALBOX, input, metrics::OmniboxEventProto::NTP_REALBOX,
ChromeAutocompleteSchemeClassifier(profile())); ChromeAutocompleteSchemeClassifier(profile()));
autocomplete_input.set_from_omnibox_focus(input.empty()); autocomplete_input.set_from_omnibox_focus(input.empty());
autocomplete_controller_->Start(autocomplete_input); autocomplete_controller_->Start(autocomplete_input);
......
...@@ -138,7 +138,7 @@ class SearchTabHelper : public content::WebContentsObserver, ...@@ -138,7 +138,7 @@ class SearchTabHelper : public content::WebContentsObserver,
void OnRevertThemeChanges() override; void OnRevertThemeChanges() override;
void OnConfirmThemeChanges() override; void OnConfirmThemeChanges() override;
void QueryAutocomplete( void QueryAutocomplete(
const std::string& input, const base::string16& input,
chrome::mojom::EmbeddedSearch::QueryAutocompleteCallback callback) chrome::mojom::EmbeddedSearch::QueryAutocompleteCallback callback)
override; override;
void StopAutocomplete(bool clear_result) override; void StopAutocomplete(bool clear_result) override;
......
...@@ -52,6 +52,17 @@ struct AutocompleteMatch { ...@@ -52,6 +52,17 @@ struct AutocompleteMatch {
bool swap_contents_and_description; bool swap_contents_and_description;
}; };
enum AutocompleteResultStatus {
SUCCESS = 0,
SKIPPED = 1,
};
struct AutocompleteResult {
mojo_base.mojom.String16 input;
array<AutocompleteMatch> matches;
AutocompleteResultStatus status;
};
// Browser interface to support embedded search. Render frames connect to this // Browser interface to support embedded search. Render frames connect to this
// interface to query browser data, such as the most visited pages. // interface to query browser data, such as the most visited pages.
// See http://dev.chromium.org/embeddedsearch // See http://dev.chromium.org/embeddedsearch
...@@ -164,7 +175,8 @@ interface EmbeddedSearch { ...@@ -164,7 +175,8 @@ interface EmbeddedSearch {
ConfirmThemeChanges(); ConfirmThemeChanges();
// Gets autocomplete matches from the browser. // Gets autocomplete matches from the browser.
QueryAutocomplete(string input) => (array<AutocompleteMatch> matches); QueryAutocomplete(mojo_base.mojom.String16 input) =>
(AutocompleteResult result);
// Cancels the current autocomplete query. Clears the result set if // Cancels the current autocomplete query. Clears the result set if
// |clear_result| is true. // |clear_result| is true.
......
...@@ -432,7 +432,7 @@ void SearchBox::ConfirmThemeChanges() { ...@@ -432,7 +432,7 @@ void SearchBox::ConfirmThemeChanges() {
embedded_search_service_->ConfirmThemeChanges(); embedded_search_service_->ConfirmThemeChanges();
} }
void SearchBox::QueryAutocomplete(const std::string& input) { void SearchBox::QueryAutocomplete(const base::string16& input) {
embedded_search_service_->QueryAutocomplete( embedded_search_service_->QueryAutocomplete(
input, base::BindOnce(&SearchBox::QueryAutocompleteResult, input, base::BindOnce(&SearchBox::QueryAutocompleteResult,
weak_ptr_factory_.GetWeakPtr())); weak_ptr_factory_.GetWeakPtr()));
...@@ -443,10 +443,10 @@ void SearchBox::StopAutocomplete(bool clear_result) { ...@@ -443,10 +443,10 @@ void SearchBox::StopAutocomplete(bool clear_result) {
} }
void SearchBox::QueryAutocompleteResult( void SearchBox::QueryAutocompleteResult(
std::vector<chrome::mojom::AutocompleteMatchPtr> results) { chrome::mojom::AutocompleteResultPtr result) {
if (can_run_js_in_renderframe_) { if (can_run_js_in_renderframe_) {
SearchBoxExtension::DispatchQueryAutocompleteResult( SearchBoxExtension::DispatchQueryAutocompleteResult(
render_frame()->GetWebFrame(), results); render_frame()->GetWebFrame(), std::move(result));
} }
} }
......
...@@ -191,7 +191,7 @@ class SearchBox : public content::RenderFrameObserver, ...@@ -191,7 +191,7 @@ class SearchBox : public content::RenderFrameObserver,
// Queries the autocomplete backend for realbox results for |input| as a // Queries the autocomplete backend for realbox results for |input| as a
// search term. Handled by |QueryAutocompleteResult|. // search term. Handled by |QueryAutocompleteResult|.
void QueryAutocomplete(const std::string& input); void QueryAutocomplete(const base::string16& input);
// Cancels the current autocomplete query. Clears the result set if // Cancels the current autocomplete query. Clears the result set if
// |clear_result| is true. // |clear_result| is true.
...@@ -225,8 +225,7 @@ class SearchBox : public content::RenderFrameObserver, ...@@ -225,8 +225,7 @@ class SearchBox : public content::RenderFrameObserver,
GURL GetURLForMostVisitedItem(InstantRestrictedID item_id) const; GURL GetURLForMostVisitedItem(InstantRestrictedID item_id) const;
// Asynchronous callback for autocomplete query results. Sends to renderer. // Asynchronous callback for autocomplete query results. Sends to renderer.
void QueryAutocompleteResult( void QueryAutocompleteResult(chrome::mojom::AutocompleteResultPtr result);
std::vector<chrome::mojom::AutocompleteMatchPtr> matches);
// The connection to the EmbeddedSearch service in the browser process. // The connection to the EmbeddedSearch service in the browser process.
chrome::mojom::EmbeddedSearchAssociatedPtr embedded_search_service_; chrome::mojom::EmbeddedSearchAssociatedPtr embedded_search_service_;
......
...@@ -545,7 +545,7 @@ class SearchBoxBindings : public gin::Wrappable<SearchBoxBindings> { ...@@ -545,7 +545,7 @@ class SearchBoxBindings : public gin::Wrappable<SearchBoxBindings> {
// Handlers for JS functions. // Handlers for JS functions.
static void Paste(const std::string& text); static void Paste(const std::string& text);
static void QueryAutocomplete(const std::string& input); static void QueryAutocomplete(const base::string16& input);
static void StopAutocomplete(bool clear_result); static void StopAutocomplete(bool clear_result);
static void StartCapturingKeyStrokes(); static void StartCapturingKeyStrokes();
static void StopCapturingKeyStrokes(); static void StopCapturingKeyStrokes();
...@@ -604,7 +604,7 @@ void SearchBoxBindings::Paste(const std::string& text) { ...@@ -604,7 +604,7 @@ void SearchBoxBindings::Paste(const std::string& text) {
} }
// static // static
void SearchBoxBindings::QueryAutocomplete(const std::string& input) { void SearchBoxBindings::QueryAutocomplete(const base::string16& input) {
SearchBox* search_box = GetSearchBoxForCurrentContext(); SearchBox* search_box = GetSearchBoxForCurrentContext();
if (!search_box) if (!search_box)
return; return;
...@@ -1345,9 +1345,13 @@ void SearchBoxExtension::DispatchDeleteCustomLinkResult( ...@@ -1345,9 +1345,13 @@ void SearchBoxExtension::DispatchDeleteCustomLinkResult(
void SearchBoxExtension::DispatchQueryAutocompleteResult( void SearchBoxExtension::DispatchQueryAutocompleteResult(
blink::WebLocalFrame* frame, blink::WebLocalFrame* frame,
const std::vector<chrome::mojom::AutocompleteMatchPtr>& matches) { chrome::mojom::AutocompleteResultPtr result) {
base::Value dict(base::Value::Type::DICTIONARY);
dict.SetStringKey("input", result->input);
dict.SetDoubleKey("status", static_cast<double>(result->status));
base::Value list(base::Value::Type::LIST); base::Value list(base::Value::Type::LIST);
for (const chrome::mojom::AutocompleteMatchPtr& match : matches) { for (const chrome::mojom::AutocompleteMatchPtr& match : result->matches) {
base::Value dict(base::Value::Type::DICTIONARY); base::Value dict(base::Value::Type::DICTIONARY);
dict.SetBoolKey("allowedToBeDefaultMatch", dict.SetBoolKey("allowedToBeDefaultMatch",
match->allowed_to_be_default_match); match->allowed_to_be_default_match);
...@@ -1378,8 +1382,10 @@ void SearchBoxExtension::DispatchQueryAutocompleteResult( ...@@ -1378,8 +1382,10 @@ void SearchBoxExtension::DispatchQueryAutocompleteResult(
dict.SetStringKey("type", match->type); dict.SetStringKey("type", match->type);
list.Append(std::move(dict)); list.Append(std::move(dict));
} }
dict.SetKey("matches", std::move(list));
std::string json; std::string json;
base::JSONWriter::Write(list, &json); base::JSONWriter::Write(dict, &json);
Dispatch(frame, blink::WebString::FromUTF8(base::StringPrintf( Dispatch(frame, blink::WebString::FromUTF8(base::StringPrintf(
kDispatchQueryAutocompleteResult, json.c_str()))); kDispatchQueryAutocompleteResult, json.c_str())));
} }
......
...@@ -39,7 +39,7 @@ class SearchBoxExtension { ...@@ -39,7 +39,7 @@ class SearchBoxExtension {
bool success); bool success);
static void DispatchQueryAutocompleteResult( static void DispatchQueryAutocompleteResult(
blink::WebLocalFrame* frame, blink::WebLocalFrame* frame,
const std::vector<chrome::mojom::AutocompleteMatchPtr>& matches); chrome::mojom::AutocompleteResultPtr result);
static void DispatchInputCancel(blink::WebLocalFrame* frame); static void DispatchInputCancel(blink::WebLocalFrame* frame);
static void DispatchInputStart(blink::WebLocalFrame* frame); static void DispatchInputStart(blink::WebLocalFrame* frame);
static void DispatchKeyCaptureChange(blink::WebLocalFrame* frame); static void DispatchKeyCaptureChange(blink::WebLocalFrame* frame);
......
...@@ -22,6 +22,12 @@ test.realbox.CLASSES = { ...@@ -22,6 +22,12 @@ test.realbox.CLASSES = {
SHOW_MATCHES: 'show-matches', SHOW_MATCHES: 'show-matches',
}; };
/** @enum {number} */
test.realbox.AutocompleteResultStatus = {
SUCCESS: 0,
SKIPPED: 1,
};
/** /**
* @param {string} name * @param {string} name
* @param {!ClipboardEvent} * @param {!ClipboardEvent}
...@@ -138,7 +144,8 @@ test.realbox.testReplyWithMatches = function() { ...@@ -138,7 +144,8 @@ test.realbox.testReplyWithMatches = function() {
assertEquals('hello world', test.realbox.queries[0]); assertEquals('hello world', test.realbox.queries[0]);
const matches = [test.realbox.getSearchMatch(), test.realbox.getUrlMatch()]; const matches = [test.realbox.getSearchMatch(), test.realbox.getUrlMatch()];
chrome.embeddedSearch.searchBox.onqueryautocompletedone(matches); chrome.embeddedSearch.searchBox.onqueryautocompletedone(
{input: test.realbox.realboxEl.value, matches});
assertTrue(test.realbox.wrapperEl.classList.contains( assertTrue(test.realbox.wrapperEl.classList.contains(
test.realbox.CLASSES.SHOW_MATCHES)); test.realbox.CLASSES.SHOW_MATCHES));
...@@ -161,7 +168,10 @@ test.realbox.testReplyWithInlineAutocompletion = function() { ...@@ -161,7 +168,10 @@ test.realbox.testReplyWithInlineAutocompletion = function() {
contents: 'hello ', contents: 'hello ',
inlineAutocompletion: 'world', inlineAutocompletion: 'world',
}); });
chrome.embeddedSearch.searchBox.onqueryautocompletedone([match]); chrome.embeddedSearch.searchBox.onqueryautocompletedone({
input: test.realbox.realboxEl.value,
matches: [match],
});
assertTrue(test.realbox.wrapperEl.classList.contains( assertTrue(test.realbox.wrapperEl.classList.contains(
test.realbox.CLASSES.SHOW_MATCHES)); test.realbox.CLASSES.SHOW_MATCHES));
...@@ -188,7 +198,10 @@ test.realbox.testDeleteWithInlineAutocompletion = function() { ...@@ -188,7 +198,10 @@ test.realbox.testDeleteWithInlineAutocompletion = function() {
contents: 'supercal', contents: 'supercal',
inlineAutocompletion: 'ifragilisticexpialidocious', inlineAutocompletion: 'ifragilisticexpialidocious',
}); });
chrome.embeddedSearch.searchBox.onqueryautocompletedone([match]); chrome.embeddedSearch.searchBox.onqueryautocompletedone({
input: test.realbox.realboxEl.value,
matches: [match],
});
const realboxValue = test.realbox.realboxEl.value; const realboxValue = test.realbox.realboxEl.value;
assertEquals('supercalifragilisticexpialidocious', realboxValue); assertEquals('supercalifragilisticexpialidocious', realboxValue);
...@@ -198,7 +211,10 @@ test.realbox.testDeleteWithInlineAutocompletion = function() { ...@@ -198,7 +211,10 @@ test.realbox.testDeleteWithInlineAutocompletion = function() {
match.contents = 'superca'; match.contents = 'superca';
match.inlineAutocompletion = 'lifragilisticexpialidocious'; match.inlineAutocompletion = 'lifragilisticexpialidocious';
chrome.embeddedSearch.searchBox.onqueryautocompletedone([match]); chrome.embeddedSearch.searchBox.onqueryautocompletedone({
input: test.realbox.realboxEl.value,
matches: [match],
});
// Ensure that removing characters from input doesn't just inline autocomplete // Ensure that removing characters from input doesn't just inline autocomplete
// to the same contents on backspace/cut/etc. // to the same contents on backspace/cut/etc.
...@@ -209,7 +225,10 @@ test.realbox.testDeleteWithInlineAutocompletion = function() { ...@@ -209,7 +225,10 @@ test.realbox.testDeleteWithInlineAutocompletion = function() {
match.contents = 'super'; match.contents = 'super';
match.inlineAutocompletion = 'califragilisticexpialidocious'; match.inlineAutocompletion = 'califragilisticexpialidocious';
chrome.embeddedSearch.searchBox.onqueryautocompletedone([match]); chrome.embeddedSearch.searchBox.onqueryautocompletedone({
input: test.realbox.realboxEl.value,
matches: [match],
});
assertEquals('super', test.realbox.realboxEl.value); assertEquals('super', test.realbox.realboxEl.value);
}; };
...@@ -217,11 +236,15 @@ test.realbox.testTypeInlineAutocompletion = function() { ...@@ -217,11 +236,15 @@ test.realbox.testTypeInlineAutocompletion = function() {
test.realbox.realboxEl.value = 'what are the'; test.realbox.realboxEl.value = 'what are the';
test.realbox.realboxEl.dispatchEvent(new CustomEvent('input')); test.realbox.realboxEl.dispatchEvent(new CustomEvent('input'));
chrome.embeddedSearch.searchBox.onqueryautocompletedone( chrome.embeddedSearch.searchBox.onqueryautocompletedone({
[test.realbox.getSearchMatch({ input: test.realbox.realboxEl.value,
matches: [
test.realbox.getSearchMatch({
contents: 'what are the', contents: 'what are the',
inlineAutocompletion: 'se strawberries', inlineAutocompletion: 'se strawberries',
})]); }),
],
});
assertEquals('what are these strawberries', test.realbox.realboxEl.value); assertEquals('what are these strawberries', test.realbox.realboxEl.value);
assertEquals('what are the'.length, test.realbox.realboxEl.selectionStart); assertEquals('what are the'.length, test.realbox.realboxEl.selectionStart);
...@@ -241,19 +264,22 @@ test.realbox.testTypeInlineAutocompletion = function() { ...@@ -241,19 +264,22 @@ test.realbox.testTypeInlineAutocompletion = function() {
} }
}); });
// test.realbox.realboxEl.value = 'what are the';
// Pretend the user typed the next character of the inline autocompletion. // Pretend the user typed the next character of the inline autocompletion.
const keyEvent = const keyEvent =
new KeyboardEvent('keydown', {bubbles: true, cancelable: true, key: 's'}); new KeyboardEvent('keydown', {bubbles: true, cancelable: true, key: 's'});
test.realbox.realboxEl.dispatchEvent(keyEvent); test.realbox.realboxEl.dispatchEvent(keyEvent);
assertTrue(keyEvent.defaultPrevented); assertTrue(keyEvent.defaultPrevented);
chrome.embeddedSearch.searchBox.onqueryautocompletedone( chrome.embeddedSearch.searchBox.onqueryautocompletedone({
[test.realbox.getSearchMatch({ input: test.realbox.realboxEl.value,
matches: [
test.realbox.getSearchMatch({
contents: 'what are thes', contents: 'what are thes',
inlineAutocompletion: 'e strawberries', inlineAutocompletion: 'e strawberries',
})]); }),
],
});
assertEquals('what are these strawberries', test.realbox.realboxEl.value); assertEquals('what are these strawberries', test.realbox.realboxEl.value);
assertEquals('what are thes'.length, test.realbox.realboxEl.selectionStart); assertEquals('what are thes'.length, test.realbox.realboxEl.selectionStart);
...@@ -267,16 +293,20 @@ test.realbox.testResultsPreserveCursorPosition = function() { ...@@ -267,16 +293,20 @@ test.realbox.testResultsPreserveCursorPosition = function() {
test.realbox.realboxEl.value = 'z'; test.realbox.realboxEl.value = 'z';
test.realbox.realboxEl.dispatchEvent(new CustomEvent('input')); test.realbox.realboxEl.dispatchEvent(new CustomEvent('input'));
chrome.embeddedSearch.searchBox.onqueryautocompletedone( chrome.embeddedSearch.searchBox.onqueryautocompletedone({
[test.realbox.getSearchMatch({contents: 'z'})]); input: test.realbox.realboxEl.value,
matches: [test.realbox.getSearchMatch({contents: 'z'})],
});
test.realbox.realboxEl.value = 'az'; test.realbox.realboxEl.value = 'az';
test.realbox.realboxEl.selectionStart = 1; test.realbox.realboxEl.selectionStart = 1;
test.realbox.realboxEl.selectionEnd = 1; test.realbox.realboxEl.selectionEnd = 1;
test.realbox.realboxEl.dispatchEvent(new CustomEvent('input')); test.realbox.realboxEl.dispatchEvent(new CustomEvent('input'));
chrome.embeddedSearch.searchBox.onqueryautocompletedone( chrome.embeddedSearch.searchBox.onqueryautocompletedone({
[test.realbox.getSearchMatch({contents: 'az'})]); input: test.realbox.realboxEl.value,
matches: [test.realbox.getSearchMatch({contents: 'az'})],
});
assertEquals(1, test.realbox.realboxEl.selectionStart); assertEquals(1, test.realbox.realboxEl.selectionStart);
assertEquals(1, test.realbox.realboxEl.selectionEnd); assertEquals(1, test.realbox.realboxEl.selectionEnd);
...@@ -292,8 +322,10 @@ test.realbox.testCopySearchResultFails = function() { ...@@ -292,8 +322,10 @@ test.realbox.testCopySearchResultFails = function() {
test.realbox.realboxEl.value = 'skittles!'; test.realbox.realboxEl.value = 'skittles!';
test.realbox.realboxEl.dispatchEvent(new CustomEvent('input')); test.realbox.realboxEl.dispatchEvent(new CustomEvent('input'));
chrome.embeddedSearch.searchBox.onqueryautocompletedone( chrome.embeddedSearch.searchBox.onqueryautocompletedone({
[test.realbox.getSearchMatch({contents: 'skittles!'})]); input: test.realbox.realboxEl.value,
matches: [test.realbox.getSearchMatch({contents: 'skittles!'})],
});
test.realbox.realboxEl.setSelectionRange(0, 'skittles!'.length); test.realbox.realboxEl.setSelectionRange(0, 'skittles!'.length);
...@@ -306,12 +338,14 @@ test.realbox.testCopyUrlSucceeds = function() { ...@@ -306,12 +338,14 @@ test.realbox.testCopyUrlSucceeds = function() {
test.realbox.realboxEl.value = 'go'; test.realbox.realboxEl.value = 'go';
test.realbox.realboxEl.dispatchEvent(new CustomEvent('input')); test.realbox.realboxEl.dispatchEvent(new CustomEvent('input'));
chrome.embeddedSearch.searchBox.onqueryautocompletedone( chrome.embeddedSearch.searchBox.onqueryautocompletedone({
[test.realbox.getUrlMatch({ input: test.realbox.realboxEl.value,
contents: 'go', matches: [test.realbox.getUrlMatch({
inlineAutocompletion: 'ogle.com', contents: 'go',
destinationUrl: 'https://www.google.com/', inlineAutocompletion: 'ogle.com',
})]); destinationUrl: 'https://www.google.com/',
})]
});
assertEquals('google.com', test.realbox.realboxEl.value); assertEquals('google.com', test.realbox.realboxEl.value);
...@@ -323,3 +357,61 @@ test.realbox.testCopyUrlSucceeds = function() { ...@@ -323,3 +357,61 @@ test.realbox.testCopyUrlSucceeds = function() {
assertEquals( assertEquals(
'https://www.google.com/', copyEvent.clipboardData.getData('text/plain')); 'https://www.google.com/', copyEvent.clipboardData.getData('text/plain'));
}; };
test.realbox.testStaleAutocompleteResult = function() {
assertEquals('', test.realbox.realboxEl.value);
chrome.embeddedSearch.searchBox.onqueryautocompletedone({
input: test.realbox.realboxEl.value + 'a', // Simulate stale response.
matches: [test.realbox.getSearchMatch(), test.realbox.getUrlMatch()],
});
};
test.realbox.testAutocompleteResultStatus = function() {
test.realbox.realboxEl.value = 'g';
test.realbox.realboxEl.dispatchEvent(new CustomEvent('input'));
test.realbox.realboxEl.value += 'o';
test.realbox.realboxEl.dispatchEvent(new CustomEvent('input'));
chrome.embeddedSearch.searchBox.onqueryautocompletedone({
input: test.realbox.realboxEl.value,
matches: [],
status: test.realbox.AutocompleteResultStatus.SKIPPED,
});
assertEquals(0, $(test.realbox.IDS.REALBOX_MATCHES).children.length);
let RESULTS = [test.realbox.getSearchMatch(), test.realbox.getUrlMatch()];
chrome.embeddedSearch.searchBox.onqueryautocompletedone({
input: test.realbox.realboxEl.value,
matches: RESULTS,
status: test.realbox.AutocompleteResultStatus.SUCCESS,
});
const matchesEl = $(test.realbox.IDS.REALBOX_MATCHES);
assertEquals(RESULTS.length, matchesEl.children.length);
test.realbox.realboxEl.value += 'o';
test.realbox.realboxEl.dispatchEvent(new CustomEvent('input'));
test.realbox.realboxEl.value += 'g';
test.realbox.realboxEl.dispatchEvent(new CustomEvent('input'));
chrome.embeddedSearch.searchBox.onqueryautocompletedone({
input: test.realbox.realboxEl.value,
matches: [],
status: test.realbox.AutocompleteResultStatus.SKIPPED,
});
// Checks to see that the matches UI wasn't re-rendered.
assertTrue(matchesEl === $(test.realbox.IDS.REALBOX_MATCHES));
chrome.embeddedSearch.searchBox.onqueryautocompletedone({
input: test.realbox.realboxEl.value,
matches: RESULTS,
status: test.realbox.AutocompleteResultStatus.SUCCESS,
});
const newMatchesEl = $(test.realbox.IDS.REALBOX_MATCHES);
assertEquals(RESULTS.length, newMatchesEl.children.length);
assertFalse(matchesEl === newMatchesEl);
};
...@@ -136,6 +136,7 @@ class AutocompleteController : public AutocompleteProviderListener, ...@@ -136,6 +136,7 @@ class AutocompleteController : public AutocompleteProviderListener,
KeywordProvider* keyword_provider() const { return keyword_provider_; } KeywordProvider* keyword_provider() const { return keyword_provider_; }
SearchProvider* search_provider() const { return search_provider_; } SearchProvider* search_provider() const { return search_provider_; }
const AutocompleteInput& input() const { return input_; }
const AutocompleteResult& result() const { return result_; } const AutocompleteResult& result() const { return result_; }
bool done() const { return done_; } bool done() const { return done_; }
const Providers& providers() const { return providers_; } const Providers& providers() const { return providers_; }
......
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