1// Copyright 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#import "ios/web/web_state/ui/crw_web_controller.h" 6 7#import <WebKit/WebKit.h> 8 9#include "base/bind.h" 10#import "base/ios/block_types.h" 11#include "base/ios/ios_util.h" 12#include "base/json/string_escape.h" 13#include "base/mac/foundation_util.h" 14#include "base/metrics/histogram_macros.h" 15#include "base/metrics/user_metrics.h" 16#include "base/metrics/user_metrics_action.h" 17#include "base/strings/sys_string_conversions.h" 18#import "ios/web/browsing_data/browsing_data_remover.h" 19#import "ios/web/common/crw_web_view_content_view.h" 20#include "ios/web/common/features.h" 21#include "ios/web/common/url_util.h" 22#import "ios/web/favicon/favicon_manager.h" 23#import "ios/web/find_in_page/find_in_page_manager_impl.h" 24#include "ios/web/history_state_util.h" 25#import "ios/web/js_messaging/crw_js_injector.h" 26#import "ios/web/js_messaging/crw_wk_script_message_router.h" 27#import "ios/web/js_messaging/web_frames_manager_impl.h" 28#import "ios/web/js_messaging/web_view_js_utils.h" 29#import "ios/web/navigation/crw_js_navigation_handler.h" 30#import "ios/web/navigation/crw_navigation_item_holder.h" 31#import "ios/web/navigation/crw_web_view_navigation_observer.h" 32#import "ios/web/navigation/crw_web_view_navigation_observer_delegate.h" 33#import "ios/web/navigation/crw_wk_navigation_handler.h" 34#import "ios/web/navigation/crw_wk_navigation_states.h" 35#import "ios/web/navigation/error_page_helper.h" 36#import "ios/web/navigation/navigation_context_impl.h" 37#import "ios/web/navigation/wk_back_forward_list_item_holder.h" 38#import "ios/web/navigation/wk_navigation_util.h" 39#include "ios/web/public/js_messaging/web_frame_util.h" 40#import "ios/web/public/ui/crw_web_view_scroll_view_proxy.h" 41#import "ios/web/public/ui/page_display_state.h" 42#import "ios/web/public/web_client.h" 43#import "ios/web/security/crw_cert_verification_controller.h" 44#import "ios/web/security/crw_ssl_status_updater.h" 45#import "ios/web/web_state/page_viewport_state.h" 46#import "ios/web/web_state/ui/cookie_blocking_error_logger.h" 47#import "ios/web/web_state/ui/crw_context_menu_controller.h" 48#import "ios/web/web_state/ui/crw_context_menu_delegate.h" 49#import "ios/web/web_state/ui/crw_swipe_recognizer_provider.h" 50#import "ios/web/web_state/ui/crw_web_controller_container_view.h" 51#import "ios/web/web_state/ui/crw_web_request_controller.h" 52#import "ios/web/web_state/ui/crw_web_view_proxy_impl.h" 53#import "ios/web/web_state/ui/crw_wk_ui_handler.h" 54#import "ios/web/web_state/ui/crw_wk_ui_handler_delegate.h" 55#import "ios/web/web_state/ui/js_window_error_manager.h" 56#import "ios/web/web_state/ui/wk_web_view_configuration_provider.h" 57#import "ios/web/web_state/user_interaction_state.h" 58#import "ios/web/web_state/web_state_impl.h" 59#import "ios/web/web_state/web_view_internal_creation_util.h" 60#import "ios/web/web_view/content_type_util.h" 61#import "ios/web/web_view/wk_web_view_util.h" 62#import "net/base/mac/url_conversions.h" 63#include "services/metrics/public/cpp/ukm_builders.h" 64#include "url/gurl.h" 65 66#if !defined(__has_feature) || !__has_feature(objc_arc) 67#error "This file requires ARC support." 68#endif 69 70using web::NavigationManager; 71using web::NavigationManagerImpl; 72using web::WebState; 73using web::WebStateImpl; 74 75using web::wk_navigation_util::IsPlaceholderUrl; 76using web::wk_navigation_util::ExtractUrlFromPlaceholderUrl; 77using web::wk_navigation_util::IsRestoreSessionUrl; 78using web::wk_navigation_util::IsWKInternalUrl; 79 80namespace { 81 82// Keys for JavaScript command handlers context. 83NSString* const kUserIsInteractingKey = @"userIsInteracting"; 84NSString* const kOriginURLKey = @"originURL"; 85NSString* const kIsMainFrame = @"isMainFrame"; 86 87// URL scheme for messages sent from javascript for asynchronous processing. 88NSString* const kScriptMessageName = @"crwebinvoke"; 89 90// URL scheme for session restore. 91NSString* const kSessionRestoreScriptMessageName = @"session_restore"; 92 93} // namespace 94 95@interface CRWWebController () <CRWWKNavigationHandlerDelegate, 96 CRWContextMenuDelegate, 97 CRWJSInjectorDelegate, 98 CRWSSLStatusUpdaterDataSource, 99 CRWSSLStatusUpdaterDelegate, 100 CRWWebControllerContainerViewDelegate, 101 CRWWebViewNavigationObserverDelegate, 102 CRWWebRequestControllerDelegate, 103 CRWJSNavigationHandlerDelegate, 104 CRWWebViewScrollViewProxyObserver, 105 CRWWKNavigationHandlerDelegate, 106 CRWWKUIHandlerDelegate, 107 UIDropInteractionDelegate, 108 WKNavigationDelegate> { 109 // The view used to display content. Must outlive |_webViewProxy|. The 110 // container view should be accessed through this property rather than 111 // |self.view| from within this class, as |self.view| triggers creation while 112 // |self.containerView| will return nil if the view hasn't been instantiated. 113 CRWWebControllerContainerView* _containerView; 114 // YES if the current URL load was triggered in Web Controller. NO by default 115 // and after web usage was disabled. Used by |-loadCurrentURLIfNecessary| to 116 // prevent extra loads. 117 BOOL _currentURLLoadWasTrigerred; 118 BOOL _isBeingDestroyed; // YES if in the process of closing. 119 // The actual URL of the document object (i.e., the last committed URL). 120 // TODO(crbug.com/549616): Remove this in favor of just updating the 121 // navigation manager and treating that as authoritative. 122 GURL _documentURL; 123 // The web::PageDisplayState recorded when the page starts loading. 124 web::PageDisplayState _displayStateOnStartLoading; 125 // Whether or not the page has zoomed since the current navigation has been 126 // committed, either by user interaction or via |-restoreStateFromHistory|. 127 BOOL _pageHasZoomed; 128 // Whether a PageDisplayState is currently being applied. 129 BOOL _applyingPageState; 130 // Actions to execute once the page load is complete. 131 NSMutableArray* _pendingLoadCompleteActions; 132 // Flag to say if browsing is enabled. 133 BOOL _webUsageEnabled; 134 // Default URL (about:blank). 135 GURL _defaultURL; 136 137 // Updates SSLStatus for current navigation item. 138 CRWSSLStatusUpdater* _SSLStatusUpdater; 139 140 // Controller used for certs verification to help with blocking requests with 141 // bad SSL cert, presenting SSL interstitials and determining SSL status for 142 // Navigation Items. 143 CRWCertVerificationController* _certVerificationController; 144 145 // State of user interaction with web content. 146 web::UserInteractionState _userInteractionState; 147 148 // Manager for favicon JavaScript messages. 149 std::unique_ptr<web::FaviconManager> _faviconManager; 150 151 // Manager for window.error message. 152 std::unique_ptr<web::JsWindowErrorManager> _jsWindowErrorManager; 153 154 // Logger for cookie;.error message. 155 std::unique_ptr<web::CookieBlockingErrorLogger> _cookieBlockingErrorLogger; 156} 157 158// The WKNavigationDelegate handler class. 159@property(nonatomic, readonly, strong) 160 CRWWKNavigationHandler* navigationHandler; 161@property(nonatomic, readonly, strong) 162 CRWJSNavigationHandler* JSNavigationHandler; 163// The WKUIDelegate handler class. 164@property(nonatomic, readonly, strong) CRWWKUIHandler* UIHandler; 165 166// YES if in the process of closing. 167@property(nonatomic, readwrite, assign) BOOL beingDestroyed; 168 169// If |contentView_| contains a web view, this is the web view it contains. 170// If not, it's nil. When setting the property, it performs basic setup. 171@property(weak, nonatomic) WKWebView* webView; 172// The scroll view of |webView|. 173@property(weak, nonatomic, readonly) UIScrollView* webScrollView; 174// The current page state of the web view. Writing to this property 175// asynchronously applies the passed value to the current web view. 176@property(nonatomic, readwrite) web::PageDisplayState pageDisplayState; 177 178@property(nonatomic, strong, readonly) 179 CRWWebViewNavigationObserver* webViewNavigationObserver; 180 181// Dictionary where keys are the names of WKWebView properties and values are 182// selector names which should be called when a corresponding property has 183// changed. e.g. @{ @"URL" : @"webViewURLDidChange" } means that 184// -[self webViewURLDidChange] must be called every time when WKWebView.URL is 185// changed. 186@property(weak, nonatomic, readonly) NSDictionary* WKWebViewObservers; 187 188// Url request controller. 189@property(nonatomic, strong, readonly) 190 CRWWebRequestController* requestController; 191 192// The web view's view of the current URL. During page transitions 193// this may not be the same as the session history's view of the current URL. 194// This method can change the state of the CRWWebController, as it will display 195// an error if the returned URL is not reliable from a security point of view. 196// Note that this method is expensive, so it should always be cached locally if 197// it's needed multiple times in a method. 198@property(nonatomic, readonly) GURL currentURL; 199 200@property(nonatomic, readonly) web::WebState* webState; 201// WebStateImpl instance associated with this CRWWebController, web controller 202// does not own this pointer. 203@property(nonatomic, readonly) web::WebStateImpl* webStateImpl; 204 205// Returns the x, y offset the content has been scrolled. 206@property(nonatomic, readonly) CGPoint scrollPosition; 207 208// The touch tracking recognizer allowing us to decide if a navigation has user 209// gesture. Lazily created. 210@property(nonatomic, strong, readonly) 211 CRWTouchTrackingRecognizer* touchTrackingRecognizer; 212 213// A custom drop interaction that is added alongside the web view's default drop 214// interaction. 215@property(nonatomic, strong) UIDropInteraction* customDropInteraction; 216 217// Session Information 218// ------------------- 219// The associated NavigationManagerImpl. 220@property(nonatomic, readonly) NavigationManagerImpl* navigationManagerImpl; 221// TODO(crbug.com/692871): Remove these functions and replace with more 222// appropriate NavigationItem getters. 223// Returns the navigation item for the current page. 224@property(nonatomic, readonly) web::NavigationItemImpl* currentNavItem; 225 226// Returns the current URL of the web view, and sets |trustLevel| accordingly 227// based on the confidence in the verification. 228- (GURL)webURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel; 229 230// Called following navigation completion to generate final navigation lifecycle 231// events. Navigation is considered complete when the document has finished 232// loading, or when other page load mechanics are completed on a 233// non-document-changing URL change. 234- (void)didFinishNavigation:(web::NavigationContextImpl*)context; 235// Update the appropriate parts of the model and broadcast to the embedder. This 236// may be called multiple times and thus must be idempotent. 237- (void)loadCompleteWithSuccess:(BOOL)loadSuccess 238 forContext:(web::NavigationContextImpl*)context; 239// Called when web controller receives a new message from the web page. 240- (void)didReceiveScriptMessage:(WKScriptMessage*)message; 241// Attempts to handle a script message. Returns YES on success, NO otherwise. 242- (BOOL)respondToWKScriptMessage:(WKScriptMessage*)scriptMessage; 243 244// Restores the state for this page from session history. 245- (void)restoreStateFromHistory; 246// Extracts the current page's viewport tag information and calls |completion|. 247// If the page has changed before the viewport tag is successfully extracted, 248// |completion| is called with nullptr. 249typedef void (^ViewportStateCompletion)(const web::PageViewportState*); 250- (void)extractViewportTagWithCompletion:(ViewportStateCompletion)completion; 251// Called by NSNotificationCenter upon orientation changes. 252- (void)orientationDidChange; 253// Queries the web view for the user-scalable meta tag and calls 254// |-applyPageDisplayState:userScalable:| with the result. 255- (void)applyPageDisplayState:(const web::PageDisplayState&)displayState; 256// Restores state of the web view's scroll view from |scrollState|. 257// |isUserScalable| represents the value of user-scalable meta tag. 258- (void)applyPageDisplayState:(const web::PageDisplayState&)displayState 259 userScalable:(BOOL)isUserScalable; 260// Calls the zoom-preparation UIScrollViewDelegate callbacks on the web view. 261// This is called before |-applyWebViewScrollZoomScaleFromScrollState:|. 262- (void)prepareToApplyWebViewScrollZoomScale; 263// Calls the zoom-completion UIScrollViewDelegate callbacks on the web view. 264// This is called after |-applyWebViewScrollZoomScaleFromScrollState:|. 265- (void)finishApplyingWebViewScrollZoomScale; 266// Sets zoom scale value for webview scroll view from |zoomState|. 267- (void)applyWebViewScrollZoomScaleFromZoomState: 268 (const web::PageZoomState&)zoomState; 269// Sets scroll offset value for webview scroll view from |scrollState|. 270- (void)applyWebViewScrollOffsetFromScrollState: 271 (const web::PageScrollState&)scrollState; 272// Finds all the scrollviews in the view hierarchy and makes sure they do not 273// interfere with scroll to top when tapping the statusbar. 274- (void)optOutScrollsToTopForSubviews; 275// Updates SSL status for the current navigation item based on the information 276// provided by web view. 277- (void)updateSSLStatusForCurrentNavigationItem; 278 279@end 280 281@implementation CRWWebController 282 283// Synthesize as it is readonly. 284@synthesize touchTrackingRecognizer = _touchTrackingRecognizer; 285 286#pragma mark - Object lifecycle 287 288- (instancetype)initWithWebState:(WebStateImpl*)webState { 289 self = [super init]; 290 if (self) { 291 _webStateImpl = webState; 292 _webUsageEnabled = YES; 293 294 _allowsBackForwardNavigationGestures = YES; 295 296 DCHECK(_webStateImpl); 297 // Content area is lazily instantiated. 298 _defaultURL = GURL(url::kAboutBlankURL); 299 _jsInjector = [[CRWJSInjector alloc] initWithDelegate:self]; 300 _requestController = [[CRWWebRequestController alloc] init]; 301 _requestController.delegate = self; 302 _webViewProxy = [[CRWWebViewProxyImpl alloc] initWithWebController:self]; 303 [[_webViewProxy scrollViewProxy] addObserver:self]; 304 _pendingLoadCompleteActions = [[NSMutableArray alloc] init]; 305 web::BrowserState* browserState = _webStateImpl->GetBrowserState(); 306 _certVerificationController = [[CRWCertVerificationController alloc] 307 initWithBrowserState:browserState]; 308 web::FindInPageManagerImpl::CreateForWebState(_webStateImpl); 309 _faviconManager = std::make_unique<web::FaviconManager>(_webStateImpl); 310 _jsWindowErrorManager = 311 std::make_unique<web::JsWindowErrorManager>(_webStateImpl); 312 _cookieBlockingErrorLogger = 313 std::make_unique<web::CookieBlockingErrorLogger>(_webStateImpl); 314 [[NSNotificationCenter defaultCenter] 315 addObserver:self 316 selector:@selector(orientationDidChange) 317 name:UIApplicationDidChangeStatusBarOrientationNotification 318 object:nil]; 319 320 _navigationHandler = [[CRWWKNavigationHandler alloc] initWithDelegate:self]; 321 322 _JSNavigationHandler = 323 [[CRWJSNavigationHandler alloc] initWithDelegate:self]; 324 325 _UIHandler = [[CRWWKUIHandler alloc] init]; 326 _UIHandler.delegate = self; 327 328 _webViewNavigationObserver = [[CRWWebViewNavigationObserver alloc] init]; 329 _webViewNavigationObserver.delegate = self; 330 } 331 return self; 332} 333 334- (void)dealloc { 335 DCHECK([NSThread isMainThread]); 336 DCHECK(_isBeingDestroyed); // 'close' must have been called already. 337 DCHECK(!_webView); 338} 339 340#pragma mark - Public property accessors 341 342- (void)setWebUsageEnabled:(BOOL)enabled { 343 if (_webUsageEnabled == enabled) 344 return; 345 // WKWebView autoreleases its WKProcessPool on removal from superview. 346 // Deferring WKProcessPool deallocation may lead to issues with cookie 347 // clearing and and Browsing Data Partitioning implementation. 348 @autoreleasepool { 349 if (!enabled) { 350 [self removeWebView]; 351 } 352 } 353 354 _webUsageEnabled = enabled; 355 356 // WKWebView autoreleases its WKProcessPool on removal from superview. 357 // Deferring WKProcessPool deallocation may lead to issues with cookie 358 // clearing and and Browsing Data Partitioning implementation. 359 @autoreleasepool { 360 if (enabled) { 361 // Don't create the web view; let it be lazy created as needed. 362 363 // The gesture is removed when the web usage is disabled. Add it back when 364 // it is enabled again. 365 [_containerView addGestureRecognizer:[self touchTrackingRecognizer]]; 366 } else { 367 self.webStateImpl->ClearTransientContent(); 368 if (_touchTrackingRecognizer) { 369 [_containerView removeGestureRecognizer:_touchTrackingRecognizer]; 370 _touchTrackingRecognizer.touchTrackingDelegate = nil; 371 _touchTrackingRecognizer = nil; 372 } 373 _currentURLLoadWasTrigerred = NO; 374 } 375 } 376} 377 378- (UIView*)view { 379 [self ensureContainerViewCreated]; 380 DCHECK(_containerView); 381 return _containerView; 382} 383 384- (id<CRWWebViewNavigationProxy>)webViewNavigationProxy { 385 return static_cast<id<CRWWebViewNavigationProxy>>(self.webView); 386} 387 388- (double)loadingProgress { 389 return [self.webView estimatedProgress]; 390} 391 392- (BOOL)isWebProcessCrashed { 393 return self.navigationHandler.webProcessCrashed; 394} 395 396- (void)setAllowsBackForwardNavigationGestures: 397 (BOOL)allowsBackForwardNavigationGestures { 398 // Store it to an instance variable as well as 399 // self.webView.allowsBackForwardNavigationGestures because self.webView may 400 // be nil. When self.webView is nil, it will be set later in -setWebView:. 401 _allowsBackForwardNavigationGestures = allowsBackForwardNavigationGestures; 402 self.webView.allowsBackForwardNavigationGestures = 403 allowsBackForwardNavigationGestures; 404} 405 406#pragma mark - Private properties accessors 407 408- (void)setWebView:(WKWebView*)webView { 409 DCHECK_NE(_webView, webView); 410 411 // Unwind the old web view. 412 413 // Remove KVO and WK*Delegate before calling methods on WKWebView so that 414 // handlers won't receive unnecessary callbacks. 415 [_webView setNavigationDelegate:nil]; 416 [_webView setUIDelegate:nil]; 417 for (NSString* keyPath in self.WKWebViewObservers) { 418 [_webView removeObserver:self forKeyPath:keyPath]; 419 } 420 self.webViewNavigationObserver.webView = nil; 421 422 CRWWKScriptMessageRouter* messageRouter = 423 [self webViewConfigurationProvider].GetScriptMessageRouter(); 424 self.webStateImpl->GetWebFramesManagerImpl().OnWebViewUpdated( 425 _webView, webView, messageRouter); 426 427 if (_webView) { 428 [_webView stopLoading]; 429 [_webView removeFromSuperview]; 430 } 431 432 // Set up the new web view. 433 _webView = webView; 434 435 if (_webView) { 436 [_webView setNavigationDelegate:self.navigationHandler]; 437 [_webView setUIDelegate:self.UIHandler]; 438 for (NSString* keyPath in self.WKWebViewObservers) { 439 [_webView addObserver:self forKeyPath:keyPath options:0 context:nullptr]; 440 } 441 442 __weak CRWWebController* weakSelf = self; 443 [messageRouter 444 setScriptMessageHandler:^(WKScriptMessage* message) { 445 [weakSelf didReceiveScriptMessage:message]; 446 } 447 name:kScriptMessageName 448 webView:_webView]; 449 450 if (self.webStateImpl->GetNavigationManager() 451 ->IsRestoreSessionInProgress()) { 452 // The session restoration script needs to use IPC to notify the app of 453 // the last step of the session restoration. See the restore_session.html 454 // file or crbug.com/1127521. 455 [messageRouter 456 setScriptMessageHandler:^(WKScriptMessage* message) { 457 [weakSelf didReceiveSessionRestoreScriptMessage:message]; 458 } 459 name:kSessionRestoreScriptMessageName 460 webView:_webView]; 461 } 462 463 _webView.allowsBackForwardNavigationGestures = 464 _allowsBackForwardNavigationGestures; 465 } 466 [_jsInjector setWebView:_webView]; 467 self.webViewNavigationObserver.webView = _webView; 468 469 [self setDocumentURL:_defaultURL context:nullptr]; 470} 471 472- (UIScrollView*)webScrollView { 473 return self.webView.scrollView; 474} 475 476- (web::PageDisplayState)pageDisplayState { 477 web::PageDisplayState displayState; 478 if (self.webView) { 479 displayState.set_scroll_state(web::PageScrollState( 480 self.scrollPosition, self.webScrollView.contentInset)); 481 UIScrollView* scrollView = self.webScrollView; 482 displayState.zoom_state().set_minimum_zoom_scale( 483 scrollView.minimumZoomScale); 484 displayState.zoom_state().set_maximum_zoom_scale( 485 scrollView.maximumZoomScale); 486 displayState.zoom_state().set_zoom_scale(scrollView.zoomScale); 487 } 488 return displayState; 489} 490 491- (void)setPageDisplayState:(web::PageDisplayState)displayState { 492 if (!displayState.IsValid()) 493 return; 494 if (self.webView) { 495 // Page state is restored after a page load completes. If the user has 496 // scrolled or changed the zoom scale while the page is still loading, don't 497 // restore any state since it will confuse the user. 498 web::PageDisplayState currentPageDisplayState = self.pageDisplayState; 499 if (currentPageDisplayState.scroll_state() == 500 _displayStateOnStartLoading.scroll_state() && 501 !_pageHasZoomed) { 502 [self applyPageDisplayState:displayState]; 503 } 504 } 505} 506 507- (NSDictionary*)WKWebViewObservers { 508 return @{ 509 @"serverTrust" : @"webViewSecurityFeaturesDidChange", 510 @"hasOnlySecureContent" : @"webViewSecurityFeaturesDidChange", 511 @"title" : @"webViewTitleDidChange", 512 }; 513} 514 515- (GURL)currentURL { 516 web::URLVerificationTrustLevel trustLevel = 517 web::URLVerificationTrustLevel::kNone; 518 return [self currentURLWithTrustLevel:&trustLevel]; 519} 520 521- (WebState*)webState { 522 return _webStateImpl; 523} 524 525- (CGPoint)scrollPosition { 526 return self.webScrollView.contentOffset; 527} 528 529- (CRWTouchTrackingRecognizer*)touchTrackingRecognizer { 530 if (!_touchTrackingRecognizer) { 531 _touchTrackingRecognizer = 532 [[CRWTouchTrackingRecognizer alloc] initWithTouchTrackingDelegate:self]; 533 } 534 return _touchTrackingRecognizer; 535} 536 537#pragma mark Navigation and Session Information 538 539- (NavigationManagerImpl*)navigationManagerImpl { 540 return self.webStateImpl ? &(self.webStateImpl->GetNavigationManagerImpl()) 541 : nil; 542} 543 544- (web::NavigationItemImpl*)currentNavItem { 545 return self.navigationManagerImpl 546 ? self.navigationManagerImpl->GetCurrentItemImpl() 547 : nullptr; 548} 549 550#pragma mark - ** Public Methods ** 551 552#pragma mark - Header public methods 553 554- (web::NavigationItemImpl*)lastPendingItemForNewNavigation { 555 WKNavigation* navigation = 556 [self.navigationHandler.navigationStates 557 lastNavigationWithPendingItemInNavigationContext]; 558 if (!navigation) 559 return nullptr; 560 web::NavigationContextImpl* context = 561 [self.navigationHandler.navigationStates contextForNavigation:navigation]; 562 return context->GetItem(); 563} 564 565- (void)showTransientContentView:(UIView<CRWScrollableContent>*)contentView { 566 DCHECK(contentView); 567 DCHECK(contentView.scrollView); 568 // TODO(crbug.com/556848) Reenable DCHECK when |CRWWebControllerContainerView| 569 // is restructured so that subviews are not added during |layoutSubviews|. 570 // DCHECK([contentView.scrollView isDescendantOfView:contentView]); 571 [_containerView displayTransientContent:contentView]; 572} 573 574- (void)clearTransientContentView { 575 // Early return if there is no transient content view. 576 if (![_containerView transientContentView]) 577 return; 578 579 // Remove the transient content view from the hierarchy. 580 [_containerView clearTransientContentView]; 581} 582 583// Caller must reset the delegate before calling. 584- (void)close { 585 self.webStateImpl->CancelDialogs(); 586 587 _SSLStatusUpdater = nil; 588 [self.navigationHandler close]; 589 [self.UIHandler close]; 590 [self.JSNavigationHandler close]; 591 [self.requestController close]; 592 self.swipeRecognizerProvider = nil; 593 [self.requestController close]; 594 [self.webViewNavigationObserver close]; 595 596 // Mark the destruction sequence has started, in case someone else holds a 597 // strong reference and tries to continue using the tab. 598 DCHECK(!_isBeingDestroyed); 599 _isBeingDestroyed = YES; 600 601 // Remove the web view now. Otherwise, delegate callbacks occur. 602 [self removeWebView]; 603 604 // Explicitly reset content to clean up views and avoid dangling KVO 605 // observers. 606 [_containerView resetContent]; 607 608 _webStateImpl = nullptr; 609 610 DCHECK(!self.webView); 611 // TODO(crbug.com/662860): Don't set the delegate to nil. 612 [_containerView setDelegate:nil]; 613 _touchTrackingRecognizer.touchTrackingDelegate = nil; 614 [[_webViewProxy scrollViewProxy] removeObserver:self]; 615 [[NSNotificationCenter defaultCenter] removeObserver:self]; 616} 617 618- (BOOL)isViewAlive { 619 return !self.navigationHandler.webProcessCrashed && 620 [_containerView isViewAlive]; 621} 622 623- (BOOL)contentIsHTML { 624 return self.webView && 625 web::IsContentTypeHtml(self.webState->GetContentsMimeType()); 626} 627 628- (GURL)currentURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel { 629 DCHECK(trustLevel) << "Verification of the trustLevel state is mandatory"; 630 631 // The web view URL is the current URL only if it is neither a placeholder URL 632 // (used to hold WKBackForwardListItem for WebUI) nor a restore_session.html 633 // (used to replay session history in WKWebView). 634 // TODO(crbug.com/738020): Investigate if this method is still needed and if 635 // it can be implemented using NavigationManager API after removal of legacy 636 // navigation stack. 637 if (self.webView && !IsWKInternalUrl(self.webView.URL)) { 638 return [self webURLWithTrustLevel:trustLevel]; 639 } 640 // Any non-web URL source is trusted. 641 *trustLevel = web::URLVerificationTrustLevel::kAbsolute; 642 web::NavigationItem* item = 643 self.navigationManagerImpl 644 ->GetLastCommittedItemInCurrentOrRestoredSession(); 645 if (item) { 646 // This special case is added for any app specific URLs that have been 647 // rewritten to about:// URLs. 648 if (item->GetURL().SchemeIs(url::kAboutScheme) && 649 web::GetWebClient()->IsAppSpecificURL(item->GetVirtualURL())) { 650 return item->GetURL(); 651 } 652 return item->GetVirtualURL(); 653 } 654 return GURL::EmptyGURL(); 655} 656 657- (void)reloadWithRendererInitiatedNavigation:(BOOL)rendererInitiated { 658 // Clear last user interaction. 659 // TODO(crbug.com/546337): Move to after the load commits, in the subclass 660 // implementation. This will be inaccurate if the reload fails or is 661 // cancelled. 662 _userInteractionState.SetLastUserInteraction(nullptr); 663 base::RecordAction(base::UserMetricsAction("Reload")); 664 [_requestController reloadWithRendererInitiatedNavigation:rendererInitiated]; 665} 666 667- (void)stopLoading { 668 base::RecordAction(base::UserMetricsAction("Stop")); 669 // Discard all pending and transient items before notifying WebState observers 670 self.navigationManagerImpl->DiscardNonCommittedItems(); 671 for (__strong id navigation in 672 [self.navigationHandler.navigationStates pendingNavigations]) { 673 if (navigation == [NSNull null]) { 674 // null is a valid navigation object passed to WKNavigationDelegate 675 // callbacks and represents window opening action. 676 navigation = nil; 677 } 678 // This will remove pending item for navigations which may still call 679 // WKNavigationDelegate callbacks see (crbug.com/969915). 680 web::NavigationContextImpl* context = 681 [self.navigationHandler.navigationStates 682 contextForNavigation:navigation]; 683 context->ReleaseItem(); 684 } 685 686 [self.webView stopLoading]; 687 [self.navigationHandler stopLoading]; 688} 689 690- (void)loadCurrentURLWithRendererInitiatedNavigation:(BOOL)rendererInitiated { 691 // If the content view doesn't exist, the tab has either been evicted, or 692 // never displayed. Bail, and let the URL be loaded when the tab is shown. 693 if (!_containerView) 694 return; 695 696 // WKBasedNavigationManagerImpl needs WKWebView to load native views, but 697 // WKWebView cannot be created while web usage is disabled to avoid breaking 698 // clearing browser data. Bail now and let the URL be loaded when web 699 // usage is enabled again. This can happen when purging web pages when an 700 // interstitial is presented over a native view. See https://crbug.com/865985 701 // for details. 702 if (!_webUsageEnabled) 703 return; 704 705 _currentURLLoadWasTrigerred = YES; 706 707 [_requestController 708 loadCurrentURLWithRendererInitiatedNavigation:rendererInitiated]; 709} 710 711- (void)loadCurrentURLIfNecessary { 712 if (self.navigationHandler.webProcessCrashed) { 713 [self loadCurrentURLWithRendererInitiatedNavigation:NO]; 714 } else if (!_currentURLLoadWasTrigerred) { 715 [self ensureContainerViewCreated]; 716 717 // TODO(crbug.com/796608): end the practice of calling |loadCurrentURL| 718 // when it is possible there is no current URL. If the call performs 719 // necessary initialization, break that out. 720 [self loadCurrentURLWithRendererInitiatedNavigation:NO]; 721 } 722} 723 724- (void)loadData:(NSData*)data 725 MIMEType:(NSString*)MIMEType 726 forURL:(const GURL&)URL { 727 [_requestController loadData:data MIMEType:MIMEType forURL:URL]; 728} 729 730// Loads the HTML into the page at the given URL. Only for testing purpose. 731- (void)loadHTML:(NSString*)HTML forURL:(const GURL&)URL { 732 [_requestController loadHTML:HTML forURL:URL]; 733} 734 735- (void)recordStateInHistory { 736 // Only record the state if: 737 // - the current NavigationItem's URL matches the current URL, and 738 // - the user has interacted with the page. 739 web::NavigationItem* item = self.currentNavItem; 740 if (item && item->GetURL() == [self currentURL] && 741 _userInteractionState.UserInteractionRegisteredSincePageLoaded()) { 742 item->SetPageDisplayState(self.pageDisplayState); 743 } 744} 745 746- (void)setVisible:(BOOL)visible { 747 _visible = visible; 748} 749 750- (void)wasShown { 751 self.visible = YES; 752 753 // WebKit adds a drop interaction to a subview (WKContentView) of WKWebView's 754 // scrollView when the web view is added to the view hierarchy. 755 [self addCustomURLDropInteractionIfNeeded]; 756} 757 758- (void)wasHidden { 759 self.visible = NO; 760 if (_isBeingDestroyed) 761 return; 762 [self recordStateInHistory]; 763} 764 765- (void)setKeepsRenderProcessAlive:(BOOL)keepsRenderProcessAlive { 766 _keepsRenderProcessAlive = keepsRenderProcessAlive; 767 [_containerView 768 updateWebViewContentViewForContainerWindow:_containerView.window]; 769} 770 771- (void)didFinishGoToIndexSameDocumentNavigationWithType: 772 (web::NavigationInitiationType)type 773 hasUserGesture:(BOOL)hasUserGesture { 774 web::NavigationItem* item = 775 self.webStateImpl->GetNavigationManager()->GetLastCommittedItem(); 776 GURL URL = item->GetVirtualURL(); 777 std::unique_ptr<web::NavigationContextImpl> context = 778 web::NavigationContextImpl::CreateNavigationContext( 779 self.webStateImpl, URL, hasUserGesture, 780 static_cast<ui::PageTransition>( 781 item->GetTransitionType() | 782 ui::PageTransition::PAGE_TRANSITION_FORWARD_BACK), 783 type == web::NavigationInitiationType::RENDERER_INITIATED); 784 context->SetIsSameDocument(true); 785 self.webStateImpl->OnNavigationStarted(context.get()); 786 [self setDocumentURL:URL context:context.get()]; 787 context->SetHasCommitted(true); 788 self.webStateImpl->OnNavigationFinished(context.get()); 789 self.navigationHandler.navigationState = web::WKNavigationState::FINISHED; 790 [_requestController didFinishWithURL:URL 791 loadSuccess:YES 792 context:context.get()]; 793} 794 795- (void)goToBackForwardListItem:(WKBackForwardListItem*)wk_item 796 navigationItem:(web::NavigationItem*)item 797 navigationInitiationType:(web::NavigationInitiationType)type 798 hasUserGesture:(BOOL)hasUserGesture { 799 WKNavigation* navigation = [self.webView goToBackForwardListItem:wk_item]; 800 801 GURL URL = net::GURLWithNSURL(wk_item.URL); 802 if (!base::FeatureList::IsEnabled(web::features::kUseJSForErrorPage) && 803 IsPlaceholderUrl(URL)) { 804 // No need to create navigation context for placeholder back forward 805 // navigations. Future callbacks do not expect that context will exist. 806 return; 807 } 808 809 self.webStateImpl->ClearWebUI(); 810 811 // This navigation can be an iframe navigation, but it's not possible to 812 // distinguish it from the main frame navigation, so context still has to be 813 // created. 814 std::unique_ptr<web::NavigationContextImpl> context = 815 web::NavigationContextImpl::CreateNavigationContext( 816 self.webStateImpl, URL, hasUserGesture, 817 static_cast<ui::PageTransition>( 818 item->GetTransitionType() | 819 ui::PageTransition::PAGE_TRANSITION_FORWARD_BACK), 820 type == web::NavigationInitiationType::RENDERER_INITIATED); 821 context->SetNavigationItemUniqueID(item->GetUniqueID()); 822 if (!navigation) { 823 // goToBackForwardListItem: returns nil for same-document back forward 824 // navigations. 825 context->SetIsSameDocument(true); 826 } else { 827 self.navigationHandler.navigationState = web::WKNavigationState::REQUESTED; 828 } 829 830 if ([ErrorPageHelper isErrorPageFileURL:URL]) { 831 context->SetLoadingErrorPage(true); 832 } 833 834 web::WKBackForwardListItemHolder* holder = 835 web::WKBackForwardListItemHolder::FromNavigationItem(item); 836 holder->set_navigation_type(WKNavigationTypeBackForward); 837 context->SetIsPost((holder && [holder->http_method() isEqual:@"POST"]) || 838 item->HasPostData()); 839 840 if (holder) { 841 context->SetMimeType(holder->mime_type()); 842 } 843 844 [self.navigationHandler.navigationStates setContext:std::move(context) 845 forNavigation:navigation]; 846 [self.navigationHandler.navigationStates 847 setState:web::WKNavigationState::REQUESTED 848 forNavigation:navigation]; 849} 850 851- (void)takeSnapshotWithRect:(CGRect)rect 852 completion:(void (^)(UIImage*))completion { 853 if (!self.webView) { 854 dispatch_async(dispatch_get_main_queue(), ^{ 855 completion(nil); 856 }); 857 } 858 859 WKSnapshotConfiguration* configuration = 860 [[WKSnapshotConfiguration alloc] init]; 861 configuration.rect = [self.webView convertRect:rect fromView:self.view]; 862 __weak CRWWebController* weakSelf = self; 863 [self.webView 864 takeSnapshotWithConfiguration:configuration 865 completionHandler:^(UIImage* snapshot, NSError* error) { 866 // Pass nil to the completion block if there is an error 867 // or if the web view has been removed before the 868 // snapshot is finished. |snapshot| can sometimes be 869 // corrupt if it's sent due to the WKWebView's 870 // deallocation, so callbacks received after 871 // |-removeWebView| are ignored to prevent crashing. 872 if (error || !weakSelf.webView) { 873 if (error) { 874 DLOG(ERROR) 875 << "WKWebView snapshot error: " 876 << base::SysNSStringToUTF8(error.description); 877 } 878 completion(nil); 879 } else { 880 completion(snapshot); 881 } 882 }]; 883} 884 885- (void)createFullPagePDFWithCompletion:(void (^)(NSData*))completionBlock { 886 // Invoke the |completionBlock| with nil rather than a blank PDF for certain 887 // URLs. 888 const GURL& URL = self.webState->GetLastCommittedURL(); 889 if (![self contentIsHTML] || !URL.is_valid() || 890 web::GetWebClient()->IsAppSpecificURL(URL)) { 891 dispatch_async(dispatch_get_main_queue(), ^{ 892 completionBlock(nil); 893 }); 894 return; 895 } 896 web::CreateFullPagePdf(self.webView, base::BindOnce(completionBlock)); 897} 898 899- (void)removeWebViewFromViewHierarchy { 900 [_containerView resetContent]; 901} 902 903- (void)addWebViewToViewHierarchy { 904 [self displayWebView]; 905} 906 907#pragma mark - CRWTouchTrackingDelegate (Public) 908 909- (void)touched:(BOOL)touched { 910 _userInteractionState.SetTapInProgress(touched); 911 if (touched) { 912 _userInteractionState.SetUserInteractionRegisteredSincePageLoaded(true); 913 if (_isBeingDestroyed) 914 return; 915 const NavigationManagerImpl* navigationManager = self.navigationManagerImpl; 916 GURL mainDocumentURL = 917 navigationManager->GetLastCommittedItem() 918 ? navigationManager->GetLastCommittedItem()->GetURL() 919 : [self currentURL]; 920 _userInteractionState.SetLastUserInteraction( 921 std::make_unique<web::UserInteractionEvent>(mainDocumentURL)); 922 } 923} 924 925#pragma mark - ** Private Methods ** 926 927- (void)setDocumentURL:(const GURL&)newURL 928 context:(web::NavigationContextImpl*)context { 929 GURL oldDocumentURL = _documentURL; 930 if (newURL != _documentURL && newURL.is_valid()) { 931 _documentURL = newURL; 932 _userInteractionState.SetUserInteractionRegisteredSinceLastUrlChange(false); 933 } 934 if (context && !context->IsLoadingErrorPage() && 935 !context->IsLoadingHtmlString() && !IsWKInternalUrl(newURL) && 936 !newURL.SchemeIs(url::kAboutScheme) && self.webView) { 937 // On iOS13, WebKit started changing the URL visible webView.URL when 938 // opening a new tab and then writing to it, e.g. 939 // window.open('javascript:document.write(1)'). This URL is never commited, 940 // so it should be OK to ignore this URL change. 941 if (base::ios::IsRunningOnIOS13OrLater() && oldDocumentURL.IsAboutBlank() && 942 !self.webStateImpl->GetNavigationManager()->GetLastCommittedItem() && 943 !self.webView.loading) { 944 return; 945 } 946 947 // Ignore mismatches triggered by a WKWebView out-of-sync back forward list. 948 if (![self.webView.backForwardList.currentItem.URL 949 isEqual:self.webView.URL]) { 950 return; 951 } 952 953 GURL documentOrigin = newURL.GetOrigin(); 954 web::NavigationItem* committedItem = 955 self.webStateImpl->GetNavigationManager()->GetLastCommittedItem(); 956 GURL committedOrigin = 957 committedItem ? committedItem->GetURL().GetOrigin() : GURL::EmptyGURL(); 958 DCHECK_EQ(documentOrigin, committedOrigin) 959 << "Old and new URL detection system have a mismatch"; 960 961 ukm::SourceId sourceID = ukm::ConvertToSourceId( 962 context->GetNavigationId(), ukm::SourceIdType::NAVIGATION_ID); 963 if (sourceID != ukm::kInvalidSourceId) { 964 ukm::builders::IOS_URLMismatchInLegacyAndSlimNavigationManager(sourceID) 965 .SetHasMismatch(documentOrigin != committedOrigin) 966 .Record(ukm::UkmRecorder::Get()); 967 } 968 } 969} 970 971- (GURL)webURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel { 972 DCHECK(trustLevel); 973 *trustLevel = web::URLVerificationTrustLevel::kAbsolute; 974 // Placeholder URL is an implementation detail. Don't expose it to users of 975 // web layer. 976 if (!base::FeatureList::IsEnabled(web::features::kUseJSForErrorPage) && 977 IsPlaceholderUrl(_documentURL)) 978 return ExtractUrlFromPlaceholderUrl(_documentURL); 979 return _documentURL; 980} 981 982- (BOOL)isUserInitiatedAction:(WKNavigationAction*)action { 983 return _userInteractionState.IsUserInteracting(self.webView); 984} 985 986// Adds a custom drop interaction to the same subview of |self.webScrollView| 987// that already has a default drop interaction. 988- (void)addCustomURLDropInteractionIfNeeded { 989 if (!base::FeatureList::IsEnabled( 990 web::features::kAddWebContentDropInteraction)) 991 return; 992 993 BOOL subviewWithDefaultInteractionFound = NO; 994 for (UIView* subview in self.webScrollView.subviews) { 995 BOOL defaultInteractionFound = NO; 996 BOOL customInteractionFound = NO; 997 for (id<UIInteraction> interaction in subview.interactions) { 998 if ([interaction isKindOfClass:[UIDropInteraction class]]) { 999 if (interaction == self.customDropInteraction) { 1000 customInteractionFound = YES; 1001 } else { 1002 DCHECK(!defaultInteractionFound && 1003 !subviewWithDefaultInteractionFound) 1004 << "There should be only one default drop interaction in the " 1005 "webScrollView."; 1006 defaultInteractionFound = YES; 1007 subviewWithDefaultInteractionFound = YES; 1008 } 1009 } 1010 } 1011 if (customInteractionFound) { 1012 // The custom interaction must be added after the default drop interaction 1013 // to work properly. 1014 [subview removeInteraction:self.customDropInteraction]; 1015 [subview addInteraction:self.customDropInteraction]; 1016 } else if (defaultInteractionFound) { 1017 if (!self.customDropInteraction) { 1018 self.customDropInteraction = 1019 [[UIDropInteraction alloc] initWithDelegate:self]; 1020 } 1021 [subview addInteraction:self.customDropInteraction]; 1022 } 1023 } 1024} 1025 1026#pragma mark - End of loading 1027 1028- (void)didFinishNavigation:(web::NavigationContextImpl*)context { 1029 // This can be called at multiple times after the document has loaded. Do 1030 // nothing if the document has already loaded. 1031 if (self.navigationHandler.navigationState == 1032 web::WKNavigationState::FINISHED) 1033 return; 1034 1035 web::NavigationItem* pendingOrCommittedItem = 1036 self.navigationManagerImpl->GetPendingItem(); 1037 if (!pendingOrCommittedItem) 1038 pendingOrCommittedItem = self.navigationManagerImpl->GetLastCommittedItem(); 1039 if (pendingOrCommittedItem) { 1040 // This stores the UserAgent that was used to load the item. 1041 if (pendingOrCommittedItem->GetUserAgentType() == 1042 web::UserAgentType::NONE && 1043 web::wk_navigation_util::URLNeedsUserAgentType( 1044 pendingOrCommittedItem->GetURL())) { 1045 pendingOrCommittedItem->SetUserAgentType( 1046 self.webStateImpl->GetUserAgentForNextNavigation( 1047 pendingOrCommittedItem->GetURL())); 1048 } 1049 } 1050 1051 // Restore allowsBackForwardNavigationGestures once restoration is complete. 1052 if (!self.navigationManagerImpl->IsRestoreSessionInProgress()) { 1053 if (_webView.allowsBackForwardNavigationGestures != 1054 _allowsBackForwardNavigationGestures) { 1055 _webView.allowsBackForwardNavigationGestures = 1056 _allowsBackForwardNavigationGestures; 1057 } 1058 } 1059 1060 BOOL success = !context || !context->GetError(); 1061 [self loadCompleteWithSuccess:success forContext:context]; 1062 1063 // WebKit adds a drop interaction to a subview (WKContentView) of WKWebView's 1064 // scrollView when a new WebProcess finishes launching. This can be loading 1065 // the first page, navigating cross-domain, or recovering from a WebProcess 1066 // crash. Add a custom drop interaction alongside the default drop 1067 // interaction. 1068 [self addCustomURLDropInteractionIfNeeded]; 1069} 1070 1071- (void)loadCompleteWithSuccess:(BOOL)loadSuccess 1072 forContext:(web::NavigationContextImpl*)context { 1073 // The webView may have been torn down. Be safe and do nothing if that's 1074 // happened. 1075 if (self.navigationHandler.navigationState != web::WKNavigationState::STARTED) 1076 return; 1077 1078 const GURL currentURL([self currentURL]); 1079 1080 self.navigationHandler.navigationState = web::WKNavigationState::FINISHED; 1081 1082 [self optOutScrollsToTopForSubviews]; 1083 1084 // Perform post-load-finished updates. 1085 [_requestController didFinishWithURL:currentURL 1086 loadSuccess:loadSuccess 1087 context:context]; 1088 1089 if (web::GetWebClient()->IsEmbedderBlockRestoreUrlEnabled()) { 1090 if (@available(iOS 14, *)) { 1091 } else { 1092 if (@available(iOS 13.5, *)) { 1093 // In some cases on iOS 13.5, when restoring about: URL, the load might 1094 // never ends. Make sure to mark the load as done here. This is fixed in 1095 // iOS 14. See crbug.com/1099235. 1096 if (currentURL.SchemeIs(url::kAboutScheme)) { 1097 self.webStateImpl->SetIsLoading(false); 1098 } 1099 } 1100 } 1101 } 1102 1103 // Execute the pending LoadCompleteActions. 1104 for (ProceduralBlock action in _pendingLoadCompleteActions) { 1105 action(); 1106 } 1107 [_pendingLoadCompleteActions removeAllObjects]; 1108} 1109 1110#pragma mark - CRWWebControllerContainerViewDelegate 1111 1112- (CRWWebViewProxyImpl*)contentViewProxyForContainerView: 1113 (CRWWebControllerContainerView*)containerView { 1114 return _webViewProxy; 1115} 1116 1117- (BOOL)shouldKeepRenderProcessAliveForContainerView: 1118 (CRWWebControllerContainerView*)containerView { 1119 return self.shouldKeepRenderProcessAlive; 1120} 1121 1122- (void)containerView:(CRWWebControllerContainerView*)containerView 1123 storeWebViewInWindow:(UIView*)viewToStash { 1124 [web::GetWebClient()->GetWindowedContainer() addSubview:viewToStash]; 1125} 1126 1127#pragma mark - JavaScript message Helpers (Private) 1128 1129- (void)didReceiveScriptMessage:(WKScriptMessage*)message { 1130 // Broken out into separate method to catch errors. 1131 if (![self respondToWKScriptMessage:message]) { 1132 DLOG(WARNING) << "Message from JS not handled due to invalid format"; 1133 } 1134} 1135 1136- (void)didReceiveSessionRestoreScriptMessage:(WKScriptMessage*)message { 1137 if ([message.name isEqualToString:kSessionRestoreScriptMessageName] && 1138 [message.body[@"offset"] isKindOfClass:[NSNumber class]]) { 1139 NSString* method = 1140 [NSString stringWithFormat:@"_crFinishSessionRestoration('%@')", 1141 message.body[@"offset"]]; 1142 // Don't use |_jsInjector| -executeJavaScript here, as it relies on 1143 // |windowID| being injected before window.onload starts. 1144 web::ExecuteJavaScript(self.webView, method, nil); 1145 1146 // Removes the script as it is no longer needed. 1147 CRWWKScriptMessageRouter* messageRouter = 1148 [self webViewConfigurationProvider].GetScriptMessageRouter(); 1149 [messageRouter 1150 removeScriptMessageHandlerForName:kSessionRestoreScriptMessageName 1151 webView:_webView]; 1152 } else { 1153 DLOG(WARNING) << "Invalid session restore JS message name."; 1154 } 1155} 1156 1157- (BOOL)respondToWKScriptMessage:(WKScriptMessage*)scriptMessage { 1158 if (![scriptMessage.name isEqualToString:kScriptMessageName]) { 1159 return NO; 1160 } 1161 1162 std::unique_ptr<base::Value> messageAsValue = 1163 web::ValueResultFromWKResult(scriptMessage.body); 1164 base::DictionaryValue* message = nullptr; 1165 if (!messageAsValue || !messageAsValue->GetAsDictionary(&message)) { 1166 return NO; 1167 } 1168 1169 web::WebFrame* senderFrame = nullptr; 1170 std::string frameID; 1171 if (message->GetString("crwFrameId", &frameID)) { 1172 senderFrame = web::GetWebFrameWithId([self webState], frameID); 1173 } 1174 // Message must be associated with a current frame. 1175 if (!senderFrame) { 1176 return NO; 1177 } 1178 1179 base::DictionaryValue* crwCommand = nullptr; 1180 if (!message->GetDictionary("crwCommand", &crwCommand)) { 1181 return NO; 1182 } 1183 1184 std::string command; 1185 if (!crwCommand->GetString("command", &command)) { 1186 DLOG(WARNING) << "JS message parameter not found: command"; 1187 return NO; 1188 } 1189 1190 self.webStateImpl->OnScriptCommandReceived( 1191 command, *crwCommand, net::GURLWithNSURL(self.webView.URL), 1192 _userInteractionState.IsUserInteracting(self.webView), senderFrame); 1193 return YES; 1194} 1195 1196#pragma mark - CRWWebViewScrollViewProxyObserver 1197 1198- (void)webViewScrollViewDidZoom: 1199 (CRWWebViewScrollViewProxy*)webViewScrollViewProxy { 1200 _pageHasZoomed = YES; 1201 1202 __weak UIScrollView* weakScrollView = self.webScrollView; 1203 [self extractViewportTagWithCompletion:^( 1204 const web::PageViewportState* viewportState) { 1205 if (!weakScrollView) 1206 return; 1207 UIScrollView* scrollView = weakScrollView; 1208 if (viewportState && !viewportState->viewport_tag_present() && 1209 [scrollView minimumZoomScale] == [scrollView maximumZoomScale] && 1210 [scrollView zoomScale] > 1.0) { 1211 UMA_HISTOGRAM_BOOLEAN("Renderer.ViewportZoomBugCount", true); 1212 } 1213 }]; 1214} 1215 1216- (void)webViewScrollViewDidResetContentSize: 1217 (CRWWebViewScrollViewProxy*)webViewScrollViewProxy { 1218 web::NavigationItem* currentItem = self.currentNavItem; 1219 if (webViewScrollViewProxy.isZooming || _applyingPageState || !currentItem) 1220 return; 1221 CGSize contentSize = webViewScrollViewProxy.contentSize; 1222 if (contentSize.width + 1 < CGRectGetWidth(webViewScrollViewProxy.frame)) { 1223 // The content area should never be narrower than the frame, but floating 1224 // point error from non-integer zoom values can cause it to be at most 1 1225 // pixel narrower. If it's any narrower than that, the renderer incorrectly 1226 // resized the content area. Resetting the scroll view's zoom scale will 1227 // force a re-rendering. rdar://23963992 1228 _applyingPageState = YES; 1229 web::PageZoomState zoomState = 1230 currentItem->GetPageDisplayState().zoom_state(); 1231 if (!zoomState.IsValid()) 1232 zoomState = web::PageZoomState(1.0, 1.0, 1.0); 1233 [self applyWebViewScrollZoomScaleFromZoomState:zoomState]; 1234 _applyingPageState = NO; 1235 } 1236} 1237 1238// Under WKWebView, JavaScript can execute asynchronously. User can start 1239// scrolling and calls to window.scrollTo executed during scrolling will be 1240// treated as "during user interaction" and can cause app to go fullscreen. 1241// This is a workaround to use this webViewScrollViewIsDragging flag to ignore 1242// window.scrollTo while user is scrolling. See crbug.com/554257 1243- (void)webViewScrollViewWillBeginDragging: 1244 (CRWWebViewScrollViewProxy*)webViewScrollViewProxy { 1245 [_jsInjector 1246 executeJavaScript:@"__gCrWeb.setWebViewScrollViewIsDragging(true)" 1247 completionHandler:nil]; 1248} 1249 1250- (void)webViewScrollViewDidEndDragging: 1251 (CRWWebViewScrollViewProxy*)webViewScrollViewProxy 1252 willDecelerate:(BOOL)decelerate { 1253 [_jsInjector 1254 executeJavaScript:@"__gCrWeb.setWebViewScrollViewIsDragging(false)" 1255 completionHandler:nil]; 1256} 1257 1258#pragma mark - Page State 1259 1260- (void)restoreStateFromHistory { 1261 web::NavigationItem* item = self.currentNavItem; 1262 if (item) 1263 self.pageDisplayState = item->GetPageDisplayState(); 1264} 1265 1266- (void)extractViewportTagWithCompletion:(ViewportStateCompletion)completion { 1267 DCHECK(completion); 1268 web::NavigationItem* currentItem = self.currentNavItem; 1269 if (!currentItem) { 1270 completion(nullptr); 1271 return; 1272 } 1273 NSString* const kViewportContentQuery = 1274 @"var viewport = document.querySelector('meta[name=\"viewport\"]');" 1275 "viewport ? viewport.content : '';"; 1276 __weak CRWWebController* weakSelf = self; 1277 int itemID = currentItem->GetUniqueID(); 1278 [_jsInjector executeJavaScript:kViewportContentQuery 1279 completionHandler:^(id viewportContent, NSError*) { 1280 web::NavigationItem* item = [weakSelf currentNavItem]; 1281 if (item && item->GetUniqueID() == itemID) { 1282 web::PageViewportState viewportState( 1283 base::mac::ObjCCast<NSString>(viewportContent)); 1284 completion(&viewportState); 1285 } else { 1286 completion(nullptr); 1287 } 1288 }]; 1289} 1290 1291- (void)orientationDidChange { 1292 // When rotating, the available zoom scale range may change, zoomScale's 1293 // percentage into this range should remain constant. However, there are 1294 // two known bugs with respect to adjusting the zoomScale on rotation: 1295 // - WKWebView sometimes erroneously resets the scroll view's zoom scale to 1296 // an incorrect value ( rdar://20100815 ). 1297 // - After zooming occurs in a UIWebView that's displaying a page with a hard- 1298 // coded viewport width, the zoom will not be updated upon rotation 1299 // ( crbug.com/485055 ). 1300 if (!self.webView) 1301 return; 1302 web::NavigationItem* currentItem = self.currentNavItem; 1303 if (!currentItem) 1304 return; 1305 web::PageDisplayState displayState = currentItem->GetPageDisplayState(); 1306 if (!displayState.IsValid()) 1307 return; 1308 CGFloat zoomPercentage = (displayState.zoom_state().zoom_scale() - 1309 displayState.zoom_state().minimum_zoom_scale()) / 1310 displayState.zoom_state().GetMinMaxZoomDifference(); 1311 displayState.zoom_state().set_minimum_zoom_scale( 1312 self.webScrollView.minimumZoomScale); 1313 displayState.zoom_state().set_maximum_zoom_scale( 1314 self.webScrollView.maximumZoomScale); 1315 displayState.zoom_state().set_zoom_scale( 1316 displayState.zoom_state().minimum_zoom_scale() + 1317 zoomPercentage * displayState.zoom_state().GetMinMaxZoomDifference()); 1318 currentItem->SetPageDisplayState(displayState); 1319 [self applyPageDisplayState:currentItem->GetPageDisplayState()]; 1320} 1321 1322- (void)applyPageDisplayState:(const web::PageDisplayState&)displayState { 1323 if (!displayState.IsValid()) 1324 return; 1325 __weak CRWWebController* weakSelf = self; 1326 web::PageDisplayState displayStateCopy = displayState; 1327 [self extractViewportTagWithCompletion:^( 1328 const web::PageViewportState* viewportState) { 1329 if (viewportState) { 1330 [weakSelf applyPageDisplayState:displayStateCopy 1331 userScalable:viewportState->user_scalable()]; 1332 } 1333 }]; 1334} 1335 1336- (void)applyPageDisplayState:(const web::PageDisplayState&)displayState 1337 userScalable:(BOOL)isUserScalable { 1338 // Early return if |scrollState| doesn't match the current NavigationItem. 1339 // This can sometimes occur in tests, as navigation occurs programmatically 1340 // and |-applyPageScrollState:| is asynchronous. 1341 web::NavigationItem* currentItem = self.currentNavItem; 1342 if (currentItem && currentItem->GetPageDisplayState() != displayState) 1343 return; 1344 DCHECK(displayState.IsValid()); 1345 _applyingPageState = YES; 1346 if (isUserScalable) { 1347 [self prepareToApplyWebViewScrollZoomScale]; 1348 [self applyWebViewScrollZoomScaleFromZoomState:displayState.zoom_state()]; 1349 [self finishApplyingWebViewScrollZoomScale]; 1350 } 1351 [self applyWebViewScrollOffsetFromScrollState:displayState.scroll_state()]; 1352 _applyingPageState = NO; 1353} 1354 1355- (void)prepareToApplyWebViewScrollZoomScale { 1356 id webView = self.webView; 1357 if (![webView respondsToSelector:@selector(viewForZoomingInScrollView:)]) { 1358 return; 1359 } 1360 1361 UIView* contentView = [webView viewForZoomingInScrollView:self.webScrollView]; 1362 1363 if ([webView respondsToSelector:@selector(scrollViewWillBeginZooming: 1364 withView:)]) { 1365 [webView scrollViewWillBeginZooming:self.webScrollView 1366 withView:contentView]; 1367 } 1368} 1369 1370- (void)finishApplyingWebViewScrollZoomScale { 1371 id webView = self.webView; 1372 if ([webView respondsToSelector:@selector 1373 (scrollViewDidEndZooming:withView:atScale:)] && 1374 [webView respondsToSelector:@selector(viewForZoomingInScrollView:)]) { 1375 // This correctly sets the content's frame in the scroll view to 1376 // fit the web page and upscales the content so that it isn't 1377 // blurry. 1378 UIView* contentView = 1379 [webView viewForZoomingInScrollView:self.webScrollView]; 1380 [webView scrollViewDidEndZooming:self.webScrollView 1381 withView:contentView 1382 atScale:self.webScrollView.zoomScale]; 1383 } 1384} 1385 1386- (void)applyWebViewScrollZoomScaleFromZoomState: 1387 (const web::PageZoomState&)zoomState { 1388 // After rendering a web page, WKWebView keeps the |minimumZoomScale| and 1389 // |maximumZoomScale| properties of its scroll view constant while adjusting 1390 // the |zoomScale| property accordingly. The maximum-scale or minimum-scale 1391 // meta tags of a page may have changed since the state was recorded, so clamp 1392 // the zoom scale to the current range if necessary. 1393 DCHECK(zoomState.IsValid()); 1394 CGFloat zoomScale = zoomState.zoom_scale(); 1395 if (zoomScale < self.webScrollView.minimumZoomScale) 1396 zoomScale = self.webScrollView.minimumZoomScale; 1397 if (zoomScale > self.webScrollView.maximumZoomScale) 1398 zoomScale = self.webScrollView.maximumZoomScale; 1399 self.webScrollView.zoomScale = zoomScale; 1400} 1401 1402- (void)applyWebViewScrollOffsetFromScrollState: 1403 (const web::PageScrollState&)scrollState { 1404 DCHECK(scrollState.IsValid()); 1405 CGPoint contentOffset = scrollState.GetEffectiveContentOffsetForContentInset( 1406 self.webScrollView.contentInset); 1407 if (self.navigationHandler.navigationState == 1408 web::WKNavigationState::FINISHED) { 1409 // If the page is loaded, update the scroll immediately. 1410 self.webScrollView.contentOffset = contentOffset; 1411 } else { 1412 // If the page isn't loaded, store the action to update the scroll 1413 // when the page finishes loading. 1414 __weak UIScrollView* weakScrollView = self.webScrollView; 1415 ProceduralBlock action = [^{ 1416 weakScrollView.contentOffset = contentOffset; 1417 } copy]; 1418 [_pendingLoadCompleteActions addObject:action]; 1419 } 1420} 1421 1422#pragma mark - Fullscreen 1423 1424- (void)optOutScrollsToTopForSubviews { 1425 NSMutableArray* stack = 1426 [NSMutableArray arrayWithArray:[self.webScrollView subviews]]; 1427 while (stack.count) { 1428 UIView* current = [stack lastObject]; 1429 [stack removeLastObject]; 1430 [stack addObjectsFromArray:[current subviews]]; 1431 if ([current isKindOfClass:[UIScrollView class]]) 1432 static_cast<UIScrollView*>(current).scrollsToTop = NO; 1433 } 1434} 1435 1436#pragma mark - Security Helpers 1437 1438- (void)updateSSLStatusForCurrentNavigationItem { 1439 if (_isBeingDestroyed) { 1440 return; 1441 } 1442 1443 NavigationManagerImpl* navManager = self.navigationManagerImpl; 1444 web::NavigationItem* currentNavItem = navManager->GetLastCommittedItem(); 1445 if (!currentNavItem) { 1446 return; 1447 } 1448 1449 if (!_SSLStatusUpdater) { 1450 _SSLStatusUpdater = 1451 [[CRWSSLStatusUpdater alloc] initWithDataSource:self 1452 navigationManager:navManager]; 1453 [_SSLStatusUpdater setDelegate:self]; 1454 } 1455 NSString* host = base::SysUTF8ToNSString(_documentURL.host()); 1456 BOOL hasOnlySecureContent = [self.webView hasOnlySecureContent]; 1457 base::ScopedCFTypeRef<SecTrustRef> trust; 1458 trust.reset([self.webView serverTrust], base::scoped_policy::RETAIN); 1459 1460 [_SSLStatusUpdater updateSSLStatusForNavigationItem:currentNavItem 1461 withCertHost:host 1462 trust:std::move(trust) 1463 hasOnlySecureContent:hasOnlySecureContent]; 1464} 1465 1466#pragma mark - WebView Helpers 1467 1468// Creates a container view if it's not yet created. 1469- (void)ensureContainerViewCreated { 1470 if (_containerView) 1471 return; 1472 1473 DCHECK(!_isBeingDestroyed); 1474 // Create the top-level parent view, which will contain the content. Note, 1475 // this needs to be created with a non-zero size to allow for subviews with 1476 // autosize constraints to be correctly processed. 1477 _containerView = 1478 [[CRWWebControllerContainerView alloc] initWithDelegate:self]; 1479 1480 // This will be resized later, but matching the final frame will minimize 1481 // re-rendering. 1482 UIView* browserContainer = self.webStateImpl->GetWebViewContainer(); 1483 if (browserContainer) { 1484 _containerView.frame = browserContainer.bounds; 1485 } else { 1486 // Use the screen size because the application's key window and the 1487 // container may still be nil. 1488 _containerView.frame = 1489 UIApplication.sharedApplication.keyWindow 1490 ? UIApplication.sharedApplication.keyWindow.bounds 1491 : UIScreen.mainScreen.bounds; 1492 } 1493 1494 DCHECK(!CGRectIsEmpty(_containerView.frame)); 1495 1496 [_containerView addGestureRecognizer:[self touchTrackingRecognizer]]; 1497} 1498 1499// Creates a web view if it's not yet created. 1500- (WKWebView*)ensureWebViewCreated { 1501 WKWebViewConfiguration* config = 1502 [self webViewConfigurationProvider].GetWebViewConfiguration(); 1503 return [self ensureWebViewCreatedWithConfiguration:config]; 1504} 1505 1506// Creates a web view with given |config|. No-op if web view is already created. 1507- (WKWebView*)ensureWebViewCreatedWithConfiguration: 1508 (WKWebViewConfiguration*)config { 1509 if (!self.webView) { 1510 // This has to be called to ensure the container view of `self.webView` is 1511 // created. Otherwise `self.webView.frame.size` will be CGSizeZero which 1512 // fails a DCHECK later. 1513 [self ensureContainerViewCreated]; 1514 1515 [self setWebView:[self webViewWithConfiguration:config]]; 1516 // The following is not called in -setWebView: as the latter used in unit 1517 // tests with fake web view, which cannot be added to view hierarchy. 1518 CHECK(_webUsageEnabled) << "Tried to create a web view while suspended!"; 1519 1520 DCHECK(self.webView); 1521 1522 [self.webView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | 1523 UIViewAutoresizingFlexibleHeight]; 1524 1525 // Create a dependency between the |webView| pan gesture and BVC side swipe 1526 // gestures. Note: This needs to be added before the longPress recognizers 1527 // below, or the longPress appears to deadlock the remaining recognizers, 1528 // thereby breaking scroll. 1529 NSSet* recognizers = [_swipeRecognizerProvider swipeRecognizers]; 1530 for (UISwipeGestureRecognizer* swipeRecognizer in recognizers) { 1531 [self.webScrollView.panGestureRecognizer 1532 requireGestureRecognizerToFail:swipeRecognizer]; 1533 } 1534 1535 web::BrowserState* browserState = self.webStateImpl->GetBrowserState(); 1536 self.UIHandler.contextMenuController = 1537 [[CRWContextMenuController alloc] initWithWebView:self.webView 1538 browserState:browserState 1539 delegate:self]; 1540 self.UIHandler.contextMenuController.webState = self.webStateImpl; 1541 1542 // WKWebViews with invalid or empty frames have exhibited rendering bugs, so 1543 // resize the view to match the container view upon creation. 1544 [self.webView setFrame:[_containerView bounds]]; 1545 } 1546 1547 // If web view is not currently displayed and if the visible NavigationItem 1548 // should be loaded in this web view, display it immediately. Otherwise, it 1549 // will be displayed when the pending load is committed. 1550 if (![_containerView webViewContentView]) { 1551 [self displayWebView]; 1552 } 1553 1554 return self.webView; 1555} 1556 1557// Returns a new autoreleased web view created with given configuration. 1558- (WKWebView*)webViewWithConfiguration:(WKWebViewConfiguration*)config { 1559 // Do not attach the context menu controller immediately as the JavaScript 1560 // delegate must be specified. 1561 web::UserAgentType defaultUserAgent = 1562 web::features::UseWebClientDefaultUserAgent() 1563 ? web::UserAgentType::AUTOMATIC 1564 : web::UserAgentType::MOBILE; 1565 web::NavigationItem* item = self.currentNavItem; 1566 web::UserAgentType userAgentType = 1567 item ? item->GetUserAgentType() : defaultUserAgent; 1568 if (userAgentType == web::UserAgentType::AUTOMATIC) { 1569 userAgentType = 1570 web::GetWebClient()->GetDefaultUserAgent(_containerView, GURL()); 1571 } 1572 1573 return web::BuildWKWebView( 1574 CGRectZero, config, self.webStateImpl->GetBrowserState(), userAgentType); 1575} 1576 1577// Wraps the web view in a CRWWebViewContentView and adds it to the container 1578// view. 1579- (void)displayWebView { 1580 if (!self.webView || [_containerView webViewContentView]) 1581 return; 1582 1583 CRWWebViewContentView* webViewContentView = 1584 [[CRWWebViewContentView alloc] initWithWebView:self.webView 1585 scrollView:self.webScrollView]; 1586 [_containerView displayWebViewContentView:webViewContentView]; 1587} 1588 1589- (void)removeWebView { 1590 if (!self.webView) 1591 return; 1592 1593 self.webStateImpl->CancelDialogs(); 1594 self.navigationManagerImpl->DetachFromWebView(); 1595 1596 [self setWebView:nil]; 1597 [self.navigationHandler stopLoading]; 1598 [_containerView resetContent]; 1599 1600 // webView:didFailProvisionalNavigation:withError: may never be called after 1601 // resetting WKWebView, so it is important to clear pending navigations now. 1602 for (__strong id navigation in 1603 [self.navigationHandler.navigationStates pendingNavigations]) { 1604 [self.navigationHandler.navigationStates removeNavigation:navigation]; 1605 } 1606} 1607 1608// Returns the WKWebViewConfigurationProvider associated with the web 1609// controller's BrowserState. 1610- (web::WKWebViewConfigurationProvider&)webViewConfigurationProvider { 1611 web::BrowserState* browserState = self.webStateImpl->GetBrowserState(); 1612 return web::WKWebViewConfigurationProvider::FromBrowserState(browserState); 1613} 1614 1615#pragma mark - CRWWKUIHandlerDelegate 1616 1617- (WKWebView*)UIHandler:(CRWWKUIHandler*)UIHandler 1618 createWebViewWithConfiguration:(WKWebViewConfiguration*)configuration 1619 forWebState:(web::WebState*)webState { 1620 CRWWebController* webController = 1621 static_cast<web::WebStateImpl*>(webState)->GetWebController(); 1622 DCHECK(!webController || webState->HasOpener()); 1623 1624 [webController ensureWebViewCreatedWithConfiguration:configuration]; 1625 return webController.webView; 1626} 1627 1628- (BOOL)UIHandler:(CRWWKUIHandler*)UIHandler 1629 isUserInitiatedAction:(WKNavigationAction*)action { 1630 return [self isUserInitiatedAction:action]; 1631} 1632 1633#pragma mark - WKNavigationDelegate Helpers 1634 1635// Called when a page has actually started loading (i.e., for 1636// a web page the document has actually changed), or after the load request has 1637// been registered for a non-document-changing URL change. Updates internal 1638// state not specific to web pages. 1639- (void)didStartLoading { 1640 self.navigationHandler.navigationState = web::WKNavigationState::STARTED; 1641 _displayStateOnStartLoading = self.pageDisplayState; 1642 1643 _userInteractionState.SetUserInteractionRegisteredSincePageLoaded(false); 1644 _pageHasZoomed = NO; 1645} 1646 1647#pragma mark - CRWSSLStatusUpdaterDataSource 1648 1649- (void)SSLStatusUpdater:(CRWSSLStatusUpdater*)SSLStatusUpdater 1650 querySSLStatusForTrust:(base::ScopedCFTypeRef<SecTrustRef>)trust 1651 host:(NSString*)host 1652 completionHandler:(StatusQueryHandler)completionHandler { 1653 [_certVerificationController querySSLStatusForTrust:std::move(trust) 1654 host:host 1655 completionHandler:completionHandler]; 1656} 1657 1658#pragma mark - CRWSSLStatusUpdaterDelegate 1659 1660- (void)SSLStatusUpdater:(CRWSSLStatusUpdater*)SSLStatusUpdater 1661 didChangeSSLStatusForNavigationItem:(web::NavigationItem*)navigationItem { 1662 web::NavigationItem* visibleItem = 1663 self.webStateImpl->GetNavigationManager()->GetVisibleItem(); 1664 if (navigationItem == visibleItem) 1665 self.webStateImpl->DidChangeVisibleSecurityState(); 1666} 1667 1668#pragma mark - CRWContextMenuDelegate methods 1669 1670- (void)webView:(WKWebView*)webView 1671 handleContextMenu:(const web::ContextMenuParams&)params { 1672 DCHECK(webView == self.webView); 1673 if (_isBeingDestroyed) { 1674 return; 1675 } 1676 self.webStateImpl->HandleContextMenu(params); 1677} 1678 1679- (void)webView:(WKWebView*)webView 1680 executeJavaScript:(NSString*)javaScript 1681 completionHandler:(void (^)(id, NSError*))completionHandler { 1682 [_jsInjector executeJavaScript:javaScript 1683 completionHandler:completionHandler]; 1684} 1685 1686#pragma mark - CRWJSInjectorDelegate methods 1687 1688- (GURL)lastCommittedURLForJSInjector:(CRWJSInjector*)injector { 1689 return self.webState->GetLastCommittedURL(); 1690} 1691 1692- (void)willExecuteUserScriptForJSInjector:(CRWJSInjector*)injector { 1693 [self touched:YES]; 1694} 1695 1696#pragma mark - KVO Observation 1697 1698- (void)observeValueForKeyPath:(NSString*)keyPath 1699 ofObject:(id)object 1700 change:(NSDictionary*)change 1701 context:(void*)context { 1702 DCHECK(!self.beingDestroyed); 1703 NSString* dispatcherSelectorName = self.WKWebViewObservers[keyPath]; 1704 DCHECK(dispatcherSelectorName); 1705 if (dispatcherSelectorName) { 1706 // With ARC memory management, it is not known what a method called 1707 // via a selector will return. If a method returns a retained value 1708 // (e.g. NS_RETURNS_RETAINED) that returned object will leak as ARC is 1709 // unable to property insert the correct release calls for it. 1710 // All selectors used here return void and take no parameters so it's safe 1711 // to call a function mapping to the method implementation manually. 1712 SEL selector = NSSelectorFromString(dispatcherSelectorName); 1713 IMP methodImplementation = [self methodForSelector:selector]; 1714 if (methodImplementation) { 1715 void (*methodCallFunction)(id, SEL) = 1716 reinterpret_cast<void (*)(id, SEL)>(methodImplementation); 1717 methodCallFunction(self, selector); 1718 } 1719 } 1720} 1721 1722// Called when WKWebView certificateChain or hasOnlySecureContent property has 1723// changed. 1724- (void)webViewSecurityFeaturesDidChange { 1725 if (self.navigationHandler.navigationState == 1726 web::WKNavigationState::REQUESTED) { 1727 // Do not update SSL Status for pending load. It will be updated in 1728 // |webView:didCommitNavigation:| callback. 1729 return; 1730 } 1731 web::NavigationItem* item = 1732 self.webStateImpl->GetNavigationManager()->GetLastCommittedItem(); 1733 // SSLStatus is manually set in CRWWKNavigationHandler for SSL errors, so 1734 // skip calling the update method in these cases. 1735 if (item && !net::IsCertStatusError(item->GetSSL().cert_status)) { 1736 [self updateSSLStatusForCurrentNavigationItem]; 1737 } 1738} 1739 1740// Called when WKWebView title has been changed. 1741- (void)webViewTitleDidChange { 1742 // WKWebView's title becomes empty when the web process dies; ignore that 1743 // update. 1744 if (self.navigationHandler.webProcessCrashed) { 1745 DCHECK_EQ(self.webView.title.length, 0U); 1746 return; 1747 } 1748 1749 web::WKNavigationState lastNavigationState = 1750 [self.navigationHandler.navigationStates lastAddedNavigationState]; 1751 bool hasPendingNavigation = 1752 lastNavigationState == web::WKNavigationState::REQUESTED || 1753 lastNavigationState == web::WKNavigationState::STARTED || 1754 lastNavigationState == web::WKNavigationState::REDIRECTED; 1755 1756 if (!hasPendingNavigation && 1757 (base::FeatureList::IsEnabled(web::features::kUseJSForErrorPage) || 1758 !IsPlaceholderUrl(net::GURLWithNSURL(self.webView.URL)))) { 1759 // Do not update the title if there is a navigation in progress because 1760 // there is no way to tell if KVO change fired for new or previous page. 1761 [self.navigationHandler 1762 setLastCommittedNavigationItemTitle:self.webView.title]; 1763 } 1764} 1765 1766#pragma mark - CRWWebViewHandlerDelegate 1767 1768- (web::WebStateImpl*)webStateImplForWebViewHandler: 1769 (CRWWebViewHandler*)handler { 1770 return self.webStateImpl; 1771} 1772 1773- (const GURL&)documentURLForWebViewHandler:(CRWWebViewHandler*)handler { 1774 return _documentURL; 1775} 1776 1777- (web::UserInteractionState*)userInteractionStateForWebViewHandler: 1778 (CRWWebViewHandler*)handler { 1779 return &_userInteractionState; 1780} 1781 1782- (void)webViewHandlerUpdateSSLStatusForCurrentNavigationItem: 1783 (CRWWebViewHandler*)handler { 1784 [self updateSSLStatusForCurrentNavigationItem]; 1785} 1786 1787- (void)webViewHandler:(CRWWebViewHandler*)handler 1788 didFinishNavigation:(web::NavigationContextImpl*)context { 1789 [self didFinishNavigation:context]; 1790} 1791 1792- (void)ensureWebViewCreatedForWebViewHandler:(CRWWebViewHandler*)handler { 1793 [self ensureWebViewCreated]; 1794} 1795 1796- (WKWebView*)webViewForWebViewHandler:(CRWWebViewHandler*)handler { 1797 return self.webView; 1798} 1799 1800#pragma mark - CRWWebViewNavigationObserverDelegate 1801 1802- (CRWWKNavigationHandler*)navigationHandlerForNavigationObserver: 1803 (CRWWebViewNavigationObserver*)navigationObserver { 1804 return self.navigationHandler; 1805} 1806 1807- (void)navigationObserver:(CRWWebViewNavigationObserver*)navigationObserver 1808 didChangeDocumentURL:(const GURL&)documentURL 1809 forContext:(web::NavigationContextImpl*)context { 1810 [self setDocumentURL:documentURL context:context]; 1811} 1812 1813- (void)navigationObserver:(CRWWebViewNavigationObserver*)navigationObserver 1814 didChangePageWithContext:(web::NavigationContextImpl*)context { 1815 [self.navigationHandler webPageChangedWithContext:context 1816 webView:self.webView]; 1817} 1818 1819- (void)navigationObserver:(CRWWebViewNavigationObserver*)navigationObserver 1820 didLoadNewURL:(const GURL&)webViewURL 1821 forSameDocumentNavigation:(BOOL)isSameDocumentNavigation { 1822 BOOL isPlaceholderURL = 1823 base::FeatureList::IsEnabled(web::features::kUseJSForErrorPage) 1824 ? NO 1825 : IsPlaceholderUrl(webViewURL); 1826 std::unique_ptr<web::NavigationContextImpl> newContext = 1827 [_requestController registerLoadRequestForURL:webViewURL 1828 sameDocumentNavigation:isSameDocumentNavigation 1829 hasUserGesture:NO 1830 rendererInitiated:YES 1831 placeholderNavigation:isPlaceholderURL]; 1832 [self.navigationHandler webPageChangedWithContext:newContext.get() 1833 webView:self.webView]; 1834 newContext->SetHasCommitted(!isSameDocumentNavigation); 1835 self.webStateImpl->OnNavigationFinished(newContext.get()); 1836 // TODO(crbug.com/792515): It is OK, but very brittle, to call 1837 // |didFinishNavigation:| here because the gating condition is mutually 1838 // exclusive with the condition below. Refactor this method after 1839 // deprecating self.navigationHandler.pendingNavigationInfo. 1840 if (newContext->GetWKNavigationType() == WKNavigationTypeBackForward) { 1841 [self didFinishNavigation:newContext.get()]; 1842 } 1843} 1844 1845- (void)navigationObserver:(CRWWebViewNavigationObserver*)navigationObserver 1846 URLDidChangeWithoutDocumentChange:(const GURL&)newURL { 1847 DCHECK(newURL == net::GURLWithNSURL(self.webView.URL)); 1848 1849 if (base::FeatureList::IsEnabled( 1850 web::features::kCrashOnUnexpectedURLChange)) { 1851 if (_documentURL.GetOrigin() != newURL.GetOrigin()) { 1852 if (!_documentURL.host().empty() && 1853 (newURL.username().find(_documentURL.host()) != std::string::npos || 1854 newURL.password().find(_documentURL.host()) != std::string::npos)) { 1855 CHECK(false); 1856 } 1857 } 1858 } 1859 1860 // Is it ok that newURL can be restore session URL? 1861 if (!IsRestoreSessionUrl(_documentURL) && !IsRestoreSessionUrl(newURL)) { 1862 bool ignore_host_change = 1863 // On iOS13 document.write() can change URL origin for about:blank page. 1864 (_documentURL.IsAboutBlank() && base::ios::IsRunningOnIOS13OrLater() && 1865 !self.webView.loading); 1866 if (!ignore_host_change) { 1867 DCHECK_EQ(_documentURL.host(), newURL.host()); 1868 } 1869 } 1870 DCHECK(_documentURL != newURL); 1871 1872 // If called during window.history.pushState or window.history.replaceState 1873 // JavaScript evaluation, only update the document URL. This callback does not 1874 // have any information about the state object and cannot create (or edit) the 1875 // navigation entry for this page change. Web controller will sync with 1876 // history changes when a window.history.didPushState or 1877 // window.history.didReplaceState message is received, which should happen in 1878 // the next runloop. 1879 // 1880 // Otherwise, simulate the whole delegate flow for a load (since the 1881 // superclass currently doesn't have a clean separation between URL changes 1882 // and document changes). Note that the order of these calls is important: 1883 // registering a load request logically comes before updating the document 1884 // URL, but also must come first since it uses state that is reset on URL 1885 // changes. 1886 1887 // |newNavigationContext| only exists if this method has to create a new 1888 // context object. 1889 std::unique_ptr<web::NavigationContextImpl> newNavigationContext; 1890 if (!self.JSNavigationHandler.changingHistoryState) { 1891 if ([self.navigationHandler 1892 contextForPendingMainFrameNavigationWithURL:newURL]) { 1893 // NavigationManager::LoadURLWithParams() was called with URL that has 1894 // different fragment comparing to the previous URL. 1895 } else { 1896 // This could be: 1897 // 1.) Renderer-initiated fragment change 1898 // 2.) Assigning same-origin URL to window.location 1899 // 3.) Incorrectly handled window.location.replace (crbug.com/307072) 1900 // 4.) Back-forward same document navigation 1901 newNavigationContext = 1902 [_requestController registerLoadRequestForURL:newURL 1903 sameDocumentNavigation:YES 1904 hasUserGesture:NO 1905 rendererInitiated:YES 1906 placeholderNavigation:NO]; 1907 } 1908 } 1909 1910 [self setDocumentURL:newURL context:newNavigationContext.get()]; 1911 1912 if (!self.JSNavigationHandler.changingHistoryState) { 1913 // Pass either newly created context (if it exists) or context that already 1914 // existed before. 1915 web::NavigationContextImpl* navigationContext = newNavigationContext.get(); 1916 if (!navigationContext) { 1917 navigationContext = [self.navigationHandler 1918 contextForPendingMainFrameNavigationWithURL:newURL]; 1919 } 1920 navigationContext->SetIsSameDocument(true); 1921 self.webStateImpl->OnNavigationStarted(navigationContext); 1922 [self didStartLoading]; 1923 self.navigationManagerImpl->CommitPendingItem( 1924 navigationContext->ReleaseItem()); 1925 navigationContext->SetHasCommitted(true); 1926 self.webStateImpl->OnNavigationFinished(navigationContext); 1927 1928 [self updateSSLStatusForCurrentNavigationItem]; 1929 [self didFinishNavigation:navigationContext]; 1930 } 1931} 1932 1933#pragma mark - CRWWKNavigationHandlerDelegate 1934 1935- (CRWJSInjector*)JSInjectorForNavigationHandler: 1936 (CRWWKNavigationHandler*)navigationHandler { 1937 return self.jsInjector; 1938} 1939 1940- (CRWCertVerificationController*) 1941 certVerificationControllerForNavigationHandler: 1942 (CRWWKNavigationHandler*)navigationHandler { 1943 return _certVerificationController; 1944} 1945 1946- (void)navigationHandler:(CRWWKNavigationHandler*)navigationHandler 1947 createWebUIForURL:(const GURL&)URL { 1948 [_requestController createWebUIForURL:URL]; 1949} 1950 1951- (void)navigationHandler:(CRWWKNavigationHandler*)navigationHandler 1952 setDocumentURL:(const GURL&)newURL 1953 context:(web::NavigationContextImpl*)context { 1954 [self setDocumentURL:newURL context:context]; 1955} 1956 1957- (std::unique_ptr<web::NavigationContextImpl>) 1958 navigationHandler:(CRWWKNavigationHandler*)navigationHandler 1959 registerLoadRequestForURL:(const GURL&)URL 1960 sameDocumentNavigation:(BOOL)sameDocumentNavigation 1961 hasUserGesture:(BOOL)hasUserGesture 1962 rendererInitiated:(BOOL)renderedInitiated 1963 placeholderNavigation:(BOOL)placeholderNavigation { 1964 return [_requestController registerLoadRequestForURL:URL 1965 sameDocumentNavigation:sameDocumentNavigation 1966 hasUserGesture:hasUserGesture 1967 rendererInitiated:renderedInitiated 1968 placeholderNavigation:placeholderNavigation]; 1969} 1970 1971- (void)navigationHandlerDisplayWebView: 1972 (CRWWKNavigationHandler*)navigationHandler { 1973 [self displayWebView]; 1974} 1975 1976- (void)navigationHandlerDidStartLoading: 1977 (CRWWKNavigationHandler*)navigationHandler { 1978 [self didStartLoading]; 1979} 1980 1981- (void)navigationHandlerWebProcessDidCrash: 1982 (CRWWKNavigationHandler*)navigationHandler { 1983 self.webStateImpl->CancelDialogs(); 1984 self.webStateImpl->OnRenderProcessGone(); 1985} 1986 1987- (void)navigationHandler:(CRWWKNavigationHandler*)navigationHandler 1988 loadCurrentURLWithRendererInitiatedNavigation:(BOOL)rendererInitiated { 1989 [self loadCurrentURLWithRendererInitiatedNavigation:rendererInitiated]; 1990} 1991 1992- (void)navigationHandler:(CRWWKNavigationHandler*)navigationHandler 1993 didCompleteLoadWithSuccess:(BOOL)loadSuccess 1994 forContext:(web::NavigationContextImpl*)context { 1995 [self loadCompleteWithSuccess:loadSuccess forContext:context]; 1996} 1997 1998#pragma mark - CRWWebRequestControllerDelegate 1999 2000- (void)webRequestControllerStopLoading: 2001 (CRWWebRequestController*)requestController { 2002 [self stopLoading]; 2003} 2004 2005- (void)webRequestControllerDidStartLoading: 2006 (CRWWebRequestController*)requestController { 2007 [self didStartLoading]; 2008} 2009 2010- (void)webRequestController:(CRWWebRequestController*)requestController 2011 didCompleteLoadWithSuccess:(BOOL)loadSuccess 2012 forContext:(web::NavigationContextImpl*)context { 2013 [self loadCompleteWithSuccess:loadSuccess forContext:context]; 2014} 2015 2016- (void)webRequestControllerDisableNavigationGesturesUntilFinishNavigation: 2017 (CRWWebRequestController*)requestController { 2018 // Disable |allowsBackForwardNavigationGestures| during restore. Otherwise, 2019 // WebKit will trigger a snapshot for each (blank) page, and quickly 2020 // overload system memory. 2021 self.webView.allowsBackForwardNavigationGestures = NO; 2022} 2023 2024- (void)webRequestControllerRestoreStateFromHistory: 2025 (CRWWebRequestController*)requestController { 2026 [self restoreStateFromHistory]; 2027} 2028 2029- (CRWWKNavigationHandler*)webRequestControllerNavigationHandler: 2030 (CRWWebRequestController*)requestController { 2031 return self.navigationHandler; 2032} 2033 2034#pragma mark - CRWJSNavigationHandlerDelegate 2035 2036- (GURL)currentURLForJSNavigationHandler: 2037 (CRWJSNavigationHandler*)navigationHandler { 2038 return self.currentURL; 2039} 2040 2041- (void)JSNavigationHandlerUpdateSSLStatusForCurrentNavigationItem: 2042 (CRWJSNavigationHandler*)navigationHandler { 2043 [self updateSSLStatusForCurrentNavigationItem]; 2044} 2045 2046- (void)JSNavigationHandlerOptOutScrollsToTopForSubviews: 2047 (CRWJSNavigationHandler*)navigationHandler { 2048 return [self optOutScrollsToTopForSubviews]; 2049} 2050 2051#pragma mark - UIDropInteractionDelegate 2052 2053- (BOOL)dropInteraction:(UIDropInteraction*)interaction 2054 canHandleSession:(id<UIDropSession>)session { 2055 return session.items.count == 1U && 2056 [session canLoadObjectsOfClass:[NSURL class]]; 2057} 2058 2059- (UIDropProposal*)dropInteraction:(UIDropInteraction*)interaction 2060 sessionDidUpdate:(id<UIDropSession>)session { 2061 return [[UIDropProposal alloc] initWithDropOperation:UIDropOperationCopy]; 2062} 2063 2064- (void)dropInteraction:(UIDropInteraction*)interaction 2065 performDrop:(id<UIDropSession>)session { 2066 DCHECK_EQ(1U, session.items.count); 2067 if ([session canLoadObjectsOfClass:[NSURL class]]) { 2068 __weak CRWWebController* weakSelf = self; 2069 [session loadObjectsOfClass:[NSURL class] 2070 completion:^(NSArray<NSURL*>* objects) { 2071 GURL URL = net::GURLWithNSURL([objects firstObject]); 2072 if (!_isBeingDestroyed && URL.is_valid()) { 2073 web::NavigationManager::WebLoadParams params(URL); 2074 params.transition_type = ui::PAGE_TRANSITION_TYPED; 2075 weakSelf.webStateImpl->GetNavigationManager() 2076 ->LoadURLWithParams(params); 2077 } 2078 }]; 2079 } 2080} 2081 2082#pragma mark - Testing-Only Methods 2083 2084- (void)injectWebViewContentView:(CRWWebViewContentView*)webViewContentView { 2085 _currentURLLoadWasTrigerred = NO; 2086 [self removeWebView]; 2087 2088 [_containerView displayWebViewContentView:webViewContentView]; 2089 [self setWebView:static_cast<WKWebView*>(webViewContentView.webView)]; 2090} 2091 2092- (void)resetInjectedWebViewContentView { 2093 _currentURLLoadWasTrigerred = NO; 2094 [self setWebView:nil]; 2095 [_containerView removeFromSuperview]; 2096 _containerView = nil; 2097} 2098 2099- (web::WKNavigationState)navigationState { 2100 return self.navigationHandler.navigationState; 2101} 2102 2103@end 2104