| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/web/web_state/ui/crw_web_controller.h" |
| |
| #import <WebKit/WebKit.h> |
| |
| #import "base/apple/foundation_util.h" |
| #import "base/containers/contains.h" |
| #import "base/functional/bind.h" |
| #import "base/ios/block_types.h" |
| #import "base/ios/ios_util.h" |
| #import "base/json/string_escape.h" |
| #import "base/metrics/histogram_functions.h" |
| #import "base/metrics/user_metrics.h" |
| #import "base/metrics/user_metrics_action.h" |
| #import "base/strings/sys_string_conversions.h" |
| #import "build/branding_buildflags.h" |
| #import "ios/web/common/annotations_utils.h" |
| #import "ios/web/common/crw_input_view_provider.h" |
| #import "ios/web/common/crw_web_view_content_view.h" |
| #import "ios/web/common/features.h" |
| #import "ios/web/common/uikit_ui_util.h" |
| #import "ios/web/common/url_util.h" |
| #import "ios/web/download/crw_web_view_download.h" |
| #import "ios/web/find_in_page/java_script_find_in_page_manager_impl.h" |
| #import "ios/web/history_state_util.h" |
| #import "ios/web/js_features/scroll_helper/scroll_helper_java_script_feature.h" |
| #import "ios/web/js_messaging/java_script_feature_util_impl.h" |
| #import "ios/web/js_messaging/web_view_js_utils.h" |
| #import "ios/web/js_messaging/web_view_web_state_map.h" |
| #import "ios/web/navigation/crw_error_page_helper.h" |
| #import "ios/web/navigation/crw_js_navigation_handler.h" |
| #import "ios/web/navigation/crw_navigation_item_holder.h" |
| #import "ios/web/navigation/crw_web_view_navigation_observer.h" |
| #import "ios/web/navigation/crw_web_view_navigation_observer_delegate.h" |
| #import "ios/web/navigation/crw_wk_navigation_handler.h" |
| #import "ios/web/navigation/crw_wk_navigation_states.h" |
| #import "ios/web/navigation/navigation_context_impl.h" |
| #import "ios/web/navigation/wk_back_forward_list_item_holder.h" |
| #import "ios/web/navigation/wk_navigation_util.h" |
| #import "ios/web/public/annotations/annotations_text_manager.h" |
| #import "ios/web/public/browser_state.h" |
| #import "ios/web/public/find_in_page/crw_find_interaction.h" |
| #import "ios/web/public/permissions/permissions.h" |
| #import "ios/web/public/ui/crw_web_view_scroll_view_proxy.h" |
| #import "ios/web/public/web_client.h" |
| #import "ios/web/security/crw_cert_verification_controller.h" |
| #import "ios/web/security/crw_ssl_status_updater.h" |
| #import "ios/web/text_fragments/text_fragments_manager_impl.h" |
| #import "ios/web/web_state/crw_web_view.h" |
| #import "ios/web/web_state/ui/crw_context_menu_controller.h" |
| #import "ios/web/web_state/ui/crw_web_controller_container_view.h" |
| #import "ios/web/web_state/ui/crw_web_request_controller.h" |
| #import "ios/web/web_state/ui/crw_web_view_proxy_impl.h" |
| #import "ios/web/web_state/ui/crw_wk_ui_handler.h" |
| #import "ios/web/web_state/ui/crw_wk_ui_handler_delegate.h" |
| #import "ios/web/web_state/ui/wk_web_view_configuration_provider.h" |
| #import "ios/web/web_state/user_interaction_state.h" |
| #import "ios/web/web_state/web_state_impl.h" |
| #import "ios/web/web_state/web_view_internal_creation_util.h" |
| #import "ios/web/web_view/content_type_util.h" |
| #import "ios/web/web_view/wk_web_view_util.h" |
| #import "net/base/apple/url_conversions.h" |
| #import "services/metrics/public/cpp/ukm_builders.h" |
| #import "url/gurl.h" |
| |
| using web::NavigationManager; |
| using web::NavigationManagerImpl; |
| using web::WebState; |
| using web::WebStateImpl; |
| |
| using web::wk_navigation_util::IsRestoreSessionUrl; |
| using web::wk_navigation_util::IsWKInternalUrl; |
| |
| namespace { |
| char const kFullScreenStateHistogram[] = "IOS.Fullscreen.State"; |
| } // namespace |
| |
| // TODO(crbug.com/40746865): Allow usage of iOS15 interactionState on iOS 14 SDK |
| // based builds. |
| #if !defined(__IPHONE_15_0) || __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_15_0 |
| @interface WKWebView (Additions) |
| @property(nonatomic, nullable, copy) id interactionState; |
| @end |
| #endif |
| |
| @interface CRWWebController () <CRWWKNavigationHandlerDelegate, |
| CRWInputViewProvider, |
| CRWSSLStatusUpdaterDataSource, |
| CRWSSLStatusUpdaterDelegate, |
| CRWWebControllerContainerViewDelegate, |
| CRWWebViewNavigationObserverDelegate, |
| CRWWebRequestControllerDelegate, |
| CRWWebViewScrollViewProxyObserver, |
| CRWWKNavigationHandlerDelegate, |
| CRWWKUIHandlerDelegate, |
| UIDropInteractionDelegate, |
| WKNavigationDelegate> { |
| // The view used to display content. Must outlive `_webViewProxy`. The |
| // container view should be accessed through this property rather than |
| // `self.view` from within this class, as `self.view` triggers creation while |
| // `self.containerView` will return nil if the view hasn't been instantiated. |
| CRWWebControllerContainerView* _containerView; |
| // YES if the current URL load was triggered in Web Controller. NO by default |
| // and after web usage was disabled. Used by `-loadCurrentURLIfNecessary` to |
| // prevent extra loads. |
| BOOL _currentURLLoadWasTrigerred; |
| BOOL _isBeingDestroyed; // YES if in the process of closing. |
| // The actual URL of the document object (i.e., the last committed URL). |
| // TODO(crbug.com/41213672): Remove this in favor of just updating the |
| // navigation manager and treating that as authoritative. |
| GURL _documentURL; |
| // Actions to execute once the page load is complete. |
| NSMutableArray* _pendingLoadCompleteActions; |
| // Flag to say if browsing is enabled. |
| BOOL _webUsageEnabled; |
| // Default URL (about:blank). |
| GURL _defaultURL; |
| |
| // Updates SSLStatus for current navigation item. |
| CRWSSLStatusUpdater* _SSLStatusUpdater; |
| |
| // Controller used for certs verification to help with blocking requests with |
| // bad SSL cert, presenting SSL interstitials and determining SSL status for |
| // Navigation Items. |
| CRWCertVerificationController* _certVerificationController; |
| |
| // State of user interaction with web content. |
| web::UserInteractionState _userInteractionState; |
| } |
| |
| // The WKNavigationDelegate handler class. |
| @property(nonatomic, readonly, strong) |
| CRWWKNavigationHandler* navigationHandler; |
| @property(nonatomic, readonly, strong) |
| CRWJSNavigationHandler* jsNavigationHandler; |
| // The WKUIDelegate handler class. |
| @property(nonatomic, readonly, strong) CRWWKUIHandler* UIHandler; |
| |
| // YES if in the process of closing. |
| @property(nonatomic, readwrite, assign) BOOL beingDestroyed; |
| |
| // If `contentView_` contains a web view, this is the web view it contains. |
| // If not, it's nil. When setting the property, it performs basic setup. |
| @property(weak, nonatomic) WKWebView* webView; |
| // The scroll view of `webView`. |
| @property(weak, nonatomic, readonly) UIScrollView* webScrollView; |
| |
| @property(nonatomic, strong, readonly) |
| CRWWebViewNavigationObserver* webViewNavigationObserver; |
| |
| // Dictionary where keys are the names of WKWebView properties and values are |
| // selector names which should be called when a corresponding property has |
| // changed. e.g. @{ @"URL" : @"webViewURLDidChange" } means that |
| // -[self webViewURLDidChange] must be called every time when WKWebView.URL is |
| // changed. |
| @property(weak, nonatomic, readonly) NSDictionary* WKWebViewObservers; |
| |
| // Url request controller. |
| @property(nonatomic, strong, readonly) |
| CRWWebRequestController* requestController; |
| |
| @property(nonatomic, readonly) web::WebState* webState; |
| // WebStateImpl instance associated with this CRWWebController, web controller |
| // does not own this pointer. |
| @property(nonatomic, readonly) web::WebStateImpl* webStateImpl; |
| |
| // Returns the x, y offset the content has been scrolled. |
| @property(nonatomic, readonly) CGPoint scrollPosition; |
| |
| // The touch tracking recognizer allowing us to decide if a navigation has user |
| // gesture. Lazily created. |
| @property(nonatomic, strong, readonly) |
| CRWTouchTrackingRecognizer* touchTrackingRecognizer; |
| |
| // A custom drop interaction that is added alongside the web view's default drop |
| // interaction. |
| @property(nonatomic, strong) UIDropInteraction* customDropInteraction; |
| |
| // Session Information |
| // ------------------- |
| // The associated NavigationManagerImpl. |
| @property(nonatomic, readonly) NavigationManagerImpl* navigationManagerImpl; |
| // TODO(crbug.com/40506829): Remove these functions and replace with more |
| // appropriate NavigationItem getters. |
| // Returns the navigation item for the current page. |
| @property(nonatomic, readonly) web::NavigationItemImpl* currentNavItem; |
| |
| // ContextMenu controller, handling the interactions with the context menu. |
| @property(nonatomic, strong) CRWContextMenuController* contextMenuController; |
| |
| // Called following navigation completion to generate final navigation lifecycle |
| // events. Navigation is considered complete when the document has finished |
| // loading, or when other page load mechanics are completed on a |
| // non-document-changing URL change. |
| - (void)didFinishNavigation:(web::NavigationContextImpl*)context; |
| // Update the appropriate parts of the model and broadcast to the embedder. This |
| // may be called multiple times and thus must be idempotent. |
| - (void)loadCompleteWithSuccess:(BOOL)loadSuccess |
| forContext:(web::NavigationContextImpl*)context; |
| // Calls the zoom-preparation UIScrollViewDelegate callbacks on the web view. |
| // This is called before `-applyWebViewScrollZoomScaleFromScrollState:`. |
| - (void)prepareToApplyWebViewScrollZoomScale; |
| // Calls the zoom-completion UIScrollViewDelegate callbacks on the web view. |
| // This is called after `-applyWebViewScrollZoomScaleFromScrollState:`. |
| - (void)finishApplyingWebViewScrollZoomScale; |
| // Finds all the scrollviews in the view hierarchy and makes sure they do not |
| // interfere with scroll to top when tapping the statusbar. |
| - (void)optOutScrollsToTopForSubviews; |
| // Updates SSL status for the current navigation item based on the information |
| // provided by web view. |
| - (void)updateSSLStatusForCurrentNavigationItem; |
| |
| @end |
| |
| @implementation CRWWebController |
| |
| // Synthesize as it is readonly. |
| @synthesize touchTrackingRecognizer = _touchTrackingRecognizer; |
| |
| #pragma mark - Object lifecycle |
| |
| - (instancetype)initWithWebState:(WebStateImpl*)webState { |
| self = [super init]; |
| if (self) { |
| _webStateImpl = webState; |
| _webUsageEnabled = YES; |
| |
| _allowsBackForwardNavigationGestures = YES; |
| |
| DCHECK(_webStateImpl); |
| // Content area is lazily instantiated. |
| _defaultURL = GURL(url::kAboutBlankURL); |
| _requestController = [[CRWWebRequestController alloc] init]; |
| _requestController.delegate = self; |
| _webViewProxy = [[CRWWebViewProxyImpl alloc] initWithWebController:self]; |
| [[_webViewProxy scrollViewProxy] addObserver:self]; |
| _pendingLoadCompleteActions = [[NSMutableArray alloc] init]; |
| web::BrowserState* browserState = _webStateImpl->GetBrowserState(); |
| _certVerificationController = [[CRWCertVerificationController alloc] |
| initWithBrowserState:browserState]; |
| web::JavaScriptFindInPageManagerImpl::CreateForWebState(_webStateImpl); |
| web::TextFragmentsManagerImpl::CreateForWebState(_webStateImpl); |
| |
| if (!browserState->IsOffTheRecord()) { |
| web::AnnotationsTextManager::CreateForWebState(_webStateImpl); |
| } |
| |
| _navigationHandler = [[CRWWKNavigationHandler alloc] initWithDelegate:self]; |
| |
| _jsNavigationHandler = [[CRWJSNavigationHandler alloc] init]; |
| |
| _UIHandler = [[CRWWKUIHandler alloc] init]; |
| _UIHandler.delegate = self; |
| |
| _webViewNavigationObserver = [[CRWWebViewNavigationObserver alloc] init]; |
| _webViewNavigationObserver.delegate = self; |
| } |
| return self; |
| } |
| |
| - (void)dealloc { |
| DCHECK([NSThread isMainThread]); |
| DCHECK(_isBeingDestroyed); // 'close' must have been called already. |
| DCHECK(!_webView); |
| } |
| |
| #pragma mark - Public property accessors |
| |
| - (void)setWebUsageEnabled:(BOOL)enabled { |
| if (_webUsageEnabled == enabled) |
| return; |
| // WKWebView autoreleases its WKProcessPool on removal from superview. |
| // Deferring WKProcessPool deallocation may lead to issues with cookie |
| // clearing and and Browsing Data Partitioning implementation. |
| @autoreleasepool { |
| if (!enabled) { |
| [self removeWebView]; |
| } |
| } |
| |
| _webUsageEnabled = enabled; |
| |
| // WKWebView autoreleases its WKProcessPool on removal from superview. |
| // Deferring WKProcessPool deallocation may lead to issues with cookie |
| // clearing and and Browsing Data Partitioning implementation. |
| @autoreleasepool { |
| if (enabled) { |
| // Don't create the web view; let it be lazy created as needed. |
| |
| // The gesture is removed when the web usage is disabled. Add it back when |
| // it is enabled again. |
| [_containerView addGestureRecognizer:[self touchTrackingRecognizer]]; |
| } else { |
| if (_touchTrackingRecognizer) { |
| [_containerView removeGestureRecognizer:_touchTrackingRecognizer]; |
| _touchTrackingRecognizer.touchTrackingDelegate = nil; |
| _touchTrackingRecognizer = nil; |
| } |
| _currentURLLoadWasTrigerred = NO; |
| } |
| } |
| } |
| |
| - (UIView*)view { |
| [self ensureContainerViewCreated]; |
| DCHECK(_containerView); |
| return _containerView; |
| } |
| |
| - (id<CRWWebViewNavigationProxy>)webViewNavigationProxy { |
| return static_cast<id<CRWWebViewNavigationProxy>>(self.webView); |
| } |
| |
| - (double)loadingProgress { |
| return [self.webView estimatedProgress]; |
| } |
| |
| - (BOOL)isWebProcessCrashed { |
| return self.navigationHandler.webProcessCrashed; |
| } |
| |
| - (BOOL)isUserInteracting { |
| return _userInteractionState.IsUserInteracting(self.webView); |
| } |
| |
| - (void)setAllowsBackForwardNavigationGestures: |
| (BOOL)allowsBackForwardNavigationGestures { |
| // Store it to an instance variable as well as |
| // self.webView.allowsBackForwardNavigationGestures because self.webView may |
| // be nil. When self.webView is nil, it will be set later in -setWebView:. |
| _allowsBackForwardNavigationGestures = allowsBackForwardNavigationGestures; |
| self.webView.allowsBackForwardNavigationGestures = |
| allowsBackForwardNavigationGestures; |
| } |
| |
| #pragma mark - Private properties accessors |
| |
| - (void)setWebView:(WKWebView*)webView { |
| DCHECK_NE(_webView, webView); |
| |
| // Unwind the old web view. |
| |
| // Remove KVO and WK*Delegate before calling methods on WKWebView so that |
| // handlers won't receive unnecessary callbacks. |
| [_webView setNavigationDelegate:nil]; |
| [_webView setUIDelegate:nil]; |
| for (NSString* keyPath in self.WKWebViewObservers) { |
| [_webView removeObserver:self forKeyPath:keyPath]; |
| } |
| self.webViewNavigationObserver.webView = nil; |
| |
| web::WebViewWebStateMap::FromBrowserState( |
| self.webStateImpl->GetBrowserState()) |
| ->SetAssociatedWebViewForWebState(webView, self.webStateImpl); |
| |
| if (_webView) { |
| self.webStateImpl->RemoveAllWebFrames(); |
| |
| [_webView stopLoading]; |
| [_webView removeFromSuperview]; |
| |
| // Since the WKWebView is about to be released, the kvo for the `loading` |
| // state will not be received. Without manually setting loading to false, |
| // the tab will appear to be endlessly loading until the next page load |
| // completes. |
| self.webStateImpl->SetIsLoading(false); |
| } |
| |
| // Set up the new web view. |
| _webView = webView; |
| |
| if (_webView) { |
| [_webView setNavigationDelegate:self.navigationHandler]; |
| [_webView setUIDelegate:self.UIHandler]; |
| for (NSString* keyPath in self.WKWebViewObservers) { |
| [_webView addObserver:self forKeyPath:keyPath options:0 context:nullptr]; |
| } |
| |
| _webView.allowsBackForwardNavigationGestures = |
| _allowsBackForwardNavigationGestures; |
| } |
| self.webViewNavigationObserver.webView = _webView; |
| |
| [self setDocumentURL:_defaultURL context:nullptr]; |
| } |
| |
| - (UIScrollView*)webScrollView { |
| return self.webView.scrollView; |
| } |
| |
| - (NSDictionary*)WKWebViewObservers { |
| NSMutableDictionary<NSString*, NSString*>* observers = |
| [[NSMutableDictionary alloc] initWithDictionary:@{ |
| @"serverTrust" : @"webViewSecurityFeaturesDidChange", |
| @"hasOnlySecureContent" : @"webViewSecurityFeaturesDidChange", |
| @"title" : @"webViewTitleDidChange", |
| @"cameraCaptureState" : @"webViewCameraCaptureStateDidChange", |
| @"microphoneCaptureState" : @"webViewMicrophoneCaptureStateDidChange", |
| }]; |
| |
| if (web::GetWebClient()->EnableFullscreenAPI()) { |
| [observers addEntriesFromDictionary:@{ |
| @"fullscreenState" : @"fullscreenStateDidChange" |
| }]; |
| } |
| |
| return observers; |
| } |
| |
| - (WebState*)webState { |
| return _webStateImpl; |
| } |
| |
| - (CGPoint)scrollPosition { |
| return self.webScrollView.contentOffset; |
| } |
| |
| - (CRWTouchTrackingRecognizer*)touchTrackingRecognizer { |
| if (!_touchTrackingRecognizer) { |
| _touchTrackingRecognizer = |
| [[CRWTouchTrackingRecognizer alloc] initWithTouchTrackingDelegate:self]; |
| } |
| return _touchTrackingRecognizer; |
| } |
| |
| - (BOOL)isCover { |
| return _containerView.cover; |
| } |
| |
| #pragma mark Navigation and Session Information |
| |
| - (NavigationManagerImpl*)navigationManagerImpl { |
| return self.webStateImpl ? &(self.webStateImpl->GetNavigationManagerImpl()) |
| : nil; |
| } |
| |
| - (web::NavigationItemImpl*)currentNavItem { |
| return self.navigationManagerImpl |
| ? self.navigationManagerImpl->GetCurrentItemImpl() |
| : nullptr; |
| } |
| |
| #pragma mark - ** Public Methods ** |
| |
| #pragma mark - Header public methods |
| |
| - (web::NavigationItemImpl*)lastPendingItemForNewNavigation { |
| WKNavigation* navigation = |
| [self.navigationHandler.navigationStates |
| lastNavigationWithPendingItemInNavigationContext]; |
| if (!navigation) |
| return nullptr; |
| web::NavigationContextImpl* context = |
| [self.navigationHandler.navigationStates contextForNavigation:navigation]; |
| return context->GetItem(); |
| } |
| |
| // Caller must reset the delegate before calling. |
| - (void)close { |
| self.webStateImpl->CancelDialogs(); |
| |
| _SSLStatusUpdater = nil; |
| [self.navigationHandler close]; |
| [self.UIHandler close]; |
| [self.jsNavigationHandler close]; |
| [self.requestController close]; |
| [self.webViewNavigationObserver close]; |
| |
| // Mark the destruction sequence has started, in case someone else holds a |
| // strong reference and tries to continue using the tab. |
| DCHECK(!_isBeingDestroyed); |
| _isBeingDestroyed = YES; |
| |
| // Remove the web view now. Otherwise, delegate callbacks occur. |
| [self removeWebView]; |
| |
| // Explicitly reset content to clean up views and avoid dangling KVO |
| // observers. |
| [_containerView resetContentForShutdown:YES]; |
| |
| _webStateImpl = nullptr; |
| |
| DCHECK(!self.webView); |
| // TODO(crbug.com/41284914): Don't set the delegate to nil. |
| [_containerView setDelegate:nil]; |
| _touchTrackingRecognizer.touchTrackingDelegate = nil; |
| [[_webViewProxy scrollViewProxy] removeObserver:self]; |
| [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| } |
| |
| - (BOOL)isViewAlive { |
| return !self.navigationHandler.webProcessCrashed && |
| [_containerView isViewAlive]; |
| } |
| |
| - (BOOL)contentIsHTML { |
| return self.webView && |
| web::IsContentTypeHtml(self.webState->GetContentsMimeType()); |
| } |
| |
| - (GURL)currentURL { |
| // The web view URL is the current URL only if it is neither a placeholder URL |
| // (used to hold WKBackForwardListItem for WebUI) nor a restore_session.html |
| // (used to replay session history in WKWebView). |
| // TODO(crbug.com/40528091): Investigate if this method is still needed and if |
| // it can be implemented using NavigationManager API after removal of legacy |
| // navigation stack. |
| if (self.webView && !IsWKInternalUrl(self.webView.URL)) { |
| return _documentURL; |
| } |
| |
| web::NavigationItem* item = |
| self.navigationManagerImpl |
| ->GetLastCommittedItemInCurrentOrRestoredSession(); |
| if (item) { |
| // This special case is added for any app specific URLs that have been |
| // rewritten to about:// URLs. |
| if (item->GetURL().SchemeIs(url::kAboutScheme) && |
| web::GetWebClient()->IsAppSpecificURL(item->GetVirtualURL())) { |
| return item->GetURL(); |
| } |
| return item->GetVirtualURL(); |
| } |
| return GURL(); |
| } |
| |
| - (void)reloadWithRendererInitiatedNavigation:(BOOL)rendererInitiated { |
| // Clear last user interaction. |
| // TODO(crbug.com/41211432): Move to after the load commits, in the subclass |
| // implementation. This will be inaccurate if the reload fails or is |
| // cancelled. |
| _userInteractionState.SetLastUserInteraction(nullptr); |
| base::RecordAction(base::UserMetricsAction("Reload")); |
| [_requestController reloadWithRendererInitiatedNavigation:rendererInitiated]; |
| } |
| |
| - (void)stopLoading { |
| base::RecordAction(base::UserMetricsAction("Stop")); |
| // Discard all pending items before notifying WebState observers |
| self.navigationManagerImpl->DiscardNonCommittedItems(); |
| for (__strong id navigation in |
| [self.navigationHandler.navigationStates pendingNavigations]) { |
| if (navigation == [NSNull null]) { |
| // null is a valid navigation object passed to WKNavigationDelegate |
| // callbacks and represents window opening action. |
| navigation = nil; |
| } |
| // This will remove pending item for navigations which may still call |
| // WKNavigationDelegate callbacks see (crbug.com/969915). |
| web::NavigationContextImpl* context = |
| [self.navigationHandler.navigationStates |
| contextForNavigation:navigation]; |
| context->ReleaseItem(); |
| } |
| |
| [self.webView stopLoading]; |
| [self.navigationHandler stopLoading]; |
| } |
| |
| - (void)loadCurrentURLWithRendererInitiatedNavigation:(BOOL)rendererInitiated { |
| // If the content view doesn't exist, the tab has either been evicted, or |
| // never displayed. Bail, and let the URL be loaded when the tab is shown. |
| if (!_containerView) |
| return; |
| |
| // NavigationManagerImpl needs WKWebView to load native views, but WKWebView |
| // cannot be created while web usage is disabled to avoid breaking clearing |
| // browser data. Bail now and let the URL be loaded when web usage is enabled |
| // again. This can happen when purging web pages when an interstitial is |
| // presented over a native view. See https://crbug.com/865985 for details. |
| if (!_webUsageEnabled) |
| return; |
| |
| _currentURLLoadWasTrigerred = YES; |
| |
| [_requestController |
| loadCurrentURLWithRendererInitiatedNavigation:rendererInitiated]; |
| } |
| |
| - (void)loadCurrentURLIfNecessary { |
| if (self.navigationHandler.webProcessCrashed) { |
| // Log a user reloading a previously crashed renderer. |
| base::RecordAction( |
| base::UserMetricsAction("IOSMobileReloadCrashedRenderer")); |
| [self loadCurrentURLWithRendererInitiatedNavigation:NO]; |
| } else if (!_currentURLLoadWasTrigerred) { |
| [self ensureContainerViewCreated]; |
| |
| // TODO(crbug.com/41361784): end the practice of calling `loadCurrentURL` |
| // when it is possible there is no current URL. If the call performs |
| // necessary initialization, break that out. |
| [self loadCurrentURLWithRendererInitiatedNavigation:NO]; |
| } |
| } |
| |
| - (void)loadData:(NSData*)data |
| MIMEType:(NSString*)MIMEType |
| forURL:(const GURL&)URL { |
| [_requestController loadData:data MIMEType:MIMEType forURL:URL]; |
| } |
| |
| - (void)loadSimulatedRequest:(const GURL&)URL |
| responseHTMLString:(NSString*)responseHTMLString { |
| NSURLRequest* request = |
| [[NSURLRequest alloc] initWithURL:net::NSURLWithGURL(URL)]; |
| [self.webView loadSimulatedRequest:request |
| responseHTMLString:responseHTMLString]; |
| } |
| |
| - (void)loadSimulatedRequest:(const GURL&)URL |
| responseData:(NSData*)responseData |
| MIMEType:(NSString*)MIMEType { |
| NSURL* url = net::NSURLWithGURL(URL); |
| NSURLRequest* request = [[NSURLRequest alloc] initWithURL:url]; |
| NSURLResponse* response = |
| [[NSURLResponse alloc] initWithURL:url |
| MIMEType:MIMEType |
| expectedContentLength:responseData.length |
| textEncodingName:nil]; |
| |
| [self.webView loadSimulatedRequest:request |
| response:response |
| responseData:responseData]; |
| } |
| |
| // Loads the HTML into the page at the given URL. Only for testing purpose. |
| - (void)loadHTML:(NSString*)HTML forURL:(const GURL&)URL { |
| [_requestController loadHTML:HTML forURL:URL]; |
| } |
| |
| - (void)recordStateInHistory { |
| // Only record the state if: |
| // - the current NavigationItem's URL matches the current URL, and |
| // - the user has interacted with the page. |
| } |
| |
| - (void)setVisible:(BOOL)visible { |
| _visible = visible; |
| } |
| |
| - (void)wasShown { |
| self.visible = YES; |
| |
| // WebKit adds a drop interaction to a subview (WKContentView) of WKWebView's |
| // scrollView when the web view is added to the view hierarchy. |
| [self addCustomURLDropInteractionIfNeeded]; |
| } |
| |
| - (void)wasHidden { |
| self.visible = NO; |
| if (_isBeingDestroyed) |
| return; |
| [self recordStateInHistory]; |
| } |
| |
| - (void)setKeepsRenderProcessAlive:(BOOL)keepsRenderProcessAlive { |
| _keepsRenderProcessAlive = keepsRenderProcessAlive; |
| [_containerView |
| updateWebViewContentViewForContainerWindow:_containerView.window]; |
| } |
| |
| - (void)goToBackForwardListItem:(WKBackForwardListItem*)wk_item |
| navigationItem:(web::NavigationItem*)item |
| navigationInitiationType:(web::NavigationInitiationType)type |
| hasUserGesture:(BOOL)hasUserGesture { |
| WKNavigation* navigation = [self.webView goToBackForwardListItem:wk_item]; |
| |
| GURL URL = net::GURLWithNSURL(wk_item.URL); |
| |
| self.webStateImpl->ClearWebUI(); |
| |
| // This navigation can be an iframe navigation, but it's not possible to |
| // distinguish it from the main frame navigation, so context still has to be |
| // created. |
| std::unique_ptr<web::NavigationContextImpl> context = |
| web::NavigationContextImpl::CreateNavigationContext( |
| self.webStateImpl, URL, hasUserGesture, |
| static_cast<ui::PageTransition>( |
| item->GetTransitionType() | |
| ui::PageTransition::PAGE_TRANSITION_FORWARD_BACK), |
| type == web::NavigationInitiationType::RENDERER_INITIATED); |
| context->SetNavigationItemUniqueID(item->GetUniqueID()); |
| bool isSameDocument = web::GURLByRemovingRefFromGURL(URL) == |
| web::GURLByRemovingRefFromGURL(_documentURL); |
| if (isSameDocument) { |
| context->SetIsSameDocument(true); |
| } else { |
| self.navigationHandler.navigationState = web::WKNavigationState::REQUESTED; |
| } |
| |
| if ([CRWErrorPageHelper isErrorPageFileURL:URL]) { |
| context->SetLoadingErrorPage(true); |
| } |
| |
| web::WKBackForwardListItemHolder* holder = |
| web::WKBackForwardListItemHolder::FromNavigationItem(item); |
| holder->set_navigation_type(WKNavigationTypeBackForward); |
| context->SetIsPost( |
| (holder && [holder->http_method() isEqualToString:@"POST"]) || |
| item->HasPostData()); |
| |
| if (holder) { |
| context->SetMimeType(holder->mime_type()); |
| } |
| |
| [self.navigationHandler.navigationStates setContext:std::move(context) |
| forNavigation:navigation]; |
| [self.navigationHandler.navigationStates |
| setState:web::WKNavigationState::REQUESTED |
| forNavigation:navigation]; |
| } |
| |
| - (void)takeSnapshotWithRect:(CGRect)rect |
| completion:(void (^)(UIImage*))completion { |
| if (!self.webView) { |
| dispatch_async(dispatch_get_main_queue(), ^{ |
| completion(nil); |
| }); |
| return; |
| } |
| |
| WKSnapshotConfiguration* configuration = |
| [[WKSnapshotConfiguration alloc] init]; |
| CGRect convertedRect = [self.webView convertRect:rect fromView:self.view]; |
| configuration.rect = convertedRect; |
| __weak CRWWebController* weakSelf = self; |
| [self.webView |
| takeSnapshotWithConfiguration:configuration |
| completionHandler:^(UIImage* snapshot, NSError* error) { |
| // Pass nil to the completion block if there is an error |
| // or if the web view has been removed before the |
| // snapshot is finished. `snapshot` can sometimes be |
| // corrupt if it's sent due to the WKWebView's |
| // deallocation, so callbacks received after |
| // `-removeWebView` are ignored to prevent crashing. |
| if (error || !weakSelf.webView) { |
| if (error) { |
| DLOG(ERROR) |
| << "WKWebView snapshot error: " |
| << base::SysNSStringToUTF8(error.description); |
| } |
| completion(nil); |
| } else { |
| completion(snapshot); |
| } |
| }]; |
| } |
| |
| - (void)createFullPagePDFWithCompletion:(void (^)(NSData*))completionBlock { |
| // Invoke the `completionBlock` with nil rather than a blank PDF for certain |
| // URLs or if there is a javascript dialog running. |
| const GURL& URL = self.webState->GetLastCommittedURL(); |
| if (![self contentIsHTML] || !URL.is_valid() || |
| web::GetWebClient()->IsAppSpecificURL(URL) || |
| self.webStateImpl->IsJavaScriptDialogRunning()) { |
| dispatch_async(dispatch_get_main_queue(), ^{ |
| completionBlock(nil); |
| }); |
| return; |
| } |
| web::CreateFullPagePdf(self.webView, base::BindOnce(completionBlock)); |
| } |
| |
| - (void)closeMediaPresentations { |
| if (@available(iOS 16, *)) { |
| if (self.webView.fullscreenState == WKFullscreenStateInFullscreen || |
| self.webView.fullscreenState == WKFullscreenStateEnteringFullscreen) { |
| [self.webView closeAllMediaPresentationsWithCompletionHandler:^{ |
| }]; |
| return; |
| } |
| } |
| |
| [self.webView requestMediaPlaybackStateWithCompletionHandler:^( |
| WKMediaPlaybackState mediaPlaybackState) { |
| if (mediaPlaybackState == WKMediaPlaybackStateNone) { |
| return; |
| } |
| |
| // Completion handler is needed to avoid a crash when called. |
| [self.webView closeAllMediaPresentationsWithCompletionHandler:^{ |
| }]; |
| }]; |
| } |
| |
| - (void)removeWebViewFromViewHierarchyForShutdown:(BOOL)shutdown { |
| [_containerView resetContentForShutdown:shutdown]; |
| } |
| |
| - (void)addWebViewToViewHierarchy { |
| [self displayWebView]; |
| } |
| |
| - (BOOL)setSessionStateData:(NSData*)data { |
| NSData* interactionState = data; |
| |
| // Old versions of chrome wrapped interactionState in a keyed unarchiver. |
| // This step was unnecessary. Rather than migrate all blobs over, simply |
| // check for an unarchiver here. NSKeyed data will start with 'bplist00', |
| // which differs from the header of a WebKit session coding (0x00000002). |
| // This logic can be removed after this change has gone live for a while. |
| constexpr char kArchiveHeader[] = "bplist00"; |
| if (data.length > strlen(kArchiveHeader) && |
| memcmp(data.bytes, kArchiveHeader, strlen(kArchiveHeader)) == 0) { |
| NSError* error = nil; |
| NSKeyedUnarchiver* unarchiver = |
| [[NSKeyedUnarchiver alloc] initForReadingFromData:data error:&error]; |
| if (!unarchiver || error) { |
| DLOG(WARNING) << "Error creating unarchiver for session state data: " |
| << base::SysNSStringToUTF8([error description]); |
| return NO; |
| } |
| unarchiver.requiresSecureCoding = NO; |
| interactionState = |
| [unarchiver decodeObjectForKey:NSKeyedArchiveRootObjectKey]; |
| if (!interactionState) { |
| DLOG(WARNING) << "Error decoding interactionState."; |
| return NO; |
| } |
| } |
| [self ensureWebViewCreated]; |
| DCHECK_EQ(self.webView.backForwardList.currentItem, nil); |
| self.navigationHandler.blockUniversalLinksOnNextDecidePolicy = true; |
| [self.webView setInteractionState:interactionState]; |
| return YES; |
| } |
| |
| - (web::PermissionState)stateForPermission:(web::Permission)permission { |
| WKMediaCaptureState captureState; |
| switch (permission) { |
| case web::PermissionCamera: |
| captureState = self.webView.cameraCaptureState; |
| break; |
| case web::PermissionMicrophone: |
| captureState = self.webView.microphoneCaptureState; |
| break; |
| } |
| switch (captureState) { |
| case WKMediaCaptureStateActive: |
| return web::PermissionStateAllowed; |
| case WKMediaCaptureStateMuted: |
| return web::PermissionStateBlocked; |
| case WKMediaCaptureStateNone: |
| return web::PermissionStateNotAccessible; |
| } |
| } |
| |
| - (void)setState:(web::PermissionState)state |
| forPermission:(web::Permission)permission { |
| WKMediaCaptureState captureState; |
| switch (state) { |
| case web::PermissionStateAllowed: |
| captureState = WKMediaCaptureStateActive; |
| break; |
| case web::PermissionStateBlocked: |
| captureState = WKMediaCaptureStateMuted; |
| break; |
| case web::PermissionStateNotAccessible: |
| captureState = WKMediaCaptureStateNone; |
| break; |
| } |
| switch (permission) { |
| case web::PermissionCamera: |
| [self.webView setCameraCaptureState:captureState completionHandler:nil]; |
| break; |
| case web::PermissionMicrophone: |
| [self.webView setMicrophoneCaptureState:captureState |
| completionHandler:nil]; |
| break; |
| } |
| } |
| |
| - (NSDictionary<NSNumber*, NSNumber*>*)statesForAllPermissions { |
| return @{ |
| @(web::PermissionCamera) : |
| @([self stateForPermission:web::PermissionCamera]), |
| @(web::PermissionMicrophone) : |
| @([self stateForPermission:web::PermissionMicrophone]) |
| }; |
| } |
| |
| - (NSData*)sessionStateData { |
| return self.webView.interactionState; |
| } |
| |
| - (void)handleViewportFit:(BOOL)isCover { |
| _containerView.cover = isCover; |
| [_containerView layoutSubviews]; |
| } |
| |
| - (void)handleNavigationHashChange { |
| web::NavigationItemImpl* currentItem = self.currentNavItem; |
| if (currentItem) { |
| currentItem->SetIsCreatedFromHashChange(true); |
| } |
| } |
| |
| - (void)handleNavigationWillChangeState { |
| [self.jsNavigationHandler handleNavigationWillChangeState]; |
| } |
| |
| - (void)handleNavigationDidPushStateMessage:(base::Value::Dict*)dict { |
| [self.jsNavigationHandler |
| handleNavigationDidPushStateMessage:dict |
| webState:_webStateImpl |
| hasUserGesture:self.isUserInteracting |
| userInteractionState:&_userInteractionState |
| currentURL:self.currentURL]; |
| [self updateSSLStatusForCurrentNavigationItem]; |
| } |
| |
| - (void)handleNavigationDidReplaceStateMessage:(base::Value::Dict*)dict { |
| [self.jsNavigationHandler |
| handleNavigationDidReplaceStateMessage:dict |
| webState:_webStateImpl |
| hasUserGesture:self.isUserInteracting |
| userInteractionState:&_userInteractionState |
| currentURL:self.currentURL]; |
| } |
| |
| - (void)downloadCurrentPageToDestinationPath:(NSString*)destination |
| delegate: |
| (id<CRWWebViewDownloadDelegate>)delegate |
| handler:(void (^)(id<CRWWebViewDownload>)) |
| handler { |
| const NavigationManagerImpl* navigationManager = self.navigationManagerImpl; |
| GURL url = navigationManager->GetLastCommittedItem() |
| ? navigationManager->GetLastCommittedItem()->GetURL() |
| : [self currentURL]; |
| |
| NSURLRequest* request = [NSURLRequest requestWithURL:net::NSURLWithGURL(url)]; |
| |
| CRWWebViewDownload* download = |
| [[CRWWebViewDownload alloc] initWithPath:destination |
| request:request |
| webview:self.webView |
| delegate:delegate]; |
| [download startDownload]; |
| handler(download); |
| } |
| |
| - (BOOL)findInteractionSupported { |
| if (@available(iOS 16, *)) { |
| // The `findInteraction` property only exists for iOS 16 or later, if there |
| // is a web view. |
| return self.webView != nil; |
| } |
| |
| return false; |
| } |
| |
| - (void)setFindInteractionEnabled:(BOOL)enabled { |
| if (@available(iOS 16, *)) { |
| self.webView.findInteractionEnabled = enabled; |
| } |
| } |
| |
| - (BOOL)findInteractionEnabled { |
| if (@available(iOS 16, *)) { |
| return self.webView.findInteractionEnabled; |
| } |
| |
| return NO; |
| } |
| |
| - (id<CRWFindInteraction>)findInteraction API_AVAILABLE(ios(16)) { |
| if (self.webView.findInteraction) { |
| return [[CRWFindInteraction alloc] |
| initWithUIFindInteraction:self.webView.findInteraction]; |
| } |
| return nil; |
| } |
| |
| - (id)activityItem { |
| if (!self.webView || ![_containerView webViewContentView]) { |
| return nil; |
| } |
| DCHECK([self.webView isKindOfClass:[WKWebView class]]); |
| return self.webView; |
| } |
| |
| - (UIColor*)themeColor { |
| return self.webView.themeColor; |
| } |
| |
| - (UIColor*)underPageBackgroundColor { |
| return self.webView.underPageBackgroundColor; |
| } |
| |
| #pragma mark - JavaScript |
| |
| - (void)retrieveExistingFramesInContentWorld:(WKContentWorld*)contentWorld { |
| web::RegisterExistingFrames(self.webView, contentWorld); |
| } |
| |
| - (void)executeJavaScript:(NSString*)javascript |
| completionHandler:(void (^)(id result, NSError* error))completion { |
| __block void (^stack_completion_block)(id result, NSError* error) = |
| [completion copy]; |
| web::ExecuteJavaScript(self.webView, javascript, ^(id value, NSError* error) { |
| if (error) { |
| DLOG(WARNING) << "Script execution failed with error: " |
| << base::SysNSStringToUTF16( |
| error.userInfo[NSLocalizedDescriptionKey]); |
| } |
| if (stack_completion_block) { |
| stack_completion_block(value, error); |
| } |
| }); |
| } |
| |
| - (void)executeUserJavaScript:(NSString*)javascript |
| completionHandler:(void (^)(id result, NSError* error))completion { |
| // For security reasons, executing JavaScript on pages with app-specific URLs |
| // is not allowed, because those pages may have elevated privileges. |
| if (web::GetWebClient()->IsAppSpecificURL( |
| self.webStateImpl->GetLastCommittedURL())) { |
| if (completion) { |
| dispatch_async(dispatch_get_main_queue(), ^{ |
| NSError* error = [[NSError alloc] |
| initWithDomain:web::kJSEvaluationErrorDomain |
| code:web::JS_EVALUATION_ERROR_CODE_REJECTED |
| userInfo:nil]; |
| completion(nil, error); |
| }); |
| } |
| return; |
| } |
| |
| [self touched:YES]; |
| |
| [self executeJavaScript:javascript completionHandler:completion]; |
| } |
| |
| #pragma mark - CRWTouchTrackingDelegate (Public) |
| |
| - (void)touched:(BOOL)touched { |
| _userInteractionState.SetTapInProgress(touched); |
| if (touched) { |
| _userInteractionState.SetUserInteractionRegisteredSincePageLoaded(true); |
| if (_isBeingDestroyed) |
| return; |
| const NavigationManagerImpl* navigationManager = self.navigationManagerImpl; |
| GURL mainDocumentURL = |
| navigationManager->GetLastCommittedItem() |
| ? navigationManager->GetLastCommittedItem()->GetURL() |
| : [self currentURL]; |
| _userInteractionState.SetLastUserInteraction( |
| std::make_unique<web::UserInteractionEvent>(mainDocumentURL)); |
| [self hideAnnotationsHighlight]; |
| } |
| } |
| |
| #pragma mark - Context Menu |
| |
| // Hides annotations highlights triggered by context menu. |
| - (void)hideAnnotationsHighlight { |
| web::AnnotationsTextManager* manager = |
| web::AnnotationsTextManager::FromWebState(_webStateImpl); |
| if (manager) { |
| manager->RemoveHighlight(); |
| } |
| } |
| |
| #pragma mark - ** Private Methods ** |
| |
| - (void)setDocumentURL:(const GURL&)newURL |
| context:(web::NavigationContextImpl*)context { |
| GURL oldDocumentURL = _documentURL; |
| if (newURL != _documentURL && newURL.is_valid()) { |
| _documentURL = newURL; |
| _userInteractionState.SetUserInteractionRegisteredSinceLastUrlChange(false); |
| } |
| if (context && !context->IsLoadingErrorPage() && |
| !context->IsLoadingHtmlString() && !IsWKInternalUrl(newURL) && |
| !newURL.SchemeIs(url::kAboutScheme) && self.webView) { |
| // On iOS13, WebKit started changing the URL visible webView.URL when |
| // opening a new tab and then writing to it, e.g. |
| // window.open('javascript:document.write(1)'). This URL is never commited, |
| // so it should be OK to ignore this URL change. |
| if (oldDocumentURL.IsAboutBlank() && |
| !self.webStateImpl->GetNavigationManager()->GetLastCommittedItem() && |
| !self.webView.loading) { |
| return; |
| } |
| |
| // Ignore mismatches triggered by a WKWebView out-of-sync back forward list. |
| if (![self.webView.backForwardList.currentItem.URL |
| isEqual:self.webView.URL]) { |
| return; |
| } |
| |
| GURL documentOrigin = newURL.DeprecatedGetOriginAsURL(); |
| web::NavigationItem* committedItem = |
| self.webStateImpl->GetNavigationManager()->GetLastCommittedItem(); |
| GURL committedURL = committedItem ? committedItem->GetURL() : GURL(); |
| GURL committedOrigin = committedURL.DeprecatedGetOriginAsURL(); |
| |
| DCHECK_EQ(documentOrigin, committedOrigin) |
| << "Old and new URL detection system have a mismatch"; |
| |
| ukm::SourceId sourceID = ukm::ConvertToSourceId( |
| context->GetNavigationId(), ukm::SourceIdType::NAVIGATION_ID); |
| if (sourceID != ukm::kInvalidSourceId) { |
| ukm::builders::IOS_URLMismatchInLegacyAndSlimNavigationManager(sourceID) |
| .SetHasMismatch(documentOrigin != committedOrigin) |
| .Record(ukm::UkmRecorder::Get()); |
| } |
| } |
| } |
| |
| - (BOOL)isUserInitiatedAction:(WKNavigationAction*)action { |
| return _userInteractionState.IsUserInteracting(self.webView); |
| } |
| |
| // Adds a custom drop interaction to the same subview of `self.webScrollView` |
| // that already has a default drop interaction. |
| - (void)addCustomURLDropInteractionIfNeeded { |
| BOOL subviewWithDefaultInteractionFound = NO; |
| for (UIView* subview in self.webScrollView.subviews) { |
| BOOL defaultInteractionFound = NO; |
| BOOL customInteractionFound = NO; |
| for (id<UIInteraction> interaction in subview.interactions) { |
| if ([interaction isKindOfClass:[UIDropInteraction class]]) { |
| if (interaction == self.customDropInteraction) { |
| customInteractionFound = YES; |
| } else { |
| DCHECK(!defaultInteractionFound && |
| !subviewWithDefaultInteractionFound) |
| << "There should be only one default drop interaction in the " |
| "webScrollView."; |
| defaultInteractionFound = YES; |
| subviewWithDefaultInteractionFound = YES; |
| } |
| } |
| } |
| if (customInteractionFound) { |
| // The custom interaction must be added after the default drop interaction |
| // to work properly. |
| [subview removeInteraction:self.customDropInteraction]; |
| [subview addInteraction:self.customDropInteraction]; |
| } else if (defaultInteractionFound) { |
| if (!self.customDropInteraction) { |
| self.customDropInteraction = |
| [[UIDropInteraction alloc] initWithDelegate:self]; |
| } |
| [subview addInteraction:self.customDropInteraction]; |
| } |
| } |
| } |
| |
| #pragma mark - End of loading |
| |
| - (void)didFinishNavigation:(web::NavigationContextImpl*)context { |
| // This can be called at multiple times after the document has loaded. Do |
| // nothing if the document has already loaded. |
| if (self.navigationHandler.navigationState == |
| web::WKNavigationState::FINISHED) |
| return; |
| |
| web::NavigationItem* pendingOrCommittedItem = |
| self.navigationManagerImpl->GetPendingItem(); |
| if (!pendingOrCommittedItem) |
| pendingOrCommittedItem = self.navigationManagerImpl->GetLastCommittedItem(); |
| if (pendingOrCommittedItem) { |
| // This stores the UserAgent that was used to load the item. |
| if (pendingOrCommittedItem->GetUserAgentType() == |
| web::UserAgentType::NONE && |
| web::wk_navigation_util::URLNeedsUserAgentType( |
| pendingOrCommittedItem->GetURL())) { |
| pendingOrCommittedItem->SetUserAgentType( |
| self.webStateImpl->GetUserAgentForNextNavigation( |
| pendingOrCommittedItem->GetURL())); |
| } |
| } |
| |
| // Restore allowsBackForwardNavigationGestures once restoration is complete. |
| if (!self.navigationManagerImpl->IsRestoreSessionInProgress()) { |
| if (_webView.allowsBackForwardNavigationGestures != |
| _allowsBackForwardNavigationGestures) { |
| _webView.allowsBackForwardNavigationGestures = |
| _allowsBackForwardNavigationGestures; |
| } |
| } |
| |
| BOOL success = !context || !context->GetError(); |
| [self loadCompleteWithSuccess:success forContext:context]; |
| |
| // WebKit adds a drop interaction to a subview (WKContentView) of WKWebView's |
| // scrollView when a new WebProcess finishes launching. This can be loading |
| // the first page, navigating cross-domain, or recovering from a WebProcess |
| // crash. Add a custom drop interaction alongside the default drop |
| // interaction. |
| [self addCustomURLDropInteractionIfNeeded]; |
| } |
| |
| - (void)loadCompleteWithSuccess:(BOOL)loadSuccess |
| forContext:(web::NavigationContextImpl*)context { |
| // The webView may have been torn down. Be safe and do nothing if that's |
| // happened. |
| if (self.navigationHandler.navigationState != web::WKNavigationState::STARTED) |
| return; |
| |
| const GURL currentURL([self currentURL]); |
| |
| self.navigationHandler.navigationState = web::WKNavigationState::FINISHED; |
| |
| [self optOutScrollsToTopForSubviews]; |
| |
| // Perform post-load-finished updates. |
| [_requestController didFinishWithURL:currentURL |
| loadSuccess:loadSuccess |
| context:context]; |
| |
| // Execute the pending LoadCompleteActions. |
| for (ProceduralBlock action in _pendingLoadCompleteActions) { |
| action(); |
| } |
| [_pendingLoadCompleteActions removeAllObjects]; |
| } |
| |
| #pragma mark - CRWWebControllerContainerViewDelegate |
| |
| - (CRWWebViewProxyImpl*)contentViewProxyForContainerView: |
| (CRWWebControllerContainerView*)containerView { |
| return _webViewProxy; |
| } |
| |
| - (BOOL)shouldKeepRenderProcessAliveForContainerView: |
| (CRWWebControllerContainerView*)containerView { |
| return self.shouldKeepRenderProcessAlive; |
| } |
| |
| - (void)containerView:(CRWWebControllerContainerView*)containerView |
| storeWebViewInWindow:(UIView*)viewToStash { |
| [web::GetWebClient()->GetWindowedContainer() addSubview:viewToStash]; |
| } |
| |
| #pragma mark - CRWWebViewScrollViewProxyObserver |
| |
| - (void)webViewScrollViewDidZoom: |
| (CRWWebViewScrollViewProxy*)webViewScrollViewProxy { |
| } |
| |
| - (void)webViewScrollViewDidResetContentSize: |
| (CRWWebViewScrollViewProxy*)webViewScrollViewProxy { |
| } |
| |
| // Under WKWebView, JavaScript can execute asynchronously. User can start |
| // scrolling and calls to window.scrollTo executed during scrolling will be |
| // treated as "during user interaction" and can cause app to go fullscreen. |
| // This is a workaround to use this webViewScrollViewIsDragging flag to ignore |
| // window.scrollTo while user is scrolling. See crbug.com/554257 |
| - (void)webViewScrollViewWillBeginDragging: |
| (CRWWebViewScrollViewProxy*)webViewScrollViewProxy { |
| web::java_script_features::GetScrollHelperJavaScriptFeature() |
| ->SetWebViewScrollViewIsDragging(self.webState, true); |
| } |
| |
| - (void)webViewScrollViewDidEndDragging: |
| (CRWWebViewScrollViewProxy*)webViewScrollViewProxy |
| willDecelerate:(BOOL)decelerate { |
| web::java_script_features::GetScrollHelperJavaScriptFeature() |
| ->SetWebViewScrollViewIsDragging(self.webState, false); |
| } |
| |
| #pragma mark - Page State |
| |
| - (void)surfaceSizeChanged { |
| // When rotating, the available zoom scale range may change, zoomScale's |
| // percentage into this range should remain constant. However, there are |
| // two known bugs with respect to adjusting the zoomScale on rotation: |
| // - WKWebView sometimes erroneously resets the scroll view's zoom scale to |
| // an incorrect value ( rdar://20100815 ). |
| // - After zooming occurs in a UIWebView that's displaying a page with a hard- |
| // coded viewport width, the zoom will not be updated upon rotation |
| // ( crbug.com/485055 ). |
| } |
| |
| - (void)prepareToApplyWebViewScrollZoomScale { |
| id webView = self.webView; |
| if (![webView respondsToSelector:@selector(viewForZoomingInScrollView:)]) { |
| return; |
| } |
| |
| UIView* contentView = [webView viewForZoomingInScrollView:self.webScrollView]; |
| |
| if ([webView respondsToSelector:@selector(scrollViewWillBeginZooming: |
| withView:)]) { |
| [webView scrollViewWillBeginZooming:self.webScrollView |
| withView:contentView]; |
| } |
| } |
| |
| - (void)finishApplyingWebViewScrollZoomScale { |
| id webView = self.webView; |
| if ([webView respondsToSelector:@selector |
| (scrollViewDidEndZooming:withView:atScale:)] && |
| [webView respondsToSelector:@selector(viewForZoomingInScrollView:)]) { |
| // This correctly sets the content's frame in the scroll view to |
| // fit the web page and upscales the content so that it isn't |
| // blurry. |
| UIView* contentView = |
| [webView viewForZoomingInScrollView:self.webScrollView]; |
| [webView scrollViewDidEndZooming:self.webScrollView |
| withView:contentView |
| atScale:self.webScrollView.zoomScale]; |
| } |
| } |
| |
| #pragma mark - Fullscreen |
| |
| - (void)optOutScrollsToTopForSubviews { |
| NSMutableArray* stack = |
| [NSMutableArray arrayWithArray:[self.webScrollView subviews]]; |
| while (stack.count) { |
| UIView* current = [stack lastObject]; |
| [stack removeLastObject]; |
| [stack addObjectsFromArray:[current subviews]]; |
| if ([current isKindOfClass:[UIScrollView class]]) |
| static_cast<UIScrollView*>(current).scrollsToTop = NO; |
| } |
| } |
| |
| #if defined(__IPHONE_16_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_16_0 |
| CrFullscreenState CrFullscreenStateFromWKFullscreenState( |
| WKFullscreenState state) API_AVAILABLE(ios(16.0)) { |
| switch (state) { |
| case WKFullscreenStateEnteringFullscreen: |
| return CrFullscreenState::kEnteringFullscreen; |
| case WKFullscreenStateExitingFullscreen: |
| return CrFullscreenState::kExitingFullscreen; |
| case WKFullscreenStateInFullscreen: |
| return CrFullscreenState::kInFullscreen; |
| case WKFullscreenStateNotInFullscreen: |
| return CrFullscreenState::kNotInFullScreen; |
| default: |
| NOTREACHED(); |
| return CrFullscreenState::kNotInFullScreen; |
| } |
| } |
| #endif // defined (__IPHONE_16_0) |
| |
| #pragma mark - Security Helpers |
| |
| - (void)updateSSLStatusForCurrentNavigationItem { |
| if (_isBeingDestroyed) { |
| return; |
| } |
| |
| NavigationManagerImpl* navManager = self.navigationManagerImpl; |
| web::NavigationItem* currentNavItem = navManager->GetLastCommittedItem(); |
| if (!currentNavItem) { |
| return; |
| } |
| |
| if (!_SSLStatusUpdater) { |
| _SSLStatusUpdater = |
| [[CRWSSLStatusUpdater alloc] initWithDataSource:self |
| navigationManager:navManager]; |
| [_SSLStatusUpdater setDelegate:self]; |
| } |
| NSString* host = base::SysUTF8ToNSString(_documentURL.host()); |
| BOOL hasOnlySecureContent = [self.webView hasOnlySecureContent]; |
| base::apple::ScopedCFTypeRef<SecTrustRef> trust; |
| trust.reset([self.webView serverTrust], base::scoped_policy::RETAIN); |
| |
| [_SSLStatusUpdater updateSSLStatusForNavigationItem:currentNavItem |
| withCertHost:host |
| trust:std::move(trust) |
| hasOnlySecureContent:hasOnlySecureContent]; |
| } |
| |
| #pragma mark - WebView Helpers |
| |
| // Creates a container view if it's not yet created. |
| - (void)ensureContainerViewCreated { |
| if (_containerView) |
| return; |
| |
| DCHECK(!_isBeingDestroyed); |
| // Create the top-level parent view, which will contain the content. Note, |
| // this needs to be created with a non-zero size to allow for subviews with |
| // autosize constraints to be correctly processed. |
| _containerView = |
| [[CRWWebControllerContainerView alloc] initWithDelegate:self]; |
| |
| // This will be resized later, but matching the final frame will minimize |
| // re-rendering. |
| UIView* browserContainer = self.webStateImpl->GetWebViewContainer(); |
| if (browserContainer) { |
| _containerView.frame = browserContainer.bounds; |
| } else { |
| // Use the screen size because the application's key window and the |
| // container may still be nil. |
| _containerView.frame = GetAnyKeyWindow() ? GetAnyKeyWindow().bounds |
| : UIScreen.mainScreen.bounds; |
| } |
| |
| DCHECK(!CGRectIsEmpty(_containerView.frame)); |
| |
| [_containerView addGestureRecognizer:[self touchTrackingRecognizer]]; |
| } |
| |
| // Creates a web view if it's not yet created. |
| - (WKWebView*)ensureWebViewCreated { |
| WKWebViewConfiguration* config = |
| [self webViewConfigurationProvider].GetWebViewConfiguration(); |
| return [self ensureWebViewCreatedWithConfiguration:config]; |
| } |
| |
| // Creates a web view with given `config`. No-op if web view is already created. |
| - (WKWebView*)ensureWebViewCreatedWithConfiguration: |
| (WKWebViewConfiguration*)config { |
| if (!self.webView) { |
| // This has to be called to ensure the container view of `self.webView` is |
| // created. Otherwise `self.webView.frame.size` will be CGSizeZero which |
| // fails a DCHECK later. |
| [self ensureContainerViewCreated]; |
| |
| [self setWebView:[self webViewWithConfiguration:config]]; |
| // The following is not called in -setWebView: as the latter used in unit |
| // tests with fake web view, which cannot be added to view hierarchy. |
| CHECK(_webUsageEnabled) << "Tried to create a web view while suspended!"; |
| |
| DCHECK(self.webView); |
| |
| [self.webView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | |
| UIViewAutoresizingFlexibleHeight]; |
| |
| if (web::GetWebClient()->EnableLongPressUIContextMenu()) { |
| self.contextMenuController = |
| [[CRWContextMenuController alloc] initWithWebView:self.webView |
| webState:self.webStateImpl]; |
| } |
| |
| // WKWebViews with invalid or empty frames have exhibited rendering bugs, so |
| // resize the view to match the container view upon creation. |
| [self.webView setFrame:[_containerView bounds]]; |
| } |
| |
| // If web view is not currently displayed and if the visible NavigationItem |
| // should be loaded in this web view, display it immediately. Otherwise, it |
| // will be displayed when the pending load is committed. |
| if (![_containerView webViewContentView]) { |
| [self displayWebView]; |
| } |
| |
| return self.webView; |
| } |
| |
| // Returns a new autoreleased web view created with given configuration. |
| - (WKWebView*)webViewWithConfiguration:(WKWebViewConfiguration*)config { |
| // Do not attach the context menu controller immediately as the JavaScript |
| // delegate must be specified. |
| web::UserAgentType defaultUserAgent = web::UserAgentType::AUTOMATIC; |
| web::NavigationItem* item = self.currentNavItem; |
| web::UserAgentType userAgentType = |
| item ? item->GetUserAgentType() : defaultUserAgent; |
| if (userAgentType == web::UserAgentType::AUTOMATIC) { |
| userAgentType = |
| web::GetWebClient()->GetDefaultUserAgent(self.webStateImpl, GURL()); |
| } |
| |
| return web::BuildWKWebView(CGRectZero, config, |
| self.webStateImpl->GetBrowserState(), |
| userAgentType, self); |
| } |
| |
| // Wraps the web view in a CRWWebViewContentView and adds it to the container |
| // view. |
| - (void)displayWebView { |
| if (!self.webView || [_containerView webViewContentView]) |
| return; |
| |
| #if defined(__IPHONE_16_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_16_0 |
| if (@available(iOS 16.0, *)) { |
| CRWWebViewContentView* webViewContentView = [[CRWWebViewContentView alloc] |
| initWithWebView:self.webView |
| scrollView:self.webScrollView |
| fullscreenState:CrFullscreenStateFromWKFullscreenState( |
| self.webView.fullscreenState)]; |
| [_containerView displayWebViewContentView:webViewContentView]; |
| return; |
| } |
| #endif // defined(__IPHONE_16_0) |
| |
| CRWWebViewContentView* webViewContentView = [[CRWWebViewContentView alloc] |
| initWithWebView:self.webView |
| scrollView:self.webScrollView |
| fullscreenState:CrFullscreenState::kNotInFullScreen]; |
| |
| [_containerView displayWebViewContentView:webViewContentView]; |
| } |
| |
| - (void)removeWebView { |
| if (!self.webView) |
| return; |
| |
| self.webStateImpl->CancelDialogs(); |
| self.navigationManagerImpl->DetachFromWebView(); |
| |
| [self setWebView:nil]; |
| [self.navigationHandler stopLoading]; |
| [_containerView resetContentForShutdown:YES]; |
| |
| // webView:didFailProvisionalNavigation:withError: may never be called after |
| // resetting WKWebView, so it is important to clear pending navigations now. |
| for (__strong id navigation in |
| [self.navigationHandler.navigationStates pendingNavigations]) { |
| [self.navigationHandler.navigationStates removeNavigation:navigation]; |
| } |
| } |
| |
| // Returns the WKWebViewConfigurationProvider associated with the web |
| // controller's BrowserState. |
| - (web::WKWebViewConfigurationProvider&)webViewConfigurationProvider { |
| web::BrowserState* browserState = self.webStateImpl->GetBrowserState(); |
| return web::WKWebViewConfigurationProvider::FromBrowserState(browserState); |
| } |
| |
| #pragma mark - CRWWKUIHandlerDelegate |
| |
| - (WKWebView*)UIHandler:(CRWWKUIHandler*)UIHandler |
| createWebViewWithConfiguration:(WKWebViewConfiguration*)configuration |
| forWebState:(web::WebState*)webState { |
| CRWWebController* webController = |
| web::WebStateImpl::FromWebState(webState)->GetWebController(); |
| DCHECK(!webController || webState->HasOpener()); |
| |
| [webController ensureWebViewCreatedWithConfiguration:configuration]; |
| return webController.webView; |
| } |
| |
| - (BOOL)UIHandler:(CRWWKUIHandler*)UIHandler |
| isUserInitiatedAction:(WKNavigationAction*)action { |
| return [self isUserInitiatedAction:action]; |
| } |
| |
| #pragma mark - WKNavigationDelegate Helpers |
| |
| // Called when a page has actually started loading (i.e., for |
| // a web page the document has actually changed), or after the load request has |
| // been registered for a non-document-changing URL change. Updates internal |
| // state not specific to web pages. |
| - (void)didStartLoading { |
| self.navigationHandler.navigationState = web::WKNavigationState::STARTED; |
| _userInteractionState.SetUserInteractionRegisteredSincePageLoaded(false); |
| } |
| |
| #pragma mark - CRWSSLStatusUpdaterDataSource |
| |
| - (void)SSLStatusUpdater:(CRWSSLStatusUpdater*)SSLStatusUpdater |
| querySSLStatusForTrust:(base::apple::ScopedCFTypeRef<SecTrustRef>)trust |
| host:(NSString*)host |
| completionHandler:(StatusQueryHandler)completionHandler { |
| [_certVerificationController querySSLStatusForTrust:std::move(trust) |
| host:host |
| completionHandler:completionHandler]; |
| } |
| |
| #pragma mark - CRWSSLStatusUpdaterDelegate |
| |
| - (void)SSLStatusUpdater:(CRWSSLStatusUpdater*)SSLStatusUpdater |
| didChangeSSLStatusForNavigationItem:(web::NavigationItem*)navigationItem { |
| web::NavigationItem* visibleItem = |
| self.webStateImpl->GetNavigationManager()->GetVisibleItem(); |
| if (navigationItem == visibleItem) |
| self.webStateImpl->DidChangeVisibleSecurityState(); |
| } |
| |
| #pragma mark - KVO Observation |
| |
| - (void)observeValueForKeyPath:(NSString*)keyPath |
| ofObject:(id)object |
| change:(NSDictionary*)change |
| context:(void*)context { |
| DCHECK(!self.beingDestroyed); |
| NSString* dispatcherSelectorName = self.WKWebViewObservers[keyPath]; |
| DCHECK(dispatcherSelectorName); |
| if (dispatcherSelectorName) { |
| // With ARC memory management, it is not known what a method called |
| // via a selector will return. If a method returns a retained value |
| // (e.g. NS_RETURNS_RETAINED) that returned object will leak as ARC is |
| // unable to property insert the correct release calls for it. |
| // All selectors used here return void and take no parameters so it's safe |
| // to call a function mapping to the method implementation manually. |
| SEL selector = NSSelectorFromString(dispatcherSelectorName); |
| IMP methodImplementation = [self methodForSelector:selector]; |
| if (methodImplementation) { |
| void (*methodCallFunction)(id, SEL) = |
| reinterpret_cast<void (*)(id, SEL)>(methodImplementation); |
| methodCallFunction(self, selector); |
| } |
| } |
| } |
| |
| // Called when WKWebView certificateChain or hasOnlySecureContent property has |
| // changed. |
| - (void)webViewSecurityFeaturesDidChange { |
| if (self.navigationHandler.navigationState == |
| web::WKNavigationState::REQUESTED) { |
| // Do not update SSL Status for pending load. It will be updated in |
| // `webView:didCommitNavigation:` callback. |
| return; |
| } |
| web::NavigationItem* item = |
| self.webStateImpl->GetNavigationManager()->GetLastCommittedItem(); |
| // SSLStatus is manually set in CRWWKNavigationHandler for SSL errors, so |
| // skip calling the update method in these cases. |
| if (item && !net::IsCertStatusError(item->GetSSL().cert_status)) { |
| [self updateSSLStatusForCurrentNavigationItem]; |
| } |
| } |
| |
| // Called when WKWebView title has been changed. |
| - (void)webViewTitleDidChange { |
| // WKWebView's title becomes empty when the web process dies; ignore that |
| // update. |
| if (self.navigationHandler.webProcessCrashed) { |
| DCHECK_EQ(self.webView.title.length, 0U); |
| return; |
| } |
| |
| web::WKNavigationState lastNavigationState = |
| [self.navigationHandler.navigationStates lastAddedNavigationState]; |
| bool hasPendingNavigation = |
| lastNavigationState == web::WKNavigationState::REQUESTED || |
| lastNavigationState == web::WKNavigationState::STARTED || |
| lastNavigationState == web::WKNavigationState::REDIRECTED; |
| |
| if (!hasPendingNavigation) { |
| // Do not update the title if there is a navigation in progress because |
| // there is no way to tell if KVO change fired for new or previous page. |
| [self.navigationHandler |
| setLastCommittedNavigationItemTitle:self.webView.title]; |
| } |
| } |
| |
| // Called when WKWebView cameraCaptureState property has changed. |
| - (void)webViewCameraCaptureStateDidChange { |
| self.webStateImpl->OnStateChangedForPermission(web::PermissionCamera); |
| } |
| |
| // Called when WKWebView microphoneCaptureState property has changed. |
| - (void)webViewMicrophoneCaptureStateDidChange { |
| self.webStateImpl->OnStateChangedForPermission(web::PermissionMicrophone); |
| } |
| |
| - (void)fullscreenStateDidChange { |
| #if defined(__IPHONE_16_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_16_0 |
| if (@available(iOS 16.0, *)) { |
| CrFullscreenState fullScreenState = |
| CrFullscreenStateFromWKFullscreenState(self.webView.fullscreenState); |
| [_containerView updateWebViewContentViewFullscreenState:fullScreenState]; |
| // Update state for `fullscreenModeOn` so that we can expose the current |
| // status of fullscreen mode through different interfaces. |
| _webPageInFullscreenMode = |
| fullScreenState == CrFullscreenState::kInFullscreen; |
| base::UmaHistogramEnumeration(kFullScreenStateHistogram, fullScreenState); |
| } |
| #endif // defined (__IPHONE_16_0) |
| } |
| |
| #pragma mark - CRWWebViewHandlerDelegate |
| |
| - (web::WebStateImpl*)webStateImplForWebViewHandler: |
| (CRWWebViewHandler*)handler { |
| return self.webStateImpl; |
| } |
| |
| - (const GURL&)documentURLForWebViewHandler:(CRWWebViewHandler*)handler { |
| return _documentURL; |
| } |
| |
| - (web::UserInteractionState*)userInteractionStateForWebViewHandler: |
| (CRWWebViewHandler*)handler { |
| return &_userInteractionState; |
| } |
| |
| - (void)webViewHandlerUpdateSSLStatusForCurrentNavigationItem: |
| (CRWWebViewHandler*)handler { |
| [self updateSSLStatusForCurrentNavigationItem]; |
| } |
| |
| - (void)webViewHandler:(CRWWebViewHandler*)handler |
| didFinishNavigation:(web::NavigationContextImpl*)context { |
| [self didFinishNavigation:context]; |
| } |
| |
| - (void)ensureWebViewCreatedForWebViewHandler:(CRWWebViewHandler*)handler { |
| [self ensureWebViewCreated]; |
| } |
| |
| - (WKWebView*)webViewForWebViewHandler:(CRWWebViewHandler*)handler { |
| return self.webView; |
| } |
| |
| #pragma mark - CRWWebViewNavigationObserverDelegate |
| |
| - (CRWWKNavigationHandler*)navigationHandlerForNavigationObserver: |
| (CRWWebViewNavigationObserver*)navigationObserver { |
| return self.navigationHandler; |
| } |
| |
| - (void)navigationObserver:(CRWWebViewNavigationObserver*)navigationObserver |
| didChangeDocumentURL:(const GURL&)documentURL |
| forContext:(web::NavigationContextImpl*)context { |
| [self setDocumentURL:documentURL context:context]; |
| } |
| |
| - (void)navigationObserver:(CRWWebViewNavigationObserver*)navigationObserver |
| didChangePageWithContext:(web::NavigationContextImpl*)context { |
| [self.navigationHandler webPageChangedWithContext:context |
| webView:self.webView]; |
| } |
| |
| - (void)navigationObserver:(CRWWebViewNavigationObserver*)navigationObserver |
| didLoadNewURL:(const GURL&)webViewURL |
| forSameDocumentNavigation:(BOOL)isSameDocumentNavigation { |
| std::unique_ptr<web::NavigationContextImpl> newContext = |
| [_requestController registerLoadRequestForURL:webViewURL |
| sameDocumentNavigation:isSameDocumentNavigation |
| hasUserGesture:NO |
| rendererInitiated:YES]; |
| [self.navigationHandler webPageChangedWithContext:newContext.get() |
| webView:self.webView]; |
| newContext->SetHasCommitted(!isSameDocumentNavigation); |
| self.webStateImpl->OnNavigationFinished(newContext.get()); |
| // TODO(crbug.com/41359661): It is OK, but very brittle, to call |
| // `didFinishNavigation:` here because the gating condition is mutually |
| // exclusive with the condition below. Refactor this method after |
| // deprecating self.navigationHandler.pendingNavigationInfo. |
| if (newContext->GetWKNavigationType() == WKNavigationTypeBackForward) { |
| [self didFinishNavigation:newContext.get()]; |
| } |
| } |
| |
| - (void)navigationObserver:(CRWWebViewNavigationObserver*)navigationObserver |
| URLDidChangeWithoutDocumentChange:(const GURL&)newURL { |
| DCHECK(newURL == net::GURLWithNSURL(self.webView.URL)); |
| |
| if (base::FeatureList::IsEnabled( |
| web::features::kCrashOnUnexpectedURLChange)) { |
| if (_documentURL.DeprecatedGetOriginAsURL() != |
| newURL.DeprecatedGetOriginAsURL()) { |
| if (!_documentURL.host().empty() && |
| (base::Contains(newURL.username(), _documentURL.host()) || |
| base::Contains(newURL.password(), _documentURL.host()))) { |
| CHECK(false); |
| } |
| } |
| } |
| |
| DCHECK(_documentURL != newURL); |
| |
| // If called during window.history.pushState or window.history.replaceState |
| // JavaScript evaluation, only update the document URL. This callback does not |
| // have any information about the state object and cannot create (or edit) the |
| // navigation entry for this page change. Web controller will sync with |
| // history changes when a window.history.didPushState or |
| // window.history.didReplaceState message is received, which should happen in |
| // the next runloop. |
| // |
| // Otherwise, simulate the whole delegate flow for a load (since the |
| // superclass currently doesn't have a clean separation between URL changes |
| // and document changes). Note that the order of these calls is important: |
| // registering a load request logically comes before updating the document |
| // URL, but also must come first since it uses state that is reset on URL |
| // changes. |
| |
| // `newNavigationContext` only exists if this method has to create a new |
| // context object. |
| std::unique_ptr<web::NavigationContextImpl> newNavigationContext; |
| if (!self.jsNavigationHandler.changingHistoryState) { |
| if ([self.navigationHandler |
| contextForPendingMainFrameNavigationWithURL:newURL]) { |
| // NavigationManager::LoadURLWithParams() was called with URL that has |
| // different fragment comparing to the previous URL. |
| } else { |
| // This could be: |
| // 1.) Renderer-initiated fragment change |
| // 2.) Assigning same-origin URL to window.location |
| // 3.) Incorrectly handled window.location.replace (crbug.com/307072) |
| // 4.) Back-forward same document navigation |
| newNavigationContext = |
| [_requestController registerLoadRequestForURL:newURL |
| sameDocumentNavigation:YES |
| hasUserGesture:NO |
| rendererInitiated:YES]; |
| } |
| } |
| |
| [self setDocumentURL:newURL context:newNavigationContext.get()]; |
| |
| if (!self.jsNavigationHandler.changingHistoryState) { |
| // Pass either newly created context (if it exists) or context that already |
| // existed before. |
| web::NavigationContextImpl* navigationContext = newNavigationContext.get(); |
| if (!navigationContext) { |
| navigationContext = [self.navigationHandler |
| contextForPendingMainFrameNavigationWithURL:newURL]; |
| } |
| navigationContext->SetIsSameDocument(true); |
| self.webStateImpl->OnNavigationStarted(navigationContext); |
| [self didStartLoading]; |
| self.navigationManagerImpl->CommitPendingItem( |
| navigationContext->ReleaseItem()); |
| navigationContext->SetHasCommitted(true); |
| self.webStateImpl->OnNavigationFinished(navigationContext); |
| |
| [self updateSSLStatusForCurrentNavigationItem]; |
| [self didFinishNavigation:navigationContext]; |
| } |
| } |
| |
| #pragma mark - CRWWKNavigationHandlerDelegate |
| |
| - (CRWCertVerificationController*) |
| certVerificationControllerForNavigationHandler: |
| (CRWWKNavigationHandler*)navigationHandler { |
| return _certVerificationController; |
| } |
| |
| - (void)navigationHandler:(CRWWKNavigationHandler*)navigationHandler |
| createWebUIForURL:(const GURL&)URL { |
| [_requestController createWebUIForURL:URL]; |
| } |
| |
| - (void)navigationHandler:(CRWWKNavigationHandler*)navigationHandler |
| setDocumentURL:(const GURL&)newURL |
| context:(web::NavigationContextImpl*)context { |
| [self setDocumentURL:newURL context:context]; |
| } |
| |
| - (std::unique_ptr<web::NavigationContextImpl>) |
| navigationHandler:(CRWWKNavigationHandler*)navigationHandler |
| registerLoadRequestForURL:(const GURL&)URL |
| sameDocumentNavigation:(BOOL)sameDocumentNavigation |
| hasUserGesture:(BOOL)hasUserGesture |
| rendererInitiated:(BOOL)renderedInitiated { |
| return [_requestController registerLoadRequestForURL:URL |
| sameDocumentNavigation:sameDocumentNavigation |
| hasUserGesture:hasUserGesture |
| rendererInitiated:renderedInitiated]; |
| } |
| |
| - (void)navigationHandlerDisplayWebView: |
| (CRWWKNavigationHandler*)navigationHandler { |
| [self displayWebView]; |
| } |
| |
| - (void)navigationHandlerDidStartLoading: |
| (CRWWKNavigationHandler*)navigationHandler { |
| [self didStartLoading]; |
| } |
| |
| - (void)navigationHandlerWebProcessDidCrash: |
| (CRWWKNavigationHandler*)navigationHandler { |
| self.webStateImpl->CancelDialogs(); |
| self.webStateImpl->OnRenderProcessGone(); |
| } |
| |
| - (void)navigationHandler:(CRWWKNavigationHandler*)navigationHandler |
| loadCurrentURLWithRendererInitiatedNavigation:(BOOL)rendererInitiated { |
| [self loadCurrentURLWithRendererInitiatedNavigation:rendererInitiated]; |
| } |
| |
| - (void)navigationHandler:(CRWWKNavigationHandler*)navigationHandler |
| didCompleteLoadWithSuccess:(BOOL)loadSuccess |
| forContext:(web::NavigationContextImpl*)context { |
| [self loadCompleteWithSuccess:loadSuccess forContext:context]; |
| } |
| |
| - (void)resumeDownloadWithData:(NSData*)data |
| completionHandler:(void (^)(WKDownload*))completionHandler { |
| // Reports some failure to higher level code if `webView` doesn't exist |
| if (!_webView) { |
| completionHandler(nil); |
| return; |
| } |
| [_webView resumeDownloadFromResumeData:data |
| completionHandler:completionHandler]; |
| } |
| |
| #pragma mark - CRWWebRequestControllerDelegate |
| |
| - (void)webRequestControllerStopLoading: |
| (CRWWebRequestController*)requestController { |
| [self stopLoading]; |
| } |
| |
| - (void)webRequestControllerDidStartLoading: |
| (CRWWebRequestController*)requestController { |
| [self didStartLoading]; |
| } |
| |
| - (CRWWKNavigationHandler*)webRequestControllerNavigationHandler: |
| (CRWWebRequestController*)requestController { |
| return self.navigationHandler; |
| } |
| |
| #pragma mark - CRWInputViewProvider |
| |
| - (id<CRWResponderInputView>)responderInputView { |
| web::WebState* webState = self.webStateImpl; |
| if (webState && webState->GetDelegate()) { |
| return webState->GetDelegate()->GetResponderInputView(webState); |
| } |
| return nil; |
| } |
| |
| #pragma mark - UIDropInteractionDelegate |
| |
| - (BOOL)dropInteraction:(UIDropInteraction*)interaction |
| canHandleSession:(id<UIDropSession>)session { |
| return session.items.count == 1U && |
| [session canLoadObjectsOfClass:[NSURL class]]; |
| } |
| |
| - (UIDropProposal*)dropInteraction:(UIDropInteraction*)interaction |
| sessionDidUpdate:(id<UIDropSession>)session { |
| return [[UIDropProposal alloc] initWithDropOperation:UIDropOperationCopy]; |
| } |
| |
| - (void)dropInteraction:(UIDropInteraction*)interaction |
| performDrop:(id<UIDropSession>)session { |
| DCHECK_EQ(1U, session.items.count); |
| if ([session canLoadObjectsOfClass:[NSURL class]]) { |
| __weak CRWWebController* weakSelf = self; |
| [session loadObjectsOfClass:[NSURL class] |
| completion:^(NSArray<NSURL*>* objects) { |
| [weakSelf loadUrlObjectsCompletion:objects]; |
| }]; |
| } |
| } |
| |
| - (void)loadUrlObjectsCompletion:(NSArray<NSURL*>*)objects { |
| GURL URL = net::GURLWithNSURL([objects firstObject]); |
| if (!_isBeingDestroyed && URL.is_valid() && URL.SchemeIsHTTPOrHTTPS()) { |
| web::NavigationManager::WebLoadParams params(URL); |
| params.transition_type = ui::PAGE_TRANSITION_TYPED; |
| self.webStateImpl->GetNavigationManager()->LoadURLWithParams(params); |
| } |
| } |
| |
| #pragma mark - Testing-Only Methods |
| |
| - (void)injectWebViewContentView:(CRWWebViewContentView*)webViewContentView { |
| _currentURLLoadWasTrigerred = NO; |
| [self removeWebView]; |
| |
| [_containerView displayWebViewContentView:webViewContentView]; |
| [self setWebView:static_cast<WKWebView*>(webViewContentView.webView)]; |
| } |
| |
| - (void)resetInjectedWebViewContentView { |
| _currentURLLoadWasTrigerred = NO; |
| [self setWebView:nil]; |
| [_containerView removeFromSuperview]; |
| _containerView = nil; |
| } |
| |
| - (web::WKNavigationState)navigationState { |
| return self.navigationHandler.navigationState; |
| } |
| |
| @end |