| // 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.h" |
| |
| #import <CoreLocation/CoreLocation.h> |
| #import <UIKit/UIKit.h> |
| |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/ios/block_types.h" |
| #include "base/json/string_escape.h" |
| #include "base/logging.h" |
| #include "base/mac/bind_objc_block.h" |
| #include "base/mac/foundation_util.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/metrics/user_metrics_action.h" |
| #include "base/scoped_observer.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/time/time.h" |
| #include "components/favicon/core/favicon_driver_observer.h" |
| #include "components/favicon/ios/web_favicon_driver.h" |
| #include "components/google/core/browser/google_util.h" |
| #include "components/history/core/browser/history_context.h" |
| #include "components/history/core/browser/history_service.h" |
| #include "components/history/core/browser/top_sites.h" |
| #include "components/history/ios/browser/web_state_top_sites_observer.h" |
| #include "components/infobars/core/infobar_manager.h" |
| #include "components/keyed_service/core/service_access_type.h" |
| #include "components/metrics_services_manager/metrics_services_manager.h" |
| #include "components/navigation_metrics/navigation_metrics.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/reading_list/core/reading_list_model.h" |
| #include "components/search_engines/template_url_service.h" |
| #include "components/sessions/core/session_types.h" |
| #include "components/sessions/ios/ios_serialized_navigation_builder.h" |
| #include "components/signin/core/browser/account_reconcilor.h" |
| #include "components/signin/core/browser/signin_metrics.h" |
| #import "components/signin/ios/browser/account_consistency_service.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "components/url_formatter/url_formatter.h" |
| #include "ios/chrome/browser/application_context.h" |
| #import "ios/chrome/browser/autofill/autofill_controller.h" |
| #import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h" |
| #import "ios/chrome/browser/autofill/form_suggestion_controller.h" |
| #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| #include "ios/chrome/browser/chrome_url_constants.h" |
| #import "ios/chrome/browser/crash_loop_detection_util.h" |
| #include "ios/chrome/browser/experimental_flags.h" |
| #import "ios/chrome/browser/find_in_page/find_in_page_controller.h" |
| #import "ios/chrome/browser/geolocation/omnibox_geolocation_controller.h" |
| #include "ios/chrome/browser/history/history_service_factory.h" |
| #include "ios/chrome/browser/history/top_sites_factory.h" |
| #include "ios/chrome/browser/infobars/infobar_manager_impl.h" |
| #import "ios/chrome/browser/metrics/tab_usage_recorder.h" |
| #import "ios/chrome/browser/native_app_launcher/native_app_navigation_controller.h" |
| #import "ios/chrome/browser/passwords/password_controller.h" |
| #import "ios/chrome/browser/passwords/passwords_ui_delegate_impl.h" |
| #include "ios/chrome/browser/pref_names.h" |
| #include "ios/chrome/browser/reading_list/reading_list_model_factory.h" |
| #include "ios/chrome/browser/search_engines/template_url_service_factory.h" |
| #include "ios/chrome/browser/sessions/ios_chrome_session_tab_helper.h" |
| #include "ios/chrome/browser/signin/account_consistency_service_factory.h" |
| #include "ios/chrome/browser/signin/account_reconcilor_factory.h" |
| #include "ios/chrome/browser/signin/authentication_service.h" |
| #include "ios/chrome/browser/signin/authentication_service_factory.h" |
| #include "ios/chrome/browser/signin/signin_capability.h" |
| #import "ios/chrome/browser/snapshots/snapshot_manager.h" |
| #import "ios/chrome/browser/snapshots/snapshot_overlay_provider.h" |
| #import "ios/chrome/browser/snapshots/web_controller_snapshot_helper.h" |
| #import "ios/chrome/browser/tabs/legacy_tab_helper.h" |
| #import "ios/chrome/browser/tabs/tab_delegate.h" |
| #import "ios/chrome/browser/tabs/tab_dialog_delegate.h" |
| #import "ios/chrome/browser/tabs/tab_headers_delegate.h" |
| #import "ios/chrome/browser/tabs/tab_helper_util.h" |
| #import "ios/chrome/browser/tabs/tab_model.h" |
| #import "ios/chrome/browser/tabs/tab_private.h" |
| #import "ios/chrome/browser/tabs/tab_snapshotting_delegate.h" |
| #include "ios/chrome/browser/translate/chrome_ios_translate_client.h" |
| #import "ios/chrome/browser/u2f/u2f_controller.h" |
| #import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h" |
| #import "ios/chrome/browser/ui/commands/generic_chrome_command.h" |
| #include "ios/chrome/browser/ui/commands/ios_command_ids.h" |
| #import "ios/chrome/browser/ui/commands/open_url_command.h" |
| #import "ios/chrome/browser/ui/commands/show_signin_command.h" |
| #import "ios/chrome/browser/ui/downloads/download_manager_controller.h" |
| #import "ios/chrome/browser/ui/fullscreen_controller.h" |
| #import "ios/chrome/browser/ui/open_in_controller.h" |
| #import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h" |
| #import "ios/chrome/browser/ui/prerender_delegate.h" |
| #import "ios/chrome/browser/ui/reader_mode/reader_mode_checker.h" |
| #import "ios/chrome/browser/ui/reader_mode/reader_mode_controller.h" |
| #include "ios/chrome/browser/ui/ui_util.h" |
| #import "ios/chrome/browser/web/auto_reload_bridge.h" |
| #import "ios/chrome/browser/web/external_app_launcher.h" |
| #import "ios/chrome/browser/web/navigation_manager_util.h" |
| #import "ios/chrome/browser/web/passkit_dialog_provider.h" |
| #include "ios/chrome/browser/web/print_observer.h" |
| #import "ios/chrome/browser/xcallback_parameters.h" |
| #include "ios/chrome/grit/ios_strings.h" |
| #include "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
| #import "ios/public/provider/chrome/browser/native_app_launcher/native_app_metadata.h" |
| #import "ios/public/provider/chrome/browser/native_app_launcher/native_app_whitelist_manager.h" |
| #import "ios/web/navigation/navigation_item_impl.h" |
| #import "ios/web/navigation/navigation_manager_impl.h" |
| #include "ios/web/public/favicon_status.h" |
| #include "ios/web/public/favicon_url.h" |
| #include "ios/web/public/interstitials/web_interstitial.h" |
| #include "ios/web/public/load_committed_details.h" |
| #import "ios/web/public/navigation_manager.h" |
| #include "ios/web/public/referrer.h" |
| #import "ios/web/public/serializable_user_data_manager.h" |
| #include "ios/web/public/ssl_status.h" |
| #include "ios/web/public/url_scheme_util.h" |
| #include "ios/web/public/url_util.h" |
| #include "ios/web/public/web_client.h" |
| #import "ios/web/public/web_state/js/crw_js_injection_receiver.h" |
| #import "ios/web/public/web_state/navigation_context.h" |
| #import "ios/web/public/web_state/ui/crw_generic_content_view.h" |
| #include "ios/web/public/web_state/web_state.h" |
| #import "ios/web/public/web_state/web_state_observer_bridge.h" |
| #include "ios/web/public/web_thread.h" |
| #import "ios/web/web_state/ui/crw_web_controller.h" |
| #import "ios/web/web_state/web_state_impl.h" |
| #include "net/base/escape.h" |
| #include "net/base/filename_util.h" |
| #import "net/base/mac/url_conversions.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| #include "net/cert/x509_certificate.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/url_request/url_fetcher.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/page_transition_types.h" |
| #include "url/origin.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| NSString* const kTabUrlStartedLoadingNotificationForCrashReporting = |
| @"kTabUrlStartedLoadingNotificationForCrashReporting"; |
| NSString* const kTabUrlMayStartLoadingNotificationForCrashReporting = |
| @"kTabUrlMayStartLoadingNotificationForCrashReporting"; |
| NSString* const kTabIsShowingExportableNotificationForCrashReporting = |
| @"kTabIsShowingExportableNotificationForCrashReporting"; |
| NSString* const kTabClosingCurrentDocumentNotificationForCrashReporting = |
| @"kTabClosingCurrentDocumentNotificationForCrashReporting"; |
| |
| NSString* const kTabUrlKey = @"url"; |
| |
| namespace { |
| class TabHistoryContext; |
| class FaviconDriverObserverBridge; |
| class TabInfoBarObserver; |
| |
| // The key under which the Tab ID is stored in the WebState's serializable user |
| // data. |
| NSString* const kTabIDKey = @"TabID"; |
| |
| // Name of histogram for recording the state of the tab when the renderer is |
| // terminated. |
| const char kRendererTerminationStateHistogram[] = |
| "Tab.StateAtRendererTermination"; |
| |
| // Referrer used for clicks on article suggestions on the NTP. |
| const char kChromeContentSuggestionsReferrer[] = |
| "https://www.googleapis.com/auth/chrome-content-suggestions"; |
| |
| // Enum corresponding to UMA's TabForegroundState, for |
| // Tab.StateAtRendererTermination. Must be kept in sync with the UMA enum. |
| enum class RendererTerminationTabState { |
| // These two values are for when the app is in the foreground. |
| FOREGROUND_TAB_FOREGROUND_APP = 0, |
| BACKGROUND_TAB_FOREGROUND_APP, |
| // These are for when the app is in the background. |
| FOREGROUND_TAB_BACKGROUND_APP, |
| BACKGROUND_TAB_BACKGROUND_APP, |
| TERMINATION_TAB_STATE_COUNT |
| }; |
| |
| // Returns true if the application is in the background or inactive state. |
| bool IsApplicationStateNotActive(UIApplicationState state) { |
| return (state == UIApplicationStateBackground || |
| state == UIApplicationStateInactive); |
| } |
| |
| // Returns true if |item| is the result of a HTTP redirect. |
| // Returns false if |item| is nullptr; |
| bool IsItemRedirectItem(web::NavigationItem* item) { |
| if (!item) |
| return false; |
| |
| return (ui::PageTransition::PAGE_TRANSITION_IS_REDIRECT_MASK & |
| item->GetTransitionType()) == 0; |
| } |
| |
| // TabHistoryContext is used by history to scope the lifetime of navigation |
| // entry references to Tab. |
| class TabHistoryContext : public history::Context { |
| public: |
| TabHistoryContext() {} |
| ~TabHistoryContext() {} |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(TabHistoryContext); |
| }; |
| } // namespace |
| |
| @interface Tab ()<CRWWebStateObserver, |
| CRWWebControllerObserver, |
| FindInPageControllerDelegate, |
| ReaderModeControllerDelegate> { |
| __weak TabModel* _parentTabModel; |
| ios::ChromeBrowserState* _browserState; |
| |
| OpenInController* _openInController; |
| |
| // Whether or not this tab is currently being displayed. |
| BOOL _visible; |
| |
| // Holds entries that need to be added to the history DB. Prerender tabs do |
| // not write navigation data to the history DB. Instead, they cache history |
| // data in this vector and add it to the DB when the prerender status is |
| // removed (when the Tab is swapped in as a real Tab). |
| std::vector<history::HistoryAddPageArgs> _addPageVector; |
| |
| // YES if this Tab is being prerendered. |
| BOOL _isPrerenderTab; |
| |
| // YES if this Tab was initiated from a voice search. |
| BOOL _isVoiceSearchResultsTab; |
| |
| // YES if the Tab needs to be reloaded after the app becomes active. |
| BOOL _requireReloadAfterBecomingActive; |
| |
| // Last visited timestamp. |
| double _lastVisitedTimestamp; |
| |
| // The Full Screen Controller responsible for hiding/showing the toolbar. |
| FullScreenController* _fullScreenController; |
| |
| // The Overscroll controller responsible for displaying the |
| // overscrollActionsView above the toolbar. |
| OverscrollActionsController* _overscrollActionsController; |
| |
| // Lightweight object dealing with various different UI behaviours when |
| // opening a URL in an external application. |
| ExternalAppLauncher* _externalAppLauncher; |
| |
| // Handles suggestions for form entry. |
| FormSuggestionController* _suggestionController; |
| |
| // Manages the input accessory view during form input. |
| FormInputAccessoryViewController* _inputAccessoryViewController; |
| |
| // Handles autofill. |
| AutofillController* _autofillController; |
| |
| // Handles GAL infobar on web pages. |
| NativeAppNavigationController* _nativeAppNavigationController; |
| |
| // Handles caching and retrieving of snapshots. |
| SnapshotManager* _snapshotManager; |
| |
| // Handles retrieving, generating and updating snapshots of CRWWebController's |
| // web page. |
| WebControllerSnapshotHelper* _webControllerSnapshotHelper; |
| |
| // Handles support for window.print JavaScript calls. |
| std::unique_ptr<PrintObserver> _printObserver; |
| |
| // AutoReloadBridge for this tab. |
| AutoReloadBridge* _autoReloadBridge; |
| |
| // WebStateImpl for this tab. |
| web::WebStateImpl* _webStateImpl; |
| |
| // Allows Tab to conform CRWWebStateDelegate protocol. |
| std::unique_ptr<web::WebStateObserverBridge> _webStateObserver; |
| |
| // Context used by history to scope the lifetime of navigation entry |
| // references to Tab. |
| TabHistoryContext _tabHistoryContext; |
| |
| // C++ bridge that receives notifications from the FaviconDriver. |
| std::unique_ptr<FaviconDriverObserverBridge> _faviconDriverObserverBridge; |
| |
| // Universal Second Factor (U2F) call controller. |
| U2FController* _secondFactorController; |
| |
| // C++ observer used to trigger snapshots after the removal of InfoBars. |
| std::unique_ptr<TabInfoBarObserver> _tabInfoBarObserver; |
| } |
| |
| // Returns the tab's reader mode controller. May contain nil if the feature is |
| // disabled. |
| @property(nonatomic, readonly) ReaderModeController* readerModeController; |
| |
| // Returns a list of FormSuggestionProviders to be queried for suggestions |
| // in order of priority. |
| - (NSArray*)suggestionProviders; |
| |
| // Returns a list of FormInputAccessoryViewProviders to be queried for an input |
| // accessory view in order of priority. |
| - (NSArray*)accessoryViewProviders; |
| |
| // Sets the favicon on the current NavigationItem. |
| - (void)setFavicon:(const gfx::Image*)image; |
| |
| // Saves the current title to the history database. |
| - (void)saveTitleToHistoryDB; |
| |
| // Adds the current session entry to this history database. |
| - (void)addCurrentEntryToHistoryDB; |
| |
| // Adds any cached entries from |_addPageVector| to the history DB. |
| - (void)commitCachedEntriesToHistoryDB; |
| |
| // Returns the OpenInController for this tab. |
| - (OpenInController*)openInController; |
| |
| // Initialize the Native App Launcher controller. |
| - (void)initNativeAppNavigationController; |
| |
| // Handles exportable files if possible. |
| - (void)handleExportableFile:(net::HttpResponseHeaders*)headers; |
| |
| // Called after the session history is replaced, useful for updating members |
| // with new sessionID. |
| - (void)didReplaceSessionHistory; |
| |
| // Called when the UIApplication's state becomes active. |
| - (void)applicationDidBecomeActive; |
| |
| @end |
| |
| namespace { |
| class FaviconDriverObserverBridge : public favicon::FaviconDriverObserver { |
| public: |
| FaviconDriverObserverBridge(Tab* owner, |
| favicon::FaviconDriver* favicon_driver); |
| ~FaviconDriverObserverBridge() override; |
| |
| // favicon::FaviconDriverObserver implementation. |
| void OnFaviconUpdated(favicon::FaviconDriver* favicon_driver, |
| NotificationIconType notification_icon_type, |
| const GURL& icon_url, |
| bool icon_url_changed, |
| const gfx::Image& image) override; |
| |
| private: |
| __weak Tab* owner_; |
| ScopedObserver<favicon::FaviconDriver, favicon::FaviconDriverObserver> |
| scoped_observer_; |
| DISALLOW_COPY_AND_ASSIGN(FaviconDriverObserverBridge); |
| }; |
| |
| FaviconDriverObserverBridge::FaviconDriverObserverBridge( |
| Tab* owner, |
| favicon::FaviconDriver* favicon_driver) |
| : owner_(owner), scoped_observer_(this) { |
| scoped_observer_.Add(favicon_driver); |
| } |
| |
| FaviconDriverObserverBridge::~FaviconDriverObserverBridge() {} |
| |
| void FaviconDriverObserverBridge::OnFaviconUpdated( |
| favicon::FaviconDriver* favicon_driver, |
| NotificationIconType notification_icon_type, |
| const GURL& icon_url, |
| bool icon_url_changed, |
| const gfx::Image& image) { |
| [owner_ setFavicon:&image]; |
| } |
| |
| // Observer class that listens for infobar signals. |
| class TabInfoBarObserver : public infobars::InfoBarManager::Observer { |
| public: |
| explicit TabInfoBarObserver(Tab* owner); |
| ~TabInfoBarObserver() override; |
| void SetShouldObserveInfoBarManager(bool should_observe); |
| void OnInfoBarAdded(infobars::InfoBar* infobar) override; |
| void OnInfoBarRemoved(infobars::InfoBar* infobar, bool animate) override; |
| void OnInfoBarReplaced(infobars::InfoBar* old_infobar, |
| infobars::InfoBar* new_infobar) override; |
| |
| private: |
| __weak Tab* owner_; |
| ScopedObserver<infobars::InfoBarManager, TabInfoBarObserver> scoped_observer_; |
| DISALLOW_COPY_AND_ASSIGN(TabInfoBarObserver); |
| }; |
| |
| TabInfoBarObserver::TabInfoBarObserver(Tab* owner) |
| : owner_(owner), scoped_observer_(this) {} |
| |
| TabInfoBarObserver::~TabInfoBarObserver() {} |
| |
| void TabInfoBarObserver::SetShouldObserveInfoBarManager(bool should_observe) { |
| infobars::InfoBarManager* infobar_manager = [owner_ infoBarManager]; |
| if (!infobar_manager) |
| return; |
| |
| if (should_observe) { |
| if (!scoped_observer_.IsObserving(infobar_manager)) |
| scoped_observer_.Add(infobar_manager); |
| } else { |
| scoped_observer_.Remove(infobar_manager); |
| } |
| } |
| |
| void TabInfoBarObserver::OnInfoBarAdded(infobars::InfoBar* infobar) { |
| // Update snapshots after the infobar has been added. |
| [owner_ updateSnapshotWithOverlay:YES visibleFrameOnly:YES]; |
| } |
| |
| void TabInfoBarObserver::OnInfoBarRemoved(infobars::InfoBar* infobar, |
| bool animate) { |
| // Update snapshots after the infobar has been removed. |
| [owner_ updateSnapshotWithOverlay:YES visibleFrameOnly:YES]; |
| } |
| |
| void TabInfoBarObserver::OnInfoBarReplaced(infobars::InfoBar* old_infobar, |
| infobars::InfoBar* new_infobar) { |
| // Update snapshots after the infobar has been replaced. |
| [owner_ updateSnapshotWithOverlay:YES visibleFrameOnly:YES]; |
| } |
| |
| } // anonymous namespace |
| |
| @implementation Tab |
| |
| @synthesize browserState = _browserState; |
| @synthesize tabId = tabId_; |
| @synthesize useGreyImageCache = useGreyImageCache_; |
| @synthesize isPrerenderTab = _isPrerenderTab; |
| @synthesize isLinkLoadingPrerenderTab = isLinkLoadingPrerenderTab_; |
| @synthesize isVoiceSearchResultsTab = _isVoiceSearchResultsTab; |
| @synthesize passwordController = passwordController_; |
| @synthesize overscrollActionsController = _overscrollActionsController; |
| @synthesize readerModeController = readerModeController_; |
| @synthesize overscrollActionsControllerDelegate = |
| overscrollActionsControllerDelegate_; |
| @synthesize passKitDialogProvider = passKitDialogProvider_; |
| @synthesize delegate = delegate_; |
| @synthesize dialogDelegate = dialogDelegate_; |
| @synthesize snapshotOverlayProvider = snapshotOverlayProvider_; |
| @synthesize tabSnapshottingDelegate = tabSnapshottingDelegate_; |
| @synthesize tabHeadersDelegate = tabHeadersDelegate_; |
| @synthesize fullScreenControllerDelegate = fullScreenControllerDelegate_; |
| |
| - (instancetype)initWithWebState:(web::WebState*)webState { |
| DCHECK(webState); |
| self = [super init]; |
| if (self) { |
| // TODO(crbug.com/620465): Tab should only use public API of WebState. |
| // Remove this cast once this is the case. |
| _webStateImpl = static_cast<web::WebStateImpl*>(webState); |
| _browserState = |
| ios::ChromeBrowserState::FromBrowserState(webState->GetBrowserState()); |
| _webStateObserver = |
| base::MakeUnique<web::WebStateObserverBridge>(webState, self); |
| |
| [self updateLastVisitedTimestamp]; |
| [[self webController] addObserver:self]; |
| [[self webController] setDelegate:self]; |
| |
| _snapshotManager = [[SnapshotManager alloc] initWithWebState:webState]; |
| _webControllerSnapshotHelper = [[WebControllerSnapshotHelper alloc] |
| initWithSnapshotManager:_snapshotManager |
| tab:self]; |
| |
| if (experimental_flags::IsNativeAppLauncherEnabled()) |
| [self initNativeAppNavigationController]; |
| |
| [[NSNotificationCenter defaultCenter] |
| addObserver:self |
| selector:@selector(applicationDidBecomeActive) |
| name:UIApplicationDidBecomeActiveNotification |
| object:nil]; |
| } |
| return self; |
| } |
| |
| - (void)attachTabHelpers { |
| _tabInfoBarObserver = base::MakeUnique<TabInfoBarObserver>(self); |
| _tabInfoBarObserver->SetShouldObserveInfoBarManager(true); |
| |
| if (experimental_flags::IsAutoReloadEnabled()) |
| _autoReloadBridge = [[AutoReloadBridge alloc] initWithTab:self]; |
| _printObserver = base::MakeUnique<PrintObserver>(self.webState); |
| |
| id<PasswordsUiDelegate> passwordsUiDelegate = |
| [[PasswordsUiDelegateImpl alloc] init]; |
| passwordController_ = |
| [[PasswordController alloc] initWithWebState:self.webState |
| passwordsUiDelegate:passwordsUiDelegate]; |
| password_manager::PasswordGenerationManager* passwordGenerationManager = |
| [passwordController_ passwordGenerationManager]; |
| _autofillController = |
| [[AutofillController alloc] initWithBrowserState:_browserState |
| passwordGenerationManager:passwordGenerationManager |
| webState:self.webState]; |
| _suggestionController = [[FormSuggestionController alloc] |
| initWithWebState:self.webState |
| providers:[self suggestionProviders]]; |
| _inputAccessoryViewController = [[FormInputAccessoryViewController alloc] |
| initWithWebState:self.webState |
| providers:[self accessoryViewProviders]]; |
| |
| [self setShouldObserveFaviconChanges:YES]; |
| |
| // Create the ReaderModeController immediately so it can register for |
| // WebState changes. |
| if (experimental_flags::IsReaderModeEnabled()) { |
| readerModeController_ = |
| [[ReaderModeController alloc] initWithWebState:self.webState |
| delegate:self]; |
| } |
| } |
| |
| - (NSArray*)accessoryViewProviders { |
| NSMutableArray* providers = [NSMutableArray array]; |
| id<FormInputAccessoryViewProvider> provider = |
| [passwordController_ accessoryViewProvider]; |
| if (provider) |
| [providers addObject:provider]; |
| [providers addObject:[_suggestionController accessoryViewProvider]]; |
| return providers; |
| } |
| |
| - (NSArray*)suggestionProviders { |
| NSMutableArray* providers = [NSMutableArray array]; |
| [providers addObject:[passwordController_ suggestionProvider]]; |
| [providers addObject:[_autofillController suggestionProvider]]; |
| return providers; |
| } |
| |
| - (id<FindInPageControllerDelegate>)findInPageControllerDelegate { |
| return self; |
| } |
| |
| - (void)setParentTabModel:(TabModel*)model { |
| DCHECK(!model || !_parentTabModel); |
| _parentTabModel = model; |
| |
| if (_parentTabModel.syncedWindowDelegate) { |
| IOSChromeSessionTabHelper::FromWebState(self.webState) |
| ->SetWindowID(model.sessionID); |
| } |
| } |
| |
| - (NSString*)description { |
| return [NSString stringWithFormat:@"%p ... %@ - %s", self, self.title, |
| self.visibleURL.spec().c_str()]; |
| } |
| |
| - (CRWWebController*)webController { |
| return _webStateImpl ? _webStateImpl->GetWebController() : nil; |
| } |
| |
| - (id<TabDialogDelegate>)dialogDelegate { |
| return dialogDelegate_; |
| } |
| |
| - (BOOL)loadFinished { |
| return [self.webController loadPhase] == web::PAGE_LOADED; |
| } |
| |
| - (void)setIsVoiceSearchResultsTab:(BOOL)isVoiceSearchResultsTab { |
| // There is intentionally no equality check in this setter, as we want the |
| // notificaiton to be sent regardless of whether the value has changed. |
| _isVoiceSearchResultsTab = isVoiceSearchResultsTab; |
| [_parentTabModel notifyTabChanged:self]; |
| } |
| |
| - (void)retrieveSnapshot:(void (^)(UIImage*))callback { |
| [_webControllerSnapshotHelper |
| retrieveSnapshotForWebController:self.webController |
| sessionID:self.tabId |
| withOverlays:[self snapshotOverlays] |
| callback:callback]; |
| } |
| |
| - (const GURL&)lastCommittedURL { |
| web::NavigationItem* item = self.navigationManager->GetLastCommittedItem(); |
| return item ? item->GetVirtualURL() : GURL::EmptyGURL(); |
| } |
| |
| - (const GURL&)visibleURL { |
| web::NavigationItem* item = self.navigationManager->GetVisibleItem(); |
| return item ? item->GetVirtualURL() : GURL::EmptyGURL(); |
| } |
| |
| - (NSString*)title { |
| base::string16 title = self.webState->GetTitle(); |
| if (title.empty()) |
| title = l10n_util::GetStringUTF16(IDS_DEFAULT_TAB_TITLE); |
| return base::SysUTF16ToNSString(title); |
| } |
| |
| - (NSString*)originalTitle { |
| // Do not use self.webState->GetTitle() as it returns the display title, |
| // not the original page title. |
| DCHECK([self navigationManager]); |
| web::NavigationItem* item = [self navigationManager]->GetLastCommittedItem(); |
| if (!item) |
| return nil; |
| base::string16 pageTitle = item->GetTitle(); |
| return pageTitle.empty() ? nil : base::SysUTF16ToNSString(pageTitle); |
| } |
| |
| - (NSString*)urlDisplayString { |
| base::string16 urlText = url_formatter::FormatUrl( |
| self.visibleURL, url_formatter::kFormatUrlOmitNothing, |
| net::UnescapeRule::SPACES, nullptr, nullptr, nullptr); |
| return base::SysUTF16ToNSString(urlText); |
| } |
| |
| - (NSString*)tabId { |
| if (!self.webState) { |
| // Tab can outlive WebState, in which case Tab is not valid anymore and |
| // tabId should be nil. |
| return nil; |
| } |
| |
| if (tabId_) |
| return tabId_; |
| |
| web::SerializableUserDataManager* userDataManager = |
| web::SerializableUserDataManager::FromWebState(self.webState); |
| NSString* tabId = base::mac::ObjCCast<NSString>( |
| userDataManager->GetValueForSerializationKey(kTabIDKey)); |
| |
| if (!tabId || ![tabId length]) { |
| tabId = [[NSUUID UUID] UUIDString]; |
| userDataManager->AddSerializableData(tabId, kTabIDKey); |
| } |
| |
| tabId_ = [tabId copy]; |
| return tabId_; |
| } |
| |
| - (web::WebState*)webState { |
| return _webStateImpl; |
| } |
| |
| - (void)fetchFavicon { |
| const GURL& url = self.visibleURL; |
| if (!url.is_valid()) |
| return; |
| |
| favicon::FaviconDriver* faviconDriver = |
| favicon::WebFaviconDriver::FromWebState(self.webState); |
| if (faviconDriver) |
| faviconDriver->FetchFavicon(url); |
| } |
| |
| - (void)setFavicon:(const gfx::Image*)image { |
| web::NavigationItem* item = [self navigationManager]->GetVisibleItem(); |
| if (!item) |
| return; |
| if (image) { |
| item->GetFavicon().image = *image; |
| item->GetFavicon().valid = true; |
| } |
| [_parentTabModel notifyTabChanged:self]; |
| } |
| |
| - (UIImage*)favicon { |
| DCHECK([self navigationManager]); |
| web::NavigationItem* item = [self navigationManager]->GetVisibleItem(); |
| if (!item) |
| return nil; |
| const gfx::Image& image = item->GetFavicon().image; |
| if (image.IsEmpty()) |
| return nil; |
| return image.ToUIImage(); |
| } |
| |
| - (UIView*)view { |
| // Record reload of previously-evicted tab. |
| if (![self.webController isViewAlive] && [_parentTabModel tabUsageRecorder]) |
| [_parentTabModel tabUsageRecorder]->RecordPageLoadStart(self); |
| return self.webState ? self.webState->GetView() : nil; |
| } |
| |
| - (UIView*)viewForPrinting { |
| return self.webController.viewForPrinting; |
| } |
| |
| - (web::NavigationManager*)navigationManager { |
| return self.webState ? self.webState->GetNavigationManager() : nullptr; |
| } |
| |
| - (web::NavigationManagerImpl*)navigationManagerImpl { |
| return self.webState ? &(_webStateImpl->GetNavigationManagerImpl()) : nullptr; |
| } |
| |
| // Swap out the existing session history with a new list of navigations. Forces |
| // the tab to reload to update the UI accordingly. This is ok because none of |
| // the session history is stored in the tab; it's always fetched through the |
| // navigation manager. |
| - (void)replaceHistoryWithNavigations: |
| (const std::vector<sessions::SerializedNavigationEntry>&)navigations |
| currentIndex:(NSInteger)currentIndex { |
| std::vector<std::unique_ptr<web::NavigationItem>> items = |
| sessions::IOSSerializedNavigationBuilder::ToNavigationItems(navigations); |
| [self navigationManagerImpl]->ReplaceSessionHistory(std::move(items), |
| currentIndex); |
| [self didReplaceSessionHistory]; |
| |
| [self.webController loadCurrentURL]; |
| } |
| |
| - (void)didReplaceSessionHistory { |
| // Replace _fullScreenController with a new sessionID when the navigation |
| // manager changes. |
| // TODO(crbug.com/661666): Consider just updating sessionID and not replacing |
| // |_fullScreenController|. |
| if (_fullScreenController) { |
| [_fullScreenController invalidate]; |
| [self.webController removeObserver:_fullScreenController]; |
| _fullScreenController = [[FullScreenController alloc] |
| initWithDelegate:fullScreenControllerDelegate_ |
| navigationManager:self.navigationManager |
| sessionID:self.tabId]; |
| [self.webController addObserver:_fullScreenController]; |
| // If the content of the page was loaded without knowledge of the |
| // toolbar position it will be misplaced under the toolbar instead of |
| // right below. This happens e.g. in the case of preloading. This is to make |
| // sure the content is moved to the right place. |
| [_fullScreenController moveContentBelowHeader]; |
| } |
| } |
| |
| - (void)setIsLinkLoadingPrerenderTab:(BOOL)isLinkLoadingPrerenderTab { |
| isLinkLoadingPrerenderTab_ = isLinkLoadingPrerenderTab; |
| [self setIsPrerenderTab:isLinkLoadingPrerenderTab]; |
| } |
| |
| - (void)setIsPrerenderTab:(BOOL)isPrerender { |
| if (_isPrerenderTab == isPrerender) |
| return; |
| |
| _isPrerenderTab = isPrerender; |
| |
| self.webController.shouldSuppressDialogs = |
| (isPrerender && !isLinkLoadingPrerenderTab_); |
| |
| if (_isPrerenderTab) |
| return; |
| |
| [_fullScreenController moveContentBelowHeader]; |
| [self commitCachedEntriesToHistoryDB]; |
| [self saveTitleToHistoryDB]; |
| |
| // If the page has finished loading, take a snapshot. If the page is still |
| // loading, do nothing, as CRWWebController will automatically take a |
| // snapshot once the load completes. |
| BOOL loadingFinished = self.webController.loadPhase == web::PAGE_LOADED; |
| if (loadingFinished) |
| [self updateSnapshotWithOverlay:YES visibleFrameOnly:YES]; |
| |
| [[OmniboxGeolocationController sharedInstance] |
| finishPageLoadForTab:self |
| loadSuccess:loadingFinished]; |
| [self countMainFrameLoad]; |
| } |
| |
| - (void)setFullScreenControllerDelegate: |
| (id<FullScreenControllerDelegate>)fullScreenControllerDelegate { |
| if (fullScreenControllerDelegate == fullScreenControllerDelegate_) |
| return; |
| // Lazily create a FullScreenController. |
| // The check for fullScreenControllerDelegate is necessary to avoid recreating |
| // a FullScreenController during teardown. |
| if (!_fullScreenController && fullScreenControllerDelegate) { |
| _fullScreenController = [[FullScreenController alloc] |
| initWithDelegate:fullScreenControllerDelegate |
| navigationManager:self.navigationManager |
| sessionID:self.tabId]; |
| [self.webController addObserver:_fullScreenController]; |
| // If the content of the page was loaded without knowledge of the |
| // toolbar position it will be misplaced under the toolbar instead of |
| // right below. This happens e.g. in the case of preloading. This is to make |
| // sure the content is moved to the right place. |
| [_fullScreenController moveContentBelowHeader]; |
| } |
| fullScreenControllerDelegate_ = fullScreenControllerDelegate; |
| } |
| |
| - (void)setOverscrollActionsControllerDelegate: |
| (id<OverscrollActionsControllerDelegate>) |
| overscrollActionsControllerDelegate { |
| if (overscrollActionsControllerDelegate_ == |
| overscrollActionsControllerDelegate) { |
| return; |
| } |
| |
| // Lazily create a OverscrollActionsController. |
| // The check for overscrollActionsControllerDelegate is necessary to avoid |
| // recreating a OverscrollActionsController during teardown. |
| if (!_overscrollActionsController) { |
| _overscrollActionsController = [[OverscrollActionsController alloc] init]; |
| [self.webController addObserver:_overscrollActionsController]; |
| } |
| OverscrollStyle style = OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO; |
| if (_browserState->IsOffTheRecord()) |
| style = OverscrollStyle::REGULAR_PAGE_INCOGNITO; |
| [_overscrollActionsController setStyle:style]; |
| [_overscrollActionsController |
| setDelegate:overscrollActionsControllerDelegate]; |
| overscrollActionsControllerDelegate_ = overscrollActionsControllerDelegate; |
| } |
| |
| - (void)saveTitleToHistoryDB { |
| // If incognito, don't update history. |
| if (_browserState->IsOffTheRecord()) |
| return; |
| // Don't update the history if current entry has no title. |
| NSString* title = [self title]; |
| if (![title length] || |
| [title isEqualToString:l10n_util::GetNSString(IDS_DEFAULT_TAB_TITLE)]) { |
| return; |
| } |
| |
| history::HistoryService* historyService = |
| ios::HistoryServiceFactory::GetForBrowserState( |
| _browserState, ServiceAccessType::IMPLICIT_ACCESS); |
| DCHECK(historyService); |
| historyService->SetPageTitle(self.lastCommittedURL, |
| base::SysNSStringToUTF16(title)); |
| } |
| |
| - (void)addCurrentEntryToHistoryDB { |
| DCHECK([self navigationManager]->GetVisibleItem()); |
| // If incognito, don't update history. |
| if (_browserState->IsOffTheRecord()) |
| return; |
| |
| web::NavigationItem* item = [self navigationManager]->GetVisibleItem(); |
| |
| // Do not update the history db for back/forward navigations. |
| // TODO(crbug.com/661667): We do not currently tag the entry with a |
| // FORWARD_BACK transition. Fix. |
| if (item->GetTransitionType() & ui::PAGE_TRANSITION_FORWARD_BACK) |
| return; |
| |
| history::HistoryService* historyService = |
| ios::HistoryServiceFactory::GetForBrowserState( |
| _browserState, ServiceAccessType::IMPLICIT_ACCESS); |
| DCHECK(historyService); |
| |
| const GURL url(item->GetURL()); |
| const web::Referrer& referrer = item->GetReferrer(); |
| |
| // Do not update the history db for data: urls. This diverges from upstream, |
| // but prevents us from dumping huge view-source urls into the history |
| // database. Since view-source is only activated in Debug builds, this check |
| // can be Debug-only as well. |
| #ifndef NDEBUG |
| if (url.scheme() == url::kDataScheme) |
| return; |
| #endif |
| |
| history::RedirectList redirects; |
| GURL originalURL = item->GetOriginalRequestURL(); |
| if (item->GetURL() != originalURL) { |
| // Simulate a valid redirect chain in case of URL that have been modified |
| // in |CRWWebController finishHistoryNavigationFromEntry:|. |
| const std::string& urlSpec = item->GetURL().spec(); |
| size_t urlSpecLength = urlSpec.size(); |
| if (item->GetTransitionType() & ui::PAGE_TRANSITION_CLIENT_REDIRECT || |
| (urlSpecLength && (urlSpec.at(urlSpecLength - 1) == '#') && |
| !urlSpec.compare(0, urlSpecLength - 1, originalURL.spec()))) { |
| redirects.push_back(referrer.url); |
| } |
| // TODO(crbug.com/703872): the redirect chain is not constructed the same |
| // way as upstream so this part needs to be revised. |
| redirects.push_back(originalURL); |
| redirects.push_back(url); |
| } |
| |
| DCHECK(item->GetTimestamp().ToInternalValue() > 0); |
| if ([self isPrerenderTab]) { |
| // Clicks on content suggestions on the NTP should not contribute to the |
| // Most Visited tiles in the NTP. |
| const bool consider_for_ntp_most_visited = |
| referrer.url != GURL(kChromeContentSuggestionsReferrer); |
| |
| history::HistoryAddPageArgs args( |
| url, item->GetTimestamp(), &_tabHistoryContext, item->GetUniqueID(), |
| referrer.url, redirects, item->GetTransitionType(), |
| history::SOURCE_BROWSED, false, consider_for_ntp_most_visited); |
| _addPageVector.push_back(args); |
| } else { |
| historyService->AddPage(url, item->GetTimestamp(), &_tabHistoryContext, |
| item->GetUniqueID(), referrer.url, redirects, |
| item->GetTransitionType(), history::SOURCE_BROWSED, |
| false); |
| [self saveTitleToHistoryDB]; |
| } |
| } |
| |
| - (void)commitCachedEntriesToHistoryDB { |
| // If OTR, don't update history. |
| if (_browserState->IsOffTheRecord()) { |
| DCHECK_EQ(0U, _addPageVector.size()); |
| return; |
| } |
| |
| history::HistoryService* historyService = |
| ios::HistoryServiceFactory::GetForBrowserState( |
| _browserState, ServiceAccessType::IMPLICIT_ACCESS); |
| DCHECK(historyService); |
| |
| for (size_t i = 0; i < _addPageVector.size(); ++i) |
| historyService->AddPage(_addPageVector[i]); |
| _addPageVector.clear(); |
| } |
| |
| - (void)webDidUpdateSessionForLoadWithParams: |
| (const web::NavigationManager::WebLoadParams&)params |
| wasInitialNavigation:(BOOL)initialNavigation { |
| // After a crash the NTP is loaded by default. |
| if (params.url.host() != kChromeUINewTabHost) { |
| static BOOL hasLoadedPage = NO; |
| if (!hasLoadedPage) { |
| // As soon as load is initialted, a crash shouldn't be counted as a |
| // startup crash. Since initiating a url load requires user action and is |
| // a significant source of crashes that could lead to false positives in |
| // crash loop detection. |
| crash_util::ResetFailedStartupAttemptCount(); |
| hasLoadedPage = YES; |
| } |
| } |
| |
| ui::PageTransition transition = params.transition_type; |
| |
| // Record any explicit, non-redirect navigation as a clobber (as long as it's |
| // in a real tab). |
| if (!initialNavigation && !_isPrerenderTab && |
| !PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD) && |
| (transition & ui::PAGE_TRANSITION_IS_REDIRECT_MASK) == 0) { |
| base::RecordAction(base::UserMetricsAction("MobileTabClobbered")); |
| } |
| if ([_parentTabModel tabUsageRecorder]) |
| [_parentTabModel tabUsageRecorder]->RecordPageLoadStart(self); |
| |
| // Reset |isVoiceSearchResultsTab| since a new page is being navigated to. |
| self.isVoiceSearchResultsTab = NO; |
| |
| web::NavigationItem* navigationItem = |
| [self navigationManager]->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 = [self navigationManager]->GetLastCommittedItem(); |
| |
| [[OmniboxGeolocationController sharedInstance] |
| addLocationToNavigationItem:navigationItem |
| browserState:_browserState]; |
| } |
| |
| - (void)loadSessionTab:(const sessions::SessionTab*)sessionTab { |
| DCHECK(sessionTab); |
| [self replaceHistoryWithNavigations:sessionTab->navigations |
| currentIndex:sessionTab->current_navigation_index]; |
| } |
| |
| // Halt the tab, which amounts to halting its webController. |
| - (void)terminateNetworkActivity { |
| [self.webController terminateNetworkActivity]; |
| } |
| |
| - (void)webStateDestroyed:(web::WebState*)webState { |
| DCHECK_EQ(_webStateImpl, webState); |
| self.fullScreenControllerDelegate = nil; |
| self.overscrollActionsControllerDelegate = nil; |
| self.passKitDialogProvider = nil; |
| self.snapshotOverlayProvider = nil; |
| |
| [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| |
| [passwordController_ detach]; |
| passwordController_ = nil; |
| _tabInfoBarObserver.reset(); |
| |
| _faviconDriverObserverBridge.reset(); |
| [_openInController detachFromWebController]; |
| _openInController = nil; |
| [_autofillController detachFromWebState]; |
| _autofillController = nil; |
| [_suggestionController detachFromWebState]; |
| _suggestionController = nil; |
| if (_fullScreenController) |
| [self.webController removeObserver:_fullScreenController]; |
| [_fullScreenController invalidate]; |
| _fullScreenController = nil; |
| if (_overscrollActionsController) |
| [self.webController removeObserver:_overscrollActionsController]; |
| [_overscrollActionsController invalidate]; |
| _overscrollActionsController = nil; |
| [readerModeController_ detachFromWebState]; |
| readerModeController_ = nil; |
| |
| // Invalidate any snapshot stored for this session. |
| DCHECK(self.tabId); |
| [_snapshotManager removeImageWithSessionID:self.tabId]; |
| |
| // Cancel any queued dialogs. |
| [self.dialogDelegate cancelDialogForTab:self]; |
| |
| _webStateObserver.reset(); |
| _webStateImpl = nullptr; |
| } |
| |
| - (void)dismissModals { |
| [_openInController disable]; |
| [self.webController dismissModals]; |
| } |
| |
| - (void)setShouldObserveInfoBarManager:(BOOL)shouldObserveInfoBarManager { |
| _tabInfoBarObserver->SetShouldObserveInfoBarManager( |
| shouldObserveInfoBarManager); |
| } |
| |
| - (void)setShouldObserveFaviconChanges:(BOOL)shouldObserveFaviconChanges { |
| if (shouldObserveFaviconChanges) { |
| favicon::FaviconDriver* faviconDriver = |
| favicon::WebFaviconDriver::FromWebState(self.webState); |
| // Some MockWebContents used in tests do not support the FaviconDriver. |
| if (faviconDriver) { |
| _faviconDriverObserverBridge = |
| base::MakeUnique<FaviconDriverObserverBridge>(self, faviconDriver); |
| } |
| } else { |
| _faviconDriverObserverBridge.reset(); |
| } |
| } |
| |
| - (void)goBack { |
| if (self.navigationManager) { |
| DCHECK(self.navigationManager->CanGoBack()); |
| base::RecordAction(base::UserMetricsAction("Back")); |
| self.navigationManager->GoBack(); |
| } |
| } |
| |
| - (void)goForward { |
| if (self.navigationManager) { |
| DCHECK(self.navigationManager->CanGoForward()); |
| base::RecordAction(base::UserMetricsAction("Forward")); |
| self.navigationManager->GoForward(); |
| } |
| } |
| |
| - (BOOL)canGoBack { |
| return self.navigationManager && self.navigationManager->CanGoBack(); |
| } |
| |
| - (BOOL)canGoForward { |
| return self.navigationManager && self.navigationManager->CanGoForward(); |
| } |
| |
| - (void)goToItem:(const web::NavigationItem*)item { |
| DCHECK(item); |
| int index = self.navigationManager->GetIndexOfItem(item); |
| DCHECK_NE(index, -1); |
| self.navigationManager->GoToIndex(index); |
| } |
| |
| - (BOOL)openExternalURL:(const GURL&)url |
| sourceURL:(const GURL&)sourceURL |
| linkClicked:(BOOL)linkClicked { |
| if (!_externalAppLauncher) |
| _externalAppLauncher = [[ExternalAppLauncher alloc] init]; |
| |
| // Make a local url copy for possible modification. |
| GURL finalURL = url; |
| |
| // Check if it's a direct FIDO U2F x-callback call. If so, do not open it, to |
| // prevent pages from spoofing requests with different origins. |
| if (finalURL.SchemeIs("u2f-x-callback")) |
| return NO; |
| |
| // Block attempts to open this application's settings in the native system |
| // settings application. |
| if (finalURL.SchemeIs("app-settings")) |
| return NO; |
| |
| // Check if it's a FIDO U2F call. |
| if (finalURL.SchemeIs("u2f")) { |
| // Create U2FController object lazily. |
| if (!_secondFactorController) |
| _secondFactorController = [[U2FController alloc] init]; |
| |
| DCHECK([self navigationManager]); |
| GURL origin = |
| [self navigationManager]->GetLastCommittedItem()->GetURL().GetOrigin(); |
| |
| // Compose u2f-x-callback URL and update urlToOpen. |
| finalURL = |
| [_secondFactorController XCallbackFromRequestURL:finalURL |
| originURL:origin |
| tabURL:self.lastCommittedURL |
| tabID:self.tabId]; |
| |
| if (!finalURL.is_valid()) |
| return NO; |
| } |
| |
| if ([_externalAppLauncher openURL:finalURL linkClicked:linkClicked]) { |
| // Clears pending navigation history after successfully launching the |
| // external app. |
| DCHECK([self navigationManager]); |
| [self navigationManager]->DiscardNonCommittedItems(); |
| // Ensure the UI reflects the current entry, not the just-discarded pending |
| // entry. |
| [_parentTabModel notifyTabChanged:self]; |
| |
| if (sourceURL.is_valid()) { |
| ReadingListModel* model = |
| ReadingListModelFactory::GetForBrowserState(_browserState); |
| if (model && model->loaded()) |
| model->SetReadStatus(sourceURL, true); |
| } |
| |
| return YES; |
| } |
| return NO; |
| } |
| |
| - (void)webState:(web::WebState*)webState |
| didFinishNavigation:(web::NavigationContext*)navigation { |
| if (navigation->IsSameDocument()) { |
| // Fetch the favicon for the new URL. |
| auto* faviconDriver = favicon::WebFaviconDriver::FromWebState(webState); |
| if (faviconDriver) |
| faviconDriver->FetchFavicon(navigation->GetUrl()); |
| } |
| |
| if (!navigation->GetError()) { |
| [self addCurrentEntryToHistoryDB]; |
| [self countMainFrameLoad]; |
| } |
| |
| [_parentTabModel notifyTabChanged:self]; |
| } |
| |
| // Records metric for the interface's orientation. |
| - (void)recordInterfaceOrientation { |
| 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; |
| } |
| } |
| |
| - (OpenInController*)openInController { |
| if (!_openInController) { |
| _openInController = [[OpenInController alloc] |
| initWithRequestContext:_browserState->GetRequestContext() |
| webController:self.webController]; |
| } |
| return _openInController; |
| } |
| |
| - (id<CRWNativeContent>)controllerForUnhandledContentAtURL:(const GURL&)url { |
| // Shows download manager UI for unhandled content. |
| DownloadManagerController* downloadController = |
| [[DownloadManagerController alloc] initWithWebState:self.webState |
| downloadURL:url]; |
| [downloadController start]; |
| return downloadController; |
| } |
| |
| - (void)handleExportableFile:(net::HttpResponseHeaders*)headers { |
| // Only "application/pdf" is supported for now. |
| if (self.webState->GetContentsMimeType() != "application/pdf") |
| return; |
| |
| [[NSNotificationCenter defaultCenter] |
| postNotificationName:kTabIsShowingExportableNotificationForCrashReporting |
| object:self]; |
| // Try to generate a filename by first looking at |content_disposition_|, then |
| // at the last component of |lastCommittedURL| and if both of these fail use |
| // the default filename "document". |
| std::string contentDisposition; |
| if (headers) |
| headers->GetNormalizedHeader("content-disposition", &contentDisposition); |
| std::string defaultFilename = |
| l10n_util::GetStringUTF8(IDS_IOS_OPEN_IN_FILE_DEFAULT_TITLE); |
| const GURL& committedURL = self.lastCommittedURL; |
| base::string16 filename = |
| net::GetSuggestedFilename(committedURL, contentDisposition, |
| "", // referrer-charset |
| "", // suggested-name |
| "application/pdf", // mime-type |
| defaultFilename); |
| [[self openInController] |
| enableWithDocumentURL:committedURL |
| suggestedFilename:base::SysUTF16ToNSString(filename)]; |
| } |
| |
| - (void)countMainFrameLoad { |
| if ([self isPrerenderTab] || |
| self.lastCommittedURL.SchemeIs(kChromeUIScheme)) { |
| return; |
| } |
| base::RecordAction(base::UserMetricsAction("MobilePageLoaded")); |
| } |
| |
| - (void)applicationDidBecomeActive { |
| if (!_requireReloadAfterBecomingActive) |
| return; |
| if (_visible) { |
| self.navigationManager->Reload(web::ReloadType::NORMAL, |
| false /* check_for_repost */); |
| } else { |
| [self.webController requirePageReload]; |
| } |
| _requireReloadAfterBecomingActive = NO; |
| } |
| |
| #pragma mark - |
| #pragma mark FindInPageControllerDelegate |
| |
| - (void)willAdjustScrollPosition { |
| // Skip the next attempt to correct the scroll offset for the toolbar height. |
| // Used when programatically scrolling down the y offset. |
| [_fullScreenController shouldSkipNextScrollOffsetForHeader]; |
| } |
| |
| #pragma mark - |
| #pragma mark FullScreen |
| |
| - (void)updateFullscreenWithToolbarVisible:(BOOL)visible { |
| [_fullScreenController moveHeaderToRestingPosition:visible]; |
| } |
| |
| #pragma mark - |
| #pragma mark Reader mode |
| |
| - (UIView*)superviewForReaderModePanel { |
| return self.view; |
| } |
| |
| - (BOOL)canSwitchToReaderMode { |
| // Only if the page is loaded and the page passes suitability checks. |
| ReaderModeController* controller = self.readerModeController; |
| return controller && controller.checker->CanSwitchToReaderMode(); |
| } |
| |
| - (void)switchToReaderMode { |
| DCHECK(self.view); |
| [self.readerModeController switchToReaderMode]; |
| } |
| |
| - (void)loadReaderModeHTML:(NSString*)html forURL:(const GURL&)url { |
| // Before changing the HTML on the current page, this checks that the URL has |
| // not changed since reader mode was requested. This could happen for example |
| // if the page does a late redirect itself or if the user tapped on a link and |
| // triggered reader mode before the page load is detected by webState. |
| if (url == self.lastCommittedURL) |
| [self.webController loadHTMLForCurrentURL:html]; |
| |
| [self.readerModeController exitReaderMode]; |
| } |
| |
| #pragma mark - |
| |
| - (BOOL)usesDesktopUserAgent { |
| if (!self.navigationManager) |
| return NO; |
| |
| web::NavigationItem* visibleItem = self.navigationManager->GetVisibleItem(); |
| return visibleItem && |
| visibleItem->GetUserAgentType() == web::UserAgentType::DESKTOP; |
| } |
| |
| - (void)reloadWithUserAgentType:(web::UserAgentType)userAgentType { |
| // TODO(crbug.com/228171): A hack in session_controller -addPendingItem |
| // discusses making tab responsible for distinguishing history stack |
| // navigation from new navigations. |
| web::NavigationManager* navigationManager = [self navigationManager]; |
| DCHECK(navigationManager); |
| |
| web::NavigationItem* lastNonRedirectItem = |
| navigationManager->GetTransientItem(); |
| if (!lastNonRedirectItem || IsItemRedirectItem(lastNonRedirectItem)) |
| lastNonRedirectItem = navigationManager->GetVisibleItem(); |
| if (!lastNonRedirectItem || IsItemRedirectItem(lastNonRedirectItem)) |
| lastNonRedirectItem = GetLastCommittedNonRedirectedItem(navigationManager); |
| |
| if (!lastNonRedirectItem) |
| return; |
| |
| // |reloadURL| will be empty if a page was open by DOM. |
| GURL reloadURL(lastNonRedirectItem->GetOriginalRequestURL()); |
| if (reloadURL.is_empty()) { |
| DCHECK(self.webState && self.webState->HasOpener()); |
| reloadURL = lastNonRedirectItem->GetVirtualURL(); |
| } |
| |
| web::NavigationManager::WebLoadParams params(reloadURL); |
| params.referrer = lastNonRedirectItem->GetReferrer(); |
| params.transition_type = ui::PAGE_TRANSITION_RELOAD; |
| |
| switch (userAgentType) { |
| case web::UserAgentType::DESKTOP: |
| params.user_agent_override_option = |
| web::NavigationManager::UserAgentOverrideOption::DESKTOP; |
| break; |
| case web::UserAgentType::MOBILE: |
| params.user_agent_override_option = |
| web::NavigationManager::UserAgentOverrideOption::MOBILE; |
| break; |
| case web::UserAgentType::NONE: |
| NOTREACHED(); |
| } |
| |
| navigationManager->LoadURLWithParams(params); |
| } |
| |
| - (void)evaluateU2FResultFromURL:(const GURL&)URL { |
| DCHECK(_secondFactorController); |
| [_secondFactorController evaluateU2FResultFromU2FURL:URL |
| webState:self.webState]; |
| } |
| |
| #pragma mark - CRWWebControllerObserver protocol methods. |
| |
| - (void)webControllerWillClose:(CRWWebController*)webController { |
| DCHECK_EQ(webController, [self webController]); |
| [[self webController] removeObserver:self]; |
| [[self webController] setDelegate:nil]; |
| } |
| |
| #pragma mark - CRWWebDelegate and CRWWebStateObserver protocol methods. |
| |
| // This method is invoked whenever the system believes the URL is about to |
| // change, or immediately after any unexpected change of the URL. The apparent |
| // destination URL is included in the |url| parameter. |
| // Warning: because of the present design it is possible for malicious websites |
| // to invoke superflous instances of this delegate with artibrary URLs. |
| // Ensure there is nothing here that could be a risk to the user beyond mild |
| // confusion in that event (e.g. progress bar starting unexpectedly). |
| - (void)webWillAddPendingURL:(const GURL&)url |
| transition:(ui::PageTransition)transition { |
| DCHECK(self.webController.loadPhase == web::LOAD_REQUESTED); |
| DCHECK([self navigationManager]); |
| |
| // Move the toolbar to visible during page load. |
| [_fullScreenController disableFullScreen]; |
| |
| BOOL isUserNavigationEvent = |
| (transition & ui::PAGE_TRANSITION_IS_REDIRECT_MASK) == 0; |
| // Check for link-follow clobbers. These are changes where there is no |
| // pending entry (since that means the change wasn't caused by this class), |
| // and where the URL changes (to avoid counting page resurrection). |
| // TODO(crbug.com/546401): Consider moving this into NavigationManager, or |
| // into a NavigationManager observer callback, so it doesn't need to be |
| // checked in several places. |
| if (isUserNavigationEvent && !_isPrerenderTab && |
| ![self navigationManager]->GetPendingItem() && |
| url != self.lastCommittedURL) { |
| base::RecordAction(base::UserMetricsAction("MobileTabClobbered")); |
| if ([_parentTabModel tabUsageRecorder]) |
| [_parentTabModel tabUsageRecorder]->RecordPageLoadStart(self); |
| } |
| if (![self navigationManager]->GetPendingItem()) { |
| // Reset |isVoiceSearchResultsTab| since a new page is being navigated to. |
| self.isVoiceSearchResultsTab = NO; |
| } |
| } |
| |
| - (void)webState:(web::WebState*)webState |
| didStartNavigation:(web::NavigationContext*)navigation { |
| if ([_parentTabModel tabUsageRecorder] && |
| PageTransitionCoreTypeIs(navigation->GetPageTransition(), |
| ui::PAGE_TRANSITION_RELOAD)) { |
| [_parentTabModel tabUsageRecorder]->RecordReload(self); |
| } |
| |
| [self.dialogDelegate cancelDialogForTab:self]; |
| [_parentTabModel notifyTabChanged:self]; |
| [_openInController disable]; |
| [[NSNotificationCenter defaultCenter] |
| postNotificationName: |
| kTabClosingCurrentDocumentNotificationForCrashReporting |
| object:self]; |
| } |
| |
| - (void)webState:(web::WebState*)webState |
| didCommitNavigationWithDetails:(const web::LoadCommittedDetails&)details { |
| DCHECK([self navigationManager]); |
| // |webWillAddPendingURL:transition:| is not called for native page loads. |
| // TODO(crbug.com/381201): Move this call there once that bug is fixed so that |
| // |disableFullScreen| is called only from one place. |
| [_fullScreenController disableFullScreen]; |
| GURL lastCommittedURL = webState->GetLastCommittedURL(); |
| [_autoReloadBridge loadStartedForURL:lastCommittedURL]; |
| |
| if (_parentTabModel) { |
| [[NSNotificationCenter defaultCenter] |
| postNotificationName:kTabModelTabWillStartLoadingNotification |
| object:_parentTabModel |
| userInfo:@{kTabModelTabKey : self}]; |
| } |
| favicon::FaviconDriver* faviconDriver = |
| favicon::WebFaviconDriver::FromWebState(webState); |
| if (faviconDriver) |
| faviconDriver->FetchFavicon(lastCommittedURL); |
| [_parentTabModel notifyTabChanged:self]; |
| if (_parentTabModel) { |
| [[NSNotificationCenter defaultCenter] |
| postNotificationName:kTabModelTabDidStartLoadingNotification |
| object:_parentTabModel |
| userInfo:@{kTabModelTabKey : self}]; |
| } |
| |
| web::NavigationItem* previousItem = nullptr; |
| if (details.previous_item_index >= 0) { |
| previousItem = webState->GetNavigationManager()->GetItemAtIndex( |
| details.previous_item_index); |
| } |
| |
| [_parentTabModel navigationCommittedInTab:self 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. |
| NSString* URLSpec = base::SysUTF8ToNSString(lastCommittedURL.spec()); |
| if (URLSpec.length) { |
| [[NSNotificationCenter defaultCenter] |
| postNotificationName:kTabUrlStartedLoadingNotificationForCrashReporting |
| object:self |
| userInfo:@{kTabUrlKey : URLSpec}]; |
| } |
| } |
| |
| - (void)webState:(web::WebState*)webState |
| didLoadPageWithSuccess:(BOOL)loadSuccess { |
| DCHECK(self.webController.loadPhase == web::PAGE_LOADED); |
| |
| // Cancel prerendering if response is "application/octet-stream". It can be a |
| // video file which should not be played from preload tab (crbug.com/436813). |
| if (_isPrerenderTab && |
| self.webState->GetContentsMimeType() == "application/octet-stream") { |
| [delegate_ discardPrerender]; |
| } |
| |
| bool wasPost = false; |
| GURL lastCommittedURL; |
| web::NavigationItem* lastCommittedItem = |
| [self navigationManager]->GetLastCommittedItem(); |
| if (lastCommittedItem) { |
| wasPost = lastCommittedItem->HasPostData(); |
| lastCommittedURL = lastCommittedItem->GetVirtualURL(); |
| } |
| if (loadSuccess) |
| [_autoReloadBridge loadFinishedForURL:lastCommittedURL wasPost:wasPost]; |
| else |
| [_autoReloadBridge loadFailedForURL:lastCommittedURL wasPost:wasPost]; |
| [_webControllerSnapshotHelper setSnapshotCoalescingEnabled:YES]; |
| if (!loadSuccess) |
| [_fullScreenController disableFullScreen]; |
| [self recordInterfaceOrientation]; |
| navigation_metrics::RecordMainFrameNavigation( |
| lastCommittedURL, true, self.browserState->IsOffTheRecord()); |
| |
| if (loadSuccess) { |
| scoped_refptr<net::HttpResponseHeaders> headers = |
| _webStateImpl->GetHttpResponseHeaders(); |
| [self handleExportableFile:headers.get()]; |
| } |
| |
| [_parentTabModel notifyTabChanged:self]; |
| |
| if (_parentTabModel) { |
| if ([_parentTabModel tabUsageRecorder]) |
| [_parentTabModel tabUsageRecorder]->RecordPageLoadDone(self, loadSuccess); |
| [[NSNotificationCenter defaultCenter] |
| postNotificationName:kTabModelTabDidFinishLoadingNotification |
| object:_parentTabModel |
| userInfo:[NSDictionary |
| dictionaryWithObjectsAndKeys: |
| self, kTabModelTabKey, |
| [NSNumber numberWithBool:loadSuccess], |
| kTabModelPageLoadSuccess, nil]]; |
| } |
| [[OmniboxGeolocationController sharedInstance] |
| finishPageLoadForTab:self |
| loadSuccess:loadSuccess]; |
| |
| if (loadSuccess) |
| [self updateSnapshotWithOverlay:YES visibleFrameOnly:YES]; |
| [_webControllerSnapshotHelper setSnapshotCoalescingEnabled:NO]; |
| } |
| |
| - (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. |
| [_parentTabModel notifyTabChanged:self]; |
| } |
| |
| - (void)webStateDidChangeTitle:(web::WebState*)webState { |
| [self saveTitleToHistoryDB]; |
| [_parentTabModel notifyTabChanged:self]; |
| } |
| |
| - (void)webStateDidDismissInterstitial:(web::WebState*)webState { |
| [_parentTabModel notifyTabChanged:self]; |
| } |
| |
| - (void)webStateDidStopLoading:(web::WebState*)webState { |
| // This is the maximum that a page will ever load and it is safe to allow |
| // fullscreen mode. |
| [_fullScreenController enableFullScreen]; |
| [_parentTabModel notifyTabChanged:self]; |
| } |
| |
| - (BOOL)webController:(CRWWebController*)webController |
| shouldOpenExternalURL:(const GURL&)URL { |
| if (_isPrerenderTab && !isLinkLoadingPrerenderTab_) { |
| [delegate_ discardPrerender]; |
| return NO; |
| } |
| return YES; |
| } |
| |
| - (BOOL)urlTriggersNativeAppLaunch:(const GURL&)url |
| sourceURL:(const GURL&)sourceURL |
| linkClicked:(BOOL)linkClicked { |
| // TODO(crbug/711511): If Native App Launcher is not enabled, returning NO |
| // bypasses all Link Navigation logic. This call should eventually be |
| // eliminated. |
| if (!experimental_flags::IsNativeAppLauncherEnabled()) |
| return NO; |
| |
| // Don't open any native app directly when prerendering or from Incognito. |
| if (_isPrerenderTab || self.browserState->IsOffTheRecord()) |
| return NO; |
| |
| id<NativeAppMetadata> metadata = |
| [ios::GetChromeBrowserProvider()->GetNativeAppWhitelistManager() |
| nativeAppForURL:url]; |
| if (![metadata shouldAutoOpenLinks]) |
| return NO; |
| |
| AuthenticationService* authenticationService = |
| AuthenticationServiceFactory::GetForBrowserState(self.browserState); |
| ChromeIdentity* identity = authenticationService->GetAuthenticatedIdentity(); |
| |
| // Attempts to open external app without x-callback. |
| if ([self openExternalURL:[metadata launchURLWithURL:url identity:identity] |
| sourceURL:sourceURL |
| linkClicked:linkClicked]) { |
| return YES; |
| } |
| |
| // Auto-open didn't work. Reset the auto-open flag. |
| [metadata unsetShouldAutoOpenLinks]; |
| return NO; |
| } |
| |
| - (double)lastVisitedTimestamp { |
| return _lastVisitedTimestamp; |
| } |
| |
| - (void)updateLastVisitedTimestamp { |
| _lastVisitedTimestamp = [[NSDate date] timeIntervalSince1970]; |
| } |
| |
| - (infobars::InfoBarManager*)infoBarManager { |
| DCHECK(self.webState); |
| return InfoBarManagerImpl::FromWebState(self.webState); |
| } |
| |
| - (NSArray*)snapshotOverlays { |
| return [snapshotOverlayProvider_ snapshotOverlaysForTab:self]; |
| } |
| |
| - (void)webViewRemoved { |
| [_openInController disable]; |
| } |
| |
| - (BOOL)webController:(CRWWebController*)webController |
| shouldOpenURL:(const GURL&)url |
| mainDocumentURL:(const GURL&)mainDocumentURL |
| linkClicked:(BOOL)linkClicked { |
| // chrome:// URLs are only allowed if the mainDocumentURL is also a chrome:// |
| // URL. |
| if (url.SchemeIs(kChromeUIScheme) && |
| !mainDocumentURL.SchemeIs(kChromeUIScheme)) { |
| return NO; |
| } |
| |
| // Always allow frame loads. |
| BOOL isFrameLoad = (url != mainDocumentURL); |
| if (isFrameLoad) |
| return YES; |
| |
| // TODO(crbug.com/546402): If this turns out to be useful, find a less hacky |
| // hook point to send this from. |
| NSString* urlString = base::SysUTF8ToNSString(url.spec()); |
| if ([urlString length]) { |
| [[NSNotificationCenter defaultCenter] |
| postNotificationName:kTabUrlMayStartLoadingNotificationForCrashReporting |
| object:self |
| userInfo:[NSDictionary dictionaryWithObject:urlString |
| forKey:kTabUrlKey]]; |
| } |
| |
| return YES; |
| } |
| |
| - (void)webController:(CRWWebController*)webController |
| retrievePlaceholderOverlayImage:(void (^)(UIImage*))block { |
| NSString* sessionID = self.tabId; |
| // The snapshot is always grey, even if |useGreyImageCache_| is NO, as this |
| // overlay represents an out-of-date website and is shown only until the |
| // has begun loading. However, if |useGreyImageCache_| is YES, the grey image |
| // is already cached in memory for swiping, and a cache miss is acceptable. |
| // In other cases, such as during startup, either disk access or a greyspace |
| // conversion is required, as there will be no grey snapshots in memory. |
| if (useGreyImageCache_) { |
| [_snapshotManager greyImageForSessionID:sessionID callback:block]; |
| } else { |
| [_webControllerSnapshotHelper |
| retrieveGreySnapshotForWebController:webController |
| sessionID:sessionID |
| withOverlays:[self snapshotOverlays] |
| callback:block]; |
| } |
| } |
| |
| - (UIImage*)updateSnapshotWithOverlay:(BOOL)shouldAddOverlay |
| visibleFrameOnly:(BOOL)visibleFrameOnly { |
| NSArray* overlays = shouldAddOverlay ? [self snapshotOverlays] : nil; |
| UIImage* snapshot = [_webControllerSnapshotHelper |
| updateSnapshotForWebController:self.webController |
| sessionID:self.tabId |
| withOverlays:overlays |
| visibleFrameOnly:visibleFrameOnly]; |
| [_parentTabModel notifyTabSnapshotChanged:self withImage:snapshot]; |
| return snapshot; |
| } |
| |
| - (UIImage*)generateSnapshotWithOverlay:(BOOL)shouldAddOverlay |
| visibleFrameOnly:(BOOL)visibleFrameOnly { |
| NSArray* overlays = shouldAddOverlay ? [self snapshotOverlays] : nil; |
| return [_webControllerSnapshotHelper |
| generateSnapshotForWebController:self.webController |
| withOverlays:overlays |
| visibleFrameOnly:visibleFrameOnly]; |
| } |
| |
| - (void)setSnapshotCoalescingEnabled:(BOOL)snapshotCoalescingEnabled { |
| [_webControllerSnapshotHelper |
| setSnapshotCoalescingEnabled:snapshotCoalescingEnabled]; |
| } |
| |
| - (CGRect)snapshotContentArea { |
| CGRect snapshotContentArea = CGRectZero; |
| if (self.tabSnapshottingDelegate) { |
| snapshotContentArea = |
| [self.tabSnapshottingDelegate snapshotContentAreaForTab:self]; |
| } else { |
| UIEdgeInsets visiblePageInsets = UIEdgeInsetsMake( |
| [self headerHeightForWebController:self.webController], 0.0, 0.0, 0.0); |
| snapshotContentArea = UIEdgeInsetsInsetRect(self.webController.view.bounds, |
| visiblePageInsets); |
| } |
| return snapshotContentArea; |
| } |
| |
| - (void)willUpdateSnapshot { |
| if ([[self.webController nativeController] |
| respondsToSelector:@selector(willUpdateSnapshot)]) { |
| [[self.webController nativeController] willUpdateSnapshot]; |
| } |
| [_overscrollActionsController clear]; |
| } |
| |
| - (void)webStateDidSuppressDialog:(web::WebState*)webState { |
| DCHECK(_isPrerenderTab); |
| [delegate_ discardPrerender]; |
| } |
| |
| - (CGFloat)headerHeightForWebController:(CRWWebController*)webController { |
| return [self.tabHeadersDelegate headerHeightForTab:self]; |
| } |
| |
| - (void)webStateDidChangeVisibleSecurityState:(web::WebState*)webState { |
| // Disable fullscreen if SSL cert is invalid. |
| web::NavigationItem* item = [self navigationManager]->GetTransientItem(); |
| if (item) { |
| web::SecurityStyle securityStyle = item->GetSSL().security_style; |
| if (securityStyle == web::SECURITY_STYLE_AUTHENTICATION_BROKEN) { |
| [_fullScreenController disableFullScreen]; |
| } |
| } |
| |
| [_parentTabModel notifyTabChanged:self]; |
| [self updateFullscreenWithToolbarVisible:YES]; |
| } |
| |
| - (void)renderProcessGoneForWebState:(web::WebState*)webState { |
| UIApplicationState state = [UIApplication sharedApplication].applicationState; |
| BOOL applicationIsNotActive = IsApplicationStateNotActive(state); |
| if (_browserState && !_browserState->IsOffTheRecord()) { |
| // Log the tab state for the termination. |
| RendererTerminationTabState tab_state = |
| _visible ? RendererTerminationTabState::FOREGROUND_TAB_FOREGROUND_APP |
| : RendererTerminationTabState::BACKGROUND_TAB_FOREGROUND_APP; |
| if (applicationIsNotActive) { |
| tab_state = |
| _visible ? RendererTerminationTabState::FOREGROUND_TAB_BACKGROUND_APP |
| : RendererTerminationTabState::BACKGROUND_TAB_BACKGROUND_APP; |
| } |
| UMA_HISTOGRAM_ENUMERATION( |
| kRendererTerminationStateHistogram, static_cast<int>(tab_state), |
| static_cast<int>( |
| RendererTerminationTabState::TERMINATION_TAB_STATE_COUNT)); |
| if ([_parentTabModel tabUsageRecorder]) |
| [_parentTabModel tabUsageRecorder]->RendererTerminated(self, _visible); |
| } |
| |
| if (_visible) { |
| if (!applicationIsNotActive) |
| [_fullScreenController disableFullScreen]; |
| } else { |
| [self.webController requirePageReload]; |
| } |
| // Returning to the app (after the renderer crashed in the background) and |
| // having the page reload is much less confusing for the user. |
| // Note: Given that the tab is visible, calling |requirePageReload| will not |
| // work when the app becomes active because there is nothing to trigger |
| // a view redisplay in that scenario. |
| _requireReloadAfterBecomingActive = _visible && applicationIsNotActive; |
| [self.dialogDelegate cancelDialogForTab:self]; |
| } |
| |
| - (void)webController:(CRWWebController*)webController |
| didLoadPassKitObject:(NSData*)data { |
| [self.passKitDialogProvider presentPassKitDialog:data]; |
| } |
| |
| #pragma mark - PrerenderDelegate |
| |
| - (void)discardPrerender { |
| DCHECK(_isPrerenderTab); |
| [delegate_ discardPrerender]; |
| } |
| |
| - (BOOL)isPrerenderTab { |
| return _isPrerenderTab; |
| } |
| |
| #pragma mark - ManageAccountsDelegate |
| |
| - (void)onManageAccounts { |
| if (_isPrerenderTab) { |
| [delegate_ discardPrerender]; |
| return; |
| } |
| if (self != [_parentTabModel currentTab]) |
| return; |
| |
| signin_metrics::LogAccountReconcilorStateOnGaiaResponse( |
| ios::AccountReconcilorFactory::GetForBrowserState(_browserState) |
| ->GetState()); |
| GenericChromeCommand* command = |
| [[GenericChromeCommand alloc] initWithTag:IDC_SHOW_ACCOUNTS_SETTINGS]; |
| [self.view chromeExecuteCommand:command]; |
| } |
| |
| - (void)onAddAccount { |
| if (_isPrerenderTab) { |
| [delegate_ discardPrerender]; |
| return; |
| } |
| if (self != [_parentTabModel currentTab]) |
| return; |
| |
| signin_metrics::LogAccountReconcilorStateOnGaiaResponse( |
| ios::AccountReconcilorFactory::GetForBrowserState(_browserState) |
| ->GetState()); |
| GenericChromeCommand* command = |
| [[GenericChromeCommand alloc] initWithTag:IDC_SHOW_ADD_ACCOUNT]; |
| [self.view chromeExecuteCommand:command]; |
| } |
| |
| - (void)onGoIncognito:(const GURL&)url { |
| if (_isPrerenderTab) { |
| [delegate_ discardPrerender]; |
| return; |
| } |
| if (self != [_parentTabModel currentTab]) |
| return; |
| |
| // The user taps on go incognito from the mobile U-turn webpage (the web page |
| // that displays all users accounts available in the content area). As the |
| // user chooses to go to incognito, the mobile U-turn page is no longer |
| // neeeded. The current solution is to go back in history. This has the |
| // advantage of keeping the current browsing session and give a good user |
| // experience when the user comes back from incognito. |
| [self goBack]; |
| |
| if (url.is_valid()) { |
| OpenUrlCommand* command = [[OpenUrlCommand alloc] |
| initWithURL:url |
| referrer:web::Referrer() // Strip referrer when switching modes. |
| inIncognito:YES |
| inBackground:NO |
| appendTo:kLastTab]; |
| [self.view chromeExecuteCommand:command]; |
| } else { |
| GenericChromeCommand* command = |
| [[GenericChromeCommand alloc] initWithTag:IDC_NEW_INCOGNITO_TAB]; |
| [self.view chromeExecuteCommand:command]; |
| } |
| } |
| |
| - (NativeAppNavigationController*)nativeAppNavigationController { |
| // TODO(crbug.com/711511): If Native App Launcher is not enabled, simply |
| // return nil here. This method should eventually be eliminated. |
| if (!experimental_flags::IsNativeAppLauncherEnabled()) |
| return nil; |
| return _nativeAppNavigationController; |
| } |
| |
| - (void)initNativeAppNavigationController { |
| if (_browserState->IsOffTheRecord()) |
| return; |
| DCHECK(!_nativeAppNavigationController); |
| _nativeAppNavigationController = |
| [[NativeAppNavigationController alloc] initWithWebState:self.webState]; |
| DCHECK(_nativeAppNavigationController); |
| } |
| |
| - (void)wasShown { |
| _visible = YES; |
| [self updateFullscreenWithToolbarVisible:YES]; |
| [self.webController wasShown]; |
| [_inputAccessoryViewController wasShown]; |
| } |
| |
| - (void)wasHidden { |
| _visible = NO; |
| [self updateFullscreenWithToolbarVisible:YES]; |
| [self.webController wasHidden]; |
| [_inputAccessoryViewController wasHidden]; |
| } |
| |
| #pragma mark - SadTabTabHelperDelegate |
| |
| - (BOOL)isTabVisibleForTabHelper:(SadTabTabHelper*)tabHelper { |
| UIApplicationState state = UIApplication.sharedApplication.applicationState; |
| return _visible && !IsApplicationStateNotActive(state); |
| } |
| |
| @end |
| |
| #pragma mark - TestingSupport |
| |
| @implementation Tab (TestingSupport) |
| |
| - (void)replaceExternalAppLauncher:(id)externalAppLauncher { |
| _externalAppLauncher = externalAppLauncher; |
| } |
| |
| - (TabModel*)parentTabModel { |
| return _parentTabModel; |
| } |
| |
| - (FormInputAccessoryViewController*)inputAccessoryViewController { |
| return _inputAccessoryViewController; |
| } |
| |
| @end |