diff options
author | mpearson@chromium.org <mpearson@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-23 04:28:07 +0000 |
---|---|---|
committer | mpearson@chromium.org <mpearson@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-23 04:28:07 +0000 |
commit | b3a84893aee323f458848fe2b28cc2d5a9aa8c97 (patch) | |
tree | 2a701266bbb440570fab09e828df3267647c853c /chrome | |
parent | 98e55e8c3281fbf5a18e99b48a1bd67428a0a8e5 (diff) | |
download | chromium_src-b3a84893aee323f458848fe2b28cc2d5a9aa8c97.zip chromium_src-b3a84893aee323f458848fe2b28cc2d5a9aa8c97.tar.gz chromium_src-b3a84893aee323f458848fe2b28cc2d5a9aa8c97.tar.bz2 |
Omnibox: Make URLs of Bookmarks Searchable
In particular, this makes
* BookmarkIndex index the URLs for bookmarks
* BookmarkIndex search for matches (match positions) within those URLs
* BookmarkProvider score based on both title and URL matches
All these is guarded by a field trial, also added in this changelist.
I added ample unit tests.
I also verified this works by testing it interactively using a local variations server and --force-fieldtrials.
BUG=157204
Review URL: https://codereview.chromium.org/184663002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@265539 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
33 files changed, 614 insertions, 278 deletions
diff --git a/chrome/browser/autocomplete/bookmark_provider.cc b/chrome/browser/autocomplete/bookmark_provider.cc index 07b80d9..de8ea9d 100644 --- a/chrome/browser/autocomplete/bookmark_provider.cc +++ b/chrome/browser/autocomplete/bookmark_provider.cc @@ -15,12 +15,13 @@ #include "chrome/browser/autocomplete/url_prefix.h" #include "chrome/browser/bookmarks/bookmark_model.h" #include "chrome/browser/bookmarks/bookmark_model_factory.h" +#include "chrome/browser/omnibox/omnibox_field_trial.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/pref_names.h" -#include "components/bookmarks/core/browser/bookmark_title_match.h" +#include "components/bookmarks/core/browser/bookmark_match.h" #include "net/base/net_util.h" -typedef std::vector<BookmarkTitleMatch> TitleMatches; +typedef std::vector<BookmarkMatch> BookmarkMatches; // BookmarkProvider ------------------------------------------------------------ @@ -29,7 +30,8 @@ BookmarkProvider::BookmarkProvider( Profile* profile) : AutocompleteProvider(listener, profile, AutocompleteProvider::TYPE_BOOKMARK), - bookmark_model_(NULL) { + bookmark_model_(NULL), + score_using_url_matches_(OmniboxFieldTrial::BookmarksIndexURLsValue()) { if (profile) { bookmark_model_ = BookmarkModelFactory::GetForProfile(profile); languages_ = profile_->GetPrefs()->GetString(prefs::kAcceptLanguages); @@ -43,8 +45,7 @@ void BookmarkProvider::Start(const AutocompleteInput& input, matches_.clear(); if (input.text().empty() || - ((input.type() != AutocompleteInput::UNKNOWN) && - (input.type() != AutocompleteInput::QUERY))) + (input.type() == AutocompleteInput::FORCED_QUERY)) return; DoAutocomplete(input); @@ -57,12 +58,12 @@ void BookmarkProvider::DoAutocomplete(const AutocompleteInput& input) { if (!bookmark_model_) return; - TitleMatches matches; + BookmarkMatches matches; // Retrieve enough bookmarks so that we have a reasonable probability of // suggesting the one that the user desires. const size_t kMaxBookmarkMatches = 50; - // GetBookmarksWithTitlesMatching returns bookmarks matching the user's + // GetBookmarksMatching returns bookmarks matching the user's // search terms using the following rules: // - The search text is broken up into search terms. Each term is searched // for separately. @@ -77,26 +78,21 @@ void BookmarkProvider::DoAutocomplete(const AutocompleteInput& input) { // - Multiple terms enclosed in quotes will require those exact words in that // exact order to match. // - // Note: GetBookmarksWithTitlesMatching() will never return a match span - // greater than the length of the title against which it is being matched, - // nor can those spans ever overlap because the match spans are coalesced - // for all matched terms. - // - // Please refer to the code for BookmarkIndex::GetBookmarksWithTitlesMatching - // for complete details of how title searches are performed against the user's + // Please refer to the code for BookmarkIndex::GetBookmarksMatching for + // complete details of how searches are performed against the user's // bookmarks. - bookmark_model_->GetBookmarksWithTitlesMatching(input.text(), - kMaxBookmarkMatches, - &matches); + bookmark_model_->GetBookmarksMatching(input.text(), + kMaxBookmarkMatches, + &matches); if (matches.empty()) return; // There were no matches. AutocompleteInput fixed_up_input(input); FixupUserInput(&fixed_up_input); - for (TitleMatches::const_iterator i = matches.begin(); i != matches.end(); + for (BookmarkMatches::const_iterator i = matches.begin(); i != matches.end(); ++i) { // Create and score the AutocompleteMatch. If its score is 0 then the // match is discarded. - AutocompleteMatch match(TitleMatchToACMatch(input, fixed_up_input, *i)); + AutocompleteMatch match(BookmarkMatchToACMatch(input, fixed_up_input, *i)); if (match.relevance > 0) matches_.push_back(match); } @@ -114,12 +110,8 @@ namespace { // for_each helper functor that calculates a match factor for each query term // when calculating the final score. // -// Calculate a 'factor' from 0.0 to 1.0 based on 1) how much of the bookmark's -// title the term matches, and 2) where the match is positioned within the -// bookmark's title. A full length match earns a 1.0. A half-length match earns -// at most a 0.5 and at least a 0.25. A single character match against a title -// that is 100 characters long where the match is at the first character will -// earn a 0.01 and at the last character will earn a 0.0001. +// Calculate a 'factor' from 0 to the bookmark's title length for a match +// based on 1) how many characters match and 2) where the match is positioned. class ScoringFunctor { public: // |title_length| is the length of the bookmark title against which this @@ -131,7 +123,7 @@ class ScoringFunctor { void operator()(const query_parser::Snippet::MatchPosition& match) { double term_length = static_cast<double>(match.second - match.first); - scoring_factor_ += term_length / title_length_ * + scoring_factor_ += term_length * (title_length_ - match.first) / title_length_; } @@ -144,32 +136,44 @@ class ScoringFunctor { } // namespace -AutocompleteMatch BookmarkProvider::TitleMatchToACMatch( +AutocompleteMatch BookmarkProvider::BookmarkMatchToACMatch( const AutocompleteInput& input, const AutocompleteInput& fixed_up_input, - const BookmarkTitleMatch& title_match) { + const BookmarkMatch& bookmark_match) { // The AutocompleteMatch we construct is non-deletable because the only // way to support this would be to delete the underlying bookmark, which is // unlikely to be what the user intends. AutocompleteMatch match(this, 0, false, AutocompleteMatchType::BOOKMARK_TITLE); - const base::string16& title(title_match.node->GetTitle()); - DCHECK(!title.empty()); - - const GURL& url(title_match.node->url()); + base::string16 title(bookmark_match.node->GetTitle()); + const GURL& url(bookmark_match.node->url()); const base::string16& url_utf16 = base::UTF8ToUTF16(url.spec()); - size_t match_start, inline_autocomplete_offset; - URLPrefix::ComputeMatchStartAndInlineAutocompleteOffset( - input, fixed_up_input, false, url_utf16, &match_start, - &inline_autocomplete_offset); + size_t inline_autocomplete_offset = URLPrefix::GetInlineAutocompleteOffset( + input, fixed_up_input, false, url_utf16); match.destination_url = url; + const size_t match_start = bookmark_match.url_match_positions.empty() ? + 0 : bookmark_match.url_match_positions[0].first; const bool trim_http = !AutocompleteInput::HasHTTPScheme(input.text()) && ((match_start == base::string16::npos) || (match_start != 0)); - match.contents = net::FormatUrl(url, languages_, + std::vector<size_t> offsets = BookmarkMatch::OffsetsFromMatchPositions( + bookmark_match.url_match_positions); + // In addition to knowing how |offsets| is transformed, we need to know how + // |inline_autocomplete_offset| is transformed. We add it to the end of + // |offsets|, compute how everything is transformed, then remove it from the + // end. + offsets.push_back(inline_autocomplete_offset); + match.contents = net::FormatUrlWithOffsets(url, languages_, net::kFormatUrlOmitAll & ~(trim_http ? 0 : net::kFormatUrlOmitHTTP), - net::UnescapeRule::SPACES, NULL, NULL, &inline_autocomplete_offset); - match.contents_class.push_back( - ACMatchClassification(0, ACMatchClassification::URL)); + net::UnescapeRule::SPACES, NULL, NULL, &offsets); + inline_autocomplete_offset = offsets.back(); + offsets.pop_back(); + BookmarkMatch::MatchPositions new_url_match_positions = + BookmarkMatch::ReplaceOffsetsInMatchPositions( + bookmark_match.url_match_positions, offsets); + match.contents_class = + ClassificationsFromMatch(new_url_match_positions, + match.contents.size(), + true); match.fill_into_edit = AutocompleteInput::FormattedStringWithEquivalentMeaning(url, match.contents); @@ -187,48 +191,56 @@ AutocompleteMatch BookmarkProvider::TitleMatchToACMatch( } match.description = title; match.description_class = - ClassificationsFromMatch(title_match.match_positions, - match.description.size()); + ClassificationsFromMatch(bookmark_match.title_match_positions, + match.description.size(), + false); match.starred = true; // Summary on how a relevance score is determined for the match: // - // For each term matching within the bookmark's title (as given by the set of - // Snippet::MatchPositions) calculate a 'factor', sum up those factors, then - // use the sum to figure out a value between the base score and the maximum - // score. - // - // The factor for each term is the product of: + // For each match within the bookmark's title or URL (or both), calculate a + // 'factor', sum up those factors, then use the sum to figure out a value + // between the base score and the maximum score. // - // 1) how much of the bookmark's title has been matched by the term: - // (term length / title length). + // The factor for each match is the product of: // - // Example: Given a bookmark title 'abcde fghijklm', with a title length - // of 14, and two different search terms, 'abcde' and 'fghijklm', with - // term lengths of 5 and 8, respectively, 'fghijklm' will score higher - // (with a partial factor of 8/14 = 0.571) than 'abcde' (5/14 = 0.357). + // 1) how many characters in the bookmark's title/URL are part of this match. + // This is capped at the length of the bookmark's title + // to prevent terms that match in both the title and the URL from + // scoring too strongly. // - // 2) where the term match occurs within the bookmark's title, giving more - // points for matches that appear earlier in the title: - // ((title length - position of match start) / title_length). + // 2) where the match occurs within the bookmark's title or URL, + // giving more points for matches that appear earlier in the string: + // ((string_length - position of match start) / string_length). // // Example: Given a bookmark title of 'abcde fghijklm', with a title length // of 14, and two different search terms, 'abcde' and 'fghij', with // start positions of 0 and 6, respectively, 'abcde' will score higher // (with a a partial factor of (14-0)/14 = 1.000 ) than 'fghij' (with - // a partial factor of (14-6)/14 = 0.571 ). + // a partial factor of (14-6)/14 = 0.571 ). (In this example neither + // term matches in the URL.) // - // Once all term factors have been calculated they are summed. The resulting - // sum will never be greater than 1.0 because of the way the bookmark model - // matches and removes overlaps. (In particular, the bookmark model only + // Once all match factors have been calculated they are summed. If URL + // matches are not considered, the resulting sum will never be greater than + // the length of the bookmark title because of the way the bookmark model + // matches and removes overlaps. (In particular, the bookmark model only // matches terms to the beginning of words and it removes all overlapping - // matches, keeping only the longest. Together these mean that each - // character is included in at most one match. This property ensures the - // sum of factors is at most 1.) This sum is then multiplied against the - // scoring range available, which is 299. The 299 is calculated by - // subtracting the minimum possible score, 900, from the maximum possible - // score, 1199. This product, ranging from 0 to 299, is added to the minimum - // possible score, 900, giving the preliminary score. + // matches, keeping only the longest. Together these mean that each + // character is included in at most one match.) If URL matches are + // considered, the sum can be greater. + // + // This sum is then normalized by the length of the bookmark title (if URL + // matches are not considered) or by the length of the bookmark title + 10 + // (if URL matches are considered) and capped at 1.0. (If URL matches + // are considered, we want to expand the scoring range so fewer bookmarks + // will hit the 1.0 cap and hence lose all ability to distinguish between + // these high-quality bookmarks.) + // + // The normalized value is multiplied against the scoring range available, + // which is 299. The 299 is calculated by subtracting the minimum possible + // score, 900, from the maximum possible score, 1199. This product, ranging + // from 0 to 299, is added to the minimum possible score, 900, giving the + // preliminary score. // // If the preliminary score is less than the maximum possible score, 1199, // it can be boosted up to that maximum possible score if the URL referenced @@ -238,18 +250,32 @@ AutocompleteMatch BookmarkProvider::TitleMatchToACMatch( // scored up to a maximum of three, the score is boosted by a fixed amount // given by |kURLCountBoost|, below. // - ScoringFunctor position_functor = - for_each(title_match.match_positions.begin(), - title_match.match_positions.end(), ScoringFunctor(title.size())); + if (score_using_url_matches_) { + // Pretend empty titles are identical to the URL. + if (title.empty()) + title = base::ASCIIToUTF16(url.spec()); + } else { + DCHECK(!title.empty()); + } + ScoringFunctor title_position_functor = + for_each(bookmark_match.title_match_positions.begin(), + bookmark_match.title_match_positions.end(), + ScoringFunctor(title.size())); + ScoringFunctor url_position_functor = + for_each(bookmark_match.url_match_positions.begin(), + bookmark_match.url_match_positions.end(), + ScoringFunctor(bookmark_match.node->url().spec().length())); + const double summed_factors = title_position_functor.ScoringFactor() + + (score_using_url_matches_ ? url_position_functor.ScoringFactor() : 0); + const double normalized_sum = std::min( + summed_factors / (title.size() + (score_using_url_matches_ ? 10 : 0)), + 1.0); const int kBaseBookmarkScore = 900; const int kMaxBookmarkScore = 1199; const double kBookmarkScoreRange = static_cast<double>(kMaxBookmarkScore - kBaseBookmarkScore); - // It's not likely that GetBookmarksWithTitlesMatching will return overlapping - // matches but let's play it safe. - match.relevance = std::min(kMaxBookmarkScore, - static_cast<int>(position_functor.ScoringFactor() * kBookmarkScoreRange) + - kBaseBookmarkScore); + match.relevance = static_cast<int>(normalized_sum * kBookmarkScoreRange) + + kBaseBookmarkScore; // Don't waste any time searching for additional referenced URLs if we // already have a perfect title match. if (match.relevance >= kMaxBookmarkScore) @@ -268,11 +294,14 @@ AutocompleteMatch BookmarkProvider::TitleMatchToACMatch( // static ACMatchClassifications BookmarkProvider::ClassificationsFromMatch( const query_parser::Snippet::MatchPositions& positions, - size_t text_length) { + size_t text_length, + bool is_url) { + ACMatchClassification::Style url_style = + is_url ? ACMatchClassification::URL : ACMatchClassification::NONE; ACMatchClassifications classifications; if (positions.empty()) { classifications.push_back( - ACMatchClassification(0, ACMatchClassification::NONE)); + ACMatchClassification(0, url_style)); return classifications; } @@ -282,7 +311,7 @@ ACMatchClassifications BookmarkProvider::ClassificationsFromMatch( ++i) { AutocompleteMatch::ACMatchClassifications new_class; AutocompleteMatch::ClassifyLocationInString(i->first, i->second - i->first, - text_length, 0, &new_class); + text_length, url_style, &new_class); classifications = AutocompleteMatch::MergeClassifications( classifications, new_class); } diff --git a/chrome/browser/autocomplete/bookmark_provider.h b/chrome/browser/autocomplete/bookmark_provider.h index 8bdf2e9..be256e9 100644 --- a/chrome/browser/autocomplete/bookmark_provider.h +++ b/chrome/browser/autocomplete/bookmark_provider.h @@ -13,7 +13,7 @@ #include "components/query_parser/snippet.h" class BookmarkModel; -struct BookmarkTitleMatch; +struct BookmarkMatch; class Profile; // This class is an autocomplete provider which quickly (and synchronously) @@ -48,15 +48,15 @@ class BookmarkProvider : public AutocompleteProvider { // |matches_|. void DoAutocomplete(const AutocompleteInput& input); - // Compose an AutocompleteMatch based on |title_match| that has 1) the URL of - // title_match's bookmark, and 2) the bookmark's title, not the URL's page + // Compose an AutocompleteMatch based on |match| that has 1) the URL of + // |match|'s bookmark, and 2) the bookmark's title, not the URL's page // title, as the description. |input| is used to compute the match's // inline_autocompletion. |fixed_up_input| is used in that way as well; // it's passed separately so this function doesn't have to compute it. - AutocompleteMatch TitleMatchToACMatch( + AutocompleteMatch BookmarkMatchToACMatch( const AutocompleteInput& input, const AutocompleteInput& fixed_up_input, - const BookmarkTitleMatch& title_match); + const BookmarkMatch& match); // Converts |positions| into ACMatchClassifications and returns the // classifications. |text_length| is used to determine the need to add an @@ -64,10 +64,14 @@ class BookmarkProvider : public AutocompleteProvider { // properly highlighted. static ACMatchClassifications ClassificationsFromMatch( const query_parser::Snippet::MatchPositions& positions, - size_t text_length); + size_t text_length, + bool is_url); BookmarkModel* bookmark_model_; + // True if we should use matches in the URL for scoring. + const bool score_using_url_matches_; + // Languages used during the URL formatting. std::string languages_; diff --git a/chrome/browser/autocomplete/bookmark_provider_unittest.cc b/chrome/browser/autocomplete/bookmark_provider_unittest.cc index 380eadb..1cca616 100644 --- a/chrome/browser/autocomplete/bookmark_provider_unittest.cc +++ b/chrome/browser/autocomplete/bookmark_provider_unittest.cc @@ -12,13 +12,14 @@ #include "base/memory/scoped_ptr.h" #include "base/strings/string16.h" #include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/autocomplete/autocomplete_provider.h" #include "chrome/browser/autocomplete/autocomplete_provider_listener.h" #include "chrome/browser/bookmarks/bookmark_model.h" #include "chrome/browser/bookmarks/bookmark_model_factory.h" #include "chrome/test/base/testing_profile.h" -#include "components/bookmarks/core/browser/bookmark_title_match.h" +#include "components/bookmarks/core/browser/bookmark_match.h" #include "testing/gtest/include/gtest/gtest.h" // The bookmark corpus against which we will simulate searches. @@ -35,6 +36,8 @@ struct BookmarksTestInfo { { "jkl ghi", "http://www.catsanddogs.com/g" }, { "frankly frankly frank", "http://www.catsanddogs.com/h" }, { "foobar foobar", "http://www.foobar.com/" }, + { "domain", "http://www.domain.com/http/" }, + { "repeat", "http://www.repeat.com/1/repeat/2/" }, // For testing inline_autocompletion. { "http://blah.com/", "http://blah.com/" }, { "http://fiddle.com/", "http://fiddle.com/" }, @@ -61,7 +64,7 @@ struct BookmarksTestInfo { class BookmarkProviderTest : public testing::Test, public AutocompleteProviderListener { public: - BookmarkProviderTest() : model_(new BookmarkModel(NULL)) {} + BookmarkProviderTest(); // AutocompleteProviderListener: Not called. virtual void OnProviderUpdate(bool updated_matches) OVERRIDE {} @@ -77,6 +80,10 @@ class BookmarkProviderTest : public testing::Test, DISALLOW_COPY_AND_ASSIGN(BookmarkProviderTest); }; +BookmarkProviderTest::BookmarkProviderTest() { + model_.reset(new BookmarkModel(NULL, false)); +} + void BookmarkProviderTest::SetUp() { profile_.reset(new TestingProfile()); DCHECK(profile_.get()); @@ -387,13 +394,69 @@ TEST_F(BookmarkProviderTest, InlineAutocompletion) { provider_->FixupUserInput(&fixed_up_input); BookmarkNode node(GURL(query_data[i].url)); node.SetTitle(base::ASCIIToUTF16(query_data[i].url)); - BookmarkTitleMatch bookmark_match; + BookmarkMatch bookmark_match; bookmark_match.node = &node; - const AutocompleteMatch& ac_match = - provider_->TitleMatchToACMatch(input, fixed_up_input, bookmark_match); + const AutocompleteMatch& ac_match = provider_->BookmarkMatchToACMatch( + input, fixed_up_input, bookmark_match); EXPECT_EQ(query_data[i].allowed_to_be_default_match, ac_match.allowed_to_be_default_match) << description; EXPECT_EQ(base::ASCIIToUTF16(query_data[i].inline_autocompletion), ac_match.inline_autocompletion) << description; } } + +TEST_F(BookmarkProviderTest, StripHttpAndAdjustOffsets) { + // Simulate searches. + struct QueryData { + const std::string query; + const std::string expected_contents; + // |expected_contents_class| is in format offset:style,offset:style,... + const std::string expected_contents_class; + } query_data[] = { + { "foo", "www.foobar.com", "0:1,4:3,7:1" }, + { "www foo", "www.foobar.com", "0:3,3:1,4:3,7:1" }, + { "foo www", "www.foobar.com", "0:3,3:1,4:3,7:1" }, + { "foo http", "http://www.foobar.com", "0:3,4:1,11:3,14:1" }, + { "blah", "blah.com", "0:3,4:1" }, + { "http blah", "http://blah.com", "0:3,4:1,7:3,11:1" }, + { "dom", "www.domain.com/http/", "0:1,4:3,7:1" }, + { "dom http", "http://www.domain.com/http/", + "0:3,4:1,11:3,14:1,22:3,26:1" }, + { "rep", "www.repeat.com/1/repeat/2/", "0:1,4:3,7:1,17:3,20:1" }, + { "versi", "chrome://version", "0:1,9:3,14:1" } + }; + + // Reload the bookmarks index with |index_urls| == true. + model_.reset(new BookmarkModel(NULL, true)); + SetUp(); + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(query_data); ++i) { + std::string description = "for query=" + query_data[i].query; + AutocompleteInput input(base::ASCIIToUTF16(query_data[i].query), + base::string16::npos, base::string16(), GURL(), + AutocompleteInput::INVALID_SPEC, false, false, + false, true); + provider_->Start(input, false); + const ACMatches& matches(provider_->matches()); + ASSERT_EQ(1U, matches.size()) << description; + const AutocompleteMatch& match = matches[0]; + EXPECT_EQ(base::ASCIIToUTF16(query_data[i].expected_contents), + match.contents) << description; + std::vector<std::string> class_strings; + base::SplitString( + query_data[i].expected_contents_class, ',', &class_strings); + ASSERT_EQ(class_strings.size(), match.contents_class.size()) + << description; + for (size_t i = 0; i < class_strings.size(); ++i) { + std::vector<std::string> chunks; + base::SplitString(class_strings[i], ':', &chunks); + ASSERT_EQ(2U, chunks.size()) << description; + size_t offset; + EXPECT_TRUE(base::StringToSizeT(chunks[0], &offset)) << description; + EXPECT_EQ(offset, match.contents_class[i].offset) << description; + int style; + EXPECT_TRUE(base::StringToInt(chunks[1], &style)) << description; + EXPECT_EQ(style, match.contents_class[i].style) << description; + } + } +} diff --git a/chrome/browser/autocomplete/history_url_provider.cc b/chrome/browser/autocomplete/history_url_provider.cc index df2d4cd..afe6422 100644 --- a/chrome/browser/autocomplete/history_url_provider.cc +++ b/chrome/browser/autocomplete/history_url_provider.cc @@ -18,6 +18,7 @@ #include "chrome/browser/autocomplete/autocomplete_match.h" #include "chrome/browser/autocomplete/autocomplete_provider_listener.h" #include "chrome/browser/autocomplete/autocomplete_result.h" +#include "chrome/browser/bookmarks/bookmark_utils.h" #include "chrome/browser/history/history_backend.h" #include "chrome/browser/history/history_database.h" #include "chrome/browser/history/history_service.h" @@ -1149,7 +1150,7 @@ int HistoryURLProvider::CalculateRelevanceScoreUsingScoringParams( ACMatchClassifications HistoryURLProvider::ClassifyDescription( const base::string16& input_text, const base::string16& description) { - base::string16 clean_description = history::CleanUpTitleForMatching( + base::string16 clean_description = bookmark_utils::CleanUpTitleForMatching( description); history::TermMatches description_matches(SortAndDeoverlapMatches( history::MatchTermInString(input_text, clean_description, 0))); diff --git a/chrome/browser/autocomplete/shortcuts_provider.cc b/chrome/browser/autocomplete/shortcuts_provider.cc index 98bf327..7d46bb6 100644 --- a/chrome/browser/autocomplete/shortcuts_provider.cc +++ b/chrome/browser/autocomplete/shortcuts_provider.cc @@ -218,11 +218,11 @@ AutocompleteMatch ShortcutsProvider::ShortcutToACMatch( // If the match is a search query this is easy: simply check whether the // user text is a prefix of the query. If the match is a navigation, we // assume the fill_into_edit looks something like a URL, so we use - // URLPrefix::ComputeMatchStartAndInlineAutocompleteOffset() to try and strip - // off any prefixes that the user might not think would change the meaning, - // but would otherwise prevent inline autocompletion. This allows, for - // example, the input of "foo.c" to autocomplete to "foo.com" for a - // fill_into_edit of "http://foo.com". + // URLPrefix::GetInlineAutocompleteOffset() to try and strip off any prefixes + // that the user might not think would change the meaning, but would + // otherwise prevent inline autocompletion. This allows, for example, the + // input of "foo.c" to autocomplete to "foo.com" for a fill_into_edit of + // "http://foo.com". if (AutocompleteMatch::IsSearchType(match.type)) { if (StartsWith(match.fill_into_edit, input.text(), false)) { match.inline_autocompletion = @@ -232,10 +232,9 @@ AutocompleteMatch ShortcutsProvider::ShortcutToACMatch( match.inline_autocompletion.empty(); } } else { - size_t match_start, inline_autocomplete_offset; - URLPrefix::ComputeMatchStartAndInlineAutocompleteOffset( - input, fixed_up_input, true, match.fill_into_edit, - &match_start, &inline_autocomplete_offset); + const size_t inline_autocomplete_offset = + URLPrefix::GetInlineAutocompleteOffset( + input, fixed_up_input, true, match.fill_into_edit); if (inline_autocomplete_offset != base::string16::npos) { match.inline_autocompletion = match.fill_into_edit.substr(inline_autocomplete_offset); diff --git a/chrome/browser/autocomplete/url_prefix.cc b/chrome/browser/autocomplete/url_prefix.cc index 1d680db..8a49f2c 100644 --- a/chrome/browser/autocomplete/url_prefix.cc +++ b/chrome/browser/autocomplete/url_prefix.cc @@ -77,13 +77,11 @@ bool URLPrefix::PrefixMatch(const URLPrefix& prefix, } // static -void URLPrefix::ComputeMatchStartAndInlineAutocompleteOffset( +size_t URLPrefix::GetInlineAutocompleteOffset( const AutocompleteInput& input, const AutocompleteInput& fixed_up_input, const bool allow_www_prefix_without_scheme, - const base::string16& text, - size_t* match_start, - size_t* inline_autocomplete_offset) { + const base::string16& text) { const URLPrefix* best_prefix = allow_www_prefix_without_scheme ? BestURLPrefixWithWWWCase(text, input.text()) : BestURLPrefix(text, input.text()); @@ -99,12 +97,7 @@ void URLPrefix::ComputeMatchStartAndInlineAutocompleteOffset( BestURLPrefix(text, fixed_up_input.text()); matching_string = &fixed_up_input.text(); } - if (best_prefix != NULL) { - *match_start = best_prefix->prefix.length(); - *inline_autocomplete_offset = - best_prefix->prefix.length() + matching_string->length(); - } else { - *match_start = base::string16::npos; - *inline_autocomplete_offset = base::string16::npos; - } + return (best_prefix != NULL) ? + (best_prefix->prefix.length() + matching_string->length()) : + base::string16::npos; } diff --git a/chrome/browser/autocomplete/url_prefix.h b/chrome/browser/autocomplete/url_prefix.h index f5494cb..b7728b0 100644 --- a/chrome/browser/autocomplete/url_prefix.h +++ b/chrome/browser/autocomplete/url_prefix.h @@ -39,20 +39,19 @@ struct URLPrefix { const base::string16& prefix_suffix); // Sees if |text| is inlineable against either |input| or |fixed_up_input|, - // filling in |match_start| and |inline_autocomplete_offset| appropriately. + // returning the appropriate inline autocomplete offset or + // base::string16::npos if |text| is not inlineable. // |allow_www_prefix_without_scheme| says whether to consider an input such // as "foo" to be allowed to match against text "www.foo.com". This is // needed because sometimes the string we're matching against here can come // from a match's fill_into_edit, which can start with "www." without having // a protocol at the beginning, and we want to allow these matches to be // inlineable. ("www." is not otherwise on the default prefix list.) - static void ComputeMatchStartAndInlineAutocompleteOffset( + static size_t GetInlineAutocompleteOffset( const AutocompleteInput& input, const AutocompleteInput& fixed_up_input, const bool allow_www_prefix_without_scheme, - const base::string16& text, - size_t* match_start, - size_t* inline_autocomplete_offset); + const base::string16& text); base::string16 prefix; diff --git a/chrome/browser/bookmarks/DEPS b/chrome/browser/bookmarks/DEPS index a702b0e..e7b25b7 100644 --- a/chrome/browser/bookmarks/DEPS +++ b/chrome/browser/bookmarks/DEPS @@ -17,6 +17,7 @@ include_rules = [ "!chrome/browser/history/history_service_factory.h", "!chrome/browser/history/query_parser.h", "!chrome/browser/history/url_database.h", + "!chrome/browser/omnibox/omnibox_field_trial.h", "!chrome/browser/profiles/incognito_helpers.h", "!chrome/browser/profiles/profile.h", "!chrome/browser/profiles/startup_task_runner_service.h", diff --git a/chrome/browser/bookmarks/bookmark_codec_unittest.cc b/chrome/browser/bookmarks/bookmark_codec_unittest.cc index 4735779..01da754 100644 --- a/chrome/browser/bookmarks/bookmark_codec_unittest.cc +++ b/chrome/browser/bookmarks/bookmark_codec_unittest.cc @@ -73,20 +73,20 @@ class BookmarkCodecTest : public testing::Test { protected: // Helpers to create bookmark models with different data. BookmarkModel* CreateTestModel1() { - scoped_ptr<BookmarkModel> model(new BookmarkModel(NULL)); + scoped_ptr<BookmarkModel> model(new BookmarkModel(NULL, false)); const BookmarkNode* bookmark_bar = model->bookmark_bar_node(); model->AddURL(bookmark_bar, 0, ASCIIToUTF16(kUrl1Title), GURL(kUrl1Url)); return model.release(); } BookmarkModel* CreateTestModel2() { - scoped_ptr<BookmarkModel> model(new BookmarkModel(NULL)); + scoped_ptr<BookmarkModel> model(new BookmarkModel(NULL, false)); const BookmarkNode* bookmark_bar = model->bookmark_bar_node(); model->AddURL(bookmark_bar, 0, ASCIIToUTF16(kUrl1Title), GURL(kUrl1Url)); model->AddURL(bookmark_bar, 1, ASCIIToUTF16(kUrl2Title), GURL(kUrl2Url)); return model.release(); } BookmarkModel* CreateTestModel3() { - scoped_ptr<BookmarkModel> model(new BookmarkModel(NULL)); + scoped_ptr<BookmarkModel> model(new BookmarkModel(NULL, false)); const BookmarkNode* bookmark_bar = model->bookmark_bar_node(); model->AddURL(bookmark_bar, 0, ASCIIToUTF16(kUrl1Title), GURL(kUrl1Url)); const BookmarkNode* folder1 = model->AddFolder(bookmark_bar, 1, @@ -173,7 +173,7 @@ class BookmarkCodecTest : public testing::Test { EXPECT_EQ("", decoder.computed_checksum()); EXPECT_EQ("", decoder.stored_checksum()); - scoped_ptr<BookmarkModel> model(new BookmarkModel(NULL)); + scoped_ptr<BookmarkModel> model(new BookmarkModel(NULL, false)); EXPECT_TRUE(Decode(&decoder, model.get(), value)); *computed_checksum = decoder.computed_checksum(); @@ -311,7 +311,7 @@ TEST_F(BookmarkCodecTest, PersistIDsTest) { BookmarkCodec encoder; scoped_ptr<base::Value> model_value(encoder.Encode(model_to_encode.get())); - BookmarkModel decoded_model(NULL); + BookmarkModel decoded_model(NULL, false); BookmarkCodec decoder; ASSERT_TRUE(Decode(&decoder, &decoded_model, *model_value.get())); ASSERT_NO_FATAL_FAILURE( @@ -331,7 +331,7 @@ TEST_F(BookmarkCodecTest, PersistIDsTest) { BookmarkCodec encoder2; scoped_ptr<base::Value> model_value2(encoder2.Encode(&decoded_model)); - BookmarkModel decoded_model2(NULL); + BookmarkModel decoded_model2(NULL, false); BookmarkCodec decoder2; ASSERT_TRUE(Decode(&decoder2, &decoded_model2, *model_value2.get())); ASSERT_NO_FATAL_FAILURE(AssertModelsEqual(&decoded_model, &decoded_model2)); @@ -347,7 +347,7 @@ TEST_F(BookmarkCodecTest, CanDecodeModelWithoutMobileBookmarks) { JSONFileValueSerializer serializer(test_file); scoped_ptr<base::Value> root(serializer.Deserialize(NULL, NULL)); - BookmarkModel decoded_model(NULL); + BookmarkModel decoded_model(NULL, false); BookmarkCodec decoder; ASSERT_TRUE(Decode(&decoder, &decoded_model, *root.get())); ExpectIDsUnique(&decoded_model); @@ -435,7 +435,7 @@ TEST_F(BookmarkCodecTest, CanDecodeMetaInfoAsString) { JSONFileValueSerializer serializer(test_file); scoped_ptr<base::Value> root(serializer.Deserialize(NULL, NULL)); - BookmarkModel model(NULL); + BookmarkModel model(NULL, false); BookmarkCodec decoder; ASSERT_TRUE(Decode(&decoder, &model, *root.get())); diff --git a/chrome/browser/bookmarks/bookmark_index.cc b/chrome/browser/bookmarks/bookmark_index.cc index feb2384..77c22d9 100644 --- a/chrome/browser/bookmarks/bookmark_index.cc +++ b/chrome/browser/bookmarks/bookmark_index.cc @@ -11,11 +11,13 @@ #include "base/i18n/case_conversion.h" #include "base/strings/string16.h" #include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/browser/bookmarks/bookmark_utils.h" #include "chrome/browser/history/history_service.h" #include "chrome/browser/history/history_service_factory.h" #include "chrome/browser/history/url_database.h" -#include "components/bookmarks/core/browser/bookmark_title_match.h" +#include "components/bookmarks/core/browser/bookmark_match.h" #include "components/query_parser/query_parser.h" +#include "components/query_parser/snippet.h" #include "third_party/icu/source/common/unicode/normalizer2.h" namespace { @@ -71,8 +73,12 @@ BookmarkIndex::NodeSet::const_iterator BookmarkIndex::Match::nodes_end() const { return nodes.empty() ? terms.front()->second.end() : nodes.end(); } -BookmarkIndex::BookmarkIndex(content::BrowserContext* browser_context) - : browser_context_(browser_context) { +BookmarkIndex::BookmarkIndex(content::BrowserContext* browser_context, + bool index_urls, + const std::string& languages) + : index_urls_(index_urls), + browser_context_(browser_context), + languages_(languages) { } BookmarkIndex::~BookmarkIndex() { @@ -85,6 +91,12 @@ void BookmarkIndex::Add(const BookmarkNode* node) { ExtractQueryWords(Normalize(node->GetTitle())); for (size_t i = 0; i < terms.size(); ++i) RegisterNode(terms[i], node); + if (index_urls_) { + terms = ExtractQueryWords( + bookmark_utils::CleanUpUrlForMatching(node->url(), languages_)); + for (size_t i = 0; i < terms.size(); ++i) + RegisterNode(terms[i], node); + } } void BookmarkIndex::Remove(const BookmarkNode* node) { @@ -95,12 +107,17 @@ void BookmarkIndex::Remove(const BookmarkNode* node) { ExtractQueryWords(Normalize(node->GetTitle())); for (size_t i = 0; i < terms.size(); ++i) UnregisterNode(terms[i], node); + if (index_urls_) { + terms = ExtractQueryWords( + bookmark_utils::CleanUpUrlForMatching(node->url(), languages_)); + for (size_t i = 0; i < terms.size(); ++i) + UnregisterNode(terms[i], node); + } } -void BookmarkIndex::GetBookmarksWithTitlesMatching( - const base::string16& input_query, - size_t max_count, - std::vector<BookmarkTitleMatch>* results) { +void BookmarkIndex::GetBookmarksMatching(const base::string16& input_query, + size_t max_count, + std::vector<BookmarkMatch>* results) { const base::string16 query = Normalize(input_query); std::vector<base::string16> terms = ExtractQueryWords(query); if (terms.empty()) @@ -108,7 +125,7 @@ void BookmarkIndex::GetBookmarksWithTitlesMatching( Matches matches; for (size_t i = 0; i < terms.size(); ++i) { - if (!GetBookmarksWithTitleMatchingTerm(terms[i], i == 0, &matches)) + if (!GetBookmarksMatchingTerm(terms[i], i == 0, &matches)) return; } @@ -177,22 +194,48 @@ void BookmarkIndex::ExtractBookmarkNodePairs( void BookmarkIndex::AddMatchToResults( const BookmarkNode* node, query_parser::QueryParser* parser, - const std::vector<query_parser::QueryNode*>& query_nodes, - std::vector<BookmarkTitleMatch>* results) { - BookmarkTitleMatch title_match; + const query_parser::QueryNodeStarVector& query_nodes, + std::vector<BookmarkMatch>* results) { // Check that the result matches the query. The previous search // was a simple per-word search, while the more complex matching // of QueryParser may filter it out. For example, the query // ["thi"] will match the bookmark titled [Thinking], but since // ["thi"] is quoted we don't want to do a prefix match. - if (parser->DoesQueryMatch(Normalize(node->GetTitle()), query_nodes, - &(title_match.match_positions))) { - title_match.node = node; - results->push_back(title_match); + query_parser::QueryWordVector title_words, url_words; + const base::string16 lower_title = + base::i18n::ToLower(Normalize(node->GetTitle())); + parser->ExtractQueryWords(lower_title, &title_words); + if (index_urls_) { + parser->ExtractQueryWords( + bookmark_utils::CleanUpUrlForMatching(node->url(), languages_), + &url_words); + } + query_parser::Snippet::MatchPositions title_matches, url_matches; + for (size_t i = 0; i < query_nodes.size(); ++i) { + const bool has_title_matches = + query_nodes[i]->HasMatchIn(title_words, &title_matches); + const bool has_url_matches = index_urls_ && + query_nodes[i]->HasMatchIn(url_words, &url_matches); + if (!has_title_matches && !has_url_matches) + return; + query_parser::QueryParser::SortAndCoalesceMatchPositions(&title_matches); + if (index_urls_) + query_parser::QueryParser::SortAndCoalesceMatchPositions(&url_matches); + } + BookmarkMatch match; + if (lower_title.length() == node->GetTitle().length()) { + // Only use title matches if the lowercase string is the same length + // as the original string, otherwise the matches are meaningless. + // TODO(mpearson): revise match positions appropriately. + match.title_match_positions.swap(title_matches); } + if (index_urls_) + match.url_match_positions.swap(url_matches); + match.node = node; + results->push_back(match); } -bool BookmarkIndex::GetBookmarksWithTitleMatchingTerm(const base::string16& term, +bool BookmarkIndex::GetBookmarksMatchingTerm(const base::string16& term, bool first_term, Matches* matches) { Index::const_iterator i = index_.lower_bound(term); diff --git a/chrome/browser/bookmarks/bookmark_index.h b/chrome/browser/bookmarks/bookmark_index.h index 4027e14d..93ce5f1 100644 --- a/chrome/browser/bookmarks/bookmark_index.h +++ b/chrome/browser/bookmarks/bookmark_index.h @@ -11,9 +11,10 @@ #include "base/basictypes.h" #include "base/strings/string16.h" +#include "components/query_parser/query_parser.h" class BookmarkNode; -struct BookmarkTitleMatch; +struct BookmarkMatch; namespace content { class BrowserContext; @@ -23,21 +24,21 @@ namespace history { class URLDatabase; } -namespace query_parser { -class QueryNode; -class QueryParser; -} - -// BookmarkIndex maintains an index of the titles of bookmarks for quick -// look up. BookmarkIndex is owned and maintained by BookmarkModel, you +// BookmarkIndex maintains an index of the titles and URLs of bookmarks for +// quick look up. BookmarkIndex is owned and maintained by BookmarkModel, you // shouldn't need to interact directly with BookmarkIndex. // // BookmarkIndex maintains the index (index_) as a map of sets. The map (type // Index) maps from a lower case string to the set (type NodeSet) of -// BookmarkNodes that contain that string in their title. +// BookmarkNodes that contain that string in their title or URL. class BookmarkIndex { public: - explicit BookmarkIndex(content::BrowserContext* browser_context); + // |index_urls| says whether URLs should be stored in the index in addition + // to bookmark titles. |languages| used to help parse IDNs in URLs for the + // bookmark index. + BookmarkIndex(content::BrowserContext* browser_context, + bool index_urls, + const std::string& languages); ~BookmarkIndex(); // Invoked when a bookmark has been added to the model. @@ -46,11 +47,12 @@ class BookmarkIndex { // Invoked when a bookmark has been removed from the model. void Remove(const BookmarkNode* node); - // Returns up to |max_count| of bookmarks containing the text |query|. - void GetBookmarksWithTitlesMatching( + // Returns up to |max_count| of bookmarks containing each term from + // the text |query| in either the title or the URL. + void GetBookmarksMatching( const base::string16& query, size_t max_count, - std::vector<BookmarkTitleMatch>* results); + std::vector<BookmarkMatch>* results); private: typedef std::set<const BookmarkNode*> NodeSet; @@ -88,21 +90,21 @@ class BookmarkIndex { void AddMatchToResults( const BookmarkNode* node, query_parser::QueryParser* parser, - const std::vector<query_parser::QueryNode*>& query_nodes, - std::vector<BookmarkTitleMatch>* results); + const query_parser::QueryNodeStarVector& query_nodes, + std::vector<BookmarkMatch>* results); // Populates |matches| for the specified term. If |first_term| is true, this // is the first term in the query. Returns true if there is at least one node // matching the term. - bool GetBookmarksWithTitleMatchingTerm(const base::string16& term, - bool first_term, - Matches* matches); + bool GetBookmarksMatchingTerm(const base::string16& term, + bool first_term, + Matches* matches); // Iterates over |matches| updating each Match's nodes to contain the // intersection of the Match's current nodes and the nodes at |index_i|. // If the intersection is empty, the Match is removed. // - // This is invoked from GetBookmarksWithTitleMatchingTerm. + // This is invoked from GetBookmarksMatchingTerm. void CombineMatchesInPlace(const Index::const_iterator& index_i, Matches* matches); @@ -114,7 +116,7 @@ class BookmarkIndex { // non-empty the result is added to result, not combined in place. This // variant is used for prefix matching. // - // This is invoked from GetBookmarksWithTitleMatchingTerm. + // This is invoked from GetBookmarksMatchingTerm. void CombineMatches(const Index::const_iterator& index_i, const Matches& current_matches, Matches* result); @@ -130,8 +132,14 @@ class BookmarkIndex { Index index_; + // True if URLs are stored in the index as well as bookmark titles. + const bool index_urls_; + content::BrowserContext* browser_context_; + // Languages used to help parse IDNs in URLs for the bookmark index. + const std::string languages_; + DISALLOW_COPY_AND_ASSIGN(BookmarkIndex); }; diff --git a/chrome/browser/bookmarks/bookmark_index_unittest.cc b/chrome/browser/bookmarks/bookmark_index_unittest.cc index 057e608..557e1a2 100644 --- a/chrome/browser/bookmarks/bookmark_index_unittest.cc +++ b/chrome/browser/bookmarks/bookmark_index_unittest.cc @@ -19,7 +19,7 @@ #include "chrome/browser/history/history_service_factory.h" #include "chrome/browser/history/url_database.h" #include "chrome/test/base/testing_profile.h" -#include "components/bookmarks/core/browser/bookmark_title_match.h" +#include "components/bookmarks/core/browser/bookmark_match.h" #include "content/public/test/test_browser_thread_bundle.h" #include "testing/gtest/include/gtest/gtest.h" @@ -27,20 +27,27 @@ using base::ASCIIToUTF16; class BookmarkIndexTest : public testing::Test { public: - BookmarkIndexTest() : model_(new BookmarkModel(NULL)) {} + BookmarkIndexTest() : model_(new BookmarkModel(NULL, false)) { + } - void AddBookmarksWithTitles(const char** titles, size_t count) { - std::vector<std::string> title_vector; - for (size_t i = 0; i < count; ++i) - title_vector.push_back(titles[i]); - AddBookmarksWithTitles(title_vector); + typedef std::pair<std::string, std::string> TitleAndURL; + + void AddBookmarks(const char** titles, const char** urls, size_t count) { + // The pair is (title, url). + std::vector<TitleAndURL> bookmarks; + for (size_t i = 0; i < count; ++i) { + TitleAndURL bookmark(titles[i], urls[i]); + bookmarks.push_back(bookmark); + } + AddBookmarks(bookmarks); } - void AddBookmarksWithTitles(const std::vector<std::string>& titles) { - GURL url("about:blank"); - for (size_t i = 0; i < titles.size(); ++i) + void AddBookmarks(const std::vector<TitleAndURL> bookmarks) { + for (size_t i = 0; i < bookmarks.size(); ++i) { model_->AddURL(model_->other_node(), static_cast<int>(i), - ASCIIToUTF16(titles[i]), url); + ASCIIToUTF16(bookmarks[i].first), + GURL(bookmarks[i].second)); + } } void ExpectMatches(const std::string& query, @@ -54,8 +61,8 @@ class BookmarkIndexTest : public testing::Test { void ExpectMatches(const std::string& query, const std::vector<std::string>& expected_titles) { - std::vector<BookmarkTitleMatch> matches; - model_->GetBookmarksWithTitlesMatching(ASCIIToUTF16(query), 1000, &matches); + std::vector<BookmarkMatch> matches; + model_->GetBookmarksMatching(ASCIIToUTF16(query), 1000, &matches); ASSERT_EQ(expected_titles.size(), matches.size()); for (size_t i = 0; i < expected_titles.size(); ++i) { bool found = false; @@ -71,14 +78,14 @@ class BookmarkIndexTest : public testing::Test { } void ExtractMatchPositions(const std::string& string, - BookmarkTitleMatch::MatchPositions* matches) { + BookmarkMatch::MatchPositions* matches) { std::vector<std::string> match_strings; base::SplitString(string, ':', &match_strings); for (size_t i = 0; i < match_strings.size(); ++i) { std::vector<std::string> chunks; base::SplitString(match_strings[i], ',', &chunks); ASSERT_EQ(2U, chunks.size()); - matches->push_back(BookmarkTitleMatch::MatchPosition()); + matches->push_back(BookmarkMatch::MatchPosition()); int chunks0, chunks1; base::StringToInt(chunks[0], &chunks0); base::StringToInt(chunks[1], &chunks1); @@ -88,16 +95,12 @@ class BookmarkIndexTest : public testing::Test { } void ExpectMatchPositions( - const std::string& query, - const BookmarkTitleMatch::MatchPositions& expected_positions) { - std::vector<BookmarkTitleMatch> matches; - model_->GetBookmarksWithTitlesMatching(ASCIIToUTF16(query), 1000, &matches); - ASSERT_EQ(1U, matches.size()); - const BookmarkTitleMatch& match = matches[0]; - ASSERT_EQ(expected_positions.size(), match.match_positions.size()); + const BookmarkMatch::MatchPositions& actual_positions, + const BookmarkMatch::MatchPositions& expected_positions) { + ASSERT_EQ(expected_positions.size(), actual_positions.size()); for (size_t i = 0; i < expected_positions.size(); ++i) { - EXPECT_EQ(expected_positions[i].first, match.match_positions[i].first); - EXPECT_EQ(expected_positions[i].second, match.match_positions[i].second); + EXPECT_EQ(expected_positions[i].first, actual_positions[i].first); + EXPECT_EQ(expected_positions[i].second, actual_positions[i].second); } } @@ -110,9 +113,9 @@ class BookmarkIndexTest : public testing::Test { // Various permutations with differing input, queries and output that exercises // all query paths. -TEST_F(BookmarkIndexTest, Tests) { +TEST_F(BookmarkIndexTest, GetBookmarksMatching) { struct TestData { - const std::string input; + const std::string titles; const std::string query; const std::string expected; } data[] = { @@ -144,8 +147,13 @@ TEST_F(BookmarkIndexTest, Tests) { }; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) { std::vector<std::string> titles; - base::SplitString(data[i].input, ';', &titles); - AddBookmarksWithTitles(titles); + base::SplitString(data[i].titles, ';', &titles); + std::vector<TitleAndURL> bookmarks; + for (size_t j = 0; j < titles.size(); ++j) { + TitleAndURL bookmark(titles[j], "about:blank"); + bookmarks.push_back(bookmark); + } + AddBookmarks(bookmarks); std::vector<std::string> expected; if (!data[i].expected.empty()) @@ -153,11 +161,69 @@ TEST_F(BookmarkIndexTest, Tests) { ExpectMatches(data[i].query, expected); - model_.reset(new BookmarkModel(NULL)); + model_.reset(new BookmarkModel(NULL, false)); + } +} + +// Analogous to GetBookmarksMatching, this test tests various permutations +// of title, URL, and input to see if the title/URL matches the input as +// expected. +TEST_F(BookmarkIndexTest, GetBookmarksMatchingWithURLs) { + struct TestData { + const std::string query; + const std::string title; + const std::string url; + const bool should_be_retrieved; + } data[] = { + // Test single-word inputs. Include both exact matches and prefix matches. + { "foo", "Foo", "http://www.bar.com/", true }, + { "foo", "Foodie", "http://www.bar.com/", true }, + { "foo", "Bar", "http://www.foo.com/", true }, + { "foo", "Bar", "http://www.foodie.com/", true }, + { "foo", "Foo", "http://www.foo.com/", true }, + { "foo", "Bar", "http://www.bar.com/", false }, + { "foo", "Bar", "http://www.bar.com/blah/foo/blah-again/ ", true }, + { "foo", "Bar", "http://www.bar.com/blah/foodie/blah-again/ ", true }, + { "foo", "Bar", "http://www.bar.com/blah-foo/blah-again/ ", true }, + { "foo", "Bar", "http://www.bar.com/blah-foodie/blah-again/ ", true }, + { "foo", "Bar", "http://www.bar.com/blahafoo/blah-again/ ", false }, + + // Test multi-word inputs. + { "foo bar", "Foo Bar", "http://baz.com/", true }, + { "foo bar", "Foodie Bar", "http://baz.com/", true }, + { "bar foo", "Foo Bar", "http://baz.com/", true }, + { "bar foo", "Foodie Barly", "http://baz.com/", true }, + { "foo bar", "Foo Baz", "http://baz.com/", false }, + { "foo bar", "Foo Baz", "http://bar.com/", true }, + { "foo bar", "Foo Baz", "http://barly.com/", true }, + { "foo bar", "Foodie Baz", "http://barly.com/", true }, + { "bar foo", "Foo Baz", "http://bar.com/", true }, + { "bar foo", "Foo Baz", "http://barly.com/", true }, + { "foo bar", "Baz Bar", "http://blah.com/foo", true }, + { "foo bar", "Baz Barly", "http://blah.com/foodie", true }, + { "foo bar", "Baz Bur", "http://blah.com/foo/bar", true }, + { "foo bar", "Baz Bur", "http://blah.com/food/barly", true }, + { "foo bar", "Baz Bur", "http://bar.com/blah/foo", true }, + { "foo bar", "Baz Bur", "http://barly.com/blah/food", true }, + { "foo bar", "Baz Bur", "http://bar.com/blah/flub", false }, + { "foo bar", "Baz Bur", "http://foo.com/blah/flub", false } + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) { + model_.reset(new BookmarkModel(NULL, true)); + std::vector<TitleAndURL> bookmarks; + bookmarks.push_back(TitleAndURL(data[i].title, data[i].url)); + AddBookmarks(bookmarks); + + std::vector<std::string> expected; + if (data[i].should_be_retrieved) + expected.push_back(data[i].title); + + ExpectMatches(data[i].query, expected); } } -TEST_F(BookmarkIndexTest, TestNormalization) { +TEST_F(BookmarkIndexTest, Normalization) { struct TestData { const char* title; const char* query; @@ -179,25 +245,25 @@ TEST_F(BookmarkIndexTest, TestNormalization) { for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) { model_->AddURL(model_->other_node(), 0, base::UTF8ToUTF16(data[i].title), url); - std::vector<BookmarkTitleMatch> matches; - model_->GetBookmarksWithTitlesMatching( + std::vector<BookmarkMatch> matches; + model_->GetBookmarksMatching( base::UTF8ToUTF16(data[i].query), 10, &matches); EXPECT_EQ(1u, matches.size()); - model_.reset(new BookmarkModel(NULL)); + model_.reset(new BookmarkModel(NULL, false)); } } -// Makes sure match positions are updated appropriately. -TEST_F(BookmarkIndexTest, MatchPositions) { +// Makes sure match positions are updated appropriately for title matches. +TEST_F(BookmarkIndexTest, MatchPositionsTitles) { struct TestData { const std::string title; const std::string query; - const std::string expected; + const std::string expected_title_match_positions; } data[] = { // Trivial test case of only one term, exact match. { "a", "A", "0,1" }, { "foo bar", "bar", "4,7" }, - { "fooey bark", "bar foo", "0,3:6,9"}, + { "fooey bark", "bar foo", "0,3:6,9" }, // Non-trivial tests. { "foobar foo", "foobar foo", "0,6:7,10" }, { "foobar foo", "foo foobar", "0,6:7,10" }, @@ -205,22 +271,70 @@ TEST_F(BookmarkIndexTest, MatchPositions) { { "foobar foobar", "foo foobar", "0,6:7,13" }, }; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) { - std::vector<std::string> titles; - titles.push_back(data[i].title); - AddBookmarksWithTitles(titles); + std::vector<TitleAndURL> bookmarks; + TitleAndURL bookmark(data[i].title, "about:blank"); + bookmarks.push_back(bookmark); + AddBookmarks(bookmarks); + + std::vector<BookmarkMatch> matches; + model_->GetBookmarksMatching(ASCIIToUTF16(data[i].query), 1000, &matches); + ASSERT_EQ(1U, matches.size()); - BookmarkTitleMatch::MatchPositions expected_matches; - ExtractMatchPositions(data[i].expected, &expected_matches); - ExpectMatchPositions(data[i].query, expected_matches); + BookmarkMatch::MatchPositions expected_title_matches; + ExtractMatchPositions(data[i].expected_title_match_positions, + &expected_title_matches); + ExpectMatchPositions(matches[0].title_match_positions, + expected_title_matches); - model_.reset(new BookmarkModel(NULL)); + model_.reset(new BookmarkModel(NULL, false)); + } +} + +// Makes sure match positions are updated appropriately for URL matches. +TEST_F(BookmarkIndexTest, MatchPositionsURLs) { + struct TestData { + const std::string query; + const std::string url; + const std::string expected_url_match_positions; + } data[] = { + { "foo", "http://www.foo.com/", "11,14" }, + { "foo", "http://www.foodie.com/", "11,14" }, + { "foo", "http://www.foofoo.com/", "11,14" }, + { "www", "http://www.foo.com/", "7,10" }, + { "foo", "http://www.foodie.com/blah/foo/fi", "11,14:27,30" }, + { "foo", "http://www.blah.com/blah/foo/fi", "25,28" }, + { "foo www", "http://www.foodie.com/blah/foo/fi", "7,10:11,14:27,30" }, + { "www foo", "http://www.foodie.com/blah/foo/fi", "7,10:11,14:27,30" }, + { "www bla", "http://www.foodie.com/blah/foo/fi", "7,10:22,25" }, + { "http", "http://www.foo.com/", "0,4" }, + { "http www", "http://www.foo.com/", "0,4:7,10" }, + { "http foo", "http://www.foo.com/", "0,4:11,14" }, + { "http foo", "http://www.bar.com/baz/foodie/hi", "0,4:23,26" } + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(data); ++i) { + model_.reset(new BookmarkModel(NULL, true)); + std::vector<TitleAndURL> bookmarks; + TitleAndURL bookmark("123456", data[i].url); + bookmarks.push_back(bookmark); + AddBookmarks(bookmarks); + + std::vector<BookmarkMatch> matches; + model_->GetBookmarksMatching(ASCIIToUTF16(data[i].query), 1000, &matches); + ASSERT_EQ(1U, matches.size()) << data[i].url << data[i].query; + + BookmarkMatch::MatchPositions expected_url_matches; + ExtractMatchPositions(data[i].expected_url_match_positions, + &expected_url_matches); + ExpectMatchPositions(matches[0].url_match_positions, expected_url_matches); } } // Makes sure index is updated when a node is removed. TEST_F(BookmarkIndexTest, Remove) { - const char* input[] = { "a", "b" }; - AddBookmarksWithTitles(input, ARRAYSIZE_UNSAFE(input)); + const char* titles[] = { "a", "b" }; + const char* urls[] = { "about:blank", "about:blank" }; + AddBookmarks(titles, urls, ARRAYSIZE_UNSAFE(titles)); // Remove the node and make sure we don't get back any results. model_->Remove(model_->other_node(), 0); @@ -229,8 +343,9 @@ TEST_F(BookmarkIndexTest, Remove) { // Makes sure index is updated when a node's title is changed. TEST_F(BookmarkIndexTest, ChangeTitle) { - const char* input[] = { "a", "b" }; - AddBookmarksWithTitles(input, ARRAYSIZE_UNSAFE(input)); + const char* titles[] = { "a", "b" }; + const char* urls[] = { "about:blank", "about:blank" }; + AddBookmarks(titles, urls, ARRAYSIZE_UNSAFE(titles)); // Remove the node and make sure we don't get back any results. const char* expected[] = { "blah" }; @@ -240,11 +355,12 @@ TEST_F(BookmarkIndexTest, ChangeTitle) { // Makes sure no more than max queries is returned. TEST_F(BookmarkIndexTest, HonorMax) { - const char* input[] = { "abcd", "abcde" }; - AddBookmarksWithTitles(input, ARRAYSIZE_UNSAFE(input)); + const char* titles[] = { "abcd", "abcde" }; + const char* urls[] = { "about:blank", "about:blank" }; + AddBookmarks(titles, urls, ARRAYSIZE_UNSAFE(titles)); - std::vector<BookmarkTitleMatch> matches; - model_->GetBookmarksWithTitlesMatching(ASCIIToUTF16("ABc"), 1, &matches); + std::vector<BookmarkMatch> matches; + model_->GetBookmarksMatching(ASCIIToUTF16("ABc"), 1, &matches); EXPECT_EQ(1U, matches.size()); } @@ -255,11 +371,11 @@ TEST_F(BookmarkIndexTest, EmptyMatchOnMultiwideLowercaseString) { base::WideToUTF16(L"\u0130 i"), GURL("http://www.google.com")); - std::vector<BookmarkTitleMatch> matches; - model_->GetBookmarksWithTitlesMatching(ASCIIToUTF16("i"), 100, &matches); + std::vector<BookmarkMatch> matches; + model_->GetBookmarksMatching(ASCIIToUTF16("i"), 100, &matches); ASSERT_EQ(1U, matches.size()); EXPECT_TRUE(matches[0].node == n1); - EXPECT_TRUE(matches[0].match_positions.empty()); + EXPECT_TRUE(matches[0].title_match_positions.empty()); } TEST_F(BookmarkIndexTest, GetResultsSortedByTypedCount) { @@ -320,8 +436,8 @@ TEST_F(BookmarkIndexTest, GetResultsSortedByTypedCount) { EXPECT_EQ(data[3].title, base::UTF16ToUTF8(result4.title())); // Populate match nodes. - std::vector<BookmarkTitleMatch> matches; - model->GetBookmarksWithTitlesMatching(ASCIIToUTF16("google"), 4, &matches); + std::vector<BookmarkMatch> matches; + model->GetBookmarksMatching(ASCIIToUTF16("google"), 4, &matches); // The resulting order should be: // 1. Google (google.com) 100 @@ -336,7 +452,7 @@ TEST_F(BookmarkIndexTest, GetResultsSortedByTypedCount) { matches.clear(); // Select top two matches. - model->GetBookmarksWithTitlesMatching(ASCIIToUTF16("google"), 2, &matches); + model->GetBookmarksMatching(ASCIIToUTF16("google"), 2, &matches); EXPECT_EQ(2, static_cast<int>(matches.size())); EXPECT_EQ(data[0].url, matches[0].node->url()); diff --git a/chrome/browser/bookmarks/bookmark_model.cc b/chrome/browser/bookmarks/bookmark_model.cc index 794e6e7..1f49a23 100644 --- a/chrome/browser/bookmarks/bookmark_model.cc +++ b/chrome/browser/bookmarks/bookmark_model.cc @@ -10,6 +10,7 @@ #include "base/bind.h" #include "base/bind_helpers.h" #include "base/i18n/string_compare.h" +#include "base/prefs/pref_service.h" #include "base/sequenced_task_runner.h" #include "chrome/browser/bookmarks/bookmark_expanded_state_tracker.h" #include "chrome/browser/bookmarks/bookmark_index.h" @@ -23,7 +24,8 @@ #include "chrome/browser/history/history_service.h" #include "chrome/browser/history/history_service_factory.h" #include "chrome/browser/profiles/profile.h" -#include "components/bookmarks/core/browser/bookmark_title_match.h" +#include "chrome/common/pref_names.h" +#include "components/bookmarks/core/browser/bookmark_match.h" #include "components/favicon_base/favicon_types.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_source.h" @@ -70,7 +72,8 @@ class SortComparator : public std::binary_function<const BookmarkNode*, // BookmarkModel -------------------------------------------------------------- -BookmarkModel::BookmarkModel(Profile* profile) +BookmarkModel::BookmarkModel(Profile* profile, + bool index_urls) : profile_(profile), loaded_(false), root_(GURL()), @@ -79,6 +82,7 @@ BookmarkModel::BookmarkModel(Profile* profile) mobile_node_(NULL), next_node_id_(1), observers_(ObserverList<BookmarkModelObserver>::NOTIFY_EXISTING_ONLY), + index_urls_(index_urls), loaded_signal_(true, false), extensive_changes_(0) { if (!profile_) { @@ -609,14 +613,14 @@ void BookmarkModel::ResetDateFolderModified(const BookmarkNode* node) { SetDateFolderModified(node, Time()); } -void BookmarkModel::GetBookmarksWithTitlesMatching( +void BookmarkModel::GetBookmarksMatching( const base::string16& text, size_t max_count, - std::vector<BookmarkTitleMatch>* matches) { + std::vector<BookmarkMatch>* matches) { if (!loaded_) return; - index_->GetBookmarksWithTitlesMatching(text, max_count, matches); + index_->GetBookmarksMatching(text, max_count, matches); } void BookmarkModel::ClearStore() { @@ -945,7 +949,13 @@ BookmarkLoadDetails* BookmarkModel::CreateLoadDetails() { CreatePermanentNode(BookmarkNode::OTHER_NODE); BookmarkPermanentNode* mobile_node = CreatePermanentNode(BookmarkNode::MOBILE); - return new BookmarkLoadDetails(bb_node, other_node, mobile_node, - new BookmarkIndex(profile_), - next_node_id_); + return new BookmarkLoadDetails( + bb_node, other_node, mobile_node, + new BookmarkIndex( + profile_, + index_urls_, + profile_ ? + profile_->GetPrefs()->GetString(prefs::kAcceptLanguages) : + std::string()), + next_node_id_); } diff --git a/chrome/browser/bookmarks/bookmark_model.h b/chrome/browser/bookmarks/bookmark_model.h index b6dfd70..816b89b 100644 --- a/chrome/browser/bookmarks/bookmark_model.h +++ b/chrome/browser/bookmarks/bookmark_model.h @@ -32,7 +32,7 @@ class BookmarkIndex; class BookmarkLoadDetails; class BookmarkModelObserver; class BookmarkStorage; -struct BookmarkTitleMatch; +struct BookmarkMatch; class Profile; class ScopedGroupBookmarkActions; @@ -58,7 +58,9 @@ class BookmarkModel : public content::NotificationObserver, public BookmarkService, public KeyedService { public: - explicit BookmarkModel(Profile* profile); + // |index_urls| says whether URLs should be stored in the BookmarkIndex + // in addition to bookmark titles. + BookmarkModel(Profile* profile, bool index_urls); virtual ~BookmarkModel(); // Invoked prior to destruction to release any necessary resources. @@ -210,10 +212,12 @@ class BookmarkModel : public content::NotificationObserver, // combobox of most recently modified folders. void ResetDateFolderModified(const BookmarkNode* node); - void GetBookmarksWithTitlesMatching( + // Returns up to |max_count| of bookmarks containing each term from |text| + // in either the title or the URL. + void GetBookmarksMatching( const base::string16& text, size_t max_count, - std::vector<BookmarkTitleMatch>* matches); + std::vector<BookmarkMatch>* matches); // Sets the store to NULL, making it so the BookmarkModel does not persist // any changes to disk. This is only useful during testing to speed up @@ -385,6 +389,10 @@ class BookmarkModel : public content::NotificationObserver, scoped_ptr<BookmarkIndex> index_; + // True if URLs are stored in the BookmarkIndex in addition to bookmark + // titles. + const bool index_urls_; + base::WaitableEvent loaded_signal_; // See description of IsDoingExtensiveChanges above. diff --git a/chrome/browser/bookmarks/bookmark_model_factory.cc b/chrome/browser/bookmarks/bookmark_model_factory.cc index c54cff9..a03b909 100644 --- a/chrome/browser/bookmarks/bookmark_model_factory.cc +++ b/chrome/browser/bookmarks/bookmark_model_factory.cc @@ -9,6 +9,7 @@ #include "base/memory/singleton.h" #include "base/values.h" #include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/browser/omnibox/omnibox_field_trial.h" #include "chrome/browser/profiles/incognito_helpers.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/startup_task_runner_service.h" @@ -47,7 +48,8 @@ BookmarkModelFactory::~BookmarkModelFactory() {} KeyedService* BookmarkModelFactory::BuildServiceInstanceFor( content::BrowserContext* context) const { Profile* profile = static_cast<Profile*>(context); - BookmarkModel* bookmark_model = new BookmarkModel(profile); + BookmarkModel* bookmark_model = + new BookmarkModel(profile, OmniboxFieldTrial::BookmarksIndexURLsValue()); bookmark_model->Load(StartupTaskRunnerServiceFactory::GetForProfile(profile)-> GetBookmarkTaskRunner()); #if !defined(OS_ANDROID) diff --git a/chrome/browser/bookmarks/bookmark_model_unittest.cc b/chrome/browser/bookmarks/bookmark_model_unittest.cc index 65139b3..eec75f9 100644 --- a/chrome/browser/bookmarks/bookmark_model_unittest.cc +++ b/chrome/browser/bookmarks/bookmark_model_unittest.cc @@ -141,7 +141,7 @@ class BookmarkModelTest : public testing::Test, }; BookmarkModelTest() - : model_(NULL) { + : model_(NULL, false) { model_.AddObserver(this); ClearCounts(); } diff --git a/chrome/browser/bookmarks/bookmark_utils.cc b/chrome/browser/bookmarks/bookmark_utils.cc index c41a933..4b4bcdd 100644 --- a/chrome/browser/bookmarks/bookmark_utils.cc +++ b/chrome/browser/bookmarks/bookmark_utils.cc @@ -21,6 +21,7 @@ #include "content/public/browser/user_metrics.h" #include "net/base/net_util.h" #include "ui/base/models/tree_node_iterator.h" +#include "url/gurl.h" #if !defined(OS_ANDROID) #include "chrome/browser/bookmarks/scoped_group_bookmark_actions.h" @@ -30,6 +31,10 @@ using base::Time; namespace { +// The maximum length of URL or title returned by the Cleanup functions. +const size_t kCleanedUpUrlMaxLength = 1024u; +const size_t kCleanedUpTitleMaxLength = 1024u; + void CloneBookmarkNodeImpl(BookmarkModel* model, const BookmarkNodeData::Element& element, const BookmarkNode* parent, @@ -123,6 +128,22 @@ const BookmarkNode* GetNodeByID(const BookmarkNode* node, int64 id) { return NULL; } +// Attempts to shorten a URL safely (i.e., by preventing the end of the URL +// from being in the middle of an escape sequence) to no more than +// kCleanedUpUrlMaxLength characters, returning the result. +std::string TruncateUrl(const std::string& url) { + if (url.length() <= kCleanedUpUrlMaxLength) + return url; + + // If we're in the middle of an escape sequence, truncate just before it. + if (url[kCleanedUpUrlMaxLength - 1] == '%') + return url.substr(0, kCleanedUpUrlMaxLength - 1); + if (url[kCleanedUpUrlMaxLength - 2] == '%') + return url.substr(0, kCleanedUpUrlMaxLength - 2); + + return url.substr(0, kCleanedUpUrlMaxLength); +} + } // namespace namespace bookmark_utils { @@ -386,6 +407,19 @@ void RemoveAllBookmarks(BookmarkModel* model, const GURL& url) { } } +base::string16 CleanUpUrlForMatching(const GURL& gurl, + const std::string& languages) { + return base::i18n::ToLower(net::FormatUrl( + GURL(TruncateUrl(gurl.spec())), languages, + net::kFormatUrlOmitUsernamePassword, + net::UnescapeRule::SPACES | net::UnescapeRule::URL_SPECIAL_CHARS, + NULL, NULL, NULL)); +} + +base::string16 CleanUpTitleForMatching(const base::string16& title) { + return base::i18n::ToLower(title.substr(0u, kCleanedUpTitleMaxLength)); +} + } // namespace bookmark_utils const BookmarkNode* GetBookmarkNodeByID(const BookmarkModel* model, int64 id) { diff --git a/chrome/browser/bookmarks/bookmark_utils.h b/chrome/browser/bookmarks/bookmark_utils.h index a96be4e..9eecedb 100644 --- a/chrome/browser/bookmarks/bookmark_utils.h +++ b/chrome/browser/bookmarks/bookmark_utils.h @@ -14,13 +14,15 @@ class BookmarkModel; class BookmarkNode; class Profile; +class GURL; namespace user_prefs { class PrefRegistrySyncable; } // A collection of bookmark utility functions used by various parts of the UI -// that show bookmarks: bookmark manager, bookmark bar view ... +// that show bookmarks (bookmark manager, bookmark bar view, ...) and other +// systems that involve indexing and searching bookmarks. namespace bookmark_utils { // Fields to use when finding matching bookmarks. @@ -107,6 +109,21 @@ void AddIfNotBookmarked(BookmarkModel* model, // Removes all bookmarks for the given |url|. void RemoveAllBookmarks(BookmarkModel* model, const GURL& url); +// Truncates an overly-long URL, unescapes it, and lower-cases it, +// returning the result. This unescaping makes it possible to match +// substrings that were originally escaped for navigation; for +// example, if the user searched for "a&p", the query would be escaped +// as "a%26p", so without unescaping, an input string of "a&p" would +// no longer match this URL. Note that the resulting unescaped URL +// may not be directly navigable (which is why we escaped it to begin +// with). |languages| is passed to net::FormatUrl(). +base::string16 CleanUpUrlForMatching(const GURL& gurl, + const std::string& languages); + +// Returns the lower-cased title, possibly truncated if the original title +// is overly-long. +base::string16 CleanUpTitleForMatching(const base::string16& title); + } // namespace bookmark_utils // Returns the node with |id|, or NULL if there is no node with |id|. diff --git a/chrome/browser/bookmarks/bookmark_utils_unittest.cc b/chrome/browser/bookmarks/bookmark_utils_unittest.cc index cfbf400..428fa1b 100644 --- a/chrome/browser/bookmarks/bookmark_utils_unittest.cc +++ b/chrome/browser/bookmarks/bookmark_utils_unittest.cc @@ -70,7 +70,7 @@ class BookmarkUtilsTest : public testing::Test, }; TEST_F(BookmarkUtilsTest, GetBookmarksMatchingPropertiesWordPhraseQuery) { - BookmarkModel model(NULL); + BookmarkModel model(NULL, false); const BookmarkNode* node1 = model.AddURL(model.other_node(), 0, ASCIIToUTF16("foo bar"), @@ -128,7 +128,7 @@ TEST_F(BookmarkUtilsTest, GetBookmarksMatchingPropertiesWordPhraseQuery) { // Check exact matching against a URL query. TEST_F(BookmarkUtilsTest, GetBookmarksMatchingPropertiesUrl) { - BookmarkModel model(NULL); + BookmarkModel model(NULL, false); const BookmarkNode* node1 = model.AddURL(model.other_node(), 0, ASCIIToUTF16("Google"), @@ -163,7 +163,7 @@ TEST_F(BookmarkUtilsTest, GetBookmarksMatchingPropertiesUrl) { // Check exact matching against a title query. TEST_F(BookmarkUtilsTest, GetBookmarksMatchingPropertiesTitle) { - BookmarkModel model(NULL); + BookmarkModel model(NULL, false); const BookmarkNode* node1 = model.AddURL(model.other_node(), 0, ASCIIToUTF16("Google"), @@ -200,7 +200,7 @@ TEST_F(BookmarkUtilsTest, GetBookmarksMatchingPropertiesTitle) { // Check matching against a query with multiple predicates. TEST_F(BookmarkUtilsTest, GetBookmarksMatchingPropertiesConjunction) { - BookmarkModel model(NULL); + BookmarkModel model(NULL, false); const BookmarkNode* node1 = model.AddURL(model.other_node(), 0, ASCIIToUTF16("Google"), @@ -249,7 +249,7 @@ TEST_F(BookmarkUtilsTest, GetBookmarksMatchingPropertiesConjunction) { } TEST_F(BookmarkUtilsTest, CopyPaste) { - BookmarkModel model(NULL); + BookmarkModel model(NULL, false); const BookmarkNode* node = model.AddURL(model.other_node(), 0, ASCIIToUTF16("foo bar"), @@ -276,7 +276,7 @@ TEST_F(BookmarkUtilsTest, CopyPaste) { } TEST_F(BookmarkUtilsTest, CutToClipboard) { - BookmarkModel model(NULL); + BookmarkModel model(NULL, false); model.AddObserver(this); base::string16 title(ASCIIToUTF16("foo")); @@ -301,7 +301,7 @@ TEST_F(BookmarkUtilsTest, CutToClipboard) { } TEST_F(BookmarkUtilsTest, GetParentForNewNodes) { - BookmarkModel model(NULL); + BookmarkModel model(NULL, false); // This tests the case where selection contains one item and that item is a // folder. std::vector<const BookmarkNode*> nodes; @@ -342,7 +342,7 @@ TEST_F(BookmarkUtilsTest, GetParentForNewNodes) { // Verifies that meta info is copied when nodes are cloned. TEST_F(BookmarkUtilsTest, CloneMetaInfo) { - BookmarkModel model(NULL); + BookmarkModel model(NULL, false); // Add a node containing meta info. const BookmarkNode* node = model.AddURL(model.other_node(), 0, diff --git a/chrome/browser/extensions/api/bookmarks/bookmark_api_helpers_unittest.cc b/chrome/browser/extensions/api/bookmarks/bookmark_api_helpers_unittest.cc index ee0eaaf..072f841 100644 --- a/chrome/browser/extensions/api/bookmarks/bookmark_api_helpers_unittest.cc +++ b/chrome/browser/extensions/api/bookmarks/bookmark_api_helpers_unittest.cc @@ -22,7 +22,7 @@ namespace bookmark_api_helpers { class ExtensionBookmarksTest : public testing::Test { public: virtual void SetUp() OVERRIDE { - model_.reset(new BookmarkModel(NULL)); + model_.reset(new BookmarkModel(NULL, false)); model_->AddURL(model_->other_node(), 0, base::ASCIIToUTF16("Digg"), GURL("http://www.reddit.com")); model_->AddURL(model_->other_node(), 0, base::ASCIIToUTF16("News"), diff --git a/chrome/browser/history/expire_history_backend_unittest.cc b/chrome/browser/history/expire_history_backend_unittest.cc index 7c9d20b..b80b25c 100644 --- a/chrome/browser/history/expire_history_backend_unittest.cc +++ b/chrome/browser/history/expire_history_backend_unittest.cc @@ -56,7 +56,7 @@ class ExpireHistoryTest : public testing::Test, public BroadcastNotificationDelegate { public: ExpireHistoryTest() - : bookmark_model_(NULL), + : bookmark_model_(NULL, false), ui_thread_(BrowserThread::UI, &message_loop_), db_thread_(BrowserThread::DB, &message_loop_), expirer_(this, &bookmark_model_), diff --git a/chrome/browser/history/history_backend_unittest.cc b/chrome/browser/history/history_backend_unittest.cc index debfcd8..4b7ef08 100644 --- a/chrome/browser/history/history_backend_unittest.cc +++ b/chrome/browser/history/history_backend_unittest.cc @@ -115,7 +115,7 @@ class HistoryBackendTestBase : public testing::Test { typedef std::vector<std::pair<int, HistoryDetails*> > NotificationList; HistoryBackendTestBase() - : bookmark_model_(NULL), + : bookmark_model_(NULL, false), loaded_(false), ui_thread_(content::BrowserThread::UI, &message_loop_) { } diff --git a/chrome/browser/history/in_memory_url_index_types.h b/chrome/browser/history/in_memory_url_index_types.h index 91ef29f..d8a0243 100644 --- a/chrome/browser/history/in_memory_url_index_types.h +++ b/chrome/browser/history/in_memory_url_index_types.h @@ -38,21 +38,6 @@ struct TermMatch { }; typedef std::vector<TermMatch> TermMatches; -// Truncates an overly-long URL, unescapes it, and lower-cases it, -// returning the result. This unescaping makes it possible to match -// substrings that were originally escaped for navigation; for -// example, if the user searched for "a&p", the query would be escaped -// as "a%26p", so without unescaping, an input string of "a&p" would -// no longer match this URL. Note that the resulting unescaped URL -// may not be directly navigable (which is why we escaped it to begin -// with). |languages| is passed to net::FormatUrl(). -base::string16 CleanUpUrlForMatching(const GURL& gurl, - const std::string& languages); - -// Returns the lower-cased title, possibly truncated if the original title -// is overly-long. -base::string16 CleanUpTitleForMatching(const base::string16& title); - // Returns a TermMatches which has an entry for each occurrence of the // string |term| found in the string |cleaned_string|. Use // CleanUpUrlForMatching() or CleanUpUrlTitleMatching() before passing diff --git a/chrome/browser/history/scored_history_match.cc b/chrome/browser/history/scored_history_match.cc index 5a385bc..d9cab36 100644 --- a/chrome/browser/history/scored_history_match.cc +++ b/chrome/browser/history/scored_history_match.cc @@ -18,6 +18,7 @@ #include "base/strings/utf_string_conversions.h" #include "chrome/browser/autocomplete/history_url_provider.h" #include "chrome/browser/autocomplete/url_prefix.h" +#include "chrome/browser/bookmarks/bookmark_utils.h" #include "chrome/browser/omnibox/omnibox_field_trial.h" #include "chrome/common/chrome_switches.h" #include "components/bookmarks/core/browser/bookmark_service.h" @@ -67,8 +68,8 @@ ScoredHistoryMatch::ScoredHistoryMatch( // Figure out where each search term appears in the URL and/or page title // so that we can score as well as provide autocomplete highlighting. - base::string16 url = CleanUpUrlForMatching(gurl, languages); - base::string16 title = CleanUpTitleForMatching(row.title()); + base::string16 url = bookmark_utils::CleanUpUrlForMatching(gurl, languages); + base::string16 title = bookmark_utils::CleanUpTitleForMatching(row.title()); int term_num = 0; for (String16Vector::const_iterator iter = terms.begin(); iter != terms.end(); ++iter, ++term_num) { diff --git a/chrome/browser/history/url_database.cc b/chrome/browser/history/url_database.cc index 2adb518..605213b 100644 --- a/chrome/browser/history/url_database.cc +++ b/chrome/browser/history/url_database.cc @@ -370,7 +370,7 @@ bool URLDatabase::GetTextMatches(const base::string16& query, "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls WHERE hidden = 0")); while (statement.Step()) { - std::vector<query_parser::QueryWord> query_words; + query_parser::QueryWordVector query_words; base::string16 url = base::i18n::ToLower(statement.ColumnString16(1)); query_parser_.ExtractQueryWords(url, &query_words); GURL gurl(url); diff --git a/chrome/browser/history/url_index_private_data.cc b/chrome/browser/history/url_index_private_data.cc index 482880f..d592f17 100644 --- a/chrome/browser/history/url_index_private_data.cc +++ b/chrome/browser/history/url_index_private_data.cc @@ -22,6 +22,7 @@ #include "base/time/time.h" #include "chrome/browser/autocomplete/autocomplete_provider.h" #include "chrome/browser/autocomplete/url_prefix.h" +#include "chrome/browser/bookmarks/bookmark_utils.h" #include "chrome/browser/history/history_database.h" #include "chrome/browser/history/history_db_task.h" #include "chrome/browser/history/history_service.h" @@ -756,10 +757,12 @@ void URLIndexPrivateData::AddRowWordsToIndex(const URLRow& row, HistoryID history_id = static_cast<HistoryID>(row.id()); // Split URL into individual, unique words then add in the title words. const GURL& gurl(row.url()); - const base::string16& url = CleanUpUrlForMatching(gurl, languages); + const base::string16& url = + bookmark_utils::CleanUpUrlForMatching(gurl, languages); String16Set url_words = String16SetFromString16(url, word_starts ? &word_starts->url_word_starts_ : NULL); - const base::string16& title = CleanUpTitleForMatching(row.title()); + const base::string16& title = + bookmark_utils::CleanUpTitleForMatching(row.title()); String16Set title_words = String16SetFromString16(title, word_starts ? &word_starts->title_word_starts_ : NULL); String16Set words; @@ -1237,9 +1240,11 @@ bool URLIndexPrivateData::RestoreWordStartsMap( iter != history_info_map_.end(); ++iter) { RowWordStarts word_starts; const URLRow& row(iter->second.url_row); - const base::string16& url = CleanUpUrlForMatching(row.url(), languages); + const base::string16& url = + bookmark_utils::CleanUpUrlForMatching(row.url(), languages); String16VectorFromString16(url, false, &word_starts.url_word_starts_); - const base::string16& title = CleanUpTitleForMatching(row.title()); + const base::string16& title = + bookmark_utils::CleanUpTitleForMatching(row.title()); String16VectorFromString16(title, false, &word_starts.title_word_starts_); word_starts_map_[iter->first] = word_starts; } diff --git a/chrome/browser/importer/profile_writer_unittest.cc b/chrome/browser/importer/profile_writer_unittest.cc index 6ef78c5..51cef04 100644 --- a/chrome/browser/importer/profile_writer_unittest.cc +++ b/chrome/browser/importer/profile_writer_unittest.cc @@ -18,7 +18,7 @@ #include "chrome/browser/importer/importer_unittest_utils.h" #include "chrome/common/importer/imported_bookmark_entry.h" #include "chrome/test/base/testing_profile.h" -#include "components/bookmarks/core/browser/bookmark_title_match.h" +#include "components/bookmarks/core/browser/bookmark_match.h" #include "content/public/test/test_browser_thread.h" #include "testing/gtest/include/gtest/gtest.h" @@ -80,11 +80,10 @@ class ProfileWriterTest : public testing::Test { const std::vector<BookmarkService::URLAndTitle>& bookmarks_record, BookmarkModel* bookmark_model, size_t expected) { - std::vector<BookmarkTitleMatch> matches; + std::vector<BookmarkMatch> matches; for (size_t i = 0; i < bookmarks_record.size(); ++i) { - bookmark_model->GetBookmarksWithTitlesMatching(bookmarks_record[i].title, - 10, - &matches); + bookmark_model->GetBookmarksMatching( + bookmarks_record[i].title, 10, &matches); EXPECT_EQ(expected, matches.size()); matches.clear(); } diff --git a/chrome/browser/omnibox/omnibox_field_trial.cc b/chrome/browser/omnibox/omnibox_field_trial.cc index 858e4f8..3d0afe7 100644 --- a/chrome/browser/omnibox/omnibox_field_trial.cc +++ b/chrome/browser/omnibox/omnibox_field_trial.cc @@ -418,6 +418,12 @@ bool OmniboxFieldTrial::HQPAllowMatchInSchemeValue() { kHQPAllowMatchInSchemeRule) == "true"; } +bool OmniboxFieldTrial::BookmarksIndexURLsValue() { + return chrome_variations::GetVariationParamValue( + kBundledExperimentFieldTrialName, + kBookmarksIndexURLsRule) == "true"; +} + const char OmniboxFieldTrial::kBundledExperimentFieldTrialName[] = "OmniboxBundledExperimentV1"; const char OmniboxFieldTrial::kShortcutsScoringMaxRelevanceRule[] = @@ -431,6 +437,7 @@ const char OmniboxFieldTrial::kHQPAllowMatchInSchemeRule[] = "HQPAllowMatchInScheme"; const char OmniboxFieldTrial::kZeroSuggestRule[] = "ZeroSuggest"; const char OmniboxFieldTrial::kZeroSuggestVariantRule[] = "ZeroSuggestVariant"; +const char OmniboxFieldTrial::kBookmarksIndexURLsRule[] = "BookmarksIndexURLs"; const char OmniboxFieldTrial::kHUPNewScoringEnabledParam[] = "HUPExperimentalScoringEnabled"; diff --git a/chrome/browser/omnibox/omnibox_field_trial.h b/chrome/browser/omnibox/omnibox_field_trial.h index 95913f9d..1b0a1c7 100644 --- a/chrome/browser/omnibox/omnibox_field_trial.h +++ b/chrome/browser/omnibox/omnibox_field_trial.h @@ -259,6 +259,17 @@ class OmniboxFieldTrial { static bool HQPAllowMatchInSchemeValue(); // --------------------------------------------------------- + // For the BookmarksIndexURLs experiment that's part of the + // bundled omnibox field trial. + + // Returns true if BookmarkIndex should index the URL of bookmarks + // (not only the titles) and search for / mark matches in the URLs, + // and BookmarkProvider should score bookmarks based on both the + // matches in bookmark title and URL. Returns false if the bookmarks + // index URLs experiment isn't active. + static bool BookmarksIndexURLsValue(); + + // --------------------------------------------------------- // Exposed publicly for the sake of unittests. static const char kBundledExperimentFieldTrialName[]; // Rule names used by the bundled experiment. @@ -271,6 +282,7 @@ class OmniboxFieldTrial { static const char kHQPAllowMatchInSchemeRule[]; static const char kZeroSuggestRule[]; static const char kZeroSuggestVariantRule[]; + static const char kBookmarksIndexURLsRule[]; // Parameter names used by the HUP new scoring experiments. static const char kHUPNewScoringEnabledParam[]; diff --git a/chrome/browser/sync/glue/bookmark_data_type_controller_unittest.cc b/chrome/browser/sync/glue/bookmark_data_type_controller_unittest.cc index 73babc4..91f0f1c 100644 --- a/chrome/browser/sync/glue/bookmark_data_type_controller_unittest.cc +++ b/chrome/browser/sync/glue/bookmark_data_type_controller_unittest.cc @@ -56,14 +56,14 @@ class HistoryMock : public HistoryService { KeyedService* BuildBookmarkModel(content::BrowserContext* context) { Profile* profile = static_cast<Profile*>(context); - BookmarkModel* bookmark_model = new BookmarkModel(profile); + BookmarkModel* bookmark_model = new BookmarkModel(profile, false); bookmark_model->Load(profile->GetIOTaskRunner()); return bookmark_model; } KeyedService* BuildBookmarkModelWithoutLoading( content::BrowserContext* profile) { - return new BookmarkModel(static_cast<Profile*>(profile)); + return new BookmarkModel(static_cast<Profile*>(profile), false); } KeyedService* BuildHistoryService(content::BrowserContext* profile) { diff --git a/chrome/browser/ui/bookmarks/bookmark_editor_unittest.cc b/chrome/browser/ui/bookmarks/bookmark_editor_unittest.cc index 7d2d504..186e1b2 100644 --- a/chrome/browser/ui/bookmarks/bookmark_editor_unittest.cc +++ b/chrome/browser/ui/bookmarks/bookmark_editor_unittest.cc @@ -12,7 +12,7 @@ using base::ASCIIToUTF16; namespace { TEST(BookmarkEditorTest, ApplyEditsWithNoFolderChange) { - BookmarkModel model(NULL); + BookmarkModel model(NULL, false); const BookmarkNode* bookmarkbar = model.bookmark_bar_node(); model.AddURL(bookmarkbar, 0, ASCIIToUTF16("url0"), GURL("chrome://newtab")); model.AddURL(bookmarkbar, 1, ASCIIToUTF16("url1"), GURL("chrome://newtab")); diff --git a/chrome/browser/ui/bookmarks/bookmark_ui_utils_unittest.cc b/chrome/browser/ui/bookmarks/bookmark_ui_utils_unittest.cc index a647b77..cbded0f 100644 --- a/chrome/browser/ui/bookmarks/bookmark_ui_utils_unittest.cc +++ b/chrome/browser/ui/bookmarks/bookmark_ui_utils_unittest.cc @@ -16,7 +16,7 @@ using base::ASCIIToUTF16; namespace { TEST(BookmarkUIUtilsTest, HasBookmarkURLs) { - BookmarkModel model(NULL); + BookmarkModel model(NULL, false); std::vector<const BookmarkNode*> nodes; @@ -56,7 +56,7 @@ TEST(BookmarkUIUtilsTest, HasBookmarkURLs) { } TEST(BookmarkUIUtilsTest, HasBookmarkURLsAllowedInIncognitoMode) { - BookmarkModel model(NULL); + BookmarkModel model(NULL, false); TestingProfile profile; std::vector<const BookmarkNode*> nodes; diff --git a/chrome/test/base/testing_profile.cc b/chrome/test/base/testing_profile.cc index 85a7bf7..4889808 100644 --- a/chrome/test/base/testing_profile.cc +++ b/chrome/test/base/testing_profile.cc @@ -494,7 +494,7 @@ void TestingProfile::DestroyTopSites() { static KeyedService* BuildBookmarkModel(content::BrowserContext* context) { Profile* profile = static_cast<Profile*>(context); - BookmarkModel* bookmark_model = new BookmarkModel(profile); + BookmarkModel* bookmark_model = new BookmarkModel(profile, false); bookmark_model->Load(profile->GetIOTaskRunner()); return bookmark_model; } |