diff options
author | hashimoto@chromium.org <hashimoto@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-05 00:29:53 +0000 |
---|---|---|
committer | hashimoto@chromium.org <hashimoto@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-05 00:29:53 +0000 |
commit | bf5c532d4a62b60de3ee1ca6b4dc8f998f6831d5 (patch) | |
tree | 4f30bb1072bc5573525e24454a3eb5e37a31af93 /components/search_engines | |
parent | cf4f689f6917abc305c0960619d683b0bc00cce3 (diff) | |
download | chromium_src-bf5c532d4a62b60de3ee1ca6b4dc8f998f6831d5.zip chromium_src-bf5c532d4a62b60de3ee1ca6b4dc8f998f6831d5.tar.gz chromium_src-bf5c532d4a62b60de3ee1ca6b4dc8f998f6831d5.tar.bz2 |
Componentize TemplateURLService
Move TemplateURLService, SearchHostToURLsMap, util.cc from chrome/browser/search_engines to components/search_engines
Move KeywordTable and KeywordWeDataService from chrome/browser/webdata to components/search_engines
Fix DEPS, OWNERS and gyp.
Replace HistoryDatabase in keyword_table.cc with URLDatabase.
BUG=386367
TEST=build and checkdeps
R=blundell@chromium.org, jochen@chromium.org, pkasting@chromium.org
TBR=atwilson@chromium.org as an owner of sync, erikwright@chromium.org as an owner of sql
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=281350
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=281403
Review URL: https://codereview.chromium.org/367023002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@281455 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'components/search_engines')
-rw-r--r-- | components/search_engines/DEPS | 6 | ||||
-rw-r--r-- | components/search_engines/keyword_table.cc | 647 | ||||
-rw-r--r-- | components/search_engines/keyword_table.h | 185 | ||||
-rw-r--r-- | components/search_engines/keyword_web_data_service.cc | 149 | ||||
-rw-r--r-- | components/search_engines/keyword_web_data_service.h | 110 | ||||
-rw-r--r-- | components/search_engines/search_host_to_urls_map.cc | 81 | ||||
-rw-r--r-- | components/search_engines/search_host_to_urls_map.h | 66 | ||||
-rw-r--r-- | components/search_engines/template_url_service.cc | 2372 | ||||
-rw-r--r-- | components/search_engines/template_url_service.h | 762 | ||||
-rw-r--r-- | components/search_engines/template_url_service_client.h | 36 | ||||
-rw-r--r-- | components/search_engines/template_url_service_observer.h | 19 | ||||
-rw-r--r-- | components/search_engines/util.cc | 389 | ||||
-rw-r--r-- | components/search_engines/util.h | 136 |
13 files changed, 4958 insertions, 0 deletions
diff --git a/components/search_engines/DEPS b/components/search_engines/DEPS index c0bc5f0..492c393 100644 --- a/components/search_engines/DEPS +++ b/components/search_engines/DEPS @@ -1,12 +1,18 @@ include_rules = [ "+components/google/core", "+components/history/core", + "+components/keyed_service/core", "+components/metrics/proto", "+components/policy/core", "+components/pref_registry", + "+components/rappor", + "+components/url_fixer", + "+components/webdata", "+google_apis", "+grit/components_strings.h", "+net", "+policy", + "+sql", + "+sync", "+ui/gfx/geometry", ] diff --git a/components/search_engines/keyword_table.cc b/components/search_engines/keyword_table.cc new file mode 100644 index 0000000..447ebfb --- /dev/null +++ b/components/search_engines/keyword_table.cc @@ -0,0 +1,647 @@ +// Copyright 2014 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 "components/search_engines/keyword_table.h" + +#include <set> + +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "components/history/core/browser/url_database.h" +#include "components/search_engines/search_terms_data.h" +#include "components/search_engines/template_url.h" +#include "components/webdata/common/web_database.h" +#include "sql/statement.h" +#include "sql/transaction.h" +#include "url/gurl.h" + +using base::Time; + +// static +const char KeywordTable::kDefaultSearchProviderKey[] = + "Default Search Provider ID"; + +namespace { + +// Keys used in the meta table. +const char kBuiltinKeywordVersion[] = "Builtin Keyword Version"; + +const std::string ColumnsForVersion(int version, bool concatenated) { + std::vector<std::string> columns; + + columns.push_back("id"); + columns.push_back("short_name"); + columns.push_back("keyword"); + columns.push_back("favicon_url"); + columns.push_back("url"); + columns.push_back("safe_for_autoreplace"); + columns.push_back("originating_url"); + columns.push_back("date_created"); + columns.push_back("usage_count"); + columns.push_back("input_encodings"); + columns.push_back("show_in_default_list"); + columns.push_back("suggest_url"); + columns.push_back("prepopulate_id"); + if (version <= 44) { + // Columns removed after version 44. + columns.push_back("autogenerate_keyword"); + columns.push_back("logo_id"); + } + columns.push_back("created_by_policy"); + columns.push_back("instant_url"); + columns.push_back("last_modified"); + columns.push_back("sync_guid"); + if (version >= 47) { + // Column added in version 47. + columns.push_back("alternate_urls"); + } + if (version >= 49) { + // Column added in version 49. + columns.push_back("search_terms_replacement_key"); + } + if (version >= 52) { + // Column added in version 52. + columns.push_back("image_url"); + columns.push_back("search_url_post_params"); + columns.push_back("suggest_url_post_params"); + columns.push_back("instant_url_post_params"); + columns.push_back("image_url_post_params"); + } + if (version >= 53) { + // Column added in version 53. + columns.push_back("new_tab_url"); + } + + return JoinString(columns, std::string(concatenated ? " || " : ", ")); +} + + +// Inserts the data from |data| into |s|. |s| is assumed to have slots for all +// the columns in the keyword table. |id_column| is the slot number to bind +// |data|'s |id| to; |starting_column| is the slot number of the first of a +// contiguous set of slots to bind all the other fields to. +void BindURLToStatement(const TemplateURLData& data, + sql::Statement* s, + int id_column, + int starting_column) { + // Serialize |alternate_urls| to JSON. + // TODO(beaudoin): Check what it would take to use a new table to store + // alternate_urls while keeping backups and table signature in a good state. + // See: crbug.com/153520 + base::ListValue alternate_urls_value; + for (size_t i = 0; i < data.alternate_urls.size(); ++i) + alternate_urls_value.AppendString(data.alternate_urls[i]); + std::string alternate_urls; + base::JSONWriter::Write(&alternate_urls_value, &alternate_urls); + + s->BindInt64(id_column, data.id); + s->BindString16(starting_column, data.short_name); + s->BindString16(starting_column + 1, data.keyword()); + s->BindString(starting_column + 2, data.favicon_url.is_valid() ? + history::URLDatabase::GURLToDatabaseURL(data.favicon_url) : + std::string()); + s->BindString(starting_column + 3, data.url()); + s->BindBool(starting_column + 4, data.safe_for_autoreplace); + s->BindString(starting_column + 5, data.originating_url.is_valid() ? + history::URLDatabase::GURLToDatabaseURL(data.originating_url) : + std::string()); + s->BindInt64(starting_column + 6, data.date_created.ToTimeT()); + s->BindInt(starting_column + 7, data.usage_count); + s->BindString(starting_column + 8, JoinString(data.input_encodings, ';')); + s->BindBool(starting_column + 9, data.show_in_default_list); + s->BindString(starting_column + 10, data.suggestions_url); + s->BindInt(starting_column + 11, data.prepopulate_id); + s->BindBool(starting_column + 12, data.created_by_policy); + s->BindString(starting_column + 13, data.instant_url); + s->BindInt64(starting_column + 14, data.last_modified.ToTimeT()); + s->BindString(starting_column + 15, data.sync_guid); + s->BindString(starting_column + 16, alternate_urls); + s->BindString(starting_column + 17, data.search_terms_replacement_key); + s->BindString(starting_column + 18, data.image_url); + s->BindString(starting_column + 19, data.search_url_post_params); + s->BindString(starting_column + 20, data.suggestions_url_post_params); + s->BindString(starting_column + 21, data.instant_url_post_params); + s->BindString(starting_column + 22, data.image_url_post_params); + s->BindString(starting_column + 23, data.new_tab_url); +} + +WebDatabaseTable::TypeKey GetKey() { + // We just need a unique constant. Use the address of a static that + // COMDAT folding won't touch in an optimizing linker. + static int table_key = 0; + return reinterpret_cast<void*>(&table_key); +} + +} // namespace + +KeywordTable::KeywordTable() { +} + +KeywordTable::~KeywordTable() {} + +KeywordTable* KeywordTable::FromWebDatabase(WebDatabase* db) { + return static_cast<KeywordTable*>(db->GetTable(GetKey())); +} + +WebDatabaseTable::TypeKey KeywordTable::GetTypeKey() const { + return GetKey(); +} + +bool KeywordTable::CreateTablesIfNecessary() { + return db_->DoesTableExist("keywords") || + db_->Execute("CREATE TABLE keywords (" + "id INTEGER PRIMARY KEY," + "short_name VARCHAR NOT NULL," + "keyword VARCHAR NOT NULL," + "favicon_url VARCHAR NOT NULL," + "url VARCHAR NOT NULL," + "safe_for_autoreplace INTEGER," + "originating_url VARCHAR," + "date_created INTEGER DEFAULT 0," + "usage_count INTEGER DEFAULT 0," + "input_encodings VARCHAR," + "show_in_default_list INTEGER," + "suggest_url VARCHAR," + "prepopulate_id INTEGER DEFAULT 0," + "created_by_policy INTEGER DEFAULT 0," + "instant_url VARCHAR," + "last_modified INTEGER DEFAULT 0," + "sync_guid VARCHAR," + "alternate_urls VARCHAR," + "search_terms_replacement_key VARCHAR," + "image_url VARCHAR," + "search_url_post_params VARCHAR," + "suggest_url_post_params VARCHAR," + "instant_url_post_params VARCHAR," + "image_url_post_params VARCHAR," + "new_tab_url VARCHAR)"); +} + +bool KeywordTable::IsSyncable() { + return true; +} + +bool KeywordTable::MigrateToVersion(int version, + bool* update_compatible_version) { + // Migrate if necessary. + switch (version) { + case 21: + *update_compatible_version = true; + return MigrateToVersion21AutoGenerateKeywordColumn(); + case 25: + *update_compatible_version = true; + return MigrateToVersion25AddLogoIDColumn(); + case 26: + *update_compatible_version = true; + return MigrateToVersion26AddCreatedByPolicyColumn(); + case 28: + *update_compatible_version = true; + return MigrateToVersion28SupportsInstantColumn(); + case 29: + *update_compatible_version = true; + return MigrateToVersion29InstantURLToSupportsInstant(); + case 38: + *update_compatible_version = true; + return MigrateToVersion38AddLastModifiedColumn(); + case 39: + *update_compatible_version = true; + return MigrateToVersion39AddSyncGUIDColumn(); + case 44: + *update_compatible_version = true; + return MigrateToVersion44AddDefaultSearchProviderBackup(); + case 45: + *update_compatible_version = true; + return MigrateToVersion45RemoveLogoIDAndAutogenerateColumns(); + case 47: + *update_compatible_version = true; + return MigrateToVersion47AddAlternateURLsColumn(); + case 48: + *update_compatible_version = true; + return MigrateToVersion48RemoveKeywordsBackup(); + case 49: + *update_compatible_version = true; + return MigrateToVersion49AddSearchTermsReplacementKeyColumn(); + case 52: + *update_compatible_version = true; + return MigrateToVersion52AddImageSearchAndPOSTSupport(); + case 53: + *update_compatible_version = true; + return MigrateToVersion53AddNewTabURLColumn(); + } + + return true; +} + +bool KeywordTable::PerformOperations(const Operations& operations) { + sql::Transaction transaction(db_); + if (!transaction.Begin()) + return false; + + for (Operations::const_iterator i(operations.begin()); i != operations.end(); + ++i) { + switch (i->first) { + case ADD: + if (!AddKeyword(i->second)) + return false; + break; + + case REMOVE: + if (!RemoveKeyword(i->second.id)) + return false; + break; + + case UPDATE: + if (!UpdateKeyword(i->second)) + return false; + break; + } + } + + return transaction.Commit(); +} + +bool KeywordTable::GetKeywords(Keywords* keywords) { + std::string query("SELECT " + GetKeywordColumns() + + " FROM keywords ORDER BY id ASC"); + sql::Statement s(db_->GetUniqueStatement(query.c_str())); + + std::set<TemplateURLID> bad_entries; + while (s.Step()) { + keywords->push_back(TemplateURLData()); + if (!GetKeywordDataFromStatement(s, &keywords->back())) { + bad_entries.insert(s.ColumnInt64(0)); + keywords->pop_back(); + } + } + bool succeeded = s.Succeeded(); + for (std::set<TemplateURLID>::const_iterator i(bad_entries.begin()); + i != bad_entries.end(); ++i) + succeeded &= RemoveKeyword(*i); + return succeeded; +} + +bool KeywordTable::SetDefaultSearchProviderID(int64 id) { + return meta_table_->SetValue(kDefaultSearchProviderKey, id); +} + +int64 KeywordTable::GetDefaultSearchProviderID() { + int64 value = kInvalidTemplateURLID; + meta_table_->GetValue(kDefaultSearchProviderKey, &value); + return value; +} + +bool KeywordTable::SetBuiltinKeywordVersion(int version) { + return meta_table_->SetValue(kBuiltinKeywordVersion, version); +} + +int KeywordTable::GetBuiltinKeywordVersion() { + int version = 0; + return meta_table_->GetValue(kBuiltinKeywordVersion, &version) ? version : 0; +} + +// static +std::string KeywordTable::GetKeywordColumns() { + return ColumnsForVersion(WebDatabase::kCurrentVersionNumber, false); +} + +bool KeywordTable::MigrateToVersion21AutoGenerateKeywordColumn() { + return db_->Execute("ALTER TABLE keywords ADD COLUMN autogenerate_keyword " + "INTEGER DEFAULT 0"); +} + +bool KeywordTable::MigrateToVersion25AddLogoIDColumn() { + return db_->Execute( + "ALTER TABLE keywords ADD COLUMN logo_id INTEGER DEFAULT 0"); +} + +bool KeywordTable::MigrateToVersion26AddCreatedByPolicyColumn() { + return db_->Execute("ALTER TABLE keywords ADD COLUMN created_by_policy " + "INTEGER DEFAULT 0"); +} + +bool KeywordTable::MigrateToVersion28SupportsInstantColumn() { + return db_->Execute("ALTER TABLE keywords ADD COLUMN supports_instant " + "INTEGER DEFAULT 0"); +} + +bool KeywordTable::MigrateToVersion29InstantURLToSupportsInstant() { + sql::Transaction transaction(db_); + return transaction.Begin() && + db_->Execute("ALTER TABLE keywords ADD COLUMN instant_url VARCHAR") && + db_->Execute("CREATE TABLE keywords_temp (" + "id INTEGER PRIMARY KEY," + "short_name VARCHAR NOT NULL," + "keyword VARCHAR NOT NULL," + "favicon_url VARCHAR NOT NULL," + "url VARCHAR NOT NULL," + "safe_for_autoreplace INTEGER," + "originating_url VARCHAR," + "date_created INTEGER DEFAULT 0," + "usage_count INTEGER DEFAULT 0," + "input_encodings VARCHAR," + "show_in_default_list INTEGER," + "suggest_url VARCHAR," + "prepopulate_id INTEGER DEFAULT 0," + "autogenerate_keyword INTEGER DEFAULT 0," + "logo_id INTEGER DEFAULT 0," + "created_by_policy INTEGER DEFAULT 0," + "instant_url VARCHAR)") && + db_->Execute("INSERT INTO keywords_temp SELECT id, short_name, keyword, " + "favicon_url, url, safe_for_autoreplace, originating_url, " + "date_created, usage_count, input_encodings, " + "show_in_default_list, suggest_url, prepopulate_id, " + "autogenerate_keyword, logo_id, created_by_policy, " + "instant_url FROM keywords") && + db_->Execute("DROP TABLE keywords") && + db_->Execute("ALTER TABLE keywords_temp RENAME TO keywords") && + transaction.Commit(); +} + +bool KeywordTable::MigrateToVersion38AddLastModifiedColumn() { + return db_->Execute( + "ALTER TABLE keywords ADD COLUMN last_modified INTEGER DEFAULT 0"); +} + +bool KeywordTable::MigrateToVersion39AddSyncGUIDColumn() { + return db_->Execute("ALTER TABLE keywords ADD COLUMN sync_guid VARCHAR"); +} + +bool KeywordTable::MigrateToVersion44AddDefaultSearchProviderBackup() { + std::string query("CREATE TABLE keywords_backup AS SELECT " + + ColumnsForVersion(44, false) + " FROM keywords ORDER BY id ASC"); + sql::Transaction transaction(db_); + return transaction.Begin() && + meta_table_->SetValue("Default Search Provider ID Backup", + GetDefaultSearchProviderID()) && + (!db_->DoesTableExist("keywords_backup") || + db_->Execute("DROP TABLE keywords_backup")) && + db_->Execute(query.c_str()) && + transaction.Commit(); +} + +bool KeywordTable::MigrateToVersion45RemoveLogoIDAndAutogenerateColumns() { + sql::Transaction transaction(db_); + if (!transaction.Begin()) + return false; + + // The version 43 migration should have been written to do this, but since it + // wasn't, we'll do it now. Unfortunately a previous change deleted this for + // some users, so we can't be sure this will succeed (so don't bail on error). + meta_table_->DeleteKey("Default Search Provider Backup"); + + return MigrateKeywordsTableForVersion45("keywords") && + MigrateKeywordsTableForVersion45("keywords_backup") && + meta_table_->SetValue("Default Search Provider ID Backup Signature", + std::string()) && + transaction.Commit(); +} + +bool KeywordTable::MigrateToVersion47AddAlternateURLsColumn() { + sql::Transaction transaction(db_); + return transaction.Begin() && + db_->Execute("ALTER TABLE keywords ADD COLUMN " + "alternate_urls VARCHAR DEFAULT ''") && + db_->Execute("ALTER TABLE keywords_backup ADD COLUMN " + "alternate_urls VARCHAR DEFAULT ''") && + meta_table_->SetValue("Default Search Provider ID Backup Signature", + std::string()) && + transaction.Commit(); +} + +bool KeywordTable::MigrateToVersion48RemoveKeywordsBackup() { + sql::Transaction transaction(db_); + return transaction.Begin() && + meta_table_->DeleteKey("Default Search Provider ID Backup") && + meta_table_->DeleteKey("Default Search Provider ID Backup Signature") && + db_->Execute("DROP TABLE keywords_backup") && + transaction.Commit(); +} + +bool KeywordTable::MigrateToVersion49AddSearchTermsReplacementKeyColumn() { + return db_->Execute("ALTER TABLE keywords ADD COLUMN " + "search_terms_replacement_key VARCHAR DEFAULT ''"); +} + +bool KeywordTable::MigrateToVersion52AddImageSearchAndPOSTSupport() { + sql::Transaction transaction(db_); + return transaction.Begin() && + db_->Execute("ALTER TABLE keywords ADD COLUMN image_url " + "VARCHAR DEFAULT ''") && + db_->Execute("ALTER TABLE keywords ADD COLUMN search_url_post_params " + "VARCHAR DEFAULT ''") && + db_->Execute("ALTER TABLE keywords ADD COLUMN suggest_url_post_params " + "VARCHAR DEFAULT ''") && + db_->Execute("ALTER TABLE keywords ADD COLUMN instant_url_post_params " + "VARCHAR DEFAULT ''") && + db_->Execute("ALTER TABLE keywords ADD COLUMN image_url_post_params " + "VARCHAR DEFAULT ''") && + transaction.Commit(); +} + +bool KeywordTable::MigrateToVersion53AddNewTabURLColumn() { + return db_->Execute("ALTER TABLE keywords ADD COLUMN new_tab_url " + "VARCHAR DEFAULT ''"); +} + +// static +bool KeywordTable::GetKeywordDataFromStatement(const sql::Statement& s, + TemplateURLData* data) { + DCHECK(data); + + data->short_name = s.ColumnString16(1); + data->SetKeyword(s.ColumnString16(2)); + // Due to past bugs, we might have persisted entries with empty URLs. Avoid + // reading these out. (GetKeywords() will delete these entries on return.) + // NOTE: This code should only be needed as long as we might be reading such + // potentially-old data and can be removed afterward. + if (s.ColumnString(4).empty()) + return false; + data->SetURL(s.ColumnString(4)); + data->suggestions_url = s.ColumnString(11); + data->instant_url = s.ColumnString(14); + data->image_url = s.ColumnString(19); + data->new_tab_url = s.ColumnString(24); + data->search_url_post_params = s.ColumnString(20); + data->suggestions_url_post_params = s.ColumnString(21); + data->instant_url_post_params = s.ColumnString(22); + data->image_url_post_params = s.ColumnString(23); + data->favicon_url = GURL(s.ColumnString(3)); + data->originating_url = GURL(s.ColumnString(6)); + data->show_in_default_list = s.ColumnBool(10); + data->safe_for_autoreplace = s.ColumnBool(5); + base::SplitString(s.ColumnString(9), ';', &data->input_encodings); + data->id = s.ColumnInt64(0); + data->date_created = Time::FromTimeT(s.ColumnInt64(7)); + data->last_modified = Time::FromTimeT(s.ColumnInt64(15)); + data->created_by_policy = s.ColumnBool(13); + data->usage_count = s.ColumnInt(8); + data->prepopulate_id = s.ColumnInt(12); + data->sync_guid = s.ColumnString(16); + + data->alternate_urls.clear(); + base::JSONReader json_reader; + scoped_ptr<base::Value> value(json_reader.ReadToValue(s.ColumnString(17))); + base::ListValue* alternate_urls_value; + if (value.get() && value->GetAsList(&alternate_urls_value)) { + std::string alternate_url; + for (size_t i = 0; i < alternate_urls_value->GetSize(); ++i) { + if (alternate_urls_value->GetString(i, &alternate_url)) + data->alternate_urls.push_back(alternate_url); + } + } + + data->search_terms_replacement_key = s.ColumnString(18); + + return true; +} + +bool KeywordTable::AddKeyword(const TemplateURLData& data) { + DCHECK(data.id); + std::string query("INSERT INTO keywords (" + GetKeywordColumns() + ") " + "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?," + " ?)"); + sql::Statement s(db_->GetCachedStatement(SQL_FROM_HERE, query.c_str())); + BindURLToStatement(data, &s, 0, 1); + + return s.Run(); +} + +bool KeywordTable::RemoveKeyword(TemplateURLID id) { + DCHECK(id); + sql::Statement s(db_->GetCachedStatement( + SQL_FROM_HERE, "DELETE FROM keywords WHERE id = ?")); + s.BindInt64(0, id); + + return s.Run(); +} + +bool KeywordTable::UpdateKeyword(const TemplateURLData& data) { + DCHECK(data.id); + sql::Statement s(db_->GetCachedStatement( + SQL_FROM_HERE, + "UPDATE keywords SET short_name=?, keyword=?, favicon_url=?, url=?, " + "safe_for_autoreplace=?, originating_url=?, date_created=?, " + "usage_count=?, input_encodings=?, show_in_default_list=?, " + "suggest_url=?, prepopulate_id=?, created_by_policy=?, instant_url=?, " + "last_modified=?, sync_guid=?, alternate_urls=?, " + "search_terms_replacement_key=?, image_url=?, search_url_post_params=?, " + "suggest_url_post_params=?, instant_url_post_params=?, " + "image_url_post_params=?, new_tab_url=? WHERE id=?")); + BindURLToStatement(data, &s, 24, 0); // "24" binds id() as the last item. + + return s.Run(); +} + +bool KeywordTable::GetKeywordAsString(TemplateURLID id, + const std::string& table_name, + std::string* result) { + std::string query("SELECT " + + ColumnsForVersion(WebDatabase::kCurrentVersionNumber, true) + + " FROM " + table_name + " WHERE id=?"); + sql::Statement s(db_->GetUniqueStatement(query.c_str())); + s.BindInt64(0, id); + + if (!s.Step()) { + LOG_IF(WARNING, s.Succeeded()) << "No keyword with id: " << id + << ", ignoring."; + return true; + } + + if (!s.Succeeded()) + return false; + + *result = s.ColumnString(0); + return true; +} + +bool KeywordTable::MigrateKeywordsTableForVersion45(const std::string& name) { + // Create a new table without the columns we're dropping. + if (!db_->Execute("CREATE TABLE keywords_temp (" + "id INTEGER PRIMARY KEY," + "short_name VARCHAR NOT NULL," + "keyword VARCHAR NOT NULL," + "favicon_url VARCHAR NOT NULL," + "url VARCHAR NOT NULL," + "safe_for_autoreplace INTEGER," + "originating_url VARCHAR," + "date_created INTEGER DEFAULT 0," + "usage_count INTEGER DEFAULT 0," + "input_encodings VARCHAR," + "show_in_default_list INTEGER," + "suggest_url VARCHAR," + "prepopulate_id INTEGER DEFAULT 0," + "created_by_policy INTEGER DEFAULT 0," + "instant_url VARCHAR," + "last_modified INTEGER DEFAULT 0," + "sync_guid VARCHAR)")) + return false; + std::string sql("INSERT INTO keywords_temp SELECT " + + ColumnsForVersion(46, false) + " FROM " + name); + if (!db_->Execute(sql.c_str())) + return false; + + // NOTE: The ORDER BY here ensures that the uniquing process for keywords will + // happen identically on both the normal and backup tables. + sql = "SELECT id, keyword, url, autogenerate_keyword FROM " + name + + " ORDER BY id ASC"; + sql::Statement s(db_->GetUniqueStatement(sql.c_str())); + base::string16 placeholder_keyword(base::ASCIIToUTF16("dummy")); + std::set<base::string16> keywords; + while (s.Step()) { + base::string16 keyword(s.ColumnString16(1)); + bool generate_keyword = keyword.empty() || s.ColumnBool(3); + if (generate_keyword) + keyword = placeholder_keyword; + TemplateURLData data; + data.SetKeyword(keyword); + data.SetURL(s.ColumnString(2)); + TemplateURL turl(data); + // Don't persist extension keywords to disk. These will get added to the + // TemplateURLService as the extensions are loaded. + bool delete_entry = turl.GetType() == TemplateURL::OMNIBOX_API_EXTENSION; + if (!delete_entry && generate_keyword) { + // Explicitly generate keywords for all rows with the autogenerate bit set + // or where the keyword is empty. + SearchTermsData terms_data; + GURL url(turl.GenerateSearchURL(terms_data)); + if (!url.is_valid()) { + delete_entry = true; + } else { + // Ensure autogenerated keywords are unique. + keyword = TemplateURL::GenerateKeyword(url); + while (keywords.count(keyword)) + keyword.append(base::ASCIIToUTF16("_")); + sql::Statement u(db_->GetUniqueStatement( + "UPDATE keywords_temp SET keyword=? WHERE id=?")); + u.BindString16(0, keyword); + u.BindInt64(1, s.ColumnInt64(0)); + if (!u.Run()) + return false; + } + } + if (delete_entry) { + sql::Statement u(db_->GetUniqueStatement( + "DELETE FROM keywords_temp WHERE id=?")); + u.BindInt64(0, s.ColumnInt64(0)); + if (!u.Run()) + return false; + } else { + keywords.insert(keyword); + } + } + + // Replace the old table with the new one. + sql = "DROP TABLE " + name; + if (!db_->Execute(sql.c_str())) + return false; + sql = "ALTER TABLE keywords_temp RENAME TO " + name; + return db_->Execute(sql.c_str()); +} diff --git a/components/search_engines/keyword_table.h b/components/search_engines/keyword_table.h new file mode 100644 index 0000000..5ff36b4 --- /dev/null +++ b/components/search_engines/keyword_table.h @@ -0,0 +1,185 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_SEARCH_ENGINES_KEYWORD_TABLE_H_ +#define COMPONENTS_SEARCH_ENGINES_KEYWORD_TABLE_H_ + +#include <string> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/strings/string16.h" +#include "components/search_engines/template_url_id.h" +#include "components/webdata/common/web_database_table.h" + +struct TemplateURLData; +class WebDatabase; + +namespace sql { +class Statement; +} // namespace sql + +// This class manages the |keywords| MetaTable within the SQLite database +// passed to the constructor. It expects the following schema: +// +// Note: The database stores time in seconds, UTC. +// +// keywords Most of the columns mirror that of a field in +// TemplateURLData. See that struct for more details. +// id +// short_name +// keyword +// favicon_url +// url +// show_in_default_list +// safe_for_autoreplace +// originating_url +// date_created This column was added after we allowed keywords. +// Keywords created before we started tracking +// creation date have a value of 0 for this. +// usage_count +// input_encodings Semicolon separated list of supported input +// encodings, may be empty. +// suggest_url +// prepopulate_id See TemplateURLData::prepopulate_id. +// created_by_policy See TemplateURLData::created_by_policy. This was +// added in version 26. +// instant_url See TemplateURLData::instant_url. This was added in +// version 29. +// last_modified See TemplateURLData::last_modified. This was added +// in version 38. +// sync_guid See TemplateURLData::sync_guid. This was added in +// version 39. +// alternate_urls See TemplateURLData::alternate_urls. This was added +// in version 47. +// search_terms_replacement_key +// See TemplateURLData::search_terms_replacement_key. +// This was added in version 49. +// image_url See TemplateURLData::image_url. This was added in +// version 52. +// search_url_post_params See TemplateURLData::search_url_post_params. This +// was added in version 52. +// suggest_url_post_params See TemplateURLData::suggestions_url_post_params. +// This was added in version 52. +// instant_url_post_params See TemplateURLData::instant_url_post_params. This +// was added in version 52. +// image_url_post_params See TemplateURLData::image_url_post_params. This +// was added in version 52. +// new_tab_url See TemplateURLData::new_tab_url. This was added in +// version 53. +// +// This class also manages some fields in the |meta| table: +// +// Default Search Provider ID The id of the default search provider. +// Builtin Keyword Version The version of builtin keywords data. +// +class KeywordTable : public WebDatabaseTable { + public: + enum OperationType { + ADD, + REMOVE, + UPDATE, + }; + + typedef std::pair<OperationType, TemplateURLData> Operation; + typedef std::vector<Operation> Operations; + typedef std::vector<TemplateURLData> Keywords; + + // Constants exposed for the benefit of test code: + + static const char kDefaultSearchProviderKey[]; + + KeywordTable(); + virtual ~KeywordTable(); + + // Retrieves the KeywordTable* owned by |database|. + static KeywordTable* FromWebDatabase(WebDatabase* db); + + virtual WebDatabaseTable::TypeKey GetTypeKey() const OVERRIDE; + virtual bool CreateTablesIfNecessary() OVERRIDE; + virtual bool IsSyncable() OVERRIDE; + virtual bool MigrateToVersion(int version, + bool* update_compatible_version) OVERRIDE; + + // Performs an arbitrary number of Add/Remove/Update operations as a single + // transaction. This is provided for efficiency reasons: if the caller needs + // to perform a large number of operations, doing them in a single transaction + // instead of one-per-transaction can be dramatically more efficient. + bool PerformOperations(const Operations& operations); + + // Loads the keywords into the specified vector. It's up to the caller to + // delete the returned objects. + // Returns true on success. + bool GetKeywords(Keywords* keywords); + + // ID (TemplateURLData->id) of the default search provider. + bool SetDefaultSearchProviderID(int64 id); + int64 GetDefaultSearchProviderID(); + + // Version of the built-in keywords. + bool SetBuiltinKeywordVersion(int version); + int GetBuiltinKeywordVersion(); + + // Returns a comma-separated list of the keyword columns for the current + // version of the table. + static std::string GetKeywordColumns(); + + // Table migration functions. + bool MigrateToVersion21AutoGenerateKeywordColumn(); + bool MigrateToVersion25AddLogoIDColumn(); + bool MigrateToVersion26AddCreatedByPolicyColumn(); + bool MigrateToVersion28SupportsInstantColumn(); + bool MigrateToVersion29InstantURLToSupportsInstant(); + bool MigrateToVersion38AddLastModifiedColumn(); + bool MigrateToVersion39AddSyncGUIDColumn(); + bool MigrateToVersion44AddDefaultSearchProviderBackup(); + bool MigrateToVersion45RemoveLogoIDAndAutogenerateColumns(); + bool MigrateToVersion47AddAlternateURLsColumn(); + bool MigrateToVersion48RemoveKeywordsBackup(); + bool MigrateToVersion49AddSearchTermsReplacementKeyColumn(); + bool MigrateToVersion52AddImageSearchAndPOSTSupport(); + bool MigrateToVersion53AddNewTabURLColumn(); + + private: + friend class KeywordTableTest; + FRIEND_TEST_ALL_PREFIXES(WebDatabaseMigrationTest, MigrateVersion44ToCurrent); + + // NOTE: Since the table columns have changed in different versions, many + // functions below take a |table_version| argument which dictates which + // version number's column set to use. + + // Fills |data| with the data in |s|. Returns false if we couldn't fill + // |data| for some reason, e.g. |s| tried to set one of the fields to an + // illegal value. + static bool GetKeywordDataFromStatement(const sql::Statement& s, + TemplateURLData* data); + + // Adds a new keyword, updating the id field on success. + // Returns true if successful. + bool AddKeyword(const TemplateURLData& data); + + // Removes the specified keyword. + // Returns true if successful. + bool RemoveKeyword(TemplateURLID id); + + // Updates the database values for the specified url. + // Returns true on success. + bool UpdateKeyword(const TemplateURLData& data); + + // Gets a string representation for keyword with id specified. + // Used to store its result in |meta| table or to compare with another + // keyword. Returns true on success, false otherwise. + bool GetKeywordAsString(TemplateURLID id, + const std::string& table_name, + std::string* result); + + // Migrates table |name| (which should be either "keywords" or + // "keywords_backup") from version 44 to version 45. + bool MigrateKeywordsTableForVersion45(const std::string& name); + + DISALLOW_COPY_AND_ASSIGN(KeywordTable); +}; + +#endif // COMPONENTS_SEARCH_ENGINES_KEYWORD_TABLE_H_ diff --git a/components/search_engines/keyword_web_data_service.cc b/components/search_engines/keyword_web_data_service.cc new file mode 100644 index 0000000..29981e4 --- /dev/null +++ b/components/search_engines/keyword_web_data_service.cc @@ -0,0 +1,149 @@ +// Copyright 2014 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 "components/search_engines/keyword_web_data_service.h" + +#include "base/bind.h" +#include "components/search_engines/keyword_table.h" +#include "components/search_engines/template_url_data.h" +#include "components/webdata/common/web_data_results.h" +#include "components/webdata/common/web_database_service.h" + +using base::Bind; + +WDKeywordsResult::WDKeywordsResult() + : default_search_provider_id(0), + builtin_keyword_version(0) { +} + +WDKeywordsResult::~WDKeywordsResult() {} + +KeywordWebDataService::BatchModeScoper::BatchModeScoper( + KeywordWebDataService* service) + : service_(service) { + if (service_) + service_->AdjustBatchModeLevel(true); +} + +KeywordWebDataService::BatchModeScoper::~BatchModeScoper() { + if (service_) + service_->AdjustBatchModeLevel(false); +} + +KeywordWebDataService::KeywordWebDataService( + scoped_refptr<WebDatabaseService> wdbs, + scoped_refptr<base::MessageLoopProxy> ui_thread, + const ProfileErrorCallback& callback) + : WebDataServiceBase(wdbs, callback, ui_thread), + batch_mode_level_(0) { +} + +void KeywordWebDataService::AddKeyword(const TemplateURLData& data) { + if (batch_mode_level_) { + queued_keyword_operations_.push_back( + KeywordTable::Operation(KeywordTable::ADD, data)); + } else { + AdjustBatchModeLevel(true); + AddKeyword(data); + AdjustBatchModeLevel(false); + } +} + +void KeywordWebDataService::RemoveKeyword(TemplateURLID id) { + if (batch_mode_level_) { + TemplateURLData data; + data.id = id; + queued_keyword_operations_.push_back( + KeywordTable::Operation(KeywordTable::REMOVE, data)); + } else { + AdjustBatchModeLevel(true); + RemoveKeyword(id); + AdjustBatchModeLevel(false); + } +} + +void KeywordWebDataService::UpdateKeyword(const TemplateURLData& data) { + if (batch_mode_level_) { + queued_keyword_operations_.push_back( + KeywordTable::Operation(KeywordTable::UPDATE, data)); + } else { + AdjustBatchModeLevel(true); + UpdateKeyword(data); + AdjustBatchModeLevel(false); + } +} + +WebDataServiceBase::Handle KeywordWebDataService::GetKeywords( + WebDataServiceConsumer* consumer) { + return wdbs_->ScheduleDBTaskWithResult( + FROM_HERE, Bind(&KeywordWebDataService::GetKeywordsImpl, this), consumer); +} + +void KeywordWebDataService::SetDefaultSearchProviderID(TemplateURLID id) { + wdbs_->ScheduleDBTask( + FROM_HERE, + Bind(&KeywordWebDataService::SetDefaultSearchProviderIDImpl, this, id)); +} + +void KeywordWebDataService::SetBuiltinKeywordVersion(int version) { + wdbs_->ScheduleDBTask( + FROM_HERE, + Bind(&KeywordWebDataService::SetBuiltinKeywordVersionImpl, + this, version)); +} + +KeywordWebDataService::~KeywordWebDataService() { + DCHECK(!batch_mode_level_); +} + +void KeywordWebDataService::AdjustBatchModeLevel(bool entering_batch_mode) { + if (entering_batch_mode) { + ++batch_mode_level_; + } else { + DCHECK(batch_mode_level_); + --batch_mode_level_; + if (!batch_mode_level_ && !queued_keyword_operations_.empty()) { + wdbs_->ScheduleDBTask( + FROM_HERE, + Bind(&KeywordWebDataService::PerformKeywordOperationsImpl, this, + queued_keyword_operations_)); + queued_keyword_operations_.clear(); + } + } +} + +WebDatabase::State KeywordWebDataService::PerformKeywordOperationsImpl( + const KeywordTable::Operations& operations, + WebDatabase* db) { + return KeywordTable::FromWebDatabase(db)->PerformOperations(operations) ? + WebDatabase::COMMIT_NEEDED : WebDatabase::COMMIT_NOT_NEEDED; +} + +scoped_ptr<WDTypedResult> KeywordWebDataService::GetKeywordsImpl( + WebDatabase* db) { + scoped_ptr<WDTypedResult> result_ptr; + WDKeywordsResult result; + if (KeywordTable::FromWebDatabase(db)->GetKeywords(&result.keywords)) { + result.default_search_provider_id = + KeywordTable::FromWebDatabase(db)->GetDefaultSearchProviderID(); + result.builtin_keyword_version = + KeywordTable::FromWebDatabase(db)->GetBuiltinKeywordVersion(); + result_ptr.reset(new WDResult<WDKeywordsResult>(KEYWORDS_RESULT, result)); + } + return result_ptr.Pass(); +} + +WebDatabase::State KeywordWebDataService::SetDefaultSearchProviderIDImpl( + TemplateURLID id, + WebDatabase* db) { + return KeywordTable::FromWebDatabase(db)->SetDefaultSearchProviderID(id) ? + WebDatabase::COMMIT_NEEDED : WebDatabase::COMMIT_NOT_NEEDED; +} + +WebDatabase::State KeywordWebDataService::SetBuiltinKeywordVersionImpl( + int version, + WebDatabase* db) { + return KeywordTable::FromWebDatabase(db)->SetBuiltinKeywordVersion(version) ? + WebDatabase::COMMIT_NEEDED : WebDatabase::COMMIT_NOT_NEEDED; +} diff --git a/components/search_engines/keyword_web_data_service.h b/components/search_engines/keyword_web_data_service.h new file mode 100644 index 0000000..da553a5 --- /dev/null +++ b/components/search_engines/keyword_web_data_service.h @@ -0,0 +1,110 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_SEARCH_ENGINES_KEYWORD_WEB_DATA_SERVICE_H__ +#define COMPONENTS_SEARCH_ENGINES_KEYWORD_WEB_DATA_SERVICE_H__ + +#include "base/memory/ref_counted.h" +#include "components/search_engines/keyword_table.h" +#include "components/search_engines/template_url_id.h" +#include "components/webdata/common/web_data_service_base.h" +#include "components/webdata/common/web_database.h" + +namespace base { +class MessageLoopProxy; +} + +class WDTypedResult; +class WebDatabaseService; +struct TemplateURLData; + +struct WDKeywordsResult { + WDKeywordsResult(); + ~WDKeywordsResult(); + + KeywordTable::Keywords keywords; + // Identifies the ID of the TemplateURL that is the default search. A value of + // 0 indicates there is no default search provider. + int64 default_search_provider_id; + // Version of the built-in keywords. A value of 0 indicates a first run. + int builtin_keyword_version; +}; + +class WebDataServiceConsumer; + +class KeywordWebDataService : public WebDataServiceBase { + public: + // Instantiate this to turn on batch mode on the provided |service| + // until the scoper is destroyed. When batch mode is on, calls to any of the + // three keyword table modification functions below will result in locally + // queueing the operation; on setting this back to false, all the + // modifications will be performed at once. This is a performance + // optimization; see comments on KeywordTable::PerformOperations(). + // + // If multiple scopers are in-scope simultaneously, batch mode will only be + // exited when all are destroyed. If |service| is NULL, the object will do + // nothing. + class BatchModeScoper { + public: + explicit BatchModeScoper(KeywordWebDataService* service); + ~BatchModeScoper(); + + private: + KeywordWebDataService* service_; + + DISALLOW_COPY_AND_ASSIGN(BatchModeScoper); + }; + + KeywordWebDataService(scoped_refptr<WebDatabaseService> wdbs, + scoped_refptr<base::MessageLoopProxy> ui_thread, + const ProfileErrorCallback& callback); + + // As the database processes requests at a later date, all deletion is + // done on the background thread. + // + // Many of the keyword related methods do not return a handle. This is because + // the caller (TemplateURLService) does not need to know when the request is + // done. + + void AddKeyword(const TemplateURLData& data); + void RemoveKeyword(TemplateURLID id); + void UpdateKeyword(const TemplateURLData& data); + + // Fetches the keywords. + // On success, consumer is notified with WDResult<KeywordTable::Keywords>. + Handle GetKeywords(WebDataServiceConsumer* consumer); + + // Sets the ID of the default search provider. + void SetDefaultSearchProviderID(TemplateURLID id); + + // Sets the version of the builtin keywords. + void SetBuiltinKeywordVersion(int version); + + protected: + virtual ~KeywordWebDataService(); + + private: + // Called by the BatchModeScoper (see comments there). + void AdjustBatchModeLevel(bool entering_batch_mode); + + ////////////////////////////////////////////////////////////////////////////// + // + // The following methods are only invoked on the DB thread. + // + ////////////////////////////////////////////////////////////////////////////// + WebDatabase::State PerformKeywordOperationsImpl( + const KeywordTable::Operations& operations, + WebDatabase* db); + scoped_ptr<WDTypedResult> GetKeywordsImpl(WebDatabase* db); + WebDatabase::State SetDefaultSearchProviderIDImpl(TemplateURLID id, + WebDatabase* db); + WebDatabase::State SetBuiltinKeywordVersionImpl(int version, WebDatabase* db); + + size_t batch_mode_level_; + KeywordTable::Operations queued_keyword_operations_; + + DISALLOW_COPY_AND_ASSIGN(KeywordWebDataService); +}; + +#endif // COMPONENTS_SEARCH_ENGINES_KEYWORD_WEB_DATA_SERVICE_H__ diff --git a/components/search_engines/search_host_to_urls_map.cc b/components/search_engines/search_host_to_urls_map.cc new file mode 100644 index 0000000..e309b07 --- /dev/null +++ b/components/search_engines/search_host_to_urls_map.cc @@ -0,0 +1,81 @@ +// Copyright 2014 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 "components/search_engines/search_host_to_urls_map.h" + +#include "base/memory/scoped_ptr.h" +#include "components/search_engines/template_url.h" + +SearchHostToURLsMap::SearchHostToURLsMap() + : initialized_(false) { +} + +SearchHostToURLsMap::~SearchHostToURLsMap() { +} + +void SearchHostToURLsMap::Init( + const TemplateURLService::TemplateURLVector& template_urls, + const SearchTermsData& search_terms_data) { + DCHECK(!initialized_); + initialized_ = true; // Set here so Add doesn't assert. + Add(template_urls, search_terms_data); +} + +void SearchHostToURLsMap::Add(TemplateURL* template_url, + const SearchTermsData& search_terms_data) { + DCHECK(initialized_); + DCHECK(template_url); + + const GURL url(template_url->GenerateSearchURL(search_terms_data)); + if (!url.is_valid() || !url.has_host()) + return; + + host_to_urls_map_[url.host()].insert(template_url); +} + +void SearchHostToURLsMap::Remove(TemplateURL* template_url) { + DCHECK(initialized_); + DCHECK(template_url); + + for (HostToURLsMap::iterator i = host_to_urls_map_.begin(); + i != host_to_urls_map_.end(); ++i) { + TemplateURLSet::iterator url_set_iterator = i->second.find(template_url); + if (url_set_iterator != i->second.end()) { + i->second.erase(url_set_iterator); + if (i->second.empty()) + host_to_urls_map_.erase(i); + // A given TemplateURL only occurs once in the map. As soon as we find the + // entry, stop. + return; + } + } +} + +TemplateURL* SearchHostToURLsMap::GetTemplateURLForHost( + const std::string& host) { + DCHECK(initialized_); + + HostToURLsMap::const_iterator iter = host_to_urls_map_.find(host); + if (iter == host_to_urls_map_.end() || iter->second.empty()) + return NULL; + return *(iter->second.begin()); // Return the 1st element. +} + +SearchHostToURLsMap::TemplateURLSet* SearchHostToURLsMap::GetURLsForHost( + const std::string& host) { + DCHECK(initialized_); + + HostToURLsMap::iterator urls_for_host = host_to_urls_map_.find(host); + if (urls_for_host == host_to_urls_map_.end() || urls_for_host->second.empty()) + return NULL; + return &urls_for_host->second; +} + +void SearchHostToURLsMap::Add( + const TemplateURLService::TemplateURLVector& template_urls, + const SearchTermsData& search_terms_data) { + for (TemplateURLService::TemplateURLVector::const_iterator i( + template_urls.begin()); i != template_urls.end(); ++i) + Add(*i, search_terms_data); +} diff --git a/components/search_engines/search_host_to_urls_map.h b/components/search_engines/search_host_to_urls_map.h new file mode 100644 index 0000000..6c580b5 --- /dev/null +++ b/components/search_engines/search_host_to_urls_map.h @@ -0,0 +1,66 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_SEARCH_ENGINES_SEARCH_HOST_TO_URLS_MAP_H_ +#define COMPONENTS_SEARCH_ENGINES_SEARCH_HOST_TO_URLS_MAP_H_ + +#include <map> +#include <set> +#include <string> + +#include "base/basictypes.h" +#include "components/search_engines/template_url_service.h" + +// Holds the host to template url mappings for the search providers. WARNING: +// This class does not own any TemplateURLs passed to it and it is up to the +// caller to ensure the right lifetime of them. +class SearchHostToURLsMap { + public: + typedef std::set<TemplateURL*> TemplateURLSet; + + SearchHostToURLsMap(); + ~SearchHostToURLsMap(); + + // Initializes the map. + void Init(const TemplateURLService::TemplateURLVector& template_urls, + const SearchTermsData& search_terms_data); + + // Adds a new TemplateURL to the map. Since |template_url| is owned + // externally, Remove or RemoveAll should be called if it becomes invalid. + void Add(TemplateURL* template_url, + const SearchTermsData& search_terms_data); + + // Removes the TemplateURL from the lookup. + void Remove(TemplateURL* template_url); + + // Returns the first TemplateURL found with a URL using the specified |host|, + // or NULL if there are no such TemplateURLs + TemplateURL* GetTemplateURLForHost(const std::string& host); + + // Return the TemplateURLSet for the given the |host| or NULL if there are + // none. + TemplateURLSet* GetURLsForHost(const std::string& host); + + private: + friend class SearchHostToURLsMapTest; + + typedef std::map<std::string, TemplateURLSet> HostToURLsMap; + + // Adds many URLs to the map. + void Add(const TemplateURLService::TemplateURLVector& template_urls, + const SearchTermsData& search_terms_data); + + // Maps from host to set of TemplateURLs whose search url host is host. + HostToURLsMap host_to_urls_map_; + + // The security origin for the default search provider. + std::string default_search_origin_; + + // Has Init been called? + bool initialized_; + + DISALLOW_COPY_AND_ASSIGN(SearchHostToURLsMap); +}; + +#endif // COMPONENTS_SEARCH_ENGINES_SEARCH_HOST_TO_URLS_MAP_H_ diff --git a/components/search_engines/template_url_service.cc b/components/search_engines/template_url_service.cc new file mode 100644 index 0000000..3eb3334 --- /dev/null +++ b/components/search_engines/template_url_service.cc @@ -0,0 +1,2372 @@ +// Copyright 2014 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 "components/search_engines/template_url_service.h" + +#include <algorithm> +#include <utility> + +#include "base/auto_reset.h" +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/guid.h" +#include "base/i18n/case_conversion.h" +#include "base/memory/scoped_vector.h" +#include "base/metrics/histogram.h" +#include "base/prefs/pref_service.h" +#include "base/stl_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "components/rappor/rappor_service.h" +#include "components/search_engines/search_engines_pref_names.h" +#include "components/search_engines/search_host_to_urls_map.h" +#include "components/search_engines/search_terms_data.h" +#include "components/search_engines/template_url.h" +#include "components/search_engines/template_url_prepopulate_data.h" +#include "components/search_engines/template_url_service_client.h" +#include "components/search_engines/template_url_service_observer.h" +#include "components/search_engines/util.h" +#include "components/url_fixer/url_fixer.h" +#include "net/base/net_util.h" +#include "net/base/registry_controlled_domains/registry_controlled_domain.h" +#include "sync/api/sync_change.h" +#include "sync/api/sync_error_factory.h" +#include "sync/protocol/search_engine_specifics.pb.h" +#include "sync/protocol/sync.pb.h" +#include "url/gurl.h" + +typedef SearchHostToURLsMap::TemplateURLSet TemplateURLSet; +typedef TemplateURLService::SyncDataMap SyncDataMap; + +namespace { + +bool IdenticalSyncGUIDs(const TemplateURLData* data, const TemplateURL* turl) { + if (!data || !turl) + return !data && !turl; + + return data->sync_guid == turl->sync_guid(); +} + +const char kDeleteSyncedEngineHistogramName[] = + "Search.DeleteSyncedSearchEngine"; + +// Values for an enumerated histogram used to track whenever an ACTION_DELETE is +// sent to the server for search engines. +enum DeleteSyncedSearchEngineEvent { + DELETE_ENGINE_USER_ACTION, + DELETE_ENGINE_PRE_SYNC, + DELETE_ENGINE_EMPTY_FIELD, + DELETE_ENGINE_MAX, +}; + +// Returns true iff the change in |change_list| at index |i| should not be sent +// up to the server based on its GUIDs presence in |sync_data| or when compared +// to changes after it in |change_list|. +// The criteria is: +// 1) It is an ACTION_UPDATE or ACTION_DELETE and the sync_guid associated +// with it is NOT found in |sync_data|. We can only update and remove +// entries that were originally from the Sync server. +// 2) It is an ACTION_ADD and the sync_guid associated with it is found in +// |sync_data|. We cannot re-add entries that Sync already knew about. +// 3) There is an update after an update for the same GUID. We prune earlier +// ones just to save bandwidth (Sync would normally coalesce them). +bool ShouldRemoveSyncChange(size_t index, + syncer::SyncChangeList* change_list, + const SyncDataMap* sync_data) { + DCHECK(index < change_list->size()); + const syncer::SyncChange& change_i = (*change_list)[index]; + const std::string guid = change_i.sync_data().GetSpecifics() + .search_engine().sync_guid(); + syncer::SyncChange::SyncChangeType type = change_i.change_type(); + if ((type == syncer::SyncChange::ACTION_UPDATE || + type == syncer::SyncChange::ACTION_DELETE) && + sync_data->find(guid) == sync_data->end()) + return true; + if (type == syncer::SyncChange::ACTION_ADD && + sync_data->find(guid) != sync_data->end()) + return true; + if (type == syncer::SyncChange::ACTION_UPDATE) { + for (size_t j = index + 1; j < change_list->size(); j++) { + const syncer::SyncChange& change_j = (*change_list)[j]; + if ((syncer::SyncChange::ACTION_UPDATE == change_j.change_type()) && + (change_j.sync_data().GetSpecifics().search_engine().sync_guid() == + guid)) + return true; + } + } + return false; +} + +// Remove SyncChanges that should not be sent to the server from |change_list|. +// This is done to eliminate incorrect SyncChanges added by the merge and +// conflict resolution logic when it is unsure of whether or not an entry is new +// from Sync or originally from the local model. This also removes changes that +// would be otherwise be coalesced by Sync in order to save bandwidth. +void PruneSyncChanges(const SyncDataMap* sync_data, + syncer::SyncChangeList* change_list) { + for (size_t i = 0; i < change_list->size(); ) { + if (ShouldRemoveSyncChange(i, change_list, sync_data)) + change_list->erase(change_list->begin() + i); + else + ++i; + } +} + +// Returns true if |turl|'s GUID is not found inside |sync_data|. This is to be +// used in MergeDataAndStartSyncing to differentiate between TemplateURLs from +// Sync and TemplateURLs that were initially local, assuming |sync_data| is the +// |initial_sync_data| parameter. +bool IsFromSync(const TemplateURL* turl, const SyncDataMap& sync_data) { + return !!sync_data.count(turl->sync_guid()); +} + +// Log the number of instances of a keyword that exist, with zero or more +// underscores, which could occur as the result of conflict resolution. +void LogDuplicatesHistogram( + const TemplateURLService::TemplateURLVector& template_urls) { + std::map<std::string, int> duplicates; + for (TemplateURLService::TemplateURLVector::const_iterator it = + template_urls.begin(); it != template_urls.end(); ++it) { + std::string keyword = base::UTF16ToASCII((*it)->keyword()); + base::TrimString(keyword, "_", &keyword); + duplicates[keyword]++; + } + + // Count the keywords with duplicates. + int num_dupes = 0; + for (std::map<std::string, int>::const_iterator it = duplicates.begin(); + it != duplicates.end(); ++it) { + if (it->second > 1) + num_dupes++; + } + + UMA_HISTOGRAM_COUNTS_100("Search.SearchEngineDuplicateCounts", num_dupes); +} + +} // namespace + + +// TemplateURLService::LessWithPrefix ----------------------------------------- + +class TemplateURLService::LessWithPrefix { + public: + // We want to find the set of keywords that begin with a prefix. The STL + // algorithms will return the set of elements that are "equal to" the + // prefix, where "equal(x, y)" means "!(cmp(x, y) || cmp(y, x))". When + // cmp() is the typical std::less<>, this results in lexicographic equality; + // we need to extend this to mark a prefix as "not less than" a keyword it + // begins, which will cause the desired elements to be considered "equal to" + // the prefix. Note: this is still a strict weak ordering, as required by + // equal_range() (though I will not prove that here). + // + // Unfortunately the calling convention is not "prefix and element" but + // rather "two elements", so we pass the prefix as a fake "element" which has + // a NULL KeywordDataElement pointer. + bool operator()(const KeywordToTemplateMap::value_type& elem1, + const KeywordToTemplateMap::value_type& elem2) const { + return (elem1.second == NULL) ? + (elem2.first.compare(0, elem1.first.length(), elem1.first) > 0) : + (elem1.first < elem2.first); + } +}; + + +// TemplateURLService --------------------------------------------------------- + +TemplateURLService::TemplateURLService( + PrefService* prefs, + scoped_ptr<SearchTermsData> search_terms_data, + KeywordWebDataService* web_data_service, + scoped_ptr<TemplateURLServiceClient> client, + GoogleURLTracker* google_url_tracker, + rappor::RapporService* rappor_service, + const base::Closure& dsp_change_callback) + : prefs_(prefs), + search_terms_data_(search_terms_data.Pass()), + web_data_service_(web_data_service), + client_(client.Pass()), + google_url_tracker_(google_url_tracker), + rappor_service_(rappor_service), + dsp_change_callback_(dsp_change_callback), + provider_map_(new SearchHostToURLsMap), + loaded_(false), + load_failed_(false), + load_handle_(0), + default_search_provider_(NULL), + next_id_(kInvalidTemplateURLID + 1), + time_provider_(&base::Time::Now), + models_associated_(false), + processing_syncer_changes_(false), + dsp_change_origin_(DSP_CHANGE_OTHER), + default_search_manager_( + prefs_, + base::Bind(&TemplateURLService::OnDefaultSearchChange, + base::Unretained(this))) { + DCHECK(search_terms_data_); + Init(NULL, 0); +} + +TemplateURLService::TemplateURLService(const Initializer* initializers, + const int count) + : prefs_(NULL), + search_terms_data_(new SearchTermsData), + web_data_service_(NULL), + google_url_tracker_(NULL), + rappor_service_(NULL), + provider_map_(new SearchHostToURLsMap), + loaded_(false), + load_failed_(false), + load_handle_(0), + default_search_provider_(NULL), + next_id_(kInvalidTemplateURLID + 1), + time_provider_(&base::Time::Now), + models_associated_(false), + processing_syncer_changes_(false), + dsp_change_origin_(DSP_CHANGE_OTHER), + default_search_manager_( + prefs_, + base::Bind(&TemplateURLService::OnDefaultSearchChange, + base::Unretained(this))) { + Init(initializers, count); +} + +TemplateURLService::~TemplateURLService() { + // |web_data_service_| should be deleted during Shutdown(). + DCHECK(!web_data_service_); + STLDeleteElements(&template_urls_); +} + +// static +bool TemplateURLService::LoadDefaultSearchProviderFromPrefs( + PrefService* prefs, + scoped_ptr<TemplateURLData>* default_provider_data, + bool* is_managed) { + if (!prefs || !prefs->HasPrefPath(prefs::kDefaultSearchProviderSearchURL) || + !prefs->HasPrefPath(prefs::kDefaultSearchProviderKeyword)) + return false; + + const PrefService::Preference* pref = + prefs->FindPreference(prefs::kDefaultSearchProviderSearchURL); + *is_managed = pref && pref->IsManaged(); + + if (!prefs->GetBoolean(prefs::kDefaultSearchProviderEnabled)) { + // The user doesn't want a default search provider. + default_provider_data->reset(NULL); + return true; + } + + base::string16 name = + base::UTF8ToUTF16(prefs->GetString(prefs::kDefaultSearchProviderName)); + base::string16 keyword = + base::UTF8ToUTF16(prefs->GetString(prefs::kDefaultSearchProviderKeyword)); + if (keyword.empty()) + return false; + std::string search_url = + prefs->GetString(prefs::kDefaultSearchProviderSearchURL); + // Force URL to be non-empty. We've never supported this case, but past bugs + // might have resulted in it slipping through; eventually this code can be + // replaced with a DCHECK(!search_url.empty());. + if (search_url.empty()) + return false; + std::string suggest_url = + prefs->GetString(prefs::kDefaultSearchProviderSuggestURL); + std::string instant_url = + prefs->GetString(prefs::kDefaultSearchProviderInstantURL); + std::string image_url = + prefs->GetString(prefs::kDefaultSearchProviderImageURL); + std::string new_tab_url = + prefs->GetString(prefs::kDefaultSearchProviderNewTabURL); + std::string search_url_post_params = + prefs->GetString(prefs::kDefaultSearchProviderSearchURLPostParams); + std::string suggest_url_post_params = + prefs->GetString(prefs::kDefaultSearchProviderSuggestURLPostParams); + std::string instant_url_post_params = + prefs->GetString(prefs::kDefaultSearchProviderInstantURLPostParams); + std::string image_url_post_params = + prefs->GetString(prefs::kDefaultSearchProviderImageURLPostParams); + std::string icon_url = + prefs->GetString(prefs::kDefaultSearchProviderIconURL); + std::string encodings = + prefs->GetString(prefs::kDefaultSearchProviderEncodings); + std::string id_string = prefs->GetString(prefs::kDefaultSearchProviderID); + std::string prepopulate_id = + prefs->GetString(prefs::kDefaultSearchProviderPrepopulateID); + const base::ListValue* alternate_urls = + prefs->GetList(prefs::kDefaultSearchProviderAlternateURLs); + std::string search_terms_replacement_key = prefs->GetString( + prefs::kDefaultSearchProviderSearchTermsReplacementKey); + + default_provider_data->reset(new TemplateURLData); + (*default_provider_data)->short_name = name; + (*default_provider_data)->SetKeyword(keyword); + (*default_provider_data)->SetURL(search_url); + (*default_provider_data)->suggestions_url = suggest_url; + (*default_provider_data)->instant_url = instant_url; + (*default_provider_data)->image_url = image_url; + (*default_provider_data)->new_tab_url = new_tab_url; + (*default_provider_data)->search_url_post_params = search_url_post_params; + (*default_provider_data)->suggestions_url_post_params = + suggest_url_post_params; + (*default_provider_data)->instant_url_post_params = instant_url_post_params; + (*default_provider_data)->image_url_post_params = image_url_post_params; + (*default_provider_data)->favicon_url = GURL(icon_url); + (*default_provider_data)->show_in_default_list = true; + (*default_provider_data)->alternate_urls.clear(); + for (size_t i = 0; i < alternate_urls->GetSize(); ++i) { + std::string alternate_url; + if (alternate_urls->GetString(i, &alternate_url)) + (*default_provider_data)->alternate_urls.push_back(alternate_url); + } + (*default_provider_data)->search_terms_replacement_key = + search_terms_replacement_key; + base::SplitString(encodings, ';', &(*default_provider_data)->input_encodings); + if (!id_string.empty() && !*is_managed) { + int64 value; + base::StringToInt64(id_string, &value); + (*default_provider_data)->id = value; + } + if (!prepopulate_id.empty() && !*is_managed) { + int value; + base::StringToInt(prepopulate_id, &value); + (*default_provider_data)->prepopulate_id = value; + } + return true; +} + +// static +base::string16 TemplateURLService::CleanUserInputKeyword( + const base::string16& keyword) { + // Remove the scheme. + base::string16 result(base::i18n::ToLower(keyword)); + base::TrimWhitespace(result, base::TRIM_ALL, &result); + url::Component scheme_component; + if (url::ExtractScheme(base::UTF16ToUTF8(keyword).c_str(), + static_cast<int>(keyword.length()), + &scheme_component)) { + // If the scheme isn't "http" or "https", bail. The user isn't trying to + // type a web address, but rather an FTP, file:, or other scheme URL, or a + // search query with some sort of initial operator (e.g. "site:"). + if (result.compare(0, scheme_component.end(), + base::ASCIIToUTF16(url::kHttpScheme)) && + result.compare(0, scheme_component.end(), + base::ASCIIToUTF16(url::kHttpsScheme))) + return base::string16(); + + // Include trailing ':'. + result.erase(0, scheme_component.end() + 1); + // Many schemes usually have "//" after them, so strip it too. + const base::string16 after_scheme(base::ASCIIToUTF16("//")); + if (result.compare(0, after_scheme.length(), after_scheme) == 0) + result.erase(0, after_scheme.length()); + } + + // Remove leading "www.". + result = net::StripWWW(result); + + // Remove trailing "/". + return (result.length() > 0 && result[result.length() - 1] == '/') ? + result.substr(0, result.length() - 1) : result; +} + +// static +void TemplateURLService::SaveDefaultSearchProviderToPrefs( + const TemplateURL* t_url, + PrefService* prefs) { + if (!prefs) + return; + + bool enabled = false; + std::string search_url; + std::string suggest_url; + std::string instant_url; + std::string image_url; + std::string new_tab_url; + std::string search_url_post_params; + std::string suggest_url_post_params; + std::string instant_url_post_params; + std::string image_url_post_params; + std::string icon_url; + std::string encodings; + std::string short_name; + std::string keyword; + std::string id_string; + std::string prepopulate_id; + base::ListValue alternate_urls; + std::string search_terms_replacement_key; + if (t_url) { + DCHECK_EQ(TemplateURL::NORMAL, t_url->GetType()); + enabled = true; + search_url = t_url->url(); + suggest_url = t_url->suggestions_url(); + instant_url = t_url->instant_url(); + image_url = t_url->image_url(); + new_tab_url = t_url->new_tab_url(); + search_url_post_params = t_url->search_url_post_params(); + suggest_url_post_params = t_url->suggestions_url_post_params(); + instant_url_post_params = t_url->instant_url_post_params(); + image_url_post_params = t_url->image_url_post_params(); + GURL icon_gurl = t_url->favicon_url(); + if (!icon_gurl.is_empty()) + icon_url = icon_gurl.spec(); + encodings = JoinString(t_url->input_encodings(), ';'); + short_name = base::UTF16ToUTF8(t_url->short_name()); + keyword = base::UTF16ToUTF8(t_url->keyword()); + id_string = base::Int64ToString(t_url->id()); + prepopulate_id = base::Int64ToString(t_url->prepopulate_id()); + for (size_t i = 0; i < t_url->alternate_urls().size(); ++i) + alternate_urls.AppendString(t_url->alternate_urls()[i]); + search_terms_replacement_key = t_url->search_terms_replacement_key(); + } + prefs->SetBoolean(prefs::kDefaultSearchProviderEnabled, enabled); + prefs->SetString(prefs::kDefaultSearchProviderSearchURL, search_url); + prefs->SetString(prefs::kDefaultSearchProviderSuggestURL, suggest_url); + prefs->SetString(prefs::kDefaultSearchProviderInstantURL, instant_url); + prefs->SetString(prefs::kDefaultSearchProviderImageURL, image_url); + prefs->SetString(prefs::kDefaultSearchProviderNewTabURL, new_tab_url); + prefs->SetString(prefs::kDefaultSearchProviderSearchURLPostParams, + search_url_post_params); + prefs->SetString(prefs::kDefaultSearchProviderSuggestURLPostParams, + suggest_url_post_params); + prefs->SetString(prefs::kDefaultSearchProviderInstantURLPostParams, + instant_url_post_params); + prefs->SetString(prefs::kDefaultSearchProviderImageURLPostParams, + image_url_post_params); + prefs->SetString(prefs::kDefaultSearchProviderIconURL, icon_url); + prefs->SetString(prefs::kDefaultSearchProviderEncodings, encodings); + prefs->SetString(prefs::kDefaultSearchProviderName, short_name); + prefs->SetString(prefs::kDefaultSearchProviderKeyword, keyword); + prefs->SetString(prefs::kDefaultSearchProviderID, id_string); + prefs->SetString(prefs::kDefaultSearchProviderPrepopulateID, prepopulate_id); + prefs->Set(prefs::kDefaultSearchProviderAlternateURLs, alternate_urls); + prefs->SetString(prefs::kDefaultSearchProviderSearchTermsReplacementKey, + search_terms_replacement_key); +} + +bool TemplateURLService::CanReplaceKeyword( + const base::string16& keyword, + const GURL& url, + TemplateURL** template_url_to_replace) { + DCHECK(!keyword.empty()); // This should only be called for non-empty + // keywords. If we need to support empty kewords + // the code needs to change slightly. + TemplateURL* existing_url = GetTemplateURLForKeyword(keyword); + if (template_url_to_replace) + *template_url_to_replace = existing_url; + if (existing_url) { + // We already have a TemplateURL for this keyword. Only allow it to be + // replaced if the TemplateURL can be replaced. + return CanReplace(existing_url); + } + + // We don't have a TemplateURL with keyword. Only allow a new one if there + // isn't a TemplateURL for the specified host, or there is one but it can + // be replaced. We do this to ensure that if the user assigns a different + // keyword to a generated TemplateURL, we won't regenerate another keyword for + // the same host. + return !url.is_valid() || url.host().empty() || + CanReplaceKeywordForHost(url.host(), template_url_to_replace); +} + +void TemplateURLService::FindMatchingKeywords( + const base::string16& prefix, + bool support_replacement_only, + TemplateURLVector* matches) { + // Sanity check args. + if (prefix.empty()) + return; + DCHECK(matches != NULL); + DCHECK(matches->empty()); // The code for exact matches assumes this. + + // Required for VS2010: http://connect.microsoft.com/VisualStudio/feedback/details/520043/error-converting-from-null-to-a-pointer-type-in-std-pair + TemplateURL* const kNullTemplateURL = NULL; + + // Find matching keyword range. Searches the element map for keywords + // beginning with |prefix| and stores the endpoints of the resulting set in + // |match_range|. + const std::pair<KeywordToTemplateMap::const_iterator, + KeywordToTemplateMap::const_iterator> match_range( + std::equal_range( + keyword_to_template_map_.begin(), keyword_to_template_map_.end(), + KeywordToTemplateMap::value_type(prefix, kNullTemplateURL), + LessWithPrefix())); + + // Return vector of matching keywords. + for (KeywordToTemplateMap::const_iterator i(match_range.first); + i != match_range.second; ++i) { + if (!support_replacement_only || + i->second->url_ref().SupportsReplacement(search_terms_data())) + matches->push_back(i->second); + } +} + +TemplateURL* TemplateURLService::GetTemplateURLForKeyword( + const base::string16& keyword) { + KeywordToTemplateMap::const_iterator elem( + keyword_to_template_map_.find(keyword)); + if (elem != keyword_to_template_map_.end()) + return elem->second; + return (!loaded_ && + initial_default_search_provider_.get() && + (initial_default_search_provider_->keyword() == keyword)) ? + initial_default_search_provider_.get() : NULL; +} + +TemplateURL* TemplateURLService::GetTemplateURLForGUID( + const std::string& sync_guid) { + GUIDToTemplateMap::const_iterator elem(guid_to_template_map_.find(sync_guid)); + if (elem != guid_to_template_map_.end()) + return elem->second; + return (!loaded_ && + initial_default_search_provider_.get() && + (initial_default_search_provider_->sync_guid() == sync_guid)) ? + initial_default_search_provider_.get() : NULL; +} + +TemplateURL* TemplateURLService::GetTemplateURLForHost( + const std::string& host) { + if (loaded_) + return provider_map_->GetTemplateURLForHost(host); + TemplateURL* initial_dsp = initial_default_search_provider_.get(); + if (!initial_dsp) + return NULL; + return (initial_dsp->GenerateSearchURL(search_terms_data()).host() == host) ? + initial_dsp : NULL; +} + +bool TemplateURLService::Add(TemplateURL* template_url) { + KeywordWebDataService::BatchModeScoper scoper(web_data_service_.get()); + if (!AddNoNotify(template_url, true)) + return false; + NotifyObservers(); + return true; +} + +void TemplateURLService::AddWithOverrides(TemplateURL* template_url, + const base::string16& short_name, + const base::string16& keyword, + const std::string& url) { + DCHECK(!keyword.empty()); + DCHECK(!url.empty()); + template_url->data_.short_name = short_name; + template_url->data_.SetKeyword(keyword); + template_url->SetURL(url); + Add(template_url); +} + +void TemplateURLService::AddExtensionControlledTURL( + TemplateURL* template_url, + scoped_ptr<TemplateURL::AssociatedExtensionInfo> info) { + DCHECK(loaded_); + DCHECK(template_url); + DCHECK_EQ(kInvalidTemplateURLID, template_url->id()); + DCHECK(info); + DCHECK_NE(TemplateURL::NORMAL, info->type); + DCHECK_EQ(info->wants_to_be_default_engine, + template_url->show_in_default_list()); + DCHECK(!FindTemplateURLForExtension(info->extension_id, info->type)); + template_url->extension_info_.swap(info); + + KeywordWebDataService::BatchModeScoper scoper(web_data_service_.get()); + if (AddNoNotify(template_url, true)) { + if (template_url->extension_info_->wants_to_be_default_engine) + UpdateExtensionDefaultSearchEngine(); + NotifyObservers(); + } +} + +void TemplateURLService::Remove(TemplateURL* template_url) { + RemoveNoNotify(template_url); + NotifyObservers(); +} + +void TemplateURLService::RemoveExtensionControlledTURL( + const std::string& extension_id, + TemplateURL::Type type) { + DCHECK(loaded_); + TemplateURL* url = FindTemplateURLForExtension(extension_id, type); + if (!url) + return; + // NULL this out so that we can call RemoveNoNotify. + // UpdateExtensionDefaultSearchEngine will cause it to be reset. + if (default_search_provider_ == url) + default_search_provider_ = NULL; + KeywordWebDataService::BatchModeScoper scoper(web_data_service_.get()); + RemoveNoNotify(url); + UpdateExtensionDefaultSearchEngine(); + NotifyObservers(); +} + +void TemplateURLService::RemoveAutoGeneratedSince(base::Time created_after) { + RemoveAutoGeneratedBetween(created_after, base::Time()); +} + +void TemplateURLService::RemoveAutoGeneratedBetween(base::Time created_after, + base::Time created_before) { + RemoveAutoGeneratedForOriginBetween(GURL(), created_after, created_before); +} + +void TemplateURLService::RemoveAutoGeneratedForOriginBetween( + const GURL& origin, + base::Time created_after, + base::Time created_before) { + GURL o(origin.GetOrigin()); + bool should_notify = false; + KeywordWebDataService::BatchModeScoper scoper(web_data_service_.get()); + for (size_t i = 0; i < template_urls_.size();) { + if (template_urls_[i]->date_created() >= created_after && + (created_before.is_null() || + template_urls_[i]->date_created() < created_before) && + CanReplace(template_urls_[i]) && + (o.is_empty() || + template_urls_[i]->GenerateSearchURL( + search_terms_data()).GetOrigin() == o)) { + RemoveNoNotify(template_urls_[i]); + should_notify = true; + } else { + ++i; + } + } + if (should_notify) + NotifyObservers(); +} + +void TemplateURLService::RegisterOmniboxKeyword( + const std::string& extension_id, + const std::string& extension_name, + const std::string& keyword, + const std::string& template_url_string) { + DCHECK(loaded_); + + if (FindTemplateURLForExtension(extension_id, + TemplateURL::OMNIBOX_API_EXTENSION)) + return; + + TemplateURLData data; + data.short_name = base::UTF8ToUTF16(extension_name); + data.SetKeyword(base::UTF8ToUTF16(keyword)); + data.SetURL(template_url_string); + TemplateURL* url = new TemplateURL(data); + scoped_ptr<TemplateURL::AssociatedExtensionInfo> info( + new TemplateURL::AssociatedExtensionInfo( + TemplateURL::OMNIBOX_API_EXTENSION, extension_id)); + AddExtensionControlledTURL(url, info.Pass()); +} + +TemplateURLService::TemplateURLVector TemplateURLService::GetTemplateURLs() { + return template_urls_; +} + +void TemplateURLService::IncrementUsageCount(TemplateURL* url) { + DCHECK(url); + // Extension-controlled search engines are not persisted. + if (url->GetType() == TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION) + return; + if (std::find(template_urls_.begin(), template_urls_.end(), url) == + template_urls_.end()) + return; + ++url->data_.usage_count; + + if (web_data_service_) + web_data_service_->UpdateKeyword(url->data()); +} + +void TemplateURLService::ResetTemplateURL(TemplateURL* url, + const base::string16& title, + const base::string16& keyword, + const std::string& search_url) { + if (ResetTemplateURLNoNotify(url, title, keyword, search_url)) + NotifyObservers(); +} + +bool TemplateURLService::CanMakeDefault(const TemplateURL* url) { + return + ((default_search_provider_source_ == DefaultSearchManager::FROM_USER) || + (default_search_provider_source_ == + DefaultSearchManager::FROM_FALLBACK)) && + (url != GetDefaultSearchProvider()) && + url->url_ref().SupportsReplacement(search_terms_data()) && + (url->GetType() == TemplateURL::NORMAL); +} + +void TemplateURLService::SetUserSelectedDefaultSearchProvider( + TemplateURL* url) { + // Omnibox keywords cannot be made default. Extension-controlled search + // engines can be made default only by the extension itself because they + // aren't persisted. + DCHECK(!url || (url->GetType() == TemplateURL::NORMAL)); + if (load_failed_) { + // Skip the DefaultSearchManager, which will persist to user preferences. + if ((default_search_provider_source_ == DefaultSearchManager::FROM_USER) || + (default_search_provider_source_ == + DefaultSearchManager::FROM_FALLBACK)) { + ApplyDefaultSearchChange(url ? &url->data() : NULL, + DefaultSearchManager::FROM_USER); + } + } else { + // We rely on the DefaultSearchManager to call OnDefaultSearchChange if, in + // fact, the effective DSE changes. + if (url) + default_search_manager_.SetUserSelectedDefaultSearchEngine(url->data()); + else + default_search_manager_.ClearUserSelectedDefaultSearchEngine(); + } +} + +TemplateURL* TemplateURLService::GetDefaultSearchProvider() { + return loaded_ ? + default_search_provider_ : initial_default_search_provider_.get(); +} + +bool TemplateURLService::IsSearchResultsPageFromDefaultSearchProvider( + const GURL& url) { + TemplateURL* default_provider = GetDefaultSearchProvider(); + return default_provider && + default_provider->IsSearchURL(url, search_terms_data()); +} + +bool TemplateURLService::IsExtensionControlledDefaultSearch() { + return default_search_provider_source_ == + DefaultSearchManager::FROM_EXTENSION; +} + +void TemplateURLService::RepairPrepopulatedSearchEngines() { + // Can't clean DB if it hasn't been loaded. + DCHECK(loaded()); + + if ((default_search_provider_source_ == DefaultSearchManager::FROM_USER) || + (default_search_provider_source_ == + DefaultSearchManager::FROM_FALLBACK)) { + // Clear |default_search_provider_| in case we want to remove the engine it + // points to. This will get reset at the end of the function anyway. + default_search_provider_ = NULL; + } + + size_t default_search_provider_index = 0; + ScopedVector<TemplateURLData> prepopulated_urls = + TemplateURLPrepopulateData::GetPrepopulatedEngines( + prefs_, &default_search_provider_index); + DCHECK(!prepopulated_urls.empty()); + ActionsFromPrepopulateData actions(CreateActionsFromCurrentPrepopulateData( + &prepopulated_urls, template_urls_, default_search_provider_)); + + KeywordWebDataService::BatchModeScoper scoper(web_data_service_.get()); + + // Remove items. + for (std::vector<TemplateURL*>::iterator i = actions.removed_engines.begin(); + i < actions.removed_engines.end(); ++i) + RemoveNoNotify(*i); + + // Edit items. + for (EditedEngines::iterator i(actions.edited_engines.begin()); + i < actions.edited_engines.end(); ++i) { + TemplateURL new_values(i->second); + UpdateNoNotify(i->first, new_values); + } + + // Add items. + for (std::vector<TemplateURLData>::const_iterator i = + actions.added_engines.begin(); + i < actions.added_engines.end(); + ++i) { + AddNoNotify(new TemplateURL(*i), true); + } + + base::AutoReset<DefaultSearchChangeOrigin> change_origin( + &dsp_change_origin_, DSP_CHANGE_PROFILE_RESET); + + default_search_manager_.ClearUserSelectedDefaultSearchEngine(); + + if (!default_search_provider_) { + // If the default search provider came from a user pref we would have been + // notified of the new (fallback-provided) value in + // ClearUserSelectedDefaultSearchEngine() above. Since we are here, the + // value was presumably originally a fallback value (which may have been + // repaired). + DefaultSearchManager::Source source; + const TemplateURLData* new_dse = + default_search_manager_.GetDefaultSearchEngine(&source); + // ApplyDefaultSearchChange will notify observers once it is done. + ApplyDefaultSearchChange(new_dse, source); + } else { + NotifyObservers(); + } +} + +void TemplateURLService::AddObserver(TemplateURLServiceObserver* observer) { + model_observers_.AddObserver(observer); +} + +void TemplateURLService::RemoveObserver(TemplateURLServiceObserver* observer) { + model_observers_.RemoveObserver(observer); +} + +void TemplateURLService::Load() { + if (loaded_ || load_handle_) + return; + + if (web_data_service_) + load_handle_ = web_data_service_->GetKeywords(this); + else + ChangeToLoadedState(); +} + +scoped_ptr<TemplateURLService::Subscription> + TemplateURLService::RegisterOnLoadedCallback( + const base::Closure& callback) { + return loaded_ ? + scoped_ptr<TemplateURLService::Subscription>() : + on_loaded_callbacks_.Add(callback); +} + +void TemplateURLService::OnWebDataServiceRequestDone( + KeywordWebDataService::Handle h, + const WDTypedResult* result) { + // Reset the load_handle so that we don't try and cancel the load in + // the destructor. + load_handle_ = 0; + + if (!result) { + // Results are null if the database went away or (most likely) wasn't + // loaded. + load_failed_ = true; + web_data_service_ = NULL; + ChangeToLoadedState(); + return; + } + + TemplateURLVector template_urls; + int new_resource_keyword_version = 0; + GetSearchProvidersUsingKeywordResult( + *result, + web_data_service_.get(), + prefs_, + &template_urls, + (default_search_provider_source_ == DefaultSearchManager::FROM_USER) ? + initial_default_search_provider_.get() : NULL, + search_terms_data(), + &new_resource_keyword_version, + &pre_sync_deletes_); + + KeywordWebDataService::BatchModeScoper scoper(web_data_service_.get()); + + PatchMissingSyncGUIDs(&template_urls); + SetTemplateURLs(&template_urls); + + // This initializes provider_map_ which should be done before + // calling UpdateKeywordSearchTermsForURL. + // This also calls NotifyObservers. + ChangeToLoadedState(); + + // Index any visits that occurred before we finished loading. + for (size_t i = 0; i < visits_to_add_.size(); ++i) + UpdateKeywordSearchTermsForURL(visits_to_add_[i]); + visits_to_add_.clear(); + + if (new_resource_keyword_version) + web_data_service_->SetBuiltinKeywordVersion(new_resource_keyword_version); + + if (default_search_provider_) { + UMA_HISTOGRAM_ENUMERATION( + "Search.DefaultSearchProviderType", + TemplateURLPrepopulateData::GetEngineType( + *default_search_provider_, search_terms_data()), + SEARCH_ENGINE_MAX); + + if (rappor_service_) { + rappor_service_->RecordSample( + "Search.DefaultSearchProvider", + rappor::ETLD_PLUS_ONE_RAPPOR_TYPE, + net::registry_controlled_domains::GetDomainAndRegistry( + default_search_provider_->url_ref().GetHost(search_terms_data()), + net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)); + } + } +} + +base::string16 TemplateURLService::GetKeywordShortName( + const base::string16& keyword, + bool* is_omnibox_api_extension_keyword) { + const TemplateURL* template_url = GetTemplateURLForKeyword(keyword); + + // TODO(sky): Once LocationBarView adds a listener to the TemplateURLService + // to track changes to the model, this should become a DCHECK. + if (template_url) { + *is_omnibox_api_extension_keyword = + template_url->GetType() == TemplateURL::OMNIBOX_API_EXTENSION; + return template_url->AdjustedShortNameForLocaleDirection(); + } + *is_omnibox_api_extension_keyword = false; + return base::string16(); +} + +void TemplateURLService::OnHistoryURLVisited(const URLVisitedDetails& details) { + if (!loaded_) + visits_to_add_.push_back(details); + else + UpdateKeywordSearchTermsForURL(details); +} + +void TemplateURLService::Shutdown() { + // This check has to be done at Shutdown() instead of in the dtor to ensure + // that no clients of KeywordWebDataService are holding ptrs to it after the + // first phase of the KeyedService Shutdown() process. + if (load_handle_) { + DCHECK(web_data_service_.get()); + web_data_service_->CancelRequest(load_handle_); + } + web_data_service_ = NULL; +} + +syncer::SyncDataList TemplateURLService::GetAllSyncData( + syncer::ModelType type) const { + DCHECK_EQ(syncer::SEARCH_ENGINES, type); + + syncer::SyncDataList current_data; + for (TemplateURLVector::const_iterator iter = template_urls_.begin(); + iter != template_urls_.end(); ++iter) { + // We don't sync keywords managed by policy. + if ((*iter)->created_by_policy()) + continue; + // We don't sync extension-controlled search engines. + if ((*iter)->GetType() == TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION) + continue; + current_data.push_back(CreateSyncDataFromTemplateURL(**iter)); + } + + return current_data; +} + +syncer::SyncError TemplateURLService::ProcessSyncChanges( + const tracked_objects::Location& from_here, + const syncer::SyncChangeList& change_list) { + if (!models_associated_) { + syncer::SyncError error(FROM_HERE, + syncer::SyncError::DATATYPE_ERROR, + "Models not yet associated.", + syncer::SEARCH_ENGINES); + return error; + } + DCHECK(loaded_); + + base::AutoReset<bool> processing_changes(&processing_syncer_changes_, true); + + // We've started syncing, so set our origin member to the base Sync value. + // As we move through Sync Code, we may set this to increasingly specific + // origins so we can tell what exactly caused a DSP change. + base::AutoReset<DefaultSearchChangeOrigin> change_origin(&dsp_change_origin_, + DSP_CHANGE_SYNC_UNINTENTIONAL); + + KeywordWebDataService::BatchModeScoper scoper(web_data_service_.get()); + + syncer::SyncChangeList new_changes; + syncer::SyncError error; + for (syncer::SyncChangeList::const_iterator iter = change_list.begin(); + iter != change_list.end(); ++iter) { + DCHECK_EQ(syncer::SEARCH_ENGINES, iter->sync_data().GetDataType()); + + std::string guid = + iter->sync_data().GetSpecifics().search_engine().sync_guid(); + TemplateURL* existing_turl = GetTemplateURLForGUID(guid); + scoped_ptr<TemplateURL> turl(CreateTemplateURLFromTemplateURLAndSyncData( + prefs_, search_terms_data(), existing_turl, iter->sync_data(), + &new_changes)); + if (!turl.get()) + continue; + + // Explicitly don't check for conflicts against extension keywords; in this + // case the functions which modify the keyword map know how to handle the + // conflicts. + // TODO(mpcomplete): If we allow editing extension keywords, then those will + // need to undergo conflict resolution. + TemplateURL* existing_keyword_turl = + FindNonExtensionTemplateURLForKeyword(turl->keyword()); + if (iter->change_type() == syncer::SyncChange::ACTION_DELETE) { + if (!existing_turl) { + error = sync_error_factory_->CreateAndUploadError( + FROM_HERE, + "ProcessSyncChanges failed on ChangeType ACTION_DELETE"); + continue; + } + if (existing_turl == GetDefaultSearchProvider()) { + // The only way Sync can attempt to delete the default search provider + // is if we had changed the kSyncedDefaultSearchProviderGUID + // preference, but perhaps it has not yet been received. To avoid + // situations where this has come in erroneously, we will un-delete + // the current default search from the Sync data. If the pref really + // does arrive later, then default search will change to the correct + // entry, but we'll have this extra entry sitting around. The result is + // not ideal, but it prevents a far more severe bug where the default is + // unexpectedly swapped to something else. The user can safely delete + // the extra entry again later, if they choose. Most users who do not + // look at the search engines UI will not notice this. + // Note that we append a special character to the end of the keyword in + // an attempt to avoid a ping-poinging situation where receiving clients + // may try to continually delete the resurrected entry. + base::string16 updated_keyword = UniquifyKeyword(*existing_turl, true); + TemplateURLData data(existing_turl->data()); + data.SetKeyword(updated_keyword); + TemplateURL new_turl(data); + if (UpdateNoNotify(existing_turl, new_turl)) + NotifyObservers(); + + syncer::SyncData sync_data = CreateSyncDataFromTemplateURL(new_turl); + new_changes.push_back(syncer::SyncChange(FROM_HERE, + syncer::SyncChange::ACTION_ADD, + sync_data)); + // Ignore the delete attempt. This means we never end up resetting the + // default search provider due to an ACTION_DELETE from sync. + continue; + } + + Remove(existing_turl); + } else if (iter->change_type() == syncer::SyncChange::ACTION_ADD) { + if (existing_turl) { + error = sync_error_factory_->CreateAndUploadError( + FROM_HERE, + "ProcessSyncChanges failed on ChangeType ACTION_ADD"); + continue; + } + const std::string guid = turl->sync_guid(); + if (existing_keyword_turl) { + // Resolve any conflicts so we can safely add the new entry. + ResolveSyncKeywordConflict(turl.get(), existing_keyword_turl, + &new_changes); + } + base::AutoReset<DefaultSearchChangeOrigin> change_origin( + &dsp_change_origin_, DSP_CHANGE_SYNC_ADD); + // Force the local ID to kInvalidTemplateURLID so we can add it. + TemplateURLData data(turl->data()); + data.id = kInvalidTemplateURLID; + TemplateURL* added = new TemplateURL(data); + if (Add(added)) + MaybeUpdateDSEAfterSync(added); + } else if (iter->change_type() == syncer::SyncChange::ACTION_UPDATE) { + if (!existing_turl) { + error = sync_error_factory_->CreateAndUploadError( + FROM_HERE, + "ProcessSyncChanges failed on ChangeType ACTION_UPDATE"); + continue; + } + if (existing_keyword_turl && (existing_keyword_turl != existing_turl)) { + // Resolve any conflicts with other entries so we can safely update the + // keyword. + ResolveSyncKeywordConflict(turl.get(), existing_keyword_turl, + &new_changes); + } + if (UpdateNoNotify(existing_turl, *turl)) { + NotifyObservers(); + MaybeUpdateDSEAfterSync(existing_turl); + } + } else { + // We've unexpectedly received an ACTION_INVALID. + error = sync_error_factory_->CreateAndUploadError( + FROM_HERE, + "ProcessSyncChanges received an ACTION_INVALID"); + } + } + + // If something went wrong, we want to prematurely exit to avoid pushing + // inconsistent data to Sync. We return the last error we received. + if (error.IsSet()) + return error; + + error = sync_processor_->ProcessSyncChanges(from_here, new_changes); + + return error; +} + +syncer::SyncMergeResult TemplateURLService::MergeDataAndStartSyncing( + syncer::ModelType type, + const syncer::SyncDataList& initial_sync_data, + scoped_ptr<syncer::SyncChangeProcessor> sync_processor, + scoped_ptr<syncer::SyncErrorFactory> sync_error_factory) { + DCHECK(loaded_); + DCHECK_EQ(type, syncer::SEARCH_ENGINES); + DCHECK(!sync_processor_.get()); + DCHECK(sync_processor.get()); + DCHECK(sync_error_factory.get()); + syncer::SyncMergeResult merge_result(type); + + // Disable sync if we failed to load. + if (load_failed_) { + merge_result.set_error(syncer::SyncError( + FROM_HERE, syncer::SyncError::DATATYPE_ERROR, + "Local database load failed.", syncer::SEARCH_ENGINES)); + return merge_result; + } + + sync_processor_ = sync_processor.Pass(); + sync_error_factory_ = sync_error_factory.Pass(); + + // We do a lot of calls to Add/Remove/ResetTemplateURL here, so ensure we + // don't step on our own toes. + base::AutoReset<bool> processing_changes(&processing_syncer_changes_, true); + + // We've started syncing, so set our origin member to the base Sync value. + // As we move through Sync Code, we may set this to increasingly specific + // origins so we can tell what exactly caused a DSP change. + base::AutoReset<DefaultSearchChangeOrigin> change_origin(&dsp_change_origin_, + DSP_CHANGE_SYNC_UNINTENTIONAL); + + syncer::SyncChangeList new_changes; + + // Build maps of our sync GUIDs to syncer::SyncData. + SyncDataMap local_data_map = CreateGUIDToSyncDataMap( + GetAllSyncData(syncer::SEARCH_ENGINES)); + SyncDataMap sync_data_map = CreateGUIDToSyncDataMap(initial_sync_data); + + KeywordWebDataService::BatchModeScoper scoper(web_data_service_.get()); + + merge_result.set_num_items_before_association(local_data_map.size()); + for (SyncDataMap::const_iterator iter = sync_data_map.begin(); + iter != sync_data_map.end(); ++iter) { + TemplateURL* local_turl = GetTemplateURLForGUID(iter->first); + scoped_ptr<TemplateURL> sync_turl( + CreateTemplateURLFromTemplateURLAndSyncData( + prefs_, search_terms_data(), local_turl, iter->second, + &new_changes)); + if (!sync_turl.get()) + continue; + + if (pre_sync_deletes_.find(sync_turl->sync_guid()) != + pre_sync_deletes_.end()) { + // This entry was deleted before the initial sync began (possibly through + // preprocessing in TemplateURLService's loading code). Ignore it and send + // an ACTION_DELETE up to the server. + new_changes.push_back( + syncer::SyncChange(FROM_HERE, + syncer::SyncChange::ACTION_DELETE, + iter->second)); + UMA_HISTOGRAM_ENUMERATION(kDeleteSyncedEngineHistogramName, + DELETE_ENGINE_PRE_SYNC, DELETE_ENGINE_MAX); + continue; + } + + if (local_turl) { + DCHECK(IsFromSync(local_turl, sync_data_map)); + // This local search engine is already synced. If the timestamp differs + // from Sync, we need to update locally or to the cloud. Note that if the + // timestamps are equal, we touch neither. + if (sync_turl->last_modified() > local_turl->last_modified()) { + // We've received an update from Sync. We should replace all synced + // fields in the local TemplateURL. Note that this includes the + // TemplateURLID and the TemplateURL may have to be reparsed. This + // also makes the local data's last_modified timestamp equal to Sync's, + // avoiding an Update on the next MergeData call. + if (UpdateNoNotify(local_turl, *sync_turl)) + NotifyObservers(); + merge_result.set_num_items_modified( + merge_result.num_items_modified() + 1); + } else if (sync_turl->last_modified() < local_turl->last_modified()) { + // Otherwise, we know we have newer data, so update Sync with our + // data fields. + new_changes.push_back( + syncer::SyncChange(FROM_HERE, + syncer::SyncChange::ACTION_UPDATE, + local_data_map[local_turl->sync_guid()])); + } + local_data_map.erase(iter->first); + } else { + // The search engine from the cloud has not been synced locally. Merge it + // into our local model. This will handle any conflicts with local (and + // already-synced) TemplateURLs. It will prefer to keep entries from Sync + // over not-yet-synced TemplateURLs. + MergeInSyncTemplateURL(sync_turl.get(), sync_data_map, &new_changes, + &local_data_map, &merge_result); + } + } + + // The remaining SyncData in local_data_map should be everything that needs to + // be pushed as ADDs to sync. + for (SyncDataMap::const_iterator iter = local_data_map.begin(); + iter != local_data_map.end(); ++iter) { + new_changes.push_back( + syncer::SyncChange(FROM_HERE, + syncer::SyncChange::ACTION_ADD, + iter->second)); + } + + // Do some post-processing on the change list to ensure that we are sending + // valid changes to sync_processor_. + PruneSyncChanges(&sync_data_map, &new_changes); + + LogDuplicatesHistogram(GetTemplateURLs()); + merge_result.set_num_items_after_association( + GetAllSyncData(syncer::SEARCH_ENGINES).size()); + merge_result.set_error( + sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes)); + if (merge_result.error().IsSet()) + return merge_result; + + // The ACTION_DELETEs from this set are processed. Empty it so we don't try to + // reuse them on the next call to MergeDataAndStartSyncing. + pre_sync_deletes_.clear(); + + models_associated_ = true; + return merge_result; +} + +void TemplateURLService::StopSyncing(syncer::ModelType type) { + DCHECK_EQ(type, syncer::SEARCH_ENGINES); + models_associated_ = false; + sync_processor_.reset(); + sync_error_factory_.reset(); +} + +void TemplateURLService::ProcessTemplateURLChange( + const tracked_objects::Location& from_here, + const TemplateURL* turl, + syncer::SyncChange::SyncChangeType type) { + DCHECK_NE(type, syncer::SyncChange::ACTION_INVALID); + DCHECK(turl); + + if (!models_associated_) + return; // Not syncing. + + if (processing_syncer_changes_) + return; // These are changes originating from us. Ignore. + + // Avoid syncing keywords managed by policy. + if (turl->created_by_policy()) + return; + + // Avoid syncing extension-controlled search engines. + if (turl->GetType() == TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION) + return; + + syncer::SyncChangeList changes; + + syncer::SyncData sync_data = CreateSyncDataFromTemplateURL(*turl); + changes.push_back(syncer::SyncChange(from_here, + type, + sync_data)); + + sync_processor_->ProcessSyncChanges(FROM_HERE, changes); +} + +// static +syncer::SyncData TemplateURLService::CreateSyncDataFromTemplateURL( + const TemplateURL& turl) { + sync_pb::EntitySpecifics specifics; + sync_pb::SearchEngineSpecifics* se_specifics = + specifics.mutable_search_engine(); + se_specifics->set_short_name(base::UTF16ToUTF8(turl.short_name())); + se_specifics->set_keyword(base::UTF16ToUTF8(turl.keyword())); + se_specifics->set_favicon_url(turl.favicon_url().spec()); + se_specifics->set_url(turl.url()); + se_specifics->set_safe_for_autoreplace(turl.safe_for_autoreplace()); + se_specifics->set_originating_url(turl.originating_url().spec()); + se_specifics->set_date_created(turl.date_created().ToInternalValue()); + se_specifics->set_input_encodings(JoinString(turl.input_encodings(), ';')); + se_specifics->set_show_in_default_list(turl.show_in_default_list()); + se_specifics->set_suggestions_url(turl.suggestions_url()); + se_specifics->set_prepopulate_id(turl.prepopulate_id()); + se_specifics->set_instant_url(turl.instant_url()); + if (!turl.image_url().empty()) + se_specifics->set_image_url(turl.image_url()); + se_specifics->set_new_tab_url(turl.new_tab_url()); + if (!turl.search_url_post_params().empty()) + se_specifics->set_search_url_post_params(turl.search_url_post_params()); + if (!turl.suggestions_url_post_params().empty()) { + se_specifics->set_suggestions_url_post_params( + turl.suggestions_url_post_params()); + } + if (!turl.instant_url_post_params().empty()) + se_specifics->set_instant_url_post_params(turl.instant_url_post_params()); + if (!turl.image_url_post_params().empty()) + se_specifics->set_image_url_post_params(turl.image_url_post_params()); + se_specifics->set_last_modified(turl.last_modified().ToInternalValue()); + se_specifics->set_sync_guid(turl.sync_guid()); + for (size_t i = 0; i < turl.alternate_urls().size(); ++i) + se_specifics->add_alternate_urls(turl.alternate_urls()[i]); + se_specifics->set_search_terms_replacement_key( + turl.search_terms_replacement_key()); + + return syncer::SyncData::CreateLocalData(se_specifics->sync_guid(), + se_specifics->keyword(), + specifics); +} + +// static +TemplateURL* TemplateURLService::CreateTemplateURLFromTemplateURLAndSyncData( + PrefService* prefs, + const SearchTermsData& search_terms_data, + TemplateURL* existing_turl, + const syncer::SyncData& sync_data, + syncer::SyncChangeList* change_list) { + DCHECK(change_list); + + sync_pb::SearchEngineSpecifics specifics = + sync_data.GetSpecifics().search_engine(); + + // Past bugs might have caused either of these fields to be empty. Just + // delete this data off the server. + if (specifics.url().empty() || specifics.sync_guid().empty()) { + change_list->push_back( + syncer::SyncChange(FROM_HERE, + syncer::SyncChange::ACTION_DELETE, + sync_data)); + UMA_HISTOGRAM_ENUMERATION(kDeleteSyncedEngineHistogramName, + DELETE_ENGINE_EMPTY_FIELD, DELETE_ENGINE_MAX); + return NULL; + } + + TemplateURLData data(existing_turl ? + existing_turl->data() : TemplateURLData()); + data.short_name = base::UTF8ToUTF16(specifics.short_name()); + data.originating_url = GURL(specifics.originating_url()); + base::string16 keyword(base::UTF8ToUTF16(specifics.keyword())); + // NOTE: Once this code has shipped in a couple of stable releases, we can + // probably remove the migration portion, comment out the + // "autogenerate_keyword" field entirely in the .proto file, and fold the + // empty keyword case into the "delete data" block above. + bool reset_keyword = + specifics.autogenerate_keyword() || specifics.keyword().empty(); + if (reset_keyword) + keyword = base::ASCIIToUTF16("dummy"); // Will be replaced below. + DCHECK(!keyword.empty()); + data.SetKeyword(keyword); + data.SetURL(specifics.url()); + data.suggestions_url = specifics.suggestions_url(); + data.instant_url = specifics.instant_url(); + data.image_url = specifics.image_url(); + data.new_tab_url = specifics.new_tab_url(); + data.search_url_post_params = specifics.search_url_post_params(); + data.suggestions_url_post_params = specifics.suggestions_url_post_params(); + data.instant_url_post_params = specifics.instant_url_post_params(); + data.image_url_post_params = specifics.image_url_post_params(); + data.favicon_url = GURL(specifics.favicon_url()); + data.show_in_default_list = specifics.show_in_default_list(); + data.safe_for_autoreplace = specifics.safe_for_autoreplace(); + base::SplitString(specifics.input_encodings(), ';', &data.input_encodings); + // If the server data has duplicate encodings, we'll want to push an update + // below to correct it. Note that we also fix this in + // GetSearchProvidersUsingKeywordResult(), since otherwise we'd never correct + // local problems for clients which have disabled search engine sync. + bool deduped = DeDupeEncodings(&data.input_encodings); + data.date_created = base::Time::FromInternalValue(specifics.date_created()); + data.last_modified = base::Time::FromInternalValue(specifics.last_modified()); + data.prepopulate_id = specifics.prepopulate_id(); + data.sync_guid = specifics.sync_guid(); + data.alternate_urls.clear(); + for (int i = 0; i < specifics.alternate_urls_size(); ++i) + data.alternate_urls.push_back(specifics.alternate_urls(i)); + data.search_terms_replacement_key = specifics.search_terms_replacement_key(); + + TemplateURL* turl = new TemplateURL(data); + // If this TemplateURL matches a built-in prepopulated template URL, it's + // possible that sync is trying to modify fields that should not be touched. + // Revert these fields to the built-in values. + UpdateTemplateURLIfPrepopulated(turl, prefs); + DCHECK_NE(TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION, turl->GetType()); + if (reset_keyword || deduped) { + if (reset_keyword) + turl->ResetKeywordIfNecessary(search_terms_data, true); + syncer::SyncData sync_data = CreateSyncDataFromTemplateURL(*turl); + change_list->push_back(syncer::SyncChange(FROM_HERE, + syncer::SyncChange::ACTION_UPDATE, + sync_data)); + } else if (turl->IsGoogleSearchURLWithReplaceableKeyword(search_terms_data)) { + if (!existing_turl) { + // We're adding a new TemplateURL that uses the Google base URL, so set + // its keyword appropriately for the local environment. + turl->ResetKeywordIfNecessary(search_terms_data, false); + } else if (existing_turl->IsGoogleSearchURLWithReplaceableKeyword( + search_terms_data)) { + // Ignore keyword changes triggered by the Google base URL changing on + // another client. If the base URL changes in this client as well, we'll + // pick that up separately at the appropriate time. Otherwise, changing + // the keyword here could result in having the wrong keyword for the local + // environment. + turl->data_.SetKeyword(existing_turl->keyword()); + } + } + + return turl; +} + +// static +SyncDataMap TemplateURLService::CreateGUIDToSyncDataMap( + const syncer::SyncDataList& sync_data) { + SyncDataMap data_map; + for (syncer::SyncDataList::const_iterator i(sync_data.begin()); + i != sync_data.end(); + ++i) + data_map[i->GetSpecifics().search_engine().sync_guid()] = *i; + return data_map; +} + +void TemplateURLService::SetKeywordSearchTermsForURL( + const TemplateURL* t_url, + const GURL& url, + const base::string16& term) { + if (client_) + client_->SetKeywordSearchTermsForURL(url, t_url->id(), term); +} + +void TemplateURLService::Init(const Initializer* initializers, + int num_initializers) { + if (client_) + client_->SetOwner(this); + + // GoogleURLTracker is not created in tests. + if (google_url_tracker_) { + google_url_updated_subscription_ = + google_url_tracker_->RegisterCallback(base::Bind( + &TemplateURLService::OnGoogleURLUpdated, base::Unretained(this))); + } + + if (prefs_) { + pref_change_registrar_.Init(prefs_); + pref_change_registrar_.Add( + prefs::kSyncedDefaultSearchProviderGUID, + base::Bind( + &TemplateURLService::OnSyncedDefaultSearchProviderGUIDChanged, + base::Unretained(this))); + } + + DefaultSearchManager::Source source = DefaultSearchManager::FROM_USER; + TemplateURLData* dse = + default_search_manager_.GetDefaultSearchEngine(&source); + ApplyDefaultSearchChange(dse, source); + + if (num_initializers > 0) { + // This path is only hit by test code and is used to simulate a loaded + // TemplateURLService. + ChangeToLoadedState(); + + // Add specific initializers, if any. + KeywordWebDataService::BatchModeScoper scoper(web_data_service_.get()); + for (int i(0); i < num_initializers; ++i) { + DCHECK(initializers[i].keyword); + DCHECK(initializers[i].url); + DCHECK(initializers[i].content); + + // TemplateURLService ends up owning the TemplateURL, don't try and free + // it. + TemplateURLData data; + data.short_name = base::UTF8ToUTF16(initializers[i].content); + data.SetKeyword(base::UTF8ToUTF16(initializers[i].keyword)); + data.SetURL(initializers[i].url); + TemplateURL* template_url = new TemplateURL(data); + AddNoNotify(template_url, true); + + // Set the first provided identifier to be the default. + if (i == 0) + default_search_manager_.SetUserSelectedDefaultSearchEngine(data); + } + } + + // Request a server check for the correct Google URL if Google is the + // default search engine. + RequestGoogleURLTrackerServerCheckIfNecessary(); +} + +void TemplateURLService::RemoveFromMaps(TemplateURL* template_url) { + const base::string16& keyword = template_url->keyword(); + DCHECK_NE(0U, keyword_to_template_map_.count(keyword)); + if (keyword_to_template_map_[keyword] == template_url) { + // We need to check whether the keyword can now be provided by another + // TemplateURL. See the comments in AddToMaps() for more information on + // extension keywords and how they can coexist with non-extension keywords. + // In the case of more than one extension, we use the most recently + // installed (which will be the most recently added, which will have the + // highest ID). + TemplateURL* best_fallback = NULL; + for (TemplateURLVector::const_iterator i(template_urls_.begin()); + i != template_urls_.end(); ++i) { + TemplateURL* turl = *i; + // This next statement relies on the fact that there can only be one + // non-Omnibox API TemplateURL with a given keyword. + if ((turl != template_url) && (turl->keyword() == keyword) && + (!best_fallback || + (best_fallback->GetType() != TemplateURL::OMNIBOX_API_EXTENSION) || + ((turl->GetType() == TemplateURL::OMNIBOX_API_EXTENSION) && + (turl->id() > best_fallback->id())))) + best_fallback = turl; + } + if (best_fallback) + keyword_to_template_map_[keyword] = best_fallback; + else + keyword_to_template_map_.erase(keyword); + } + + if (!template_url->sync_guid().empty()) + guid_to_template_map_.erase(template_url->sync_guid()); + // |provider_map_| is only initialized after loading has completed. + if (loaded_) { + provider_map_->Remove(template_url); + } +} + +void TemplateURLService::AddToMaps(TemplateURL* template_url) { + bool template_url_is_omnibox_api = + template_url->GetType() == TemplateURL::OMNIBOX_API_EXTENSION; + const base::string16& keyword = template_url->keyword(); + KeywordToTemplateMap::const_iterator i = + keyword_to_template_map_.find(keyword); + if (i == keyword_to_template_map_.end()) { + keyword_to_template_map_[keyword] = template_url; + } else { + const TemplateURL* existing_url = i->second; + // We should only have overlapping keywords when at least one comes from + // an extension. In that case, the ranking order is: + // Manually-modified keywords > extension keywords > replaceable keywords + // When there are multiple extensions, the last-added wins. + bool existing_url_is_omnibox_api = + existing_url->GetType() == TemplateURL::OMNIBOX_API_EXTENSION; + DCHECK(existing_url_is_omnibox_api || template_url_is_omnibox_api); + if (existing_url_is_omnibox_api ? + !CanReplace(template_url) : CanReplace(existing_url)) + keyword_to_template_map_[keyword] = template_url; + } + + if (!template_url->sync_guid().empty()) + guid_to_template_map_[template_url->sync_guid()] = template_url; + // |provider_map_| is only initialized after loading has completed. + if (loaded_) + provider_map_->Add(template_url, search_terms_data()); +} + +// Helper for partition() call in next function. +bool HasValidID(TemplateURL* t_url) { + return t_url->id() != kInvalidTemplateURLID; +} + +void TemplateURLService::SetTemplateURLs(TemplateURLVector* urls) { + // Partition the URLs first, instead of implementing the loops below by simply + // scanning the input twice. While it's not supposed to happen normally, it's + // possible for corrupt databases to return multiple entries with the same + // keyword. In this case, the first loop may delete the first entry when + // adding the second. If this happens, the second loop must not attempt to + // access the deleted entry. Partitioning ensures this constraint. + TemplateURLVector::iterator first_invalid( + std::partition(urls->begin(), urls->end(), HasValidID)); + + // First, add the items that already have id's, so that the next_id_ gets + // properly set. + for (TemplateURLVector::const_iterator i = urls->begin(); i != first_invalid; + ++i) { + next_id_ = std::max(next_id_, (*i)->id()); + AddNoNotify(*i, false); + } + + // Next add the new items that don't have id's. + for (TemplateURLVector::const_iterator i = first_invalid; i != urls->end(); + ++i) + AddNoNotify(*i, true); + + // Clear the input vector to reduce the chance callers will try to use a + // (possibly deleted) entry. + urls->clear(); +} + +void TemplateURLService::ChangeToLoadedState() { + DCHECK(!loaded_); + + provider_map_->Init(template_urls_, search_terms_data()); + loaded_ = true; + + // This will cause a call to NotifyObservers(). + ApplyDefaultSearchChangeNoMetrics( + initial_default_search_provider_ ? + &initial_default_search_provider_->data() : NULL, + default_search_provider_source_); + initial_default_search_provider_.reset(); + on_loaded_callbacks_.Notify(); +} + +bool TemplateURLService::CanReplaceKeywordForHost( + const std::string& host, + TemplateURL** to_replace) { + DCHECK(!to_replace || !*to_replace); + const TemplateURLSet* urls = provider_map_->GetURLsForHost(host); + if (!urls) + return true; + for (TemplateURLSet::const_iterator i(urls->begin()); i != urls->end(); ++i) { + if (CanReplace(*i)) { + if (to_replace) + *to_replace = *i; + return true; + } + } + return false; +} + +bool TemplateURLService::CanReplace(const TemplateURL* t_url) { + return (t_url != default_search_provider_ && !t_url->show_in_default_list() && + t_url->safe_for_autoreplace()); +} + +TemplateURL* TemplateURLService::FindNonExtensionTemplateURLForKeyword( + const base::string16& keyword) { + TemplateURL* keyword_turl = GetTemplateURLForKeyword(keyword); + if (!keyword_turl || (keyword_turl->GetType() == TemplateURL::NORMAL)) + return keyword_turl; + // The extension keyword in the model may be hiding a replaceable + // non-extension keyword. Look for it. + for (TemplateURLVector::const_iterator i(template_urls_.begin()); + i != template_urls_.end(); ++i) { + if (((*i)->GetType() == TemplateURL::NORMAL) && + ((*i)->keyword() == keyword)) + return *i; + } + return NULL; +} + +bool TemplateURLService::UpdateNoNotify(TemplateURL* existing_turl, + const TemplateURL& new_values) { + DCHECK(existing_turl); + if (std::find(template_urls_.begin(), template_urls_.end(), existing_turl) == + template_urls_.end()) + return false; + + base::string16 old_keyword(existing_turl->keyword()); + keyword_to_template_map_.erase(old_keyword); + if (!existing_turl->sync_guid().empty()) + guid_to_template_map_.erase(existing_turl->sync_guid()); + + // |provider_map_| is only initialized after loading has completed. + if (loaded_) + provider_map_->Remove(existing_turl); + + TemplateURLID previous_id = existing_turl->id(); + existing_turl->CopyFrom(new_values); + existing_turl->data_.id = previous_id; + + if (loaded_) { + provider_map_->Add(existing_turl, search_terms_data()); + } + + const base::string16& keyword = existing_turl->keyword(); + KeywordToTemplateMap::const_iterator i = + keyword_to_template_map_.find(keyword); + if (i == keyword_to_template_map_.end()) { + keyword_to_template_map_[keyword] = existing_turl; + } else { + // We can theoretically reach here in two cases: + // * There is an existing extension keyword and sync brings in a rename of + // a non-extension keyword to match. In this case we just need to pick + // which keyword has priority to update the keyword map. + // * Autogeneration of the keyword for a Google default search provider + // at load time causes it to conflict with an existing keyword. In this + // case we delete the existing keyword if it's replaceable, or else undo + // the change in keyword for |existing_turl|. + TemplateURL* existing_keyword_turl = i->second; + if (existing_keyword_turl->GetType() != TemplateURL::NORMAL) { + if (!CanReplace(existing_turl)) + keyword_to_template_map_[keyword] = existing_turl; + } else { + if (CanReplace(existing_keyword_turl)) { + RemoveNoNotify(existing_keyword_turl); + } else { + existing_turl->data_.SetKeyword(old_keyword); + keyword_to_template_map_[old_keyword] = existing_turl; + } + } + } + if (!existing_turl->sync_guid().empty()) + guid_to_template_map_[existing_turl->sync_guid()] = existing_turl; + + if (web_data_service_) + web_data_service_->UpdateKeyword(existing_turl->data()); + + // Inform sync of the update. + ProcessTemplateURLChange( + FROM_HERE, existing_turl, syncer::SyncChange::ACTION_UPDATE); + + if (default_search_provider_ == existing_turl && + default_search_provider_source_ == DefaultSearchManager::FROM_USER) { + default_search_manager_.SetUserSelectedDefaultSearchEngine( + default_search_provider_->data()); + } + return true; +} + +// static +void TemplateURLService::UpdateTemplateURLIfPrepopulated( + TemplateURL* template_url, + PrefService* prefs) { + int prepopulate_id = template_url->prepopulate_id(); + if (template_url->prepopulate_id() == 0) + return; + + size_t default_search_index; + ScopedVector<TemplateURLData> prepopulated_urls = + TemplateURLPrepopulateData::GetPrepopulatedEngines( + prefs, &default_search_index); + + for (size_t i = 0; i < prepopulated_urls.size(); ++i) { + if (prepopulated_urls[i]->prepopulate_id == prepopulate_id) { + MergeIntoPrepopulatedEngineData(template_url, prepopulated_urls[i]); + template_url->CopyFrom(TemplateURL(*prepopulated_urls[i])); + } + } +} + +void TemplateURLService::MaybeUpdateDSEAfterSync(TemplateURL* synced_turl) { + if (prefs_ && + (synced_turl->sync_guid() == + prefs_->GetString(prefs::kSyncedDefaultSearchProviderGUID))) { + default_search_manager_.SetUserSelectedDefaultSearchEngine( + synced_turl->data()); + } +} + +void TemplateURLService::UpdateKeywordSearchTermsForURL( + const URLVisitedDetails& details) { + if (!details.url.is_valid()) + return; + + const TemplateURLSet* urls_for_host = + provider_map_->GetURLsForHost(details.url.host()); + if (!urls_for_host) + return; + + for (TemplateURLSet::const_iterator i = urls_for_host->begin(); + i != urls_for_host->end(); ++i) { + base::string16 search_terms; + if ((*i)->ExtractSearchTermsFromURL(details.url, search_terms_data(), + &search_terms) && + !search_terms.empty()) { + if (details.is_keyword_transition) { + // The visit is the result of the user entering a keyword, generate a + // KEYWORD_GENERATED visit for the KEYWORD so that the keyword typed + // count is boosted. + AddTabToSearchVisit(**i); + } + SetKeywordSearchTermsForURL(*i, details.url, search_terms); + } + } +} + +void TemplateURLService::AddTabToSearchVisit(const TemplateURL& t_url) { + // Only add visits for entries the user hasn't modified. If the user modified + // the entry the keyword may no longer correspond to the host name. It may be + // possible to do something more sophisticated here, but it's so rare as to + // not be worth it. + if (!t_url.safe_for_autoreplace()) + return; + + if (!client_) + return; + + GURL url( + url_fixer::FixupURL(base::UTF16ToUTF8(t_url.keyword()), std::string())); + if (!url.is_valid()) + return; + + // Synthesize a visit for the keyword. This ensures the url for the keyword is + // autocompleted even if the user doesn't type the url in directly. + client_->AddKeywordGeneratedVisit(url); +} + +void TemplateURLService::RequestGoogleURLTrackerServerCheckIfNecessary() { + if (default_search_provider_ && + default_search_provider_->HasGoogleBaseURLs(search_terms_data()) && + google_url_tracker_) + google_url_tracker_->RequestServerCheck(false); +} + +void TemplateURLService::GoogleBaseURLChanged() { + KeywordWebDataService::BatchModeScoper scoper(web_data_service_.get()); + bool something_changed = false; + for (TemplateURLVector::iterator i(template_urls_.begin()); + i != template_urls_.end(); ++i) { + TemplateURL* t_url = *i; + if (t_url->HasGoogleBaseURLs(search_terms_data())) { + TemplateURL updated_turl(t_url->data()); + updated_turl.ResetKeywordIfNecessary(search_terms_data(), false); + KeywordToTemplateMap::const_iterator existing_entry = + keyword_to_template_map_.find(updated_turl.keyword()); + if ((existing_entry != keyword_to_template_map_.end()) && + (existing_entry->second != t_url)) { + // The new autogenerated keyword conflicts with another TemplateURL. + // Overwrite it if it's replaceable; otherwise, leave |t_url| using its + // current keyword. (This will not prevent |t_url| from auto-updating + // the keyword in the future if the conflicting TemplateURL disappears.) + // Note that we must still update |t_url| in this case, or the + // |provider_map_| will not be updated correctly. + if (CanReplace(existing_entry->second)) + RemoveNoNotify(existing_entry->second); + else + updated_turl.data_.SetKeyword(t_url->keyword()); + } + something_changed = true; + // This will send the keyword change to sync. Note that other clients + // need to reset the keyword to an appropriate local value when this + // change arrives; see CreateTemplateURLFromTemplateURLAndSyncData(). + UpdateNoNotify(t_url, updated_turl); + } + } + if (something_changed) + NotifyObservers(); +} + +void TemplateURLService::OnGoogleURLUpdated(GURL old_url, GURL new_url) { + if (loaded_) + GoogleBaseURLChanged(); +} + +void TemplateURLService::OnDefaultSearchChange( + const TemplateURLData* data, + DefaultSearchManager::Source source) { + if (prefs_ && (source == DefaultSearchManager::FROM_USER) && + ((source != default_search_provider_source_) || + !IdenticalSyncGUIDs(data, GetDefaultSearchProvider()))) { + prefs_->SetString(prefs::kSyncedDefaultSearchProviderGUID, data->sync_guid); + } + ApplyDefaultSearchChange(data, source); +} + +void TemplateURLService::ApplyDefaultSearchChange( + const TemplateURLData* data, + DefaultSearchManager::Source source) { + if (!ApplyDefaultSearchChangeNoMetrics(data, source)) + return; + + UMA_HISTOGRAM_ENUMERATION( + "Search.DefaultSearchChangeOrigin", dsp_change_origin_, DSP_CHANGE_MAX); + + if (GetDefaultSearchProvider() && + GetDefaultSearchProvider()->HasGoogleBaseURLs(search_terms_data()) && + !dsp_change_callback_.is_null()) + dsp_change_callback_.Run(); +} + +bool TemplateURLService::ApplyDefaultSearchChangeNoMetrics( + const TemplateURLData* data, + DefaultSearchManager::Source source) { + if (!loaded_) { + // Set |initial_default_search_provider_| from the preferences. This is + // mainly so we can hold ownership until we get to the point where the list + // of keywords from Web Data is the owner of everything including the + // default. + bool changed = TemplateURL::MatchesData( + initial_default_search_provider_.get(), data, search_terms_data()); + initial_default_search_provider_.reset( + data ? new TemplateURL(*data) : NULL); + default_search_provider_source_ = source; + return changed; + } + + // Prevent recursion if we update the value stored in default_search_manager_. + // Note that we exclude the case of data == NULL because that could cause a + // false positive for recursion when the initial_default_search_provider_ is + // NULL due to policy. We'll never actually get recursion with data == NULL. + if (source == default_search_provider_source_ && data != NULL && + TemplateURL::MatchesData(default_search_provider_, data, + search_terms_data())) + return false; + + // This may be deleted later. Use exclusively for pointer comparison to detect + // a change. + TemplateURL* previous_default_search_engine = default_search_provider_; + + KeywordWebDataService::BatchModeScoper scoper(web_data_service_.get()); + if (default_search_provider_source_ == DefaultSearchManager::FROM_POLICY || + source == DefaultSearchManager::FROM_POLICY) { + // We do this both to remove any no-longer-applicable policy-defined DSE as + // well as to add the new one, if appropriate. + UpdateProvidersCreatedByPolicy( + &template_urls_, + source == DefaultSearchManager::FROM_POLICY ? data : NULL); + } + + if (!data) { + default_search_provider_ = NULL; + } else if (source == DefaultSearchManager::FROM_EXTENSION) { + default_search_provider_ = FindMatchingExtensionTemplateURL( + *data, TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION); + } else if (source == DefaultSearchManager::FROM_FALLBACK) { + default_search_provider_ = + FindPrepopulatedTemplateURL(data->prepopulate_id); + if (default_search_provider_) { + TemplateURLData update_data(*data); + update_data.sync_guid = default_search_provider_->sync_guid(); + if (!default_search_provider_->safe_for_autoreplace()) { + update_data.safe_for_autoreplace = false; + update_data.SetKeyword(default_search_provider_->keyword()); + update_data.short_name = default_search_provider_->short_name(); + } + UpdateNoNotify(default_search_provider_, TemplateURL(update_data)); + } else { + // Normally the prepopulated fallback should be present in + // |template_urls_|, but in a few cases it might not be: + // (1) Tests that initialize the TemplateURLService in peculiar ways. + // (2) If the user deleted the pre-populated default and we subsequently + // lost their user-selected value. + TemplateURL* new_dse = new TemplateURL(*data); + if (AddNoNotify(new_dse, true)) + default_search_provider_ = new_dse; + } + } else if (source == DefaultSearchManager::FROM_USER) { + default_search_provider_ = GetTemplateURLForGUID(data->sync_guid); + if (!default_search_provider_ && data->prepopulate_id) { + default_search_provider_ = + FindPrepopulatedTemplateURL(data->prepopulate_id); + } + TemplateURLData new_data(*data); + new_data.show_in_default_list = true; + if (default_search_provider_) { + UpdateNoNotify(default_search_provider_, TemplateURL(new_data)); + } else { + new_data.id = kInvalidTemplateURLID; + TemplateURL* new_dse = new TemplateURL(new_data); + if (AddNoNotify(new_dse, true)) + default_search_provider_ = new_dse; + } + if (default_search_provider_ && prefs_) { + prefs_->SetString(prefs::kSyncedDefaultSearchProviderGUID, + default_search_provider_->sync_guid()); + } + + } + + default_search_provider_source_ = source; + + bool changed = default_search_provider_ != previous_default_search_engine; + if (changed) + RequestGoogleURLTrackerServerCheckIfNecessary(); + + NotifyObservers(); + + return changed; +} + +bool TemplateURLService::AddNoNotify(TemplateURL* template_url, + bool newly_adding) { + DCHECK(template_url); + + if (newly_adding) { + DCHECK_EQ(kInvalidTemplateURLID, template_url->id()); + DCHECK(std::find(template_urls_.begin(), template_urls_.end(), + template_url) == template_urls_.end()); + template_url->data_.id = ++next_id_; + } + + template_url->ResetKeywordIfNecessary(search_terms_data(), false); + // Check whether |template_url|'s keyword conflicts with any already in the + // model. + TemplateURL* existing_keyword_turl = + GetTemplateURLForKeyword(template_url->keyword()); + + // Check whether |template_url|'s keyword conflicts with any already in the + // model. Note that we can reach here during the loading phase while + // processing the template URLs from the web data service. In this case, + // GetTemplateURLForKeyword() will look not only at what's already in the + // model, but at the |initial_default_search_provider_|. Since this engine + // will presumably also be present in the web data, we need to double-check + // that any "pre-existing" entries we find are actually coming from + // |template_urls_|, lest we detect a "conflict" between the + // |initial_default_search_provider_| and the web data version of itself. + if (existing_keyword_turl && + (std::find(template_urls_.begin(), template_urls_.end(), + existing_keyword_turl) != template_urls_.end())) { + DCHECK_NE(existing_keyword_turl, template_url); + // Only replace one of the TemplateURLs if they are either both extensions, + // or both not extensions. + bool are_same_type = existing_keyword_turl->GetType() == + template_url->GetType(); + if (CanReplace(existing_keyword_turl) && are_same_type) { + RemoveNoNotify(existing_keyword_turl); + } else if (CanReplace(template_url) && are_same_type) { + delete template_url; + return false; + } else { + base::string16 new_keyword = + UniquifyKeyword(*existing_keyword_turl, false); + ResetTemplateURLNoNotify(existing_keyword_turl, + existing_keyword_turl->short_name(), new_keyword, + existing_keyword_turl->url()); + } + } + template_urls_.push_back(template_url); + AddToMaps(template_url); + + if (newly_adding && + (template_url->GetType() != + TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION)) { + if (web_data_service_) + web_data_service_->AddKeyword(template_url->data()); + + // Inform sync of the addition. Note that this will assign a GUID to + // template_url and add it to the guid_to_template_map_. + ProcessTemplateURLChange(FROM_HERE, + template_url, + syncer::SyncChange::ACTION_ADD); + } + + return true; +} + +void TemplateURLService::RemoveNoNotify(TemplateURL* template_url) { + DCHECK(template_url != default_search_provider_); + + TemplateURLVector::iterator i = + std::find(template_urls_.begin(), template_urls_.end(), template_url); + if (i == template_urls_.end()) + return; + + RemoveFromMaps(template_url); + + // Remove it from the vector containing all TemplateURLs. + template_urls_.erase(i); + + if (template_url->GetType() != TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION) { + if (web_data_service_) + web_data_service_->RemoveKeyword(template_url->id()); + + // Inform sync of the deletion. + ProcessTemplateURLChange(FROM_HERE, + template_url, + syncer::SyncChange::ACTION_DELETE); + + UMA_HISTOGRAM_ENUMERATION(kDeleteSyncedEngineHistogramName, + DELETE_ENGINE_USER_ACTION, DELETE_ENGINE_MAX); + } + + if (loaded_ && client_) + client_->DeleteAllSearchTermsForKeyword(template_url->id()); + + // We own the TemplateURL and need to delete it. + delete template_url; +} + +bool TemplateURLService::ResetTemplateURLNoNotify( + TemplateURL* url, + const base::string16& title, + const base::string16& keyword, + const std::string& search_url) { + DCHECK(!keyword.empty()); + DCHECK(!search_url.empty()); + TemplateURLData data(url->data()); + data.short_name = title; + data.SetKeyword(keyword); + if (search_url != data.url()) { + data.SetURL(search_url); + // The urls have changed, reset the favicon url. + data.favicon_url = GURL(); + } + data.safe_for_autoreplace = false; + data.last_modified = time_provider_(); + return UpdateNoNotify(url, TemplateURL(data)); +} + +void TemplateURLService::NotifyObservers() { + if (!loaded_) + return; + + FOR_EACH_OBSERVER(TemplateURLServiceObserver, model_observers_, + OnTemplateURLServiceChanged()); +} + +// |template_urls| are the TemplateURLs loaded from the database. +// |default_from_prefs| is the default search provider from the preferences, or +// NULL if the DSE is not policy-defined. +// +// This function removes from the vector and the database all the TemplateURLs +// that were set by policy, unless it is the current default search provider, in +// which case it is updated with the data from prefs. +void TemplateURLService::UpdateProvidersCreatedByPolicy( + TemplateURLVector* template_urls, + const TemplateURLData* default_from_prefs) { + DCHECK(template_urls); + + for (TemplateURLVector::iterator i = template_urls->begin(); + i != template_urls->end(); ) { + TemplateURL* template_url = *i; + if (template_url->created_by_policy()) { + if (default_from_prefs && + TemplateURL::MatchesData(template_url, default_from_prefs, + search_terms_data())) { + // If the database specified a default search provider that was set + // by policy, and the default search provider from the preferences + // is also set by policy and they are the same, keep the entry in the + // database and the |default_search_provider|. + default_search_provider_ = template_url; + // Prevent us from saving any other entries, or creating a new one. + default_from_prefs = NULL; + ++i; + continue; + } + + RemoveFromMaps(template_url); + i = template_urls->erase(i); + if (web_data_service_) + web_data_service_->RemoveKeyword(template_url->id()); + delete template_url; + } else { + ++i; + } + } + + if (default_from_prefs) { + default_search_provider_ = NULL; + default_search_provider_source_ = DefaultSearchManager::FROM_POLICY; + TemplateURLData new_data(*default_from_prefs); + if (new_data.sync_guid.empty()) + new_data.sync_guid = base::GenerateGUID(); + new_data.created_by_policy = true; + TemplateURL* new_dse = new TemplateURL(new_data); + if (AddNoNotify(new_dse, true)) + default_search_provider_ = new_dse; + } +} + +void TemplateURLService::ResetTemplateURLGUID(TemplateURL* url, + const std::string& guid) { + DCHECK(loaded_); + DCHECK(!guid.empty()); + + TemplateURLData data(url->data()); + data.sync_guid = guid; + UpdateNoNotify(url, TemplateURL(data)); +} + +base::string16 TemplateURLService::UniquifyKeyword(const TemplateURL& turl, + bool force) { + if (!force) { + // Already unique. + if (!GetTemplateURLForKeyword(turl.keyword())) + return turl.keyword(); + + // First, try to return the generated keyword for the TemplateURL (except + // for extensions, as their keywords are not associated with their URLs). + GURL gurl(turl.url()); + if (gurl.is_valid() && + (turl.GetType() != TemplateURL::OMNIBOX_API_EXTENSION)) { + base::string16 keyword_candidate = TemplateURL::GenerateKeyword(gurl); + if (!GetTemplateURLForKeyword(keyword_candidate)) + return keyword_candidate; + } + } + + // We try to uniquify the keyword by appending a special character to the end. + // This is a best-effort approach where we try to preserve the original + // keyword and let the user do what they will after our attempt. + base::string16 keyword_candidate(turl.keyword()); + do { + keyword_candidate.append(base::ASCIIToUTF16("_")); + } while (GetTemplateURLForKeyword(keyword_candidate)); + + return keyword_candidate; +} + +bool TemplateURLService::IsLocalTemplateURLBetter( + const TemplateURL* local_turl, + const TemplateURL* sync_turl) { + DCHECK(GetTemplateURLForGUID(local_turl->sync_guid())); + return local_turl->last_modified() > sync_turl->last_modified() || + local_turl->created_by_policy() || + local_turl== GetDefaultSearchProvider(); +} + +void TemplateURLService::ResolveSyncKeywordConflict( + TemplateURL* unapplied_sync_turl, + TemplateURL* applied_sync_turl, + syncer::SyncChangeList* change_list) { + DCHECK(loaded_); + DCHECK(unapplied_sync_turl); + DCHECK(applied_sync_turl); + DCHECK(change_list); + DCHECK_EQ(applied_sync_turl->keyword(), unapplied_sync_turl->keyword()); + DCHECK_NE(TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION, + applied_sync_turl->GetType()); + + // Both |unapplied_sync_turl| and |applied_sync_turl| are known to Sync, so + // don't delete either of them. Instead, determine which is "better" and + // uniquify the other one, sending an update to the server for the updated + // entry. + const bool applied_turl_is_better = + IsLocalTemplateURLBetter(applied_sync_turl, unapplied_sync_turl); + TemplateURL* loser = applied_turl_is_better ? + unapplied_sync_turl : applied_sync_turl; + base::string16 new_keyword = UniquifyKeyword(*loser, false); + DCHECK(!GetTemplateURLForKeyword(new_keyword)); + if (applied_turl_is_better) { + // Just set the keyword of |unapplied_sync_turl|. The caller is responsible + // for adding or updating unapplied_sync_turl in the local model. + unapplied_sync_turl->data_.SetKeyword(new_keyword); + } else { + // Update |applied_sync_turl| in the local model with the new keyword. + TemplateURLData data(applied_sync_turl->data()); + data.SetKeyword(new_keyword); + if (UpdateNoNotify(applied_sync_turl, TemplateURL(data))) + NotifyObservers(); + } + // The losing TemplateURL should have their keyword updated. Send a change to + // the server to reflect this change. + syncer::SyncData sync_data = CreateSyncDataFromTemplateURL(*loser); + change_list->push_back(syncer::SyncChange(FROM_HERE, + syncer::SyncChange::ACTION_UPDATE, + sync_data)); +} + +void TemplateURLService::MergeInSyncTemplateURL( + TemplateURL* sync_turl, + const SyncDataMap& sync_data, + syncer::SyncChangeList* change_list, + SyncDataMap* local_data, + syncer::SyncMergeResult* merge_result) { + DCHECK(sync_turl); + DCHECK(!GetTemplateURLForGUID(sync_turl->sync_guid())); + DCHECK(IsFromSync(sync_turl, sync_data)); + + TemplateURL* conflicting_turl = + FindNonExtensionTemplateURLForKeyword(sync_turl->keyword()); + bool should_add_sync_turl = true; + + // If there was no TemplateURL in the local model that conflicts with + // |sync_turl|, skip the following preparation steps and just add |sync_turl| + // directly. Otherwise, modify |conflicting_turl| to make room for + // |sync_turl|. + if (conflicting_turl) { + if (IsFromSync(conflicting_turl, sync_data)) { + // |conflicting_turl| is already known to Sync, so we're not allowed to + // remove it. In this case, we want to uniquify the worse one and send an + // update for the changed keyword to sync. We can reuse the logic from + // ResolveSyncKeywordConflict for this. + ResolveSyncKeywordConflict(sync_turl, conflicting_turl, change_list); + merge_result->set_num_items_modified( + merge_result->num_items_modified() + 1); + } else { + // |conflicting_turl| is not yet known to Sync. If it is better, then we + // want to transfer its values up to sync. Otherwise, we remove it and + // allow the entry from Sync to overtake it in the model. + const std::string guid = conflicting_turl->sync_guid(); + if (IsLocalTemplateURLBetter(conflicting_turl, sync_turl)) { + ResetTemplateURLGUID(conflicting_turl, sync_turl->sync_guid()); + syncer::SyncData sync_data = + CreateSyncDataFromTemplateURL(*conflicting_turl); + change_list->push_back(syncer::SyncChange( + FROM_HERE, syncer::SyncChange::ACTION_UPDATE, sync_data)); + // Note that in this case we do not add the Sync TemplateURL to the + // local model, since we've effectively "merged" it in by updating the + // local conflicting entry with its sync_guid. + should_add_sync_turl = false; + merge_result->set_num_items_modified( + merge_result->num_items_modified() + 1); + } else { + // We guarantee that this isn't the local search provider. Otherwise, + // local would have won. + DCHECK(conflicting_turl != GetDefaultSearchProvider()); + Remove(conflicting_turl); + merge_result->set_num_items_deleted( + merge_result->num_items_deleted() + 1); + } + // This TemplateURL was either removed or overwritten in the local model. + // Remove the entry from the local data so it isn't pushed up to Sync. + local_data->erase(guid); + } + } + + if (should_add_sync_turl) { + // Force the local ID to kInvalidTemplateURLID so we can add it. + TemplateURLData data(sync_turl->data()); + data.id = kInvalidTemplateURLID; + TemplateURL* added = new TemplateURL(data); + base::AutoReset<DefaultSearchChangeOrigin> change_origin( + &dsp_change_origin_, DSP_CHANGE_SYNC_ADD); + if (Add(added)) + MaybeUpdateDSEAfterSync(added); + merge_result->set_num_items_added( + merge_result->num_items_added() + 1); + } +} + +void TemplateURLService::PatchMissingSyncGUIDs( + TemplateURLVector* template_urls) { + DCHECK(template_urls); + for (TemplateURLVector::iterator i = template_urls->begin(); + i != template_urls->end(); ++i) { + TemplateURL* template_url = *i; + DCHECK(template_url); + if (template_url->sync_guid().empty() && + (template_url->GetType() != + TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION)) { + template_url->data_.sync_guid = base::GenerateGUID(); + if (web_data_service_) + web_data_service_->UpdateKeyword(template_url->data()); + } + } +} + +void TemplateURLService::OnSyncedDefaultSearchProviderGUIDChanged() { + base::AutoReset<DefaultSearchChangeOrigin> change_origin( + &dsp_change_origin_, DSP_CHANGE_SYNC_PREF); + + std::string new_guid = + prefs_->GetString(prefs::kSyncedDefaultSearchProviderGUID); + if (new_guid.empty()) { + default_search_manager_.ClearUserSelectedDefaultSearchEngine(); + return; + } + + TemplateURL* turl = GetTemplateURLForGUID(new_guid); + if (turl) + default_search_manager_.SetUserSelectedDefaultSearchEngine(turl->data()); +} + +TemplateURL* TemplateURLService::FindPrepopulatedTemplateURL( + int prepopulated_id) { + for (TemplateURLVector::const_iterator i = template_urls_.begin(); + i != template_urls_.end(); ++i) { + if ((*i)->prepopulate_id() == prepopulated_id) + return *i; + } + return NULL; +} + +TemplateURL* TemplateURLService::FindTemplateURLForExtension( + const std::string& extension_id, + TemplateURL::Type type) { + DCHECK_NE(TemplateURL::NORMAL, type); + for (TemplateURLVector::const_iterator i = template_urls_.begin(); + i != template_urls_.end(); ++i) { + if ((*i)->GetType() == type && + (*i)->GetExtensionId() == extension_id) + return *i; + } + return NULL; +} + +TemplateURL* TemplateURLService::FindMatchingExtensionTemplateURL( + const TemplateURLData& data, + TemplateURL::Type type) { + DCHECK_NE(TemplateURL::NORMAL, type); + for (TemplateURLVector::const_iterator i = template_urls_.begin(); + i != template_urls_.end(); ++i) { + if ((*i)->GetType() == type && + TemplateURL::MatchesData(*i, &data, search_terms_data())) + return *i; + } + return NULL; +} + +void TemplateURLService::UpdateExtensionDefaultSearchEngine() { + TemplateURL* most_recently_intalled_default = NULL; + for (TemplateURLVector::const_iterator i = template_urls_.begin(); + i != template_urls_.end(); ++i) { + if (((*i)->GetType() == TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION) && + (*i)->extension_info_->wants_to_be_default_engine && + (*i)->SupportsReplacement(search_terms_data()) && + (!most_recently_intalled_default || + (most_recently_intalled_default->extension_info_->install_time < + (*i)->extension_info_->install_time))) + most_recently_intalled_default = *i; + } + + if (most_recently_intalled_default) { + base::AutoReset<DefaultSearchChangeOrigin> change_origin( + &dsp_change_origin_, DSP_CHANGE_OVERRIDE_SETTINGS_EXTENSION); + default_search_manager_.SetExtensionControlledDefaultSearchEngine( + most_recently_intalled_default->data()); + } else { + default_search_manager_.ClearExtensionControlledDefaultSearchEngine(); + } +} diff --git a/components/search_engines/template_url_service.h b/components/search_engines/template_url_service.h new file mode 100644 index 0000000..9e1ed61 --- /dev/null +++ b/components/search_engines/template_url_service.h @@ -0,0 +1,762 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_SEARCH_ENGINES_TEMPLATE_URL_SERVICE_H_ +#define COMPONENTS_SEARCH_ENGINES_TEMPLATE_URL_SERVICE_H_ + +#include <list> +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/callback_list.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "base/prefs/pref_change_registrar.h" +#include "components/google/core/browser/google_url_tracker.h" +#include "components/keyed_service/core/keyed_service.h" +#include "components/search_engines/default_search_manager.h" +#include "components/search_engines/keyword_web_data_service.h" +#include "components/search_engines/template_url.h" +#include "components/search_engines/template_url_id.h" +#include "components/webdata/common/web_data_service_consumer.h" +#include "sync/api/sync_change.h" +#include "sync/api/syncable_service.h" + +class GURL; +class PrefService; +class SearchHostToURLsMap; +class SearchTermsData; +class TemplateURL; +struct TemplateURLData; +class TemplateURLServiceClient; +class TemplateURLServiceObserver; + +namespace rappor { +class RapporService; +} + +namespace syncer { +class SyncData; +class SyncErrorFactory; +} + +// TemplateURLService is the backend for keywords. It's used by +// KeywordAutocomplete. +// +// TemplateURLService stores a vector of TemplateURLs. The TemplateURLs are +// persisted to the database maintained by KeywordWebDataService. +// *ALL* mutations to the TemplateURLs must funnel through TemplateURLService. +// This allows TemplateURLService to notify listeners of changes as well as keep +// the database in sync. +// +// TemplateURLService does not load the vector of TemplateURLs in its +// constructor (except for testing). Use the Load method to trigger a load. +// When TemplateURLService has completed loading, observers are notified via +// OnTemplateURLServiceChanged, or by a callback registered prior to calling +// the Load method. +// +// TemplateURLService takes ownership of any TemplateURL passed to it. If there +// is a KeywordWebDataService, deletion is handled by KeywordWebDataService, +// otherwise TemplateURLService handles deletion. + +class TemplateURLService : public WebDataServiceConsumer, + public KeyedService, + public syncer::SyncableService { + public: + typedef std::map<std::string, std::string> QueryTerms; + typedef std::vector<TemplateURL*> TemplateURLVector; + // Type for a static function pointer that acts as a time source. + typedef base::Time(TimeProvider)(); + typedef std::map<std::string, syncer::SyncData> SyncDataMap; + typedef base::CallbackList<void(void)>::Subscription Subscription; + + // Struct used for initializing the data store with fake data. + // Each initializer is mapped to a TemplateURL. + struct Initializer { + const char* const keyword; + const char* const url; + const char* const content; + }; + + struct URLVisitedDetails { + GURL url; + bool is_keyword_transition; + }; + + TemplateURLService(PrefService* prefs, + scoped_ptr<SearchTermsData> search_terms_data, + KeywordWebDataService* web_data_service, + scoped_ptr<TemplateURLServiceClient> client, + GoogleURLTracker* google_url_tracker, + rappor::RapporService* rappor_service, + const base::Closure& dsp_change_callback); + // The following is for testing. + TemplateURLService(const Initializer* initializers, const int count); + virtual ~TemplateURLService(); + + // Creates a TemplateURLData that was previously saved to |prefs| via + // SaveDefaultSearchProviderToPrefs or set via policy. + // Returns true if successful, false otherwise. + // If the user or the policy has opted for no default search, this + // returns true but default_provider is set to NULL. + // |*is_managed| specifies whether the default is managed via policy. + static bool LoadDefaultSearchProviderFromPrefs( + PrefService* prefs, + scoped_ptr<TemplateURLData>* default_provider_data, + bool* is_managed); + + // Removes any unnecessary characters from a user input keyword. + // This removes the leading scheme, "www." and any trailing slash. + static base::string16 CleanUserInputKeyword(const base::string16& keyword); + + // Saves enough of url to |prefs| so that it can be loaded from preferences on + // start up. + static void SaveDefaultSearchProviderToPrefs(const TemplateURL* url, + PrefService* prefs); + + // Returns true if there is no TemplateURL that conflicts with the + // keyword/url pair, or there is one but it can be replaced. If there is an + // existing keyword that can be replaced and template_url_to_replace is + // non-NULL, template_url_to_replace is set to the keyword to replace. + // + // url gives the url of the search query. The url is used to avoid generating + // a TemplateURL for an existing TemplateURL that shares the same host. + bool CanReplaceKeyword(const base::string16& keyword, + const GURL& url, + TemplateURL** template_url_to_replace); + + // Returns (in |matches|) all TemplateURLs whose keywords begin with |prefix|, + // sorted shortest keyword-first. If |support_replacement_only| is true, only + // TemplateURLs that support replacement are returned. + void FindMatchingKeywords(const base::string16& prefix, + bool support_replacement_only, + TemplateURLVector* matches); + + // Looks up |keyword| and returns the element it maps to. Returns NULL if + // the keyword was not found. + // The caller should not try to delete the returned pointer; the data store + // retains ownership of it. + TemplateURL* GetTemplateURLForKeyword(const base::string16& keyword); + + // Returns that TemplateURL with the specified GUID, or NULL if not found. + // The caller should not try to delete the returned pointer; the data store + // retains ownership of it. + TemplateURL* GetTemplateURLForGUID(const std::string& sync_guid); + + // Returns the first TemplateURL found with a URL using the specified |host|, + // or NULL if there are no such TemplateURLs + TemplateURL* GetTemplateURLForHost(const std::string& host); + + // Takes ownership of |template_url| and adds it to this model. For obvious + // reasons, it is illegal to Add() the same |template_url| pointer twice. + // Returns true if the Add is successful. + bool Add(TemplateURL* template_url); + + // Like Add(), but overwrites the |template_url|'s values with the provided + // ones. + void AddWithOverrides(TemplateURL* template_url, + const base::string16& short_name, + const base::string16& keyword, + const std::string& url); + + // Adds a search engine with the specified info. + void AddExtensionControlledTURL( + TemplateURL* template_url, + scoped_ptr<TemplateURL::AssociatedExtensionInfo> info); + + // Removes the keyword from the model. This deletes the supplied TemplateURL. + // This fails if the supplied template_url is the default search provider. + void Remove(TemplateURL* template_url); + + // Removes any TemplateURL of the specified |type| associated with + // |extension_id|. Unlike with Remove(), this can be called when the + // TemplateURL in question is the current default search provider. + void RemoveExtensionControlledTURL(const std::string& extension_id, + TemplateURL::Type type); + + // Removes all auto-generated keywords that were created on or after the + // date passed in. + void RemoveAutoGeneratedSince(base::Time created_after); + + // Removes all auto-generated keywords that were created in the specified + // range. + void RemoveAutoGeneratedBetween(base::Time created_after, + base::Time created_before); + + // Removes all auto-generated keywords that were created in the specified + // range for a specified |origin|. If |origin| is empty, deletes all + // auto-generated keywords in the range. + void RemoveAutoGeneratedForOriginBetween(const GURL& origin, + base::Time created_after, + base::Time created_before); + + // Adds a TemplateURL for an extension with an omnibox keyword. + // Only 1 keyword is allowed for a given extension. If a keyword + // already exists for this extension, does nothing. + void RegisterOmniboxKeyword(const std::string& extension_id, + const std::string& extension_name, + const std::string& keyword, + const std::string& template_url_string); + + // Returns the set of URLs describing the keywords. The elements are owned + // by TemplateURLService and should not be deleted. + TemplateURLVector GetTemplateURLs(); + + // Increment the usage count of a keyword. + // Called when a URL is loaded that was generated from a keyword. + void IncrementUsageCount(TemplateURL* url); + + // Resets the title, keyword and search url of the specified TemplateURL. + // The TemplateURL is marked as not replaceable. + void ResetTemplateURL(TemplateURL* url, + const base::string16& title, + const base::string16& keyword, + const std::string& search_url); + + // Return true if the given |url| can be made the default. This returns false + // regardless of |url| if the default search provider is managed by policy or + // controlled by an extension. + bool CanMakeDefault(const TemplateURL* url); + + // Set the default search provider. |url| may be null. + // This will assert if the default search is managed; the UI should not be + // invoking this method in that situation. + void SetUserSelectedDefaultSearchProvider(TemplateURL* url); + + // Returns the default search provider. If the TemplateURLService hasn't been + // loaded, the default search provider is pulled from preferences. + // + // NOTE: At least in unittest mode, this may return NULL. + TemplateURL* GetDefaultSearchProvider(); + + // Returns true if the |url| is a search results page from the default search + // provider. + bool IsSearchResultsPageFromDefaultSearchProvider(const GURL& url); + + // Returns true if the default search is managed through group policy. + bool is_default_search_managed() const { + return default_search_provider_source_ == DefaultSearchManager::FROM_POLICY; + } + + // Returns true if the default search provider is controlled by an extension. + bool IsExtensionControlledDefaultSearch(); + + // Returns the default search specified in the prepopulated data, if it + // exists. If not, returns first URL in |template_urls_|, or NULL if that's + // empty. The returned object is owned by TemplateURLService and can be + // destroyed at any time so should be used right after the call. + TemplateURL* FindNewDefaultSearchProvider(); + + // Performs the same actions that happen when the prepopulate data version is + // revved: all existing prepopulated entries are checked against the current + // prepopulate data, any now-extraneous safe_for_autoreplace() entries are + // removed, any existing engines are reset to the provided data (except for + // user-edited names or keywords), and any new prepopulated engines are + // added. + // + // After this, the default search engine is reset to the default entry in the + // prepopulate data. + void RepairPrepopulatedSearchEngines(); + + // Observers used to listen for changes to the model. + // TemplateURLService does NOT delete the observers when deleted. + void AddObserver(TemplateURLServiceObserver* observer); + void RemoveObserver(TemplateURLServiceObserver* observer); + + // Loads the keywords. This has no effect if the keywords have already been + // loaded. + // Observers are notified when loading completes via the method + // OnTemplateURLServiceChanged. + void Load(); + + // Registers a callback to be called when the service has loaded. + // + // If the service has already loaded, this function does nothing. + scoped_ptr<Subscription> RegisterOnLoadedCallback( + const base::Closure& callback); + +#if defined(UNIT_TEST) + void set_loaded(bool value) { loaded_ = value; } +#endif + + // Whether or not the keywords have been loaded. + bool loaded() { return loaded_; } + + // Notification that the keywords have been loaded. + // This is invoked from WebDataService, and should not be directly + // invoked. + virtual void OnWebDataServiceRequestDone( + KeywordWebDataService::Handle h, + const WDTypedResult* result) OVERRIDE; + + // Returns the locale-direction-adjusted short name for the given keyword. + // Also sets the out param to indicate whether the keyword belongs to an + // Omnibox extension. + base::string16 GetKeywordShortName(const base::string16& keyword, + bool* is_omnibox_api_extension_keyword); + + // Called by the history service when a URL is visited. + void OnHistoryURLVisited(const URLVisitedDetails& details); + + // KeyedService implementation. + virtual void Shutdown() OVERRIDE; + + // syncer::SyncableService implementation. + + // Returns all syncable TemplateURLs from this model as SyncData. This should + // include every search engine and no Extension keywords. + virtual syncer::SyncDataList GetAllSyncData( + syncer::ModelType type) const OVERRIDE; + // Process new search engine changes from Sync, merging them into our local + // data. This may send notifications if local search engines are added, + // updated or removed. + virtual syncer::SyncError ProcessSyncChanges( + const tracked_objects::Location& from_here, + const syncer::SyncChangeList& change_list) OVERRIDE; + // Merge initial search engine data from Sync and push any local changes up + // to Sync. This may send notifications if local search engines are added, + // updated or removed. + virtual syncer::SyncMergeResult MergeDataAndStartSyncing( + syncer::ModelType type, + const syncer::SyncDataList& initial_sync_data, + scoped_ptr<syncer::SyncChangeProcessor> sync_processor, + scoped_ptr<syncer::SyncErrorFactory> sync_error_factory) OVERRIDE; + virtual void StopSyncing(syncer::ModelType type) OVERRIDE; + + // Processes a local TemplateURL change for Sync. |turl| is the TemplateURL + // that has been modified, and |type| is the Sync ChangeType that took place. + // This may send a new SyncChange to the cloud. If our model has not yet been + // associated with Sync, or if this is triggered by a Sync change, then this + // does nothing. + void ProcessTemplateURLChange(const tracked_objects::Location& from_here, + const TemplateURL* turl, + syncer::SyncChange::SyncChangeType type); + + // Returns a SearchTermsData which can be used to call TemplateURL methods. + const SearchTermsData& search_terms_data() const { + return *search_terms_data_; + } + + // Returns a SyncData with a sync representation of the search engine data + // from |turl|. + static syncer::SyncData CreateSyncDataFromTemplateURL( + const TemplateURL& turl); + + // Creates a new heap-allocated TemplateURL* which is populated by overlaying + // |sync_data| atop |existing_turl|. |existing_turl| may be NULL; if not it + // remains unmodified. The caller owns the returned TemplateURL*. + // + // If the created TemplateURL is migrated in some way from out-of-date sync + // data, an appropriate SyncChange is added to |change_list|. If the sync + // data is bad for some reason, an ACTION_DELETE change is added and the + // function returns NULL. + static TemplateURL* CreateTemplateURLFromTemplateURLAndSyncData( + PrefService* prefs, + const SearchTermsData& search_terms_data, + TemplateURL* existing_turl, + const syncer::SyncData& sync_data, + syncer::SyncChangeList* change_list); + + // Returns a map mapping Sync GUIDs to pointers to syncer::SyncData. + static SyncDataMap CreateGUIDToSyncDataMap( + const syncer::SyncDataList& sync_data); + +#if defined(UNIT_TEST) + // Sets a different time provider function, such as + // base::MockTimeProvider::StaticNow, for testing calls to base::Time::Now. + void set_time_provider(TimeProvider* time_provider) { + time_provider_ = time_provider; + } +#endif + + protected: + // Cover method for the method of the same name on the HistoryService. + // url is the one that was visited with the given search terms. + // + // This exists and is virtual for testing. + virtual void SetKeywordSearchTermsForURL(const TemplateURL* t_url, + const GURL& url, + const base::string16& term); + + private: + FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceTest, TestManagedDefaultSearch); + FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceTest, + UpdateKeywordSearchTermsForURL); + FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceTest, + DontUpdateKeywordSearchForNonReplaceable); + FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceTest, ChangeGoogleBaseValue); + FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceTest, MergeDeletesUnusedProviders); + FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceSyncTest, UniquifyKeyword); + FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceSyncTest, + IsLocalTemplateURLBetter); + FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceSyncTest, + ResolveSyncKeywordConflict); + FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceSyncTest, PreSyncDeletes); + FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceSyncTest, MergeInSyncTemplateURL); + + friend class InstantUnitTestBase; + friend class TemplateURLServiceTestUtilBase; + + typedef std::map<base::string16, TemplateURL*> KeywordToTemplateMap; + typedef std::map<std::string, TemplateURL*> GUIDToTemplateMap; + + // Declaration of values to be used in an enumerated histogram to tally + // changes to the default search provider from various entry points. In + // particular, we use this to see what proportion of changes are from Sync + // entry points, to help spot erroneous Sync activity. + enum DefaultSearchChangeOrigin { + // Various known Sync entry points. + DSP_CHANGE_SYNC_PREF, + DSP_CHANGE_SYNC_ADD, + DSP_CHANGE_SYNC_DELETE, + DSP_CHANGE_SYNC_NOT_MANAGED, + // "Other" origins. We differentiate between Sync and not Sync so we know if + // certain changes were intentionally from the system, or possibly some + // unintentional change from when we were Syncing. + DSP_CHANGE_SYNC_UNINTENTIONAL, + // All changes that don't fall into another category; we can't reorder the + // list for clarity as this would screw up stat collection. + DSP_CHANGE_OTHER, + // Changed through "Profile Reset" feature. + DSP_CHANGE_PROFILE_RESET, + // Changed by an extension through the Override Settings API. + DSP_CHANGE_OVERRIDE_SETTINGS_EXTENSION, + // New DSP during database/prepopulate data load, which was not previously + // in the known engine set, and with no previous value in prefs. The + // typical time to see this is during first run. + DSP_CHANGE_NEW_ENGINE_NO_PREFS, + // Boundary value. + DSP_CHANGE_MAX, + }; + + // Helper functor for FindMatchingKeywords(), for finding the range of + // keywords which begin with a prefix. + class LessWithPrefix; + + void Init(const Initializer* initializers, int num_initializers); + + void RemoveFromMaps(TemplateURL* template_url); + + void AddToMaps(TemplateURL* template_url); + + // Sets the keywords. This is used once the keywords have been loaded. + // This does NOT notify the delegate or the database. + // + // This transfers ownership of the elements in |urls| to |this|, and may + // delete some elements, so it's not safe for callers to access any elements + // after calling; to reinforce this, this function clears |urls| on exit. + void SetTemplateURLs(TemplateURLVector* urls); + + // Transitions to the loaded state. + void ChangeToLoadedState(); + + // Callback that is called when the Google URL is updated. + void OnGoogleURLUpdated(GURL old_url, GURL new_url); + + // Called by DefaultSearchManager when the effective default search engine has + // changed. + void OnDefaultSearchChange(const TemplateURLData* new_dse_data, + DefaultSearchManager::Source source); + + // Applies a DSE change and reports metrics if appropriate. + void ApplyDefaultSearchChange(const TemplateURLData* new_dse_data, + DefaultSearchManager::Source source); + + + // Applies a DSE change. May be called at startup or after transitioning to + // the loaded state. Returns true if a change actually occurred. + bool ApplyDefaultSearchChangeNoMetrics(const TemplateURLData* new_dse_data, + DefaultSearchManager::Source source); + + // Returns true if there is no TemplateURL that has a search url with the + // specified host, or the only TemplateURLs matching the specified host can + // be replaced. + bool CanReplaceKeywordForHost(const std::string& host, + TemplateURL** to_replace); + + // Returns true if the TemplateURL is replaceable. This doesn't look at the + // uniqueness of the keyword or host and is intended to be called after those + // checks have been done. This returns true if the TemplateURL doesn't appear + // in the default list and is marked as safe_for_autoreplace. + bool CanReplace(const TemplateURL* t_url); + + // Like GetTemplateURLForKeyword(), but ignores extension-provided keywords. + TemplateURL* FindNonExtensionTemplateURLForKeyword( + const base::string16& keyword); + + // Updates the information in |existing_turl| using the information from + // |new_values|, but the ID for |existing_turl| is retained. Notifying + // observers is the responsibility of the caller. Returns whether + // |existing_turl| was found in |template_urls_| and thus could be updated. + // + // NOTE: This should not be called with an extension keyword as there are no + // updates needed in that case. + bool UpdateNoNotify(TemplateURL* existing_turl, + const TemplateURL& new_values); + + // If the TemplateURL comes from a prepopulated URL available in the current + // country, update all its fields save for the keyword, short name and id so + // that they match the internal prepopulated URL. TemplateURLs not coming from + // a prepopulated URL are not modified. + static void UpdateTemplateURLIfPrepopulated(TemplateURL* existing_turl, + PrefService* prefs); + + // If the TemplateURL's sync GUID matches the kSyncedDefaultSearchProviderGUID + // preference it will be used to update the DSE in memory and as persisted in + // preferences. + void MaybeUpdateDSEAfterSync(TemplateURL* synced_turl); + + // Iterates through the TemplateURLs to see if one matches the visited url. + // For each TemplateURL whose url matches the visited url + // SetKeywordSearchTermsForURL is invoked. + void UpdateKeywordSearchTermsForURL(const URLVisitedDetails& details); + + // If necessary, generates a visit for the site http:// + t_url.keyword(). + void AddTabToSearchVisit(const TemplateURL& t_url); + + // Requests the Google URL tracker to check the server if necessary. + void RequestGoogleURLTrackerServerCheckIfNecessary(); + + // Invoked when the Google base URL has changed. Updates the mapping for all + // TemplateURLs that have a replacement term of {google:baseURL} or + // {google:baseSuggestURL}. + void GoogleBaseURLChanged(); + + // Adds a new TemplateURL to this model. TemplateURLService will own the + // reference, and delete it when the TemplateURL is removed. + // If |newly_adding| is false, we assume that this TemplateURL was already + // part of the model in the past, and therefore we don't need to do things + // like assign it an ID or notify sync. + // This function guarantees that on return the model will not have two + // non-extension TemplateURLs with the same keyword. If that means that it + // cannot add the provided argument, it will delete it and return false. + // Caller is responsible for notifying observers if this function returns + // true. + bool AddNoNotify(TemplateURL* template_url, bool newly_adding); + + // Removes the keyword from the model. This deletes the supplied TemplateURL. + // This fails if the supplied template_url is the default search provider. + // Caller is responsible for notifying observers. + void RemoveNoNotify(TemplateURL* template_url); + + // Like ResetTemplateURL(), but instead of notifying observers, returns + // whether anything has changed. + bool ResetTemplateURLNoNotify(TemplateURL* url, + const base::string16& title, + const base::string16& keyword, + const std::string& search_url); + + // Notify the observers that the model has changed. This is done only if the + // model is loaded. + void NotifyObservers(); + + // Updates |template_urls| so that the only "created by policy" entry is + // |default_from_prefs|. |default_from_prefs| may be NULL if there is no + // policy-defined DSE in effect. + void UpdateProvidersCreatedByPolicy( + TemplateURLVector* template_urls, + const TemplateURLData* default_from_prefs); + + // Resets the sync GUID of the specified TemplateURL and persists the change + // to the database. This does not notify observers. + void ResetTemplateURLGUID(TemplateURL* url, const std::string& guid); + + // Attempts to generate a unique keyword for |turl| based on its original + // keyword. If its keyword is already unique, that is returned. Otherwise, it + // tries to return the autogenerated keyword if that is unique to the Service, + // and finally it repeatedly appends special characters to the keyword until + // it is unique to the Service. If |force| is true, then this will only + // execute the special character appending functionality. + base::string16 UniquifyKeyword(const TemplateURL& turl, bool force); + + // Returns true iff |local_turl| is considered "better" than |sync_turl| for + // the purposes of resolving conflicts. |local_turl| must be a TemplateURL + // known to the local model (though it may already be synced), and |sync_turl| + // is a new TemplateURL known to Sync but not yet known to the local model. + // The criteria for if |local_turl| is better than |sync_turl| is whether any + // of the following are true: + // * |local_turl|'s last_modified timestamp is newer than sync_turl. + // * |local_turl| is created by policy. + // * |local_turl| is the local default search provider. + bool IsLocalTemplateURLBetter(const TemplateURL* local_turl, + const TemplateURL* sync_turl); + + // Given two synced TemplateURLs with a conflicting keyword, one of which + // needs to be added to or updated in the local model (|unapplied_sync_turl|) + // and one which is already known to the local model (|applied_sync_turl|), + // prepares the local model so that |unapplied_sync_turl| can be added to it, + // or applied as an update to an existing TemplateURL. + // Since both entries are known to Sync and one of their keywords will change, + // an ACTION_UPDATE will be appended to |change_list| to reflect this change. + // Note that |applied_sync_turl| must not be an extension keyword. + void ResolveSyncKeywordConflict(TemplateURL* unapplied_sync_turl, + TemplateURL* applied_sync_turl, + syncer::SyncChangeList* change_list); + + // Adds |sync_turl| into the local model, possibly removing or updating a + // local TemplateURL to make room for it. This expects |sync_turl| to be a new + // entry from Sync, not currently known to the local model. |sync_data| should + // be a SyncDataMap where the contents are entries initially known to Sync + // during MergeDataAndStartSyncing. + // Any necessary updates to Sync will be appended to |change_list|. This can + // include updates on local TemplateURLs, if they are found in |sync_data|. + // |initial_data| should be a SyncDataMap of the entries known to the local + // model during MergeDataAndStartSyncing. If |sync_turl| replaces a local + // entry, that entry is removed from |initial_data| to prevent it from being + // sent up to Sync. + // |merge_result| tracks the changes made to the local model. Added/modified/ + // deleted are updated depending on how the |sync_turl| is merged in. + // This should only be called from MergeDataAndStartSyncing. + void MergeInSyncTemplateURL(TemplateURL* sync_turl, + const SyncDataMap& sync_data, + syncer::SyncChangeList* change_list, + SyncDataMap* local_data, + syncer::SyncMergeResult* merge_result); + + // Goes through a vector of TemplateURLs and ensure that both the in-memory + // and database copies have valid sync_guids. This is to fix crbug.com/102038, + // where old entries were being pushed to Sync without a sync_guid. + void PatchMissingSyncGUIDs(TemplateURLVector* template_urls); + + void OnSyncedDefaultSearchProviderGUIDChanged(); + + // Adds |template_urls| to |template_urls_|. + // + // This transfers ownership of the elements in |template_urls| to |this|, and + // may delete some elements, so it's not safe for callers to access any + // elements after calling; to reinforce this, this function clears + // |template_urls| on exit. + void AddTemplateURLs(TemplateURLVector* template_urls); + + // Returns the TemplateURL corresponding to |prepopulated_id|, if any. + TemplateURL* FindPrepopulatedTemplateURL(int prepopulated_id); + + // Returns the TemplateURL associated with |extension_id|, if any. + TemplateURL* FindTemplateURLForExtension(const std::string& extension_id, + TemplateURL::Type type); + + // Finds the extension-supplied TemplateURL that matches |data|, if any. + TemplateURL* FindMatchingExtensionTemplateURL(const TemplateURLData& data, + TemplateURL::Type type); + + // Finds the most recently-installed NORMAL_CONTROLLED_BY_EXTENSION engine + // that supports replacement and wants to be default, if any. Notifies the + // DefaultSearchManager, which might change the effective default search + // engine. + void UpdateExtensionDefaultSearchEngine(); + + + // ---------- Browser state related members --------------------------------- + PrefService* prefs_; + + scoped_ptr<SearchTermsData> search_terms_data_; + + // ---------- Dependencies on other components ------------------------------ + // Service used to store entries. + scoped_refptr<KeywordWebDataService> web_data_service_; + + scoped_ptr<TemplateURLServiceClient> client_; + + GoogleURLTracker* google_url_tracker_; + + // ---------- Metrics related members --------------------------------------- + rappor::RapporService* rappor_service_; + + // This closure is run when the default search provider is set to Google. + base::Closure dsp_change_callback_; + + + PrefChangeRegistrar pref_change_registrar_; + + // Mapping from keyword to the TemplateURL. + KeywordToTemplateMap keyword_to_template_map_; + + // Mapping from Sync GUIDs to the TemplateURL. + GUIDToTemplateMap guid_to_template_map_; + + TemplateURLVector template_urls_; + + ObserverList<TemplateURLServiceObserver> model_observers_; + + // Maps from host to set of TemplateURLs whose search url host is host. + // NOTE: This is always non-NULL; we use a scoped_ptr<> to avoid circular + // header dependencies. + scoped_ptr<SearchHostToURLsMap> provider_map_; + + // Whether the keywords have been loaded. + bool loaded_; + + // Set when the web data service fails to load properly. This prevents + // further communication with sync or writing to prefs, so we don't persist + // inconsistent state data anywhere. + bool load_failed_; + + // If non-zero, we're waiting on a load. + KeywordWebDataService::Handle load_handle_; + + // All visits that occurred before we finished loading. Once loaded + // UpdateKeywordSearchTermsForURL is invoked for each element of the vector. + std::vector<URLVisitedDetails> visits_to_add_; + + // Once loaded, the default search provider. This is a pointer to a + // TemplateURL owned by |template_urls_|. + TemplateURL* default_search_provider_; + + // A temporary location for the DSE until Web Data has been loaded and it can + // be merged into |template_urls_|. + scoped_ptr<TemplateURL> initial_default_search_provider_; + + // Source of the default search provider. + DefaultSearchManager::Source default_search_provider_source_; + + // ID assigned to next TemplateURL added to this model. This is an ever + // increasing integer that is initialized from the database. + TemplateURLID next_id_; + + // Function returning current time in base::Time units. + TimeProvider* time_provider_; + + // Do we have an active association between the TemplateURLs and sync models? + // Set in MergeDataAndStartSyncing, reset in StopSyncing. While this is not + // set, we ignore any local search engine changes (when we start syncing we + // will look up the most recent values anyways). + bool models_associated_; + + // Whether we're currently processing changes from the syncer. While this is + // true, we ignore any local search engine changes, since we triggered them. + bool processing_syncer_changes_; + + // Sync's syncer::SyncChange handler. We push all our changes through this. + scoped_ptr<syncer::SyncChangeProcessor> sync_processor_; + + // Sync's error handler. We use it to create a sync error. + scoped_ptr<syncer::SyncErrorFactory> sync_error_factory_; + + // A set of sync GUIDs denoting TemplateURLs that have been removed from this + // model or the underlying KeywordWebDataService prior to + // MergeDataAndStartSyncing. + // This set is used to determine what entries from the server we want to + // ignore locally and return a delete command for. + std::set<std::string> pre_sync_deletes_; + + // This is used to log the origin of changes to the default search provider. + // We set this value to increasingly specific values when we know what is the + // cause/origin of a default search change. + DefaultSearchChangeOrigin dsp_change_origin_; + + // Stores a list of callbacks to be run after TemplateURLService has loaded. + base::CallbackList<void(void)> on_loaded_callbacks_; + + // Helper class to manage the default search engine. + DefaultSearchManager default_search_manager_; + + scoped_ptr<GoogleURLTracker::Subscription> google_url_updated_subscription_; + + DISALLOW_COPY_AND_ASSIGN(TemplateURLService); +}; + +#endif // COMPONENTS_SEARCH_ENGINES_TEMPLATE_URL_SERVICE_H_ diff --git a/components/search_engines/template_url_service_client.h b/components/search_engines/template_url_service_client.h new file mode 100644 index 0000000..9ab8068 --- /dev/null +++ b/components/search_engines/template_url_service_client.h @@ -0,0 +1,36 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_SEARCH_ENGINES_TEMPLATE_URL_SERVICE_CLIENT_H_ +#define COMPONENTS_SEARCH_ENGINES_TEMPLATE_URL_SERVICE_CLIENT_H_ + +#include "base/strings/string16.h" +#include "components/search_engines/template_url_id.h" + +class GURL; +class TemplateURLService; + +// This interface provides history related functionality required by +// TemplateURLService. +// TODO(hashimoto): Get rid of this once HistoryService gets componentized. +class TemplateURLServiceClient { + public: + virtual ~TemplateURLServiceClient() {} + + // Sets the pointer to the owner of this object. + virtual void SetOwner(TemplateURLService* owner) = 0; + + // Deletes all search terms for the specified keyword. + virtual void DeleteAllSearchTermsForKeyword(TemplateURLID id) = 0; + + // Sets the search terms for the specified url and keyword. + virtual void SetKeywordSearchTermsForURL(const GURL& url, + TemplateURLID id, + const base::string16& term) = 0; + + // Adds the given URL to history as a keyword generated visit. + virtual void AddKeywordGeneratedVisit(const GURL& url) = 0; +}; + +#endif // COMPONENTS_SEARCH_ENGINES_TEMPLATE_URL_SERVICE_CLIENT_H_ diff --git a/components/search_engines/template_url_service_observer.h b/components/search_engines/template_url_service_observer.h new file mode 100644 index 0000000..acd016f --- /dev/null +++ b/components/search_engines/template_url_service_observer.h @@ -0,0 +1,19 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_SEARCH_ENGINES_TEMPLATE_URL_SERVICE_OBSERVER_H_ +#define COMPONENTS_SEARCH_ENGINES_TEMPLATE_URL_SERVICE_OBSERVER_H_ + +// TemplateURLServiceObserver is notified whenever the set of TemplateURLs +// are modified. +class TemplateURLServiceObserver { + public: + // Notification that the template url model has changed in some way. + virtual void OnTemplateURLServiceChanged() = 0; + + protected: + virtual ~TemplateURLServiceObserver() {} +}; + +#endif // COMPONENTS_SEARCH_ENGINES_TEMPLATE_URL_SERVICE_OBSERVER_H_ diff --git a/components/search_engines/util.cc b/components/search_engines/util.cc new file mode 100644 index 0000000..961a93e --- /dev/null +++ b/components/search_engines/util.cc @@ -0,0 +1,389 @@ +// Copyright 2014 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 "components/search_engines/util.h" + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/logging.h" +#include "base/memory/scoped_vector.h" +#include "base/prefs/pref_service.h" +#include "base/time/time.h" +#include "components/search_engines/template_url.h" +#include "components/search_engines/template_url_prepopulate_data.h" +#include "components/search_engines/template_url_service.h" + +base::string16 GetDefaultSearchEngineName(TemplateURLService* service) { + DCHECK(service); + const TemplateURL* const default_provider = + service->GetDefaultSearchProvider(); + if (!default_provider) { + // TODO(cpu): bug 1187517. It is possible to have no default provider. + // returning an empty string is a stopgap measure for the crash + // http://code.google.com/p/chromium/issues/detail?id=2573 + return base::string16(); + } + return default_provider->short_name(); +} + +GURL GetDefaultSearchURLForSearchTerms(TemplateURLService* service, + const base::string16& terms) { + DCHECK(service); + const TemplateURL* default_provider = service->GetDefaultSearchProvider(); + if (!default_provider) + return GURL(); + const TemplateURLRef& search_url = default_provider->url_ref(); + DCHECK(search_url.SupportsReplacement(service->search_terms_data())); + TemplateURLRef::SearchTermsArgs search_terms_args(terms); + search_terms_args.append_extra_query_params = true; + return GURL(search_url.ReplaceSearchTerms(search_terms_args, + service->search_terms_data())); +} + +void RemoveDuplicatePrepopulateIDs( + KeywordWebDataService* service, + const ScopedVector<TemplateURLData>& prepopulated_urls, + TemplateURL* default_search_provider, + TemplateURLService::TemplateURLVector* template_urls, + const SearchTermsData& search_terms_data, + std::set<std::string>* removed_keyword_guids) { + DCHECK(template_urls); + + // For convenience construct an ID->TemplateURL* map from |prepopulated_urls|. + typedef std::map<int, TemplateURLData*> PrepopulatedURLMap; + PrepopulatedURLMap prepopulated_url_map; + for (std::vector<TemplateURLData*>::const_iterator i( + prepopulated_urls.begin()); + i != prepopulated_urls.end(); + ++i) + prepopulated_url_map[(*i)->prepopulate_id] = *i; + + // Separate |template_urls| into prepopulated and non-prepopulated groups. + typedef std::multimap<int, TemplateURL*> UncheckedURLMap; + UncheckedURLMap unchecked_urls; + TemplateURLService::TemplateURLVector checked_urls; + for (TemplateURLService::TemplateURLVector::iterator i( + template_urls->begin()); i != template_urls->end(); ++i) { + TemplateURL* turl = *i; + int prepopulate_id = turl->prepopulate_id(); + if (prepopulate_id) + unchecked_urls.insert(std::make_pair(prepopulate_id, turl)); + else + checked_urls.push_back(turl); + } + + // For each group of prepopulated URLs with one ID, find the best URL to use + // and add it to the (initially all non-prepopulated) URLs we've already OKed. + // Delete the others from the service and from memory. + while (!unchecked_urls.empty()) { + // Find the best URL. + int prepopulate_id = unchecked_urls.begin()->first; + PrepopulatedURLMap::const_iterator prepopulated_url = + prepopulated_url_map.find(prepopulate_id); + UncheckedURLMap::iterator end = unchecked_urls.upper_bound(prepopulate_id); + UncheckedURLMap::iterator best = unchecked_urls.begin(); + bool matched_keyword = false; + for (UncheckedURLMap::iterator i = unchecked_urls.begin(); i != end; ++i) { + // If the user-selected DSE is a prepopulated engine its properties will + // either come from the prepopulation origin or from the user preferences + // file (see DefaultSearchManager). Those properties will end up + // overwriting whatever we load now anyway. If we are eliminating + // duplicates, then, we err on the side of keeping the thing that looks + // more like the value we will end up with in the end. + if (default_search_provider && + (default_search_provider->prepopulate_id() == + i->second->prepopulate_id()) && + default_search_provider->HasSameKeywordAs(i->second->data(), + search_terms_data)) { + best = i; + break; + } + + // Otherwise, a URL is best if it matches the prepopulated data's keyword; + // if none match, just fall back to using the one with the lowest ID. + if (matched_keyword) + continue; + if ((prepopulated_url != prepopulated_url_map.end()) && + i->second->HasSameKeywordAs(*prepopulated_url->second, + search_terms_data)) { + best = i; + matched_keyword = true; + } else if (i->second->id() < best->second->id()) { + best = i; + } + } + + // Add the best URL to the checked group and delete the rest. + checked_urls.push_back(best->second); + for (UncheckedURLMap::iterator i = unchecked_urls.begin(); i != end; ++i) { + if (i == best) + continue; + if (service) { + service->RemoveKeyword(i->second->id()); + if (removed_keyword_guids) + removed_keyword_guids->insert(i->second->sync_guid()); + } + delete i->second; + } + + // Done with this group. + unchecked_urls.erase(unchecked_urls.begin(), end); + } + + // Return the checked URLs. + template_urls->swap(checked_urls); +} + +// Returns the TemplateURL with id specified from the list of TemplateURLs. +// If not found, returns NULL. +TemplateURL* GetTemplateURLByID( + const TemplateURLService::TemplateURLVector& template_urls, + int64 id) { + for (TemplateURLService::TemplateURLVector::const_iterator i( + template_urls.begin()); i != template_urls.end(); ++i) { + if ((*i)->id() == id) { + return *i; + } + } + return NULL; +} + +TemplateURL* FindURLByPrepopulateID( + const TemplateURLService::TemplateURLVector& template_urls, + int prepopulate_id) { + for (std::vector<TemplateURL*>::const_iterator i = template_urls.begin(); + i < template_urls.end(); ++i) { + if ((*i)->prepopulate_id() == prepopulate_id) + return *i; + } + return NULL; +} + +void MergeIntoPrepopulatedEngineData(const TemplateURL* original_turl, + TemplateURLData* prepopulated_url) { + DCHECK_EQ(original_turl->prepopulate_id(), prepopulated_url->prepopulate_id); + if (!original_turl->safe_for_autoreplace()) { + prepopulated_url->safe_for_autoreplace = false; + prepopulated_url->SetKeyword(original_turl->keyword()); + prepopulated_url->short_name = original_turl->short_name(); + } + prepopulated_url->id = original_turl->id(); + prepopulated_url->sync_guid = original_turl->sync_guid(); + prepopulated_url->date_created = original_turl->date_created(); + prepopulated_url->last_modified = original_turl->last_modified(); +} + +ActionsFromPrepopulateData::ActionsFromPrepopulateData() {} + +ActionsFromPrepopulateData::~ActionsFromPrepopulateData() {} + +// This is invoked when the version of the prepopulate data changes. +// If |removed_keyword_guids| is not NULL, the Sync GUID of each item removed +// from the DB will be added to it. Note that this function will take +// ownership of |prepopulated_urls| and will clear the vector. +void MergeEnginesFromPrepopulateData( + KeywordWebDataService* service, + ScopedVector<TemplateURLData>* prepopulated_urls, + size_t default_search_index, + TemplateURLService::TemplateURLVector* template_urls, + TemplateURL* default_search_provider, + std::set<std::string>* removed_keyword_guids) { + DCHECK(prepopulated_urls); + DCHECK(template_urls); + + ActionsFromPrepopulateData actions(CreateActionsFromCurrentPrepopulateData( + prepopulated_urls, *template_urls, default_search_provider)); + + // Remove items. + for (std::vector<TemplateURL*>::iterator i = actions.removed_engines.begin(); + i < actions.removed_engines.end(); ++i) { + scoped_ptr<TemplateURL> template_url(*i); + TemplateURLService::TemplateURLVector::iterator j = + std::find(template_urls->begin(), template_urls->end(), template_url); + DCHECK(j != template_urls->end()); + DCHECK(!default_search_provider || + (*j)->prepopulate_id() != default_search_provider->prepopulate_id()); + template_urls->erase(j); + if (service) { + service->RemoveKeyword(template_url->id()); + if (removed_keyword_guids) + removed_keyword_guids->insert(template_url->sync_guid()); + } + } + + // Edit items. + for (EditedEngines::iterator i(actions.edited_engines.begin()); + i < actions.edited_engines.end(); ++i) { + TemplateURLData& data = i->second; + scoped_ptr<TemplateURL> existing_url(i->first); + if (service) + service->UpdateKeyword(data); + + // Replace the entry in |template_urls| with the updated one. + TemplateURLService::TemplateURLVector::iterator j = std::find( + template_urls->begin(), template_urls->end(), existing_url.get()); + *j = new TemplateURL(data); + } + + // Add items. + for (std::vector<TemplateURLData>::const_iterator it = + actions.added_engines.begin(); + it != actions.added_engines.end(); + ++it) { + template_urls->push_back(new TemplateURL(*it)); + } +} + +ActionsFromPrepopulateData CreateActionsFromCurrentPrepopulateData( + ScopedVector<TemplateURLData>* prepopulated_urls, + const TemplateURLService::TemplateURLVector& existing_urls, + const TemplateURL* default_search_provider) { + // Create a map to hold all provided |template_urls| that originally came from + // prepopulate data (i.e. have a non-zero prepopulate_id()). + typedef std::map<int, TemplateURL*> IDMap; + IDMap id_to_turl; + for (TemplateURLService::TemplateURLVector::const_iterator i( + existing_urls.begin()); i != existing_urls.end(); ++i) { + int prepopulate_id = (*i)->prepopulate_id(); + if (prepopulate_id > 0) + id_to_turl[prepopulate_id] = *i; + } + + // For each current prepopulated URL, check whether |template_urls| contained + // a matching prepopulated URL. If so, update the passed-in URL to match the + // current data. (If the passed-in URL was user-edited, we persist the user's + // name and keyword.) If not, add the prepopulated URL. + ActionsFromPrepopulateData actions; + for (size_t i = 0; i < prepopulated_urls->size(); ++i) { + // We take ownership of |prepopulated_urls[i]|. + scoped_ptr<TemplateURLData> prepopulated_url((*prepopulated_urls)[i]); + const int prepopulated_id = prepopulated_url->prepopulate_id; + DCHECK_NE(0, prepopulated_id); + + IDMap::iterator existing_url_iter(id_to_turl.find(prepopulated_id)); + if (existing_url_iter != id_to_turl.end()) { + // Update the data store with the new prepopulated data. Preserve user + // edits to the name and keyword. + TemplateURL* existing_url(existing_url_iter->second); + id_to_turl.erase(existing_url_iter); + MergeIntoPrepopulatedEngineData(existing_url, prepopulated_url.get()); + // Update last_modified to ensure that if this entry is later merged with + // entries from Sync, the conflict resolution logic knows that this was + // updated and propagates the new values to the server. + prepopulated_url->last_modified = base::Time::Now(); + actions.edited_engines.push_back( + std::make_pair(existing_url, *prepopulated_url)); + } else { + actions.added_engines.push_back(*prepopulated_url); + } + } + // The above loop takes ownership of all the contents of prepopulated_urls. + // Clear the pointers. + prepopulated_urls->weak_erase(prepopulated_urls->begin(), + prepopulated_urls->end()); + + // The block above removed all the URLs from the |id_to_turl| map that were + // found in the prepopulate data. Any remaining URLs that haven't been + // user-edited or made default can be removed from the data store. + // We assume that this entry is equivalent to the DSE if its prepopulate ID + // and keyword both match. If the prepopulate ID _does_ match all properties + // will be replaced with those from |default_search_provider| anyway. + for (IDMap::iterator i(id_to_turl.begin()); i != id_to_turl.end(); ++i) { + TemplateURL* template_url = i->second; + if ((template_url->safe_for_autoreplace()) && + (!default_search_provider || + (template_url->prepopulate_id() != + default_search_provider->prepopulate_id()) || + (template_url->keyword() != default_search_provider->keyword()))) + actions.removed_engines.push_back(template_url); + } + + return actions; +} + +void GetSearchProvidersUsingKeywordResult( + const WDTypedResult& result, + KeywordWebDataService* service, + PrefService* prefs, + TemplateURLService::TemplateURLVector* template_urls, + TemplateURL* default_search_provider, + const SearchTermsData& search_terms_data, + int* new_resource_keyword_version, + std::set<std::string>* removed_keyword_guids) { + DCHECK(template_urls); + DCHECK(template_urls->empty()); + DCHECK_EQ(KEYWORDS_RESULT, result.GetType()); + DCHECK(new_resource_keyword_version); + + WDKeywordsResult keyword_result = reinterpret_cast< + const WDResult<WDKeywordsResult>*>(&result)->GetValue(); + + for (KeywordTable::Keywords::iterator i(keyword_result.keywords.begin()); + i != keyword_result.keywords.end(); ++i) { + // Fix any duplicate encodings in the local database. Note that we don't + // adjust the last_modified time of this keyword; this way, we won't later + // overwrite any changes on the sync server that happened to this keyword + // since the last time we synced. Instead, we also run a de-duping pass on + // the server-provided data in + // TemplateURLService::CreateTemplateURLFromTemplateURLAndSyncData() and + // update the server with the merged, de-duped results at that time. We + // still fix here, though, to correct problems in clients that have disabled + // search engine sync, since in that case that code will never be reached. + if (DeDupeEncodings(&i->input_encodings) && service) + service->UpdateKeyword(*i); + template_urls->push_back(new TemplateURL(*i)); + } + + *new_resource_keyword_version = keyword_result.builtin_keyword_version; + GetSearchProvidersUsingLoadedEngines(service, prefs, template_urls, + default_search_provider, + search_terms_data, + new_resource_keyword_version, + removed_keyword_guids); +} + +void GetSearchProvidersUsingLoadedEngines( + KeywordWebDataService* service, + PrefService* prefs, + TemplateURLService::TemplateURLVector* template_urls, + TemplateURL* default_search_provider, + const SearchTermsData& search_terms_data, + int* resource_keyword_version, + std::set<std::string>* removed_keyword_guids) { + DCHECK(template_urls); + DCHECK(resource_keyword_version); + size_t default_search_index; + ScopedVector<TemplateURLData> prepopulated_urls = + TemplateURLPrepopulateData::GetPrepopulatedEngines(prefs, + &default_search_index); + RemoveDuplicatePrepopulateIDs(service, prepopulated_urls, + default_search_provider, template_urls, + search_terms_data, removed_keyword_guids); + + const int prepopulate_resource_keyword_version = + TemplateURLPrepopulateData::GetDataVersion(prefs); + if (*resource_keyword_version < prepopulate_resource_keyword_version) { + MergeEnginesFromPrepopulateData( + service, &prepopulated_urls, default_search_index, template_urls, + default_search_provider, removed_keyword_guids); + *resource_keyword_version = prepopulate_resource_keyword_version; + } else { + *resource_keyword_version = 0; + } +} + +bool DeDupeEncodings(std::vector<std::string>* encodings) { + std::vector<std::string> deduped_encodings; + std::set<std::string> encoding_set; + for (std::vector<std::string>::const_iterator i(encodings->begin()); + i != encodings->end(); ++i) { + if (encoding_set.insert(*i).second) + deduped_encodings.push_back(*i); + } + encodings->swap(deduped_encodings); + return encodings->size() != deduped_encodings.size(); +} diff --git a/components/search_engines/util.h b/components/search_engines/util.h new file mode 100644 index 0000000..a9afa2f --- /dev/null +++ b/components/search_engines/util.h @@ -0,0 +1,136 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_SEARCH_ENGINES_UTIL_H_ +#define COMPONENTS_SEARCH_ENGINES_UTIL_H_ + +// This file contains utility functions for search engine functionality. +#include <set> +#include <string> +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" +#include "components/search_engines/template_url_service.h" + +class KeywordWebDataService; +class PrefService; +class TemplateURL; +class WDTypedResult; + +// Returns the short name of the default search engine, or the empty string if +// none is set. +base::string16 GetDefaultSearchEngineName(TemplateURLService* service); + +// Returns a GURL that searches for |terms| using the default search engine of +// |service|. +GURL GetDefaultSearchURLForSearchTerms(TemplateURLService* service, + const base::string16& terms); + +// Returns matching URL from |template_urls| or NULL. +TemplateURL* FindURLByPrepopulateID( + const TemplateURLService::TemplateURLVector& template_urls, + int prepopulate_id); + +// Modifies |prepopulated_url| so that it contains user-modified fields from +// |original_turl|. Both URLs must have the same prepopulate_id. +void MergeIntoPrepopulatedEngineData(const TemplateURL* original_turl, + TemplateURLData* prepopulated_url); + +// CreateActionsFromCurrentPrepopulateData() (see below) takes in the current +// prepopulated URLs as well as the user's current URLs, and returns an instance +// of the following struct representing the changes necessary to bring the +// user's URLs in line with the prepopulated URLs. +// +// There are three types of changes: +// (1) Previous prepopulated engines that no longer exist in the current set of +// prepopulated engines and thus should be removed from the user's current +// URLs. +// (2) Previous prepopulated engines whose data has changed. The existing +// entries for these engines should be updated to reflect the new data, +// except for any user-set names and keywords, which can be preserved. +// (3) New prepopulated engines not in the user's engine list, which should be +// added. + +// The pair of current search engine and its new value. +typedef std::pair<TemplateURL*, TemplateURLData> EditedSearchEngine; +typedef std::vector<EditedSearchEngine> EditedEngines; + +struct ActionsFromPrepopulateData { + ActionsFromPrepopulateData(); + ~ActionsFromPrepopulateData(); + + TemplateURLService::TemplateURLVector removed_engines; + EditedEngines edited_engines; + std::vector<TemplateURLData> added_engines; +}; + +// Given the user's current URLs and the current set of prepopulated URLs, +// produces the set of actions (see above) required to make the user's URLs +// reflect the prepopulate data. |default_search_provider| is used to avoid +// placing the current default provider on the "to be removed" list. +// +// NOTE: Takes ownership of, and clears, |prepopulated_urls|. +ActionsFromPrepopulateData CreateActionsFromCurrentPrepopulateData( + ScopedVector<TemplateURLData>* prepopulated_urls, + const TemplateURLService::TemplateURLVector& existing_urls, + const TemplateURL* default_search_provider); + +// Processes the results of KeywordWebDataService::GetKeywords, combining it +// with prepopulated search providers to result in: +// * a set of template_urls (search providers). The caller owns the +// TemplateURL* returned in template_urls. +// * whether there is a new resource keyword version (and the value). +// |*new_resource_keyword_version| is set to 0 if no new value. Otherwise, +// it is the new value. +// Only pass in a non-NULL value for service if the KeywordWebDataService should +// be updated. If |removed_keyword_guids| is not NULL, any TemplateURLs removed +// from the keyword table in the KeywordWebDataService will have their Sync +// GUIDs added to it. |default_search_provider| will be used to prevent removing +// the current user-selected DSE, regardless of changes in prepopulate data. +void GetSearchProvidersUsingKeywordResult( + const WDTypedResult& result, + KeywordWebDataService* service, + PrefService* prefs, + TemplateURLService::TemplateURLVector* template_urls, + TemplateURL* default_search_provider, + const SearchTermsData& search_terms_data, + int* new_resource_keyword_version, + std::set<std::string>* removed_keyword_guids); + +// Like GetSearchProvidersUsingKeywordResult(), but allows the caller to pass in +// engines in |template_urls| instead of getting them via processing a web data +// service request. +// |resource_keyword_version| should contain the version number of the current +// keyword data, i.e. the version number of the most recent prepopulate data +// that has been merged into the current keyword data. On exit, this will be +// set as in GetSearchProvidersUsingKeywordResult(). +void GetSearchProvidersUsingLoadedEngines( + KeywordWebDataService* service, + PrefService* prefs, + TemplateURLService::TemplateURLVector* template_urls, + TemplateURL* default_search_provider, + const SearchTermsData& search_terms_data, + int* resource_keyword_version, + std::set<std::string>* removed_keyword_guids); + +// Due to a bug, the |input_encodings| field of TemplateURLData could have +// contained duplicate entries. This removes those entries and returns whether +// any were found. +bool DeDupeEncodings(std::vector<std::string>* encodings); + +// Removes (and deletes) TemplateURLs from |template_urls| and |service| if they +// have duplicate prepopulate ids. If |removed_keyword_guids| is not NULL, the +// Sync GUID of each item removed from the DB will be added to it. This is a +// helper used by GetSearchProvidersUsingKeywordResult(), but is declared here +// so it's accessible by unittests. +void RemoveDuplicatePrepopulateIDs( + KeywordWebDataService* service, + const ScopedVector<TemplateURLData>& prepopulated_urls, + TemplateURL* default_search_provider, + TemplateURLService::TemplateURLVector* template_urls, + const SearchTermsData& search_terms_data, + std::set<std::string>* removed_keyword_guids); + +#endif // COMPONENTS_SEARCH_ENGINES_UTIL_H_ |