| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stddef.h> |
| |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "components/omnibox/browser/autocomplete_classifier.h" |
| #include "components/omnibox/browser/autocomplete_controller.h" |
| #include "components/omnibox/browser/autocomplete_scheme_classifier.h" |
| #include "components/omnibox/browser/mock_autocomplete_provider_client.h" |
| #include "components/omnibox/browser/omnibox_client.h" |
| #include "components/omnibox/browser/omnibox_edit_controller.h" |
| #include "components/omnibox/browser/omnibox_edit_model.h" |
| #include "components/omnibox/browser/omnibox_popup_model.h" |
| #include "components/omnibox/browser/omnibox_popup_view.h" |
| #include "components/omnibox/browser/omnibox_view.h" |
| #include "components/omnibox/browser/search_provider.h" |
| #include "components/search_engines/search_terms_data.h" |
| #include "components/search_engines/template_url_service.h" |
| #include "components/search_engines/template_url_service_client.h" |
| #include "components/sessions/core/session_id.h" |
| #include "components/toolbar/test_toolbar_model.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/geometry/rect.h" |
| |
| namespace { |
| |
| class TestingOmniboxView : public OmniboxView { |
| public: |
| explicit TestingOmniboxView(OmniboxEditController* controller) |
| : OmniboxView(controller, nullptr) {} |
| |
| // OmniboxView: |
| void Update() override {} |
| void OpenMatch(const AutocompleteMatch& match, |
| WindowOpenDisposition disposition, |
| const GURL& alternate_nav_url, |
| const base::string16& pasted_text, |
| size_t selected_line) override {} |
| base::string16 GetText() const override { return text_; } |
| void SetUserText(const base::string16& text, |
| bool update_popup) override { |
| text_ = text; |
| } |
| void SetWindowTextAndCaretPos(const base::string16& text, |
| size_t caret_pos, |
| bool update_popup, |
| bool notify_text_changed) override { |
| text_ = text; |
| } |
| void SetCaretPos(size_t caret_pos) override {} |
| void EnterKeywordModeForDefaultSearchProvider() override {} |
| bool IsSelectAll() const override { return false; } |
| bool DeleteAtEndPressed() override { return false; } |
| void GetSelectionBounds(size_t* start, size_t* end) const override {} |
| void SelectAll(bool reversed) override {} |
| void RevertAll() override {} |
| void UpdatePopup() override {} |
| void SetFocus() override {} |
| void ApplyCaretVisibility() override {} |
| void OnTemporaryTextMaybeChanged(const base::string16& display_text, |
| bool save_original_selection, |
| bool notify_text_changed) override { |
| text_ = display_text; |
| } |
| bool OnInlineAutocompleteTextMaybeChanged(const base::string16& display_text, |
| size_t user_text_length) override { |
| const bool text_changed = text_ != display_text; |
| text_ = display_text; |
| inline_autocomplete_text_ = display_text.substr(user_text_length); |
| return text_changed; |
| } |
| void OnInlineAutocompleteTextCleared() override { |
| inline_autocomplete_text_.clear(); |
| } |
| void OnRevertTemporaryText() override {} |
| void OnBeforePossibleChange() override {} |
| bool OnAfterPossibleChange(bool allow_keyword_ui_change) override { |
| return false; |
| } |
| gfx::NativeView GetNativeView() const override { return nullptr; } |
| gfx::NativeView GetRelativeWindowForPopup() const override { return nullptr; } |
| int GetTextWidth() const override { return 0; } |
| int GetWidth() const override { return 0; } |
| bool IsImeComposing() const override { return false; } |
| int GetOmniboxTextLength() const override { return 0; } |
| void EmphasizeURLComponents() override {} |
| void SetEmphasis(bool emphasize, const gfx::Range& range) override {} |
| void UpdateSchemeStyle(const gfx::Range& range) override {} |
| |
| const base::string16& inline_autocomplete_text() const { |
| return inline_autocomplete_text_; |
| } |
| |
| private: |
| base::string16 text_; |
| base::string16 inline_autocomplete_text_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestingOmniboxView); |
| }; |
| |
| class TestingOmniboxEditController : public OmniboxEditController { |
| public: |
| explicit TestingOmniboxEditController(ToolbarModel* toolbar_model) |
| : toolbar_model_(toolbar_model) {} |
| |
| protected: |
| // OmniboxEditController: |
| void OnInputInProgress(bool in_progress) override {} |
| void OnChanged() override {} |
| ToolbarModel* GetToolbarModel() override { return toolbar_model_; } |
| const ToolbarModel* GetToolbarModel() const override { |
| return toolbar_model_; |
| } |
| |
| private: |
| ToolbarModel* toolbar_model_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestingOmniboxEditController); |
| }; |
| |
| class TestingOmniboxPopupView : public OmniboxPopupView { |
| public: |
| ~TestingOmniboxPopupView() override {} |
| bool IsOpen() const override { return false; } |
| void InvalidateLine(size_t line) override {} |
| void OnLineSelected(size_t line) override {} |
| void UpdatePopupAppearance() override {} |
| void OnMatchIconUpdated(size_t match_index) override {} |
| gfx::Rect GetTargetBounds() override { return gfx::Rect(); } |
| void PaintUpdatesNow() override {} |
| void OnDragCanceled() override {} |
| }; |
| |
| class TestingSchemeClassifier : public AutocompleteSchemeClassifier { |
| public: |
| TestingSchemeClassifier() {} |
| |
| metrics::OmniboxInputType GetInputTypeForScheme( |
| const std::string& scheme) const override { |
| return metrics::OmniboxInputType::URL; |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(TestingSchemeClassifier); |
| }; |
| |
| class TestingOmniboxClient : public OmniboxClient { |
| public: |
| TestingOmniboxClient(); |
| ~TestingOmniboxClient() override; |
| |
| const AutocompleteMatch& alternate_nav_match() const { |
| return alternate_nav_match_; |
| } |
| |
| // OmniboxClient: |
| std::unique_ptr<AutocompleteProviderClient> CreateAutocompleteProviderClient() |
| override; |
| |
| std::unique_ptr<OmniboxNavigationObserver> CreateOmniboxNavigationObserver( |
| const base::string16& text, |
| const AutocompleteMatch& match, |
| const AutocompleteMatch& alternate_nav_match) override { |
| alternate_nav_match_ = alternate_nav_match; |
| return nullptr; |
| } |
| const SessionID& GetSessionID() const override { return session_id_; } |
| const AutocompleteSchemeClassifier& GetSchemeClassifier() const override { |
| return scheme_classifier_; |
| } |
| AutocompleteClassifier* GetAutocompleteClassifier() override { |
| return &autocomplete_classifier_; |
| } |
| |
| private: |
| SessionID session_id_; |
| TestingSchemeClassifier scheme_classifier_; |
| AutocompleteClassifier autocomplete_classifier_; |
| AutocompleteMatch alternate_nav_match_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestingOmniboxClient); |
| }; |
| |
| TestingOmniboxClient::TestingOmniboxClient() |
| : autocomplete_classifier_( |
| base::MakeUnique<AutocompleteController>( |
| CreateAutocompleteProviderClient(), |
| nullptr, |
| AutocompleteClassifier::DefaultOmniboxProviders()), |
| base::MakeUnique<TestingSchemeClassifier>()) {} |
| |
| TestingOmniboxClient::~TestingOmniboxClient() { |
| autocomplete_classifier_.Shutdown(); |
| } |
| |
| std::unique_ptr<AutocompleteProviderClient> |
| TestingOmniboxClient::CreateAutocompleteProviderClient() { |
| std::unique_ptr<MockAutocompleteProviderClient> provider_client( |
| new testing::NiceMock<MockAutocompleteProviderClient>()); |
| EXPECT_CALL(*provider_client.get(), GetBuiltinURLs()) |
| .WillRepeatedly(testing::Return(std::vector<base::string16>())); |
| EXPECT_CALL(*provider_client.get(), GetSchemeClassifier()) |
| .WillRepeatedly(testing::ReturnRef(scheme_classifier_)); |
| |
| std::unique_ptr<TemplateURLService> template_url_service( |
| new TemplateURLService( |
| nullptr, std::unique_ptr<SearchTermsData>(new SearchTermsData), |
| nullptr, std::unique_ptr<TemplateURLServiceClient>(), nullptr, |
| nullptr, base::Closure())); |
| provider_client->set_template_url_service(std::move(template_url_service)); |
| |
| return std::move(provider_client); |
| } |
| |
| } // namespace |
| |
| class OmniboxEditTest : public ::testing::Test { |
| public: |
| OmniboxEditTest() |
| : controller_(&toolbar_model_), |
| view_(&controller_), |
| model_(&view_, &controller_, base::MakeUnique<TestingOmniboxClient>()), |
| popup_model_(&popup_view_, &model_) {} |
| |
| TestToolbarModel* toolbar_model() { return &toolbar_model_; } |
| const TestingOmniboxView& view() { return view_; } |
| OmniboxEditModel* model() { return &model_; } |
| OmniboxPopupModel* popup_model() { return &popup_model_; } |
| |
| private: |
| base::MessageLoop message_loop_; |
| TestToolbarModel toolbar_model_; |
| TestingOmniboxEditController controller_; |
| TestingOmniboxView view_; |
| OmniboxEditModel model_; |
| TestingOmniboxPopupView popup_view_; |
| OmniboxPopupModel popup_model_; |
| |
| DISALLOW_COPY_AND_ASSIGN(OmniboxEditTest); |
| }; |
| |
| // Tests various permutations of AutocompleteModel::AdjustTextForCopy. |
| TEST_F(OmniboxEditTest, AdjustTextForCopy) { |
| struct Data { |
| const char* perm_text; |
| const int sel_start; |
| const bool is_all_selected; |
| const char* input; |
| const char* expected_output; |
| const bool write_url; |
| const char* expected_url; |
| } input[] = { |
| // Test that http:// is inserted if all text is selected. |
| { "a.de/b", 0, true, "a.de/b", "http://a.de/b", true, "http://a.de/b", }, |
| |
| // Test that http:// is inserted if the host is selected. |
| { "a.de/b", 0, false, "a.de/", "http://a.de/", true, "http://a.de/" }, |
| |
| // Tests that http:// is inserted if the path is modified. |
| { "a.de/b", 0, false, "a.de/c", "http://a.de/c", true, "http://a.de/c" }, |
| |
| // Tests that http:// isn't inserted if the host is modified. |
| { "a.de/b", 0, false, "a.com/b", "a.com/b", false, "" }, |
| |
| // Tests that http:// isn't inserted if the start of the selection is 1. |
| { "a.de/b", 1, false, "a.de/b", "a.de/b", false, "" }, |
| |
| // Tests that http:// isn't inserted if a portion of the host is selected. |
| { "a.de/", 0, false, "a.d", "a.d", false, "" }, |
| |
| // Tests that http:// isn't inserted for an https url after the user nukes |
| // https. |
| { "https://a.com/", 0, false, "a.com/", "a.com/", false, "" }, |
| |
| // Tests that http:// isn't inserted if the user adds to the host. |
| { "a.de/", 0, false, "a.de.com/", "a.de.com/", false, "" }, |
| |
| // Tests that we don't get double http if the user manually inserts http. |
| { "a.de/", 0, false, "http://a.de/", "http://a.de/", true, "http://a.de/" }, |
| |
| // Makes sure intranet urls get 'http://' prefixed to them. |
| { "b/foo", 0, true, "b/foo", "http://b/foo", true, "http://b/foo" }, |
| |
| // Verifies a search term 'foo' doesn't end up with http. |
| { "www.google.com/search?", 0, false, "foo", "foo", false, "" }, |
| }; |
| |
| for (size_t i = 0; i < arraysize(input); ++i) { |
| toolbar_model()->set_text(base::ASCIIToUTF16(input[i].perm_text)); |
| model()->UpdatePermanentText(); |
| |
| base::string16 result = base::ASCIIToUTF16(input[i].input); |
| GURL url; |
| bool write_url; |
| model()->AdjustTextForCopy(input[i].sel_start, input[i].is_all_selected, |
| &result, &url, &write_url); |
| EXPECT_EQ(base::ASCIIToUTF16(input[i].expected_output), result) << "@: " |
| << i; |
| EXPECT_EQ(input[i].write_url, write_url) << " @" << i; |
| if (write_url) |
| EXPECT_EQ(input[i].expected_url, url.spec()) << " @" << i; |
| } |
| } |
| |
| TEST_F(OmniboxEditTest, InlineAutocompleteText) { |
| // Test if the model updates the inline autocomplete text in the view. |
| EXPECT_EQ(base::string16(), view().inline_autocomplete_text()); |
| model()->SetUserText(base::ASCIIToUTF16("he")); |
| model()->OnPopupDataChanged(base::ASCIIToUTF16("llo"), nullptr, |
| base::string16(), false); |
| EXPECT_EQ(base::ASCIIToUTF16("hello"), view().GetText()); |
| EXPECT_EQ(base::ASCIIToUTF16("llo"), view().inline_autocomplete_text()); |
| |
| base::string16 text_before = base::ASCIIToUTF16("he"); |
| base::string16 text_after = base::ASCIIToUTF16("hel"); |
| OmniboxView::StateChanges state_changes{ |
| &text_before, &text_after, 3, 3, false, true, false, false}; |
| model()->OnAfterPossibleChange(state_changes, true); |
| EXPECT_EQ(base::string16(), view().inline_autocomplete_text()); |
| model()->OnPopupDataChanged(base::ASCIIToUTF16("lo"), nullptr, |
| base::string16(), false); |
| EXPECT_EQ(base::ASCIIToUTF16("hello"), view().GetText()); |
| EXPECT_EQ(base::ASCIIToUTF16("lo"), view().inline_autocomplete_text()); |
| |
| model()->Revert(); |
| EXPECT_EQ(base::string16(), view().GetText()); |
| EXPECT_EQ(base::string16(), view().inline_autocomplete_text()); |
| |
| model()->SetUserText(base::ASCIIToUTF16("he")); |
| model()->OnPopupDataChanged(base::ASCIIToUTF16("llo"), nullptr, |
| base::string16(), false); |
| EXPECT_EQ(base::ASCIIToUTF16("hello"), view().GetText()); |
| EXPECT_EQ(base::ASCIIToUTF16("llo"), view().inline_autocomplete_text()); |
| |
| model()->AcceptTemporaryTextAsUserText(); |
| EXPECT_EQ(base::ASCIIToUTF16("hello"), view().GetText()); |
| EXPECT_EQ(base::string16(), view().inline_autocomplete_text()); |
| } |
| |
| // This verifies the fix for a bug where calling OpenMatch() with a valid |
| // alternate nav URL would fail a DCHECK if the input began with "http://". |
| // The failure was due to erroneously trying to strip the scheme from the |
| // resulting fill_into_edit. Alternate nav matches are never shown, so there's |
| // no need to ever try and strip this scheme. |
| TEST_F(OmniboxEditTest, AlternateNavHasHTTP) { |
| const TestingOmniboxClient* client = |
| static_cast<TestingOmniboxClient*>(model()->client()); |
| const AutocompleteMatch match( |
| model()->autocomplete_controller()->search_provider(), 0, false, |
| AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED); |
| const GURL alternate_nav_url("http://abcd/"); |
| |
| model()->OnSetFocus(false); // Avoids DCHECK in OpenMatch(). |
| model()->SetUserText(base::ASCIIToUTF16("http://abcd")); |
| model()->OpenMatch(match, WindowOpenDisposition::CURRENT_TAB, |
| alternate_nav_url, base::string16(), 0); |
| EXPECT_TRUE(AutocompleteInput::HasHTTPScheme( |
| client->alternate_nav_match().fill_into_edit)); |
| |
| model()->SetUserText(base::ASCIIToUTF16("abcd")); |
| model()->OpenMatch(match, WindowOpenDisposition::CURRENT_TAB, |
| alternate_nav_url, base::string16(), 0); |
| EXPECT_TRUE(AutocompleteInput::HasHTTPScheme( |
| client->alternate_nav_match().fill_into_edit)); |
| } |
| |
| // This verifies that the new treatment of the user's selected match in |
| // |SetSelectedLine()| with removed |AutocompleteResult::Selection::empty()| |
| // is correct in the face of various replacement versions of |empty()|. |
| TEST_F(OmniboxEditTest, SetSelectedLine) { |
| ACMatches matches; |
| for (size_t i = 0; i < 2; ++i) { |
| AutocompleteMatch match(nullptr, 1000, false, |
| AutocompleteMatchType::URL_WHAT_YOU_TYPED); |
| match.keyword = base::ASCIIToUTF16("match"); |
| matches.push_back(match); |
| } |
| auto* result = &model()->autocomplete_controller()->result_; |
| result->AppendMatches(AutocompleteInput(), matches); |
| result->SortAndCull(AutocompleteInput(), nullptr); |
| popup_model()->OnResultChanged(); |
| EXPECT_FALSE(popup_model()->has_selected_match()); |
| popup_model()->SetSelectedLine(0, true, false); |
| EXPECT_FALSE(popup_model()->has_selected_match()); |
| popup_model()->SetSelectedLine(0, false, false); |
| EXPECT_TRUE(popup_model()->has_selected_match()); |
| } |