blob: 48ef318c7962a6d8a794c769b3d0a22221daf7d2 [file] [log] [blame]
// Copyright 2012 The Chromium Authors. All rights reserved.
// 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/tab_model.h"
#include <cstdint>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#import "base/mac/foundation_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics_action.h"
#include "base/stl_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/task/cancelable_task_tracker.h"
#include "base/task/post_task.h"
#include "components/favicon/ios/web_favicon_driver.h"
#include "components/navigation_metrics/navigation_metrics.h"
#include "components/sessions/core/serialized_navigation_entry.h"
#include "components/sessions/core/session_id.h"
#include "components/sessions/core/tab_restore_service.h"
#include "components/sessions/ios/ios_live_tab.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#include "ios/chrome/browser/chrome_url_constants.h"
#import "ios/chrome/browser/chrome_url_util.h"
#include "ios/chrome/browser/crash_loop_detection_util.h"
#import "ios/chrome/browser/geolocation/omnibox_geolocation_controller.h"
#import "ios/chrome/browser/metrics/tab_usage_recorder.h"
#import "ios/chrome/browser/prerender/prerender_service_factory.h"
#include "ios/chrome/browser/sessions/ios_chrome_tab_restore_service_factory.h"
#import "ios/chrome/browser/sessions/session_ios.h"
#import "ios/chrome/browser/sessions/session_service_ios.h"
#import "ios/chrome/browser/sessions/session_window_ios.h"
#import "ios/chrome/browser/snapshots/snapshot_cache.h"
#import "ios/chrome/browser/snapshots/snapshot_cache_factory.h"
#import "ios/chrome/browser/snapshots/snapshot_cache_web_state_list_observer.h"
#include "ios/chrome/browser/tab_parenting_global_observer.h"
#import "ios/chrome/browser/tabs/legacy_tab_helper.h"
#import "ios/chrome/browser/tabs/tab.h"
#import "ios/chrome/browser/tabs/tab_model_closing_web_state_observer.h"
#import "ios/chrome/browser/tabs/tab_model_favicon_driver_observer.h"
#import "ios/chrome/browser/tabs/tab_model_list.h"
#import "ios/chrome/browser/tabs/tab_model_notification_observer.h"
#import "ios/chrome/browser/tabs/tab_model_observers.h"
#import "ios/chrome/browser/tabs/tab_model_observers_bridge.h"
#import "ios/chrome/browser/tabs/tab_model_selected_tab_observer.h"
#import "ios/chrome/browser/tabs/tab_model_synced_window_delegate.h"
#import "ios/chrome/browser/tabs/tab_model_web_state_list_delegate.h"
#import "ios/chrome/browser/tabs/tab_parenting_observer.h"
#import "ios/chrome/browser/web/page_placeholder_tab_helper.h"
#import "ios/chrome/browser/web_state_list/web_state_list.h"
#import "ios/chrome/browser/web_state_list/web_state_list_metrics_observer.h"
#import "ios/chrome/browser/web_state_list/web_state_list_observer.h"
#import "ios/chrome/browser/web_state_list/web_state_list_serialization.h"
#import "ios/chrome/browser/web_state_list/web_state_opener.h"
#import "ios/chrome/browser/web_state_list/web_usage_enabler/web_state_list_web_usage_enabler.h"
#import "ios/chrome/browser/web_state_list/web_usage_enabler/web_state_list_web_usage_enabler_factory.h"
#include "ios/web/public/browser_state.h"
#include "ios/web/public/certificate_policy_cache.h"
#import "ios/web/public/load_committed_details.h"
#include "ios/web/public/navigation_item.h"
#import "ios/web/public/navigation_manager.h"
#import "ios/web/public/serializable_user_data_manager.h"
#import "ios/web/public/web_state/navigation_context.h"
#include "ios/web/public/web_state/session_certificate_policy_cache.h"
#import "ios/web/public/web_state/web_state_observer_bridge.h"
#include "ios/web/public/web_task_traits.h"
#include "ios/web/public/web_thread.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Updates CRWSessionCertificatePolicyManager's certificate policy cache.
void UpdateCertificatePolicyCacheFromWebState(
const scoped_refptr<web::CertificatePolicyCache>& policy_cache,
const web::WebState* web_state) {
DCHECK(web_state);
DCHECK_CURRENTLY_ON(web::WebThread::UI);
web_state->GetSessionCertificatePolicyCache()->UpdateCertificatePolicyCache(
policy_cache);
}
// Populates the certificate policy cache based on the WebStates of
// |web_state_list|.
void RestoreCertificatePolicyCacheFromModel(
const scoped_refptr<web::CertificatePolicyCache>& policy_cache,
WebStateList* web_state_list) {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
for (int index = 0; index < web_state_list->count(); ++index) {
UpdateCertificatePolicyCacheFromWebState(
policy_cache, web_state_list->GetWebStateAt(index));
}
}
// Scrubs the certificate policy cache of all certificates policies except
// those for the current entries in |web_state_list|.
void CleanCertificatePolicyCache(
base::CancelableTaskTracker* task_tracker,
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
const scoped_refptr<web::CertificatePolicyCache>& policy_cache,
WebStateList* web_state_list) {
DCHECK(policy_cache);
DCHECK(web_state_list);
DCHECK_CURRENTLY_ON(web::WebThread::UI);
task_tracker->PostTaskAndReply(
task_runner.get(), FROM_HERE,
base::Bind(&web::CertificatePolicyCache::ClearCertificatePolicies,
policy_cache),
base::Bind(&RestoreCertificatePolicyCacheFromModel, policy_cache,
base::Unretained(web_state_list)));
}
// Returns whether |rhs| and |lhs| are different user agent types. If either
// of them is web::UserAgentType::NONE, then return NO.
BOOL IsTransitionBetweenDesktopAndMobileUserAgent(web::UserAgentType lhs,
web::UserAgentType rhs) {
if (lhs == web::UserAgentType::NONE)
return NO;
if (rhs == web::UserAgentType::NONE)
return NO;
return lhs != rhs;
}
// Returns whether TabUsageRecorder::RecordPageLoadStart should be called for
// the given navigation.
BOOL ShouldRecordPageLoadStartForNavigation(
web::NavigationContext* navigation) {
web::NavigationManager* navigation_manager =
navigation->GetWebState()->GetNavigationManager();
web::NavigationItem* last_committed_item =
navigation_manager->GetLastCommittedItem();
if (!last_committed_item) {
// Opening a child window and loading URL there.
// http://crbug.com/773160
return NO;
}
web::NavigationItem* pending_item = navigation_manager->GetPendingItem();
if (pending_item) {
if (IsTransitionBetweenDesktopAndMobileUserAgent(
pending_item->GetUserAgentType(),
last_committed_item->GetUserAgentType())) {
// Switching between Desktop and Mobile user agent.
return NO;
}
}
ui::PageTransition transition = navigation->GetPageTransition();
if (!ui::PageTransitionIsNewNavigation(transition)) {
// Back/forward navigation or reload.
return NO;
}
if ((transition & ui::PAGE_TRANSITION_CLIENT_REDIRECT) != 0) {
// Client redirect.
return NO;
}
static const ui::PageTransition kRecordedPageTransitionTypes[] = {
ui::PAGE_TRANSITION_TYPED,
ui::PAGE_TRANSITION_LINK,
ui::PAGE_TRANSITION_GENERATED,
ui::PAGE_TRANSITION_AUTO_BOOKMARK,
ui::PAGE_TRANSITION_FORM_SUBMIT,
ui::PAGE_TRANSITION_KEYWORD,
ui::PAGE_TRANSITION_KEYWORD_GENERATED,
};
for (size_t i = 0; i < base::size(kRecordedPageTransitionTypes); ++i) {
const ui::PageTransition recorded_type = kRecordedPageTransitionTypes[i];
if (ui::PageTransitionCoreTypeIs(transition, recorded_type)) {
return YES;
}
}
return NO;
}
// Records metrics for the interface's orientation.
void RecordInterfaceOrientationMetric() {
switch ([[UIApplication sharedApplication] statusBarOrientation]) {
case UIInterfaceOrientationPortrait:
case UIInterfaceOrientationPortraitUpsideDown:
UMA_HISTOGRAM_BOOLEAN("Tab.PageLoadInPortrait", YES);
break;
case UIInterfaceOrientationLandscapeLeft:
case UIInterfaceOrientationLandscapeRight:
UMA_HISTOGRAM_BOOLEAN("Tab.PageLoadInPortrait", NO);
break;
case UIInterfaceOrientationUnknown:
// TODO(crbug.com/228832): Convert from a boolean histogram to an
// enumerated histogram and log this case as well.
break;
}
}
// Records metrics for main frame navigation.
void RecordMainFrameNavigationMetric(web::WebState* web_state) {
DCHECK(web_state);
DCHECK(web_state->GetBrowserState());
DCHECK(web_state->GetNavigationManager());
web::NavigationItem* item =
web_state->GetNavigationManager()->GetLastCommittedItem();
navigation_metrics::RecordMainFrameNavigation(
item ? item->GetVirtualURL() : GURL::EmptyGURL(), true,
web_state->GetBrowserState()->IsOffTheRecord());
}
} // anonymous namespace
@interface TabModel ()<CRWWebStateObserver, WebStateListObserving> {
// Delegate for the WebStateList.
std::unique_ptr<WebStateListDelegate> _webStateListDelegate;
// Underlying shared model implementation.
std::unique_ptr<WebStateList> _webStateList;
// WebStateListObservers reacting to modifications of the model (may send
// notification, translate and forward events, update metrics, ...).
std::vector<std::unique_ptr<WebStateListObserver>> _webStateListObservers;
// Strong references to id<WebStateListObserving> wrapped by non-owning
// WebStateListObserverBridges.
NSArray<id<WebStateListObserving>>* _retainedWebStateListObservers;
// The delegate for sync.
std::unique_ptr<TabModelSyncedWindowDelegate> _syncedWindowDelegate;
// The observer that calls -notifyNewTabWillOpen on this object.
TabModelNotificationObserver* _tabModelNotificationObserver;
// Counters for metrics.
WebStateListMetricsObserver* _webStateListMetricsObserver;
// Backs up property with the same name.
std::unique_ptr<TabUsageRecorder> _tabUsageRecorder;
// Saves session's state.
SessionServiceIOS* _sessionService;
// List of TabModelObservers.
TabModelObservers* _observers;
// Used to ensure thread-safety of the certificate policy management code.
base::CancelableTaskTracker _clearPoliciesTaskTracker;
// Used to observe owned Tabs' WebStates.
std::unique_ptr<web::WebStateObserver> _webStateObserver;
}
// Session window for the contents of the tab model.
@property(nonatomic, readonly) SessionIOS* sessionForSaving;
// Whether the underlying WebStateList's web usage is enabled.
@property(nonatomic, readonly, getter=isWebUsageEnabled) BOOL webUsageEnabled;
// Helper method to restore a saved session and control if the state should
// be persisted or not. Used to implement the public -restoreSessionWindow:
// method and restoring session in the initialiser.
- (BOOL)restoreSessionWindow:(SessionWindowIOS*)window
persistState:(BOOL)persistState;
@end
@implementation TabModel
@synthesize browserState = _browserState;
#pragma mark - Overriden
- (void)dealloc {
// browserStateDestroyed should always have been called before destruction.
DCHECK(!_browserState);
// Make sure the observers do clean after themselves.
DCHECK([_observers empty]);
}
#pragma mark - Public methods
- (Tab*)currentTab {
web::WebState* webState = _webStateList->GetActiveWebState();
return webState ? LegacyTabHelper::GetTabForWebState(webState) : nil;
}
- (void)setCurrentTab:(Tab*)newTab {
int indexOfTab = _webStateList->GetIndexOfWebState(newTab.webState);
DCHECK_NE(indexOfTab, WebStateList::kInvalidIndex);
_webStateList->ActivateWebStateAt(indexOfTab);
}
- (TabModelSyncedWindowDelegate*)syncedWindowDelegate {
return _syncedWindowDelegate.get();
}
- (TabUsageRecorder*)tabUsageRecorder {
return _tabUsageRecorder.get();
}
- (BOOL)isOffTheRecord {
return _browserState && _browserState->IsOffTheRecord();
}
- (BOOL)isEmpty {
return _webStateList->empty();
}
- (NSUInteger)count {
DCHECK_GE(_webStateList->count(), 0);
return static_cast<NSUInteger>(_webStateList->count());
}
- (WebStateList*)webStateList {
return _webStateList.get();
}
- (instancetype)initWithSessionWindow:(SessionWindowIOS*)window
sessionService:(SessionServiceIOS*)service
browserState:(ios::ChromeBrowserState*)browserState {
if ((self = [super init])) {
_observers = [TabModelObservers observers];
_webStateListDelegate =
std::make_unique<TabModelWebStateListDelegate>(self);
_webStateList = std::make_unique<WebStateList>(_webStateListDelegate.get());
_browserState = browserState;
DCHECK(_browserState);
_webStateObserver = std::make_unique<web::WebStateObserverBridge>(self);
// Normal browser states are the only ones to get tab restore. Tab sync
// handles incognito browser states by filtering on profile, so it's
// important to the backend code to always have a sync window delegate.
if (!_browserState->IsOffTheRecord()) {
// Set up the usage recorder before tabs are created.
_tabUsageRecorder = std::make_unique<TabUsageRecorder>(
_webStateList.get(),
PrerenderServiceFactory::GetForBrowserState(browserState));
}
_syncedWindowDelegate =
std::make_unique<TabModelSyncedWindowDelegate>(_webStateList.get());
// There must be a valid session service defined to consume session windows.
DCHECK(service);
_sessionService = service;
NSMutableArray<id<WebStateListObserving>>* retainedWebStateListObservers =
[[NSMutableArray alloc] init];
TabModelClosingWebStateObserver* tabModelClosingWebStateObserver = [
[TabModelClosingWebStateObserver alloc]
initWithTabModel:self
restoreService:IOSChromeTabRestoreServiceFactory::GetForBrowserState(
_browserState)];
[retainedWebStateListObservers addObject:tabModelClosingWebStateObserver];
_webStateListObservers.push_back(
std::make_unique<WebStateListObserverBridge>(self));
_webStateListObservers.push_back(
std::make_unique<WebStateListObserverBridge>(
tabModelClosingWebStateObserver));
SnapshotCache* snapshotCache =
SnapshotCacheFactory::GetForBrowserState(_browserState);
if (snapshotCache) {
_webStateListObservers.push_back(
std::make_unique<SnapshotCacheWebStateListObserver>(snapshotCache));
}
_webStateListObservers.push_back(std::make_unique<TabParentingObserver>());
TabModelSelectedTabObserver* tabModelSelectedTabObserver =
[[TabModelSelectedTabObserver alloc] initWithTabModel:self];
[retainedWebStateListObservers addObject:tabModelSelectedTabObserver];
_webStateListObservers.push_back(
std::make_unique<WebStateListObserverBridge>(
tabModelSelectedTabObserver));
TabModelObserversBridge* tabModelObserversBridge =
[[TabModelObserversBridge alloc] initWithTabModel:self
tabModelObservers:_observers];
[retainedWebStateListObservers addObject:tabModelObserversBridge];
_webStateListObservers.push_back(
std::make_unique<WebStateListObserverBridge>(tabModelObserversBridge));
_webStateListObservers.push_back(
std::make_unique<TabModelFaviconDriverObserver>(self, _observers));
auto webStateListMetricsObserver =
std::make_unique<WebStateListMetricsObserver>();
_webStateListMetricsObserver = webStateListMetricsObserver.get();
_webStateListObservers.push_back(std::move(webStateListMetricsObserver));
auto tabModelNotificationObserver =
std::make_unique<TabModelNotificationObserver>(self);
_tabModelNotificationObserver = tabModelNotificationObserver.get();
_webStateListObservers.push_back(std::move(tabModelNotificationObserver));
for (const auto& webStateListObserver : _webStateListObservers)
_webStateList->AddObserver(webStateListObserver.get());
_retainedWebStateListObservers = [retainedWebStateListObservers copy];
if (window) {
DCHECK([_observers empty]);
// Restore the session and reset the session metrics (as the event have
// not been generated by the user but by a cold start cycle).
[self restoreSessionWindow:window persistState:NO];
[self resetSessionMetrics];
}
// Register for resign active notification.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(willResignActive:)
name:UIApplicationWillResignActiveNotification
object:nil];
// Register for background notification.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
// Associate with ios::ChromeBrowserState.
TabModelList::RegisterTabModelWithChromeBrowserState(_browserState, self);
}
return self;
}
- (BOOL)restoreSessionWindow:(SessionWindowIOS*)window {
return [self restoreSessionWindow:window persistState:YES];
}
- (void)saveSessionImmediately:(BOOL)immediately {
// Do nothing if there are tabs in the model but no selected tab. This is
// a transitional state.
if ((!self.currentTab && _webStateList->count()) || !_browserState)
return;
NSString* statePath =
base::SysUTF8ToNSString(_browserState->GetStatePath().AsUTF8Unsafe());
__weak TabModel* weakSelf = self;
SessionIOSFactory sessionFactory = ^{
return weakSelf.sessionForSaving;
};
[_sessionService saveSession:sessionFactory
directory:statePath
immediately:immediately];
}
- (Tab*)tabAtIndex:(NSUInteger)index {
DCHECK_LE(index, static_cast<NSUInteger>(INT_MAX));
return LegacyTabHelper::GetTabForWebState(
_webStateList->GetWebStateAt(static_cast<int>(index)));
}
- (NSUInteger)indexOfTab:(Tab*)tab {
int index = _webStateList->GetIndexOfWebState(tab.webState);
if (index == WebStateList::kInvalidIndex)
return NSNotFound;
DCHECK_GE(index, 0);
return static_cast<NSUInteger>(index);
}
- (Tab*)insertTabWithURL:(const GURL&)URL
referrer:(const web::Referrer&)referrer
transition:(ui::PageTransition)transition
opener:(Tab*)parentTab
openedByDOM:(BOOL)openedByDOM
atIndex:(NSUInteger)index
inBackground:(BOOL)inBackground {
web::NavigationManager::WebLoadParams params(URL);
params.referrer = referrer;
params.transition_type = transition;
return [self insertTabWithLoadParams:params
opener:parentTab
openedByDOM:openedByDOM
atIndex:index
inBackground:inBackground];
}
- (Tab*)insertOpenByDOMTabWithOpener:(Tab*)opener {
DCHECK(_browserState);
web::WebState::CreateParams createParams(_browserState);
createParams.created_with_opener = YES;
std::unique_ptr<web::WebState> webState = web::WebState::Create(createParams);
int insertionFlags =
WebStateList::INSERT_FORCE_INDEX | WebStateList::INSERT_ACTIVATE;
int insertedIndex = _webStateList->InsertWebState(
_webStateList->count(), std::move(webState), insertionFlags,
WebStateOpener(opener.webState));
return LegacyTabHelper::GetTabForWebState(
_webStateList->GetWebStateAt(insertedIndex));
}
- (Tab*)insertTabWithLoadParams:
(const web::NavigationManager::WebLoadParams&)loadParams
opener:(Tab*)parentTab
openedByDOM:(BOOL)openedByDOM
atIndex:(NSUInteger)index
inBackground:(BOOL)inBackground {
DCHECK(_browserState);
int insertionIndex = WebStateList::kInvalidIndex;
int insertionFlags = WebStateList::INSERT_NO_FLAGS;
if (index != TabModelConstants::kTabPositionAutomatically) {
DCHECK_LE(index, static_cast<NSUInteger>(INT_MAX));
insertionIndex = static_cast<int>(index);
insertionFlags |= WebStateList::INSERT_FORCE_INDEX;
} else if (!ui::PageTransitionCoreTypeIs(loadParams.transition_type,
ui::PAGE_TRANSITION_LINK)) {
insertionIndex = _webStateList->count();
insertionFlags |= WebStateList::INSERT_FORCE_INDEX;
}
if (!inBackground) {
insertionFlags |= WebStateList::INSERT_ACTIVATE;
}
web::WebState::CreateParams createParams(self.browserState);
createParams.created_with_opener = openedByDOM;
std::unique_ptr<web::WebState> webState = web::WebState::Create(createParams);
webState->GetNavigationManager()->LoadURLWithParams(loadParams);
insertionIndex = _webStateList->InsertWebState(
insertionIndex, std::move(webState), insertionFlags,
WebStateOpener(parentTab.webState));
return LegacyTabHelper::GetTabForWebState(
_webStateList->GetWebStateAt(insertionIndex));
}
- (void)moveTab:(Tab*)tab toIndex:(NSUInteger)toIndex {
DCHECK_LE(toIndex, static_cast<NSUInteger>(INT_MAX));
int fromIndex = _webStateList->GetIndexOfWebState(tab.webState);
_webStateList->MoveWebStateAt(fromIndex, static_cast<int>(toIndex));
}
- (void)closeTabAtIndex:(NSUInteger)index {
DCHECK_LE(index, static_cast<NSUInteger>(INT_MAX));
_webStateList->CloseWebStateAt(static_cast<int>(index),
WebStateList::CLOSE_USER_ACTION);
}
- (void)closeTab:(Tab*)tab {
[self closeTabAtIndex:[self indexOfTab:tab]];
}
- (void)closeAllTabs {
_webStateList->CloseAllWebStates(WebStateList::CLOSE_USER_ACTION);
[_observers tabModelClosedAllTabs:self];
}
- (void)haltAllTabs {
for (int index = 0; index < _webStateList->count(); ++index) {
web::WebState* webState = _webStateList->GetWebStateAt(index);
Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
[tab terminateNetworkActivity];
}
}
- (void)notifyTabChanged:(Tab*)tab {
[_observers tabModel:self didChangeTab:tab];
}
- (void)notifyTabLoading:(Tab*)tab {
[_observers tabModel:self willStartLoadingTab:tab];
[self notifyTabChanged:tab];
[_observers tabModel:self didStartLoadingTab:tab];
}
- (void)notifyTabFinishedLoading:(Tab*)tab success:(BOOL)success {
[self notifyTabChanged:tab];
[_observers tabModel:self didFinishLoadingTab:tab success:success];
}
- (void)notifyNewTabWillOpen:(Tab*)tab inBackground:(BOOL)background {
[_observers tabModel:self newTabWillOpen:tab inBackground:background];
}
- (void)notifyTabWasDeselected:(Tab*)tab {
[_observers tabModel:self didDeselectTab:tab];
}
- (void)addObserver:(id<TabModelObserver>)observer {
[_observers addObserver:observer];
}
- (void)removeObserver:(id<TabModelObserver>)observer {
[_observers removeObserver:observer];
}
- (void)resetSessionMetrics {
if (_webStateListMetricsObserver)
_webStateListMetricsObserver->ResetSessionMetrics();
}
- (void)recordSessionMetrics {
if (_webStateListMetricsObserver)
_webStateListMetricsObserver->RecordSessionMetrics();
}
- (void)notifyTabSnapshotChanged:(Tab*)tab withImage:(UIImage*)image {
DCHECK([NSThread isMainThread]);
[_observers tabModel:self didChangeTabSnapshot:tab withImage:image];
}
- (void)setPrimary:(BOOL)primary {
if (_tabUsageRecorder) {
_tabUsageRecorder->RecordPrimaryTabModelChange(primary,
self.currentTab.webState);
}
}
- (NSSet*)currentlyReferencedExternalFiles {
NSMutableSet* referencedFiles = [NSMutableSet set];
if (!_browserState)
return referencedFiles;
// Check the currently open tabs for external files.
for (int index = 0; index < _webStateList->count(); ++index) {
web::WebState* webState = _webStateList->GetWebStateAt(index);
const GURL& lastCommittedURL = webState->GetLastCommittedURL();
if (UrlIsExternalFileReference(lastCommittedURL)) {
[referencedFiles addObject:base::SysUTF8ToNSString(
lastCommittedURL.ExtractFileName())];
}
web::NavigationItem* pendingItem =
webState->GetNavigationManager()->GetPendingItem();
if (pendingItem && UrlIsExternalFileReference(pendingItem->GetURL())) {
[referencedFiles addObject:base::SysUTF8ToNSString(
pendingItem->GetURL().ExtractFileName())];
}
}
// Do the same for the recently closed tabs.
sessions::TabRestoreService* restoreService =
IOSChromeTabRestoreServiceFactory::GetForBrowserState(_browserState);
DCHECK(restoreService);
for (const auto& entry : restoreService->entries()) {
sessions::TabRestoreService::Tab* tab =
static_cast<sessions::TabRestoreService::Tab*>(entry.get());
int navigationIndex = tab->current_navigation_index;
sessions::SerializedNavigationEntry navigation =
tab->navigations[navigationIndex];
GURL URL = navigation.virtual_url();
if (UrlIsExternalFileReference(URL)) {
NSString* fileName = base::SysUTF8ToNSString(URL.ExtractFileName());
[referencedFiles addObject:fileName];
}
}
return referencedFiles;
}
// NOTE: This can be called multiple times, so must be robust against that.
- (void)browserStateDestroyed {
if (!_browserState)
return;
[[NSNotificationCenter defaultCenter] removeObserver:self];
TabModelList::UnregisterTabModelFromChromeBrowserState(_browserState, self);
_browserState = nullptr;
// Clear weak pointer to observers before destroying them.
_tabModelNotificationObserver = nullptr;
_webStateListMetricsObserver = nullptr;
// Close all tabs. Do this in an @autoreleasepool as WebStateList observers
// will be notified (they are unregistered later). As some of them may be
// implemented in Objective-C and unregister themselves in their -dealloc
// method, ensure they -autorelease introduced by ARC are processed before
// the WebStateList destructor is called.
@autoreleasepool {
_webStateList->CloseAllWebStates(WebStateList::CLOSE_NO_FLAGS);
}
// Unregister all observers after closing all the tabs as some of them are
// required to properly clean up the Tabs.
for (const auto& webStateListObserver : _webStateListObservers)
_webStateList->RemoveObserver(webStateListObserver.get());
_webStateListObservers.clear();
_retainedWebStateListObservers = nil;
_clearPoliciesTaskTracker.TryCancelAll();
_tabUsageRecorder.reset();
_webStateObserver.reset();
}
- (void)navigationCommittedInTab:(Tab*)tab
previousItem:(web::NavigationItem*)previousItem {
if (self.offTheRecord)
return;
if (![tab navigationManager])
return;
// See if the navigation was within a page; if so ignore it.
if (previousItem) {
GURL previousURL = previousItem->GetURL();
GURL currentURL = [tab navigationManager]->GetVisibleItem()->GetURL();
url::Replacements<char> replacements;
replacements.ClearRef();
if (previousURL.ReplaceComponents(replacements) ==
currentURL.ReplaceComponents(replacements)) {
return;
}
}
int tabCount = static_cast<int>(self.count);
UMA_HISTOGRAM_CUSTOM_COUNTS("Tabs.TabCountPerLoad", tabCount, 1, 200, 50);
}
#pragma mark - Private methods
- (SessionIOS*)sessionForSaving {
// Build the array of sessions. Copy the session objects as the saving will
// be done on a separate thread.
// TODO(crbug.com/661986): This could get expensive especially since this
// window may never be saved (if another call comes in before the delay).
return [[SessionIOS alloc]
initWithWindows:@[ SerializeWebStateList(_webStateList.get()) ]];
}
- (BOOL)isWebUsageEnabled {
DCHECK(_browserState);
return WebStateListWebUsageEnablerFactory::GetInstance()
->GetForBrowserState(_browserState)
->IsWebUsageEnabled();
}
- (BOOL)restoreSessionWindow:(SessionWindowIOS*)window
persistState:(BOOL)persistState {
DCHECK(_browserState);
DCHECK(window);
// Disable calling -notifyNewTabWillOpen: while restoring a session as it
// breaks the BVC (see crbug.com/763964).
base::ScopedClosureRunner enableTabModelNotificationObserver;
if (_tabModelNotificationObserver) {
_tabModelNotificationObserver->SetDisabled(true);
enableTabModelNotificationObserver.ReplaceClosure(
base::BindOnce(&TabModelNotificationObserver::SetDisabled,
base::Unretained(_tabModelNotificationObserver), false));
}
if (!window.sessions.count)
return NO;
int oldCount = _webStateList->count();
DCHECK_GE(oldCount, 0);
web::WebState::CreateParams createParams(_browserState);
DeserializeWebStateList(
_webStateList.get(), window,
base::BindRepeating(&web::WebState::CreateWithStorageSession,
createParams));
DCHECK_GT(_webStateList->count(), oldCount);
int restoredCount = _webStateList->count() - oldCount;
DCHECK_EQ(window.sessions.count, static_cast<NSUInteger>(restoredCount));
scoped_refptr<web::CertificatePolicyCache> policyCache =
web::BrowserState::GetCertificatePolicyCache(_browserState);
std::vector<web::WebState*> restoredWebStates;
if (_tabUsageRecorder)
restoredWebStates.reserve(window.sessions.count);
for (int index = oldCount; index < _webStateList->count(); ++index) {
web::WebState* webState = _webStateList->GetWebStateAt(index);
web::NavigationItem* visible_item =
webState->GetNavigationManager()->GetVisibleItem();
if (!(visible_item &&
visible_item->GetVirtualURL() == kChromeUINewTabURL)) {
PagePlaceholderTabHelper::FromWebState(webState)
->AddPlaceholderForNextNavigation();
}
if (visible_item && visible_item->GetVirtualURL().is_valid()) {
favicon::WebFaviconDriver::FromWebState(webState)->FetchFavicon(
visible_item->GetVirtualURL(), /*is_same_document=*/false);
}
// Restore the CertificatePolicyCache (note that webState is invalid after
// passing it via move semantic to -initWithWebState:model:).
UpdateCertificatePolicyCacheFromWebState(policyCache, webState);
if (_tabUsageRecorder)
restoredWebStates.push_back(webState);
}
// If there was only one tab and it was the new tab page, clobber it.
BOOL closedNTPTab = NO;
if (oldCount == 1) {
Tab* tab = [self tabAtIndex:0];
BOOL hasPendingLoad =
tab.webState->GetNavigationManager()->GetPendingItem() != nullptr;
if (!hasPendingLoad &&
tab.webState->GetLastCommittedURL() == kChromeUINewTabURL) {
[self closeTab:tab];
closedNTPTab = YES;
oldCount = 0;
}
}
if (_tabUsageRecorder) {
_tabUsageRecorder->InitialRestoredTabs(self.currentTab.webState,
restoredWebStates);
}
return closedNTPTab;
}
#pragma mark - Notification Handlers
// Called when UIApplicationWillResignActiveNotification is received.
- (void)willResignActive:(NSNotification*)notify {
if (self.webUsageEnabled && self.currentTab) {
[SnapshotCacheFactory::GetForBrowserState(_browserState)
willBeSavedGreyWhenBackgrounding:self.currentTab.tabId];
}
}
// Called when UIApplicationDidEnterBackgroundNotification is received.
- (void)applicationDidEnterBackground:(NSNotification*)notify {
if (!_browserState)
return;
// Evict all the certificate policies except for the current entries of the
// active sessions.
CleanCertificatePolicyCache(
&_clearPoliciesTaskTracker,
base::CreateSingleThreadTaskRunnerWithTraits({web::WebThread::IO}),
web::BrowserState::GetCertificatePolicyCache(_browserState),
_webStateList.get());
// Normally, the session is saved after some timer expires but since the app
// is about to enter the background send YES to save the session immediately.
[self saveSessionImmediately:YES];
// Write out a grey version of the current website to disk.
if (self.webUsageEnabled && self.currentTab) {
[SnapshotCacheFactory::GetForBrowserState(_browserState)
saveGreyInBackgroundForSessionID:self.currentTab.tabId];
}
}
#pragma mark - CRWWebStateObserver
- (void)webState:(web::WebState*)webState
didCommitNavigationWithDetails:
(const web::LoadCommittedDetails&)load_details {
Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
[self notifyTabLoading:tab];
web::NavigationItem* previousItem = nullptr;
if (load_details.previous_item_index >= 0) {
DCHECK(webState->GetNavigationManager());
previousItem = webState->GetNavigationManager()->GetItemAtIndex(
load_details.previous_item_index);
}
[self navigationCommittedInTab:tab previousItem:previousItem];
// Sending a notification about the url change for crash reporting.
// TODO(crbug.com/661675): Consider using the navigation entry committed
// notification now that it's in the right place.
const std::string& lastCommittedURL = webState->GetLastCommittedURL().spec();
if (!lastCommittedURL.empty()) {
[[NSNotificationCenter defaultCenter]
postNotificationName:kTabUrlStartedLoadingNotificationForCrashReporting
object:tab
userInfo:@{
kTabUrlKey : base::SysUTF8ToNSString(lastCommittedURL)
}];
}
}
- (void)webState:(web::WebState*)webState
didStartNavigation:(web::NavigationContext*)navigation {
Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
// In order to avoid false positive in the crash loop detection, disable the
// counter as soon as an URL is loaded. This requires an user action and is a
// significant source of crashes. Ignore NTP as it is loaded by default after
// a crash.
if (navigation->GetUrl().host_piece() != kChromeUINewTabHost) {
static dispatch_once_t dispatch_once_token;
dispatch_once(&dispatch_once_token, ^{
crash_util::ResetFailedStartupAttemptCount();
});
}
if (_tabUsageRecorder && ShouldRecordPageLoadStartForNavigation(navigation)) {
_tabUsageRecorder->RecordPageLoadStart(webState);
}
[self notifyTabChanged:tab];
[[NSNotificationCenter defaultCenter]
postNotificationName:
kTabClosingCurrentDocumentNotificationForCrashReporting
object:tab];
DCHECK(webState->GetNavigationManager());
web::NavigationItem* navigationItem =
webState->GetNavigationManager()->GetPendingItem();
// TODO(crbug.com/676129): the pending item is not correctly set when the
// page is reloading, use the last committed item if pending item is null.
// Remove this once tracking bug is fixed.
if (!navigationItem) {
navigationItem = webState->GetNavigationManager()->GetLastCommittedItem();
}
[[OmniboxGeolocationController sharedInstance]
addLocationToNavigationItem:navigationItem
browserState:ios::ChromeBrowserState::FromBrowserState(
webState->GetBrowserState())];
}
- (void)webState:(web::WebState*)webState
didFinishNavigation:(web::NavigationContext*)navigation {
Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
[self notifyTabChanged:tab];
}
- (void)webState:(web::WebState*)webState didLoadPageWithSuccess:(BOOL)success {
DCHECK(!webState->IsLoading());
Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
[self notifyTabFinishedLoading:tab success:success];
RecordInterfaceOrientationMetric();
RecordMainFrameNavigationMetric(webState);
[[OmniboxGeolocationController sharedInstance] finishPageLoadForTab:tab
loadSuccess:success];
}
- (void)webState:(web::WebState*)webState
didChangeLoadingProgress:(double)progress {
// TODO(crbug.com/546406): It is probably possible to do something smarter,
// but the fact that this is not always sent will have to be taken into
// account.
Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
[self notifyTabChanged:tab];
}
- (void)webStateDidChangeTitle:(web::WebState*)webState {
Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
[self notifyTabChanged:tab];
}
- (void)webStateDidChangeVisibleSecurityState:(web::WebState*)webState {
Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
[self notifyTabChanged:tab];
}
- (void)webStateDestroyed:(web::WebState*)webState {
// The TabModel is removed from WebState's observer when the WebState is
// detached from WebStateList which happens before WebState destructor,
// so this method should never be called.
NOTREACHED();
}
- (void)webStateDidStopLoading:(web::WebState*)webState {
Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
[self notifyTabChanged:tab];
}
#pragma mark - WebStateListObserving
- (void)webStateList:(WebStateList*)webStateList
didInsertWebState:(web::WebState*)webState
atIndex:(int)index
activating:(BOOL)activating {
DCHECK(webState);
webState->AddObserver(_webStateObserver.get());
}
- (void)webStateList:(WebStateList*)webStateList
didReplaceWebState:(web::WebState*)oldWebState
withWebState:(web::WebState*)newWebState
atIndex:(int)atIndex {
DCHECK(oldWebState);
DCHECK(newWebState);
newWebState->AddObserver(_webStateObserver.get());
oldWebState->RemoveObserver(_webStateObserver.get());
}
- (void)webStateList:(WebStateList*)webStateList
didDetachWebState:(web::WebState*)webState
atIndex:(int)atIndex {
DCHECK(webState);
webState->RemoveObserver(_webStateObserver.get());
}
@end