Commit f5946a0a authored by Dan Beam's avatar Dan Beam Committed by Commit Bot

Local NTP, Realbox: fix navigation to chrome:// URLs

Partially borrowed from https://crrev.com/c/1836021 by Archana Simha <archanasihma@chromium.org>

Bug: 1020025
Change-Id: I7de22a41d872ec624f25d1633782d27448988c58
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1893824
Commit-Queue: David Bokan <bokan@chromium.org>
Reviewed-by: default avatarDavid Bokan <bokan@chromium.org>
Reviewed-by: default avatarMoe Ahmadi <mahmadi@chromium.org>
Reviewed-by: default avatarNasko Oskov <nasko@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Auto-Submit: Dan Beam <dbeam@chromium.org>
Cr-Commit-Position: refs/heads/master@{#715106}
parent 851088cf
...@@ -417,6 +417,7 @@ let ACMatchClassification; ...@@ -417,6 +417,7 @@ let ACMatchClassification;
/** /**
* @typedef {{ * @typedef {{
* allowedToBeDefaultMatch: boolean, * allowedToBeDefaultMatch: boolean,
* canDisplay: boolean,
* contents: string, * contents: string,
* contentsClass: !Array<!ACMatchClassification>, * contentsClass: !Array<!ACMatchClassification>,
* description: string, * description: string,
...@@ -443,6 +444,17 @@ let AutocompleteResult; ...@@ -443,6 +444,17 @@ let AutocompleteResult;
/** @type {function(!AutocompleteResult):void} */ /** @type {function(!AutocompleteResult):void} */
window.chrome.embeddedSearch.searchBox.autocompleteresultchanged; window.chrome.embeddedSearch.searchBox.autocompleteresultchanged;
/**
* @param {number} line
* @param {string} url
* @param {number} button
* @param {boolean} altKey
* @param {boolean} ctrlKey
* @param {boolean} metaKey
* @param {boolean} shiftKey
*/
window.chrome.embeddedSearch.searchBox.openAutocompleteMatch;
/**************************** Translated Strings *****************************/ /**************************** Translated Strings *****************************/
/** /**
......
...@@ -458,6 +458,23 @@ function disableIframesAndVoiceSearchForTesting() { ...@@ -458,6 +458,23 @@ function disableIframesAndVoiceSearchForTesting() {
iframesAndVoiceSearchDisabledForTesting = true; iframesAndVoiceSearchDisabledForTesting = true;
} }
/**
* TODO(dbeam): reconcile this with //ui/webui/resources/js/util.js.
* @param {?Node} node The node to check.
* @param {function(?Node):boolean} predicate The function that tests the
* nodes.
* @return {?Node} The found ancestor or null if not found.
*/
function findAncestor(node, predicate) {
while (node !== null) {
if (predicate(node)) {
break;
}
node = node.parentNode;
}
return node;
}
/** /**
* Animates the pop-up notification to float down, and clears the timeout to * Animates the pop-up notification to float down, and clears the timeout to
* hide the notification. * hide the notification.
...@@ -1050,6 +1067,24 @@ function listen() { ...@@ -1050,6 +1067,24 @@ function listen() {
document.addEventListener('DOMContentLoaded', init); document.addEventListener('DOMContentLoaded', init);
} }
/**
* @param {!AutocompleteMatch} match
* @param {!Event} e
*/
function navigateToMatch(match, e) {
const line = autocompleteMatches.indexOf(match);
assert(line >= 0);
if (match.canDisplay) {
const matchEl = $(IDS.REALBOX_MATCHES).children[line];
matchEl.dispatchEvent(new MouseEvent('click', e));
} else {
window.chrome.embeddedSearch.searchBox.openAutocompleteMatch(
line, match.destinationUrl, e.button || 0, e.altKey, e.ctrlKey,
e.metaKey, e.shiftKey);
}
e.preventDefault();
}
/** /**
* Callback for embeddedSearch.newTabPage.onaddcustomlinkdone. Called when the * Callback for embeddedSearch.newTabPage.onaddcustomlinkdone. Called when the
* custom link was successfully added. Shows the "Shortcut added" notification. * custom link was successfully added. Shows the "Shortcut added" notification.
...@@ -1174,14 +1209,12 @@ function onRealboxWrapperFocusIn(e) { ...@@ -1174,14 +1209,12 @@ function onRealboxWrapperFocusIn(e) {
if (e.target.matches(`#${IDS.REALBOX}`) && !$(IDS.REALBOX).value) { if (e.target.matches(`#${IDS.REALBOX}`) && !$(IDS.REALBOX).value) {
queryAutocomplete(''); queryAutocomplete('');
} else if (e.target.matches(`#${IDS.REALBOX_MATCHES} *`)) { } else if (e.target.matches(`#${IDS.REALBOX_MATCHES} *`)) {
let target = e.target; const target = /** @type {Element} */ (e.target);
while (target && target.nodeName !== 'A') { const link = findAncestor(target, el => el.nodeName === 'A');
target = target.parentNode; if (!link) {
}
if (!target) {
return; return;
} }
const selectedIndex = selectMatchEl(target); const selectedIndex = selectMatchEl(link);
// It doesn't really make sense to use fillFromMatch() here as the focus // It doesn't really make sense to use fillFromMatch() here as the focus
// change drops the selection (and is probably just noisy to // change drops the selection (and is probably just noisy to
// screenreaders). // screenreaders).
...@@ -1202,9 +1235,16 @@ function onRealboxWrapperFocusOut(e) { ...@@ -1202,9 +1235,16 @@ function onRealboxWrapperFocusOut(e) {
updateRealboxOutput({inline: '', text: ''}); updateRealboxOutput({inline: '', text: ''});
} }
setRealboxMatchesVisible(false); setRealboxMatchesVisible(false);
// Note: intentionally leaving keydown listening (see
// onRealboxWrapperKeydown) intact. // Stop autocomplete but leave (potentially stale) results and continue
clearAutocompleteMatches(); // 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.
window.chrome.embeddedSearch.searchBox.stopAutocomplete(
/*clearResult=*/ false);
} }
} }
...@@ -1245,14 +1285,11 @@ function onRealboxWrapperKeydown(e) { ...@@ -1245,14 +1285,11 @@ function onRealboxWrapperKeydown(e) {
return matchEl.classList.contains(CLASSES.SELECTED); return matchEl.classList.contains(CLASSES.SELECTED);
}); });
// Enter should work whether or not matches are visible. assert(autocompleteMatches.length === matchEls.length);
if (key === 'Enter') { if (key === 'Enter') {
if (matchEls[selected] && matchEls.concat(realboxEl).includes(e.target)) { if (matchEls[selected] && matchEls.concat(realboxEl).includes(e.target)) {
// Note: dispatching a MouseEvent here instead of using e.g. .click() as navigateToMatch(autocompleteMatches[selected], e);
// this forwards key modifiers. This enables Shift+Enter to open a match
// in a new window, for example.
matchEls[selected].dispatchEvent(new MouseEvent('click', e));
e.preventDefault();
} }
return; return;
} }
...@@ -1268,9 +1305,6 @@ function onRealboxWrapperKeydown(e) { ...@@ -1268,9 +1305,6 @@ function onRealboxWrapperKeydown(e) {
return; return;
} }
// If the matches are visible, the autocomplete results must also be intact.
assert(autocompleteMatches.length === matchEls.length);
if (key === 'Delete') { if (key === 'Delete') {
if (e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey) { if (e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey) {
const selectedMatch = autocompleteMatches[selected]; const selectedMatch = autocompleteMatches[selected];
...@@ -1406,6 +1440,20 @@ function populateAutocompleteMatches(matches) { ...@@ -1406,6 +1440,20 @@ function populateAutocompleteMatches(matches) {
matchEl.href = match.destinationUrl; matchEl.href = match.destinationUrl;
matchEl.setAttribute('role', 'option'); matchEl.setAttribute('role', 'option');
matchEl.onclick = matchEl.onauxclick = e => {
if (!e.isTrusted || e.defaultPrevented || e.button > 1) {
// Don't re-handle events dispatched from navigateToMatch(). Ignore
// already handled events (i.e. remove button, defaultPrevented). Ignore
// right clicks (but do handle middle click, button == 1).
return;
}
const target = /** @type {Element} */ (e.target);
const link = findAncestor(target, el => el.nodeName === 'A');
if (link === matchEl) {
navigateToMatch(match, e);
}
};
if (match.isSearchType) { if (match.isSearchType) {
const icon = document.createElement('div'); const icon = document.createElement('div');
const isSearchHistory = SEARCH_HISTORY_MATCH_TYPES.includes(match.type); const isSearchHistory = SEARCH_HISTORY_MATCH_TYPES.includes(match.type);
...@@ -1454,6 +1502,12 @@ function populateAutocompleteMatches(matches) { ...@@ -1454,6 +1502,12 @@ function populateAutocompleteMatches(matches) {
icon.onmousedown = e => { icon.onmousedown = e => {
e.preventDefault(); // Stops default browser action (focus) 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 => { icon.onclick = e => {
window.chrome.embeddedSearch.searchBox.deleteAutocompleteMatch(i); window.chrome.embeddedSearch.searchBox.deleteAutocompleteMatch(i);
e.preventDefault(); // Stops default browser action (navigation) e.preventDefault(); // Stops default browser action (navigation)
......
...@@ -479,6 +479,21 @@ void SearchIPCRouter::BlocklistPromo(const std::string& promo_id) { ...@@ -479,6 +479,21 @@ void SearchIPCRouter::BlocklistPromo(const std::string& promo_id) {
delegate_->BlocklistPromo(promo_id); delegate_->BlocklistPromo(promo_id);
} }
void SearchIPCRouter::OpenAutocompleteMatch(uint8_t line,
const GURL& url,
double button,
bool alt_key,
bool ctrl_key,
bool meta_key,
bool shift_key) {
if (!policy_->ShouldProcessOpenAutocompleteMatch(is_active_tab_)) {
return;
}
delegate_->OpenAutocompleteMatch(line, url, button, alt_key, ctrl_key,
meta_key, shift_key);
}
void SearchIPCRouter::DeleteAutocompleteMatch(uint8_t line) { void SearchIPCRouter::DeleteAutocompleteMatch(uint8_t line) {
if (!policy_->ShouldProcessDeleteAutocompleteMatch()) { if (!policy_->ShouldProcessDeleteAutocompleteMatch()) {
return; return;
......
...@@ -163,6 +163,14 @@ class SearchIPCRouter : public content::WebContentsObserver, ...@@ -163,6 +163,14 @@ class SearchIPCRouter : public content::WebContentsObserver,
virtual void BlocklistPromo(const std::string& promo_id) = 0; virtual void BlocklistPromo(const std::string& promo_id) = 0;
virtual void OpenAutocompleteMatch(uint8_t line,
const GURL& url,
double button,
bool alt_key,
bool ctrl_key,
bool meta_key,
bool shift_key) = 0;
virtual void DeleteAutocompleteMatch(uint8_t line) = 0; virtual void DeleteAutocompleteMatch(uint8_t line) = 0;
}; };
...@@ -206,6 +214,7 @@ class SearchIPCRouter : public content::WebContentsObserver, ...@@ -206,6 +214,7 @@ class SearchIPCRouter : public content::WebContentsObserver,
virtual bool ShouldProcessQueryAutocomplete(bool is_active_tab) = 0; virtual bool ShouldProcessQueryAutocomplete(bool is_active_tab) = 0;
virtual bool ShouldProcessStopAutocomplete() = 0; virtual bool ShouldProcessStopAutocomplete() = 0;
virtual bool ShouldProcessBlocklistPromo() = 0; virtual bool ShouldProcessBlocklistPromo() = 0;
virtual bool ShouldProcessOpenAutocompleteMatch(bool is_active_tab) = 0;
virtual bool ShouldProcessDeleteAutocompleteMatch() = 0; virtual bool ShouldProcessDeleteAutocompleteMatch() = 0;
}; };
...@@ -319,6 +328,13 @@ class SearchIPCRouter : public content::WebContentsObserver, ...@@ -319,6 +328,13 @@ class SearchIPCRouter : public content::WebContentsObserver,
bool prevent_inline_autocomplete) override; bool prevent_inline_autocomplete) override;
void StopAutocomplete(bool clear_result) override; void StopAutocomplete(bool clear_result) override;
void BlocklistPromo(const std::string& promo_id) override; void BlocklistPromo(const std::string& promo_id) override;
void OpenAutocompleteMatch(uint8_t line,
const GURL& url,
double button,
bool alt_key,
bool ctrl_key,
bool meta_key,
bool shift_key) override;
void DeleteAutocompleteMatch(uint8_t line) override; void DeleteAutocompleteMatch(uint8_t line) override;
void set_embedded_search_client_factory_for_testing( void set_embedded_search_client_factory_for_testing(
std::unique_ptr<EmbeddedSearchClientFactory> factory) { std::unique_ptr<EmbeddedSearchClientFactory> factory) {
......
...@@ -150,6 +150,11 @@ bool SearchIPCRouterPolicyImpl::ShouldProcessBlocklistPromo() { ...@@ -150,6 +150,11 @@ bool SearchIPCRouterPolicyImpl::ShouldProcessBlocklistPromo() {
return !is_incognito_ && search::IsInstantNTP(web_contents_); return !is_incognito_ && search::IsInstantNTP(web_contents_);
} }
bool SearchIPCRouterPolicyImpl::ShouldProcessOpenAutocompleteMatch(
bool is_active_tab) {
return is_active_tab && !is_incognito_ && search::IsInstantNTP(web_contents_);
}
bool SearchIPCRouterPolicyImpl::ShouldProcessDeleteAutocompleteMatch() { bool SearchIPCRouterPolicyImpl::ShouldProcessDeleteAutocompleteMatch() {
return !is_incognito_ && search::IsInstantNTP(web_contents_); return !is_incognito_ && search::IsInstantNTP(web_contents_);
} }
...@@ -58,6 +58,7 @@ class SearchIPCRouterPolicyImpl : public SearchIPCRouter::Policy { ...@@ -58,6 +58,7 @@ class SearchIPCRouterPolicyImpl : public SearchIPCRouter::Policy {
bool ShouldProcessQueryAutocomplete(bool is_active_tab) override; bool ShouldProcessQueryAutocomplete(bool is_active_tab) override;
bool ShouldProcessStopAutocomplete() override; bool ShouldProcessStopAutocomplete() override;
bool ShouldProcessBlocklistPromo() override; bool ShouldProcessBlocklistPromo() override;
bool ShouldProcessOpenAutocompleteMatch(bool is_active_tab) override;
bool ShouldProcessDeleteAutocompleteMatch() override; bool ShouldProcessDeleteAutocompleteMatch() override;
// Used by unit tests. // Used by unit tests.
......
...@@ -109,6 +109,14 @@ class MockSearchIPCRouterDelegate : public SearchIPCRouter::Delegate { ...@@ -109,6 +109,14 @@ class MockSearchIPCRouterDelegate : public SearchIPCRouter::Delegate {
bool prevent_inline_autocomplete)); bool prevent_inline_autocomplete));
MOCK_METHOD1(StopAutocomplete, void(bool clear_result)); MOCK_METHOD1(StopAutocomplete, void(bool clear_result));
MOCK_METHOD1(BlocklistPromo, void(const std::string& promo_id)); MOCK_METHOD1(BlocklistPromo, void(const std::string& promo_id));
MOCK_METHOD7(OpenAutocompleteMatch,
void(uint8_t line,
const GURL& url,
double button,
bool alt_key,
bool ctrl_key,
bool meta_key,
bool shift_key));
}; };
class MockSearchIPCRouterPolicy : public SearchIPCRouter::Policy { class MockSearchIPCRouterPolicy : public SearchIPCRouter::Policy {
...@@ -147,6 +155,7 @@ class MockSearchIPCRouterPolicy : public SearchIPCRouter::Policy { ...@@ -147,6 +155,7 @@ class MockSearchIPCRouterPolicy : public SearchIPCRouter::Policy {
MOCK_METHOD1(ShouldProcessQueryAutocomplete, bool(bool)); MOCK_METHOD1(ShouldProcessQueryAutocomplete, bool(bool));
MOCK_METHOD0(ShouldProcessStopAutocomplete, bool()); MOCK_METHOD0(ShouldProcessStopAutocomplete, bool());
MOCK_METHOD0(ShouldProcessBlocklistPromo, bool()); MOCK_METHOD0(ShouldProcessBlocklistPromo, bool());
MOCK_METHOD1(ShouldProcessOpenAutocompleteMatch, bool(bool));
MOCK_METHOD0(ShouldProcessDeleteAutocompleteMatch, bool()); MOCK_METHOD0(ShouldProcessDeleteAutocompleteMatch, bool());
}; };
...@@ -1127,6 +1136,20 @@ TEST_F(SearchIPCRouterTest, IgnoreBlocklistPromo) { ...@@ -1127,6 +1136,20 @@ TEST_F(SearchIPCRouterTest, IgnoreBlocklistPromo) {
GetSearchIPCRouter().BlocklistPromo(std::string()); GetSearchIPCRouter().BlocklistPromo(std::string());
} }
TEST_F(SearchIPCRouterTest, IgnoreOpenAutocompleteMatch) {
NavigateAndCommitActiveTab(GURL("chrome-search://foo/bar"));
SetupMockDelegateAndPolicy();
MockSearchIPCRouterPolicy* policy = GetSearchIPCRouterPolicy();
EXPECT_CALL(*mock_delegate(), OpenAutocompleteMatch(_, _, _, _, _, _, _))
.Times(0);
EXPECT_CALL(*policy, ShouldProcessOpenAutocompleteMatch(_))
.Times(1)
.WillOnce(Return(false));
GetSearchIPCRouter().OpenAutocompleteMatch(0, GURL(), 0, false, false, false,
false);
}
TEST_F(SearchIPCRouterTest, IgnoreDeleteAutocompleteMatch) { TEST_F(SearchIPCRouterTest, IgnoreDeleteAutocompleteMatch) {
NavigateAndCommitActiveTab(GURL("chrome-search://foo/bar")); NavigateAndCommitActiveTab(GURL("chrome-search://foo/bar"));
SetupMockDelegateAndPolicy(); SetupMockDelegateAndPolicy();
......
...@@ -732,6 +732,35 @@ void SearchTabHelper::BlocklistPromo(const std::string& promo_id) { ...@@ -732,6 +732,35 @@ void SearchTabHelper::BlocklistPromo(const std::string& promo_id) {
promo_service->BlocklistPromo(promo_id); promo_service->BlocklistPromo(promo_id);
} }
void SearchTabHelper::OpenAutocompleteMatch(uint8_t line,
const GURL& url,
double button,
bool alt_key,
bool ctrl_key,
bool meta_key,
bool shift_key) {
DCHECK(autocomplete_controller_);
if (!search::DefaultSearchProviderIsGoogle(profile()) ||
!autocomplete_controller_ ||
line >= autocomplete_controller_->result().size()) {
return;
}
const auto& match = autocomplete_controller_->result().match_at(line);
if (match.destination_url != url) {
// TODO(https://crbug.com/1020025): this could be malice or staleness.
// Either way: don't navigate.
return;
}
WindowOpenDisposition disposition = ui::DispositionFromClick(
button == 1.0, alt_key, ctrl_key, meta_key, shift_key);
web_contents_->OpenURL(
content::OpenURLParams(match.destination_url, content::Referrer(),
disposition, ui::PAGE_TRANSITION_LINK, false));
}
OmniboxView* SearchTabHelper::GetOmniboxView() { OmniboxView* SearchTabHelper::GetOmniboxView() {
return const_cast<OmniboxView*>( return const_cast<OmniboxView*>(
const_cast<const SearchTabHelper*>(this)->GetOmniboxView()); const_cast<const SearchTabHelper*>(this)->GetOmniboxView());
......
...@@ -143,6 +143,13 @@ class SearchTabHelper : public content::WebContentsObserver, ...@@ -143,6 +143,13 @@ class SearchTabHelper : public content::WebContentsObserver,
void DeleteAutocompleteMatch(uint8_t line) override; void DeleteAutocompleteMatch(uint8_t line) override;
void StopAutocomplete(bool clear_result) override; void StopAutocomplete(bool clear_result) override;
void BlocklistPromo(const std::string& promo_id) override; void BlocklistPromo(const std::string& promo_id) override;
void OpenAutocompleteMatch(uint8_t line,
const GURL& url,
double button,
bool alt_key,
bool ctrl_key,
bool meta_key,
bool shift_key) override;
// Overridden from InstantServiceObserver: // Overridden from InstantServiceObserver:
void NtpThemeChanged(const NtpTheme& theme) override; void NtpThemeChanged(const NtpTheme& theme) override;
......
...@@ -183,6 +183,16 @@ interface EmbeddedSearch { ...@@ -183,6 +183,16 @@ interface EmbeddedSearch {
// Called when a user dismisses a promo. // Called when a user dismisses a promo.
BlocklistPromo(string promo_id); BlocklistPromo(string promo_id);
// Handles navigation to a link that points to a chrome: url by calling the
// browser to do the navigation.
OpenAutocompleteMatch(uint8 line,
url.mojom.Url url,
double button,
bool alt_key,
bool ctrl_key,
bool meta_key,
bool shift_key);
}; };
[Native] [Native]
......
...@@ -457,6 +457,17 @@ void SearchBox::BlocklistPromo(const std::string& promo_id) { ...@@ -457,6 +457,17 @@ void SearchBox::BlocklistPromo(const std::string& promo_id) {
embedded_search_service_->BlocklistPromo(promo_id); embedded_search_service_->BlocklistPromo(promo_id);
} }
void SearchBox::OpenAutocompleteMatch(uint8_t line,
const GURL& url,
double button,
bool alt_key,
bool ctrl_key,
bool meta_key,
bool shift_key) {
embedded_search_service_->OpenAutocompleteMatch(
line, url, button, alt_key, ctrl_key, meta_key, shift_key);
}
void SearchBox::SetPageSequenceNumber(int page_seq_no) { void SearchBox::SetPageSequenceNumber(int page_seq_no) {
page_seq_no_ = page_seq_no; page_seq_no_ = page_seq_no;
} }
......
...@@ -206,6 +206,16 @@ class SearchBox : public content::RenderFrameObserver, ...@@ -206,6 +206,16 @@ class SearchBox : public content::RenderFrameObserver,
// Called when a user dismisses a promo. // Called when a user dismisses a promo.
void BlocklistPromo(const std::string& promo_id); void BlocklistPromo(const std::string& promo_id);
// Handles navigation to privileged (i.e. chrome://) URLs by calling the
// browser to do the navigation.
void OpenAutocompleteMatch(uint8_t line,
const GURL& url,
double button,
bool alt_key,
bool ctrl_key,
bool meta_key,
bool shift_key);
bool is_focused() const { return is_focused_; } bool is_focused() const { return is_focused_; }
bool is_input_in_progress() const { return is_input_in_progress_; } bool is_input_in_progress() const { return is_input_in_progress_; }
bool is_key_capture_enabled() const { return is_key_capture_enabled_; } bool is_key_capture_enabled() const { return is_key_capture_enabled_; }
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
#include "components/ntp_tiles/ntp_tile_impression.h" #include "components/ntp_tiles/ntp_tile_impression.h"
#include "components/ntp_tiles/tile_source.h" #include "components/ntp_tiles/tile_source.h"
#include "components/ntp_tiles/tile_visual_type.h" #include "components/ntp_tiles/tile_visual_type.h"
#include "content/public/common/url_constants.h"
#include "content/public/renderer/chrome_object_extensions_utils.h" #include "content/public/renderer/chrome_object_extensions_utils.h"
#include "content/public/renderer/render_frame.h" #include "content/public/renderer/render_frame.h"
#include "content/public/renderer/render_thread.h" #include "content/public/renderer/render_thread.h"
...@@ -413,12 +414,16 @@ SearchBox* GetSearchBoxForCurrentContext() { ...@@ -413,12 +414,16 @@ SearchBox* GetSearchBoxForCurrentContext() {
} }
base::Value CreateAutocompleteMatches( base::Value CreateAutocompleteMatches(
const std::vector<chrome::mojom::AutocompleteMatchPtr>& matches) { const std::vector<chrome::mojom::AutocompleteMatchPtr>& matches,
const blink::WebLocalFrame* frame) {
DCHECK(frame);
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 : 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);
dict.SetBoolKey("canDisplay", frame->GetSecurityOrigin().CanDisplay(
GURL(match->destination_url)));
dict.SetStringKey("contents", match->contents); dict.SetStringKey("contents", match->contents);
base::Value contents_class(base::Value::Type::LIST); base::Value contents_class(base::Value::Type::LIST);
for (const auto& classification : match->contents_class) { for (const auto& classification : match->contents_class) {
...@@ -600,6 +605,13 @@ class SearchBoxBindings : public gin::Wrappable<SearchBoxBindings> { ...@@ -600,6 +605,13 @@ class SearchBoxBindings : public gin::Wrappable<SearchBoxBindings> {
static void StopAutocomplete(bool clear_result); static void StopAutocomplete(bool clear_result);
static void StartCapturingKeyStrokes(); static void StartCapturingKeyStrokes();
static void StopCapturingKeyStrokes(); static void StopCapturingKeyStrokes();
static void OpenAutocompleteMatch(int line,
const std::string& url,
double button,
bool alt_key,
bool ctrl_key,
bool meta_key,
bool shift_key);
DISALLOW_COPY_AND_ASSIGN(SearchBoxBindings); DISALLOW_COPY_AND_ASSIGN(SearchBoxBindings);
}; };
...@@ -619,6 +631,8 @@ gin::ObjectTemplateBuilder SearchBoxBindings::GetObjectTemplateBuilder( ...@@ -619,6 +631,8 @@ gin::ObjectTemplateBuilder SearchBoxBindings::GetObjectTemplateBuilder(
&SearchBoxBindings::IsKeyCaptureEnabled) &SearchBoxBindings::IsKeyCaptureEnabled)
.SetMethod("deleteAutocompleteMatch", .SetMethod("deleteAutocompleteMatch",
&SearchBoxBindings::DeleteAutocompleteMatch) &SearchBoxBindings::DeleteAutocompleteMatch)
.SetMethod("openAutocompleteMatch",
&SearchBoxBindings::OpenAutocompleteMatch)
.SetMethod("paste", &SearchBoxBindings::Paste) .SetMethod("paste", &SearchBoxBindings::Paste)
.SetMethod("queryAutocomplete", &SearchBoxBindings::QueryAutocomplete) .SetMethod("queryAutocomplete", &SearchBoxBindings::QueryAutocomplete)
.SetMethod("stopAutocomplete", &SearchBoxBindings::StopAutocomplete) .SetMethod("stopAutocomplete", &SearchBoxBindings::StopAutocomplete)
...@@ -654,6 +668,24 @@ void SearchBoxBindings::DeleteAutocompleteMatch(int line) { ...@@ -654,6 +668,24 @@ void SearchBoxBindings::DeleteAutocompleteMatch(int line) {
search_box->DeleteAutocompleteMatch(line); search_box->DeleteAutocompleteMatch(line);
} }
// static
void SearchBoxBindings::OpenAutocompleteMatch(int line,
const std::string& url,
double button,
bool alt_key,
bool ctrl_key,
bool meta_key,
bool shift_key) {
DCHECK_GE(line, 0);
DCHECK_LE(line, 255);
SearchBox* search_box = GetSearchBoxForCurrentContext();
if (!search_box)
return;
search_box->OpenAutocompleteMatch(line, GURL(url), button, alt_key, ctrl_key,
meta_key, shift_key);
}
// static // static
void SearchBoxBindings::Paste(const std::string& text) { void SearchBoxBindings::Paste(const std::string& text) {
SearchBox* search_box = GetSearchBoxForCurrentContext(); SearchBox* search_box = GetSearchBoxForCurrentContext();
...@@ -1418,7 +1450,7 @@ void SearchBoxExtension::DispatchAutocompleteResultChanged( ...@@ -1418,7 +1450,7 @@ void SearchBoxExtension::DispatchAutocompleteResultChanged(
chrome::mojom::AutocompleteResultPtr result) { chrome::mojom::AutocompleteResultPtr result) {
base::Value dict(base::Value::Type::DICTIONARY); base::Value dict(base::Value::Type::DICTIONARY);
dict.SetStringKey("input", result->input); dict.SetStringKey("input", result->input);
dict.SetKey("matches", CreateAutocompleteMatches(result->matches)); dict.SetKey("matches", CreateAutocompleteMatches(result->matches, frame));
std::string json; std::string json;
base::JSONWriter::Write(dict, &json); base::JSONWriter::Write(dict, &json);
......
...@@ -40,6 +40,23 @@ test.realbox.clipboardEvent = function(name) { ...@@ -40,6 +40,23 @@ test.realbox.clipboardEvent = function(name) {
name, {cancelable: true, clipboardData: new DataTransfer()}); name, {cancelable: true, clipboardData: new DataTransfer()});
}; };
/**
* @param {string} type
* @param {!Object=} modifiers
*/
test.realbox.trustedEventFacade = function(type, modifiers = {}) {
return Object.assign(
{
type,
isTrusted: true,
defaultPrevented: false,
preventDefault() {
this.defaultPrevented = true;
},
},
modifiers);
};
/** /**
* @param {!Object=} modifiers Things to override about the returned result. * @param {!Object=} modifiers Things to override about the returned result.
* @return {!AutocompleteResult} * @return {!AutocompleteResult}
...@@ -48,6 +65,7 @@ test.realbox.getUrlMatch = function(modifiers = {}) { ...@@ -48,6 +65,7 @@ test.realbox.getUrlMatch = function(modifiers = {}) {
return Object.assign( return Object.assign(
{ {
allowedToBeDefaultMatch: true, allowedToBeDefaultMatch: true,
canDisplay: true,
contents: 'helloworld.com', contents: 'helloworld.com',
contentsClass: [{offset: 0, style: 1}], contentsClass: [{offset: 0, style: 1}],
description: '', description: '',
...@@ -70,6 +88,7 @@ test.realbox.getSearchMatch = function(modifiers = {}) { ...@@ -70,6 +88,7 @@ test.realbox.getSearchMatch = function(modifiers = {}) {
return Object.assign( return Object.assign(
{ {
allowedToBeDefaultMatch: true, allowedToBeDefaultMatch: true,
canDisplay: true,
contents: 'hello world', contents: 'hello world',
contentsClass: [{offset: 0, style: 0}], contentsClass: [{offset: 0, style: 0}],
description: 'Google search', description: 'Google search',
...@@ -84,15 +103,18 @@ test.realbox.getSearchMatch = function(modifiers = {}) { ...@@ -84,15 +103,18 @@ test.realbox.getSearchMatch = function(modifiers = {}) {
modifiers); modifiers);
}; };
/** @type {!Array<number>} */
test.realbox.deletedLines;
/** @type {!Array<Object>} */
test.realbox.opens;
/** @typedef {{input: string, preventInlineAutocomplete: bool}} */ /** @typedef {{input: string, preventInlineAutocomplete: bool}} */
let AutocompleteQuery; let AutocompleteQuery;
/** @type {!Array<AutocompleteQuery>} */ /** @type {!Array<AutocompleteQuery>} */
test.realbox.queries; test.realbox.queries;
/** @type {!Array<number>} */
test.realbox.deletedLines;
/** @type {!Element} */ /** @type {!Element} */
test.realbox.realboxEl; test.realbox.realboxEl;
...@@ -111,17 +133,18 @@ test.realbox.setUp = function() { ...@@ -111,17 +133,18 @@ test.realbox.setUp = function() {
deleteAutocompleteMatch(line) { deleteAutocompleteMatch(line) {
test.realbox.deletedLines.push(line); test.realbox.deletedLines.push(line);
}, },
openAutocompleteMatch(index, url, button, alt, ctrl, meta, shift) {
test.realbox.opens.push({index, url, button, alt, ctrl, meta, shift});
},
queryAutocomplete(input, preventInlineAutocomplete) { queryAutocomplete(input, preventInlineAutocomplete) {
test.realbox.queries.push({ test.realbox.queries.push({input, preventInlineAutocomplete});
input: input,
preventInlineAutocomplete: preventInlineAutocomplete
});
}, },
stopAutocomplete(clearResult) {} stopAutocomplete(clearResult) {}
}, },
}; };
test.realbox.deletedLines = []; test.realbox.deletedLines = [];
test.realbox.opens = [];
test.realbox.queries = []; test.realbox.queries = [];
initLocalNTP(/*isGooglePage=*/ true); initLocalNTP(/*isGooglePage=*/ true);
...@@ -702,6 +725,7 @@ test.realbox.testRemoveIcon = function() { ...@@ -702,6 +725,7 @@ test.realbox.testRemoveIcon = function() {
assertEquals(1, test.realbox.deletedLines.length); assertEquals(1, test.realbox.deletedLines.length);
assertEquals(0, test.realbox.deletedLines[0]); assertEquals(0, test.realbox.deletedLines[0]);
assertEquals(0, test.realbox.opens.length);
chrome.embeddedSearch.searchBox.autocompleteresultchanged( chrome.embeddedSearch.searchBox.autocompleteresultchanged(
{input: test.realbox.queries[0].input, matches: []}); {input: test.realbox.queries[0].input, matches: []});
...@@ -738,6 +762,7 @@ test.realbox.testPressEnterOnSelectedMatch = function() { ...@@ -738,6 +762,7 @@ test.realbox.testPressEnterOnSelectedMatch = function() {
assertTrue(shiftEnter.defaultPrevented); assertTrue(shiftEnter.defaultPrevented);
assertTrue(clicked); assertTrue(clicked);
assertEquals(0, test.realbox.opens.length);
}; };
test.realbox.testPressEnterNoSelectedMatch = function() { test.realbox.testPressEnterNoSelectedMatch = function() {
...@@ -768,6 +793,7 @@ test.realbox.testPressEnterNoSelectedMatch = function() { ...@@ -768,6 +793,7 @@ test.realbox.testPressEnterNoSelectedMatch = function() {
assertFalse(enter.defaultPrevented); assertFalse(enter.defaultPrevented);
assertFalse(clicked); assertFalse(clicked);
assertEquals(0, test.realbox.opens.length);
}; };
test.realbox.testArrowDownMovesFocus = function() { test.realbox.testArrowDownMovesFocus = function() {
...@@ -869,6 +895,7 @@ test.realbox.testPressEnterAfterFocusout = function() { ...@@ -869,6 +895,7 @@ test.realbox.testPressEnterAfterFocusout = function() {
assertTrue(enter.defaultPrevented); assertTrue(enter.defaultPrevented);
assertTrue(clicked); assertTrue(clicked);
assertEquals(0, test.realbox.opens.length);
}; };
test.realbox.testInputAfterFocusoutPrefixMatches = function() { test.realbox.testInputAfterFocusoutPrefixMatches = function() {
...@@ -990,3 +1017,59 @@ test.realbox.testArrowUpDownShowsMatchesWhenHidden = function() { ...@@ -990,3 +1017,59 @@ test.realbox.testArrowUpDownShowsMatchesWhenHidden = function() {
assertTrue(test.realbox.areMatchesShowing()); assertTrue(test.realbox.areMatchesShowing());
}; };
// Test that trying to open e.g. chrome:// links goes through the mojo API.
test.realbox.testPrivilegedDestinationUrls = function() {
test.realbox.realboxEl.value = 'about';
test.realbox.realboxEl.dispatchEvent(new CustomEvent('input'));
chrome.embeddedSearch.searchBox.autocompleteresultchanged({
input: test.realbox.realboxEl.value,
matches: [
test.realbox.getUrlMatch({
canDisplay: false,
destinationUrl: 'chrome://settings/',
supportsDeletion: true,
}),
],
});
const matchEls = $(test.realbox.IDS.REALBOX_MATCHES).children;
assertEquals(1, matchEls.length);
const target = matchEls[0];
matchEls[0].onclick(test.realbox.trustedEventFacade('click', {target}));
// Accept left clicks.
assertEquals(1, test.realbox.opens.length);
assertEquals('chrome://settings/', test.realbox.opens[0].url);
// Accept middle clicks.
const middleClick =
test.realbox.trustedEventFacade('auxclick', {button: 1, target});
matchEls[0].onauxclick(middleClick);
assertTrue(middleClick.defaultPrevented);
assertEquals(2, test.realbox.opens.length);
// Ignore right clicks.
const rightClick =
test.realbox.trustedEventFacade('auxclick', {button: 2, target});
matchEls[0].onauxclick(rightClick);
assertFalse(rightClick.defaultPrevented);
assertEquals(2, test.realbox.opens.length);
// Accept 'Enter' keypress.
const enter = new KeyboardEvent('keydown', {
bubbles: true,
cancelable: true,
key: 'Enter',
});
test.realbox.realboxEl.dispatchEvent(enter);
assertTrue(enter.defaultPrevented);
assertEquals(3, test.realbox.opens.length);
// Ensure clicking remove icon doesn't accidentally trigger navigation.
assertEquals(0, test.realbox.deletedLines.length);
matchEls[0].querySelector(`.${test.realbox.CLASSES.REMOVE_ICON}`).click();
assertEquals(1, test.realbox.deletedLines.length);
assertEquals(3, test.realbox.opens.length);
};
...@@ -93,6 +93,11 @@ class WebSecurityOrigin { ...@@ -93,6 +93,11 @@ class WebSecurityOrigin {
// from a given security origin to receive contents from a given URL. // from a given security origin to receive contents from a given URL.
BLINK_PLATFORM_EXPORT bool CanRequest(const WebURL&) const; BLINK_PLATFORM_EXPORT bool CanRequest(const WebURL&) const;
// Returns true if this WebSecurityOrigin can display content from the given
// URL (e.g., in an iframe or as an image). For example, web sites generally
// cannot display content from the user's files system.
BLINK_PLATFORM_EXPORT bool CanDisplay(const WebURL&) const;
// Returns true if the origin loads resources either from the local // Returns true if the origin loads resources either from the local
// machine or over the network from a // machine or over the network from a
// cryptographically-authenticated origin, as described in // cryptographically-authenticated origin, as described in
......
...@@ -93,6 +93,11 @@ bool WebSecurityOrigin::CanRequest(const WebURL& url) const { ...@@ -93,6 +93,11 @@ bool WebSecurityOrigin::CanRequest(const WebURL& url) const {
return private_->CanRequest(url); return private_->CanRequest(url);
} }
bool WebSecurityOrigin::CanDisplay(const WebURL& url) const {
DCHECK(private_);
return private_->CanDisplay(url);
}
bool WebSecurityOrigin::IsPotentiallyTrustworthy() const { bool WebSecurityOrigin::IsPotentiallyTrustworthy() const {
DCHECK(private_); DCHECK(private_);
return private_->IsPotentiallyTrustworthy(); return private_->IsPotentiallyTrustworthy();
......
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