blob: 51d93f92455693ffd3ee53318319e79e79a5967b [file] [log] [blame]
// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/web/web_state/ui/crw_web_controller.h"
#import <WebKit/WebKit.h>
#import "base/ios/block_types.h"
#include "base/ios/ios_util.h"
#include "base/json/string_escape.h"
#include "base/mac/foundation_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/strings/sys_string_conversions.h"
#import "ios/web/browsing_data/browsing_data_remover.h"
#import "ios/web/browsing_data/browsing_data_remover_observer.h"
#import "ios/web/common/crw_web_view_content_view.h"
#include "ios/web/common/features.h"
#include "ios/web/common/url_util.h"
#import "ios/web/favicon/favicon_manager.h"
#import "ios/web/find_in_page/find_in_page_manager_impl.h"
#include "ios/web/history_state_util.h"
#import "ios/web/js_messaging/crw_js_injector.h"
#import "ios/web/js_messaging/crw_wk_script_message_router.h"
#import "ios/web/js_messaging/web_frames_manager_impl.h"
#import "ios/web/js_messaging/web_view_js_utils.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/deprecated/crw_context_menu_delegate.h"
#import "ios/web/public/deprecated/crw_native_content_provider.h"
#include "ios/web/public/js_messaging/web_frame_util.h"
#import "ios/web/public/ui/crw_web_view_scroll_view_proxy.h"
#import "ios/web/public/ui/page_display_state.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/web_state/page_viewport_state.h"
#import "ios/web/web_state/ui/controller/crw_legacy_native_content_controller.h"
#import "ios/web/web_state/ui/controller/crw_legacy_native_content_controller_delegate.h"
#import "ios/web/web_state/ui/crw_context_menu_controller.h"
#import "ios/web/web_state/ui/crw_swipe_recognizer_provider.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/js_window_error_manager.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/mac/url_conversions.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using web::NavigationManager;
using web::NavigationManagerImpl;
using web::WebState;
using web::WebStateImpl;
using web::wk_navigation_util::IsPlaceholderUrl;
using web::wk_navigation_util::ExtractUrlFromPlaceholderUrl;
using web::wk_navigation_util::IsRestoreSessionUrl;
using web::wk_navigation_util::IsWKInternalUrl;
namespace {
// Keys for JavaScript command handlers context.
NSString* const kUserIsInteractingKey = @"userIsInteracting";
NSString* const kOriginURLKey = @"originURL";
NSString* const kIsMainFrame = @"isMainFrame";
// URL scheme for messages sent from javascript for asynchronous processing.
NSString* const kScriptMessageName = @"crwebinvoke";
} // namespace
@interface CRWWebController () <BrowsingDataRemoverObserver,
CRWWKNavigationHandlerDelegate,
CRWContextMenuDelegate,
CRWJSInjectorDelegate,
CRWLegacyNativeContentControllerDelegate,
CRWSSLStatusUpdaterDataSource,
CRWSSLStatusUpdaterDelegate,
CRWWebControllerContainerViewDelegate,
CRWWebViewNavigationObserverDelegate,
CRWWebRequestControllerDelegate,
CRWJSNavigationHandlerDelegate,
CRWWebViewScrollViewProxyObserver,
CRWWKNavigationHandlerDelegate,
CRWWKUIHandlerDelegate,
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/549616): Remove this in favor of just updating the
// navigation manager and treating that as authoritative.
GURL _documentURL;
// The web::PageDisplayState recorded when the page starts loading.
web::PageDisplayState _displayStateOnStartLoading;
// Whether or not the page has zoomed since the current navigation has been
// committed, either by user interaction or via |-restoreStateFromHistory|.
BOOL _pageHasZoomed;
// Whether a PageDisplayState is currently being applied.
BOOL _applyingPageState;
// 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;
// Manager for favicon JavaScript messages.
std::unique_ptr<web::FaviconManager> _faviconManager;
// Manager for window.error message.
std::unique_ptr<web::JsWindowErrorManager> _jsWindowErrorManager;
}
// 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;
// The current page state of the web view. Writing to this property
// asynchronously applies the passed value to the current web view.
@property(nonatomic, readwrite) web::PageDisplayState pageDisplayState;
@property(nonatomic, strong)
CRWLegacyNativeContentController* legacyNativeController;
@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;
// The web view's view of the current URL. During page transitions
// this may not be the same as the session history's view of the current URL.
// This method can change the state of the CRWWebController, as it will display
// an error if the returned URL is not reliable from a security point of view.
// Note that this method is expensive, so it should always be cached locally if
// it's needed multiple times in a method.
@property(nonatomic, readonly) GURL currentURL;
// User agent type of the transient item if any, the pending item if a
// navigation is in progress or the last committed item otherwise.
// Returns MOBILE, the default type, if navigation manager is nullptr or empty.
@property(nonatomic, readonly) web::UserAgentType userAgentType;
@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;
// Session Information
// -------------------
// Returns NavigationManager's session controller.
@property(weak, nonatomic, readonly) CRWSessionController* sessionController;
// The associated NavigationManagerImpl.
@property(nonatomic, readonly) NavigationManagerImpl* navigationManagerImpl;
// TODO(crbug.com/692871): Remove these functions and replace with more
// appropriate NavigationItem getters.
// Returns the navigation item for the current page.
@property(nonatomic, readonly) web::NavigationItemImpl* currentNavItem;
// Returns the current URL of the web view, and sets |trustLevel| accordingly
// based on the confidence in the verification.
- (GURL)webURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel;
// 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;
// Called when web controller receives a new message from the web page.
- (void)didReceiveScriptMessage:(WKScriptMessage*)message;
// Attempts to handle a script message. Returns YES on success, NO otherwise.
- (BOOL)respondToWKScriptMessage:(WKScriptMessage*)scriptMessage;
// Restores the state for this page from session history.
- (void)restoreStateFromHistory;
// Extracts the current page's viewport tag information and calls |completion|.
// If the page has changed before the viewport tag is successfully extracted,
// |completion| is called with nullptr.
typedef void (^ViewportStateCompletion)(const web::PageViewportState*);
- (void)extractViewportTagWithCompletion:(ViewportStateCompletion)completion;
// Called by NSNotificationCenter upon orientation changes.
- (void)orientationDidChange;
// Queries the web view for the user-scalable meta tag and calls
// |-applyPageDisplayState:userScalable:| with the result.
- (void)applyPageDisplayState:(const web::PageDisplayState&)displayState;
// Restores state of the web view's scroll view from |scrollState|.
// |isUserScalable| represents the value of user-scalable meta tag.
- (void)applyPageDisplayState:(const web::PageDisplayState&)displayState
userScalable:(BOOL)isUserScalable;
// 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;
// Sets zoom scale value for webview scroll view from |zoomState|.
- (void)applyWebViewScrollZoomScaleFromZoomState:
(const web::PageZoomState&)zoomState;
// Sets scroll offset value for webview scroll view from |scrollState|.
- (void)applyWebViewScrollOffsetFromScrollState:
(const web::PageScrollState&)scrollState;
// 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;
if (web::GetWebClient()->IsSlimNavigationManagerEnabled())
_allowsBackForwardNavigationGestures = YES;
DCHECK(_webStateImpl);
// Content area is lazily instantiated.
_defaultURL = GURL(url::kAboutBlankURL);
_jsInjector = [[CRWJSInjector alloc] initWithDelegate:self];
_requestController =
[[CRWWebRequestController alloc] initWithWebState:_webStateImpl];
_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::BrowsingDataRemover::FromBrowserState(browserState)->AddObserver(self);
web::FindInPageManagerImpl::CreateForWebState(_webStateImpl);
_faviconManager = std::make_unique<web::FaviconManager>(_webStateImpl);
_jsWindowErrorManager =
std::make_unique<web::JsWindowErrorManager>(_webStateImpl);
_legacyNativeController =
[[CRWLegacyNativeContentController alloc] initWithWebState:webState];
_legacyNativeController.delegate = self;
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(orientationDidChange)
name:UIApplicationDidChangeStatusBarOrientationNotification
object:nil];
_navigationHandler = [[CRWWKNavigationHandler alloc] initWithDelegate:self];
_JSNavigationHandler =
[[CRWJSNavigationHandler alloc] initWithDelegate:self];
_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 {
[self.legacyNativeController
setNativeControllerWebUsageEnabled:_webUsageEnabled];
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 {
self.webStateImpl->ClearTransientContent();
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;
}
- (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;
CRWWKScriptMessageRouter* messageRouter =
[self webViewConfigurationProvider].GetScriptMessageRouter();
self.webStateImpl->GetWebFramesManagerImpl().OnWebViewUpdated(
_webView, webView, messageRouter);
if (_webView) {
[_webView stopLoading];
[_webView removeFromSuperview];
}
// 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];
}
__weak CRWWebController* weakSelf = self;
[messageRouter
setScriptMessageHandler:^(WKScriptMessage* message) {
[weakSelf didReceiveScriptMessage:message];
}
name:kScriptMessageName
webView:_webView];
_webView.allowsBackForwardNavigationGestures =
_allowsBackForwardNavigationGestures;
}
[_jsInjector setWebView:_webView];
self.webViewNavigationObserver.webView = _webView;
[self setDocumentURL:_defaultURL context:nullptr];
}
- (UIScrollView*)webScrollView {
return self.webView.scrollView;
}
- (web::PageDisplayState)pageDisplayState {
web::PageDisplayState displayState;
// If a native controller is present, record its display state instead of that
// of the underlying placeholder webview.
if ([self.legacyNativeController hasController]) {
displayState.scroll_state().set_content_offset(
[self.legacyNativeController contentOffset]);
displayState.scroll_state().set_content_inset(
[self.legacyNativeController contentInset]);
} else if (self.webView) {
displayState.set_scroll_state(web::PageScrollState(
self.scrollPosition, self.webScrollView.contentInset));
UIScrollView* scrollView = self.webScrollView;
displayState.zoom_state().set_minimum_zoom_scale(
scrollView.minimumZoomScale);
displayState.zoom_state().set_maximum_zoom_scale(
scrollView.maximumZoomScale);
displayState.zoom_state().set_zoom_scale(scrollView.zoomScale);
}
return displayState;
}
- (void)setPageDisplayState:(web::PageDisplayState)displayState {
if (!displayState.IsValid())
return;
if (self.webView) {
// Page state is restored after a page load completes. If the user has
// scrolled or changed the zoom scale while the page is still loading, don't
// restore any state since it will confuse the user.
web::PageDisplayState currentPageDisplayState = self.pageDisplayState;
if (currentPageDisplayState.scroll_state() ==
_displayStateOnStartLoading.scroll_state() &&
!_pageHasZoomed) {
[self applyPageDisplayState:displayState];
}
}
}
- (NSDictionary*)WKWebViewObservers {
return @{
@"serverTrust" : @"webViewSecurityFeaturesDidChange",
@"hasOnlySecureContent" : @"webViewSecurityFeaturesDidChange",
@"title" : @"webViewTitleDidChange",
};
}
- (GURL)currentURL {
web::URLVerificationTrustLevel trustLevel =
web::URLVerificationTrustLevel::kNone;
return [self currentURLWithTrustLevel:&trustLevel];
}
- (web::UserAgentType)userAgentType {
web::NavigationItem* item = self.currentNavItem;
return item ? item->GetUserAgentType() : web::UserAgentType::MOBILE;
}
- (WebState*)webState {
return _webStateImpl;
}
- (CGPoint)scrollPosition {
return self.webScrollView.contentOffset;
}
- (CRWTouchTrackingRecognizer*)touchTrackingRecognizer {
if (!_touchTrackingRecognizer) {
_touchTrackingRecognizer =
[[CRWTouchTrackingRecognizer alloc] initWithTouchTrackingDelegate:self];
}
return _touchTrackingRecognizer;
}
#pragma mark Navigation and Session Information
- (CRWSessionController*)sessionController {
NavigationManagerImpl* navigationManager = self.navigationManagerImpl;
return navigationManager ? navigationManager->GetSessionController() : nil;
}
- (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();
}
- (void)showTransientContentView:(UIView<CRWScrollableContent>*)contentView {
DCHECK(contentView);
DCHECK(contentView.scrollView);
// TODO(crbug.com/556848) Reenable DCHECK when |CRWWebControllerContainerView|
// is restructured so that subviews are not added during |layoutSubviews|.
// DCHECK([contentView.scrollView isDescendantOfView:contentView]);
[_containerView displayTransientContent:contentView];
}
- (void)clearTransientContentView {
// Early return if there is no transient content view.
if (![_containerView transientContentView])
return;
// Remove the transient content view from the hierarchy.
[_containerView clearTransientContentView];
}
// 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.swipeRecognizerProvider = nil;
[self.legacyNativeController 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 resetContent];
_webStateImpl = nullptr;
DCHECK(!self.webView);
// TODO(crbug.com/662860): 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());
}
// Returns YES if the current live view is a web view with an image MIME type.
- (BOOL)contentIsImage {
return self.webView &&
web::IsContentTypeImage(self.webState->GetContentsMimeType());
}
- (GURL)currentURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel {
DCHECK(trustLevel) << "Verification of the trustLevel state is mandatory";
// The web view URL is the current URL only if it is neither a placeholder URL
// (used to hold WKBackForwardListItem for WebUI and Native Content views) nor
// a restore_session.html (used to replay session history in WKWebView).
// TODO(crbug.com/738020): Investigate if this method is still needed and if
// it can be implemented using NavigationManager API after removal of legacy
// navigation stack.
GURL webViewURL = net::GURLWithNSURL(self.webView.URL);
if (self.webView && !IsWKInternalUrl(webViewURL)) {
return [self webURLWithTrustLevel:trustLevel];
}
// Any non-web URL source is trusted.
*trustLevel = web::URLVerificationTrustLevel::kAbsolute;
if ([self.legacyNativeController hasController]) {
return [self.legacyNativeController URL];
}
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::EmptyGURL();
}
- (void)reloadWithRendererInitiatedNavigation:(BOOL)rendererInitiated {
// Clear last user interaction.
// TODO(crbug.com/546337): 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 and transient 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];
[self.legacyNativeController 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;
// WKBasedNavigationManagerImpl 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 (web::GetWebClient()->IsSlimNavigationManagerEnabled() &&
!_webUsageEnabled)
return;
_currentURLLoadWasTrigerred = YES;
[_requestController
loadCurrentURLWithRendererInitiatedNavigation:rendererInitiated];
}
- (void)loadCurrentURLIfNecessary {
if (self.navigationHandler.webProcessCrashed) {
[self loadCurrentURLWithRendererInitiatedNavigation:NO];
} else if (!_currentURLLoadWasTrigerred) {
[self ensureContainerViewCreated];
// This method reloads last committed item, so make than item also pending.
self.sessionController.pendingItemIndex =
self.sessionController.lastCommittedItemIndex;
// TODO(crbug.com/796608): 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];
}
// 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)requirePageReconstruction {
// TODO(crbug.com/736103): Removing web view will destroy session history for
// WKBasedNavigationManager.
if (!web::GetWebClient()->IsSlimNavigationManagerEnabled())
[self removeWebView];
}
- (void)recordStateInHistory {
// Only record the state if:
// - the current NavigationItem's URL matches the current URL, and
// - the user has interacted with the page.
web::NavigationItem* item = self.currentNavItem;
if (item && item->GetURL() == [self currentURL] &&
_userInteractionState.UserInteractionRegisteredSincePageLoaded()) {
item->SetPageDisplayState(self.pageDisplayState);
}
}
- (void)wasShown {
self.visible = YES;
[self.legacyNativeController wasShown];
}
- (void)wasHidden {
self.visible = NO;
if (_isBeingDestroyed)
return;
[self recordStateInHistory];
[self.legacyNativeController wasHidden];
}
- (id<CRWNativeContentHolder>)nativeContentHolder {
return self.legacyNativeController;
}
- (void)setKeepsRenderProcessAlive:(BOOL)keepsRenderProcessAlive {
_keepsRenderProcessAlive = keepsRenderProcessAlive;
[_containerView
updateWebViewContentViewForContainerWindow:_containerView.window];
}
- (void)didFinishGoToIndexSameDocumentNavigationWithType:
(web::NavigationInitiationType)type
hasUserGesture:(BOOL)hasUserGesture {
web::NavigationItem* item =
self.webStateImpl->GetNavigationManager()->GetLastCommittedItem();
GURL URL = item->GetVirtualURL();
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->SetIsSameDocument(true);
self.webStateImpl->SetIsLoading(true);
self.webStateImpl->OnNavigationStarted(context.get());
[self updateHTML5HistoryState];
[self setDocumentURL:URL context:context.get()];
context->SetHasCommitted(true);
self.webStateImpl->OnNavigationFinished(context.get());
self.navigationHandler.navigationState = web::WKNavigationState::FINISHED;
[_requestController didFinishWithURL:URL
loadSuccess:YES
context:context.get()];
}
- (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);
if (IsPlaceholderUrl(URL)) {
// No need to create navigation context for placeholder back forward
// navigations. Future callbacks do not expect that context will exist.
return;
}
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());
if (!navigation) {
// goToBackForwardListItem: returns nil for same-document back forward
// navigations.
context->SetIsSameDocument(true);
} else {
self.webStateImpl->SetIsLoading(true);
self.navigationHandler.navigationState = web::WKNavigationState::REQUESTED;
}
web::WKBackForwardListItemHolder* holder =
web::WKBackForwardListItemHolder::FromNavigationItem(item);
holder->set_navigation_type(WKNavigationTypeBackForward);
context->SetIsPost((holder && [holder->http_method() isEqual:@"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);
});
}
WKSnapshotConfiguration* configuration =
[[WKSnapshotConfiguration alloc] init];
configuration.rect = [self.webView convertRect:rect fromView:self.view];
__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: "
<< error.description;
}
completion(nil);
} else {
completion(snapshot);
}
}];
}
#pragma mark - CRWSessionControllerDelegate (Public)
- (web::NavigationItemImpl*)pendingItemForSessionController:
(CRWSessionController*)sessionController {
return [self lastPendingItemForNewNavigation];
}
#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));
}
}
#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 (web::GetWebClient()->IsSlimNavigationManagerEnabled() && context &&
!context->IsLoadingHtmlString() && !context->IsLoadingErrorPage() &&
!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 (base::ios::IsRunningOnIOS13OrLater() && oldDocumentURL.IsAboutBlank() &&
!self.webStateImpl->GetNavigationManager()->GetLastCommittedItem() &&
!self.webView.loading) {
return;
}
GURL documentOrigin = newURL.GetOrigin();
web::NavigationItem* committedItem =
self.webStateImpl->GetNavigationManager()->GetLastCommittedItem();
GURL committedOrigin =
committedItem ? committedItem->GetURL().GetOrigin() : GURL::EmptyGURL();
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());
}
}
}
- (GURL)webURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel {
DCHECK(trustLevel);
*trustLevel = web::URLVerificationTrustLevel::kAbsolute;
// Placeholder URL is an implementation detail. Don't expose it to users of
// web layer.
if (IsPlaceholderUrl(_documentURL))
return ExtractUrlFromPlaceholderUrl(_documentURL);
return _documentURL;
}
- (BOOL)isUserInitiatedAction:(WKNavigationAction*)action {
return _userInteractionState.IsUserInteracting(self.webView);
}
#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;
// Restore allowsBackForwardNavigationGestures once restoration is complete.
if (web::GetWebClient()->IsSlimNavigationManagerEnabled() &&
!self.navigationManagerImpl->IsRestoreSessionInProgress()) {
if (_webView.allowsBackForwardNavigationGestures !=
_allowsBackForwardNavigationGestures) {
_webView.allowsBackForwardNavigationGestures =
_allowsBackForwardNavigationGestures;
}
}
BOOL success = !context || !context->GetError();
[self loadCompleteWithSuccess:success forContext:context];
}
- (void)loadCompleteWithSuccess:(BOOL)loadSuccess
forContext:(web::NavigationContextImpl*)context {
// The webView may have been torn down (or replaced by a native view). 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 - BrowsingDataRemoverObserver
- (void)willRemoveBrowsingData:(web::BrowsingDataRemover*)dataRemover {
self.webUsageEnabled = NO;
}
- (void)didRemoveBrowsingData:(web::BrowsingDataRemover*)dataRemover {
self.webUsageEnabled = YES;
}
#pragma mark - JavaScript history manipulation
// Updates the HTML5 history state of the page using the current NavigationItem.
// For same-document navigations and navigations affected by
// window.history.[push/replace]State(), the URL and serialized state object
// will be updated to the current NavigationItem's values. A popState event
// will be triggered for all same-document navigations. Additionally, a
// hashchange event will be triggered for same-document navigations where the
// only difference between the current and previous URL is the fragment.
// TODO(crbug.com/788465): Verify that the history state management here are not
// needed for WKBasedNavigationManagerImpl and delete this method. The
// OnNavigationItemCommitted() call is likely the only thing that needs to be
// retained.
- (void)updateHTML5HistoryState {
web::NavigationItemImpl* currentItem = self.currentNavItem;
if (!currentItem)
return;
// Same-document navigations must trigger a popState event.
CRWSessionController* sessionController = self.sessionController;
BOOL sameDocumentNavigation = [sessionController
isSameDocumentNavigationBetweenItem:sessionController.currentItem
andItem:sessionController.previousItem];
// WKWebView doesn't send hashchange events for same-document non-BFLI
// navigations, so one must be dispatched manually for hash change same-
// document navigations.
const GURL URL = currentItem->GetURL();
web::NavigationItem* previousItem = self.sessionController.previousItem;
const GURL oldURL = previousItem ? previousItem->GetURL() : GURL();
BOOL shouldDispatchHashchange = sameDocumentNavigation && previousItem &&
(web::GURLByRemovingRefFromGURL(URL) ==
web::GURLByRemovingRefFromGURL(oldURL));
// The URL and state object must be set for same-document navigations and
// NavigationItems that were created or updated by calls to pushState() or
// replaceState().
BOOL shouldUpdateState = sameDocumentNavigation ||
currentItem->IsCreatedFromPushState() ||
currentItem->HasStateBeenReplaced();
if (!shouldUpdateState)
return;
// TODO(stuartmorgan): Make CRWSessionController manage this internally (or
// remove it; it's not clear this matches other platforms' behavior).
self.navigationManagerImpl->OnNavigationItemCommitted();
// Record that a same-document hashchange event will be fired. This flag will
// be reset when resonding to the hashchange message. Note that resetting the
// flag in the completion block below is too early, as that block is called
// before hashchange event listeners have a chance to fire.
self.JSNavigationHandler.dispatchingSameDocumentHashChangeEvent =
shouldDispatchHashchange;
// Inject the JavaScript to update the state on the browser side.
[self injectHTML5HistoryScriptWithHashChange:shouldDispatchHashchange
sameDocumentNavigation:sameDocumentNavigation];
}
// Generates the JavaScript string used to manually dispatch a popstate event,
// using |stateObjectJSON| as the event parameter.
- (NSString*)javaScriptToDispatchPopStateWithObject:(NSString*)stateObjectJSON {
std::string outState;
base::EscapeJSONString(base::SysNSStringToUTF8(stateObjectJSON), true,
&outState);
return [NSString stringWithFormat:@"__gCrWeb.dispatchPopstateEvent(%@);",
base::SysUTF8ToNSString(outState)];
}
// Generates the JavaScript string used to manually dispatch a hashchange event,
// using |oldURL| and |newURL| as the event parameters.
- (NSString*)javaScriptToDispatchHashChangeWithOldURL:(const GURL&)oldURL
newURL:(const GURL&)newURL {
return [NSString
stringWithFormat:@"__gCrWeb.dispatchHashchangeEvent(\'%s\', \'%s\');",
oldURL.spec().c_str(), newURL.spec().c_str()];
}
// Injects JavaScript to update the URL and state object of the webview to the
// values found in the current NavigationItem. A hashchange event will be
// dispatched if |dispatchHashChange| is YES, and a popstate event will be
// dispatched if |sameDocument| is YES. Upon the script's completion, resets
// |urlOnStartLoading_| and |_lastRegisteredRequestURL| to the current
// NavigationItem's URL. This is necessary so that sites that depend on URL
// params/fragments continue to work correctly and that checks for the URL don't
// incorrectly trigger |-webPageChangedWithContext| calls.
- (void)injectHTML5HistoryScriptWithHashChange:(BOOL)dispatchHashChange
sameDocumentNavigation:(BOOL)sameDocumentNavigation {
web::NavigationItemImpl* currentItem = self.currentNavItem;
if (!currentItem)
return;
const GURL URL = currentItem->GetURL();
NSString* stateObject = currentItem->GetSerializedStateObject();
NSMutableString* script = [NSMutableString
stringWithString:[self.JSNavigationHandler
javaScriptToReplaceWebViewURL:URL
stateObjectJSON:stateObject]];
if (sameDocumentNavigation) {
[script
appendString:[self javaScriptToDispatchPopStateWithObject:stateObject]];
}
if (dispatchHashChange) {
web::NavigationItemImpl* previousItem = self.sessionController.previousItem;
const GURL oldURL = previousItem ? previousItem->GetURL() : GURL();
[script appendString:[self javaScriptToDispatchHashChangeWithOldURL:oldURL
newURL:URL]];
}
[_jsInjector executeJavaScript:script completionHandler:nil];
}
#pragma mark - CRWLegacyNativeContentControllerDelegate
- (BOOL)legacyNativeContentControllerWebUsageEnabled:
(CRWLegacyNativeContentController*)contentController {
return [self webUsageEnabled];
}
- (void)legacyNativeContentControllerRemoveWebView:
(CRWLegacyNativeContentController*)contentController {
[self removeWebView];
}
- (void)legacyNativeContentControllerDidStartLoading:
(CRWLegacyNativeContentController*)contentController {
[self didStartLoading];
}
- (web::NavigationContextImpl*)
legacyNativeContentController:
(CRWLegacyNativeContentController*)contentController
loadPlaceholderInWebViewForURL:(const GURL&)originalURL
rendererInitiated:(BOOL)rendererInitiated
forContext:(std::unique_ptr<web::NavigationContextImpl>)
originalContext {
return [self.navigationHandler
loadPlaceholderInWebViewForURL:originalURL
rendererInitiated:rendererInitiated
forContext:std::move(originalContext)];
}
- (std::unique_ptr<web::NavigationContextImpl>)
legacyNativeContentController:
(CRWLegacyNativeContentController*)contentController
registerLoadRequestForURL:(const GURL&)requestURL
referrer:(const web::Referrer&)referrer
transition:(ui::PageTransition)transition
sameDocumentNavigation:(BOOL)sameDocumentNavigation
hasUserGesture:(BOOL)hasUserGesture
rendererInitiated:(BOOL)rendererInitiated
placeholderNavigation:(BOOL)placeholderNavigation {
return [_requestController registerLoadRequestForURL:requestURL
referrer:referrer
transition:transition
sameDocumentNavigation:sameDocumentNavigation
hasUserGesture:hasUserGesture
rendererInitiated:rendererInitiated
placeholderNavigation:placeholderNavigation];
}
- (void)legacyNativeContentController:
(CRWLegacyNativeContentController*)contentController
setNativeContentTitle:(NSString*)title {
[self.navigationHandler setLastCommittedNavigationItemTitle:title];
}
- (void)legacyNativeContentController:
(CRWLegacyNativeContentController*)contentController
nativeContentDidChange:
(id<CRWNativeContent>)previousNativeController {
[_containerView nativeContentDidChange:previousNativeController];
}
- (void)legacyNativeContentController:
(CRWLegacyNativeContentController*)contentController
nativeContentLoadDidFinishWithURL:(const GURL&)targetURL
context:(web::NavigationContextImpl*)context {
self.navigationHandler.navigationState = web::WKNavigationState::FINISHED;
[_requestController didFinishWithURL:targetURL
loadSuccess:YES
context:context];
}
#pragma mark - CRWWebControllerContainerViewDelegate
- (CRWWebViewProxyImpl*)contentViewProxyForContainerView:
(CRWWebControllerContainerView*)containerView {
return _webViewProxy;
}
- (UIEdgeInsets)nativeContentInsetsForContainerView:
(CRWWebControllerContainerView*)containerView {
return [[self nativeContentHolder].nativeProvider
nativeContentInsetForWebState:self.webState];
}
- (BOOL)shouldKeepRenderProcessAliveForContainerView:
(CRWWebControllerContainerView*)containerView {
return self.shouldKeepRenderProcessAlive;
}
- (void)containerView:(CRWWebControllerContainerView*)containerView
storeWebViewInWindow:(UIView*)viewToStash {
[web::GetWebClient()->GetWindowedContainer() addSubview:viewToStash];
}
- (void)containerViewResetNativeController:
(CRWWebControllerContainerView*)containerView {
[self.legacyNativeController resetNativeController];
}
- (id<CRWNativeContentHolder>)containerViewNativeContentHolder:
(CRWWebControllerContainerView*)containerView {
return self.nativeContentHolder;
}
#pragma mark - JavaScript message Helpers (Private)
- (void)didReceiveScriptMessage:(WKScriptMessage*)message {
// Broken out into separate method to catch errors.
if (![self respondToWKScriptMessage:message]) {
DLOG(WARNING) << "Message from JS not handled due to invalid format";
}
}
- (BOOL)respondToWKScriptMessage:(WKScriptMessage*)scriptMessage {
if (![scriptMessage.name isEqualToString:kScriptMessageName]) {
return NO;
}
std::unique_ptr<base::Value> messageAsValue =
web::ValueResultFromWKResult(scriptMessage.body);
base::DictionaryValue* message = nullptr;
if (!messageAsValue || !messageAsValue->GetAsDictionary(&message)) {
return NO;
}
web::WebFrame* senderFrame = nullptr;
std::string frameID;
if (message->GetString("crwFrameId", &frameID)) {
senderFrame = web::GetWebFrameWithId([self webState], frameID);
}
// Message must be associated with a current frame.
if (!senderFrame) {
return NO;
}
base::DictionaryValue* crwCommand = nullptr;
if (!message->GetDictionary("crwCommand", &crwCommand)) {
return NO;
}
std::string command;
if (!crwCommand->GetString("command", &command)) {
DLOG(WARNING) << "JS message parameter not found: command";
return NO;
}
self.webStateImpl->OnScriptCommandReceived(
command, *crwCommand, net::GURLWithNSURL(self.webView.URL),
_userInteractionState.IsUserInteracting(self.webView), senderFrame);
return YES;
}
#pragma mark - CRWWebViewScrollViewProxyObserver
- (void)webViewScrollViewDidZoom:
(CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
_pageHasZoomed = YES;
__weak UIScrollView* weakScrollView = self.webScrollView;
[self extractViewportTagWithCompletion:^(
const web::PageViewportState* viewportState) {
if (!weakScrollView)
return;
UIScrollView* scrollView = weakScrollView;
if (viewportState && !viewportState->viewport_tag_present() &&
[scrollView minimumZoomScale] == [scrollView maximumZoomScale] &&
[scrollView zoomScale] > 1.0) {
UMA_HISTOGRAM_BOOLEAN("Renderer.ViewportZoomBugCount", true);
}
}];
}
- (void)webViewScrollViewDidResetContentSize:
(CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
web::NavigationItem* currentItem = self.currentNavItem;
if (webViewScrollViewProxy.isZooming || _applyingPageState || !currentItem)
return;
CGSize contentSize = webViewScrollViewProxy.contentSize;
if (contentSize.width + 1 < CGRectGetWidth(webViewScrollViewProxy.frame)) {
// The content area should never be narrower than the frame, but floating
// point error from non-integer zoom values can cause it to be at most 1
// pixel narrower. If it's any narrower than that, the renderer incorrectly
// resized the content area. Resetting the scroll view's zoom scale will
// force a re-rendering. rdar://23963992
_applyingPageState = YES;
web::PageZoomState zoomState =
currentItem->GetPageDisplayState().zoom_state();
if (!zoomState.IsValid())
zoomState = web::PageZoomState(1.0, 1.0, 1.0);
[self applyWebViewScrollZoomScaleFromZoomState:zoomState];
_applyingPageState = NO;
}
}
// 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 {
[_jsInjector
executeJavaScript:@"__gCrWeb.setWebViewScrollViewIsDragging(true)"
completionHandler:nil];
}
- (void)webViewScrollViewDidEndDragging:
(CRWWebViewScrollViewProxy*)webViewScrollViewProxy
willDecelerate:(BOOL)decelerate {
[_jsInjector
executeJavaScript:@"__gCrWeb.setWebViewScrollViewIsDragging(false)"
completionHandler:nil];
}
#pragma mark - Page State
- (void)restoreStateFromHistory {
web::NavigationItem* item = self.currentNavItem;
if (item)
self.pageDisplayState = item->GetPageDisplayState();
}
- (void)extractViewportTagWithCompletion:(ViewportStateCompletion)completion {
DCHECK(completion);
web::NavigationItem* currentItem = self.currentNavItem;
if (!currentItem) {
completion(nullptr);
return;
}
NSString* const kViewportContentQuery =
@"var viewport = document.querySelector('meta[name=\"viewport\"]');"
"viewport ? viewport.content : '';";
__weak CRWWebController* weakSelf = self;
int itemID = currentItem->GetUniqueID();
[_jsInjector executeJavaScript:kViewportContentQuery
completionHandler:^(id viewportContent, NSError*) {
web::NavigationItem* item = [weakSelf currentNavItem];
if (item && item->GetUniqueID() == itemID) {
web::PageViewportState viewportState(
base::mac::ObjCCast<NSString>(viewportContent));
completion(&viewportState);
} else {
completion(nullptr);
}
}];
}
- (void)orientationDidChange {
// 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 ).
if (!self.webView)
return;
web::NavigationItem* currentItem = self.currentNavItem;
if (!currentItem)
return;
web::PageDisplayState displayState = currentItem->GetPageDisplayState();
if (!displayState.IsValid())
return;
CGFloat zoomPercentage = (displayState.zoom_state().zoom_scale() -
displayState.zoom_state().minimum_zoom_scale()) /
displayState.zoom_state().GetMinMaxZoomDifference();
displayState.zoom_state().set_minimum_zoom_scale(
self.webScrollView.minimumZoomScale);
displayState.zoom_state().set_maximum_zoom_scale(
self.webScrollView.maximumZoomScale);
displayState.zoom_state().set_zoom_scale(
displayState.zoom_state().minimum_zoom_scale() +
zoomPercentage * displayState.zoom_state().GetMinMaxZoomDifference());
currentItem->SetPageDisplayState(displayState);
[self applyPageDisplayState:currentItem->GetPageDisplayState()];
}
- (void)applyPageDisplayState:(const web::PageDisplayState&)displayState {
if (!displayState.IsValid())
return;
__weak CRWWebController* weakSelf = self;
web::PageDisplayState displayStateCopy = displayState;
[self extractViewportTagWithCompletion:^(
const web::PageViewportState* viewportState) {
if (viewportState) {
[weakSelf applyPageDisplayState:displayStateCopy
userScalable:viewportState->user_scalable()];
}
}];
}
- (void)applyPageDisplayState:(const web::PageDisplayState&)displayState
userScalable:(BOOL)isUserScalable {
// Early return if |scrollState| doesn't match the current NavigationItem.
// This can sometimes occur in tests, as navigation occurs programmatically
// and |-applyPageScrollState:| is asynchronous.
web::NavigationItem* currentItem = self.currentNavItem;
if (currentItem && currentItem->GetPageDisplayState() != displayState)
return;
DCHECK(displayState.IsValid());
_applyingPageState = YES;
if (isUserScalable) {
[self prepareToApplyWebViewScrollZoomScale];
[self applyWebViewScrollZoomScaleFromZoomState:displayState.zoom_state()];
[self finishApplyingWebViewScrollZoomScale];
}
[self applyWebViewScrollOffsetFromScrollState:displayState.scroll_state()];
_applyingPageState = NO;
}
- (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];
}
}
- (void)applyWebViewScrollZoomScaleFromZoomState:
(const web::PageZoomState&)zoomState {
// After rendering a web page, WKWebView keeps the |minimumZoomScale| and
// |maximumZoomScale| properties of its scroll view constant while adjusting
// the |zoomScale| property accordingly. The maximum-scale or minimum-scale
// meta tags of a page may have changed since the state was recorded, so clamp
// the zoom scale to the current range if necessary.
DCHECK(zoomState.IsValid());
CGFloat zoomScale = zoomState.zoom_scale();
if (zoomScale < self.webScrollView.minimumZoomScale)
zoomScale = self.webScrollView.minimumZoomScale;
if (zoomScale > self.webScrollView.maximumZoomScale)
zoomScale = self.webScrollView.maximumZoomScale;
self.webScrollView.zoomScale = zoomScale;
}
- (void)applyWebViewScrollOffsetFromScrollState:
(const web::PageScrollState&)scrollState {
DCHECK(scrollState.IsValid());
CGPoint contentOffset = scrollState.GetEffectiveContentOffsetForContentInset(
self.webScrollView.contentInset);
if (self.navigationHandler.navigationState ==
web::WKNavigationState::FINISHED) {
// If the page is loaded, update the scroll immediately.
self.webScrollView.contentOffset = contentOffset;
} else {
// If the page isn't loaded, store the action to update the scroll
// when the page finishes loading.
__weak UIScrollView* weakScrollView = self.webScrollView;
ProceduralBlock action = [^{
weakScrollView.contentOffset = contentOffset;
} copy];
[_pendingLoadCompleteActions addObject:action];
}
}
#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;
}
}
#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::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 (whether
// native or web). Note, this needs to be created with a non-zero size
// to allow for (native) 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. Use the screen size because the application's key window
// may still be nil.
_containerView.frame = UIApplication.sharedApplication.keyWindow
? UIApplication.sharedApplication.keyWindow.bounds
: UIScreen.mainScreen.bounds;
DCHECK(!CGRectIsEmpty(_containerView.frame));
[_containerView addGestureRecognizer:[self touchTrackingRecognizer]];
}
// Creates a web view if it's not yet created.
- (void)ensureWebViewCreated {
WKWebViewConfiguration* config =
[self webViewConfigurationProvider].GetWebViewConfiguration();
[self ensureWebViewCreatedWithConfiguration:config];
}
// Creates a web view with given |config|. No-op if web view is already created.
- (void)ensureWebViewCreatedWithConfiguration:(WKWebViewConfiguration*)config {
if (!self.webView) {
[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];
// Create a dependency between the |webView| pan gesture and BVC side swipe
// gestures. Note: This needs to be added before the longPress recognizers
// below, or the longPress appears to deadlock the remaining recognizers,
// thereby breaking scroll.
NSSet* recognizers = [_swipeRecognizerProvider swipeRecognizers];
for (UISwipeGestureRecognizer* swipeRecognizer in recognizers) {
[self.webScrollView.panGestureRecognizer
requireGestureRecognizerToFail:swipeRecognizer];
}
web::BrowserState* browserState = self.webStateImpl->GetBrowserState();
self.UIHandler.contextMenuController =
[[CRWContextMenuController alloc] initWithWebView:self.webView
browserState:browserState
delegate:self];
self.UIHandler.contextMenuController.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]) {
web::NavigationItem* visibleItem =
self.navigationManagerImpl->GetVisibleItem();
const GURL& visibleURL =
visibleItem ? visibleItem->GetURL() : GURL::EmptyGURL();
if (![self.legacyNativeController shouldLoadURLInNativeView:visibleURL])
[self displayWebView];
}
}
// 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.
return web::BuildWKWebView(CGRectZero, config,
self.webStateImpl->GetBrowserState(),
[self userAgentType]);
}
// Wraps the web view in a CRWWebViewContentView and adds it to the container
// view.
- (void)displayWebView {
if (!self.webView || [_containerView webViewContentView])
return;
CRWWebViewContentView* webViewContentView =
[[CRWWebViewContentView alloc] initWithWebView:self.webView
scrollView:self.webScrollView];
[_containerView displayWebViewContentView:webViewContentView];
}
- (void)removeWebView {
if (!self.webView)
return;
self.webStateImpl->CancelDialogs();
self.navigationManagerImpl->DetachFromWebView();
[self setWebView:nil];
[self.navigationHandler stopLoading];
[_containerView resetContent];
// 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
- (const GURL&)documentURLForUIHandler:(CRWWKUIHandler*)UIHandler {
return _documentURL;
}
- (WKWebView*)UIHandler:(CRWWKUIHandler*)UIHandler
createWebViewWithConfiguration:(WKWebViewConfiguration*)configuration
forWebState:(web::WebState*)webState {
CRWWebController* webController =
static_cast<web::WebStateImpl*>(webState)->GetWebController();
DCHECK(!webController || webState->HasOpener());
[webController ensureWebViewCreatedWithConfiguration:configuration];
return webController.webView;
}
- (BOOL)UIHandler:(CRWWKUIHandler*)UIHandler
isUserInitiatedAction:(WKNavigationAction*)action {
return [self isUserInitiatedAction:action];
}
- (web::WebStateImpl*)webStateImplForUIHandler:(CRWWKUIHandler*)UIHandler {
return self.webStateImpl;
}
#pragma mark - WKNavigationDelegate Helpers
// Called when a page (native or web) 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;
_displayStateOnStartLoading = self.pageDisplayState;
_userInteractionState.SetUserInteractionRegisteredSincePageLoaded(false);
_pageHasZoomed = NO;
}
#pragma mark - CRWSSLStatusUpdaterDataSource
- (void)SSLStatusUpdater:(CRWSSLStatusUpdater*)SSLStatusUpdater
querySSLStatusForTrust:(base::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 - CRWContextMenuDelegate methods
- (void)webView:(WKWebView*)webView
handleContextMenu:(const web::ContextMenuParams&)params {
DCHECK(webView == self.webView);
if (_isBeingDestroyed) {
return;
}
self.webStateImpl->HandleContextMenu(params);
}
- (void)webView:(WKWebView*)webView
executeJavaScript:(NSString*)javaScript
completionHandler:(void (^)(id, NSError*))completionHandler {
[_jsInjector executeJavaScript:javaScript
completionHandler:completionHandler];
}
#pragma mark - CRWJSInjectorDelegate methods
- (GURL)lastCommittedURLForJSInjector:(CRWJSInjector*)injector {
return self.webState->GetLastCommittedURL();
}
- (void)willExecuteUserScriptForJSInjector:(CRWJSInjector*)injector {
[self touched:YES];
}
#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;
}
[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 &&
!IsPlaceholderUrl(net::GURLWithNSURL(self.webView.URL))) {
// 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];
}
}
#pragma mark - CRWWebViewNavigationObserverDelegate
- (web::WebStateImpl*)webStateImplForNavigationObserver:
(CRWWebViewNavigationObserver*)navigationObserver {
return self.webStateImpl;
}
- (CRWWKNavigationHandler*)navigationHandlerForNavigationObserver:
(CRWWebViewNavigationObserver*)navigationObserver {
return self.navigationHandler;
}
- (const GURL&)documentURLForNavigationObserver:
(CRWWebViewNavigationObserver*)navigationObserver {
return _documentURL;
}
- (void)navigationObserverDidChangeSSLStatus:
(CRWWebViewNavigationObserver*)navigationObserver {
[self updateSSLStatusForCurrentNavigationItem];
}
- (void)navigationObserver:(CRWWebViewNavigationObserver*)navigationObserver
didFinishNavigation:(web::NavigationContextImpl*)context {
[self didFinishNavigation:context];
}
- (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
placeholderNavigation:IsPlaceholderUrl(webViewURL)];
[self.navigationHandler webPageChangedWithContext:newContext.get()
webView:self.webView];
newContext->SetHasCommitted(!isSameDocumentNavigation);
self.webStateImpl->OnNavigationFinished(newContext.get());
// TODO(crbug.com/792515): 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.GetOrigin() != newURL.GetOrigin()) {
if (!_documentURL.host().empty() &&
(newURL.username().find(_documentURL.host()) != std::string::npos ||
newURL.password().find(_documentURL.host()) != std::string::npos)) {
CHECK(false);
}
}
}
// Is it ok that newURL can be restore session URL?
if (!IsRestoreSessionUrl(_documentURL) && !IsRestoreSessionUrl(newURL)) {
bool ignore_host_change =
// On iOS13 document.write() can change URL origin for about:blank page.
(_documentURL.IsAboutBlank() && base::ios::IsRunningOnIOS13OrLater() &&
!self.webView.loading);
if (!ignore_host_change) {
DCHECK_EQ(_documentURL.host(), newURL.host());
}
}
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
placeholderNavigation:NO];
// With slim nav, the web page title is stored in WKBackForwardListItem
// and synced to Navigationitem when the web view title changes.
// Otherwise, sync the current title for items created by same document
// navigations.
if (!web::GetWebClient()->IsSlimNavigationManagerEnabled()) {
auto* pendingItem = newNavigationContext->GetItem();
if (pendingItem)
pendingItem->SetTitle(self.webStateImpl->GetTitle());
}
}
}
[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
- (web::WebStateImpl*)webStateImplForNavigationHandler:
(CRWWKNavigationHandler*)navigationHandler {
return self.webStateImpl;
}
- (web::UserInteractionState*)userInteractionStateForNavigationHandler:
(CRWWKNavigationHandler*)navigationHandler {
return &_userInteractionState;
}
- (CRWJSInjector*)JSInjectorForNavigationHandler:
(CRWWKNavigationHandler*)navigationHandler {
return self.jsInjector;
}
- (CRWLegacyNativeContentController*)
legacyNativeContentControllerForNavigationHandler:
(CRWWKNavigationHandler*)navigationHandler {
return self.legacyNativeController;
}
- (CRWCertVerificationController*)
certVerificationControllerForNavigationHandler:
(CRWWKNavigationHandler*)navigationHandler {
return _certVerificationController;
}
- (GURL)navigationHandlerDocumentURL:
(CRWWKNavigationHandler*)navigationHandler {
return _documentURL;
}
- (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];
}
- (BOOL)navigationHandler:(CRWWKNavigationHandler*)navigationHandler
shouldLoadURLInNativeView:(const GURL&)url {
return [self.legacyNativeController shouldLoadURLInNativeView:url];
}
- (void)navigationHandlerRequirePageReconstruction:
(CRWWKNavigationHandler*)navigationHandler {
[self requirePageReconstruction];
}
- (std::unique_ptr<web::NavigationContextImpl>)
navigationHandler:(CRWWKNavigationHandler*)navigationHandler
registerLoadRequestForURL:(const GURL&)URL
sameDocumentNavigation:(BOOL)sameDocumentNavigation
hasUserGesture:(BOOL)hasUserGesture
rendererInitiated:(BOOL)renderedInitiated
placeholderNavigation:(BOOL)placeholderNavigation {
return [_requestController registerLoadRequestForURL:URL
sameDocumentNavigation:sameDocumentNavigation
hasUserGesture:hasUserGesture
rendererInitiated:renderedInitiated
placeholderNavigation:placeholderNavigation];
}
- (void)navigationHandlerDisplayWebView:
(CRWWKNavigationHandler*)navigationHandler {
[self displayWebView];
}
- (void)navigationHandlerDidStartLoading:
(CRWWKNavigationHandler*)navigationHandler {
[self didStartLoading];
}
- (void)navigationHandlerUpdateSSLStatusForCurrentNavigationItem:
(CRWWKNavigationHandler*)navigationHandler {
[self updateSSLStatusForCurrentNavigationItem];
}
- (void)navigationHandlerUpdateHTML5HistoryState:
(CRWWKNavigationHandler*)navigationHandler {
[self updateHTML5HistoryState];
}
- (void)navigationHandler:(CRWWKNavigationHandler*)navigationHandler
didFinishNavigation:(web::NavigationContextImpl*)context {
[self didFinishNavigation:context];
}
- (void)navigationHandlerWebProcessDidCrash:
(CRWWKNavigationHandler*)navigationHandler {
// On iOS 11 WKWebView does not repaint after crash and reload. Recreating
// web view fixes the issue. TODO(crbug.com/770914): Remove this workaround
// once rdar://35063950 is fixed.
[self removeWebView];
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];
}
- (WKWebView*)navigationHandlerEnsureWebViewCreated:
(CRWWKNavigationHandler*)navigationHandler {
[self ensureWebViewCreated];
return self.webView;
}
#pragma mark - CRWWebRequestControllerDelegate
- (void)webRequestControllerStopLoading:
(CRWWebRequestController*)requestController {
[self stopLoading];
}
- (void)webRequestControllerEnsureWebViewCreated:
(CRWWebRequestController*)requestController {
[self ensureWebViewCreated];
}
- (void)webRequestControllerDidStartLoading:
(CRWWebRequestController*)requestController {
[self didStartLoading];
}
- (void)webRequestController:(CRWWebRequestController*)requestController
didCompleteLoadWithSuccess:(BOOL)loadSuccess
forContext:(web::NavigationContextImpl*)context {
[self loadCompleteWithSuccess:loadSuccess forContext:context];
}
- (void)webRequestControllerDisableNavigationGesturesUntilFinishNavigation:
(CRWWebRequestController*)requestController {
// Disable |allowsBackForwardNavigationGestures| during restore. Otherwise,
// WebKit will trigger a snapshot for each (blank) page, and quickly
// overload system memory.
self.webView.allowsBackForwardNavigationGestures = NO;
}
- (web::UserInteractionState*)webRequestControllerUserInteractionState:
(CRWWebRequestController*)requestController {
return &_userInteractionState;
}
- (CRWLegacyNativeContentController*)
webRequestControllerLegacyNativeContentController:
(CRWWebRequestController*)requestController {
return self.legacyNativeController;
}
- (void)webRequestControllerRecordStateInHistory:
(CRWWebRequestController*)requestController {
[self recordStateInHistory];
}
- (void)webRequestControllerRestoreStateFromHistory:
(CRWWebRequestController*)requestController {
[self restoreStateFromHistory];
}
- (WKWebView*)webRequestControllerWebView:
(CRWWebRequestController*)requestController {
return self.webView;
}
- (CRWWKNavigationHandler*)webRequestControllerNavigationHandler:
(CRWWebRequestController*)requestController {
return self.navigationHandler;
}
#pragma mark - CRWJSNavigationHandlerDelegate
- (web::WebStateImpl*)webStateImplForJSNavigationHandler:
(CRWJSNavigationHandler*)navigationHandler {
return self.webStateImpl;
}
// Returns the current URL of web view.
- (GURL)currentURLForJSNavigationHandler:
(CRWJSNavigationHandler*)navigationHandler {
return self.currentURL;
}
// Returns associated UserInteractionState.
- (web::UserInteractionState*)userInteractionStateForJSNavigationHandler:
(CRWJSNavigationHandler*)navigationHandler {
return &_userInteractionState;
}
// Returns associated WKWebView.
- (WKWebView*)webViewForJSNavigationHandler:
(CRWJSNavigationHandler*)navigationHandler {
return self.webView;
}
- (CRWJSInjector*)JSInjectorForJSNavigationHandler:
(CRWJSNavigationHandler*)navigationHandler {
return self.jsInjector;
}
- (void)JSNavigationHandlerUpdateSSLStatusForCurrentNavigationItem:
(CRWJSNavigationHandler*)navigationHandler {
[self updateSSLStatusForCurrentNavigationItem];
}
- (void)JSNavigationHandlerOptOutScrollsToTopForSubviews:
(CRWJSNavigationHandler*)navigationHandler {
return [self optOutScrollsToTopForSubviews];
}
- (void)JSNavigationHandler:(CRWJSNavigationHandler*)navigationHandler
didFinishNavigation:(web::NavigationContextImpl*)context {
[self didFinishNavigation:context];
}
- (void)JSNavigationHandlerReloadWithRendererInitiatedNavigation:
(CRWJSNavigationHandler*)navigationHandler {
[self reloadWithRendererInitiatedNavigation:YES];
}
#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