blob: 79481c21dbdb192e38239241936af4b0146abe71 [file] [log] [blame]
// 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/sync/ios_chrome_synced_tab_delegate.h"
#import "base/check.h"
#import "components/sessions/ios/ios_serialized_navigation_builder.h"
#import "components/sync_sessions/sync_sessions_client.h"
#import "components/sync_sessions/synced_window_delegates_getter.h"
#import "ios/chrome/browser/complex_tasks/ios_task_tab_helper.h"
#import "ios/chrome/browser/sessions/ios_chrome_session_tab_helper.h"
#import "ios/web/common/features.h"
#import "ios/web/public/navigation/navigation_item.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/session/crw_navigation_item_storage.h"
#import "ios/web/public/session/crw_session_storage.h"
#import "ios/web/public/web_state.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// 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 whether placeholder tabs are supported.
bool ArePlaceholderTabsSupported() {
// The support for placeholder tabs requires the WebState session id to be
// stable across application restart. It is the case since M-114 which added
// the code to save and restore the identifier. However, it also requires
// that the stable identifier is communicated to sync with the detail about
// the corresponding session which will happen as the application is used.
//
// Yet, placeholder tabs support is required to enable session restoration
// optimisations. As this will be launched later, it is expected that by
// that point, all existing sessions will have been converted to use the
// stable session id and the session state uploaded to sync. Thus it is
// safe to enable the support of placeholder tabs behind the same feature
// controlling the other session restoration optimisations.
return web::features::UseSessionSerializationOptimizations();
}
} // namespace
IOSChromeSyncedTabDelegate::IOSChromeSyncedTabDelegate(web::WebState* web_state)
: web_state_(web_state) {
DCHECK(web_state);
}
IOSChromeSyncedTabDelegate::~IOSChromeSyncedTabDelegate() {}
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();
}
std::string IOSChromeSyncedTabDelegate::GetExtensionAppId() const {
return std::string();
}
bool IOSChromeSyncedTabDelegate::IsInitialBlankNavigation() const {
DCHECK(!IsPlaceholderTab());
if (GetSessionStorageIfNeeded()) {
return session_storage_.itemStorages.count == 0;
}
return web_state_->GetNavigationItemCount() == 0;
}
int IOSChromeSyncedTabDelegate::GetCurrentEntryIndex() const {
DCHECK(!IsPlaceholderTab());
if (GetSessionStorageIfNeeded()) {
NSInteger lastCommittedIndex = session_storage_.lastCommittedItemIndex;
if (lastCommittedIndex < 0 ||
lastCommittedIndex >=
static_cast<NSInteger>(session_storage_.itemStorages.count)) {
// It has been observed that lastCommittedIndex can be invalid (see
// crbug.com/1060553). Returning an invalid index will cause a crash.
// If lastCommittedIndex is invalid, consider the last index as the
// current one.
// As GetSessionStorageIfNeeded just returned true,
// session_storage_.itemStorages.count is not 0 and
// session_storage_.itemStorages.count - 1 is valid.
return session_storage_.itemStorages.count - 1;
}
return session_storage_.lastCommittedItemIndex;
}
return web_state_->GetNavigationManager()->GetLastCommittedItemIndex();
}
int IOSChromeSyncedTabDelegate::GetEntryCount() const {
DCHECK(!IsPlaceholderTab());
if (GetSessionStorageIfNeeded()) {
return static_cast<int>(session_storage_.itemStorages.count);
}
return web_state_->GetNavigationItemCount();
}
GURL IOSChromeSyncedTabDelegate::GetVirtualURLAtIndex(int i) const {
DCHECK(!IsPlaceholderTab());
if (GetSessionStorageIfNeeded()) {
DCHECK_GE(i, 0);
NSArray<CRWNavigationItemStorage*>* item_storages =
session_storage_.itemStorages;
DCHECK_LT(i, static_cast<int>(item_storages.count));
CRWNavigationItemStorage* item = item_storages[i];
return item.virtualURL;
}
web::NavigationItem* item = GetPossiblyPendingItemAtIndex(web_state_, i);
return item ? item->GetVirtualURL() : GURL();
}
std::string IOSChromeSyncedTabDelegate::GetPageLanguageAtIndex(int i) const {
// TODO(crbug.com/957657): Add page language to NavigationItem.
DCHECK(!IsPlaceholderTab());
return std::string();
}
void IOSChromeSyncedTabDelegate::GetSerializedNavigationAtIndex(
int i,
sessions::SerializedNavigationEntry* serialized_entry) const {
DCHECK(!IsPlaceholderTab());
if (GetSessionStorageIfNeeded()) {
NSArray<CRWNavigationItemStorage*>* item_storages =
session_storage_.itemStorages;
DCHECK_GE(i, 0);
DCHECK_LT(i, static_cast<int>(item_storages.count));
CRWNavigationItemStorage* item = item_storages[i];
*serialized_entry =
sessions::IOSSerializedNavigationBuilder::FromNavigationStorageItem(
i, item);
return;
}
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();
return nullptr;
}
bool IOSChromeSyncedTabDelegate::IsPlaceholderTab() const {
// Can't be a placeholder tab if the support for placeholder tabs is not
// enabled.
if (!ArePlaceholderTabsSupported()) {
return false;
}
// 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.
int entry_count = GetEntryCount();
for (int i = 0; i < entry_count; ++i) {
const GURL& virtual_url = GetVirtualURLAtIndex(i);
if (!virtual_url.is_valid())
continue;
if (sessions_client->ShouldSyncURL(virtual_url))
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;
}
bool IOSChromeSyncedTabDelegate::GetSessionStorageIfNeeded() const {
// Never use the session storage when placeholder tabs support is enabled.
// In fact, using the session storage is a workaround to missing placeholder
// tab support.
if (ArePlaceholderTabsSupported()) {
return false;
}
// Unrealized web states should always use session storage, regardless of
// navigation items.
if (!web_state_->IsRealized()) {
if (!session_storage_) {
session_storage_ = web_state_->BuildSessionStorage();
}
return true;
}
// With slim navigation, the navigation manager is only restored when the tab
// is displayed. Before restoration, the session storage must be used.
bool should_use_storage =
web_state_->GetNavigationManager()->IsRestoreSessionInProgress();
bool storage_has_navigation_items = false;
if (should_use_storage) {
if (!session_storage_) {
session_storage_ = web_state_->BuildSessionStorage();
}
storage_has_navigation_items = session_storage_.itemStorages.count != 0;
#if DCHECK_IS_ON()
if (storage_has_navigation_items) {
DCHECK_GE(session_storage_.lastCommittedItemIndex, 0);
DCHECK_LT(session_storage_.lastCommittedItemIndex,
static_cast<int>(session_storage_.itemStorages.count));
}
#endif
}
return should_use_storage && storage_has_navigation_items;
}
WEB_STATE_USER_DATA_KEY_IMPL(IOSChromeSyncedTabDelegate)