// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/tabs/model/ios_chrome_synced_tab_delegate.h"
#import "base/check.h"
#import "components/prefs/pref_service.h"
#import "components/sessions/ios/ios_serialized_navigation_builder.h"
#import "components/sync/base/features.h"
#import "components/sync_sessions/sync_sessions_client.h"
#import "components/sync_sessions/synced_window_delegates_getter.h"
#import "ios/chrome/browser/complex_tasks/model/ios_task_tab_helper.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_feature.h"
#import "ios/chrome/browser/sessions/model/ios_chrome_session_tab_helper.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/signin/model/authentication_service.h"
#import "ios/chrome/browser/signin/model/authentication_service_factory.h"
#import "ios/web/public/navigation/navigation_item.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/web_state.h"
namespace {
// The minimum time between two sync updates of `last_active_time` when the tab
// hasn't changed.
constexpr base::TimeDelta kSyncActiveTimeThreshold = base::Minutes(10);
// Helper to access the correct NavigationItem, accounting for pending entries.
// May return null in rare cases such as a FORWARD_BACK navigation cancelling a
// slow-loading navigation.
web::NavigationItem* GetPossiblyPendingItemAtIndex(web::WebState* web_state,
int index) {
int pending_index = web_state->GetNavigationManager()->GetPendingItemIndex();
return (pending_index == index)
? web_state->GetNavigationManager()->GetPendingItem()
: web_state->GetNavigationManager()->GetItemAtIndex(index);
}
// Returns the time of the most recent activity for `web_state`, which is the
// maximum of the last navigation timestamp (if available) and the last active
// time (i.e. the last time the tab was made visible).
base::Time GetMostRecentActivityTime(const web::WebState* web_state) {
base::Time result = web_state->GetLastActiveTime();
if (web_state->IsRealized()) {
web::NavigationItem* last_committed_item =
web_state->GetNavigationManager()->GetLastCommittedItem();
if (last_committed_item) {
result = std::max(result, last_committed_item->GetTimestamp());
}
}
return result;
}
} // namespace
IOSChromeSyncedTabDelegate::IOSChromeSyncedTabDelegate(web::WebState* web_state)
: web_state_(web_state) {
DCHECK(web_state);
}
IOSChromeSyncedTabDelegate::~IOSChromeSyncedTabDelegate() {}
void IOSChromeSyncedTabDelegate::ResetCachedLastActiveTime() {
cached_last_active_time_.reset();
}
SessionID IOSChromeSyncedTabDelegate::GetWindowId() const {
return IOSChromeSessionTabHelper::FromWebState(web_state_)->window_id();
}
SessionID IOSChromeSyncedTabDelegate::GetSessionId() const {
return IOSChromeSessionTabHelper::FromWebState(web_state_)->session_id();
}
bool IOSChromeSyncedTabDelegate::IsBeingDestroyed() const {
return web_state_->IsBeingDestroyed();
}
base::Time IOSChromeSyncedTabDelegate::GetLastActiveTime() {
base::Time last_active_time = web_state_->GetLastActiveTime();
if (cached_last_active_time_.has_value() &&
last_active_time - cached_last_active_time_.value() <
kSyncActiveTimeThreshold) {
return cached_last_active_time_.value();
}
cached_last_active_time_ = last_active_time;
return last_active_time;
}
std::string IOSChromeSyncedTabDelegate::GetExtensionAppId() const {
return std::string();
}
bool IOSChromeSyncedTabDelegate::IsInitialBlankNavigation() const {
DCHECK(!IsPlaceholderTab());
return web_state_->GetNavigationItemCount() == 0;
}
int IOSChromeSyncedTabDelegate::GetCurrentEntryIndex() const {
DCHECK(!IsPlaceholderTab());
return web_state_->GetNavigationManager()->GetLastCommittedItemIndex();
}
int IOSChromeSyncedTabDelegate::GetEntryCount() const {
DCHECK(!IsPlaceholderTab());
return web_state_->GetNavigationItemCount();
}
GURL IOSChromeSyncedTabDelegate::GetVirtualURLAtIndex(int i) const {
DCHECK(!IsPlaceholderTab());
web::NavigationItem* item = GetPossiblyPendingItemAtIndex(web_state_, i);
return item ? item->GetVirtualURL() : GURL();
}
void IOSChromeSyncedTabDelegate::GetSerializedNavigationAtIndex(
int i,
sessions::SerializedNavigationEntry* serialized_entry) const {
DCHECK(!IsPlaceholderTab());
web::NavigationItem* item = GetPossiblyPendingItemAtIndex(web_state_, i);
if (item) {
*serialized_entry =
sessions::IOSSerializedNavigationBuilder::FromNavigationItem(i, *item);
}
}
bool IOSChromeSyncedTabDelegate::ProfileHasChildAccount() const {
DCHECK(!IsPlaceholderTab());
return false;
}
const std::vector<std::unique_ptr<const sessions::SerializedNavigationEntry>>*
IOSChromeSyncedTabDelegate::GetBlockedNavigations() const {
NOTREACHED_IN_MIGRATION();
return nullptr;
}
bool IOSChromeSyncedTabDelegate::IsPlaceholderTab() const {
// A tab is considered as "placeholder" if it is not fully loaded. This
// corresponds to "unrealized" tabs or tabs that are still restoring their
// navigation history.
if (!web_state_->IsRealized()) {
return true;
}
if (web_state_->GetNavigationManager()->IsRestoreSessionInProgress()) {
return true;
}
// The WebState is realized and the navigation history fully loaded, the
// tab can be considered as valid for sync.
return false;
}
bool IOSChromeSyncedTabDelegate::ShouldSync(
sync_sessions::SyncSessionsClient* sessions_client) {
DCHECK(!IsPlaceholderTab());
if (!sessions_client->GetSyncedWindowDelegatesGetter()->FindById(
GetWindowId())) {
return false;
}
if (IsInitialBlankNavigation()) {
return false; // This deliberately ignores a new pending entry.
}
if (base::FeatureList::IsEnabled(kIdentityDiscAccountMenu)) {
// If fast account switching via the account particle disk on the NTP is
// enabled, then for managed accounts, only sync tabs that have been updated
// after the signin.
ChromeBrowserState* browser_state =
ChromeBrowserState::FromBrowserState(web_state_->GetBrowserState());
AuthenticationService* auth_service =
AuthenticationServiceFactory::GetForBrowserState(browser_state);
if (auth_service && auth_service->HasPrimaryIdentityManaged(
signin::ConsentLevel::kSignin)) {
base::Time signin_time =
browser_state->GetPrefs()->GetTime(prefs::kLastSigninTimestamp);
// Note: Don't use GetLastActiveTime() here: (a) it only tracks when the
// tab was last made visible (not when it was last used), and (b) it
// intentionally caches outdated values for a few minutes. Instead, query
// the most-recent activity time from the WebState directly.
if (GetMostRecentActivityTime(web_state_) < signin_time) {
return false;
}
}
}
int entry_count = GetEntryCount();
for (int i = 0; i < entry_count; ++i) {
if (sessions_client->ShouldSyncURL(GetVirtualURLAtIndex(i))) {
return true;
}
}
return false;
}
int64_t IOSChromeSyncedTabDelegate::GetTaskIdForNavigationId(int nav_id) const {
DCHECK(!IsPlaceholderTab());
const IOSContentRecordTaskId* record =
IOSTaskTabHelper::FromWebState(web_state_)
->GetContextRecordTaskId(nav_id);
return record ? record->task_id() : -1;
}
int64_t IOSChromeSyncedTabDelegate::GetParentTaskIdForNavigationId(
int nav_id) const {
DCHECK(!IsPlaceholderTab());
const IOSContentRecordTaskId* record =
IOSTaskTabHelper::FromWebState(web_state_)
->GetContextRecordTaskId(nav_id);
return record ? record->parent_task_id() : -1;
}
int64_t IOSChromeSyncedTabDelegate::GetRootTaskIdForNavigationId(
int nav_id) const {
DCHECK(!IsPlaceholderTab());
const IOSContentRecordTaskId* record =
IOSTaskTabHelper::FromWebState(web_state_)
->GetContextRecordTaskId(nav_id);
return record ? record->root_task_id() : -1;
}
std::unique_ptr<sync_sessions::SyncedTabDelegate>
IOSChromeSyncedTabDelegate::ReadPlaceholderTabSnapshotIfItShouldSync(
sync_sessions::SyncSessionsClient* sessions_client) {
NOTREACHED_IN_MIGRATION()
<< "ReadPlaceholderTabSnapshotIfItShouldSync is not supported for the "
"iOS platform.";
return nullptr;
}
WEB_STATE_USER_DATA_KEY_IMPL(IOSChromeSyncedTabDelegate)