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