Index: ios/chrome/browser/tabs/tab.mm |
diff --git a/ios/chrome/browser/tabs/tab.mm b/ios/chrome/browser/tabs/tab.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..6c7d0c2b988b7ccc4dff1a19bd58dc0d69fa091d |
--- /dev/null |
+++ b/ios/chrome/browser/tabs/tab.mm |
@@ -0,0 +1,2451 @@ |
+// 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" |
+#import "base/ios/weak_nsobject.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/mac/objc_property_releaser.h" |
+#include "base/mac/scoped_nsobject.h" |
+#include "base/metrics/histogram.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/content_settings/core/browser/host_content_settings_map.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/navigation_metrics/origins_seen_service.h" |
+#include "components/prefs/pref_service.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/bookmarks/bookmark_model_factory.h" |
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
+#include "ios/chrome/browser/chrome_url_constants.h" |
+#include "ios/chrome/browser/content_settings/host_content_settings_map_factory.h" |
+#import "ios/chrome/browser/crash_loop_detection_util.h" |
+#include "ios/chrome/browser/experimental_flags.h" |
+#include "ios/chrome/browser/favicon/favicon_service_factory.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" |
+#include "ios/chrome/browser/metrics/ios_chrome_origins_seen_service_factory.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/net/metrics_network_client_manager.h" |
+#import "ios/chrome/browser/passwords/credential_manager.h" |
+#import "ios/chrome/browser/passwords/js_credential_manager.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/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" |
+#include "ios/chrome/browser/ssl/ios_security_state_tab_helper.h" |
+#import "ios/chrome/browser/storekit_launcher.h" |
+#include "ios/chrome/browser/sync/ios_chrome_synced_tab_delegate.h" |
+#import "ios/chrome/browser/tabs/tab_delegate.h" |
+#import "ios/chrome/browser/tabs/tab_dialog_delegate.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" |
+#import "ios/chrome/browser/ui/sad_tab/sad_tab_view.h" |
+#include "ios/chrome/browser/ui/ui_util.h" |
+#import "ios/chrome/browser/web/auto_reload_bridge.h" |
+#import "ios/chrome/browser/web/blocked_popup_handler.h" |
+#import "ios/chrome/browser/web/external_app_launcher.h" |
+#include "ios/chrome/browser/web/network_activity_indicator_tab_helper.h" |
+#import "ios/chrome/browser/web/passkit_dialog_provider.h" |
+#include "ios/chrome/browser/web/print_observer.h" |
+#import "ios/chrome/browser/web/resubmit_data_controller.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/crw_session_controller.h" |
+#import "ios/web/navigation/crw_session_entry.h" |
+#import "ios/web/navigation/navigation_item_impl.h" |
+#import "ios/web/navigation/navigation_manager_impl.h" |
+#include "ios/web/net/request_tracker_impl.h" |
+#include "ios/web/public/favicon_status.h" |
+#include "ios/web/public/favicon_url.h" |
+#include "ios/web/public/interstitials/web_interstitial.h" |
+#import "ios/web/public/navigation_manager.h" |
+#include "ios/web/public/referrer.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/crw_web_user_interface_delegate.h" |
+#import "ios/web/public/web_state/js/crw_js_injection_receiver.h" |
+#import "ios/web/public/web_state/ui/crw_generic_content_view.h" |
+#include "ios/web/public/web_state/web_state.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" |
+ |
+using base::UserMetricsAction; |
+using web::NavigationManagerImpl; |
+using net::RequestTracker; |
+ |
+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; |
+ |
+// 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 |
+}; |
+} // namespace |
+ |
+@interface Tab ()<BlockedPopupHandlerDelegate, |
+ CRWWebUserInterfaceDelegate, |
+ FindInPageControllerDelegate, |
+ ReaderModeControllerDelegate> { |
+ TabModel* parentTabModel_; // weak |
+ ios::ChromeBrowserState* browserState_; // weak |
+ |
+ base::scoped_nsobject<OpenInController> openInController_; |
+ base::WeakNSProtocol<id<PassKitDialogProvider>> passKitDialogProvider_; |
+ // TODO(crbug.com/546213): Move this out of Tab, probably to native app |
+ // launcher since that's the only thing that uses it. |
+ base::WeakNSProtocol<id<StoreKitLauncher>> storeKitLauncher_; |
+ |
+ // Whether or not this tab is currently being displayed. |
+ BOOL visible_; |
+ |
+ // Used between -webWillStartLoadingURL: and -webDidStartLoadingURL:. |
+ BOOL isUserNavigationEvent_; |
+ |
+ // 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_; |
+ |
+ base::mac::ObjCPropertyReleaser propertyReleaser_Tab_; |
+ |
+ id<TabDelegate> delegate_; // weak |
+ base::WeakNSProtocol<id<TabDialogDelegate>> dialogDelegate_; |
+ base::WeakNSProtocol<id<SnapshotOverlayProvider>> snapshotOverlayProvider_; |
+ |
+ // Delegate used for snapshotting geometry. |
+ id<TabSnapshottingDelegate> tabSnapshottingDelegate_; // weak |
+ |
+ // The Full Screen Controller responsible for hiding/showing the toolbar. |
+ base::scoped_nsobject<FullScreenController> fullScreenController_; |
+ |
+ base::WeakNSProtocol<id<FullScreenControllerDelegate>> |
+ fullScreenControllerDelegate_; |
+ |
+ // The Overscroll controller responsible for displaying the |
+ // overscrollActionsView above the toolbar. |
+ base::scoped_nsobject<OverscrollActionsController> |
+ overscrollActionsController_; |
+ base::WeakNSProtocol<id<OverscrollActionsControllerDelegate>> |
+ overscrollActionsControllerDelegate_; |
+ |
+ // Lightweight object dealing with various different UI behaviours when |
+ // opening a URL in an external application. |
+ base::scoped_nsobject<ExternalAppLauncher> externalAppLauncher_; |
+ |
+ // Handles suggestions for form entry. |
+ base::scoped_nsobject<FormSuggestionController> suggestionController_; |
+ |
+ // Manages the input accessory view during form input. |
+ base::scoped_nsobject<FormInputAccessoryViewController> |
+ inputAccessoryViewController_; |
+ |
+ // TODO(crbug.com/661665): move the WebContentsObservers into their own |
+ // container. |
+ // Handles saving and autofill of passwords. |
+ base::scoped_nsobject<PasswordController> passwordController_; |
+ |
+ // Handles autofill. |
+ base::scoped_nsobject<AutofillController> autofillController_; |
+ |
+ // The popup blocker to show blocked popup to the user. |
+ std::unique_ptr<BlockedPopupHandler> popupHandler_; |
+ |
+ // Handles find on page. |
+ base::scoped_nsobject<FindInPageController> findInPageController_; |
+ |
+ // Handles GAL infobar on web pages. |
+ base::scoped_nsobject<NativeAppNavigationController> |
+ nativeAppNavigationController_; |
+ |
+ // Handles caching and retrieving of snapshots. |
+ base::scoped_nsobject<SnapshotManager> snapshotManager_; |
+ |
+ // Handles retrieving, generating and updating snapshots of CRWWebController's |
+ // web page. |
+ base::scoped_nsobject<WebControllerSnapshotHelper> |
+ webControllerSnapshotHelper_; |
+ |
+ // The controller that displays an action sheet to confirm form data |
+ // resubmission. |
+ base::scoped_nsobject<ResubmitDataController> resubmitDataController_; |
+ |
+ // Number of attempts to show the resubmit data action sheet. |
+ NSUInteger showResubmitDataActionSheetAttempt_; |
+ |
+ // Handles support for window.print JavaScript calls. |
+ std::unique_ptr<PrintObserver> printObserver_; |
+ |
+ // AutoReloadBridge for this tab. |
+ base::scoped_nsobject<AutoReloadBridge> autoReloadBridge_; |
+ |
+ // WebStateImpl for this tab. |
+ std::unique_ptr<web::WebStateImpl> webStateImpl_; |
+ |
+ // Context used by history to scope the lifetime of navigation entry |
+ // references to Tab. |
+ std::unique_ptr<TabHistoryContext> tabHistoryContext_; |
+ |
+ // The controller for everything related to reader mode. |
+ base::scoped_nsobject<ReaderModeController> readerModeController_; |
+ |
+ // C++ bridge that receives notifications from the FaviconDriver. |
+ std::unique_ptr<FaviconDriverObserverBridge> faviconDriverObserverBridge_; |
+ |
+ // U2F call controller object. |
+ base::scoped_nsobject<U2FController> U2FController_; |
+ |
+ // C++ observer used to trigger snapshots after the removal of InfoBars. |
+ std::unique_ptr<TabInfoBarObserver> tabInfoBarObserver_; |
+ |
+ // C++ observer to implement the credential management JavaScript API. |
+ std::unique_ptr<CredentialManager> credentialManager_; |
+ |
+ // Client factory created for metrics tracking. The Tab will signal page |
+ // load starts and finishes to this. |
+ base::scoped_nsobject<MetricsNetworkClientManager> metricsClientManager_; |
+} |
+ |
+// Returns the current sessionEntry for the sesionController associated with |
+// this tab. Don't use this to get the underlying NavigationItem; instead |
+// go through the NavigationManager. |
+// This is nil if there's no NavigationManager. |
+@property(nonatomic, readonly) CRWSessionEntry* currentSessionEntry; |
+ |
+// 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; |
+ |
+// Updates the title field of the current session entry. Also updates the |
+// history database. |
+- (void)updateTitle:(NSString*)title; |
+ |
+// Saves the current title to the history database. |
+- (void)saveTitleToHistoryDB; |
+ |
+// Returns a lazily instantiated popup handler. |
+- (BlockedPopupHandler*)popupHandler; |
+ |
+// 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; |
+ |
+// Calls the model and ask to close this tab. |
+- (void)closeThisTab; |
+ |
+// Shows the ResubmitDataActionSheet to the user to allow the user to make a |
+// choice. |
+- (void)showResubmitDataActionSheet; |
+ |
+// Clears the ResubmitDataActionSheet from the UI. |
+- (void)clearResubmitDataActionSheet; |
+ |
+// Initialize the Native App Launcher controller. |
+- (void)initNativeAppNavigationController; |
+ |
+// YES if toEntry is behind fromEntry in the current history stack. |
+- (BOOL)navigationIsBackwards:(const CRWSessionEntry*)fromEntry |
+ toEntry:(const CRWSessionEntry*)toEntry; |
+ |
+// Opens a link in an external app. Returns YES iff |url| is launched in an |
+// external app. |
+- (BOOL)openExternalURL:(const GURL&)url linkClicked:(BOOL)linkClicked; |
+ |
+// 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 { |
+// 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); |
+}; |
+ |
+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: |
+ Tab* owner_; // Owns this instance. |
+ 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: |
+ Tab* owner_; // Owns this instance; |
+ 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]; |
+} |
+ |
+// Registers |factory| with |tracker| on the IO thread. |
+void AddNetworkClientFactoryOnIOThread( |
+ web::RequestTrackerImpl* tracker, |
+ CRNForwardingNetworkClientFactory* factory) { |
+ base::scoped_nsobject<CRNForwardingNetworkClientFactory> scoped_factory( |
+ [factory retain]); |
+ tracker->PostIOTask(base::Bind(&net::RequestTracker::AddNetworkClientFactory, |
+ tracker, scoped_factory)); |
+} |
+ |
+} // anonymous namespace |
+ |
+@implementation Tab |
+ |
+@synthesize browserState = browserState_; |
+@synthesize useGreyImageCache = useGreyImageCache_; |
+@synthesize isPrerenderTab = isPrerenderTab_; |
+@synthesize isLinkLoadingPrerenderTab = isLinkLoadingPrerenderTab_; |
+@synthesize isVoiceSearchResultsTab = isVoiceSearchResultsTab_; |
+@synthesize delegate = delegate_; |
+@synthesize tabSnapshottingDelegate = tabSnapshottingDelegate_; |
+ |
+- (instancetype)initWithWindowName:(NSString*)windowName |
+ opener:(Tab*)opener |
+ openedByDOM:(BOOL)openedByDOM |
+ model:(TabModel*)parentModel |
+ browserState:(ios::ChromeBrowserState*)browserState { |
+ NSInteger openerIndex = -1; |
+ if ([opener navigationManager]) { |
+ NavigationManagerImpl* openerNavManager = [opener navigationManager]; |
+ openerIndex = openerNavManager->GetLastCommittedItemIndex(); |
+ } |
+ std::unique_ptr<web::WebStateImpl> webState( |
+ new web::WebStateImpl(browserState)); |
+ webState->GetNavigationManagerImpl().InitializeSession( |
+ windowName, [opener currentSessionID], openedByDOM, openerIndex); |
+ |
+ return [self initWithWebState:std::move(webState) model:parentModel]; |
+} |
+ |
+- (instancetype)initWithWebState:(std::unique_ptr<web::WebState>)webState |
+ model:(TabModel*)parentModel { |
+ DCHECK(webState); |
+ self = [super init]; |
+ if (self) { |
+ propertyReleaser_Tab_.Init(self, [Tab class]); |
+ tabHistoryContext_.reset(new TabHistoryContext()); |
+ parentTabModel_ = parentModel; |
+ browserState_ = |
+ ios::ChromeBrowserState::FromBrowserState(webState->GetBrowserState()); |
+ |
+ webStateImpl_.reset(static_cast<web::WebStateImpl*>(webState.release())); |
+ [self.webController setDelegate:self]; |
+ [self.webController setUIDelegate:self]; |
+ |
+ NSString* sessionID = [self currentSessionID]; |
+ DCHECK(sessionID); |
+ snapshotManager_.reset([[SnapshotManager alloc] init]); |
+ |
+ webControllerSnapshotHelper_.reset([[WebControllerSnapshotHelper alloc] |
+ initWithSnapshotManager:snapshotManager_ |
+ tab:self]); |
+ |
+ findInPageController_.reset([[FindInPageController alloc] |
+ initWithWebState:self.webState |
+ delegate:self]); |
+ |
+ [self initNativeAppNavigationController]; |
+ // IOSChromeSessionTabHelper comes first because it sets up the tab ID, and |
+ // other helpers may rely on that. |
+ IOSChromeSessionTabHelper::CreateForWebState(self.webState); |
+ NetworkActivityIndicatorTabHelper::CreateForWebState(self.webState, |
+ self.tabId); |
+ IOSChromeSyncedTabDelegate::CreateForWebState(self.webState); |
+ InfoBarManagerImpl::CreateForWebState(self.webState); |
+ IOSSecurityStateTabHelper::CreateForWebState(self.webState); |
+ |
+ tabInfoBarObserver_.reset(new TabInfoBarObserver(self)); |
+ tabInfoBarObserver_->SetShouldObserveInfoBarManager(true); |
+ |
+ if (AccountConsistencyService* account_consistency_service = |
+ ios::AccountConsistencyServiceFactory::GetForBrowserState( |
+ browserState_)) { |
+ account_consistency_service->SetWebStateHandler(self.webState, self); |
+ } |
+ ChromeIOSTranslateClient::CreateForWebState(self.webState); |
+ if (experimental_flags::IsAutoReloadEnabled()) { |
+ autoReloadBridge_.reset([[AutoReloadBridge alloc] initWithTab:self]); |
+ } |
+ printObserver_.reset(new PrintObserver(self.webState)); |
+ |
+ base::scoped_nsprotocol<id<PasswordsUiDelegate>> passwordsUiDelegate( |
+ [[PasswordsUiDelegateImpl alloc] init]); |
+ passwordController_.reset([[PasswordController alloc] |
+ initWithWebState:self.webState |
+ passwordsUiDelegate:passwordsUiDelegate]); |
+ password_manager::PasswordGenerationManager* passwordGenerationManager = |
+ [passwordController_ passwordGenerationManager]; |
+ autofillController_.reset([[AutofillController alloc] |
+ initWithBrowserState:browserState_ |
+ passwordGenerationManager:passwordGenerationManager |
+ webState:self.webState]); |
+ suggestionController_.reset([[FormSuggestionController alloc] |
+ initWithWebState:self.webState |
+ providers:[self suggestionProviders]]); |
+ inputAccessoryViewController_.reset( |
+ [[FormInputAccessoryViewController alloc] |
+ initWithWebState:self.webState |
+ providers:[self accessoryViewProviders]]); |
+ if (experimental_flags::IsCredentialManagementEnabled()) { |
+ credentialManager_.reset(new CredentialManager( |
+ self.webState, [passwordController_ passwordManagerClient], |
+ [passwordController_ passwordManagerDriver], |
+ base::mac::ObjCCastStrict<JSCredentialManager>( |
+ [self.webState->GetJSInjectionReceiver() |
+ instanceOfClass:[JSCredentialManager class]]))); |
+ } |
+ |
+ ios::ChromeBrowserState* original_browser_state = |
+ ios::ChromeBrowserState::FromBrowserState( |
+ self.webState->GetBrowserState()) |
+ ->GetOriginalChromeBrowserState(); |
+ favicon::WebFaviconDriver::CreateForWebState( |
+ self.webState, |
+ ios::FaviconServiceFactory::GetForBrowserState( |
+ original_browser_state, ServiceAccessType::IMPLICIT_ACCESS), |
+ ios::HistoryServiceFactory::GetForBrowserState( |
+ original_browser_state, ServiceAccessType::IMPLICIT_ACCESS), |
+ ios::BookmarkModelFactory::GetForBrowserState(original_browser_state)); |
+ history::WebStateTopSitesObserver::CreateForWebState( |
+ self.webState, |
+ ios::TopSitesFactory::GetForBrowserState(original_browser_state).get()); |
+ [self setShouldObserveFaviconChanges:YES]; |
+ web::RequestTrackerImpl* requestTracker = |
+ webStateImpl_->GetRequestTracker(); |
+ |
+ metricsClientManager_.reset([[MetricsNetworkClientManager alloc] init]); |
+ AddNetworkClientFactoryOnIOThread(requestTracker, metricsClientManager_); |
+ |
+ if (parentModel && parentModel.syncedWindowDelegate) { |
+ IOSChromeSessionTabHelper::FromWebState(self.webState) |
+ ->SetWindowID(parentModel.sessionID); |
+ } |
+ |
+ // Create the ReaderModeController immediately so it can register for |
+ // WebState changes. |
+ if (experimental_flags::IsReaderModeEnabled()) { |
+ readerModeController_.reset([[ReaderModeController alloc] |
+ initWithWebState:self.webState |
+ delegate:self]); |
+ } |
+ |
+ // Allow the embedder to attach tab helpers. |
+ ios::GetChromeBrowserProvider()->AttachTabHelpers(self.webState, self); |
+ |
+ [[NSNotificationCenter defaultCenter] |
+ addObserver:self |
+ selector:@selector(applicationDidBecomeActive) |
+ name:UIApplicationDidBecomeActiveNotification |
+ object:nil]; |
+ } |
+ return self; |
+} |
+ |
+- (instancetype)init { |
+ NOTREACHED(); |
+ return nil; |
+} |
+ |
+- (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; |
+} |
+ |
++ (Tab*)newPreloadingTabWithBrowserState:(ios::ChromeBrowserState*)browserState |
+ url:(const GURL&)URL |
+ referrer:(const web::Referrer&)referrer |
+ transition:(ui::PageTransition)transition |
+ provider:(id<CRWNativeContentProvider>)provider |
+ opener:(Tab*)opener |
+ desktopUserAgent:(BOOL)desktopUserAgent |
+ configuration:(void (^)(Tab*))configuration { |
+ Tab* tab = [[[Tab alloc] initWithWindowName:nil |
+ opener:opener |
+ openedByDOM:NO |
+ model:nil |
+ browserState:browserState] autorelease]; |
+ if (desktopUserAgent) |
+ [tab enableDesktopUserAgent]; |
+ [[tab webController] setNativeProvider:provider]; |
+ [[tab webController] setWebUsageEnabled:YES]; |
+ |
+ if (configuration) |
+ configuration(tab); |
+ |
+ web::NavigationManager::WebLoadParams params(URL); |
+ params.transition_type = transition; |
+ params.referrer = referrer; |
+ [[tab webController] loadWithParams:params]; |
+ |
+ return tab; |
+} |
+ |
+- (void)dealloc { |
+ DCHECK([NSThread isMainThread]); |
+ // Note that -[CRWWebController close] has already been called, so nothing |
+ // significant should be done with it in this method. |
+ DCHECK_NE(self.webController.delegate, self); |
+ [super dealloc]; |
+} |
+ |
+- (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.url.spec().c_str()]; |
+} |
+ |
+- (CRWWebController*)webController { |
+ return webStateImpl_ ? webStateImpl_->GetWebController() : nil; |
+} |
+ |
+- (id<TabDialogDelegate>)dialogDelegate { |
+ return dialogDelegate_; |
+} |
+ |
+- (void)setDialogDelegate:(id<TabDialogDelegate>)dialogDelegate { |
+ dialogDelegate_.reset(dialogDelegate); |
+} |
+ |
+- (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]; |
+} |
+ |
+- (PasswordController*)passwordController { |
+ return passwordController_.get(); |
+} |
+ |
+- (void)retrieveSnapshot:(void (^)(UIImage*))callback { |
+ [webControllerSnapshotHelper_ |
+ retrieveSnapshotForWebController:self.webController |
+ sessionID:[self currentSessionID] |
+ withOverlays:[self snapshotOverlays] |
+ callback:callback]; |
+} |
+ |
+- (const GURL&)url { |
+ // See note in header; this method should be removed. |
+ web::NavigationItem* item = [[self currentSessionEntry] navigationItem]; |
+ return item ? item->GetVirtualURL() : GURL::EmptyGURL(); |
+} |
+ |
+- (NSString*)title { |
+ base::string16 title = self.webStateImpl->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.url, url_formatter::kFormatUrlOmitNothing, net::UnescapeRule::SPACES, |
+ nullptr, nullptr, nullptr); |
+ return base::SysUTF16ToNSString(urlText); |
+} |
+ |
+- (NSString*)windowName { |
+ DCHECK([self navigationManager]); |
+ return [self navigationManager]->GetSessionController().windowName; |
+} |
+ |
+- (NSString*)tabId { |
+ DCHECK([self navigationManager]); |
+ return [[self navigationManager]->GetSessionController() tabId]; |
+} |
+ |
+- (web::WebState*)webState { |
+ return webStateImpl_.get(); |
+} |
+ |
+- (web::WebStateImpl*)webStateImpl { |
+ return webStateImpl_.get(); |
+} |
+ |
+- (void)fetchFavicon { |
+ const GURL& url = self.url; |
+ 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; |
+} |
+ |
+- (NavigationManagerImpl*)navigationManager { |
+ if (!self.webStateImpl) |
+ return nil; |
+ return &(self.webStateImpl->GetNavigationManagerImpl()); |
+} |
+ |
+- (id<StoreKitLauncher>)storeKitLauncher { |
+ return storeKitLauncher_.get(); |
+} |
+ |
+- (void)setStoreKitLauncher:(id<StoreKitLauncher>)storeKitLauncher { |
+ storeKitLauncher_.reset(storeKitLauncher); |
+} |
+ |
+// 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 { |
+ ScopedVector<web::NavigationItem> items = |
+ sessions::IOSSerializedNavigationBuilder::ToNavigationItems(navigations); |
+ [self navigationManager]->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_.reset([[FullScreenController alloc] |
+ initWithDelegate:fullScreenControllerDelegate_ |
+ navigationManager:&(self.webStateImpl->GetNavigationManagerImpl()) |
+ sessionID:[self currentSessionID]]); |
+ [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]; |
+} |
+ |
+- (id<FullScreenControllerDelegate>)fullScreenControllerDelegate { |
+ return fullScreenControllerDelegate_.get(); |
+} |
+ |
+- (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) { |
+ NavigationManagerImpl* navigationManager = |
+ &(self.webStateImpl->GetNavigationManagerImpl()); |
+ fullScreenController_.reset([[FullScreenController alloc] |
+ initWithDelegate:fullScreenControllerDelegate |
+ navigationManager:navigationManager |
+ sessionID:[self currentSessionID]]); |
+ if (fullScreenController_) { |
+ [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_.reset(fullScreenControllerDelegate); |
+} |
+ |
+- (OverscrollActionsController*)overscrollActionsController { |
+ return overscrollActionsController_.get(); |
+} |
+ |
+- (id<OverscrollActionsControllerDelegate>)overscrollActionsControllerDelegate { |
+ return overscrollActionsControllerDelegate_.get(); |
+} |
+ |
+- (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_.reset( |
+ [[OverscrollActionsController alloc] init]); |
+ [self.webController addObserver:overscrollActionsController_]; |
+ } |
+ ios_internal::OverscrollStyle style = |
+ ios_internal::OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO; |
+ if (browserState_->IsOffTheRecord()) { |
+ style = ios_internal::OverscrollStyle::REGULAR_PAGE_INCOGNITO; |
+ } |
+ [overscrollActionsController_ setStyle:style]; |
+ [overscrollActionsController_ |
+ setDelegate:overscrollActionsControllerDelegate]; |
+ overscrollActionsControllerDelegate_.reset( |
+ overscrollActionsControllerDelegate); |
+} |
+ |
+- (void)updateTitle:(NSString*)title { |
+ web::NavigationItem* item = [self navigationManager]->GetVisibleItem(); |
+ if (!item) |
+ return; |
+ item->SetTitle(base::SysNSStringToUTF16(title)); |
+ // TODO(crbug.com/546218): See if this can be removed; it's not clear that |
+ // other platforms send this (tab sync triggers need to be compared against |
+ // upstream). |
+ if (self.webStateImpl) |
+ self.webStateImpl->GetNavigationManagerImpl().OnNavigationItemChanged(); |
+ |
+ [self saveTitleToHistoryDB]; |
+} |
+ |
+- (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.url, base::SysNSStringToUTF16(title)); |
+} |
+ |
+- (void)addCurrentEntryToHistoryDB { |
+ DCHECK(self.currentSessionEntry); |
+ // If incognito, don't update history. |
+ if (browserState_->IsOffTheRecord()) |
+ return; |
+ |
+ CRWSessionEntry* sessionEntry = self.currentSessionEntry; |
+ 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; |
+ if (item->GetURL() != sessionEntry.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, |
+ sessionEntry.originalUrl.spec()))) { |
+ redirects.push_back(referrer.url); |
+ } |
+ // TODO(crbug.com/661670): the redirect chain is not constructed the same |
+ // way as upstream so this part needs to be revised. |
+ redirects.push_back(sessionEntry.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_.get(), |
+ 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_.get(), |
+ 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)webWillInitiateLoadWithParams: |
+ (web::NavigationManager::WebLoadParams&)params { |
+ GURL navUrl = params.url; |
+ |
+ // After a crash the NTP is loaded by default. |
+ if (navUrl.host() != kChromeUINewTabHost) { |
+ static BOOL hasLoadedPage = NO; |
+ if (!hasLoadedPage) { |
+ // As soon as an URL is loaded, a crash shouldn't be counted as a startup |
+ // crash. Since loading an url 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; |
+ } |
+ } |
+} |
+ |
+- (void)webDidUpdateSessionForLoadWithParams: |
+ (const web::NavigationManager::WebLoadParams&)params |
+ wasInitialNavigation:(BOOL)initialNavigation { |
+ GURL navUrl = params.url; |
+ 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(UserMetricsAction("MobileTabClobbered")); |
+ } |
+ if ([parentTabModel_ tabUsageRecorder]) |
+ [parentTabModel_ tabUsageRecorder]->RecordPageLoadStart(self); |
+ |
+ // Reset |isVoiceSearchResultsTab| since a new page is being navigated to. |
+ self.isVoiceSearchResultsTab = NO; |
+ |
+ [[OmniboxGeolocationController sharedInstance] |
+ addLocationToNavigationItem:self.currentSessionEntry.navigationItem |
+ browserState:browserState_]; |
+} |
+ |
+- (void)loadSessionTab:(const sessions::SessionTab*)sessionTab { |
+ DCHECK(sessionTab); |
+ [self replaceHistoryWithNavigations:sessionTab->navigations |
+ currentIndex:sessionTab->current_navigation_index]; |
+} |
+ |
+- (void)openJavascript:(NSString*)javaScript { |
+ DCHECK(javaScript); |
+ javaScript = [javaScript stringByRemovingPercentEncoding]; |
+ if (webStateImpl_) |
+ webStateImpl_->ExecuteJavaScript(base::SysNSStringToUTF16(javaScript)); |
+} |
+ |
+- (void)reload { |
+ // TODO(crbug.com/661671): Convert callers to go through CRWWebController |
+ // directly and remove this passthrough method. |
+ [self.webController reload]; |
+} |
+ |
+- (void)webWillReload { |
+ if ([parentTabModel_ tabUsageRecorder]) { |
+ [parentTabModel_ tabUsageRecorder]->RecordReload(self); |
+ } |
+} |
+ |
+// Stop the page loading. |
+// Equivalent to the user pressing 'stop', or a window.stop() command. |
+- (void)stopLoading { |
+ [self.webController stopLoading]; |
+} |
+ |
+// Halt the tab, which amounts to halting its webController. |
+- (void)terminateNetworkActivity { |
+ [self.webController terminateNetworkActivity]; |
+} |
+ |
+// This can't be done in dealloc in case someone holds an extra strong |
+// reference to the Tab, which would cause the close sequence to fire at a |
+// random time. |
+- (void)close { |
+ self.fullScreenControllerDelegate = nil; |
+ self.overscrollActionsControllerDelegate = nil; |
+ self.passKitDialogProvider = nil; |
+ self.snapshotOverlayProvider = nil; |
+ self.storeKitLauncher = nil; |
+ |
+ [[NSNotificationCenter defaultCenter] removeObserver:self]; |
+ |
+ [findInPageController_ detachFromWebState]; |
+ findInPageController_.reset(); |
+ |
+ [passwordController_ detach]; |
+ passwordController_.reset(); |
+ tabInfoBarObserver_.reset(); |
+ |
+ faviconDriverObserverBridge_.reset(); |
+ [openInController_ detachFromWebController]; |
+ openInController_.reset(); |
+ [autofillController_ detachFromWebState]; |
+ [suggestionController_ detachFromWebState]; |
+ if (fullScreenController_) |
+ [self.webController removeObserver:fullScreenController_]; |
+ [fullScreenController_ invalidate]; |
+ fullScreenController_.reset(); |
+ if (overscrollActionsController_) |
+ [self.webController removeObserver:overscrollActionsController_]; |
+ [overscrollActionsController_ invalidate]; |
+ overscrollActionsController_.reset(); |
+ [readerModeController_ detachFromWebState]; |
+ readerModeController_.reset(); |
+ |
+ [self clearResubmitDataActionSheet]; |
+ |
+ // Invalidate any snapshot stored for this session. |
+ NSString* sessionID = [self currentSessionID]; |
+ DCHECK(sessionID); |
+ [snapshotManager_ removeImageWithSessionID:sessionID]; |
+ // Reset association with the webController. |
+ [self.webController setDelegate:nil]; |
+ [self.webController setUIDelegate:nil]; |
+ |
+ webStateImpl_->ClearTransientContentView(); |
+ // Terminate the network activity before notifying the parent model, because |
+ // the parent model may initiate the request context destruction. |
+ [self terminateNetworkActivity]; |
+ |
+ // Cancel any queued dialogs. |
+ [self.dialogDelegate cancelDialogForTab:self]; |
+ |
+ // These steps must be done last, and must be done in this order; nothing |
+ // involving the tab should be done after didCloseTab:, and the |
+ // CRWWebController backing the tab should outlive anything done during tab |
+ // closure (since -[CRWWebController close] is what begins tearing down the |
+ // web/ layer, and tab closure may trigger operations that need to query the |
+ // web/ layer). The scoped strong ref is because didCloseTab: is often the |
+ // trigger for deallocating the tab, but that can in turn cause |
+ // CRWWebController to be deallocated before its close is called. The facade |
+ // delegate should be torn down after |-didCloseTab:| so components triggered |
+ // by tab closure can use the content facade, and it should be deleted before |
+ // the web controller since the web controller owns the facade's backing |
+ // objects. |
+ // TODO(crbug.com/546222): Fix the need for this; TabModel should be |
+ // responsible for making the lifetime of Tab sane, rather than allowing Tab |
+ // to drive its own destruction. |
+ base::scoped_nsobject<Tab> kungFuDeathGrip([self retain]); |
+ [parentTabModel_ didCloseTab:self]; // Inform parent of tab closure. |
+ webStateImpl_.reset(); |
+} |
+ |
+- (void)dismissModals { |
+ [openInController_ disable]; |
+ [self.webController dismissModals]; |
+} |
+ |
+- (void)updateDesktopUserAgentForEntry:(CRWSessionEntry*)entry |
+ fromEntry:(CRWSessionEntry*)fromEntry { |
+ web::NavigationItemImpl* item = entry.navigationItemImpl; |
+ web::NavigationItemImpl* fromItem = fromEntry.navigationItemImpl; |
+ if (!item || !fromItem) |
+ return; |
+ bool useDesktopUserAgent = item->IsOverridingUserAgent(); |
+ if (useDesktopUserAgent != fromItem->IsOverridingUserAgent()) { |
+ [self.webController requirePageReconstruction]; |
+ } |
+} |
+ |
+- (CRWSessionEntry*)currentSessionEntry { |
+ if (![self navigationManager]) |
+ return nil; |
+ return [[self navigationManager]->GetSessionController() currentEntry]; |
+} |
+ |
+- (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_.reset( |
+ new FaviconDriverObserverBridge(self, faviconDriver)); |
+ } |
+ } else { |
+ faviconDriverObserverBridge_.reset(); |
+ } |
+} |
+ |
+- (void)goBack { |
+ if (self.navigationManager) { |
+ DCHECK(self.navigationManager->CanGoBack()); |
+ base::RecordAction(UserMetricsAction("Back")); |
+ self.navigationManager->GoBack(); |
+ } |
+} |
+ |
+- (void)goForward { |
+ if (self.navigationManager) { |
+ DCHECK(self.navigationManager->CanGoForward()); |
+ base::RecordAction(UserMetricsAction("Forward")); |
+ self.navigationManager->GoForward(); |
+ } |
+} |
+ |
+- (BOOL)canGoBack { |
+ return self.navigationManager && self.navigationManager->CanGoBack(); |
+} |
+ |
+- (BOOL)canGoForward { |
+ return self.navigationManager && self.navigationManager->CanGoForward(); |
+} |
+ |
+- (void)goToEntry:(CRWSessionEntry*)entry { |
+ DCHECK(entry); |
+ |
+ if (self.navigationManager) { |
+ CRWSessionController* sessionController = |
+ self.navigationManager->GetSessionController(); |
+ DCHECK([sessionController.entries containsObject:entry]); |
+ NSUInteger index = [sessionController.entries indexOfObject:entry]; |
+ self.navigationManager->GoToIndex(index); |
+ } |
+} |
+ |
+- (void)openURLWithParams:(const web::WebState::OpenURLParams&)params { |
+ switch (params.disposition) { |
+ case WindowOpenDisposition::NEW_FOREGROUND_TAB: |
+ case WindowOpenDisposition::NEW_BACKGROUND_TAB: |
+ [parentTabModel_ |
+ insertOrUpdateTabWithURL:params.url |
+ referrer:params.referrer |
+ transition:params.transition |
+ windowName:nil |
+ opener:self |
+ openedByDOM:NO |
+ atIndex:TabModelConstants::kTabPositionAutomatically |
+ inBackground:(params.disposition == |
+ WindowOpenDisposition::NEW_BACKGROUND_TAB)]; |
+ break; |
+ case WindowOpenDisposition::CURRENT_TAB: { |
+ web::NavigationManager::WebLoadParams loadParams(params.url); |
+ loadParams.referrer = params.referrer; |
+ loadParams.transition_type = params.transition; |
+ loadParams.is_renderer_initiated = params.is_renderer_initiated; |
+ self.navigationManager->LoadURLWithParams(loadParams); |
+ } break; |
+ default: |
+ NOTIMPLEMENTED(); |
+ break; |
+ }; |
+} |
+ |
+- (BOOL)openExternalURL:(const GURL&)url linkClicked:(BOOL)linkClicked { |
+ if (!externalAppLauncher_.get()) |
+ externalAppLauncher_.reset([[ExternalAppLauncher alloc] init]); |
+ |
+ // This method may release CRWWebController which may cause a crash |
+ // (crbug.com/393949). |
+ [[self.webController retain] autorelease]; |
+ |
+ // 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; |
+ } |
+ |
+ // Check if it's a FIDO U2F call. |
+ if (finalURL.SchemeIs("u2f")) { |
+ // Create U2FController object lazily. |
+ if (!U2FController_) { |
+ U2FController_.reset([[U2FController alloc] init]); |
+ } |
+ |
+ DCHECK([self navigationManager]); |
+ GURL origin = |
+ [self navigationManager]->GetLastCommittedItem()->GetURL().GetOrigin(); |
+ |
+ // Compose u2f-x-callback URL and update urlToOpen. |
+ finalURL = [U2FController_ XCallbackFromRequestURL:finalURL |
+ originURL:origin |
+ tabURL:self.url |
+ 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]->GetSessionController() |
+ discardNonCommittedEntries]; |
+ // Ensure the UI reflects the current entry, not the just-discarded pending |
+ // entry. |
+ [parentTabModel_ notifyTabChanged:self]; |
+ return YES; |
+ } |
+ return NO; |
+} |
+ |
+- (void)webWillFinishHistoryNavigationFromEntry:(CRWSessionEntry*)fromEntry { |
+ DCHECK(fromEntry); |
+ [self updateDesktopUserAgentForEntry:self.currentSessionEntry |
+ fromEntry:fromEntry]; |
+ [parentTabModel_ notifyTabChanged:self]; |
+} |
+ |
+- (void)webDidUpdateHistoryStateWithPageURL:(const GURL&)pageUrl { |
+ favicon::FaviconDriver* faviconDriver = |
+ favicon::WebFaviconDriver::FromWebState(self.webState); |
+ if (faviconDriver) { |
+ // Fetch the favicon for the new URL. |
+ faviconDriver->FetchFavicon(pageUrl); |
+ } |
+ [parentTabModel_ notifyTabChanged:self]; |
+} |
+ |
+// Records the state (scroll position, form values, whatever can be |
+// harvested) from the current page into the current session entry. |
+- (void)recordStateInHistory { |
+ // Link-loading prerender tab may not have correct zoom value during the load. |
+ if (!self.isLinkLoadingPrerenderTab) |
+ [self.webController recordStateInHistory]; |
+} |
+ |
+// 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_.reset([[OpenInController alloc] |
+ initWithRequestContext:browserState_->GetRequestContext() |
+ webController:self.webController]); |
+ } |
+ return openInController_.get(); |
+} |
+ |
+- (void)closeThisTab { |
+ if (!parentTabModel_) |
+ return; |
+ |
+ NSUInteger index = [parentTabModel_ indexOfTab:self]; |
+ if (index != NSNotFound) |
+ [parentTabModel_ closeTabAtIndex:index]; |
+} |
+ |
+- (id<CRWNativeContent>)controllerForUnhandledContentAtURL:(const GURL&)url { |
+ DownloadManagerController* downloadController = |
+ [[[DownloadManagerController alloc] |
+ initWithURL:url |
+ requestContextGetter:browserState_->GetRequestContext() |
+ storeKitLauncher:self.storeKitLauncher] autorelease]; |
+ [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 |self.url| 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); |
+ base::string16 filename = |
+ net::GetSuggestedFilename(self.url, contentDisposition, |
+ "", // referrer-charset |
+ "", // suggested-name |
+ "application/pdf", // mime-type |
+ defaultFilename); |
+ [[self openInController] |
+ enableWithDocumentURL:self.url |
+ suggestedFilename:base::SysUTF16ToNSString(filename)]; |
+} |
+ |
+- (void)countMainFrameLoad { |
+ if ([self isPrerenderTab] || [self url].SchemeIs(kChromeUIScheme)) { |
+ return; |
+ } |
+ base::RecordAction(UserMetricsAction("MobilePageLoaded")); |
+} |
+ |
+- (void)applicationDidBecomeActive { |
+ if (requireReloadAfterBecomingActive_) { |
+ if (visible_) { |
+ [self.webController reload]; |
+ } 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; |
+} |
+ |
+- (ReaderModeController*)readerModeController { |
+ return readerModeController_.get(); |
+} |
+ |
+- (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.url) |
+ [self.webController loadHTMLForCurrentURL:html]; |
+ |
+ [self.readerModeController exitReaderMode]; |
+} |
+ |
+#pragma mark - |
+ |
+- (void)openAppStore:(NSString*)appId { |
+ [storeKitLauncher_ openAppStore:appId]; |
+} |
+ |
+- (std::vector<GURL>)currentRedirectedUrls { |
+ DCHECK([self navigationManager]); |
+ return |
+ [[self navigationManager]->GetSessionController() currentRedirectedUrls]; |
+} |
+ |
+- (BOOL)useDesktopUserAgent { |
+ web::NavigationItem* currentItem = self.currentSessionEntry.navigationItem; |
+ return currentItem && currentItem->IsOverridingUserAgent(); |
+} |
+ |
+- (void)enableDesktopUserAgent { |
+ DCHECK_EQ(self.useDesktopUserAgent, NO); |
+ DCHECK([self navigationManager]); |
+ [[self navigationManager]->GetSessionController() |
+ useDesktopUserAgentForNextPendingEntry]; |
+} |
+ |
+- (void)reloadForDesktopUserAgent { |
+ // |loadWithParams| will recreate the removed UIWebView. |
+ [self.webController requirePageReconstruction]; |
+ |
+ // TODO(crbug.com/228171): A hack in session_controller -addPendingEntry |
+ // discusses making tab responsible for distinguishing history stack |
+ // navigation from new navigations. Because we want a new navigation here, we |
+ // use |PAGE_TRANSITION_FORM_SUBMIT|. When session_controller changes, so |
+ // should this. |
+ ui::PageTransition transition = |
+ ui::PageTransitionFromInt(ui::PAGE_TRANSITION_FORM_SUBMIT); |
+ DCHECK([self navigationManager]); |
+ CRWSessionController* sessionController = |
+ [self navigationManager]->GetSessionController(); |
+ CRWSessionEntry* lastUserEntry = [sessionController lastUserEntry]; |
+ if (!lastUserEntry) |
+ return; |
+ |
+ // |originalUrl| will be empty if a page was open by DOM. |
+ GURL reloadURL(lastUserEntry.originalUrl); |
+ if (reloadURL.is_empty()) { |
+ DCHECK(sessionController.openedByDOM); |
+ reloadURL = [lastUserEntry navigationItem]->GetVirtualURL(); |
+ } |
+ |
+ web::NavigationManager::WebLoadParams params(reloadURL); |
+ params.referrer = lastUserEntry.navigationItem->GetReferrer(); |
+ params.transition_type = transition; |
+ if (self.navigationManager) |
+ self.navigationManager->LoadURLWithParams(params); |
+} |
+ |
+- (id<SnapshotOverlayProvider>)snapshotOverlayProvider { |
+ return snapshotOverlayProvider_.get(); |
+} |
+ |
+- (void)setSnapshotOverlayProvider: |
+ (id<SnapshotOverlayProvider>)snapshotOverlayProvider { |
+ snapshotOverlayProvider_.reset(snapshotOverlayProvider); |
+} |
+ |
+- (BlockedPopupHandler*)popupHandler { |
+ if (!popupHandler_.get()) { |
+ popupHandler_.reset(new BlockedPopupHandler(self.browserState)); |
+ popupHandler_->SetDelegate(self); |
+ } |
+ return popupHandler_.get(); |
+} |
+ |
+- (void)evaluateU2FResultFromURL:(const GURL&)URL { |
+ DCHECK(U2FController_); |
+ [U2FController_ evaluateU2FResultFromU2FURL:URL webState:self.webState]; |
+} |
+ |
+- (NSString*)currentSessionID { |
+ return [self tabId]; |
+} |
+ |
+#pragma mark - WebDelegate protocol methods. |
+ |
+- (CRWWebController*)webPageOrderedOpen:(const GURL&)URL |
+ referrer:(const web::Referrer&)referrer |
+ windowName:(NSString*)windowName |
+ inBackground:(BOOL)inBackground { |
+ // Prerendered Tabs cannot open child windows. |
+ // TODO(crbug.com/661673): Should we kill prerendering in this case? |
+ if (!parentTabModel_) |
+ return nil; |
+ if (!inBackground) |
+ [self updateSnapshotWithOverlay:YES visibleFrameOnly:YES]; |
+ // Open a new tab or update an existing one. Tabs opened from a web page are |
+ Tab* tab = [parentTabModel_ |
+ insertOrUpdateTabWithURL:URL |
+ referrer:referrer |
+ transition:ui::PAGE_TRANSITION_LINK |
+ windowName:windowName |
+ opener:self |
+ openedByDOM:YES |
+ atIndex:TabModelConstants::kTabPositionAutomatically |
+ inBackground:inBackground]; |
+ return tab.webController; |
+} |
+ |
+// This can be combined with the other versions once Tab loading is separated |
+// from creation. |
+- (CRWWebController*)webPageOrderedOpen { |
+ [self updateSnapshotWithOverlay:YES visibleFrameOnly:YES]; |
+ |
+ Tab* tab = [parentTabModel_ |
+ insertBlankTabWithTransition:ui::PAGE_TRANSITION_LINK |
+ opener:self |
+ openedByDOM:YES |
+ atIndex:TabModelConstants::kTabPositionAutomatically |
+ inBackground:NO]; |
+ return tab.webController; |
+} |
+ |
+- (void)webController:(CRWWebController*)webController |
+ onFormResubmissionForRequest:(NSURLRequest*)request |
+ continueBlock:(ProceduralBlock)continueBlock |
+ cancelBlock:(ProceduralBlock)cancelBlock { |
+ // Clear the resubmit data action sheet before loading a new request. |
+ [self clearResubmitDataActionSheet]; |
+ resubmitDataController_.reset([[ResubmitDataController alloc] |
+ initWithContinueBlock:continueBlock |
+ cancelBlock:cancelBlock]); |
+ [self showResubmitDataActionSheet]; |
+} |
+ |
+- (void)showResubmitDataActionSheet { |
+ // Return early if the CRWWebController has been closed or web |
+ // usage is disabled on it. |
+ if (![self.webController webUsageEnabled]) |
+ return; |
+ // Check to see if an action sheet can be shown. |
+ if (self.webState && [self.webState->GetView() window]) { |
+ // Display the action sheet with the arrow pointing at the top center of the |
+ // web contents. |
+ CGFloat xOrigin = CGRectGetMidX(self.webState->GetView().frame); |
+ CGFloat yOrigin = CGRectGetMinY(self.webState->GetView().frame) + |
+ [[self fullScreenControllerDelegate] headerHeight]; |
+ [resubmitDataController_ |
+ presentActionSheetFromRect:CGRectMake(xOrigin, yOrigin, 1, 1) |
+ inView:self.webState->GetView()]; |
+ showResubmitDataActionSheetAttempt_ = 0; |
+ return; |
+ } |
+ |
+ // The resubmit data action cannot be presented as the |contentView_| was not |
+ // yet added to the window. Retry after |kDelayBetweenAttemptsNanoSecs|. |
+ // TODO(crbug.com/227868): The strategy to poll until the resubmit data action |
+ // sheet can be presented is a temporary workaround. This needs to be |
+ // refactored to match the Chromium implementation: |
+ // * web_controller should notify/ the BVC once an action sheet should be |
+ // shown. |
+ // * BVC should present the action sheet and then trigger the reload |
+ const NSUInteger kMaximumNumberAttempts = 10; |
+ // 400 milliseconds |
+ const int64_t kDelayBetweenAttemptsNanoSecs = 0.4 * NSEC_PER_SEC; |
+ if (showResubmitDataActionSheetAttempt_ >= kMaximumNumberAttempts) { |
+ NOTREACHED(); |
+ [self clearResubmitDataActionSheet]; |
+ return; |
+ } |
+ base::WeakNSObject<Tab> weakTab(self); |
+ dispatch_after( |
+ dispatch_time(DISPATCH_TIME_NOW, kDelayBetweenAttemptsNanoSecs), |
+ dispatch_get_main_queue(), ^{ |
+ [weakTab showResubmitDataActionSheet]; |
+ }); |
+ showResubmitDataActionSheetAttempt_++; |
+} |
+ |
+- (void)clearResubmitDataActionSheet { |
+ [resubmitDataController_ dismissActionSheet]; |
+ resubmitDataController_.reset(); |
+ showResubmitDataActionSheetAttempt_ = 0; |
+} |
+ |
+// The web page wants to close its own window. |
+- (void)webPageOrderedClose { |
+ // Only allow a web page to close itself if it was opened by DOM, or if there |
+ // are no navigation items. |
+ DCHECK([[self navigationManager]->GetSessionController() isOpenedByDOM] || |
+ ![self navigationManager]->GetItemCount()); |
+ [self closeThisTab]; |
+} |
+ |
+// 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]); |
+ [self clearResubmitDataActionSheet]; |
+ |
+ // Move the toolbar to visible during page load. |
+ [fullScreenController_ disableFullScreen]; |
+ |
+ 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.url) { |
+ base::RecordAction(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)webDidAddPendingURL { |
+ [parentTabModel_ notifyTabChanged:self]; |
+ [openInController_ disable]; |
+ [[NSNotificationCenter defaultCenter] |
+ postNotificationName: |
+ kTabClosingCurrentDocumentNotificationForCrashReporting |
+ object:self]; |
+ [metricsClientManager_ pageLoadStarted:self.url]; |
+} |
+ |
+- (void)webCancelStartLoadingRequest { |
+ DCHECK(self.webController.loadPhase == web::PAGE_LOADED); |
+ [parentTabModel_ notifyTabChanged:self]; |
+} |
+ |
+// Called when the page URL has changed. |
+- (void)webDidStartLoadingURL:(const GURL&)currentUrl |
+ shouldUpdateHistory:(BOOL)updateHistory { |
+ DCHECK(self.webController.loadPhase == web::PAGE_LOADING); |
+ DCHECK([self navigationManager]); |
+ // |webWillStartLoading:| 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]; |
+ [findInPageController_ disableFindInPageWithCompletionHandler:nil]; |
+ [autoReloadBridge_ loadStartedForURL:currentUrl]; |
+ |
+ if (isUserNavigationEvent_) { |
+ [[NSNotificationCenter defaultCenter] |
+ postNotificationName:kTabModelUserNavigatedNotification |
+ object:self]; |
+ } |
+ if (parentTabModel_) { |
+ [[NSNotificationCenter defaultCenter] |
+ postNotificationName:kTabModelTabWillStartLoadingNotification |
+ object:parentTabModel_ |
+ userInfo:[NSDictionary |
+ dictionaryWithObject:self |
+ forKey:kTabModelTabKey]]; |
+ } |
+ favicon::FaviconDriver* faviconDriver = |
+ favicon::WebFaviconDriver::FromWebState(self.webState); |
+ if (faviconDriver) { |
+ faviconDriver->FetchFavicon(currentUrl); |
+ } |
+ [parentTabModel_ notifyTabChanged:self]; |
+ if (parentTabModel_) { |
+ [[NSNotificationCenter defaultCenter] |
+ postNotificationName:kTabModelTabDidStartLoadingNotification |
+ object:parentTabModel_ |
+ userInfo:[NSDictionary |
+ dictionaryWithObject:self |
+ forKey:kTabModelTabKey]]; |
+ } |
+ |
+ [parentTabModel_ navigationCommittedInTab:self]; |
+ if (updateHistory) { |
+ [self addCurrentEntryToHistoryDB]; |
+ [self countMainFrameLoad]; |
+ } |
+ |
+ // 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* urlString = base::SysUTF8ToNSString(currentUrl.spec()); |
+ if ([urlString length]) { |
+ [[NSNotificationCenter defaultCenter] |
+ postNotificationName:kTabUrlStartedLoadingNotificationForCrashReporting |
+ object:self |
+ userInfo:[NSDictionary dictionaryWithObject:urlString |
+ forKey:kTabUrlKey]]; |
+ } |
+} |
+ |
+// Called when the page finishes loading, with the URL and a boolean indicating |
+// if the page was successfully loaded. |
+- (void)webDidFinishWithURL:(const GURL&)url loadSuccess:(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; |
+ if (self.currentSessionEntry) |
+ wasPost = self.currentSessionEntry.navigationItem->HasPostData(); |
+ if (loadSuccess) |
+ [autoReloadBridge_ loadFinishedForURL:url wasPost:wasPost]; |
+ else |
+ [autoReloadBridge_ loadFailedForURL:url wasPost:wasPost]; |
+ [webControllerSnapshotHelper_ setSnapshotCoalescingEnabled:YES]; |
+ if (!loadSuccess) { |
+ [fullScreenController_ disableFullScreen]; |
+ } |
+ [self recordInterfaceOrientation]; |
+ navigation_metrics::OriginsSeenService* originsSeenService = |
+ IOSChromeOriginsSeenServiceFactory::GetForBrowserState(self.browserState); |
+ bool already_seen = originsSeenService->Insert(url::Origin::Origin(url)); |
+ navigation_metrics::RecordMainFrameNavigation( |
+ url, true, self.browserState->IsOffTheRecord(), already_seen); |
+ |
+ if (loadSuccess) { |
+ scoped_refptr<net::HttpResponseHeaders> headers = |
+ self.webStateImpl->GetHttpResponseHeaders(); |
+ [self handleExportableFile:headers.get()]; |
+ } |
+ |
+ [metricsClientManager_ pageLoadCompleted]; |
+ [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]; |
+ |
+ // Always take snapshots on iPad if the tab switcher is enabled. |
+ // If the tab switcher is not enabled, don't take snapshot of chrome scheme |
+ // pages. |
+ BOOL takeSnapshotOnIpad = |
+ IsIPadIdiom() && (experimental_flags::IsTabSwitcherEnabled() || |
+ !web::GetWebClient()->IsAppSpecificURL(url)); |
+ // Always take snapshot on iPhone. |
+ BOOL takeSnapshot = !IsIPadIdiom() || takeSnapshotOnIpad; |
+ if (loadSuccess && takeSnapshot) { |
+ [self updateSnapshotWithOverlay:YES visibleFrameOnly:YES]; |
+ } |
+ [webControllerSnapshotHelper_ setSnapshotCoalescingEnabled:NO]; |
+} |
+ |
+- (void)webLoadCancelled:(const GURL&)url { |
+ // When a load is cancelled, this is the maximum that a page will ever load. |
+ [fullScreenController_ enableFullScreen]; |
+ [parentTabModel_ notifyTabChanged:self]; |
+} |
+ |
+- (BOOL)webController:(CRWWebController*)webController |
+ shouldOpenExternalURL:(const GURL&)URL { |
+ if (isPrerenderTab_ && !isLinkLoadingPrerenderTab_) { |
+ [delegate_ discardPrerender]; |
+ return NO; |
+ } |
+ return YES; |
+} |
+ |
+- (void)webController:(CRWWebController*)webController |
+ titleDidChange:(NSString*)title { |
+ NSString* oldTitle = [self title]; |
+ BOOL isTitleChanged = (!oldTitle && title) || (oldTitle && !title) || |
+ (![oldTitle isEqualToString:title]); |
+ if (isTitleChanged) { |
+ [self updateTitle:title]; |
+ [parentTabModel_ notifyTabChanged:self]; |
+ } |
+} |
+ |
+- (BOOL)urlTriggersNativeAppLaunch:(const GURL&)url |
+ sourceURL:(const GURL&)sourceURL |
+ linkClicked:(BOOL)linkClicked { |
+ // Don't open any native app directly when prerendering or from Incognito. |
+ if (isPrerenderTab_ || self.browserState->IsOffTheRecord()) |
+ return NO; |
+ |
+ base::scoped_nsprotocol<id<NativeAppMetadata>> metadata( |
+ [ios::GetChromeBrowserProvider()->GetNativeAppWhitelistManager() |
+ newNativeAppForURL: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] |
+ linkClicked:linkClicked]) { |
+ return YES; |
+ } |
+ |
+ // Auto-open didn't work. Reset the auto-open flag. |
+ [metadata unsetShouldAutoOpenLinks]; |
+ return NO; |
+} |
+ |
+- (double)lastVisitedTimestamp { |
+ DCHECK([self navigationManager]); |
+ return |
+ [[self navigationManager]->GetSessionController() lastVisitedTimestamp]; |
+} |
+ |
+- (void)updateLastVisitedTimestamp { |
+ // Stores this information in self.history and it will be written into disc |
+ // with other information when needed. |
+ DCHECK([self navigationManager]); |
+ [[self navigationManager]->GetSessionController() |
+ setLastVisitedTimestamp:[[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 currentSessionID]; |
+ // 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)setWebUsageEnabled:(BOOL)webUsageEnabled { |
+ [self.webController setWebUsageEnabled:webUsageEnabled]; |
+} |
+ |
+- (void)webControllerDidSuppressDialog:(id)webController { |
+ DCHECK(isPrerenderTab_); |
+ [delegate_ discardPrerender]; |
+} |
+ |
+- (BOOL)webController:(CRWWebController*)webController |
+ shouldBlockPopupWithURL:(const GURL&)popupURL |
+ sourceURL:(const GURL&)sourceURL { |
+ ContentSetting setting = |
+ ios::HostContentSettingsMapFactory::GetForBrowserState(browserState_) |
+ ->GetContentSetting(sourceURL, sourceURL, |
+ CONTENT_SETTINGS_TYPE_POPUPS, std::string()); |
+ |
+ return setting != CONTENT_SETTING_ALLOW; |
+} |
+ |
+- (void)webController:(CRWWebController*)webController |
+ didBlockPopup:(const web::BlockedPopupInfo&)blockedPopupInfo { |
+ [self popupHandler]->HandlePopup(blockedPopupInfo); |
+} |
+ |
+- (CGFloat)headerHeightForWebController:(CRWWebController*)webController { |
+ return [fullScreenControllerDelegate_ headerHeight]; |
+} |
+ |
+- (void)webControllerDidUpdateSSLStatusForCurrentNavigationItem: |
+ (CRWWebController*)webController { |
+ // Disable fullscreen if SSL cert is invalid. |
+ web::NavigationItem* item = [self navigationManager]->GetTransientItem(); |
+ web::SecurityStyle securityStyle = |
+ item ? item->GetSSL().security_style : web::SECURITY_STYLE_UNKNOWN; |
+ if (securityStyle == web::SECURITY_STYLE_AUTHENTICATION_BROKEN) { |
+ [fullScreenController_ disableFullScreen]; |
+ } |
+ |
+ [parentTabModel_ notifyTabChanged:self]; |
+ [self updateFullscreenWithToolbarVisible:YES]; |
+} |
+ |
+- (void)webControllerWebProcessDidCrash:(CRWWebController*)webController { |
+ if (browserState_ && !browserState_->IsOffTheRecord()) { |
+ // Report the crash. |
+ GetApplicationContext() |
+ ->GetMetricsServicesManager() |
+ ->OnRendererProcessCrash(); |
+ |
+ // Log the tab state for the termination. |
+ RendererTerminationTabState tab_state = |
+ visible_ ? RendererTerminationTabState::FOREGROUND_TAB_FOREGROUND_APP |
+ : RendererTerminationTabState::BACKGROUND_TAB_FOREGROUND_APP; |
+ if ([UIApplication sharedApplication].applicationState == |
+ UIApplicationStateBackground) { |
+ 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_); |
+ } |
+ |
+ BOOL applicationIsBackgrounded = |
+ [UIApplication sharedApplication].applicationState == |
+ UIApplicationStateBackground; |
+ if (visible_) { |
+ if (!applicationIsBackgrounded) { |
+ base::WeakNSObject<Tab> weakSelf(self); |
+ base::scoped_nsobject<SadTabView> sadTabView( |
+ [[SadTabView alloc] initWithReloadHandler:^{ |
+ base::scoped_nsobject<Tab> strongSelf([weakSelf retain]); |
+ [strongSelf reload]; |
+ }]); |
+ base::scoped_nsobject<CRWContentView> contentView( |
+ [[CRWGenericContentView alloc] initWithView:sadTabView]); |
+ self.webState->ShowTransientContentView(contentView); |
+ [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_ && applicationIsBackgrounded; |
+} |
+ |
+- (void)webController:(CRWWebController*)webController |
+ didLoadPassKitObject:(NSData*)data { |
+ [self.passKitDialogProvider presentPassKitDialog:data]; |
+} |
+ |
+#pragma mark - WebUserInterfaceDelegate methods. |
+ |
+- (void)webController:(CRWWebController*)webController |
+ runAuthDialogForProtectionSpace:(NSURLProtectionSpace*)protectionSpace |
+ proposedCredential:(NSURLCredential*)credential |
+ completionHandler: |
+ (void (^)(NSString* user, NSString* password))handler { |
+ if (self.dialogDelegate) { |
+ [self.dialogDelegate tab:self |
+ runAuthDialogForProtectionSpace:protectionSpace |
+ proposedCredential:credential |
+ completionHandler:handler]; |
+ } else if (handler) { |
+ handler(nil, nil); |
+ } |
+} |
+ |
+- (void)cancelDialogsForWebController:(CRWWebController*)webController { |
+ [self.dialogDelegate cancelDialogForTab:self]; |
+} |
+ |
+#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()); |
+ base::scoped_nsobject<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()); |
+ base::scoped_nsobject<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()) { |
+ base::scoped_nsobject<OpenUrlCommand> command([[OpenUrlCommand alloc] |
+ initWithURL:url |
+ referrer:web::Referrer() // Strip referrer when switching modes. |
+ windowName:nil |
+ inIncognito:YES |
+ inBackground:NO |
+ appendTo:kLastTab]); |
+ [self.view chromeExecuteCommand:command]; |
+ } else { |
+ base::scoped_nsobject<GenericChromeCommand> chromeCommand( |
+ [[GenericChromeCommand alloc] initWithTag:IDC_NEW_INCOGNITO_TAB]); |
+ [self.view chromeExecuteCommand:chromeCommand]; |
+ } |
+} |
+ |
+- (FindInPageController*)findInPageController { |
+ return findInPageController_; |
+} |
+ |
+- (NativeAppNavigationController*)nativeAppNavigationController { |
+ return nativeAppNavigationController_; |
+} |
+ |
+- (void)initNativeAppNavigationController { |
+ if (browserState_->IsOffTheRecord()) |
+ return; |
+ DCHECK(!nativeAppNavigationController_); |
+ nativeAppNavigationController_.reset([[NativeAppNavigationController alloc] |
+ initWithRequestContextGetter:browserState_->GetRequestContext() |
+ tab:self]); |
+ [self.webController addObserver:nativeAppNavigationController_]; |
+ DCHECK(nativeAppNavigationController_); |
+} |
+ |
+- (id<PassKitDialogProvider>)passKitDialogProvider { |
+ return passKitDialogProvider_.get(); |
+} |
+ |
+- (void)setPassKitDialogProvider:(id<PassKitDialogProvider>)provider { |
+ passKitDialogProvider_.reset(provider); |
+} |
+ |
+- (void)wasShown { |
+ visible_ = YES; |
+ [self updateFullscreenWithToolbarVisible:YES]; |
+ [self.webController wasShown]; |
+ [inputAccessoryViewController_ wasShown]; |
+} |
+ |
+- (void)wasHidden { |
+ visible_ = NO; |
+ [self updateFullscreenWithToolbarVisible:YES]; |
+ [self.webController wasHidden]; |
+ [inputAccessoryViewController_ wasHidden]; |
+} |
+ |
+- (BOOL)navigationIsBackwards:(const CRWSessionEntry*)fromEntry |
+ toEntry:(const CRWSessionEntry*)toEntry { |
+ DCHECK([self navigationManager]); |
+ NSArray* entries = [self navigationManager]->GetSessionController().entries; |
+ NSInteger fromIndex = [entries indexOfObject:fromEntry]; |
+ NSInteger toIndex = [entries indexOfObject:toEntry]; |
+ return (fromIndex != NSNotFound && toIndex != NSNotFound && |
+ fromIndex > toIndex); |
+} |
+ |
+@end |
+ |
+#pragma mark - TestingSupport |
+ |
+@implementation Tab (TestingSupport) |
+ |
+- (void)replaceWebStateImpl:(std::unique_ptr<web::WebStateImpl>)webState { |
+ // Stop observing the old InfoBarManager and FaviconDriver since they will |
+ // be deleted with the old web controller. |
+ [self setShouldObserveInfoBarManager:NO]; |
+ [self setShouldObserveFaviconChanges:NO]; |
+ [self.webController setDelegate:nil]; |
+ // Set the new web state. |
+ webStateImpl_.reset(webState.release()); |
+ [self.webController setDelegate:self]; |
+ // SessionTabHelper comes first because it sets up the tab ID, and other |
+ // helpers may rely on that. |
+ IOSChromeSessionTabHelper::CreateForWebState(webStateImpl_.get()); |
+ IOSChromeSyncedTabDelegate::CreateForWebState(webStateImpl_.get()); |
+ // Start observing the new web controller's InfoBarManager and FaviconDriver. |
+ [self setShouldObserveInfoBarManager:YES]; |
+ [self setShouldObserveFaviconChanges:YES]; |
+ findInPageController_.reset(); |
+} |
+ |
+- (void)replaceExternalAppLauncher:(id)externalAppLauncher { |
+ externalAppLauncher_.reset([externalAppLauncher retain]); |
+} |
+ |
+- (TabModel*)parentTabModel { |
+ return parentTabModel_; |
+} |
+ |
+- (FormInputAccessoryViewController*)inputAccessoryViewController { |
+ return inputAccessoryViewController_.get(); |
+} |
+ |
+@end |