// Copyright 2024 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/ui/content_suggestions/tab_resumption/tab_resumption_mediator.h"
#import "base/apple/foundation_util.h"
#import "base/command_line.h"
#import "base/memory/raw_ptr.h"
#import "base/strings/sys_string_conversions.h"
#import "components/page_image_service/features.h"
#import "components/page_image_service/image_service.h"
#import "components/page_image_service/mojom/page_image_service.mojom.h"
#import "components/sessions/core/session_id.h"
#import "components/signin/public/identity_manager/objc/identity_manager_observer_bridge.h"
#import "components/sync/base/user_selectable_type.h"
#import "components/sync/service/sync_service.h"
#import "components/sync/service/sync_user_settings.h"
#import "components/sync_sessions/open_tabs_ui_delegate.h"
#import "components/sync_sessions/session_sync_service.h"
#import "components/visited_url_ranking/public/visited_url_ranking_service.h"
#import "ios/chrome/browser/favicon/model/favicon_loader.h"
#import "ios/chrome/browser/favicon/model/ios_chrome_favicon_loader_factory.h"
#import "ios/chrome/browser/intents/intents_donation_helper.h"
#import "ios/chrome/browser/metrics/model/new_tab_page_uma.h"
#import "ios/chrome/browser/ntp/model/new_tab_page_tab_helper.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_metrics_delegate.h"
#import "ios/chrome/browser/ntp_tiles/model/tab_resumption/tab_resumption_prefs.h"
#import "ios/chrome/browser/page_image/model/page_image_service_factory.h"
#import "ios/chrome/browser/sessions/model/session_util.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/browser/browser_list.h"
#import "ios/chrome/browser/shared/model/browser/browser_list_factory.h"
#import "ios/chrome/browser/shared/model/prefs/pref_backed_boolean.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/shared/model/url/chrome_url_constants.h"
#import "ios/chrome/browser/shared/model/utils/observable_boolean.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_opener.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/snapshots/model/snapshot_tab_helper.h"
#import "ios/chrome/browser/start_surface/ui_bundled/start_surface_features.h"
#import "ios/chrome/browser/start_surface/ui_bundled/start_surface_recent_tab_browser_agent.h"
#import "ios/chrome/browser/start_surface/ui_bundled/start_surface_recent_tab_removal_observer_bridge.h"
#import "ios/chrome/browser/start_surface/ui_bundled/start_surface_util.h"
#import "ios/chrome/browser/sync/model/session_sync_service_factory.h"
#import "ios/chrome/browser/sync/model/sync_observer_bridge.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/chrome/browser/synced_sessions/model/distant_session.h"
#import "ios/chrome/browser/synced_sessions/model/distant_tab.h"
#import "ios/chrome/browser/synced_sessions/model/synced_sessions.h"
#import "ios/chrome/browser/synced_sessions/model/synced_sessions_bridge.h"
#import "ios/chrome/browser/tabs/model/tab_sync_util.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_constants.h"
#import "ios/chrome/browser/ui/content_suggestions/content_suggestions_metrics_recorder.h"
#import "ios/chrome/browser/ui/content_suggestions/tab_resumption/tab_resumption_commands.h"
#import "ios/chrome/browser/ui/content_suggestions/tab_resumption/tab_resumption_constants.h"
#import "ios/chrome/browser/ui/content_suggestions/tab_resumption/tab_resumption_helper_delegate.h"
#import "ios/chrome/browser/ui/content_suggestions/tab_resumption/tab_resumption_item.h"
#import "ios/chrome/browser/url_loading/model/url_loading_browser_agent.h"
#import "ios/chrome/browser/url_loading/model/url_loading_params.h"
#import "ios/chrome/browser/visited_url_ranking/model/visited_url_ranking_service_factory.h"
#import "ios/chrome/common/ui/favicon/favicon_attributes.h"
#import "ios/chrome/common/ui/favicon/favicon_constants.h"
#import "services/network/public/cpp/shared_url_loader_factory.h"
namespace {
// The key to store the timestamp when the scene enters into background.
NSString* kStartSurfaceSceneEnterIntoBackgroundTime =
@"StartSurfaceSceneEnterIntoBackgroundTime";
// Helper function to extract tab data from url aggregate.
// Try first the session tab data, then the tab model tab data.
const visited_url_ranking::URLVisitAggregate::TabData* ExtractTabData(
visited_url_ranking::URLVisitAggregate& url_aggregate) {
const auto& session_iterator = url_aggregate.fetcher_data_map.find(
visited_url_ranking::Fetcher::kSession);
if (session_iterator != url_aggregate.fetcher_data_map.end()) {
const visited_url_ranking::URLVisitAggregate::URLVisitVariant&
url_visit_variant = session_iterator->second;
const visited_url_ranking::URLVisitAggregate::TabData* tab_data =
std::get_if<visited_url_ranking::URLVisitAggregate::TabData>(
&url_visit_variant);
if (tab_data) {
return tab_data;
}
}
const auto& tab_model_iterator = url_aggregate.fetcher_data_map.find(
visited_url_ranking::Fetcher::kTabModel);
if (tab_model_iterator != url_aggregate.fetcher_data_map.end()) {
const visited_url_ranking::URLVisitAggregate::URLVisitVariant&
url_visit_variant = tab_model_iterator->second;
const visited_url_ranking::URLVisitAggregate::TabData* tab_data =
std::get_if<visited_url_ranking::URLVisitAggregate::TabData>(
&url_visit_variant);
if (tab_data) {
return tab_data;
}
}
return nullptr;
}
// Whether the item should be displayed immediately (before fetching an image).
bool ShouldShowItemImmediately() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
kTabResumptionShowItemImmediately);
}
// Salient images should come from gstatic.com.
const char kGStatic[] = ".gstatic.com";
} // namespace
@interface TabResumptionMediator () <BooleanObserver,
IdentityManagerObserverBridgeDelegate,
MagicStackModuleDelegate,
StartSurfaceRecentTabObserving,
SyncedSessionsObserver,
SyncObserverModelBridge,
TabResumptionCommands>
// readwrite override.
@property(nonatomic, strong, readwrite) TabResumptionItem* itemConfig;
@end
@implementation TabResumptionMediator {
// Last distant tab resumption item URL.
GURL _lastDistinctItemURL;
// Tab identifier of the last distant tab resumption item.
std::optional<SessionID> _tabId;
// Session tag of the last distant tab resumption item.
std::string _sessionTag;
BOOL _isOffTheRecord;
// The last item that is returned by the model.
// The URL/title will be used to not fetch again images if the same item is
// returned twice, or to ignore update on obsolete items.
TabResumptionItem* _pendingItem;
// The owning Browser.
raw_ptr<Browser> _browser;
raw_ptr<PrefService> _localState;
raw_ptr<PrefService> _browserStatePrefs;
SceneState* _sceneState;
// Loads favicons.
raw_ptr<FaviconLoader> _faviconLoader;
// Browser Agent that manages the most recent WebState.
raw_ptr<StartSurfaceRecentTabBrowserAgent> _recentTabBrowserAgent;
// KeyedService responsible session sync.
raw_ptr<sync_sessions::SessionSyncService> _sessionSyncService;
// KeyedService responsible for sync state.
raw_ptr<syncer::SyncService> _syncService;
raw_ptr<UrlLoadingBrowserAgent> _URLLoadingBrowserAgent;
raw_ptr<WebStateList> _webStateList;
// KeyedService for Tab resumption 2.0.
raw_ptr<visited_url_ranking::VisitedURLRankingService>
_visitedURLRankingService;
// KeyedService for Salient images.
raw_ptr<page_image_service::ImageService> _pageImageService;
// Observer bridge for mediator to listen to
// StartSurfaceRecentTabObserverBridge.
std::unique_ptr<StartSurfaceRecentTabObserverBridge> _startSurfaceObserver;
std::unique_ptr<image_fetcher::ImageDataFetcher> _imageFetcher;
std::unique_ptr<SyncObserverBridge> _syncObserverModelBridge;
// Observer for changes to the user's identity state.
std::unique_ptr<signin::IdentityManagerObserverBridge>
_identityManagerObserverBridge;
std::unique_ptr<synced_sessions::SyncedSessionsObserverBridge>
_syncedSessionsObserverBridge;
// Whether the item is currently presented as Top Module by Magic Stack.
BOOL _currentlyTopModule;
PrefBackedBoolean* _tabResumptionDisabled;
}
- (instancetype)initWithLocalState:(PrefService*)localState
prefService:(PrefService*)prefService
identityManager:(signin::IdentityManager*)identityManager
browser:(Browser*)browser {
self = [super init];
if (self) {
CHECK(IsTabResumptionEnabled());
_localState = localState;
_browserStatePrefs = prefService;
_browser = browser;
_tabId = SessionID::InvalidValue();
_sceneState = _browser->GetSceneState();
_webStateList = _browser->GetWebStateList();
_isOffTheRecord = _browser->GetBrowserState()->IsOffTheRecord();
if (IsHomeCustomizationEnabled()) {
_tabResumptionDisabled = [[PrefBackedBoolean alloc]
initWithPrefService:_browserStatePrefs
prefName:
prefs::
kHomeCustomizationMagicStackTabResumptionEnabled];
[_tabResumptionDisabled setObserver:self];
} else {
_tabResumptionDisabled = [[PrefBackedBoolean alloc]
initWithPrefService:_localState
prefName:tab_resumption_prefs::kTabResumptioDisabledPref];
[_tabResumptionDisabled setObserver:self];
}
ChromeBrowserState* browserState = _browser->GetBrowserState();
_sessionSyncService =
SessionSyncServiceFactory::GetForBrowserState(browserState);
_syncService = SyncServiceFactory::GetForBrowserState(browserState);
_faviconLoader =
IOSChromeFaviconLoaderFactory::GetForBrowserState(browserState);
_recentTabBrowserAgent =
StartSurfaceRecentTabBrowserAgent::FromBrowser(_browser);
_URLLoadingBrowserAgent = UrlLoadingBrowserAgent::FromBrowser(_browser);
_startSurfaceObserver =
std::make_unique<StartSurfaceRecentTabObserverBridge>(self);
StartSurfaceRecentTabBrowserAgent::FromBrowser(_browser)->AddObserver(
_startSurfaceObserver.get());
if (IsTabResumption1_5Enabled()) {
_pageImageService =
PageImageServiceFactory::GetForBrowserState(browserState);
}
_imageFetcher = std::make_unique<image_fetcher::ImageDataFetcher>(
browserState->GetSharedURLLoaderFactory());
if (IsTabResumption2_0Enabled()) {
_visitedURLRankingService =
VisitedURLRankingServiceFactory::GetForBrowserState(browserState);
}
if (IsTabResumption2_0Enabled() ||
!IsTabResumptionEnabledForMostRecentTabOnly()) {
// Tab resumption 2.0 will get foreign tabs and so needs to register to
// sync/identity notifications.
_syncedSessionsObserverBridge.reset(
new synced_sessions::SyncedSessionsObserverBridge(
self, _sessionSyncService));
_syncObserverModelBridge.reset(
new SyncObserverBridge(self, _syncService));
_identityManagerObserverBridge.reset(
new signin::IdentityManagerObserverBridge(identityManager, self));
}
}
return self;
}
- (void)disconnect {
_syncedSessionsObserverBridge.reset();
if (_startSurfaceObserver) {
_recentTabBrowserAgent->RemoveObserver(_startSurfaceObserver.get());
_startSurfaceObserver.reset();
}
_recentTabBrowserAgent = nullptr;
_syncObserverModelBridge.reset();
_identityManagerObserverBridge.reset();
[_tabResumptionDisabled setObserver:nil];
_tabResumptionDisabled = nil;
}
#pragma mark - Public methods
- (void)openTabResumptionItem:(TabResumptionItem*)item {
[self.contentSuggestionsMetricsRecorder recordTabResumptionTabOpened];
tab_resumption_prefs::SetTabResumptionLastOpenedTabURL(item.tabURL,
_browserStatePrefs);
[self.delegate logMagicStackEngagementForType:ContentSuggestionsModuleType::
kTabResumption];
NSUInteger index = [self.delegate
indexForMagicStackModule:ContentSuggestionsModuleType::kTabResumption];
if (IsTabResumption2_0Enabled() && index == 0 && _visitedURLRankingService) {
_visitedURLRankingService->RecordAction(visited_url_ranking::kActivated,
self.itemConfig.URLKey,
self.itemConfig.requestID);
}
switch (item.itemType) {
case TabResumptionItemType::kLastSyncedTab:
[self.NTPMetricsDelegate distantTabResumptionOpenedAtIndex:index];
[self openDistantTab:item];
break;
case TabResumptionItemType::kMostRecentTab: {
[self.NTPMetricsDelegate recentTabTileOpenedAtIndex:index];
[IntentDonationHelper donateIntent:IntentType::kOpenLatestTab];
web::NavigationManager::WebLoadParams webLoadParams =
web::NavigationManager::WebLoadParams(item.tabURL);
UrlLoadParams params = UrlLoadParams::SwitchToTab(webLoadParams);
params.web_params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
_URLLoadingBrowserAgent->Load(params);
break;
}
}
[self.delegate removeTabResumptionModule];
}
- (void)openDistantTab:(TabResumptionItem*)item {
ChromeBrowserState* browserState = _browser->GetBrowserState();
sync_sessions::OpenTabsUIDelegate* openTabsDelegate =
SessionSyncServiceFactory::GetForBrowserState(browserState)
->GetOpenTabsUIDelegate();
const sessions::SessionTab* sessionTab = nullptr;
if (openTabsDelegate->GetForeignTab(_sessionTag, _tabId.value(),
&sessionTab)) {
bool isNTP = _webStateList->GetActiveWebState()->GetVisibleURL() ==
kChromeUINewTabURL;
new_tab_page_uma::RecordNTPAction(
_isOffTheRecord, isNTP,
new_tab_page_uma::ACTION_OPENED_FOREIGN_SESSION);
std::unique_ptr<web::WebState> webState =
session_util::CreateWebStateWithNavigationEntries(
browserState, sessionTab->current_navigation_index,
sessionTab->navigations);
_webStateList->ReplaceWebStateAt(_webStateList->active_index(),
std::move(webState));
} else {
web::NavigationManager::WebLoadParams webLoadParams =
web::NavigationManager::WebLoadParams(item.tabURL);
UrlLoadParams params = UrlLoadParams::InCurrentTab(webLoadParams);
params.web_params.transition_type = ui::PAGE_TRANSITION_AUTO_BOOKMARK;
_URLLoadingBrowserAgent->Load(params);
}
}
- (void)disableModule {
tab_resumption_prefs::DisableTabResumption(
IsHomeCustomizationEnabled() ? _browserStatePrefs : _localState);
}
- (void)setDelegate:(id<TabResumptionHelperDelegate>)delegate {
_delegate = delegate;
if (_delegate) {
[self fetchLastTabResumptionItem];
}
}
#pragma mark - MagicStackModuleDelegate
- (void)magicStackModule:(MagicStackModule*)magicStackModule
wasDisplayedAtIndex:(NSUInteger)index {
CHECK(self.itemConfig == magicStackModule);
_currentlyTopModule = (index == 0);
if (IsTabResumption2_0Enabled() && index == 0 && _visitedURLRankingService) {
_visitedURLRankingService->RecordAction(visited_url_ranking::kSeen,
self.itemConfig.URLKey,
self.itemConfig.requestID);
}
switch (self.itemConfig.itemType) {
case TabResumptionItemType::kLastSyncedTab:
[self.NTPMetricsDelegate distantTabResumptionDisplayedAtIndex:index];
break;
case TabResumptionItemType::kMostRecentTab:
[self.NTPMetricsDelegate recentTabTileDisplayedAtIndex:index];
break;
}
}
#pragma mark - Boolean Observer
- (void)booleanDidChange:(id<ObservableBoolean>)observableBoolean {
if (observableBoolean == _tabResumptionDisabled) {
if ((IsHomeCustomizationEnabled() && !observableBoolean.value) ||
(!IsHomeCustomizationEnabled() && observableBoolean.value)) {
[self.delegate removeTabResumptionModule];
}
}
}
#pragma mark - SyncObserverBridge
- (void)onSyncStateChanged {
// If tabs are not synced, hide the tab resumption tile.
if (!_syncService->GetUserSettings()->GetSelectedTypes().Has(
syncer::UserSelectableType::kTabs)) {
[self.delegate removeTabResumptionModule];
}
}
#pragma mark - IdentityManagerObserverBridgeDelegate
- (void)onPrimaryAccountChanged:
(const signin::PrimaryAccountChangeEvent&)event {
switch (event.GetEventTypeFor(signin::ConsentLevel::kSignin)) {
case signin::PrimaryAccountChangeEvent::Type::kCleared: {
// If the user is signed out, remove the tab resumption tile.
[self.delegate removeTabResumptionModule];
self.itemConfig = nil;
break;
}
default:
break;
}
}
#pragma mark - SyncedSessionsObserver
- (void)onForeignSessionsChanged {
[self fetchLastTabResumptionItem];
}
#pragma mark - StartSurfaceRecentTabObserving
- (void)mostRecentTabWasRemoved:(web::WebState*)webState {
if (self.itemConfig && self.itemConfig.itemType == kMostRecentTab) {
[self.delegate removeTabResumptionModule];
_currentlyTopModule = NO;
self.itemConfig = nil;
}
}
- (void)mostRecentTab:(web::WebState*)webState
faviconUpdatedWithImage:(UIImage*)image {
}
- (void)mostRecentTab:(web::WebState*)webState
titleWasUpdated:(NSString*)title {
}
#pragma mark - Private
// Fetches the item to display from the model.
- (void)fetchLastTabResumptionItem {
if (tab_resumption_prefs::IsTabResumptionDisabled(
IsHomeCustomizationEnabled() ? _browserStatePrefs : _localState)) {
return;
}
if (_visitedURLRankingService && IsTabResumption2_0Enabled()) {
__weak __typeof(self) weakSelf = self;
_visitedURLRankingService->FetchURLVisitAggregates(
visited_url_ranking::FetchOptions::
CreateDefaultFetchOptionsForTabResumption(),
base::BindOnce(
^(visited_url_ranking::ResultStatus status,
std::vector<visited_url_ranking::URLVisitAggregate> urls) {
[weakSelf onURLFetched:std::move(urls) withStatus:status];
}));
return;
}
_sessionTag = "";
_tabId = SessionID::InvalidValue();
if (!IsTabResumptionEnabledForMostRecentTabOnly()) {
// If sync is enabled and `GetOpenTabsUIDelegate()` returns nullptr, that
// means the `_sessionSyncService` is not fully operational.
if (_syncService->IsSyncFeatureEnabled() &&
!_sessionSyncService->GetOpenTabsUIDelegate()) {
return;
}
}
base::Time mostRecentTabOpenedTime = base::Time::UnixEpoch();
base::Time lastSyncedTabSyncedTime = base::Time::UnixEpoch();
web::WebState* mostRecentTab = _recentTabBrowserAgent->most_recent_tab();
if (mostRecentTab) {
NSDate* mostRecentTabDate = base::apple::ObjCCastStrict<NSDate>([_sceneState
sessionObjectForKey:kStartSurfaceSceneEnterIntoBackgroundTime]);
if (mostRecentTabDate != nil) {
mostRecentTabOpenedTime = base::Time::FromNSDate(mostRecentTabDate);
}
}
const synced_sessions::DistantSession* session = nullptr;
const synced_sessions::DistantTab* tab = nullptr;
auto const syncedSessions =
std::make_unique<synced_sessions::SyncedSessions>(_sessionSyncService);
if (!IsTabResumptionEnabledForMostRecentTabOnly()) {
LastActiveDistantTab lastDistantTab = GetLastActiveDistantTab(
syncedSessions.get(), TabResumptionForXDevicesTimeThreshold());
if (lastDistantTab.tab) {
tab = lastDistantTab.tab;
if (_lastDistinctItemURL != tab->virtual_url) {
_lastDistinctItemURL = tab->virtual_url;
session = lastDistantTab.session;
lastSyncedTabSyncedTime = tab->last_active_time;
}
}
}
web::WebState* activeWebState = _webStateList->GetActiveWebState();
bool canShowMostRecentItem =
activeWebState && NewTabPageTabHelper::FromWebState(activeWebState)
->ShouldShowStartSurface();
// If both times have not been updated, that means there is no item to return.
if (mostRecentTabOpenedTime == base::Time::UnixEpoch() &&
lastSyncedTabSyncedTime == base::Time::UnixEpoch()) {
return;
} else if (lastSyncedTabSyncedTime > mostRecentTabOpenedTime) {
CHECK(!IsTabResumptionEnabledForMostRecentTabOnly());
[self fetchLastSyncedTabItemFromLastActiveDistantTab:tab session:session];
_sessionTag = session->tag;
_tabId = tab->tab_id;
} else if (canShowMostRecentItem) {
[self fetchMostRecentTabItemFromWebState:mostRecentTab
openedTime:mostRecentTabOpenedTime];
}
}
// Fetches a relevant image for the `item` to display.
- (void)fetchImageForItem:(TabResumptionItem*)item {
if ([self isPendingItem:item]) {
// The item was already fetched or is being fetched, ignore it.
return;
}
_pendingItem = item;
if (ShouldShowItemImmediately()) {
[self showItem:item];
}
if (item.itemType == kMostRecentTab) {
[self fetchSnapshotForItem:item];
} else {
[self fetchSalientImageForItem:item];
}
[self fetchFaviconForItem:item];
}
// Fetches the snapshot of the tab showing `item`.
- (void)fetchSnapshotForItem:(TabResumptionItem*)item {
if (!IsTabResumption1_5ThumbnailsImageEnabled()) {
return [self fetchSalientImageForItem:item];
}
BrowserList* browserList =
BrowserListFactory::GetForBrowserState(_browser->GetBrowserState());
for (Browser* browser : browserList->BrowsersOfType(
BrowserList::BrowserType::kRegularAndInactive)) {
WebStateList* const webStateList = browser->GetWebStateList();
const int index = webStateList->GetIndexOfWebStateWithURL(item.tabURL);
if (index == WebStateList::kInvalidIndex) {
continue;
}
web::WebState* webState = webStateList->GetWebStateAt(index);
if (!webState) {
continue;
}
__weak TabResumptionMediator* weakSelf = self;
SnapshotTabHelper* snapshotTabHelper =
SnapshotTabHelper::FromWebState(webState);
snapshotTabHelper->RetrieveColorSnapshot(^(UIImage* image) {
[weakSelf snapshotFetched:image forItem:item];
});
return;
}
return [self fetchSalientImageForItem:item];
}
// The snapshot of the tab showing `item` was fetched.
- (void)snapshotFetched:(UIImage*)image forItem:(TabResumptionItem*)item {
if (!image) {
return [self fetchSalientImageForItem:item];
}
item.contentImage = image;
[self showItem:item];
}
// Fetches the salient image for `item`.
- (void)fetchSalientImageForItem:(TabResumptionItem*)item {
if (!IsTabResumption1_5SalientImageEnabled() || !_pageImageService ||
!base::FeatureList::IsEnabled(page_image_service::kImageService)) {
return;
}
__weak TabResumptionMediator* weakSelf = self;
page_image_service::mojom::Options options;
options.optimization_guide_images = true;
options.suggest_images = false;
_pageImageService->FetchImageFor(
page_image_service::mojom::ClientId::NtpTabResumption, item.tabURL,
options, base::BindOnce(^(const GURL& URL) {
[weakSelf salientImageURLReceived:URL forItem:item];
}));
}
// The URL for the salient image has been received. Download the image if it
// is valid or fallbacks to favicon.
- (void)salientImageURLReceived:(const GURL&)URL
forItem:(TabResumptionItem*)item {
__weak TabResumptionMediator* weakSelf = self;
if (!URL.is_valid() || !URL.SchemeIsCryptographic() ||
!base::EndsWith(URL.host(), kGStatic)) {
return;
}
_imageFetcher->FetchImageData(
URL,
base::BindOnce(^(const std::string& imageData,
const image_fetcher::RequestMetadata& metadata) {
[weakSelf salientImageReceived:imageData forItem:item];
}),
NO_TRAFFIC_ANNOTATION_YET);
}
// Salient image has been received. Display it.
- (void)salientImageReceived:(const std::string&)imageData
forItem:(TabResumptionItem*)item {
UIImage* image =
[UIImage imageWithData:[NSData dataWithBytes:imageData.c_str()
length:imageData.size()]];
if (!image) {
return;
}
item.contentImage = image;
[self showItem:item];
}
// Fetches the favicon for `item`.
- (void)fetchFaviconForItem:(TabResumptionItem*)item {
__weak TabResumptionMediator* weakSelf = self;
_faviconLoader->FaviconForPageUrl(
item.tabURL, kDesiredSmallFaviconSizePt, kMinFaviconSizePt,
/*fallback_to_google_server=*/true, ^(FaviconAttributes* attributes) {
[weakSelf faviconReceived:attributes forItem:item];
});
}
// The favicon has been received. Display it.
- (void)faviconReceived:(FaviconAttributes*)attributes
forItem:(TabResumptionItem*)item {
if (!attributes.usesDefaultImage) {
if ([UIImagePNGRepresentation(item.faviconImage)
isEqual:UIImagePNGRepresentation(attributes.faviconImage)]) {
return;
}
item.faviconImage = attributes.faviconImage;
[self showItem:item];
}
}
// Sends `item` to TabResumption to be displayed.
- (void)showItem:(TabResumptionItem*)item {
if (![self isPendingItem:item]) {
// A new item has been fetched, ignore.
return;
}
if (!self.itemConfig) {
self.itemConfig = item;
[self.delegate tabResumptionHelperDidReceiveItem];
return;
}
if (IsTabResumption2_0Enabled() && _currentlyTopModule &&
_visitedURLRankingService) {
// If the item is currently displayed, report the display of the new URL.
if (item.requestID != self.itemConfig.requestID ||
item.URLKey != self.itemConfig.URLKey) {
_visitedURLRankingService->RecordAction(visited_url_ranking::kSeen,
self.itemConfig.URLKey,
self.itemConfig.requestID);
}
}
// The item is already used by some view, so it cannot be replaced.
// Instead the existing config must be updated.
[self.itemConfig reconfigureWithItem:item];
[self.delegate tabResumptionHelperDidReconfigureItem];
}
// Creates a TabResumptionItem corresponding to the last synced tab.
- (void)fetchLastSyncedTabItemFromLastActiveDistantTab:
(const synced_sessions::DistantTab*)tab
session:(const synced_sessions::
DistantSession*)
session {
CHECK(!IsTabResumptionEnabledForMostRecentTabOnly());
TabResumptionItem* item = [[TabResumptionItem alloc]
initWithItemType:TabResumptionItemType::kLastSyncedTab];
item.sessionName = base::SysUTF8ToNSString(session->name);
item.tabTitle = base::SysUTF16ToNSString(tab->title);
item.syncedTime = tab->last_active_time;
item.tabURL = tab->virtual_url;
item.commandHandler = self;
item.delegate = self;
item.shouldShowSeeMore = IsTabResumption1_5SeeMoreEnabled();
// Fetch the image.
[self fetchImageForItem:item];
}
// Creates a TabResumptionItem corresponding to the `webState`.
- (void)fetchMostRecentTabItemFromWebState:(web::WebState*)webState
openedTime:(base::Time)openedTime {
TabResumptionItem* item = [[TabResumptionItem alloc]
initWithItemType:TabResumptionItemType::kMostRecentTab];
item.tabTitle = base::SysUTF16ToNSString(webState->GetTitle());
item.syncedTime = openedTime;
item.tabURL = webState->GetLastCommittedURL();
item.commandHandler = self;
item.delegate = self;
item.shouldShowSeeMore = IsTabResumption1_5SeeMoreEnabled();
// Fetch the image.
[self fetchImageForItem:item];
}
// Compares `item` and `_pendingItem` on tabURL and tabTitle field.
- (BOOL)isPendingItem:(TabResumptionItem*)item {
if (_pendingItem == nil) {
return NO;
}
return item.tabURL == _pendingItem.tabURL &&
[item.tabTitle isEqualToString:_pendingItem.tabTitle];
}
#pragma mark - Private method for Tab resumption 2.0 tab fetch.
// Called when the URLs have been fetched from the different fetcher.
// This method just forwards the URLs to the ranker.
- (void)onURLFetched:(std::vector<visited_url_ranking::URLVisitAggregate>)URLs
withStatus:(visited_url_ranking::ResultStatus)status {
if (status != visited_url_ranking::ResultStatus::kSuccess) {
return;
}
__weak __typeof(self) weakSelf = self;
visited_url_ranking::Config config = {
.key = visited_url_ranking::kTabResumptionRankerKey};
_visitedURLRankingService->RankURLVisitAggregates(
config, std::move(URLs),
base::BindOnce(
^(visited_url_ranking::ResultStatus rankStatus,
std::vector<visited_url_ranking::URLVisitAggregate> rankedURLs) {
[weakSelf onURLRanked:std::move(rankedURLs) withStatus:rankStatus];
}));
}
// Called when the URLs have been ranked. Pass the first one to MagicStack.
- (void)onURLRanked:(std::vector<visited_url_ranking::URLVisitAggregate>)URLs
withStatus:(visited_url_ranking::ResultStatus)status {
if (status != visited_url_ranking::ResultStatus::kSuccess ||
URLs.size() == 0) {
return;
}
const visited_url_ranking::URLVisitAggregate::TabData* tabData = nullptr;
const visited_url_ranking::URLVisitAggregate* URLAggregate = nullptr;
for (auto& aggregate : URLs) {
tabData = ExtractTabData(aggregate);
if (tabData) {
URLAggregate = &aggregate;
break;
}
}
if (!tabData || !URLAggregate) {
return;
}
const visited_url_ranking::URLVisitAggregate::Tab& tab =
tabData->last_active_tab;
bool isLocal =
tab.visit.source == visited_url_ranking::URLVisit::Source::kLocal;
TabResumptionItemType type =
(isLocal ? TabResumptionItemType::kMostRecentTab
: TabResumptionItemType::kLastSyncedTab);
TabResumptionItem* item = [[TabResumptionItem alloc] initWithItemType:type];
item.tabTitle = base::SysUTF16ToNSString(tab.visit.title);
item.syncedTime = tab.visit.last_modified;
item.tabURL = tab.visit.url;
item.shouldShowSeeMore = IsTabResumption1_5SeeMoreEnabled();
item.URLKey = URLAggregate->url_key;
item.requestID = URLAggregate->request_id;
item.commandHandler = self;
item.delegate = self;
if (IsTabResumption2BubbleEnabled()) {
// Dummy reason for now as TR2 does not return one.
// TODO(crbug.com/342389622): put the correct reason.
item.reason = @"TEST TR2 REASON.";
}
if (tab.id > 0 && tab.session_tag && !isLocal) {
item.sessionName = base::SysUTF8ToNSString(tab.session_name.value());
_sessionTag = tab.session_tag.value();
_tabId = SessionID::FromSerializedValue(tab.id);
}
// Fetch the favicon.
[self fetchImageForItem:item];
}
@end