OLD | NEW |
1 // Copyright 2017 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "components/history/core/browser/typed_url_sync_bridge.h" | 5 #include "components/history/core/browser/typed_url_sync_bridge.h" |
6 | 6 |
| 7 #include "base/big_endian.h" |
7 #include "base/memory/ptr_util.h" | 8 #include "base/memory/ptr_util.h" |
| 9 #include "base/strings/utf_string_conversions.h" |
| 10 #include "components/history/core/browser/history_backend.h" |
| 11 #include "components/sync/model/mutable_data_batch.h" |
8 #include "components/sync/model_impl/sync_metadata_store_change_list.h" | 12 #include "components/sync/model_impl/sync_metadata_store_change_list.h" |
9 | 13 |
| 14 using syncer::EntityData; |
| 15 using sync_pb::TypedUrlSpecifics; |
| 16 using syncer::MutableDataBatch; |
| 17 |
10 namespace history { | 18 namespace history { |
11 | 19 |
| 20 namespace { |
| 21 |
| 22 // The server backend can't handle arbitrarily large node sizes, so to keep |
| 23 // the size under control we limit the visit array. |
| 24 static const int kMaxTypedUrlVisits = 100; |
| 25 |
| 26 // There's no limit on how many visits the history DB could have for a given |
| 27 // typed URL, so we limit how many we fetch from the DB to avoid crashes due to |
| 28 // running out of memory (http://crbug.com/89793). This value is different |
| 29 // from kMaxTypedUrlVisits, as some of the visits fetched from the DB may be |
| 30 // RELOAD visits, which will be stripped. |
| 31 static const int kMaxVisitsToFetch = 1000; |
| 32 |
| 33 // Enforce oldest to newest visit order. |
| 34 static bool CheckVisitOrdering(const VisitVector& visits) { |
| 35 int64_t previous_visit_time = 0; |
| 36 for (VisitVector::const_iterator visit = visits.begin(); |
| 37 visit != visits.end(); ++visit) { |
| 38 if (visit != visits.begin() && |
| 39 previous_visit_time > visit->visit_time.ToInternalValue()) |
| 40 return false; |
| 41 |
| 42 previous_visit_time = visit->visit_time.ToInternalValue(); |
| 43 } |
| 44 return true; |
| 45 } |
| 46 |
| 47 std::string GetStorageKeyFromURLRow(const URLRow& row) { |
| 48 std::string storage_key(sizeof(row.id()), 0); |
| 49 base::WriteBigEndian<URLID>(&storage_key[0], row.id()); |
| 50 return storage_key; |
| 51 } |
| 52 |
| 53 } // namespace |
| 54 |
12 TypedURLSyncBridge::TypedURLSyncBridge( | 55 TypedURLSyncBridge::TypedURLSyncBridge( |
13 HistoryBackend* history_backend, | 56 HistoryBackend* history_backend, |
14 syncer::SyncMetadataStore* sync_metadata_store, | 57 TypedURLSyncMetadataDatabase* sync_metadata_database, |
15 const ChangeProcessorFactory& change_processor_factory) | 58 const ChangeProcessorFactory& change_processor_factory) |
16 : ModelTypeSyncBridge(change_processor_factory, syncer::TYPED_URLS), | 59 : ModelTypeSyncBridge(change_processor_factory, syncer::TYPED_URLS), |
17 history_backend_(history_backend), | 60 history_backend_(history_backend), |
18 sync_metadata_store_(sync_metadata_store) { | 61 sync_metadata_database_(sync_metadata_database), |
| 62 num_db_accesses_(0), |
| 63 num_db_errors_(0), |
| 64 history_backend_observer_(this) { |
19 DCHECK(history_backend_); | 65 DCHECK(history_backend_); |
20 DCHECK(sequence_checker_.CalledOnValidSequence()); | 66 DCHECK(sequence_checker_.CalledOnValidSequence()); |
21 DCHECK(sync_metadata_store_); | 67 DCHECK(sync_metadata_database_); |
22 NOTIMPLEMENTED(); | |
23 } | 68 } |
24 | 69 |
25 TypedURLSyncBridge::~TypedURLSyncBridge() { | 70 TypedURLSyncBridge::~TypedURLSyncBridge() { |
26 // TODO(gangwu): unregister as HistoryBackendObserver, can use ScopedObserver | 71 // TODO(gangwu): unregister as HistoryBackendObserver, can use ScopedObserver |
27 // to do it. | 72 // to do it. |
28 } | 73 } |
29 | 74 |
30 std::unique_ptr<syncer::MetadataChangeList> | 75 std::unique_ptr<syncer::MetadataChangeList> |
31 TypedURLSyncBridge::CreateMetadataChangeList() { | 76 TypedURLSyncBridge::CreateMetadataChangeList() { |
32 DCHECK(sequence_checker_.CalledOnValidSequence()); | 77 DCHECK(sequence_checker_.CalledOnValidSequence()); |
33 return base::MakeUnique<syncer::SyncMetadataStoreChangeList>( | 78 return base::MakeUnique<syncer::SyncMetadataStoreChangeList>( |
34 sync_metadata_store_, syncer::TYPED_URLS); | 79 sync_metadata_database_, syncer::TYPED_URLS); |
35 } | 80 } |
36 | 81 |
37 base::Optional<syncer::ModelError> TypedURLSyncBridge::MergeSyncData( | 82 base::Optional<syncer::ModelError> TypedURLSyncBridge::MergeSyncData( |
38 std::unique_ptr<syncer::MetadataChangeList> metadata_change_list, | 83 std::unique_ptr<syncer::MetadataChangeList> metadata_change_list, |
39 syncer::EntityDataMap entity_data_map) { | 84 syncer::EntityDataMap entity_data_map) { |
40 DCHECK(sequence_checker_.CalledOnValidSequence()); | 85 DCHECK(sequence_checker_.CalledOnValidSequence()); |
41 NOTIMPLEMENTED(); | 86 NOTIMPLEMENTED(); |
42 return {}; | 87 return {}; |
43 } | 88 } |
44 | 89 |
45 base::Optional<syncer::ModelError> TypedURLSyncBridge::ApplySyncChanges( | 90 base::Optional<syncer::ModelError> TypedURLSyncBridge::ApplySyncChanges( |
46 std::unique_ptr<syncer::MetadataChangeList> metadata_change_list, | 91 std::unique_ptr<syncer::MetadataChangeList> metadata_change_list, |
47 syncer::EntityChangeList entity_changes) { | 92 syncer::EntityChangeList entity_changes) { |
48 DCHECK(sequence_checker_.CalledOnValidSequence()); | 93 DCHECK(sequence_checker_.CalledOnValidSequence()); |
49 NOTIMPLEMENTED(); | 94 NOTIMPLEMENTED(); |
50 return {}; | 95 return {}; |
51 } | 96 } |
52 | 97 |
53 void TypedURLSyncBridge::GetData(StorageKeyList storage_keys, | 98 void TypedURLSyncBridge::GetData(StorageKeyList storage_keys, |
54 DataCallback callback) { | 99 DataCallback callback) { |
55 DCHECK(sequence_checker_.CalledOnValidSequence()); | 100 DCHECK(sequence_checker_.CalledOnValidSequence()); |
56 NOTIMPLEMENTED(); | 101 NOTIMPLEMENTED(); |
57 } | 102 } |
58 | 103 |
59 void TypedURLSyncBridge::GetAllData(DataCallback callback) { | 104 void TypedURLSyncBridge::GetAllData(DataCallback callback) { |
60 DCHECK(sequence_checker_.CalledOnValidSequence()); | 105 DCHECK(sequence_checker_.CalledOnValidSequence()); |
61 NOTIMPLEMENTED(); | 106 |
| 107 history::URLRows typed_urls; |
| 108 ++num_db_accesses_; |
| 109 if (!history_backend_->GetAllTypedURLs(&typed_urls)) { |
| 110 ++num_db_errors_; |
| 111 change_processor()->ReportError(FROM_HERE, |
| 112 "Could not get the typed_url entries."); |
| 113 return; |
| 114 } |
| 115 |
| 116 auto batch = base::MakeUnique<MutableDataBatch>(); |
| 117 for (history::URLRow& url : typed_urls) { |
| 118 VisitVector visits_vector; |
| 119 FixupURLAndGetVisits(&url, &visits_vector); |
| 120 batch->Put(GetStorageKeyFromURLRow(url), |
| 121 CreateEntityData(url, visits_vector)); |
| 122 } |
| 123 callback.Run(std::move(batch)); |
62 } | 124 } |
63 | 125 |
64 // Must be exactly the value of GURL::spec() for backwards comparability with | 126 // Must be exactly the value of GURL::spec() for backwards comparability with |
65 // the previous (Directory + SyncableService) iteration of sync integration. | 127 // the previous (Directory + SyncableService) iteration of sync integration. |
66 // This can be large but it is assumed that this is not held in memory at steady | 128 // This can be large but it is assumed that this is not held in memory at steady |
67 // state. | 129 // state. |
68 std::string TypedURLSyncBridge::GetClientTag( | 130 std::string TypedURLSyncBridge::GetClientTag(const EntityData& entity_data) { |
69 const syncer::EntityData& entity_data) { | |
70 DCHECK(sequence_checker_.CalledOnValidSequence()); | 131 DCHECK(sequence_checker_.CalledOnValidSequence()); |
71 DCHECK(entity_data.specifics.has_typed_url()) | 132 DCHECK(entity_data.specifics.has_typed_url()) |
72 << "EntityData does not have typed urls specifics."; | 133 << "EntityData does not have typed urls specifics."; |
73 | 134 |
74 return entity_data.specifics.typed_url().url(); | 135 return entity_data.specifics.typed_url().url(); |
75 } | 136 } |
76 | 137 |
77 // Prefer to use URLRow::id() to uniquely identify entities when coordinating | 138 // Prefer to use URLRow::id() to uniquely identify entities when coordinating |
78 // with sync because it has a significantly low memory cost than a URL. | 139 // with sync because it has a significantly low memory cost than a URL. |
79 std::string TypedURLSyncBridge::GetStorageKey( | 140 std::string TypedURLSyncBridge::GetStorageKey(const EntityData& entity_data) { |
80 const syncer::EntityData& entity_data) { | |
81 DCHECK(sequence_checker_.CalledOnValidSequence()); | 141 DCHECK(sequence_checker_.CalledOnValidSequence()); |
82 NOTIMPLEMENTED(); | 142 DCHECK(history_backend_); |
83 return std::string(); | 143 DCHECK(entity_data.specifics.has_typed_url()) |
| 144 << "EntityData does not have typed urls specifics."; |
| 145 |
| 146 const TypedUrlSpecifics& typed_url(entity_data.specifics.typed_url()); |
| 147 URLRow existing_url; |
| 148 ++num_db_accesses_; |
| 149 bool is_existing_url = |
| 150 history_backend_->GetURL(GURL(typed_url.url()), &existing_url); |
| 151 |
| 152 if (!is_existing_url) { |
| 153 // The typed url did not save to local history database yet, so return URL |
| 154 // for now. |
| 155 return entity_data.specifics.typed_url().url(); |
| 156 } |
| 157 |
| 158 return GetStorageKeyFromURLRow(existing_url); |
84 } | 159 } |
85 | 160 |
86 void TypedURLSyncBridge::OnURLVisited(history::HistoryBackend* history_backend, | 161 void TypedURLSyncBridge::OnURLVisited(history::HistoryBackend* history_backend, |
87 ui::PageTransition transition, | 162 ui::PageTransition transition, |
88 const history::URLRow& row, | 163 const history::URLRow& row, |
89 const history::RedirectList& redirects, | 164 const history::RedirectList& redirects, |
90 base::Time visit_time) { | 165 base::Time visit_time) { |
91 DCHECK(sequence_checker_.CalledOnValidSequence()); | 166 DCHECK(sequence_checker_.CalledOnValidSequence()); |
92 NOTIMPLEMENTED(); | 167 NOTIMPLEMENTED(); |
93 } | 168 } |
94 | 169 |
95 void TypedURLSyncBridge::OnURLsModified( | 170 void TypedURLSyncBridge::OnURLsModified( |
96 history::HistoryBackend* history_backend, | 171 history::HistoryBackend* history_backend, |
97 const history::URLRows& changed_urls) { | 172 const history::URLRows& changed_urls) { |
98 DCHECK(sequence_checker_.CalledOnValidSequence()); | 173 DCHECK(sequence_checker_.CalledOnValidSequence()); |
99 NOTIMPLEMENTED(); | 174 NOTIMPLEMENTED(); |
100 } | 175 } |
101 | 176 |
102 void TypedURLSyncBridge::OnURLsDeleted(history::HistoryBackend* history_backend, | 177 void TypedURLSyncBridge::OnURLsDeleted(history::HistoryBackend* history_backend, |
103 bool all_history, | 178 bool all_history, |
104 bool expired, | 179 bool expired, |
105 const history::URLRows& deleted_rows, | 180 const history::URLRows& deleted_rows, |
106 const std::set<GURL>& favicon_urls) { | 181 const std::set<GURL>& favicon_urls) { |
107 DCHECK(sequence_checker_.CalledOnValidSequence()); | 182 DCHECK(sequence_checker_.CalledOnValidSequence()); |
108 NOTIMPLEMENTED(); | 183 NOTIMPLEMENTED(); |
109 } | 184 } |
110 | 185 |
| 186 void TypedURLSyncBridge::Init() { |
| 187 DCHECK(sequence_checker_.CalledOnValidSequence()); |
| 188 |
| 189 history_backend_observer_.Add(history_backend_); |
| 190 LoadMetadata(); |
| 191 } |
| 192 |
| 193 int TypedURLSyncBridge::GetErrorPercentage() const { |
| 194 return num_db_accesses_ ? (100 * num_db_errors_ / num_db_accesses_) : 0; |
| 195 } |
| 196 |
| 197 bool TypedURLSyncBridge::WriteToTypedUrlSpecifics( |
| 198 const URLRow& url, |
| 199 const VisitVector& visits, |
| 200 TypedUrlSpecifics* typed_url) { |
| 201 DCHECK(!url.last_visit().is_null()); |
| 202 DCHECK(!visits.empty()); |
| 203 DCHECK_EQ(url.last_visit().ToInternalValue(), |
| 204 visits.back().visit_time.ToInternalValue()); |
| 205 |
| 206 typed_url->set_url(url.url().spec()); |
| 207 typed_url->set_title(base::UTF16ToUTF8(url.title())); |
| 208 typed_url->set_hidden(url.hidden()); |
| 209 |
| 210 DCHECK(CheckVisitOrdering(visits)); |
| 211 |
| 212 bool only_typed = false; |
| 213 int skip_count = 0; |
| 214 |
| 215 if (std::find_if(visits.begin(), visits.end(), |
| 216 [](const history::VisitRow& visit) { |
| 217 return ui::PageTransitionCoreTypeIs( |
| 218 visit.transition, ui::PAGE_TRANSITION_TYPED); |
| 219 }) == visits.end()) { |
| 220 // This URL has no TYPED visits, don't sync it |
| 221 return false; |
| 222 } |
| 223 |
| 224 if (visits.size() > static_cast<size_t>(kMaxTypedUrlVisits)) { |
| 225 int typed_count = 0; |
| 226 int total = 0; |
| 227 // Walk the passed-in visit vector and count the # of typed visits. |
| 228 for (VisitRow visit : visits) { |
| 229 // We ignore reload visits. |
| 230 if (PageTransitionCoreTypeIs(visit.transition, |
| 231 ui::PAGE_TRANSITION_RELOAD)) { |
| 232 continue; |
| 233 } |
| 234 ++total; |
| 235 if (PageTransitionCoreTypeIs(visit.transition, |
| 236 ui::PAGE_TRANSITION_TYPED)) { |
| 237 ++typed_count; |
| 238 } |
| 239 } |
| 240 |
| 241 // We should have at least one typed visit. This can sometimes happen if |
| 242 // the history DB has an inaccurate count for some reason (there's been |
| 243 // bugs in the history code in the past which has left users in the wild |
| 244 // with incorrect counts - http://crbug.com/84258). |
| 245 DCHECK(typed_count > 0); |
| 246 |
| 247 if (typed_count > kMaxTypedUrlVisits) { |
| 248 only_typed = true; |
| 249 skip_count = typed_count - kMaxTypedUrlVisits; |
| 250 } else if (total > kMaxTypedUrlVisits) { |
| 251 skip_count = total - kMaxTypedUrlVisits; |
| 252 } |
| 253 } |
| 254 |
| 255 for (const auto& visit : visits) { |
| 256 // Skip reload visits. |
| 257 if (PageTransitionCoreTypeIs(visit.transition, ui::PAGE_TRANSITION_RELOAD)) |
| 258 continue; |
| 259 |
| 260 // If we only have room for typed visits, then only add typed visits. |
| 261 if (only_typed && !PageTransitionCoreTypeIs(visit.transition, |
| 262 ui::PAGE_TRANSITION_TYPED)) { |
| 263 continue; |
| 264 } |
| 265 |
| 266 if (skip_count > 0) { |
| 267 // We have too many entries to fit, so we need to skip the oldest ones. |
| 268 // Only skip typed URLs if there are too many typed URLs to fit. |
| 269 if (only_typed || !PageTransitionCoreTypeIs(visit.transition, |
| 270 ui::PAGE_TRANSITION_TYPED)) { |
| 271 --skip_count; |
| 272 continue; |
| 273 } |
| 274 } |
| 275 typed_url->add_visits(visit.visit_time.ToInternalValue()); |
| 276 typed_url->add_visit_transitions(visit.transition); |
| 277 } |
| 278 DCHECK_EQ(skip_count, 0); |
| 279 |
| 280 CHECK_GT(typed_url->visits_size(), 0); |
| 281 CHECK_LE(typed_url->visits_size(), kMaxTypedUrlVisits); |
| 282 CHECK_EQ(typed_url->visits_size(), typed_url->visit_transitions_size()); |
| 283 |
| 284 return true; |
| 285 } |
| 286 |
| 287 void TypedURLSyncBridge::LoadMetadata() { |
| 288 if (!history_backend_ || !sync_metadata_database_) { |
| 289 change_processor()->ReportError( |
| 290 FROM_HERE, "Failed to load TypedURLSyncMetadataDatabase."); |
| 291 return; |
| 292 } |
| 293 |
| 294 auto batch = base::MakeUnique<syncer::MetadataBatch>(); |
| 295 if (!sync_metadata_database_->GetAllSyncMetadata(batch.get())) { |
| 296 change_processor()->ReportError( |
| 297 FROM_HERE, |
| 298 "Failed reading typed url metadata from TypedURLSyncMetadataDatabase."); |
| 299 return; |
| 300 } |
| 301 change_processor()->ModelReadyToSync(std::move(batch)); |
| 302 } |
| 303 |
| 304 void TypedURLSyncBridge::ClearErrorStats() { |
| 305 num_db_accesses_ = 0; |
| 306 num_db_errors_ = 0; |
| 307 } |
| 308 |
| 309 bool TypedURLSyncBridge::FixupURLAndGetVisits(URLRow* url, |
| 310 VisitVector* visits) { |
| 311 ++num_db_accesses_; |
| 312 if (!history_backend_->GetMostRecentVisitsForURL(url->id(), kMaxVisitsToFetch, |
| 313 visits)) { |
| 314 ++num_db_errors_; |
| 315 // Couldn't load the visits for this URL due to some kind of DB error. |
| 316 // Don't bother writing this URL to the history DB (if we ignore the |
| 317 // error and continue, we might end up duplicating existing visits). |
| 318 DLOG(ERROR) << "Could not load visits for url: " << url->url(); |
| 319 return false; |
| 320 } |
| 321 |
| 322 // Sometimes (due to a bug elsewhere in the history or sync code, or due to |
| 323 // a crash between adding a URL to the history database and updating the |
| 324 // visit DB) the visit vector for a URL can be empty. If this happens, just |
| 325 // create a new visit whose timestamp is the same as the last_visit time. |
| 326 // This is a workaround for http://crbug.com/84258. |
| 327 if (visits->empty()) { |
| 328 DVLOG(1) << "Found empty visits for URL: " << url->url(); |
| 329 if (url->last_visit().is_null()) { |
| 330 // If modified URL is bookmarked, history backend treats it as modified |
| 331 // even if all its visits are deleted. Return false to stop further |
| 332 // processing because sync expects valid visit time for modified entry. |
| 333 return false; |
| 334 } |
| 335 |
| 336 VisitRow visit(url->id(), url->last_visit(), 0, ui::PAGE_TRANSITION_TYPED, |
| 337 0); |
| 338 visits->push_back(visit); |
| 339 } |
| 340 |
| 341 // GetMostRecentVisitsForURL() returns the data in the opposite order that |
| 342 // we need it, so reverse it. |
| 343 std::reverse(visits->begin(), visits->end()); |
| 344 |
| 345 // Sometimes, the last_visit field in the URL doesn't match the timestamp of |
| 346 // the last visit in our visit array (they come from different tables, so |
| 347 // crashes/bugs can cause them to mismatch), so just set it here. |
| 348 url->set_last_visit(visits->back().visit_time); |
| 349 DCHECK(CheckVisitOrdering(*visits)); |
| 350 |
| 351 // Removes all visits that are older than the current expiration time. Visits |
| 352 // are in ascending order now, so we can check from beginning to check how |
| 353 // many expired visits. |
| 354 size_t num_expired_visits = 0; |
| 355 for (auto& visit : *visits) { |
| 356 base::Time time = visit.visit_time; |
| 357 if (history_backend_->IsExpiredVisitTime(time)) { |
| 358 ++num_expired_visits; |
| 359 } else { |
| 360 break; |
| 361 } |
| 362 } |
| 363 if (num_expired_visits != 0) { |
| 364 if (num_expired_visits == visits->size()) { |
| 365 DVLOG(1) << "All visits are expired for url: " << url->url(); |
| 366 visits->clear(); |
| 367 return false; |
| 368 } |
| 369 visits->erase(visits->begin(), visits->begin() + num_expired_visits); |
| 370 } |
| 371 DCHECK(CheckVisitOrdering(*visits)); |
| 372 |
| 373 return true; |
| 374 } |
| 375 |
| 376 std::unique_ptr<EntityData> TypedURLSyncBridge::CreateEntityData( |
| 377 const URLRow& row, |
| 378 const VisitVector& visits) { |
| 379 auto entity_data = base::MakeUnique<EntityData>(); |
| 380 TypedUrlSpecifics* specifics = entity_data->specifics.mutable_typed_url(); |
| 381 |
| 382 if (!WriteToTypedUrlSpecifics(row, visits, specifics)) { |
| 383 // Cannot write to specifics, ex. no TYPED visits. |
| 384 return base::MakeUnique<EntityData>(); |
| 385 } |
| 386 entity_data->non_unique_name = row.url().spec(); |
| 387 |
| 388 return entity_data; |
| 389 } |
| 390 |
111 } // namespace history | 391 } // namespace history |
OLD | NEW |