Move decidePolicyForNavigationAction into CRWWKNavigationHandler.

This CL moves WKNavigationDelegate method
"webView:decidePolicyForNavigationAction:decisionHandler" from
CRWWebController into CRWWKNavigationHandler.

Bug: 956511
Change-Id: I174e56214a59c8f272c36d983445fe4e230fc8ed
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1609906
Commit-Queue: Yi Su <mrsuyi@chromium.org>
Reviewed-by: Eugene But <eugenebut@chromium.org>
Cr-Commit-Position: refs/heads/master@{#661301}
diff --git a/ios/web/navigation/BUILD.gn b/ios/web/navigation/BUILD.gn
index 816b5c8e..71194d6 100644
--- a/ios/web/navigation/BUILD.gn
+++ b/ios/web/navigation/BUILD.gn
@@ -15,14 +15,17 @@
   deps = [
     ":block_universal_links_buildflags",
     ":core",
+    ":navigation_manager_util",
     "//base",
     "//ios/web:core",
     "//ios/web/common",
     "//ios/web/navigation:wk_navigation_util",
     "//ios/web/public",
     "//ios/web/web_state:session_certificate_policy_cache",
+    "//ios/web/web_state:user_interaction",
     "//ios/web/web_state:web_state_impl_header",
     "//ios/web/web_state/ui:crw_web_view_navigation_proxy",
+    "//ios/web/web_view",
     "//ui/base",
     "//url",
   ]
diff --git a/ios/web/navigation/crw_wk_navigation_handler.h b/ios/web/navigation/crw_wk_navigation_handler.h
index aff7624..0e891cbb 100644
--- a/ios/web/navigation/crw_wk_navigation_handler.h
+++ b/ios/web/navigation/crw_wk_navigation_handler.h
@@ -8,21 +8,72 @@
 #import <UIKit/UIKit.h>
 #import <WebKit/WebKit.h>
 
+#include "ui/base/page_transition_types.h"
+
+@class CRWWKNavigationHandler;
 @class CRWPendingNavigationInfo;
 @class CRWWKNavigationStates;
-@class CRWWKNavigationHandler;
-
+class GURL;
 namespace base {
 class RepeatingTimer;
 }
+namespace web {
+struct Referrer;
+class WebStateImpl;
+class NavigationContextImpl;
+class UserInteractionState;
+}
 
 // CRWWKNavigationHandler uses this protocol to interact with its owner.
 @protocol CRWWKNavigationHandlerDelegate <NSObject>
 
+// Returns associated WebStateImpl.
+- (web::WebStateImpl*)webStateImplForNavigationHandler:
+    (CRWWKNavigationHandler*)navigationHandler;
+
+// Returns associated UserInteractionState.
+- (web::UserInteractionState*)userInteractionStateForNavigationHandler:
+    (CRWWKNavigationHandler*)navigationHandler;
+
+// Returns current Referrer.
+- (web::Referrer)currentReferrerForNavigationHandler:
+    (CRWWKNavigationHandler*)navigationHandler;
+
 // Returns YES if WKWebView was deallocated or is being deallocated.
 - (BOOL)navigationHandlerWebViewBeingDestroyed:
     (CRWWKNavigationHandler*)navigationHandler;
 
+// Returns the actual URL of the document object (i.e., the last committed URL
+// of the main frame).
+- (GURL)navigationHandlerDocumentURL:(CRWWKNavigationHandler*)navigationHandler;
+
+// Sets document URL to newURL, and updates any relevant state information.
+- (void)navigationHandler:(CRWWKNavigationHandler*)navigationHandler
+           setDocumentURL:(const GURL&)newURL
+                  context:(web::NavigationContextImpl*)context;
+
+// Maps WKNavigationType to ui::PageTransition.
+- (ui::PageTransition)navigationHandler:
+                          (CRWWKNavigationHandler*)navigationHandler
+       pageTransitionFromNavigationType:(WKNavigationType)navigationType;
+
+// Sets up WebUI for URL.
+- (void)navigationHandler:(CRWWKNavigationHandler*)navigationHandler
+        createWebUIForURL:(const GURL&)URL;
+
+// Stop Loading current page.
+- (void)navigationHandlerStopLoading:(CRWWKNavigationHandler*)navigationHandler;
+
+// Returns YES if |url| should be loaded in a native view.
+- (BOOL)navigationHandler:(CRWWKNavigationHandler*)navigationHandler
+    shouldLoadURLInNativeView:(const GURL&)url;
+
+// Requires that the next load rebuild the web view. This is expensive, and
+// should be used only in the case where something has changed that the web view
+// only checks on creation, such that the whole object needs to be rebuilt.
+- (void)navigationHandlerRequirePageReconstruction:
+    (CRWWKNavigationHandler*)navigationHandler;
+
 @end
 
 // Handler class for WKNavigationDelegate, deals with navigation callbacks from
@@ -31,6 +82,10 @@
 
 @property(nonatomic, weak) id<CRWWKNavigationHandlerDelegate> delegate;
 
+// TODO(crbug.com/956511): Change this to readonly when
+// |webViewWebProcessDidCrash| is moved to CRWWKNavigationHandler.
+@property(nonatomic, assign) BOOL webProcessCrashed;
+
 // Pending information for an in-progress page navigation. The lifetime of
 // this object starts at |decidePolicyForNavigationAction| where the info is
 // extracted from the request, and ends at either |didCommitNavigation| or
@@ -46,9 +101,19 @@
 @property(nonatomic, readonly, assign)
     base::RepeatingTimer* safeBrowsingWarningDetectionTimer;
 
+// Discards non committed items, only if the last committed URL was not loaded
+// in native view. But if it was a native view, no discard will happen to avoid
+// an ugly animation where the web view is inserted and quickly removed.
+- (void)discardNonCommittedItemsIfLastCommittedWasNotNativeView;
+
 // Instructs this handler to stop loading.
 - (void)stopLoading;
 
+// Returns context for pending navigation that has |URL|. null if there is no
+// matching pending navigation.
+- (web::NavigationContextImpl*)contextForPendingMainFrameNavigationWithURL:
+    (const GURL&)URL;
+
 @end
 
 #endif  // IOS_WEB_NAVIGATION_CRW_WK_NAVIGATION_HANDLER_H_
diff --git a/ios/web/navigation/crw_wk_navigation_handler.mm b/ios/web/navigation/crw_wk_navigation_handler.mm
index 8568a9e5..46fcead 100644
--- a/ios/web/navigation/crw_wk_navigation_handler.mm
+++ b/ios/web/navigation/crw_wk_navigation_handler.mm
@@ -4,15 +4,48 @@
 
 #import "ios/web/navigation/crw_wk_navigation_handler.h"
 
+#include "base/feature_list.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/timer/timer.h"
+#include "ios/web/common/features.h"
 #import "ios/web/navigation/crw_pending_navigation_info.h"
 #import "ios/web/navigation/crw_wk_navigation_states.h"
+#import "ios/web/navigation/navigation_context_impl.h"
+#import "ios/web/navigation/navigation_manager_impl.h"
+#include "ios/web/navigation/navigation_manager_util.h"
+#import "ios/web/navigation/wk_navigation_action_policy_util.h"
+#import "ios/web/navigation/wk_navigation_action_util.h"
+#import "ios/web/navigation/wk_navigation_util.h"
+#include "ios/web/public/browser_state.h"
+#import "ios/web/public/url_scheme_util.h"
+#import "ios/web/public/web_client.h"
+#import "ios/web/web_state/user_interaction_state.h"
+#import "ios/web/web_state/web_state_impl.h"
+#import "ios/web/web_view/wk_web_view_util.h"
+#import "net/base/mac/url_conversions.h"
+#include "url/gurl.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
 #error "This file requires ARC support."
 #endif
 
+using web::wk_navigation_util::IsPlaceholderUrl;
+using web::wk_navigation_util::kReferrerHeaderName;
+using web::wk_navigation_util::IsRestoreSessionUrl;
+
+@interface CRWWKNavigationHandler ()
+
+// Returns the WebStateImpl from self.delegate.
+@property(nonatomic, readonly, assign) web::WebStateImpl* webStateImpl;
+// Returns the NavigationManagerImpl from self.webStateImpl.
+@property(nonatomic, readonly, assign)
+    web::NavigationManagerImpl* navigationManagerImpl;
+// Returns the UserInteractionState from self.delegate.
+@property(nonatomic, readonly, assign)
+    web::UserInteractionState* userInteractionState;
+
+@end
+
 @implementation CRWWKNavigationHandler {
   // Used to poll for a SafeBrowsing warning being displayed. This is created in
   // |decidePolicyForNavigationAction| and destroyed once any of the following
@@ -35,6 +68,322 @@
                     decisionHandler:
                         (void (^)(WKNavigationActionPolicy))decisionHandler {
   [self didReceiveWKNavigationDelegateCallback];
+
+  self.webProcessCrashed = NO;
+  if ([self.delegate navigationHandlerWebViewBeingDestroyed:self]) {
+    decisionHandler(WKNavigationActionPolicyCancel);
+    return;
+  }
+
+  GURL requestURL = net::GURLWithNSURL(action.request.URL);
+
+  // Workaround for a WKWebView bug where the web content loaded using
+  // |-loadHTMLString:baseURL| clobbers the next WKBackForwardListItem. It works
+  // by detecting back/forward navigation to a clobbered item and replacing the
+  // clobberred item and its forward history using a partial session restore in
+  // the current web view. There is an unfortunate caveat: if the workaround is
+  // triggered in a back navigation to a clobbered item, the restored forward
+  // session is inserted after the current item before the back navigation, so
+  // it doesn't fully replaces the "bad" history, even though user will be
+  // navigated to the expected URL and may not notice the issue until they
+  // review the back history by long pressing on "Back" button.
+  //
+  // TODO(crbug.com/887497): remove this workaround once iOS ships the fix.
+  if (web::GetWebClient()->IsSlimNavigationManagerEnabled() &&
+      action.targetFrame.mainFrame) {
+    GURL webViewURL = net::GURLWithNSURL(webView.URL);
+    GURL currentWKItemURL =
+        net::GURLWithNSURL(webView.backForwardList.currentItem.URL);
+    GURL backItemURL = net::GURLWithNSURL(webView.backForwardList.backItem.URL);
+    web::NavigationContextImpl* context =
+        [self contextForPendingMainFrameNavigationWithURL:webViewURL];
+    bool willClobberHistory =
+        action.navigationType == WKNavigationTypeBackForward &&
+        requestURL == backItemURL && webView.backForwardList.currentItem &&
+        requestURL != currentWKItemURL && currentWKItemURL == webViewURL &&
+        context &&
+        (context->GetPageTransition() & ui::PAGE_TRANSITION_FORWARD_BACK);
+
+    UMA_HISTOGRAM_BOOLEAN("IOS.WKWebViewClobberedHistory", willClobberHistory);
+
+    if (willClobberHistory && base::FeatureList::IsEnabled(
+                                  web::features::kHistoryClobberWorkaround)) {
+      decisionHandler(WKNavigationActionPolicyCancel);
+      self.navigationManagerImpl
+          ->ApplyWKWebViewForwardHistoryClobberWorkaround();
+      return;
+    }
+  }
+
+  // The page will not be changed until this navigation is committed, so the
+  // retrieved state will be pending until |didCommitNavigation| callback.
+  [self updatePendingNavigationInfoFromNavigationAction:action];
+
+  if (web::GetWebClient()->IsSlimNavigationManagerEnabled() &&
+      action.targetFrame.mainFrame &&
+      action.navigationType == WKNavigationTypeBackForward) {
+    web::NavigationContextImpl* context =
+        [self contextForPendingMainFrameNavigationWithURL:requestURL];
+    if (context) {
+      // Context is null for renderer-initiated navigations.
+      int index = web::GetCommittedItemIndexWithUniqueID(
+          self.navigationManagerImpl, context->GetNavigationItemUniqueID());
+      self.navigationManagerImpl->SetPendingItemIndex(index);
+    }
+  }
+
+  // If this is a placeholder navigation, pass through.
+  if (IsPlaceholderUrl(requestURL)) {
+    decisionHandler(WKNavigationActionPolicyAllow);
+    return;
+  }
+
+  ui::PageTransition transition =
+      [self.delegate navigationHandler:self
+          pageTransitionFromNavigationType:action.navigationType];
+  BOOL isMainFrameNavigationAction = [self isMainFrameNavigationAction:action];
+  if (isMainFrameNavigationAction) {
+    web::NavigationContextImpl* context =
+        [self contextForPendingMainFrameNavigationWithURL:requestURL];
+    if (context) {
+      DCHECK(!context->IsRendererInitiated() ||
+             (context->GetPageTransition() & ui::PAGE_TRANSITION_FORWARD_BACK));
+      transition = context->GetPageTransition();
+      if (context->IsLoadingErrorPage()) {
+        // loadHTMLString: navigation which loads error page into WKWebView.
+        decisionHandler(WKNavigationActionPolicyAllow);
+        return;
+      }
+    }
+  }
+
+  if (web::GetWebClient()->IsSlimNavigationManagerEnabled()) {
+    // WKBasedNavigationManager doesn't use |loadCurrentURL| for reload or back/
+    // forward navigation. So this is the first point where a form repost would
+    // be detected. Display the confirmation dialog.
+    if ([action.request.HTTPMethod isEqual:@"POST"] &&
+        (action.navigationType == WKNavigationTypeFormResubmitted)) {
+      self.webStateImpl->ShowRepostFormWarningDialog(
+          base::BindOnce(^(bool shouldContinue) {
+            if (shouldContinue) {
+              decisionHandler(WKNavigationActionPolicyAllow);
+            } else {
+              decisionHandler(WKNavigationActionPolicyCancel);
+              if (action.targetFrame.mainFrame) {
+                [self.pendingNavigationInfo setCancelled:YES];
+                self.webStateImpl->SetIsLoading(false);
+              }
+            }
+          }));
+      return;
+    }
+  }
+
+  // Invalid URLs should not be loaded.
+  if (!requestURL.is_valid()) {
+    decisionHandler(WKNavigationActionPolicyCancel);
+    // The HTML5 spec indicates that window.open with an invalid URL should open
+    // about:blank.
+    BOOL isFirstLoadInOpenedWindow =
+        self.webStateImpl->HasOpener() &&
+        !self.webStateImpl->GetNavigationManager()->GetLastCommittedItem();
+    BOOL isMainFrame = action.targetFrame.mainFrame;
+    if (isFirstLoadInOpenedWindow && isMainFrame) {
+      GURL aboutBlankURL(url::kAboutBlankURL);
+      web::NavigationManager::WebLoadParams loadParams(aboutBlankURL);
+      loadParams.referrer =
+          [self.delegate currentReferrerForNavigationHandler:self];
+
+      self.webStateImpl->GetNavigationManager()->LoadURLWithParams(loadParams);
+    }
+    return;
+  }
+
+  // First check if the navigation action should be blocked by the controller
+  // and make sure to update the controller in the case that the controller
+  // can't handle the request URL. Then use the embedders' policyDeciders to
+  // either: 1- Handle the URL it self and return false to stop the controller
+  // from proceeding with the navigation if needed. or 2- return true to allow
+  // the navigation to be proceeded by the web controller.
+  BOOL allowLoad = YES;
+  if (web::GetWebClient()->IsAppSpecificURL(requestURL)) {
+    allowLoad = [self shouldAllowAppSpecificURLNavigationAction:action
+                                                     transition:transition];
+    if (allowLoad && !self.webStateImpl->HasWebUI()) {
+      [self.delegate navigationHandler:self createWebUIForURL:requestURL];
+    }
+  }
+
+  BOOL webControllerCanShow =
+      web::UrlHasWebScheme(requestURL) ||
+      web::GetWebClient()->IsAppSpecificURL(requestURL) ||
+      requestURL.SchemeIs(url::kFileScheme) ||
+      requestURL.SchemeIs(url::kAboutScheme) ||
+      requestURL.SchemeIs(url::kBlobScheme);
+
+  if (allowLoad) {
+    // If the URL doesn't look like one that can be shown as a web page, it may
+    // handled by the embedder. In that case, update the web controller to
+    // correctly reflect the current state.
+    if (!webControllerCanShow) {
+      if (!web::features::StorePendingItemInContext()) {
+        if ([self isMainFrameNavigationAction:action]) {
+          [self.delegate navigationHandlerStopLoading:self];
+        }
+      }
+
+      // Purge web view if last committed URL is different from the document
+      // URL. This can happen if external URL was added to the navigation stack
+      // and was loaded using Go Back or Go Forward navigation (in which case
+      // document URL will point to the previous page).  If this is the first
+      // load for a NavigationManager, there will be no last committed item, so
+      // check here.
+      // TODO(crbug.com/850760): Check if this code is still needed. The current
+      // implementation doesn't put external apps URLs in the history, so they
+      // shouldn't be accessable by Go Back or Go Forward navigation.
+      web::NavigationItem* lastCommittedItem =
+          self.webStateImpl->GetNavigationManager()->GetLastCommittedItem();
+      if (lastCommittedItem) {
+        GURL lastCommittedURL = lastCommittedItem->GetURL();
+        if (lastCommittedURL !=
+            [self.delegate navigationHandlerDocumentURL:self]) {
+          [self.delegate navigationHandlerRequirePageReconstruction:self];
+          [self.delegate navigationHandler:self
+                            setDocumentURL:lastCommittedURL
+                                   context:nullptr];
+        }
+      }
+    }
+  }
+
+  if (allowLoad) {
+    BOOL userInteractedWithRequestMainFrame =
+        self.userInteractionState->HasUserTappedRecently(webView) &&
+        net::GURLWithNSURL(action.request.mainDocumentURL) ==
+            self.userInteractionState->LastUserInteraction()->main_document_url;
+    web::WebStatePolicyDecider::RequestInfo requestInfo(
+        transition, isMainFrameNavigationAction,
+        userInteractedWithRequestMainFrame);
+
+    allowLoad =
+        self.webStateImpl->ShouldAllowRequest(action.request, requestInfo);
+    // The WebState may have been closed in the ShouldAllowRequest callback.
+    if ([self.delegate navigationHandlerWebViewBeingDestroyed:self]) {
+      decisionHandler(WKNavigationActionPolicyCancel);
+      return;
+    }
+  }
+
+  if (!webControllerCanShow && web::features::StorePendingItemInContext()) {
+    allowLoad = NO;
+  }
+
+  if (allowLoad) {
+    if ([[action.request HTTPMethod] isEqualToString:@"POST"]) {
+      web::NavigationItemImpl* item =
+          self.navigationManagerImpl->GetCurrentItemImpl();
+      // TODO(crbug.com/570699): Remove this check once it's no longer possible
+      // to have no current entries.
+      if (item)
+        [self cachePOSTDataForRequest:action.request inNavigationItem:item];
+    }
+  } else {
+    if (action.targetFrame.mainFrame) {
+      [self.pendingNavigationInfo setCancelled:YES];
+      // Discard the pending item to ensure that the current URL is not
+      // different from what is displayed on the view. Discard only happens
+      // if the last item was not a native view, to avoid ugly animation of
+      // inserting the webview.
+      [self discardNonCommittedItemsIfLastCommittedWasNotNativeView];
+
+      web::NavigationContextImpl* context =
+          [self contextForPendingMainFrameNavigationWithURL:requestURL];
+      if (context) {
+        // Destroy associated pending item, because this will be the last
+        // WKWebView callback for this navigation context.
+        context->ReleaseItem();
+      }
+
+      if (![self.delegate navigationHandlerWebViewBeingDestroyed:self] &&
+          [self shouldClosePageOnNativeApplicationLoad]) {
+        // Loading was started for user initiated navigations and should be
+        // stopped because no other WKWebView callbacks are called.
+        // TODO(crbug.com/767092): Loading should not start until
+        // webView.loading is changed to YES.
+        self.webStateImpl->SetIsLoading(false);
+        self.webStateImpl->CloseWebState();
+        decisionHandler(WKNavigationActionPolicyCancel);
+        return;
+      }
+    }
+
+    if (![self.delegate navigationHandlerWebViewBeingDestroyed:self]) {
+      // Loading was started for user initiated navigations and should be
+      // stopped because no other WKWebView callbacks are called.
+      // TODO(crbug.com/767092): Loading should not start until webView.loading
+      // is changed to YES.
+      self.webStateImpl->SetIsLoading(false);
+    }
+  }
+
+  // Only try to detect a SafeBrowsing warning if one isn't already displayed,
+  // since the detection logic won't be able to distinguish between the current
+  // warning and a warning for the page that's about to be loaded. Also, since
+  // the purpose of running this logic is to ensure that the right URL is
+  // displayed in the omnibox, don't try to detect a SafeBrowsing warning for
+  // iframe navigations, because the omnibox already shows the correct main
+  // frame URL in that case.
+  if (allowLoad && isMainFrameNavigationAction &&
+      !web::IsSafeBrowsingWarningDisplayedInWebView(webView)) {
+    __weak CRWWKNavigationHandler* weakSelf = self;
+    __weak WKWebView* weakWebView = webView;
+    const base::TimeDelta kDelayUntilSafeBrowsingWarningCheck =
+        base::TimeDelta::FromMilliseconds(20);
+    _safeBrowsingWarningDetectionTimer.Start(
+        FROM_HERE, kDelayUntilSafeBrowsingWarningCheck, base::BindRepeating(^{
+          __strong __typeof(weakSelf) strongSelf = weakSelf;
+          __strong __typeof(weakWebView) strongWebView = weakWebView;
+          if (web::IsSafeBrowsingWarningDisplayedInWebView(strongWebView)) {
+            // Extract state from an existing navigation context if one exists.
+            // Create a new context rather than just re-using the existing one,
+            // since the existing context will continue to be used if the user
+            // decides to proceed to the unsafe page. In that case, WebKit
+            // continues the navigation with the same WKNavigation* that's
+            // associated with the existing context.
+            web::NavigationContextImpl* existingContext = [strongSelf
+                contextForPendingMainFrameNavigationWithURL:requestURL];
+            bool hasUserGesture =
+                existingContext ? existingContext->HasUserGesture() : false;
+            bool isRendererInitiated =
+                existingContext ? existingContext->IsRendererInitiated() : true;
+            std::unique_ptr<web::NavigationContextImpl> context =
+                web::NavigationContextImpl::CreateNavigationContext(
+                    [strongSelf webStateImpl], requestURL, hasUserGesture,
+                    transition, isRendererInitiated);
+            [strongSelf navigationManagerImpl] -> AddTransientItem(requestURL);
+            [strongSelf webStateImpl] -> OnNavigationStarted(context.get());
+            [strongSelf webStateImpl] -> OnNavigationFinished(context.get());
+            strongSelf->_safeBrowsingWarningDetectionTimer.Stop();
+            if (!existingContext) {
+              // If there's an existing context, observers will already be aware
+              // of a load in progress. Otherwise, observers need to be notified
+              // here, so that if the user decides to go back to the previous
+              // page (stopping the load), observers will be aware of a possible
+              // URL change and the URL displayed in the omnibox will get
+              // updated.
+              DCHECK(strongWebView.loading);
+              [strongSelf webStateImpl] -> SetIsLoading(true);
+            }
+          }
+        }));
+  }
+
+  if (!allowLoad) {
+    decisionHandler(WKNavigationActionPolicyCancel);
+    return;
+  }
+  BOOL isOffTheRecord = self.webStateImpl->GetBrowserState()->IsOffTheRecord();
+  decisionHandler(web::GetAllowNavigationActionPolicy(isOffTheRecord));
 }
 
 - (void)webView:(WKWebView*)webView
@@ -90,6 +439,18 @@
 
 #pragma mark - Private methods
 
+- (web::NavigationManagerImpl*)navigationManagerImpl {
+  return &(self.webStateImpl->GetNavigationManagerImpl());
+}
+
+- (web::WebStateImpl*)webStateImpl {
+  return [self.delegate webStateImplForNavigationHandler:self];
+}
+
+- (web::UserInteractionState*)userInteractionState {
+  return [self.delegate userInteractionStateForNavigationHandler:self];
+}
+
 // This method should be called on receiving WKNavigationDelegate callbacks. It
 // will log a metric if the callback occurs after the reciever has already been
 // closed. It also stops the SafeBrowsing warning detection timer, since after
@@ -102,6 +463,163 @@
   _safeBrowsingWarningDetectionTimer.Stop();
 }
 
+// Extracts navigation info from WKNavigationAction and sets it as a pending.
+// Some pieces of navigation information are only known in
+// |decidePolicyForNavigationAction|, but must be in a pending state until
+// |didgo/Navigation| where it becames current.
+- (void)updatePendingNavigationInfoFromNavigationAction:
+    (WKNavigationAction*)action {
+  if (action.targetFrame.mainFrame) {
+    self.pendingNavigationInfo = [[CRWPendingNavigationInfo alloc] init];
+    self.pendingNavigationInfo.referrer =
+        [action.request valueForHTTPHeaderField:kReferrerHeaderName];
+    self.pendingNavigationInfo.navigationType = action.navigationType;
+    self.pendingNavigationInfo.HTTPMethod = action.request.HTTPMethod;
+    self.pendingNavigationInfo.hasUserGesture =
+        web::GetNavigationActionInitiationType(action) ==
+        web::NavigationActionInitiationType::kUserInitiated;
+  }
+}
+
+// Returns YES if the navigation action is associated with a main frame request.
+- (BOOL)isMainFrameNavigationAction:(WKNavigationAction*)action {
+  if (action.targetFrame) {
+    return action.targetFrame.mainFrame;
+  }
+  // According to WKNavigationAction documentation, in the case of a new window
+  // navigation, target frame will be nil. In this case check if the
+  // |sourceFrame| is the mainFrame.
+  return action.sourceFrame.mainFrame;
+}
+
+// Returns YES if the given |action| should be allowed to continue for app
+// specific URL. If this returns NO, the navigation should be cancelled.
+// App specific pages have elevated privileges and WKWebView uses the same
+// renderer process for all page frames. With that Chromium does not allow
+// running App specific pages in the same process as a web site from the
+// internet. Allows navigation to app specific URL in the following cases:
+//   - last committed URL is app specific
+//   - navigation not a new navigation (back-forward or reload)
+//   - navigation is typed, generated or bookmark
+//   - navigation is performed in iframe and main frame is app-specific page
+- (BOOL)shouldAllowAppSpecificURLNavigationAction:(WKNavigationAction*)action
+                                       transition:
+                                           (ui::PageTransition)pageTransition {
+  GURL requestURL = net::GURLWithNSURL(action.request.URL);
+  DCHECK(web::GetWebClient()->IsAppSpecificURL(requestURL));
+  if (web::GetWebClient()->IsAppSpecificURL(
+          self.webStateImpl->GetLastCommittedURL())) {
+    // Last committed page is also app specific and navigation should be
+    // allowed.
+    return YES;
+  }
+
+  if (!ui::PageTransitionIsNewNavigation(pageTransition)) {
+    // Allow reloads and back-forward navigations.
+    return YES;
+  }
+
+  if (ui::PageTransitionTypeIncludingQualifiersIs(pageTransition,
+                                                  ui::PAGE_TRANSITION_TYPED)) {
+    return YES;
+  }
+
+  if (ui::PageTransitionTypeIncludingQualifiersIs(
+          pageTransition, ui::PAGE_TRANSITION_GENERATED)) {
+    return YES;
+  }
+
+  if (ui::PageTransitionTypeIncludingQualifiersIs(
+          pageTransition, ui::PAGE_TRANSITION_AUTO_BOOKMARK)) {
+    return YES;
+  }
+
+  // If the session is being restored, allow the navigation.
+  if (IsRestoreSessionUrl([self.delegate navigationHandlerDocumentURL:self])) {
+    return YES;
+  }
+
+  GURL mainDocumentURL = net::GURLWithNSURL(action.request.mainDocumentURL);
+  if (web::GetWebClient()->IsAppSpecificURL(mainDocumentURL) &&
+      !action.sourceFrame.mainFrame) {
+    // AppSpecific URLs are allowed inside iframe if the main frame is also
+    // app specific page.
+    return YES;
+  }
+
+  return NO;
+}
+
+// Caches request POST data in the given session entry.
+- (void)cachePOSTDataForRequest:(NSURLRequest*)request
+               inNavigationItem:(web::NavigationItemImpl*)item {
+  NSUInteger maxPOSTDataSizeInBytes = 4096;
+  NSString* cookieHeaderName = @"cookie";
+
+  DCHECK(item);
+  const bool shouldUpdateEntry =
+      ui::PageTransitionCoreTypeIs(item->GetTransitionType(),
+                                   ui::PAGE_TRANSITION_FORM_SUBMIT) &&
+      ![request HTTPBodyStream] &&  // Don't cache streams.
+      !item->HasPostData() &&
+      item->GetURL() == net::GURLWithNSURL([request URL]);
+  const bool belowSizeCap =
+      [[request HTTPBody] length] < maxPOSTDataSizeInBytes;
+  DLOG_IF(WARNING, shouldUpdateEntry && !belowSizeCap)
+      << "Data in POST request exceeds the size cap (" << maxPOSTDataSizeInBytes
+      << " bytes), and will not be cached.";
+
+  if (shouldUpdateEntry && belowSizeCap) {
+    item->SetPostData([request HTTPBody]);
+    item->ResetHttpRequestHeaders();
+    item->AddHttpRequestHeaders([request allHTTPHeaderFields]);
+    // Don't cache the "Cookie" header.
+    // According to NSURLRequest documentation, |-valueForHTTPHeaderField:| is
+    // case insensitive, so it's enough to test the lower case only.
+    if ([request valueForHTTPHeaderField:cookieHeaderName]) {
+      // Case insensitive search in |headers|.
+      NSSet* cookieKeys = [item->GetHttpRequestHeaders()
+          keysOfEntriesPassingTest:^(id key, id obj, BOOL* stop) {
+            NSString* header = (NSString*)key;
+            const BOOL found =
+                [header caseInsensitiveCompare:cookieHeaderName] ==
+                NSOrderedSame;
+            *stop = found;
+            return found;
+          }];
+      DCHECK_EQ(1u, [cookieKeys count]);
+      item->RemoveHttpRequestHeaderForKey([cookieKeys anyObject]);
+    }
+  }
+}
+
+// Discards non committed items, only if the last committed URL was not loaded
+// in native view. But if it was a native view, no discard will happen to avoid
+// an ugly animation where the web view is inserted and quickly removed.
+- (void)discardNonCommittedItemsIfLastCommittedWasNotNativeView {
+  GURL lastCommittedURL = self.webStateImpl->GetLastCommittedURL();
+  BOOL previousItemWasLoadedInNativeView =
+      [self.delegate navigationHandler:self
+             shouldLoadURLInNativeView:lastCommittedURL];
+  if (!previousItemWasLoadedInNativeView)
+    self.navigationManagerImpl->DiscardNonCommittedItems();
+}
+
+// If YES, the page should be closed if it successfully redirects to a native
+// application, for example if a new tab redirects to the App Store.
+- (BOOL)shouldClosePageOnNativeApplicationLoad {
+  // The page should be closed if it was initiated by the DOM and there has been
+  // no user interaction with the page since the web view was created, or if
+  // the page has no navigation items, as occurs when an App Store link is
+  // opened from another application.
+  BOOL rendererInitiatedWithoutInteraction =
+      self.webStateImpl->HasOpener() &&
+      !self.userInteractionState
+           ->UserInteractionRegisteredSinceWebViewCreated();
+  BOOL noNavigationItems = !(self.navigationManagerImpl->GetItemCount());
+  return rendererInitiatedWithoutInteraction || noNavigationItems;
+}
+
 #pragma mark - Public methods
 
 - (void)stopLoading {
@@ -113,4 +631,26 @@
   return &_safeBrowsingWarningDetectionTimer;
 }
 
+// Returns context for pending navigation that has |URL|. null if there is no
+// matching pending navigation.
+- (web::NavigationContextImpl*)contextForPendingMainFrameNavigationWithURL:
+    (const GURL&)URL {
+  // Here the enumeration variable |navigation| is __strong to allow setting it
+  // to nil.
+  for (__strong id navigation in [self.navigationStates pendingNavigations]) {
+    if (navigation == [NSNull null]) {
+      // null is a valid navigation object passed to WKNavigationDelegate
+      // callbacks and represents window opening action.
+      navigation = nil;
+    }
+
+    web::NavigationContextImpl* context =
+        [self.navigationStates contextForNavigation:navigation];
+    if (context && context->GetUrl() == URL) {
+      return context;
+    }
+  }
+  return nullptr;
+}
+
 @end
diff --git a/ios/web/web_state/ui/crw_web_controller.h b/ios/web/web_state/ui/crw_web_controller.h
index 65cb9ff4..60587c7 100644
--- a/ios/web/web_state/ui/crw_web_controller.h
+++ b/ios/web/web_state/ui/crw_web_controller.h
@@ -74,7 +74,8 @@
 @property(nonatomic, readonly) double loadingProgress;
 
 // YES if the web process backing WebView is believed to currently be crashed.
-@property(nonatomic, assign, getter=isWebProcessCrashed) BOOL webProcessCrashed;
+@property(nonatomic, readonly, assign, getter=isWebProcessCrashed)
+    BOOL webProcessCrashed;
 
 // Whether the WebController is visible. Returns YES after wasShown call and
 // NO after wasHidden() call.
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index 6df18d68..181b886 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -223,7 +223,8 @@
                                 CRWWebControllerContainerViewDelegate,
                                 CRWWebViewScrollViewProxyObserver,
                                 WKNavigationDelegate,
-                                WKUIDelegate> {
+                                WKUIDelegate,
+                                CRWWKNavigationHandlerDelegate> {
   // 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
@@ -400,9 +401,6 @@
 // Updates the internal state and informs the delegate that any outstanding load
 // operations are cancelled.
 - (void)loadCancelled;
-// If YES, the page should be closed if it successfully redirects to a native
-// application, for example if a new tab redirects to the App Store.
-- (BOOL)shouldClosePageOnNativeApplicationLoad;
 // 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
@@ -435,9 +433,6 @@
 - (void)frameBecameUnavailableWithMessage:(WKScriptMessage*)message;
 // Clears the frames list.
 - (void)removeAllWebFrames;
-// Maps WKNavigationType to ui::PageTransition.
-- (ui::PageTransition)pageTransitionFromNavigationType:
-    (WKNavigationType)navigationType;
 
 // Restores the state for this page from session history.
 - (void)restoreStateFromHistory;
@@ -467,9 +462,6 @@
 // Sets scroll offset value for webview scroll view from |scrollState|.
 - (void)applyWebViewScrollOffsetFromScrollState:
     (const web::PageScrollState&)scrollState;
-// Sets _documentURL to newURL, and updates any relevant state information.
-- (void)setDocumentURL:(const GURL&)newURL
-               context:(web::NavigationContextImpl*)context;
 // Sets last committed NavigationItem's title to the given |title|, which can
 // not be nil.
 - (void)setNavigationItemTitle:(NSString*)title;
@@ -484,8 +476,6 @@
 // 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;
-// Returns YES if the navigation action is associated with a main frame request.
-- (BOOL)isMainFrameNavigationAction:(WKNavigationAction*)action;
 // Updates SSL status for the current navigation item based on the information
 // provided by web view.
 - (void)updateSSLStatusForCurrentNavigationItem;
@@ -522,8 +512,6 @@
 // navigation is not back forward navigation.
 - (void)reportBackForwardNavigationTypeForFastNavigation:(BOOL)isFast;
 
-// Sets up WebUI for URL.
-- (void)createWebUIForURL:(const GURL&)URL;
 // Clears WebUI, if one exists.
 - (void)clearWebUI;
 
@@ -632,6 +620,10 @@
   return [self.webView estimatedProgress];
 }
 
+- (BOOL)isWebProcessCrashed {
+  return self.navigationHandler.webProcessCrashed;
+}
+
 - (void)setAllowsBackForwardNavigationGestures:
     (BOOL)allowsBackForwardNavigationGestures {
   // Store it to an instance variable as well as
@@ -967,7 +959,8 @@
 }
 
 - (BOOL)isViewAlive {
-  return !_webProcessCrashed && [_containerView isViewAlive];
+  return !self.navigationHandler.webProcessCrashed &&
+         [_containerView isViewAlive];
 }
 
 - (BOOL)contentIsHTML {
@@ -1156,7 +1149,7 @@
 }
 
 - (void)loadCurrentURLIfNecessary {
-  if (_webProcessCrashed) {
+  if (self.navigationHandler.webProcessCrashed) {
     [self loadCurrentURLWithRendererInitiatedNavigation:NO];
   } else if (!_currentURLLoadWasTrigerred) {
     [self ensureContainerViewCreated];
@@ -1537,18 +1530,7 @@
   return _documentURL;
 }
 
-- (BOOL)shouldClosePageOnNativeApplicationLoad {
-  // The page should be closed if it was initiated by the DOM and there has been
-  // no user interaction with the page since the web view was created, or if
-  // the page has no navigation items, as occurs when an App Store link is
-  // opened from another application.
-  BOOL rendererInitiatedWithoutInteraction =
-      self.hasOpener &&
-      !_userInteractionState.UserInteractionRegisteredSinceWebViewCreated();
-  BOOL noNavigationItems = !(self.navigationManagerImpl->GetItemCount());
-  return rendererInitiatedWithoutInteraction || noNavigationItems;
-}
-
+// Maps WKNavigationType to ui::PageTransition.
 - (ui::PageTransition)pageTransitionFromNavigationType:
     (WKNavigationType)navigationType {
   switch (navigationType) {
@@ -2524,17 +2506,6 @@
   [self didFinishWithURL:targetURL loadSuccess:YES context:context.get()];
 }
 
-// Discards non committed items, only if the last committed URL was not loaded
-// in native view. But if it was a native view, no discard will happen to avoid
-// an ugly animation where the web view is inserted and quickly removed.
-- (void)discardNonCommittedItemsIfLastCommittedWasNotNativeView {
-  GURL lastCommittedURL = self.webState->GetLastCommittedURL();
-  BOOL previousItemWasLoadedInNativeView =
-      [self shouldLoadURLInNativeView:lastCommittedURL];
-  if (!previousItemWasLoadedInNativeView)
-    self.navigationManagerImpl->DiscardNonCommittedItems();
-}
-
 #pragma mark - CRWWebControllerContainerViewDelegate
 
 - (CRWWebViewProxyImpl*)contentViewProxyForContainerView:
@@ -3104,6 +3075,7 @@
 
 #pragma mark - WebUI
 
+// Sets up WebUI for URL.
 - (void)createWebUIForURL:(const GURL&)URL {
   // |CreateWebUI| will do nothing if |URL| is not a WebUI URL and then
   // |HasWebUI| will return false.
@@ -3430,18 +3402,6 @@
   }
 }
 
-#pragma mark - WKNavigationDelegate Helpers
-
-- (BOOL)isMainFrameNavigationAction:(WKNavigationAction*)action {
-  if (action.targetFrame) {
-    return action.targetFrame.mainFrame;
-  }
-  // According to WKNavigationAction documentation, in the case of a new window
-  // navigation, target frame will be nil. In this case check if the
-  // |sourceFrame| is the mainFrame.
-  return action.sourceFrame.mainFrame;
-}
-
 #pragma mark - Security Helpers
 
 - (void)updateSSLStatusForCurrentNavigationItem {
@@ -3715,7 +3675,7 @@
   // once rdar://35063950 is fixed.
   [self removeWebView];
 
-  _webProcessCrashed = YES;
+  self.navigationHandler.webProcessCrashed = YES;
   self.webStateImpl->CancelDialogs();
   self.webStateImpl->OnRenderProcessGone();
 }
@@ -3900,321 +3860,12 @@
 #pragma mark - WKNavigationDelegate Methods
 
 - (void)webView:(WKWebView*)webView
-    decidePolicyForNavigationAction:(WKNavigationAction*)action
+    decidePolicyForNavigationAction:(WKNavigationAction*)navigationAction
                     decisionHandler:
                         (void (^)(WKNavigationActionPolicy))decisionHandler {
   [self.navigationHandler webView:webView
-      decidePolicyForNavigationAction:action
+      decidePolicyForNavigationAction:navigationAction
                       decisionHandler:decisionHandler];
-
-  _webProcessCrashed = NO;
-  if (_isBeingDestroyed) {
-    decisionHandler(WKNavigationActionPolicyCancel);
-    return;
-  }
-
-  GURL requestURL = net::GURLWithNSURL(action.request.URL);
-
-  // Workaround for a WKWebView bug where the web content loaded using
-  // |-loadHTMLString:baseURL| clobbers the next WKBackForwardListItem. It works
-  // by detecting back/forward navigation to a clobbered item and replacing the
-  // clobberred item and its forward history using a partial session restore in
-  // the current web view. There is an unfortunate caveat: if the workaround is
-  // triggered in a back navigation to a clobbered item, the restored forward
-  // session is inserted after the current item before the back navigation, so
-  // it doesn't fully replaces the "bad" history, even though user will be
-  // navigated to the expected URL and may not notice the issue until they
-  // review the back history by long pressing on "Back" button.
-  //
-  // TODO(crbug.com/887497): remove this workaround once iOS ships the fix.
-  if (web::GetWebClient()->IsSlimNavigationManagerEnabled() &&
-      action.targetFrame.mainFrame) {
-    GURL webViewURL = net::GURLWithNSURL(webView.URL);
-    GURL currentWKItemURL =
-        net::GURLWithNSURL(webView.backForwardList.currentItem.URL);
-    GURL backItemURL = net::GURLWithNSURL(webView.backForwardList.backItem.URL);
-    web::NavigationContextImpl* context =
-        [self contextForPendingMainFrameNavigationWithURL:webViewURL];
-    bool willClobberHistory =
-        action.navigationType == WKNavigationTypeBackForward &&
-        requestURL == backItemURL && webView.backForwardList.currentItem &&
-        requestURL != currentWKItemURL && currentWKItemURL == webViewURL &&
-        context &&
-        (context->GetPageTransition() & ui::PAGE_TRANSITION_FORWARD_BACK);
-
-    UMA_HISTOGRAM_BOOLEAN("IOS.WKWebViewClobberedHistory", willClobberHistory);
-
-    if (willClobberHistory && base::FeatureList::IsEnabled(
-                                  web::features::kHistoryClobberWorkaround)) {
-      decisionHandler(WKNavigationActionPolicyCancel);
-      self.navigationManagerImpl
-          ->ApplyWKWebViewForwardHistoryClobberWorkaround();
-      return;
-    }
-  }
-
-  // The page will not be changed until this navigation is committed, so the
-  // retrieved state will be pending until |didCommitNavigation| callback.
-  [self updatePendingNavigationInfoFromNavigationAction:action];
-
-  if (web::GetWebClient()->IsSlimNavigationManagerEnabled() &&
-      action.targetFrame.mainFrame &&
-      action.navigationType == WKNavigationTypeBackForward) {
-    web::NavigationContextImpl* context =
-        [self contextForPendingMainFrameNavigationWithURL:requestURL];
-    if (context) {
-      // Context is null for renderer-initiated navigations.
-      int index = web::GetCommittedItemIndexWithUniqueID(
-          self.navigationManagerImpl, context->GetNavigationItemUniqueID());
-      self.navigationManagerImpl->SetPendingItemIndex(index);
-    }
-  }
-
-  // If this is a placeholder navigation, pass through.
-  if (IsPlaceholderUrl(requestURL)) {
-    decisionHandler(WKNavigationActionPolicyAllow);
-    return;
-  }
-
-  ui::PageTransition transition =
-      [self pageTransitionFromNavigationType:action.navigationType];
-  BOOL isMainFrameNavigationAction = [self isMainFrameNavigationAction:action];
-  if (isMainFrameNavigationAction) {
-    web::NavigationContextImpl* context =
-        [self contextForPendingMainFrameNavigationWithURL:requestURL];
-    if (context) {
-      DCHECK(!context->IsRendererInitiated() ||
-             (context->GetPageTransition() & ui::PAGE_TRANSITION_FORWARD_BACK));
-      transition = context->GetPageTransition();
-      if (context->IsLoadingErrorPage()) {
-        // loadHTMLString: navigation which loads error page into WKWebView.
-        decisionHandler(WKNavigationActionPolicyAllow);
-        return;
-      }
-    }
-  }
-
-  if (web::GetWebClient()->IsSlimNavigationManagerEnabled()) {
-    // WKBasedNavigationManager doesn't use |loadCurrentURL| for reload or back/
-    // forward navigation. So this is the first point where a form repost would
-    // be detected. Display the confirmation dialog.
-    if ([action.request.HTTPMethod isEqual:@"POST"] &&
-        (action.navigationType == WKNavigationTypeFormResubmitted)) {
-      self.webStateImpl->ShowRepostFormWarningDialog(
-          base::BindOnce(^(bool shouldContinue) {
-            if (shouldContinue) {
-              decisionHandler(WKNavigationActionPolicyAllow);
-            } else {
-              decisionHandler(WKNavigationActionPolicyCancel);
-              if (action.targetFrame.mainFrame) {
-                self.navigationHandler.pendingNavigationInfo.cancelled = YES;
-                self.webStateImpl->SetIsLoading(false);
-              }
-            }
-          }));
-      return;
-    }
-  }
-
-  // Invalid URLs should not be loaded.
-  if (!requestURL.is_valid()) {
-    decisionHandler(WKNavigationActionPolicyCancel);
-    // The HTML5 spec indicates that window.open with an invalid URL should open
-    // about:blank.
-    BOOL isFirstLoadInOpenedWindow =
-        self.webState->HasOpener() &&
-        !self.webState->GetNavigationManager()->GetLastCommittedItem();
-    BOOL isMainFrame = action.targetFrame.mainFrame;
-    if (isFirstLoadInOpenedWindow && isMainFrame) {
-      GURL aboutBlankURL(url::kAboutBlankURL);
-      NavigationManager::WebLoadParams loadParams(aboutBlankURL);
-      loadParams.referrer = [self currentReferrer];
-      self.webState->GetNavigationManager()->LoadURLWithParams(loadParams);
-    }
-    return;
-  }
-
-  // First check if the navigation action should be blocked by the controller
-  // and make sure to update the controller in the case that the controller
-  // can't handle the request URL. Then use the embedders' policyDeciders to
-  // either: 1- Handle the URL it self and return false to stop the controller
-  // from proceeding with the navigation if needed. or 2- return true to allow
-  // the navigation to be proceeded by the web controller.
-  BOOL allowLoad = YES;
-  if (web::GetWebClient()->IsAppSpecificURL(requestURL)) {
-    allowLoad = [self shouldAllowAppSpecificURLNavigationAction:action
-                                                     transition:transition];
-    if (allowLoad && !self.webStateImpl->HasWebUI()) {
-      [self createWebUIForURL:requestURL];
-    }
-  }
-
-  BOOL webControllerCanShow =
-      web::UrlHasWebScheme(requestURL) ||
-      web::GetWebClient()->IsAppSpecificURL(requestURL) ||
-      requestURL.SchemeIs(url::kFileScheme) ||
-      requestURL.SchemeIs(url::kAboutScheme) ||
-      requestURL.SchemeIs(url::kBlobScheme);
-
-  if (allowLoad) {
-    // If the URL doesn't look like one that can be shown as a web page, it may
-    // handled by the embedder. In that case, update the web controller to
-    // correctly reflect the current state.
-    if (!webControllerCanShow) {
-      if (!web::features::StorePendingItemInContext()) {
-        if ([self isMainFrameNavigationAction:action]) {
-          [self stopLoading];
-        }
-      }
-
-      // Purge web view if last committed URL is different from the document
-      // URL. This can happen if external URL was added to the navigation stack
-      // and was loaded using Go Back or Go Forward navigation (in which case
-      // document URL will point to the previous page).  If this is the first
-      // load for a NavigationManager, there will be no last committed item, so
-      // check here.
-      // TODO(crbug.com/850760): Check if this code is still needed. The current
-      // implementation doesn't put external apps URLs in the history, so they
-      // shouldn't be accessable by Go Back or Go Forward navigation.
-      web::NavigationItem* lastCommittedItem =
-          self.webState->GetNavigationManager()->GetLastCommittedItem();
-      if (lastCommittedItem) {
-        GURL lastCommittedURL = lastCommittedItem->GetURL();
-        if (lastCommittedURL != _documentURL) {
-          [self requirePageReconstruction];
-          [self setDocumentURL:lastCommittedURL context:nullptr];
-        }
-      }
-    }
-  }
-
-  if (allowLoad) {
-    BOOL userInteractedWithRequestMainFrame =
-        _userInteractionState.HasUserTappedRecently(self.webView) &&
-        net::GURLWithNSURL(action.request.mainDocumentURL) ==
-            _userInteractionState.LastUserInteraction()->main_document_url;
-    web::WebStatePolicyDecider::RequestInfo requestInfo(
-        transition, isMainFrameNavigationAction,
-        userInteractedWithRequestMainFrame);
-
-    allowLoad =
-        self.webStateImpl->ShouldAllowRequest(action.request, requestInfo);
-    // The WebState may have been closed in the ShouldAllowRequest callback.
-    if (_isBeingDestroyed) {
-      decisionHandler(WKNavigationActionPolicyCancel);
-      return;
-    }
-  }
-
-  if (!webControllerCanShow && web::features::StorePendingItemInContext()) {
-    allowLoad = NO;
-  }
-
-  if (allowLoad) {
-    if ([[action.request HTTPMethod] isEqualToString:@"POST"]) {
-      web::NavigationItemImpl* item = self.currentNavItem;
-      // TODO(crbug.com/570699): Remove this check once it's no longer possible
-      // to have no current entries.
-      if (item)
-        [self cachePOSTDataForRequest:action.request inNavigationItem:item];
-    }
-  } else {
-    if (action.targetFrame.mainFrame) {
-      self.navigationHandler.pendingNavigationInfo.cancelled = YES;
-      // Discard the pending item to ensure that the current URL is not
-      // different from what is displayed on the view. Discard only happens
-      // if the last item was not a native view, to avoid ugly animation of
-      // inserting the webview.
-      [self discardNonCommittedItemsIfLastCommittedWasNotNativeView];
-
-      web::NavigationContextImpl* context =
-          [self contextForPendingMainFrameNavigationWithURL:requestURL];
-      if (context) {
-        // Destroy associated pending item, because this will be the last
-        // WKWebView callback for this navigation context.
-        context->ReleaseItem();
-      }
-
-      if (!_isBeingDestroyed && [self shouldClosePageOnNativeApplicationLoad]) {
-        // Loading was started for user initiated navigations and should be
-        // stopped because no other WKWebView callbacks are called.
-        // TODO(crbug.com/767092): Loading should not start until
-        // webView.loading is changed to YES.
-        self.webStateImpl->SetIsLoading(false);
-
-        self.webStateImpl->CloseWebState();
-        decisionHandler(WKNavigationActionPolicyCancel);
-        return;
-      }
-    }
-
-    if (!_isBeingDestroyed) {
-      // Loading was started for user initiated navigations and should be
-      // stopped because no other WKWebView callbacks are called.
-      // TODO(crbug.com/767092): Loading should not start until webView.loading
-      // is changed to YES.
-      self.webStateImpl->SetIsLoading(false);
-    }
-  }
-
-  // Only try to detect a SafeBrowsing warning if one isn't already displayed,
-  // since the detection logic won't be able to distinguish between the current
-  // warning and a warning for the page that's about to be loaded. Also, since
-  // the purpose of running this logic is to ensure that the right URL is
-  // displayed in the omnibox, don't try to detect a SafeBrowsing warning for
-  // iframe navigations, because the omnibox already shows the correct main
-  // frame URL in that case.
-  if (allowLoad && isMainFrameNavigationAction &&
-      !web::IsSafeBrowsingWarningDisplayedInWebView(self.webView)) {
-    __weak CRWWebController* weakSelf = self;
-    const base::TimeDelta kDelayUntilSafeBrowsingWarningCheck =
-        base::TimeDelta::FromMilliseconds(20);
-    self.navigationHandler.safeBrowsingWarningDetectionTimer->Start(
-        FROM_HERE, kDelayUntilSafeBrowsingWarningCheck, base::BindRepeating(^{
-          __strong __typeof(weakSelf) strongSelf = weakSelf;
-          if (web::IsSafeBrowsingWarningDisplayedInWebView(
-                  strongSelf.webView)) {
-            // Extract state from an existing navigation context if one exists.
-            // Create a new context rather than just re-using the existing one,
-            // since the existing context will continue to be used if the user
-            // decides to proceed to the unsafe page. In that case, WebKit
-            // continues the navigation with the same WKNavigation* that's
-            // associated with the existing context.
-            web::NavigationContextImpl* existingContext = [strongSelf
-                contextForPendingMainFrameNavigationWithURL:requestURL];
-            bool hasUserGesture =
-                existingContext ? existingContext->HasUserGesture() : false;
-            bool isRendererInitiated =
-                existingContext ? existingContext->IsRendererInitiated() : true;
-            std::unique_ptr<web::NavigationContextImpl> context =
-                web::NavigationContextImpl::CreateNavigationContext(
-                    strongSelf->_webStateImpl, requestURL, hasUserGesture,
-                    transition, isRendererInitiated);
-            strongSelf.navigationManagerImpl->AddTransientItem(requestURL);
-            strongSelf.webStateImpl->OnNavigationStarted(context.get());
-            strongSelf.webStateImpl->OnNavigationFinished(context.get());
-            strongSelf.navigationHandler.safeBrowsingWarningDetectionTimer
-                ->Stop();
-            if (!existingContext) {
-              // If there's an existing context, observers will already be aware
-              // of a load in progress. Otherwise, observers need to be notified
-              // here, so that if the user decides to go back to the previous
-              // page (stopping the load), observers will be aware of a possible
-              // URL change and the URL displayed in the omnibox will get
-              // updated.
-              DCHECK(strongSelf->_webView.loading);
-              strongSelf->_webStateImpl->SetIsLoading(true);
-            }
-          }
-        }));
-  }
-
-  if (!allowLoad) {
-    decisionHandler(WKNavigationActionPolicyCancel);
-    return;
-  }
-  BOOL isOffTheRecord = self.webState->GetBrowserState()->IsOffTheRecord();
-  decisionHandler(web::GetAllowNavigationActionPolicy(isOffTheRecord));
 }
 
 - (void)webView:(WKWebView*)webView
@@ -4255,7 +3906,8 @@
     }
     // Discard the pending item to ensure that the current URL is not different
     // from what is displayed on the view.
-    [self discardNonCommittedItemsIfLastCommittedWasNotNativeView];
+    [self.navigationHandler
+            discardNonCommittedItemsIfLastCommittedWasNotNativeView];
     if (!web::features::StorePendingItemInContext()) {
       // Loading will be stopped in webView:didFinishNavigation: callback. This
       // call is here to preserve the original behavior when pending item is not
@@ -5002,107 +4654,6 @@
   _pageHasZoomed = NO;
 }
 
-// Caches request POST data in the given session entry.
-- (void)cachePOSTDataForRequest:(NSURLRequest*)request
-               inNavigationItem:(web::NavigationItemImpl*)item {
-  NSUInteger maxPOSTDataSizeInBytes = 4096;
-  NSString* cookieHeaderName = @"cookie";
-
-  DCHECK(item);
-  const bool shouldUpdateEntry =
-      ui::PageTransitionCoreTypeIs(item->GetTransitionType(),
-                                   ui::PAGE_TRANSITION_FORM_SUBMIT) &&
-      ![request HTTPBodyStream] &&  // Don't cache streams.
-      !item->HasPostData() &&
-      item->GetURL() == net::GURLWithNSURL([request URL]);
-  const bool belowSizeCap =
-      [[request HTTPBody] length] < maxPOSTDataSizeInBytes;
-  DLOG_IF(WARNING, shouldUpdateEntry && !belowSizeCap)
-      << "Data in POST request exceeds the size cap (" << maxPOSTDataSizeInBytes
-      << " bytes), and will not be cached.";
-
-  if (shouldUpdateEntry && belowSizeCap) {
-    item->SetPostData([request HTTPBody]);
-    item->ResetHttpRequestHeaders();
-    item->AddHttpRequestHeaders([request allHTTPHeaderFields]);
-    // Don't cache the "Cookie" header.
-    // According to NSURLRequest documentation, |-valueForHTTPHeaderField:| is
-    // case insensitive, so it's enough to test the lower case only.
-    if ([request valueForHTTPHeaderField:cookieHeaderName]) {
-      // Case insensitive search in |headers|.
-      NSSet* cookieKeys = [item->GetHttpRequestHeaders()
-          keysOfEntriesPassingTest:^(id key, id obj, BOOL* stop) {
-            NSString* header = (NSString*)key;
-            const BOOL found =
-                [header caseInsensitiveCompare:cookieHeaderName] ==
-                NSOrderedSame;
-            *stop = found;
-            return found;
-          }];
-      DCHECK_EQ(1u, [cookieKeys count]);
-      item->RemoveHttpRequestHeaderForKey([cookieKeys anyObject]);
-    }
-  }
-}
-
-// Returns YES if the given |action| should be allowed to continue for app
-// specific URL. If this returns NO, the navigation should be cancelled.
-// App specific pages have elevated privileges and WKWebView uses the same
-// renderer process for all page frames. With that Chromium does not allow
-// running App specific pages in the same process as a web site from the
-// internet. Allows navigation to app specific URL in the following cases:
-//   - last committed URL is app specific
-//   - navigation not a new navigation (back-forward or reload)
-//   - navigation is typed, generated or bookmark
-//   - navigation is performed in iframe and main frame is app-specific page
-- (BOOL)shouldAllowAppSpecificURLNavigationAction:(WKNavigationAction*)action
-                                       transition:
-                                           (ui::PageTransition)pageTransition {
-  GURL requestURL = net::GURLWithNSURL(action.request.URL);
-  DCHECK(web::GetWebClient()->IsAppSpecificURL(requestURL));
-  if (web::GetWebClient()->IsAppSpecificURL(
-          self.webStateImpl->GetLastCommittedURL())) {
-    // Last committed page is also app specific and navigation should be
-    // allowed.
-    return YES;
-  }
-
-  if (!ui::PageTransitionIsNewNavigation(pageTransition)) {
-    // Allow reloads and back-forward navigations.
-    return YES;
-  }
-
-  if (ui::PageTransitionTypeIncludingQualifiersIs(pageTransition,
-                                                  ui::PAGE_TRANSITION_TYPED)) {
-    return YES;
-  }
-
-  if (ui::PageTransitionTypeIncludingQualifiersIs(
-          pageTransition, ui::PAGE_TRANSITION_GENERATED)) {
-    return YES;
-  }
-
-  if (ui::PageTransitionTypeIncludingQualifiersIs(
-          pageTransition, ui::PAGE_TRANSITION_AUTO_BOOKMARK)) {
-    return YES;
-  }
-
-  // If the session is being restored, allow the navigation.
-  if (IsRestoreSessionUrl(_documentURL)) {
-    return YES;
-  }
-
-  GURL mainDocumentURL = net::GURLWithNSURL(action.request.mainDocumentURL);
-  if (web::GetWebClient()->IsAppSpecificURL(mainDocumentURL) &&
-      !action.sourceFrame.mainFrame) {
-    // AppSpecific URLs are allowed inside iframe if the main frame is also
-    // app specific page.
-    return YES;
-  }
-
-  return NO;
-}
-
 // Called when a load ends in an error.
 - (void)handleLoadError:(NSError*)error
           forNavigation:(WKNavigation*)navigation
@@ -5293,8 +4844,8 @@
   if (responseURL.SchemeIs(url::kDataScheme) && WKResponse.forMainFrame) {
     // Block rendering data URLs for renderer-initiated navigations in main
     // frame to prevent abusive behavior (crbug.com/890558).
-    web::NavigationContext* context =
-        [self contextForPendingMainFrameNavigationWithURL:responseURL];
+    web::NavigationContext* context = [self.navigationHandler
+        contextForPendingMainFrameNavigationWithURL:responseURL];
     if (context->IsRendererInitiated()) {
       return NO;
     }
@@ -5320,8 +4871,8 @@
 
   ui::PageTransition transition = ui::PAGE_TRANSITION_AUTO_SUBFRAME;
   if (WKResponse.forMainFrame) {
-    web::NavigationContextImpl* context =
-        [self contextForPendingMainFrameNavigationWithURL:responseURL];
+    web::NavigationContextImpl* context = [self.navigationHandler
+        contextForPendingMainFrameNavigationWithURL:responseURL];
     context->SetIsDownload(true);
     context->ReleaseItem();
     // Navigation callbacks can only be called for the main frame.
@@ -5344,27 +4895,6 @@
                            MIMEType, transition);
 }
 
-// Extracts navigation info from WKNavigationAction and sets it as a pending.
-// Some pieces of navigation information are only known in
-// |decidePolicyForNavigationAction|, but must be in a pending state until
-// |didgo/Navigation| where it becames current.
-- (void)updatePendingNavigationInfoFromNavigationAction:
-    (WKNavigationAction*)action {
-  if (action.targetFrame.mainFrame) {
-    self.navigationHandler.pendingNavigationInfo =
-        [[CRWPendingNavigationInfo alloc] init];
-    self.navigationHandler.pendingNavigationInfo.referrer =
-        [action.request valueForHTTPHeaderField:kReferrerHeaderName];
-    self.navigationHandler.pendingNavigationInfo.navigationType =
-        action.navigationType;
-    self.navigationHandler.pendingNavigationInfo.HTTPMethod =
-        action.request.HTTPMethod;
-    self.navigationHandler.pendingNavigationInfo.hasUserGesture =
-        web::GetNavigationActionInitiationType(action) ==
-        web::NavigationActionInitiationType::kUserInitiated;
-  }
-}
-
 // Extracts navigation info from WKNavigationResponse and sets it as a pending.
 // Some pieces of navigation information are only known in
 // |decidePolicyForNavigationResponse|, but must be in a pending state until
@@ -5422,30 +4952,6 @@
         self.navigationHandler.pendingNavigationInfo.MIMEType);
 }
 
-// Returns context for pending navigation that has |URL|. null if there is no
-// matching pending navigation.
-- (web::NavigationContextImpl*)contextForPendingMainFrameNavigationWithURL:
-    (const GURL&)URL {
-  // Here the enumeration variable |navigation| is __strong to allow setting it
-  // to nil.
-  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;
-    }
-
-    web::NavigationContextImpl* context =
-        [self.navigationHandler.navigationStates
-            contextForNavigation:navigation];
-    if (context && context->GetUrl() == URL) {
-      return context;
-    }
-  }
-  return nullptr;
-}
-
 // Updates URL for navigation context and navigation item.
 - (void)didReceiveRedirectForNavigation:(web::NavigationContextImpl*)context
                                 withURL:(const GURL&)URL {
@@ -5606,8 +5112,8 @@
   if (![self isCurrentNavigationBackForward])
     return;
 
-  web::NavigationContextImpl* existingContext =
-      [self contextForPendingMainFrameNavigationWithURL:webViewURL];
+  web::NavigationContextImpl* existingContext = [self.navigationHandler
+      contextForPendingMainFrameNavigationWithURL:webViewURL];
 
   // When traversing history restored from a previous session, WKWebView does
   // not fire 'pageshow', 'onload', 'popstate' or any of the
@@ -5693,7 +5199,7 @@
 - (void)webViewTitleDidChange {
   // WKWebView's title becomes empty when the web process dies; ignore that
   // update.
-  if (_webProcessCrashed) {
+  if (self.navigationHandler.webProcessCrashed) {
     DCHECK_EQ(self.webView.title.length, 0U);
     return;
   }
@@ -5941,7 +5447,8 @@
   // context object.
   std::unique_ptr<web::NavigationContextImpl> newNavigationContext;
   if (!_changingHistoryState) {
-    if ([self contextForPendingMainFrameNavigationWithURL:newURL]) {
+    if ([self.navigationHandler
+            contextForPendingMainFrameNavigationWithURL:newURL]) {
       // NavigationManager::LoadURLWithParams() was called with URL that has
       // different fragment comparing to the previous URL.
     } else {
@@ -5977,8 +5484,8 @@
     // existed before.
     web::NavigationContextImpl* navigationContext = newNavigationContext.get();
     if (!navigationContext) {
-      navigationContext =
-          [self contextForPendingMainFrameNavigationWithURL:newURL];
+      navigationContext = [self.navigationHandler
+          contextForPendingMainFrameNavigationWithURL:newURL];
     }
     navigationContext->SetIsSameDocument(true);
     self.webStateImpl->OnNavigationStarted(navigationContext);
@@ -5997,7 +5504,59 @@
 
 - (BOOL)navigationHandlerWebViewBeingDestroyed:
     (CRWWKNavigationHandler*)navigationHandler {
-  return _beingDestroyed;
+  return _isBeingDestroyed;
+}
+
+- (web::WebStateImpl*)webStateImplForNavigationHandler:
+    (CRWWKNavigationHandler*)navigationHandler {
+  return self.webStateImpl;
+}
+
+- (web::UserInteractionState*)userInteractionStateForNavigationHandler:
+    (CRWWKNavigationHandler*)navigationHandler {
+  return &_userInteractionState;
+}
+
+- (web::Referrer)currentReferrerForNavigationHandler:
+    (CRWWKNavigationHandler*)navigationHandler {
+  return self.currentReferrer;
+}
+
+- (GURL)navigationHandlerDocumentURL:
+    (CRWWKNavigationHandler*)navigationHandler {
+  return _documentURL;
+}
+
+- (ui::PageTransition)navigationHandler:
+                          (CRWWKNavigationHandler*)navigationHandler
+       pageTransitionFromNavigationType:(WKNavigationType)navigationType {
+  return [self pageTransitionFromNavigationType:navigationType];
+}
+
+- (void)navigationHandler:(CRWWKNavigationHandler*)navigationHandler
+        createWebUIForURL:(const GURL&)URL {
+  [self createWebUIForURL:URL];
+}
+
+- (void)navigationHandlerStopLoading:
+    (CRWWKNavigationHandler*)navigationHandler {
+  [self stopLoading];
+}
+
+- (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 shouldLoadURLInNativeView:url];
+}
+
+- (void)navigationHandlerRequirePageReconstruction:
+    (CRWWKNavigationHandler*)navigationHandler {
+  [self requirePageReconstruction];
 }
 
 #pragma mark - Testing-Only Methods