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"
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"
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"
53 #include "base/linux_util.h"
58 using content::BrowserThread
;
59 using content::NavigationEntry
;
60 using prefs::kSyncSessionsGUID
;
61 using sessions::SerializedNavigationEntry
;
62 using syncer::SESSIONS
;
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());
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";
87 std::string client_id
= session_tag
.substr(
88 SessionTagPrefix().length(),
89 session_tag
.length());
96 namespace browser_sync
{
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;
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_
);
135 SessionModelAssociator::SessionModelAssociator(ProfileSyncService
* sync_service
,
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_
);
150 DCHECK(setup_for_test
);
153 SessionModelAssociator::~SessionModelAssociator() {
154 DCHECK(CalledOnValidThread());
157 bool SessionModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes
) {
158 DCHECK(CalledOnValidThread());
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
;
168 // The sync model has user created nodes iff the sessions folder has
170 *has_nodes
= root
.HasChildren();
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
;
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
217 if ((*i
)->IsTypeTabbed()) {
218 window_s
.set_browser_type(
219 sync_pb::SessionWindow_BrowserType_TYPE_TABBED
);
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.
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
);
245 window_s
.add_tab(tab_id
);
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.
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
)) {
270 window_s
.add_tab(tab_id
);
273 // Only add a window if it contains valid 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(
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
) {
299 *error
= error_handler_
->CreateAndUploadError(
301 "Failed to load local session header node.",
306 header_node
.SetSessionSpecifics(specifics
);
307 if (waiting_for_change_
) QuitLoopForSubtleTesting();
312 bool SessionModelAssociator::ShouldSyncWindow(
313 const SyncedWindowDelegate
* window
) {
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();
326 if (!AssociateTab(*i
, error
))
329 if (waiting_for_change_
) QuitLoopForSubtleTesting();
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).
346 local_tab_pool_
.FreeTabNode(tab_iter
->second
->tab_node_id());
347 local_tab_map_
.erase(tab_iter
);
351 if (!ShouldSyncTab(*tab
))
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
) {
364 *error
= error_handler_
->CreateAndUploadError(
366 "Received invalid tab node from tab pool.",
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
);
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
);
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
);
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();
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
:
417 bool SessionModelAssociator::WriteTabContentsToSyncModel(
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(
434 TabNodePool::TabIdToTag(current_machine_tag_
, tab_node_id
)) !=
435 syncer::BaseNode::INIT_OK
) {
437 *error
= error_handler_
->CreateAndUploadError(
439 "Failed to look up local tab node",
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
);
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
=
488 void SessionModelAssociator::SetSessionTabFromDelegate(
489 const SyncedTabDelegate
& tab_delegate
,
491 SessionTab
* 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()) :
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
);
521 if (!entry
->GetVirtualURL().is_valid())
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
));
533 session_tab
->navigations
.push_back(
534 SerializedNavigationEntry::FromNavigationEntry(i
, *network_time_entry
));
536 session_tab
->navigations
.back().set_blocked_state(
537 SerializedNavigationEntry::STATE_ALLOWED
);
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();
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
;
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(
599 kNoSessionsFolderError
,
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();
609 return error_handler_
->CreateAndUploadError(
611 "Failed to get device info.",
614 synced_session_tracker_
.SetLocalSessionTag(current_machine_tag_
);
615 if (!UpdateAssociationsFromSyncModel(root
, &trans
, &error
)) {
616 DCHECK(error
.IsSet());
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(
631 "Failed to create sessions header sync node.",
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
);
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
);
659 // Check if anything has changed on the client side.
660 if (!UpdateSyncModelDataFromClient(&error
)) {
661 DCHECK(error
.IsSet());
665 DVLOG(1) << "Session models associated.";
666 DCHECK(!error
.IsSet());
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: "
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
) {
736 *error
= error_handler_
->CreateAndUploadError(
738 "Failed to load sync node",
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
);
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();
766 if (specifics
.has_header() || !specifics
.has_tab()) {
767 LOG(WARNING
) << "Found invalid session node, deleting.";
768 sync_node
.Tombstone();
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());
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_
)
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
797 // Load (or create) the SyncedSession object for this client.
798 const sync_pb::SessionHeader
& header
= specifics
.header();
799 PopulateSessionHeaderFromSpecifics(header
,
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
,
817 PopulateSessionWindowFromSpecifics(foreign_session_tag
,
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();
830 synced_session_tracker_
.GetTab(foreign_session_tag
,
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
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
;
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 "
858 DVLOG(1) << "Disassociating session " << foreign_session_tag
;
859 return synced_session_tracker_
.DeleteSession(foreign_session_tag
);
863 void SessionModelAssociator::PopulateSessionHeaderFromSpecifics(
864 const sync_pb::SessionHeader
& header_specifics
,
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
;
875 case sync_pb::SyncEnums_DeviceType_TYPE_MAC
:
876 session_header
->device_type
= SyncedSession::TYPE_MACOSX
;
878 case sync_pb::SyncEnums_DeviceType_TYPE_LINUX
:
879 session_header
->device_type
= SyncedSession::TYPE_LINUX
;
881 case sync_pb::SyncEnums_DeviceType_TYPE_CROS
:
882 session_header
->device_type
= SyncedSession::TYPE_CHROMEOS
;
884 case sync_pb::SyncEnums_DeviceType_TYPE_PHONE
:
885 session_header
->device_type
= SyncedSession::TYPE_PHONE
;
887 case sync_pb::SyncEnums_DeviceType_TYPE_TABLET
:
888 session_header
->device_type
= SyncedSession::TYPE_TABLET
;
890 case sync_pb::SyncEnums_DeviceType_TYPE_OTHER
:
891 // Intentionally fall-through
893 session_header
->device_type
= SyncedSession::TYPE_OTHER
;
897 session_header
->modified_time
= mtime
;
901 void SessionModelAssociator::PopulateSessionWindowFromSpecifics(
902 const std::string
& session_tag
,
903 const sync_pb::SessionWindow
& specifics
,
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;
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(),
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
),
941 syncer::TimeToProtoTime(
946 // Then go through and check for any legacy favicon data.
947 if (!tab
.has_favicon() || tab
.favicon().empty())
949 if (!tab
.has_favicon_type() ||
950 tab
.favicon_type() != sync_pb::SessionTab::TYPE_WEB_FAVICON
) {
951 DVLOG(1) << "Ignoring non-web favicon.";
954 if (tab
.navigation_size() == 0)
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())
964 GURL
favicon_source(tab
.favicon_source());
965 if (!favicon_source
.is_valid())
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
,
974 syncer::TimeToProtoTime(
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())
1000 *local_session
= synced_session_tracker_
.GetSession(GetCurrentMachineTag());
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
,
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.";
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 "
1077 if (!DisassociateForeignSession(tag
)) {
1078 // We don't have any data for this session, our work here is done!
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
;
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
;
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()) &&
1114 const SyncedWindowDelegate
* window
=
1115 SyncedWindowDelegate::FindSyncedWindowDelegateWithId(
1117 if (!window
&& !setup_for_test_
)
1122 bool SessionModelAssociator::TabHasValidEntry(
1123 const SyncedTabDelegate
& tab
) const {
1124 if (tab
.ProfileIsManaged() && tab
.GetBlockedNavigations()->size() > 0)
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
);
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
))
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())
1175 waiting_for_change_
= true;
1176 base::MessageLoop::current()->PostDelayedTask(
1178 base::Bind(&SessionModelAssociator::QuitLoopForSubtleTesting
,
1179 test_weak_factory_
.GetWeakPtr()),
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(
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