Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / sync / glue / session_model_associator.cc
blob910e702b56305d0ac5879158dcf8daea8786681d
1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/sync/glue/session_model_associator.h"
7 #include <algorithm>
8 #include <set>
9 #include <utility>
11 #include "base/bind.h"
12 #include "base/location.h"
13 #include "base/logging.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/threading/sequenced_worker_pool.h"
16 #include "chrome/browser/chrome_notification_types.h"
17 #include "chrome/browser/favicon/favicon_service_factory.h"
18 #include "chrome/browser/history/history_service.h"
19 #if !defined(OS_ANDROID)
20 #include "chrome/browser/network_time/navigation_time_helper.h"
21 #endif
22 #include "chrome/browser/prefs/pref_service_syncable.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/sessions/session_id.h"
25 #include "chrome/browser/sync/glue/device_info.h"
26 #include "chrome/browser/sync/glue/synced_device_tracker.h"
27 #include "chrome/browser/sync/glue/synced_session.h"
28 #include "chrome/browser/sync/glue/synced_tab_delegate.h"
29 #include "chrome/browser/sync/glue/synced_window_delegate.h"
30 #include "chrome/browser/sync/profile_sync_service.h"
31 #include "chrome/common/chrome_switches.h"
32 #include "chrome/common/pref_names.h"
33 #include "chrome/common/url_constants.h"
34 #include "components/sessions/serialized_navigation_entry.h"
35 #include "components/user_prefs/pref_registry_syncable.h"
36 #include "content/public/browser/favicon_status.h"
37 #include "content/public/browser/navigation_entry.h"
38 #include "content/public/browser/notification_details.h"
39 #include "content/public/browser/notification_service.h"
40 #include "content/public/common/url_constants.h"
41 #include "sync/api/sync_error.h"
42 #include "sync/api/time.h"
43 #include "sync/internal_api/public/base/model_type.h"
44 #include "sync/internal_api/public/read_node.h"
45 #include "sync/internal_api/public/read_transaction.h"
46 #include "sync/internal_api/public/write_node.h"
47 #include "sync/internal_api/public/write_transaction.h"
48 #include "sync/protocol/session_specifics.pb.h"
49 #include "sync/syncable/directory.h"
50 #include "sync/syncable/syncable_read_transaction.h"
51 #include "sync/syncable/syncable_write_transaction.h"
52 #if defined(OS_LINUX)
53 #include "base/linux_util.h"
54 #elif defined(OS_WIN)
55 #include <windows.h>
56 #endif
58 using content::BrowserThread;
59 using content::NavigationEntry;
60 using prefs::kSyncSessionsGUID;
61 using sessions::SerializedNavigationEntry;
62 using syncer::SESSIONS;
64 namespace {
66 std::string SessionTagPrefix() {
67 return std::string("session_sync");
70 // Given a transaction, returns the GUID-based string that should be used for
71 // |current_machine_tag_|.
72 std::string GetMachineTagFromTransaction(
73 syncer::WriteTransaction* trans) {
74 syncer::syncable::Directory* dir = trans->GetWrappedWriteTrans()->directory();
75 std::string machine_tag = SessionTagPrefix();
76 machine_tag.append(dir->cache_guid());
77 return machine_tag;
80 // Given a session tag this function returns the client_id(cache_guid).
81 std::string GetClientIdFromSessionTag(const std::string& session_tag) {
82 if (session_tag.find_first_of(SessionTagPrefix()) == std::string::npos) {
83 LOG(ERROR) << "Session tag is malformatted";
84 return std::string();
87 std::string client_id = session_tag.substr(
88 SessionTagPrefix().length(),
89 session_tag.length());
91 return client_id;
94 } // namespace
96 namespace browser_sync {
98 namespace {
99 static const char kNoSessionsFolderError[] =
100 "Server did not create the top-level sessions node. We "
101 "might be running against an out-of-date server.";
103 // The maximum number of navigations in each direction we care to sync.
104 static const int kMaxSyncNavigationCount = 6;
106 // Default number of days without activity after which a session is considered
107 // stale and becomes a candidate for garbage collection.
108 static const size_t kDefaultStaleSessionThresholdDays = 14; // 2 weeks.
110 // Maximum number of favicons to sync.
111 // TODO(zea): pull this from the server.
112 static const int kMaxSyncFavicons = 200;
114 } // namespace
116 SessionModelAssociator::SessionModelAssociator(
117 ProfileSyncService* sync_service,
118 DataTypeErrorHandler* error_handler)
119 : local_tab_pool_(sync_service),
120 local_session_syncid_(syncer::kInvalidId),
121 sync_service_(sync_service),
122 stale_session_threshold_days_(kDefaultStaleSessionThresholdDays),
123 setup_for_test_(false),
124 waiting_for_change_(false),
125 profile_(sync_service->profile()),
126 error_handler_(error_handler),
127 favicon_cache_(profile_,
128 sync_service->current_experiments().favicon_sync_limit),
129 test_weak_factory_(this) {
130 DCHECK(CalledOnValidThread());
131 DCHECK(sync_service_);
132 DCHECK(profile_);
135 SessionModelAssociator::SessionModelAssociator(ProfileSyncService* sync_service,
136 bool setup_for_test)
137 : local_tab_pool_(sync_service),
138 local_session_syncid_(syncer::kInvalidId),
139 sync_service_(sync_service),
140 stale_session_threshold_days_(kDefaultStaleSessionThresholdDays),
141 setup_for_test_(setup_for_test),
142 waiting_for_change_(false),
143 profile_(sync_service->profile()),
144 error_handler_(NULL),
145 favicon_cache_(profile_, kMaxSyncFavicons),
146 test_weak_factory_(this) {
147 DCHECK(CalledOnValidThread());
148 DCHECK(sync_service_);
149 DCHECK(profile_);
150 DCHECK(setup_for_test);
153 SessionModelAssociator::~SessionModelAssociator() {
154 DCHECK(CalledOnValidThread());
157 bool SessionModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) {
158 DCHECK(CalledOnValidThread());
159 CHECK(has_nodes);
160 *has_nodes = false;
161 syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare());
162 syncer::ReadNode root(&trans);
163 if (root.InitByTagLookup(syncer::ModelTypeToRootTag(syncer::SESSIONS)) !=
164 syncer::BaseNode::INIT_OK) {
165 LOG(ERROR) << kNoSessionsFolderError;
166 return false;
168 // The sync model has user created nodes iff the sessions folder has
169 // any children.
170 *has_nodes = root.HasChildren();
171 return true;
174 int64 SessionModelAssociator::GetSyncIdFromSessionTag(const std::string& tag) {
175 DCHECK(CalledOnValidThread());
176 syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare());
177 syncer::ReadNode node(&trans);
178 if (node.InitByClientTagLookup(SESSIONS, tag) != syncer::BaseNode::INIT_OK)
179 return syncer::kInvalidId;
180 return node.GetId();
183 bool SessionModelAssociator::AssociateWindows(bool reload_tabs,
184 syncer::SyncError* error) {
185 DCHECK(CalledOnValidThread());
186 std::string local_tag = GetCurrentMachineTag();
187 sync_pb::SessionSpecifics specifics;
188 specifics.set_session_tag(local_tag);
189 sync_pb::SessionHeader* header_s = specifics.mutable_header();
190 SyncedSession* current_session =
191 synced_session_tracker_.GetSession(local_tag);
192 current_session->modified_time = base::Time::Now();
193 header_s->set_client_name(current_session_name_);
194 header_s->set_device_type(DeviceInfo::GetLocalDeviceType());
196 synced_session_tracker_.ResetSessionTracking(local_tag);
197 std::set<SyncedWindowDelegate*> windows =
198 SyncedWindowDelegate::GetSyncedWindowDelegates();
199 for (std::set<SyncedWindowDelegate*>::const_iterator i =
200 windows.begin(); i != windows.end(); ++i) {
201 // Make sure the window has tabs and a viewable window. The viewable window
202 // check is necessary because, for example, when a browser is closed the
203 // destructor is not necessarily run immediately. This means its possible
204 // for us to get a handle to a browser that is about to be removed. If
205 // the tab count is 0 or the window is NULL, the browser is about to be
206 // deleted, so we ignore it.
207 if (ShouldSyncWindow(*i) && (*i)->GetTabCount() && (*i)->HasWindow()) {
208 sync_pb::SessionWindow window_s;
209 SessionID::id_type window_id = (*i)->GetSessionId();
210 DVLOG(1) << "Associating window " << window_id << " with "
211 << (*i)->GetTabCount() << " tabs.";
212 window_s.set_window_id(window_id);
213 // Note: We don't bother to set selected tab index anymore. We still
214 // consume it when receiving foreign sessions, as reading it is free, but
215 // it triggers too many sync cycles with too little value to make setting
216 // it worthwhile.
217 if ((*i)->IsTypeTabbed()) {
218 window_s.set_browser_type(
219 sync_pb::SessionWindow_BrowserType_TYPE_TABBED);
220 } else {
221 window_s.set_browser_type(
222 sync_pb::SessionWindow_BrowserType_TYPE_POPUP);
225 // Store the order of tabs.
226 bool found_tabs = false;
227 for (int j = 0; j < (*i)->GetTabCount(); ++j) {
228 SessionID::id_type tab_id = (*i)->GetTabIdAt(j);
229 SyncedTabDelegate* synced_tab = (*i)->GetTabAt(j);
231 // GetTabAt can return a null tab; in that case just skip it.
232 if (!synced_tab)
233 continue;
235 if (!synced_tab->HasWebContents()) {
236 // For tabs without WebContents update the |tab_id|, as it could have
237 // changed after a session restore.
238 // Note: We cannot check if a tab is valid if it has no WebContents.
239 // We assume any such tab is valid and leave the contents of
240 // corresponding sync node unchanged.
241 if (synced_tab->GetSyncId() > TabNodePool::kInvalidTabNodeID &&
242 tab_id > TabNodePool::kInvalidTabID) {
243 UpdateTabIdIfNecessary(synced_tab->GetSyncId(), tab_id);
244 found_tabs = true;
245 window_s.add_tab(tab_id);
247 continue;
250 if (reload_tabs) {
251 // It's possible for GetTabAt to return a tab which has no web
252 // contents. We can assume this means the tab already existed but
253 // hasn't changed, so no need to reassociate.
254 if (synced_tab->HasWebContents() &&
255 !AssociateTab(synced_tab, error)) {
256 // Association failed. Either we need to re-associate, or this is an
257 // unrecoverable error.
258 return false;
262 // If the tab is valid, it would have been added to the tracker either
263 // by the above AssociateTab call (at association time), or by the
264 // change processor calling AssociateTab for all modified tabs.
265 // Therefore, we can key whether this window has valid tabs based on
266 // the tab's presence in the tracker.
267 const SessionTab* tab = NULL;
268 if (synced_session_tracker_.LookupSessionTab(local_tag, tab_id, &tab)) {
269 found_tabs = true;
270 window_s.add_tab(tab_id);
273 // Only add a window if it contains valid tabs.
274 if (found_tabs) {
275 sync_pb::SessionWindow* header_window = header_s->add_window();
276 *header_window = window_s;
278 // Update this window's representation in the synced session tracker.
279 synced_session_tracker_.PutWindowInSession(local_tag, window_id);
280 PopulateSessionWindowFromSpecifics(
281 local_tag,
282 window_s,
283 base::Time::Now(),
284 current_session->windows[window_id],
285 &synced_session_tracker_);
290 local_tab_pool_.DeleteUnassociatedTabNodes();
291 // Free memory for closed windows and tabs.
292 synced_session_tracker_.CleanupSession(local_tag);
294 syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
295 syncer::WriteNode header_node(&trans);
296 if (header_node.InitByIdLookup(local_session_syncid_) !=
297 syncer::BaseNode::INIT_OK) {
298 if (error) {
299 *error = error_handler_->CreateAndUploadError(
300 FROM_HERE,
301 "Failed to load local session header node.",
302 model_type());
304 return false;
306 header_node.SetSessionSpecifics(specifics);
307 if (waiting_for_change_) QuitLoopForSubtleTesting();
308 return true;
311 // Static.
312 bool SessionModelAssociator::ShouldSyncWindow(
313 const SyncedWindowDelegate* window) {
314 if (window->IsApp())
315 return false;
316 return window->IsTypeTabbed() || window->IsTypePopup();
319 bool SessionModelAssociator::AssociateTabs(
320 const std::vector<SyncedTabDelegate*>& tabs,
321 syncer::SyncError* error) {
322 DCHECK(CalledOnValidThread());
323 for (std::vector<SyncedTabDelegate*>::const_iterator i = tabs.begin();
324 i != tabs.end();
325 ++i) {
326 if (!AssociateTab(*i, error))
327 return false;
329 if (waiting_for_change_) QuitLoopForSubtleTesting();
330 return true;
333 bool SessionModelAssociator::AssociateTab(SyncedTabDelegate* const tab,
334 syncer::SyncError* error) {
335 DCHECK(CalledOnValidThread());
336 DCHECK(tab->HasWebContents());
337 int tab_node_id(TabNodePool::kInvalidTabNodeID);
338 SessionID::id_type tab_id = tab->GetSessionId();
339 if (tab->IsBeingDestroyed()) {
340 // This tab is closing.
341 TabLinksMap::iterator tab_iter = local_tab_map_.find(tab_id);
342 if (tab_iter == local_tab_map_.end()) {
343 // We aren't tracking this tab (for example, sync setting page).
344 return true;
346 local_tab_pool_.FreeTabNode(tab_iter->second->tab_node_id());
347 local_tab_map_.erase(tab_iter);
348 return true;
351 if (!ShouldSyncTab(*tab))
352 return true;
354 TabLinksMap::iterator local_tab_map_iter = local_tab_map_.find(tab_id);
355 TabLink* tab_link = NULL;
356 if (local_tab_map_iter == local_tab_map_.end()) {
357 tab_node_id = tab->GetSyncId();
358 // if there is an old sync node for the tab, reuse it.
359 if (!local_tab_pool_.IsUnassociatedTabNode(tab_node_id)) {
360 // This is a new tab, get a sync node for it.
361 tab_node_id = local_tab_pool_.GetFreeTabNode();
362 if (tab_node_id == TabNodePool::kInvalidTabNodeID) {
363 if (error) {
364 *error = error_handler_->CreateAndUploadError(
365 FROM_HERE,
366 "Received invalid tab node from tab pool.",
367 model_type());
369 return false;
371 tab->SetSyncId(tab_node_id);
373 local_tab_pool_.AssociateTabNode(tab_node_id, tab_id);
374 tab_link = new TabLink(tab_node_id, tab);
375 local_tab_map_[tab_id] = make_linked_ptr<TabLink>(tab_link);
376 } else {
377 // This tab is already associated with a sync node, reuse it.
378 // Note: on some platforms the tab object may have changed, so we ensure
379 // the tab link is up to date.
380 tab_link = local_tab_map_iter->second.get();
381 local_tab_map_iter->second->set_tab(tab);
383 DCHECK(tab_link);
384 DCHECK_NE(tab_link->tab_node_id(), TabNodePool::kInvalidTabNodeID);
386 DVLOG(1) << "Reloading tab " << tab_id << " from window "
387 << tab->GetWindowId();
388 return WriteTabContentsToSyncModel(tab_link, error);
391 // static
392 GURL SessionModelAssociator::GetCurrentVirtualURL(
393 const SyncedTabDelegate& tab_delegate) {
394 const int current_index = tab_delegate.GetCurrentEntryIndex();
395 const int pending_index = tab_delegate.GetPendingEntryIndex();
396 const NavigationEntry* current_entry =
397 (current_index == pending_index) ?
398 tab_delegate.GetPendingEntry() :
399 tab_delegate.GetEntryAtIndex(current_index);
400 return current_entry->GetVirtualURL();
403 // static
404 GURL SessionModelAssociator::GetCurrentFaviconURL(
405 const SyncedTabDelegate& tab_delegate) {
406 const int current_index = tab_delegate.GetCurrentEntryIndex();
407 const int pending_index = tab_delegate.GetPendingEntryIndex();
408 const NavigationEntry* current_entry =
409 (current_index == pending_index) ?
410 tab_delegate.GetPendingEntry() :
411 tab_delegate.GetEntryAtIndex(current_index);
412 return (current_entry->GetFavicon().valid ?
413 current_entry->GetFavicon().url :
414 GURL());
417 bool SessionModelAssociator::WriteTabContentsToSyncModel(
418 TabLink* tab_link,
419 syncer::SyncError* error) {
420 DCHECK(CalledOnValidThread());
421 const SyncedTabDelegate& tab_delegate = *(tab_link->tab());
422 int tab_node_id = tab_link->tab_node_id();
423 GURL old_tab_url = tab_link->url();
424 const GURL new_url = GetCurrentVirtualURL(tab_delegate);
425 DVLOG(1) << "Local tab " << tab_delegate.GetSessionId()
426 << " now has URL " << new_url.spec();
428 SessionTab* session_tab = NULL;
430 syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
431 syncer::WriteNode tab_node(&trans);
432 if (tab_node.InitByClientTagLookup(
433 syncer::SESSIONS,
434 TabNodePool::TabIdToTag(current_machine_tag_, tab_node_id)) !=
435 syncer::BaseNode::INIT_OK) {
436 if (error) {
437 *error = error_handler_->CreateAndUploadError(
438 FROM_HERE,
439 "Failed to look up local tab node",
440 model_type());
442 return false;
445 // Load the last stored version of this tab so we can compare changes. If
446 // this is a new tab, session_tab will be a new, blank SessionTab object.
447 sync_pb::SessionSpecifics specifics = tab_node.GetSessionSpecifics();
448 const int s_tab_node_id(specifics.tab_node_id());
449 DCHECK_EQ(tab_node_id, s_tab_node_id);
450 session_tab =
451 synced_session_tracker_.GetTab(GetCurrentMachineTag(),
452 tab_delegate.GetSessionId(),
453 specifics.tab_node_id());
454 SetSessionTabFromDelegate(tab_delegate, base::Time::Now(), session_tab);
455 sync_pb::SessionTab tab_s = session_tab->ToSyncData();
457 if (new_url == old_tab_url) {
458 // Load the old specifics and copy over the favicon data if needed.
459 // TODO(zea): remove this once favicon sync is enabled as a separate type.
460 tab_s.set_favicon(specifics.tab().favicon());
461 tab_s.set_favicon_source(specifics.tab().favicon_source());
462 tab_s.set_favicon_type(specifics.tab().favicon_type());
464 // Retain the base SessionSpecifics data (tag, tab_node_id, etc.), and just
465 // write the new SessionTabSpecifics.
466 specifics.mutable_tab()->CopyFrom(tab_s);
468 // Write into the actual sync model.
469 tab_node.SetSessionSpecifics(specifics);
472 // Trigger the favicon load if needed. We do this outside the write
473 // transaction to avoid jank.
474 tab_link->set_url(new_url);
475 if (new_url != old_tab_url) {
476 favicon_cache_.OnFaviconVisited(new_url,
477 GetCurrentFaviconURL(tab_delegate));
480 // Update our last modified time.
481 synced_session_tracker_.GetSession(GetCurrentMachineTag())->modified_time =
482 base::Time::Now();
484 return true;
487 // static
488 void SessionModelAssociator::SetSessionTabFromDelegate(
489 const SyncedTabDelegate& tab_delegate,
490 base::Time mtime,
491 SessionTab* session_tab) {
492 DCHECK(session_tab);
493 session_tab->window_id.set_id(tab_delegate.GetWindowId());
494 session_tab->tab_id.set_id(tab_delegate.GetSessionId());
495 session_tab->tab_visual_index = 0;
496 session_tab->current_navigation_index = tab_delegate.GetCurrentEntryIndex();
497 session_tab->pinned = tab_delegate.IsPinned();
498 session_tab->extension_app_id = tab_delegate.GetExtensionAppId();
499 session_tab->user_agent_override.clear();
500 session_tab->timestamp = mtime;
501 const int current_index = tab_delegate.GetCurrentEntryIndex();
502 const int pending_index = tab_delegate.GetPendingEntryIndex();
503 const int min_index = std::max(0, current_index - kMaxSyncNavigationCount);
504 const int max_index = std::min(current_index + kMaxSyncNavigationCount,
505 tab_delegate.GetEntryCount());
506 bool is_managed = tab_delegate.ProfileIsManaged();
507 session_tab->navigations.clear();
509 #if !defined(OS_ANDROID)
510 // For getting navigation time in network time.
511 NavigationTimeHelper* nav_time_helper =
512 tab_delegate.HasWebContents() ?
513 NavigationTimeHelper::FromWebContents(tab_delegate.GetWebContents()) :
514 NULL;
515 #endif
517 for (int i = min_index; i < max_index; ++i) {
518 const NavigationEntry* entry = (i == pending_index) ?
519 tab_delegate.GetPendingEntry() : tab_delegate.GetEntryAtIndex(i);
520 DCHECK(entry);
521 if (!entry->GetVirtualURL().is_valid())
522 continue;
524 scoped_ptr<content::NavigationEntry> network_time_entry(
525 content::NavigationEntry::Create(*entry));
526 #if !defined(OS_ANDROID)
527 if (nav_time_helper) {
528 network_time_entry->SetTimestamp(
529 nav_time_helper->GetNavigationTime(entry));
531 #endif
533 session_tab->navigations.push_back(
534 SerializedNavigationEntry::FromNavigationEntry(i, *network_time_entry));
535 if (is_managed) {
536 session_tab->navigations.back().set_blocked_state(
537 SerializedNavigationEntry::STATE_ALLOWED);
541 if (is_managed) {
542 const std::vector<const NavigationEntry*>& blocked_navigations =
543 *tab_delegate.GetBlockedNavigations();
544 int offset = session_tab->navigations.size();
545 for (size_t i = 0; i < blocked_navigations.size(); ++i) {
546 session_tab->navigations.push_back(
547 SerializedNavigationEntry::FromNavigationEntry(
548 i + offset, *blocked_navigations[i]));
549 // Blocked navigations already use network navigation time.
550 session_tab->navigations.back().set_blocked_state(
551 SerializedNavigationEntry::STATE_BLOCKED);
552 // TODO(bauerb): Add categories
555 session_tab->session_storage_persistent_id.clear();
558 void SessionModelAssociator::FaviconsUpdated(
559 const std::set<GURL>& urls) {
560 // TODO(zea): consider a separate container for tabs with outstanding favicon
561 // loads so we don't have to iterate through all tabs comparing urls.
562 for (std::set<GURL>::const_iterator i = urls.begin(); i != urls.end(); ++i) {
563 for (TabLinksMap::iterator tab_iter = local_tab_map_.begin();
564 tab_iter != local_tab_map_.end();
565 ++tab_iter) {
566 if (tab_iter->second->url() == *i)
567 favicon_cache_.OnPageFaviconUpdated(*i);
572 syncer::SyncError SessionModelAssociator::AssociateModels(
573 syncer::SyncMergeResult* local_merge_result,
574 syncer::SyncMergeResult* syncer_merge_result) {
575 DCHECK(CalledOnValidThread());
576 syncer::SyncError error;
578 // Ensure that we disassociated properly, otherwise memory might leak.
579 DCHECK(synced_session_tracker_.Empty());
580 DCHECK_EQ(0U, local_tab_pool_.Capacity());
582 local_session_syncid_ = syncer::kInvalidId;
584 scoped_ptr<DeviceInfo> local_device_info(sync_service_->GetLocalDeviceInfo());
586 #if defined(OS_ANDROID)
587 std::string transaction_tag;
588 #endif
589 // Read any available foreign sessions and load any session data we may have.
590 // If we don't have any local session data in the db, create a header node.
592 syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
594 syncer::ReadNode root(&trans);
595 if (root.InitByTagLookup(syncer::ModelTypeToRootTag(model_type())) !=
596 syncer::BaseNode::INIT_OK) {
597 return error_handler_->CreateAndUploadError(
598 FROM_HERE,
599 kNoSessionsFolderError,
600 model_type());
603 // Make sure we have a machine tag.
604 if (current_machine_tag_.empty())
605 InitializeCurrentMachineTag(&trans);
606 if (local_device_info) {
607 current_session_name_ = local_device_info->client_name();
608 } else {
609 return error_handler_->CreateAndUploadError(
610 FROM_HERE,
611 "Failed to get device info.",
612 model_type());
614 synced_session_tracker_.SetLocalSessionTag(current_machine_tag_);
615 if (!UpdateAssociationsFromSyncModel(root, &trans, &error)) {
616 DCHECK(error.IsSet());
617 return error;
620 if (local_session_syncid_ == syncer::kInvalidId) {
621 // The sync db didn't have a header node for us, we need to create one.
622 syncer::WriteNode write_node(&trans);
623 syncer::WriteNode::InitUniqueByCreationResult result =
624 write_node.InitUniqueByCreation(SESSIONS, root, current_machine_tag_);
625 if (result != syncer::WriteNode::INIT_SUCCESS) {
626 // If we can't look it up, and we can't create it, chances are there's
627 // a pre-existing node that has encryption issues. But, since we can't
628 // load the item, we can't remove it, and error out at this point.
629 return error_handler_->CreateAndUploadError(
630 FROM_HERE,
631 "Failed to create sessions header sync node.",
632 model_type());
635 // Write the initial values to the specifics so that in case of a crash or
636 // error we don't persist a half-written node.
637 write_node.SetTitle(base::UTF8ToWide(current_machine_tag_));
638 sync_pb::SessionSpecifics base_specifics;
639 base_specifics.set_session_tag(current_machine_tag_);
640 sync_pb::SessionHeader* header_s = base_specifics.mutable_header();
641 header_s->set_client_name(current_session_name_);
642 header_s->set_device_type(DeviceInfo::GetLocalDeviceType());
643 write_node.SetSessionSpecifics(base_specifics);
645 local_session_syncid_ = write_node.GetId();
647 #if defined(OS_ANDROID)
648 transaction_tag = GetMachineTagFromTransaction(&trans);
649 #endif
651 #if defined(OS_ANDROID)
652 // We need to delete foreign sessions after giving up our
653 // syncer::WriteTransaction, since DeleteForeignSession(std::string&) uses
654 // its own syncer::WriteTransaction.
655 if (current_machine_tag_.compare(transaction_tag) != 0)
656 DeleteForeignSession(transaction_tag);
657 #endif
659 // Check if anything has changed on the client side.
660 if (!UpdateSyncModelDataFromClient(&error)) {
661 DCHECK(error.IsSet());
662 return error;
665 DVLOG(1) << "Session models associated.";
666 DCHECK(!error.IsSet());
667 return error;
670 syncer::SyncError SessionModelAssociator::DisassociateModels() {
671 DCHECK(CalledOnValidThread());
672 DVLOG(1) << "Disassociating local session " << GetCurrentMachineTag();
673 synced_session_tracker_.Clear();
674 local_tab_map_.clear();
675 local_tab_pool_.Clear();
676 local_session_syncid_ = syncer::kInvalidId;
677 current_machine_tag_ = "";
678 current_session_name_ = "";
680 // There is no local model stored with which to disassociate, just notify
681 // foreign session handlers.
682 content::NotificationService::current()->Notify(
683 chrome::NOTIFICATION_FOREIGN_SESSION_DISABLED,
684 content::Source<Profile>(sync_service_->profile()),
685 content::NotificationService::NoDetails());
686 return syncer::SyncError();
689 void SessionModelAssociator::InitializeCurrentMachineTag(
690 syncer::WriteTransaction* trans) {
691 DCHECK(CalledOnValidThread());
692 DCHECK(current_machine_tag_.empty());
693 std::string persisted_guid;
694 browser_sync::SyncPrefs prefs(profile_->GetPrefs());
695 persisted_guid = prefs.GetSyncSessionsGUID();
696 if (!persisted_guid.empty()) {
697 current_machine_tag_ = persisted_guid;
698 DVLOG(1) << "Restoring persisted session sync guid: "
699 << persisted_guid;
700 } else {
701 current_machine_tag_ = GetMachineTagFromTransaction(trans);
702 DVLOG(1) << "Creating session sync guid: " << current_machine_tag_;
703 prefs.SetSyncSessionsGUID(current_machine_tag_);
706 local_tab_pool_.SetMachineTag(current_machine_tag_);
709 bool SessionModelAssociator::GetSyncedFaviconForPageURL(
710 const std::string& page_url,
711 scoped_refptr<base::RefCountedMemory>* favicon_png) const {
712 return favicon_cache_.GetSyncedFaviconForPageURL(GURL(page_url), favicon_png);
715 scoped_ptr<browser_sync::DeviceInfo>
716 SessionModelAssociator::GetDeviceInfoForSessionTag(
717 const std::string& session_tag) {
718 std::string client_id = GetClientIdFromSessionTag(session_tag);
719 return sync_service_->GetDeviceInfo(client_id);
722 bool SessionModelAssociator::UpdateAssociationsFromSyncModel(
723 const syncer::ReadNode& root,
724 syncer::WriteTransaction* trans,
725 syncer::SyncError* error) {
726 DCHECK(CalledOnValidThread());
727 DCHECK(local_tab_pool_.Empty());
728 DCHECK_EQ(local_session_syncid_, syncer::kInvalidId);
730 // Iterate through the nodes and associate any foreign sessions.
731 int64 id = root.GetFirstChildId();
732 while (id != syncer::kInvalidId) {
733 syncer::WriteNode sync_node(trans);
734 if (sync_node.InitByIdLookup(id) != syncer::BaseNode::INIT_OK) {
735 if (error) {
736 *error = error_handler_->CreateAndUploadError(
737 FROM_HERE,
738 "Failed to load sync node",
739 model_type());
741 return false;
743 int64 next_id = sync_node.GetSuccessorId();
745 const sync_pb::SessionSpecifics& specifics =
746 sync_node.GetSessionSpecifics();
747 const base::Time& modification_time = sync_node.GetModificationTime();
748 if (specifics.session_tag().empty() ||
749 (specifics.has_tab() && (!specifics.has_tab_node_id() ||
750 !specifics.tab().has_tab_id()))) {
751 // This is a corrupted node. Just delete it.
752 LOG(WARNING) << "Found invalid session node, deleting.";
753 sync_node.Tombstone();
754 } else if (specifics.session_tag() != GetCurrentMachineTag()) {
755 AssociateForeignSpecifics(specifics, modification_time);
756 } else {
757 // This is previously stored local session information.
758 if (specifics.has_header() &&
759 local_session_syncid_ == syncer::kInvalidId) {
760 // This is our previous header node, reuse it.
761 local_session_syncid_ = id;
762 if (specifics.header().has_client_name()) {
763 current_session_name_ = specifics.header().client_name();
765 } else {
766 if (specifics.has_header() || !specifics.has_tab()) {
767 LOG(WARNING) << "Found invalid session node, deleting.";
768 sync_node.Tombstone();
769 } else {
770 // This is a valid old tab node, add it to the pool so it can be
771 // reused for reassociation.
772 local_tab_pool_.AddTabNode(specifics.tab_node_id());
776 id = next_id;
779 return true;
782 void SessionModelAssociator::AssociateForeignSpecifics(
783 const sync_pb::SessionSpecifics& specifics,
784 const base::Time& modification_time) {
785 DCHECK(CalledOnValidThread());
786 std::string foreign_session_tag = specifics.session_tag();
787 if (foreign_session_tag == GetCurrentMachineTag() && !setup_for_test_)
788 return;
790 SyncedSession* foreign_session =
791 synced_session_tracker_.GetSession(foreign_session_tag);
792 if (specifics.has_header()) {
793 // Read in the header data for this foreign session.
794 // Header data contains window information and ordered tab id's for each
795 // window.
797 // Load (or create) the SyncedSession object for this client.
798 const sync_pb::SessionHeader& header = specifics.header();
799 PopulateSessionHeaderFromSpecifics(header,
800 modification_time,
801 foreign_session);
803 // Reset the tab/window tracking for this session (must do this before
804 // we start calling PutWindowInSession and PutTabInWindow so that all
805 // unused tabs/windows get cleared by the CleanupSession(...) call).
806 synced_session_tracker_.ResetSessionTracking(foreign_session_tag);
808 // Process all the windows and their tab information.
809 int num_windows = header.window_size();
810 DVLOG(1) << "Associating " << foreign_session_tag << " with "
811 << num_windows << " windows.";
812 for (int i = 0; i < num_windows; ++i) {
813 const sync_pb::SessionWindow& window_s = header.window(i);
814 SessionID::id_type window_id = window_s.window_id();
815 synced_session_tracker_.PutWindowInSession(foreign_session_tag,
816 window_id);
817 PopulateSessionWindowFromSpecifics(foreign_session_tag,
818 window_s,
819 modification_time,
820 foreign_session->windows[window_id],
821 &synced_session_tracker_);
824 // Delete any closed windows and unused tabs as necessary.
825 synced_session_tracker_.CleanupSession(foreign_session_tag);
826 } else if (specifics.has_tab()) {
827 const sync_pb::SessionTab& tab_s = specifics.tab();
828 SessionID::id_type tab_id = tab_s.tab_id();
829 SessionTab* tab =
830 synced_session_tracker_.GetTab(foreign_session_tag,
831 tab_id,
832 specifics.tab_node_id());
834 // Update SessionTab based on protobuf.
835 tab->SetFromSyncData(tab_s, modification_time);
837 // If a favicon or favicon urls are present, load them into the in-memory
838 // favicon cache.
839 LoadForeignTabFavicon(tab_s);
841 // Update the last modified time.
842 if (foreign_session->modified_time < modification_time)
843 foreign_session->modified_time = modification_time;
844 } else {
845 LOG(WARNING) << "Ignoring foreign session node with missing header/tab "
846 << "fields and tag " << foreign_session_tag << ".";
850 bool SessionModelAssociator::DisassociateForeignSession(
851 const std::string& foreign_session_tag) {
852 DCHECK(CalledOnValidThread());
853 if (foreign_session_tag == GetCurrentMachineTag()) {
854 DVLOG(1) << "Local session deleted! Doing nothing until a navigation is "
855 << "triggered.";
856 return false;
858 DVLOG(1) << "Disassociating session " << foreign_session_tag;
859 return synced_session_tracker_.DeleteSession(foreign_session_tag);
862 // Static
863 void SessionModelAssociator::PopulateSessionHeaderFromSpecifics(
864 const sync_pb::SessionHeader& header_specifics,
865 base::Time mtime,
866 SyncedSession* session_header) {
867 if (header_specifics.has_client_name()) {
868 session_header->session_name = header_specifics.client_name();
870 if (header_specifics.has_device_type()) {
871 switch (header_specifics.device_type()) {
872 case sync_pb::SyncEnums_DeviceType_TYPE_WIN:
873 session_header->device_type = SyncedSession::TYPE_WIN;
874 break;
875 case sync_pb::SyncEnums_DeviceType_TYPE_MAC:
876 session_header->device_type = SyncedSession::TYPE_MACOSX;
877 break;
878 case sync_pb::SyncEnums_DeviceType_TYPE_LINUX:
879 session_header->device_type = SyncedSession::TYPE_LINUX;
880 break;
881 case sync_pb::SyncEnums_DeviceType_TYPE_CROS:
882 session_header->device_type = SyncedSession::TYPE_CHROMEOS;
883 break;
884 case sync_pb::SyncEnums_DeviceType_TYPE_PHONE:
885 session_header->device_type = SyncedSession::TYPE_PHONE;
886 break;
887 case sync_pb::SyncEnums_DeviceType_TYPE_TABLET:
888 session_header->device_type = SyncedSession::TYPE_TABLET;
889 break;
890 case sync_pb::SyncEnums_DeviceType_TYPE_OTHER:
891 // Intentionally fall-through
892 default:
893 session_header->device_type = SyncedSession::TYPE_OTHER;
894 break;
897 session_header->modified_time = mtime;
900 // Static
901 void SessionModelAssociator::PopulateSessionWindowFromSpecifics(
902 const std::string& session_tag,
903 const sync_pb::SessionWindow& specifics,
904 base::Time mtime,
905 SessionWindow* session_window,
906 SyncedSessionTracker* tracker) {
907 if (specifics.has_window_id())
908 session_window->window_id.set_id(specifics.window_id());
909 if (specifics.has_selected_tab_index())
910 session_window->selected_tab_index = specifics.selected_tab_index();
911 if (specifics.has_browser_type()) {
912 if (specifics.browser_type() ==
913 sync_pb::SessionWindow_BrowserType_TYPE_TABBED) {
914 session_window->type = 1;
915 } else {
916 session_window->type = 2;
919 session_window->timestamp = mtime;
920 session_window->tabs.resize(specifics.tab_size(), NULL);
921 for (int i = 0; i < specifics.tab_size(); i++) {
922 SessionID::id_type tab_id = specifics.tab(i);
923 tracker->PutTabInWindow(session_tag,
924 session_window->window_id.id(),
925 tab_id,
930 void SessionModelAssociator::LoadForeignTabFavicon(
931 const sync_pb::SessionTab& tab) {
932 // First go through and iterate over all the navigations, checking if any
933 // have valid favicon urls.
934 for (int i = 0; i < tab.navigation_size(); ++i) {
935 if (!tab.navigation(i).favicon_url().empty()) {
936 const std::string& page_url = tab.navigation(i).virtual_url();
937 const std::string& favicon_url = tab.navigation(i).favicon_url();
938 favicon_cache_.OnReceivedSyncFavicon(GURL(page_url),
939 GURL(favicon_url),
940 std::string(),
941 syncer::TimeToProtoTime(
942 base::Time::Now()));
946 // Then go through and check for any legacy favicon data.
947 if (!tab.has_favicon() || tab.favicon().empty())
948 return;
949 if (!tab.has_favicon_type() ||
950 tab.favicon_type() != sync_pb::SessionTab::TYPE_WEB_FAVICON) {
951 DVLOG(1) << "Ignoring non-web favicon.";
952 return;
954 if (tab.navigation_size() == 0)
955 return;
956 int selected_index = tab.current_navigation_index();
957 selected_index = std::max(
959 std::min(selected_index,
960 static_cast<int>(tab.navigation_size() - 1)));
961 GURL navigation_url(tab.navigation(selected_index).virtual_url());
962 if (!navigation_url.is_valid())
963 return;
964 GURL favicon_source(tab.favicon_source());
965 if (!favicon_source.is_valid())
966 return;
968 const std::string& favicon = tab.favicon();
969 DVLOG(1) << "Storing synced favicon for url " << navigation_url.spec()
970 << " with size " << favicon.size() << " bytes.";
971 favicon_cache_.OnReceivedSyncFavicon(navigation_url,
972 favicon_source,
973 favicon,
974 syncer::TimeToProtoTime(
975 base::Time::Now()));
978 bool SessionModelAssociator::UpdateSyncModelDataFromClient(
979 syncer::SyncError* error) {
980 DCHECK(CalledOnValidThread());
982 // Associate all open windows and their tabs.
983 return AssociateWindows(true, error);
986 void SessionModelAssociator::AttemptSessionsDataRefresh() const {
987 DVLOG(1) << "Triggering sync refresh for sessions datatype.";
988 const syncer::ModelTypeSet types(syncer::SESSIONS);
989 content::NotificationService::current()->Notify(
990 chrome::NOTIFICATION_SYNC_REFRESH_LOCAL,
991 content::Source<Profile>(profile_),
992 content::Details<const syncer::ModelTypeSet>(&types));
995 bool SessionModelAssociator::GetLocalSession(
996 const SyncedSession* * local_session) {
997 DCHECK(CalledOnValidThread());
998 if (current_machine_tag_.empty())
999 return false;
1000 *local_session = synced_session_tracker_.GetSession(GetCurrentMachineTag());
1001 return true;
1004 bool SessionModelAssociator::GetAllForeignSessions(
1005 std::vector<const SyncedSession*>* sessions) {
1006 DCHECK(CalledOnValidThread());
1007 return synced_session_tracker_.LookupAllForeignSessions(sessions);
1010 bool SessionModelAssociator::GetForeignSession(
1011 const std::string& tag,
1012 std::vector<const SessionWindow*>* windows) {
1013 DCHECK(CalledOnValidThread());
1014 return synced_session_tracker_.LookupSessionWindows(tag, windows);
1017 bool SessionModelAssociator::GetForeignTab(
1018 const std::string& tag,
1019 const SessionID::id_type tab_id,
1020 const SessionTab** tab) {
1021 DCHECK(CalledOnValidThread());
1022 const SessionTab* synced_tab = NULL;
1023 bool success = synced_session_tracker_.LookupSessionTab(tag,
1024 tab_id,
1025 &synced_tab);
1026 if (success)
1027 *tab = synced_tab;
1028 return success;
1031 void SessionModelAssociator::DeleteStaleSessions() {
1032 DCHECK(CalledOnValidThread());
1033 std::vector<const SyncedSession*> sessions;
1034 if (!GetAllForeignSessions(&sessions))
1035 return; // No foreign sessions.
1037 // Iterate through all the sessions and delete any with age older than
1038 // |stale_session_threshold_days_|.
1039 for (std::vector<const SyncedSession*>::const_iterator iter =
1040 sessions.begin(); iter != sessions.end(); ++iter) {
1041 const SyncedSession* session = *iter;
1042 int session_age_in_days =
1043 (base::Time::Now() - session->modified_time).InDays();
1044 std::string session_tag = session->session_tag;
1045 if (session_age_in_days > 0 && // If false, local clock is not trustworty.
1046 static_cast<size_t>(session_age_in_days) >
1047 stale_session_threshold_days_) {
1048 DVLOG(1) << "Found stale session " << session_tag
1049 << " with age " << session_age_in_days << ", deleting.";
1050 DeleteForeignSession(session_tag);
1055 void SessionModelAssociator::SetStaleSessionThreshold(
1056 size_t stale_session_threshold_days) {
1057 DCHECK(CalledOnValidThread());
1058 if (stale_session_threshold_days_ == 0) {
1059 NOTREACHED() << "Attempted to set invalid stale session threshold.";
1060 return;
1062 stale_session_threshold_days_ = stale_session_threshold_days;
1063 // TODO(zea): maybe make this preference-based? Might be nice to let users be
1064 // able to modify this once and forget about it. At the moment, if we want a
1065 // different threshold we will need to call this everytime we create a new
1066 // model associator and before we AssociateModels (probably from DTC).
1069 void SessionModelAssociator::DeleteForeignSession(const std::string& tag) {
1070 DCHECK(CalledOnValidThread());
1071 if (tag == GetCurrentMachineTag()) {
1072 LOG(ERROR) << "Attempting to delete local session. This is not currently "
1073 << "supported.";
1074 return;
1077 if (!DisassociateForeignSession(tag)) {
1078 // We don't have any data for this session, our work here is done!
1079 return;
1082 syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
1083 syncer::ReadNode root(&trans);
1084 if (root.InitByTagLookup(syncer::ModelTypeToRootTag(syncer::SESSIONS)) !=
1085 syncer::BaseNode::INIT_OK) {
1086 LOG(ERROR) << kNoSessionsFolderError;
1087 return;
1089 int64 id = root.GetFirstChildId();
1090 while (id != syncer::kInvalidId) {
1091 syncer::WriteNode sync_node(&trans);
1092 if (sync_node.InitByIdLookup(id) != syncer::BaseNode::INIT_OK) {
1093 LOG(ERROR) << "Failed to fetch sync node for id " << id;
1094 continue;
1096 id = sync_node.GetSuccessorId();
1097 const sync_pb::SessionSpecifics& specifics =
1098 sync_node.GetSessionSpecifics();
1099 if (specifics.session_tag() == tag)
1100 sync_node.Tombstone();
1103 content::NotificationService::current()->Notify(
1104 chrome::NOTIFICATION_FOREIGN_SESSION_UPDATED,
1105 content::Source<Profile>(sync_service_->profile()),
1106 content::NotificationService::NoDetails());
1109 bool SessionModelAssociator::IsValidTab(const SyncedTabDelegate& tab) const {
1110 if ((!sync_service_ || tab.profile() != sync_service_->profile()) &&
1111 !setup_for_test_) {
1112 return false;
1114 const SyncedWindowDelegate* window =
1115 SyncedWindowDelegate::FindSyncedWindowDelegateWithId(
1116 tab.GetWindowId());
1117 if (!window && !setup_for_test_)
1118 return false;
1119 return true;
1122 bool SessionModelAssociator::TabHasValidEntry(
1123 const SyncedTabDelegate& tab) const {
1124 if (tab.ProfileIsManaged() && tab.GetBlockedNavigations()->size() > 0)
1125 return true;
1127 int entry_count = tab.GetEntryCount();
1128 if (entry_count == 0)
1129 return false; // This deliberately ignores a new pending entry.
1131 int pending_index = tab.GetPendingEntryIndex();
1132 bool found_valid_url = false;
1133 for (int i = 0; i < entry_count; ++i) {
1134 const content::NavigationEntry* entry = (i == pending_index) ?
1135 tab.GetPendingEntry() : tab.GetEntryAtIndex(i);
1136 if (!entry)
1137 return false;
1138 const GURL& virtual_url = entry->GetVirtualURL();
1139 if (virtual_url.is_valid() &&
1140 !virtual_url.SchemeIs(chrome::kChromeUIScheme) &&
1141 !virtual_url.SchemeIs(chrome::kChromeNativeScheme) &&
1142 !virtual_url.SchemeIsFile()) {
1143 found_valid_url = true;
1146 return found_valid_url;
1149 // If this functionality changes, browser_sync::ShouldSyncSessionTab should be
1150 // modified to match.
1151 bool SessionModelAssociator::ShouldSyncTab(const SyncedTabDelegate& tab) const {
1152 DCHECK(CalledOnValidThread());
1153 if (!IsValidTab(tab))
1154 return false;
1155 return TabHasValidEntry(tab);
1158 void SessionModelAssociator::QuitLoopForSubtleTesting() {
1159 if (waiting_for_change_) {
1160 DVLOG(1) << "Quitting base::MessageLoop for test.";
1161 waiting_for_change_ = false;
1162 test_weak_factory_.InvalidateWeakPtrs();
1163 base::MessageLoop::current()->Quit();
1167 FaviconCache* SessionModelAssociator::GetFaviconCache() {
1168 return &favicon_cache_;
1171 void SessionModelAssociator::BlockUntilLocalChangeForTest(
1172 base::TimeDelta timeout) {
1173 if (test_weak_factory_.HasWeakPtrs())
1174 return;
1175 waiting_for_change_ = true;
1176 base::MessageLoop::current()->PostDelayedTask(
1177 FROM_HERE,
1178 base::Bind(&SessionModelAssociator::QuitLoopForSubtleTesting,
1179 test_weak_factory_.GetWeakPtr()),
1180 timeout);
1183 bool SessionModelAssociator::CryptoReadyIfNecessary() {
1184 // We only access the cryptographer while holding a transaction.
1185 syncer::ReadTransaction trans(FROM_HERE, sync_service_->GetUserShare());
1186 const syncer::ModelTypeSet encrypted_types = trans.GetEncryptedTypes();
1187 return !encrypted_types.Has(SESSIONS) ||
1188 sync_service_->IsCryptographerReady(&trans);
1191 void SessionModelAssociator::UpdateTabIdIfNecessary(
1192 int tab_node_id,
1193 SessionID::id_type new_tab_id) {
1194 DCHECK_NE(tab_node_id, TabNodePool::kInvalidTabNodeID);
1195 SessionID::id_type old_tab_id =
1196 local_tab_pool_.GetTabIdFromTabNodeId(tab_node_id);
1197 if (old_tab_id != new_tab_id) {
1198 // Rewrite tab id if required.
1199 syncer::WriteTransaction trans(FROM_HERE, sync_service_->GetUserShare());
1200 syncer::WriteNode tab_node(&trans);
1201 if (tab_node.InitByClientTagLookup(syncer::SESSIONS,
1202 TabNodePool::TabIdToTag(current_machine_tag_, tab_node_id)) ==
1203 syncer::BaseNode::INIT_OK) {
1204 sync_pb::SessionSpecifics session_specifics =
1205 tab_node.GetSessionSpecifics();
1206 DCHECK(session_specifics.has_tab());
1207 if (session_specifics.has_tab()) {
1208 sync_pb::SessionTab* tab_s = session_specifics.mutable_tab();
1209 tab_s->set_tab_id(new_tab_id);
1210 tab_node.SetSessionSpecifics(session_specifics);
1211 // Update tab node pool with the new association.
1212 local_tab_pool_.ReassociateTabNode(tab_node_id, new_tab_id);
1218 } // namespace browser_sync