blob: c44e69bdf6b11a7bd98b8f6ae0a6a815965911b5 [file] [log] [blame]
// 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_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"
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 TestingSchemeClassifier : public AutocompleteSchemeClassifier {
public:
TestingSchemeClassifier() {}
metrics::OmniboxInputType::Type 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 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>()) {
}
TestToolbarModel* toolbar_model() { return &toolbar_model_; }
const TestingOmniboxView& view() { return view_; }
OmniboxEditModel* model() { return &model_; }
private:
base::MessageLoop message_loop_;
TestToolbarModel toolbar_model_;
TestingOmniboxEditController controller_;
TestingOmniboxView view_;
OmniboxEditModel 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));
}