| // 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/reading_list/core/reading_list_switches.h" |
| #include "components/reading_list/ios/reading_list_model.h" |
| #include "components/search_engines/template_url_service.h" |
| #include "components/sessions/core/session_types.h" |
| #include "components/sessions/ios/ios_serialized_navigation_builder.h" |
| #include "components/signin/core/browser/account_reconcilor.h" |
| #include "components/signin/core/browser/signin_metrics.h" |
| #import "components/signin/ios/browser/account_consistency_service.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "components/url_formatter/url_formatter.h" |
| #include "ios/chrome/browser/application_context.h" |
| #import "ios/chrome/browser/autofill/autofill_controller.h" |
| #import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h" |
| #import "ios/chrome/browser/autofill/form_suggestion_controller.h" |
| #include "ios/chrome/browser/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/reading_list/reading_list_model_factory.h" |
| #include "ios/chrome/browser/reading_list/reading_list_web_state_observer.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_headers_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/alert_coordinator/form_resubmission_coordinator.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/ui/util/top_view_controller.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/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" |
| #import "ios/web/public/web_state/web_state_observer_bridge.h" |
| #include "ios/web/public/web_thread.h" |
| #import "ios/web/web_state/ui/crw_web_controller.h" |
| #import "ios/web/web_state/web_state_impl.h" |
| #include "net/base/escape.h" |
| #include "net/base/filename_util.h" |
| #import "net/base/mac/url_conversions.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| #include "net/cert/x509_certificate.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/url_request/url_fetcher.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/page_transition_types.h" |
| #include "url/origin.h" |
| |
| 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, |
| CRWWebStateObserver, |
| 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_; |
| |
| // The delegate responsible for headers over the tab. |
| id<TabHeadersDelegate> tabHeadersDelegate_; // weak |
| |
| 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_; |
| |
| // Coordinates Form Resubmission dialog presentation. |
| base::scoped_nsobject<FormResubmissionCoordinator> |
| formResubmissionCoordinator_; |
| |
| // 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_; |
| |
| // Allows Tab to conform CRWWebStateDelegate protocol. |
| std::unique_ptr<web::WebStateObserverBridge> webStateObserver_; |
| |
| // Context used by history to scope the lifetime of navigation entry |
| // references to Tab. |
| 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; |
| |
| // 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_; |
| @synthesize tabHeadersDelegate = tabHeadersDelegate_; |
| |
| - (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())); |
| webStateObserver_.reset( |
| new web::WebStateObserverBridge(webStateImpl_.get(), self)); |
| |
| [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); |
| |
| if (reading_list::switches::IsReadingListEnabled()) { |
| ReadingListModel* model = |
| ReadingListModelFactory::GetForBrowserState(browserState_); |
| ReadingListWebStateObserver::FromWebState(self.webState, model); |
| } |
| |
| 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 { |
| std::vector<std::unique_ptr<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_]; |
| } |
| OverscrollStyle style = OverscrollStyle::REGULAR_PAGE_NON_INCOGNITO; |
| if (browserState_->IsOffTheRecord()) { |
| style = 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(); |
| |
| formResubmissionCoordinator_.reset(); |
| |
| // 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); |
| } |
| } |
| |
| - (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 - CRWWebDelegate and CRWWebStateObserver 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 { |
| UIViewController* topController = |
| top_view_controller::TopPresentedViewControllerFrom( |
| [UIApplication sharedApplication].keyWindow.rootViewController); |
| |
| // Display the action sheet with the arrow pointing at the top center of the |
| // web contents. |
| CGPoint dialogLocation = |
| CGPointMake(CGRectGetMidX(webController.view.frame), |
| CGRectGetMinY(webController.view.frame) + |
| [self.tabHeadersDelegate headerHeightForTab:self]); |
| |
| formResubmissionCoordinator_.reset([[FormResubmissionCoordinator alloc] |
| initWithBaseViewController:topController |
| dialogLocation:dialogLocation |
| webState:webController.webState |
| completionHandler:^(BOOL shouldContinue) { |
| if (shouldContinue) |
| continueBlock(); |
| else |
| cancelBlock(); |
| }]); |
| [formResubmissionCoordinator_ start]; |
| } |
| |
| // 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]); |
| formResubmissionCoordinator_.reset(); |
| |
| // 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)webState:(web::WebState*)webState |
| didStartProvisionalNavigationForURL:(const GURL&)URL { |
| [parentTabModel_ notifyTabChanged:self]; |
| [openInController_ disable]; |
| [[NSNotificationCenter defaultCenter] |
| postNotificationName: |
| kTabClosingCurrentDocumentNotificationForCrashReporting |
| object:self]; |
| [metricsClientManager_ pageLoadStarted: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]]; |
| } |
| } |
| |
| - (void)webState:(web::WebState*)webState |
| didLoadPageWithSuccess:(BOOL)loadSuccess { |
| DCHECK(self.webController.loadPhase == web::PAGE_LOADED); |
| |
| // Cancel prerendering if response is "application/octet-stream". It can be a |
| // video file which should not be played from preload tab (crbug.com/436813). |
| if (isPrerenderTab_ && |
| self.webState->GetContentsMimeType() == "application/octet-stream") { |
| [delegate_ discardPrerender]; |
| } |
| |
| bool wasPost = false; |
| if (self.currentSessionEntry) |
| wasPost = self.currentSessionEntry.navigationItem->HasPostData(); |
| GURL lastCommittedURL = self.webState->GetLastCommittedURL(); |
| if (loadSuccess) |
| [autoReloadBridge_ loadFinishedForURL:lastCommittedURL wasPost:wasPost]; |
| else |
| [autoReloadBridge_ loadFailedForURL:lastCommittedURL wasPost:wasPost]; |
| [webControllerSnapshotHelper_ setSnapshotCoalescingEnabled:YES]; |
| if (!loadSuccess) { |
| [fullScreenController_ disableFullScreen]; |
| } |
| [self recordInterfaceOrientation]; |
| navigation_metrics::OriginsSeenService* originsSeenService = |
| IOSChromeOriginsSeenServiceFactory::GetForBrowserState(self.browserState); |
| bool alreadySeen = |
| originsSeenService->Insert(url::Origin::Origin(lastCommittedURL)); |
| navigation_metrics::RecordMainFrameNavigation( |
| lastCommittedURL, true, self.browserState->IsOffTheRecord(), alreadySeen); |
| |
| 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(lastCommittedURL)); |
| // 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 [self.tabHeadersDelegate headerHeightForTab:self]; |
| } |
| |
| - (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]; |
| webStateObserver_.reset( |
| new web::WebStateObserverBridge(webStateImpl_.get(), 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 |