Infobar material design refresh: bg color
[chromium-blink-merge.git] / ios / web / web_state / ui / crw_wk_web_view_web_controller.mm
blob0d1845039df51e0c8d9b331d76f3f73df2746cab
1 // Copyright 2014 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.
5 #import "ios/web/web_state/ui/crw_wk_web_view_web_controller.h"
7 #import <WebKit/WebKit.h>
9 #include "base/ios/ios_util.h"
10 #include "base/ios/weak_nsobject.h"
11 #include "base/json/json_reader.h"
12 #import "base/mac/scoped_nsobject.h"
13 #include "base/macros.h"
14 #include "base/strings/sys_string_conversions.h"
15 #include "base/values.h"
16 #import "ios/net/http_response_headers_util.h"
17 #import "ios/web/crw_network_activity_indicator_manager.h"
18 #import "ios/web/navigation/crw_session_controller.h"
19 #import "ios/web/navigation/crw_session_entry.h"
20 #include "ios/web/navigation/navigation_item_impl.h"
21 #include "ios/web/navigation/web_load_params.h"
22 #include "ios/web/public/web_client.h"
23 #import "ios/web/public/web_state/js/crw_js_injection_manager.h"
24 #import "ios/web/public/web_state/ui/crw_native_content_provider.h"
25 #import "ios/web/public/web_state/ui/crw_web_view_content_view.h"
26 #import "ios/web/ui_web_view_util.h"
27 #include "ios/web/web_state/blocked_popup_info.h"
28 #import "ios/web/web_state/error_translation_util.h"
29 #include "ios/web/web_state/frame_info.h"
30 #import "ios/web/web_state/js/crw_js_window_id_manager.h"
31 #import "ios/web/web_state/js/page_script_util.h"
32 #import "ios/web/web_state/ui/crw_web_controller+protected.h"
33 #import "ios/web/web_state/ui/crw_wk_web_view_crash_detector.h"
34 #import "ios/web/web_state/ui/web_view_js_utils.h"
35 #import "ios/web/web_state/ui/wk_back_forward_list_item_holder.h"
36 #import "ios/web/web_state/ui/wk_web_view_configuration_provider.h"
37 #import "ios/web/web_state/web_state_impl.h"
38 #import "ios/web/web_state/web_view_internal_creation_util.h"
39 #import "ios/web/webui/crw_web_ui_manager.h"
40 #import "net/base/mac/url_conversions.h"
41 #include "url/url_constants.h"
43 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
44 #include "ios/web/public/cert_store.h"
45 #include "ios/web/public/navigation_item.h"
46 #include "ios/web/public/ssl_status.h"
47 #import "ios/web/web_state/wk_web_view_security_util.h"
48 #include "net/cert/x509_certificate.h"
49 #include "net/ssl/ssl_info.h"
50 #endif
52 namespace {
53 // Extracts Referer value from WKNavigationAction request header.
54 NSString* GetRefererFromNavigationAction(WKNavigationAction* action) {
55   return [action.request valueForHTTPHeaderField:@"Referer"];
58 NSString* const kScriptMessageName = @"crwebinvoke";
59 NSString* const kScriptImmediateName = @"crwebinvokeimmediate";
61 // Utility functions for storing the source of NSErrors received by WKWebViews:
62 // - Errors received by |-webView:didFailProvisionalNavigation:withError:| are
63 //   recorded using WKWebViewErrorSource::PROVISIONAL_LOAD.  These should be
64 //   aborted.
65 // - Errors received by |-webView:didFailNavigation:withError:| are recorded
66 //   using WKWebViewsource::NAVIGATION.  These errors should not be aborted, as
67 //   the WKWebView will automatically retry the load.
68 NSString* const kWKWebViewErrorSourceKey = @"ErrorSource";
69 typedef enum { NONE = 0, PROVISIONAL_LOAD, NAVIGATION } WKWebViewErrorSource;
70 NSError* WKWebViewErrorWithSource(NSError* error, WKWebViewErrorSource source) {
71   DCHECK(error);
72   base::scoped_nsobject<NSMutableDictionary> userInfo(
73       [error.userInfo mutableCopy]);
74   [userInfo setObject:@(source) forKey:kWKWebViewErrorSourceKey];
75   return [NSError errorWithDomain:error.domain
76                              code:error.code
77                          userInfo:userInfo];
79 WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) {
80   DCHECK(error);
81   return static_cast<WKWebViewErrorSource>(
82       [error.userInfo[kWKWebViewErrorSourceKey] integerValue]);
85 }  // namespace
87 @interface CRWWKWebViewWebController () <WKNavigationDelegate,
88                                          WKScriptMessageHandler,
89                                          WKUIDelegate> {
90   // The WKWebView managed by this instance.
91   base::scoped_nsobject<WKWebView> _wkWebView;
93   // The Watch Dog that detects and reports WKWebView crashes.
94   base::scoped_nsobject<CRWWKWebViewCrashDetector> _crashDetector;
96   // The actual URL of the document object (i.e., the last committed URL).
97   // TODO(stuartmorgan): Remove this in favor of just updating the session
98   // controller and treating that as authoritative. For now, this allows sharing
99   // the flow that's currently in the superclass.
100   GURL _documentURL;
102   // A set of script managers whose scripts have been injected into the current
103   // page.
104   // TODO(stuartmorgan): Revisit this approach; it's intended only as a stopgap
105   // measure to make all the existing script managers work. Longer term, there
106   // should probably be a couple of points where managers can register to have
107   // things happen automatically based on page lifecycle, and if they don't want
108   // to use one of those fixed points, they should make their scripts internally
109   // idempotent.
110   base::scoped_nsobject<NSMutableSet> _injectedScriptManagers;
112   // Referrer pending for the next navigated page. Lifetime of this value starts
113   // at |decidePolicyForNavigationAction| where the referrer is extracted from
114   // the request and ends at |didCommitNavigation| where the request is
115   // committed.
116   base::scoped_nsobject<NSString> _pendingReferrerString;
118   // Referrer for the current page.
119   base::scoped_nsobject<NSString> _currentReferrerString;
121   // Backs the property of the same name.
122   base::scoped_nsobject<NSString> _documentMIMEType;
124   // Navigation type of the pending navigation action of the main frame. This
125   // value is assigned at |decidePolicyForNavigationAction| where the navigation
126   // type is extracted from the request and associated with a committed
127   // navigation item at |didCommitNavigation|.
128   scoped_ptr<WKNavigationType> _pendingNavigationTypeForMainFrame;
130   // Whether the web page is currently performing window.history.pushState or
131   // window.history.replaceState
132   // Set to YES on window.history.willChangeState message. To NO on
133   // window.history.didPushState or window.history.didReplaceState.
134   BOOL _changingHistoryState;
136   // CRWWebUIManager object for loading WebUI pages.
137   base::scoped_nsobject<CRWWebUIManager> _webUIManager;
140 // Response's MIME type of the last known navigation.
141 @property(nonatomic, copy) NSString* documentMIMEType;
143 // Dictionary where keys are the names of WKWebView properties and values are
144 // selector names which should be called when a corresponding property has
145 // changed. e.g. @{ @"URL" : @"webViewURLDidChange" } means that
146 // -[self webViewURLDidChange] must be called every time when WKWebView.URL is
147 // changed.
148 @property(nonatomic, readonly) NSDictionary* wkWebViewObservers;
150 // Returns the string to use as the request group ID in the user agent. Returns
151 // nil unless the network stack is enabled.
152 @property(nonatomic, readonly) NSString* requestGroupIDForUserAgent;
154 // Activity indicator group ID for this web controller.
155 @property(nonatomic, readonly) NSString* activityIndicatorGroupID;
157 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
158 // Identifier used for storing and retrieving certificates.
159 @property(nonatomic, readonly) int certGroupID;
160 #endif  // #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
162 // Returns the WKWebViewConfigurationProvider associated with the web
163 // controller's BrowserState.
164 - (web::WKWebViewConfigurationProvider&)webViewConfigurationProvider;
166 // Creates a web view with given |config|. No-op if web view is already created.
167 - (void)ensureWebViewCreatedWithConfiguration:(WKWebViewConfiguration*)config;
169 // Returns a new autoreleased web view created with given configuration.
170 - (WKWebView*)createWebViewWithConfiguration:(WKWebViewConfiguration*)config;
172 // Sets the value of the webView property, and performs its basic setup.
173 - (void)setWebView:(WKWebView*)webView;
175 // Returns whether the given navigation is triggered by a user link click.
176 - (BOOL)isLinkNavigation:(WKNavigationType)navigationType;
178 // Sets value of the pendingReferrerString property.
179 - (void)setPendingReferrerString:(NSString*)referrer;
181 // Extracts the referrer value from WKNavigationAction and sets it as a pending.
182 // The referrer is known in |decidePolicyForNavigationAction| however it must
183 // be in a pending state until |didCommitNavigation| where it becames current.
184 - (void)updatePendingReferrerFromNavigationAction:(WKNavigationAction*)action;
186 // Replaces the current referrer with the pending one. Referrer becames current
187 // at |didCommitNavigation| callback.
188 - (void)commitPendingReferrerString;
190 // Discards the pending referrer.
191 - (void)discardPendingReferrerString;
193 // Extracts the current navigation type from WKNavigationAction and sets it as
194 // the pending navigation type. The value should be considered pending until it
195 // becomes associated with a navigation item at |didCommitNavigation|.
196 - (void)updatePendingNavigationTypeForMainFrameFromNavigationAction:
197     (WKNavigationAction*)action;
199 // Returns the WKBackForwardListItemHolder for the current navigation item.
200 - (web::WKBackForwardListItemHolder*)currentBackForwardListItemHolder;
202 // Stores the current WKBackForwardListItem and the current navigation type
203 // with the current navigation item.
204 - (void)updateCurrentBackForwardListItemHolder;
206 // Returns YES if the given WKBackForwardListItem is valid to use for
207 // navigation.
208 - (BOOL)isBackForwardListItemValid:(WKBackForwardListItem*)item;
210 // Returns a new CRWWKWebViewCrashDetector created with the given |webView| or
211 // nil if |webView| is nil. Callers are responsible for releasing the object.
212 - (CRWWKWebViewCrashDetector*)newCrashDetectorWithWebView:(WKWebView*)webView;
214 // Called when web view process has been terminated.
215 - (void)webViewWebProcessDidCrash;
217 // Asynchronously returns the referrer policy for the current page.
218 - (void)queryPageReferrerPolicy:(void(^)(NSString*))responseHandler;
220 // Informs CWRWebDelegate that CRWWebController has detected and blocked a
221 // popup.
222 - (void)didBlockPopupWithURL:(GURL)popupURL
223                    sourceURL:(GURL)sourceURL
224               referrerPolicy:(const std::string&)referrerPolicyString;
226 // Convenience method to inform CWRWebDelegate about a blocked popup.
227 - (void)didBlockPopupWithURL:(GURL)popupURL sourceURL:(GURL)sourceURL;
229 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
230 // Called when a load ends in an SSL error.
231 - (void)handleSSLError:(NSError*)error;
232 #endif
234 // Adds an activity indicator tasks for this web controller.
235 - (void)addActivityIndicatorTask;
237 // Clears all activity indicator tasks for this web controller.
238 - (void)clearActivityIndicatorTasks;
240 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
241 // Updates SSL status for the current navigation item based on the information
242 // provided by web view.
243 - (void)updateSSLStatusForCurrentNavigationItem;
244 #endif
246 // Registers load request with empty referrer and link or client redirect
247 // transition based on user interaction state.
248 - (void)registerLoadRequest:(const GURL&)url;
250 // Called when a non-document-changing URL change occurs. Updates the
251 // _documentURL, and informs the superclass of the change.
252 - (void)URLDidChangeWithoutDocumentChange:(const GURL&)URL;
254 // Returns new autoreleased instance of WKUserContentController which has
255 // early page script.
256 - (WKUserContentController*)createUserContentController;
258 // Attempts to handle a script message. Returns YES on success, NO otherwise.
259 - (BOOL)respondToWKScriptMessage:(WKScriptMessage*)scriptMessage;
261 // Used to decide whether a load that generates errors with the
262 // NSURLErrorCancelled code should be cancelled.
263 - (BOOL)shouldAbortLoadForCancelledError:(NSError*)error;
265 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
266 // Called when WKWebView estimatedProgress has been changed.
267 - (void)webViewEstimatedProgressDidChange;
269 // Called when WKWebView certificateChain or hasOnlySecureContent property has
270 // changed.
271 - (void)webViewSecurityFeaturesDidChange;
272 #endif  // !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
274 // Called when WKWebView loading state has been changed.
275 - (void)webViewLoadingStateDidChange;
277 // Called when WKWebView title has been changed.
278 - (void)webViewTitleDidChange;
280 // Called when WKWebView URL has been changed.
281 - (void)webViewURLDidChange;
283 @end
285 @implementation CRWWKWebViewWebController
287 #pragma mark CRWWebController public methods
289 - (instancetype)initWithWebState:(scoped_ptr<web::WebStateImpl>)webState {
290   return [super initWithWebState:webState.Pass()];
293 - (BOOL)keyboardDisplayRequiresUserAction {
294   // TODO(stuartmorgan): Find out whether YES or NO is correct; see comment
295   // in protected header.
296   NOTIMPLEMENTED();
297   return NO;
300 - (void)setKeyboardDisplayRequiresUserAction:(BOOL)requiresUserAction {
301   NOTIMPLEMENTED();
304 - (void)evaluateJavaScript:(NSString*)script
305        stringResultHandler:(web::JavaScriptCompletion)handler {
306   NSString* safeScript = [self scriptByAddingWindowIDCheckForScript:script];
307   web::EvaluateJavaScript(_wkWebView, safeScript, handler);
310 - (web::WebViewType)webViewType {
311   return web::WK_WEB_VIEW_TYPE;
314 - (void)evaluateUserJavaScript:(NSString*)script {
315   [self setUserInteractionRegistered:YES];
316   web::EvaluateJavaScript(_wkWebView, script, nil);
319 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
320 - (void)terminateNetworkActivity {
321   web::CertStore::GetInstance()->RemoveCertsForGroup(self.certGroupID);
322   [super terminateNetworkActivity];
324 #endif  // #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
326 - (void)setPageDialogOpenPolicy:(web::PageDialogOpenPolicy)policy {
327   // TODO(eugenebut): implement dialogs/windows suppression using
328   // WKNavigationDelegate methods where possible.
329   [super setPageDialogOpenPolicy:policy];
332 #pragma mark -
333 #pragma mark Testing-Only Methods
335 - (void)injectWebViewContentView:(CRWWebViewContentView*)webViewContentView {
336   [super injectWebViewContentView:webViewContentView];
337   [self setWebView:static_cast<WKWebView*>(webViewContentView.webView)];
340 #pragma mark - Protected property implementations
342 - (UIView*)webView {
343   return _wkWebView.get();
346 - (UIScrollView*)webScrollView {
347   return [_wkWebView scrollView];
350 - (BOOL)ignoreURLVerificationFailures {
351   return NO;
354 - (NSString*)title {
355   return [_wkWebView title];
358 - (NSString*)currentReferrerString {
359   return _currentReferrerString.get();
362 #pragma mark Protected method implementations
364 - (void)ensureWebViewCreated {
365   WKWebViewConfiguration* config =
366       [self webViewConfigurationProvider].GetWebViewConfiguration();
367   [self ensureWebViewCreatedWithConfiguration:config];
370 - (void)resetWebView {
371   [self setWebView:nil];
374 - (GURL)webURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel {
375   DCHECK(trustLevel);
376   *trustLevel = web::URLVerificationTrustLevel::kAbsolute;
377   return _documentURL;
380 // TODO(stuartmorgan): Remove this method and use the API for WKWebView,
381 // making the reset-on-each-load behavior specific to the UIWebView subclass.
382 - (void)registerUserAgent {
383   web::BuildAndRegisterUserAgentForUIWebView([self requestGroupIDForUserAgent],
384                                              [self useDesktopUserAgent]);
387 // The core.js cannot pass messages back to obj-c  if it is injected
388 // to |WEB_VIEW_DOCUMENT| because it does not support iframe creation used
389 // by core.js to communicate back. That functionality is only supported
390 // by |WEB_VIEW_HTML_DOCUMENT|. |WEB_VIEW_DOCUMENT| is used when displaying
391 // non-HTML contents (e.g. PDF documents).
392 - (web::WebViewDocumentType)webViewDocumentType {
393   // This happens during tests.
394   if (!_wkWebView) {
395     return web::WEB_VIEW_DOCUMENT_TYPE_GENERIC;
396   }
398   if (!self.documentMIMEType) {
399     return web::WEB_VIEW_DOCUMENT_TYPE_UNKNOWN;
400   }
402   if ([self.documentMIMEType isEqualToString:@"text/html"] ||
403       [self.documentMIMEType isEqualToString:@"application/xhtml+xml"] ||
404       [self.documentMIMEType isEqualToString:@"application/xml"]) {
405     return web::WEB_VIEW_DOCUMENT_TYPE_HTML;
406   }
408   return web::WEB_VIEW_DOCUMENT_TYPE_GENERIC;
411 - (void)loadRequest:(NSMutableURLRequest*)request {
412   [_wkWebView loadRequest:request];
415 - (void)loadWebHTMLString:(NSString*)html forURL:(const GURL&)URL {
416   [_wkWebView loadHTMLString:html baseURL:net::NSURLWithGURL(URL)];
419 - (BOOL)scriptHasBeenInjectedForClass:(Class)jsInjectionManagerClass
420                        presenceBeacon:(NSString*)beacon {
421   return [_injectedScriptManagers containsObject:jsInjectionManagerClass];
424 - (void)injectScript:(NSString*)script forClass:(Class)JSInjectionManagerClass {
425   // Skip evaluation if there's no content (e.g., if what's being injected is
426   // an umbrella manager).
427   if ([script length]) {
428     [super injectScript:script forClass:JSInjectionManagerClass];
429     // Every injection except windowID requires windowID check.
430     if (JSInjectionManagerClass != [CRWJSWindowIdManager class])
431       script = [self scriptByAddingWindowIDCheckForScript:script];
432     web::EvaluateJavaScript(_wkWebView, script, nil);
433   }
434   [_injectedScriptManagers addObject:JSInjectionManagerClass];
437 - (void)willLoadCurrentURLInWebView {
438   // TODO(stuartmorgan): Get a WKWebView version of the request ID verification
439   // code working for debug builds.
442 - (void)loadRequestForCurrentNavigationItem {
443   DCHECK(self.webView && !self.nativeController);
445   ProceduralBlock defaultNavigationBlock = ^{
446     [self registerLoadRequest:[self currentNavigationURL]
447                      referrer:[self currentSessionEntryReferrer]
448                    transition:[self currentTransition]];
449     [self loadRequest:[self requestForCurrentNavigationItem]];
450   };
452   // If there is no corresponding WKBackForwardListItem, fall back to
453   // the standard loading mechanism.
454   web::WKBackForwardListItemHolder* holder =
455       [self currentBackForwardListItemHolder];
456   if (!holder->back_forward_list_item()) {
457     defaultNavigationBlock();
458     return;
459   }
461   // The current back-forward list item MUST be in the WKWebView's back-forward
462   // list to be valid.
463   DCHECK([self isBackForwardListItemValid:holder->back_forward_list_item()]);
465   ProceduralBlock webViewNavigationBlock = ^{
466     // If the current navigation URL is the same as the URL of the visible
467     // page, that means the user requested a reload. |goToBackForwardListItem|
468     // will be a no-op when it is passed the current back forward list item,
469     // so |reload| must be explicitly called.
470     [self registerLoadRequest:[self currentNavigationURL]
471                      referrer:[self currentSessionEntryReferrer]
472                    transition:[self currentTransition]];
473     if ([self currentNavigationURL] == net::GURLWithNSURL([_wkWebView URL])) {
474       [_wkWebView reload];
475     } else {
476       [_wkWebView goToBackForwardListItem:holder->back_forward_list_item()];
477     }
478   };
480   // If the request is not a form submission or resubmission, or the user
481   // doesn't need to confirm the load, then continue right away.
482   web::NavigationItemImpl* currentItem =
483       [self currentSessionEntry].navigationItemImpl;
484   if ((holder->navigation_type() != WKNavigationTypeFormResubmitted &&
485        holder->navigation_type() != WKNavigationTypeFormSubmitted) ||
486       currentItem->ShouldSkipResubmitDataConfirmation()) {
487     webViewNavigationBlock();
488     return;
489   }
491   // If the request is form submission or resubmission, then prompt the
492   // user before proceeding.
493   [self.delegate webController:self
494       onFormResubmissionForRequest:nil
495                      continueBlock:webViewNavigationBlock
496                        cancelBlock:defaultNavigationBlock];
499 // Overrides the hashchange workaround in the super class that manually
500 // triggers Javascript hashchange events. If navigating with native API,
501 // i.e. using a back forward list item, hashchange events will be triggered
502 // automatically, so no URL tampering is required.
503 - (GURL)URLForHistoryNavigationFromItem:(web::NavigationItem*)fromItem
504                                  toItem:(web::NavigationItem*)toItem {
505   web::WKBackForwardListItemHolder* holder =
506       web::WKBackForwardListItemHolder::FromNavigationItem(toItem);
508   if (holder->back_forward_list_item()) {
509     return toItem->GetURL();
510   }
511   return [super URLForHistoryNavigationFromItem:fromItem toItem:toItem];
514 - (void)setPageChangeProbability:(web::PageChangeProbability)probability {
515   // Nothing to do; no polling timer.
518 - (void)abortWebLoad {
519   [_wkWebView stopLoading];
522 - (void)resetLoadState {
523   // Nothing to do.
526 - (void)setSuppressDialogsWithHelperScript:(NSString*)script {
527   [self evaluateJavaScript:script stringResultHandler:nil];
530 - (void)applyWebViewScrollZoomScaleFromZoomState:
531     (const web::PageZoomState&)zoomState {
532   // After rendering a web page, WKWebView keeps the |minimumZoomScale| and
533   // |maximumZoomScale| properties of its scroll view constant while adjusting
534   // the |zoomScale| property accordingly.  The maximum-scale or minimum-scale
535   // meta tags of a page may have changed since the state was recorded, so clamp
536   // the zoom scale to the current range if necessary.
537   DCHECK(zoomState.IsValid());
538   // Legacy-format scroll states cannot be applied to WKWebViews.
539   if (zoomState.IsLegacyFormat())
540     return;
541   CGFloat zoomScale = zoomState.zoom_scale();
542   if (zoomScale < self.webScrollView.minimumZoomScale)
543     zoomScale = self.webScrollView.minimumZoomScale;
544   if (zoomScale > self.webScrollView.maximumZoomScale)
545     zoomScale = self.webScrollView.maximumZoomScale;
546   self.webScrollView.zoomScale = zoomScale;
549 - (void)handleCancelledError:(NSError*)error {
550   if ([self shouldAbortLoadForCancelledError:error]) {
551     // Do not abort the load for WKWebView, because calling stopLoading may
552     // stop the subsequent provisional load as well.
553     [self loadCancelled];
554     [[self sessionController] discardNonCommittedEntries];
555   }
558 #pragma mark Private methods
560 - (NSString*)documentMIMEType {
561   return _documentMIMEType.get();
564 - (void)setDocumentMIMEType:(NSString*)type {
565   _documentMIMEType.reset([type copy]);
568 - (NSDictionary*)wkWebViewObservers {
569   return @{
570 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
571     @"estimatedProgress" : @"webViewEstimatedProgressDidChange",
572     @"certificateChain" : @"webViewSecurityFeaturesDidChange",
573     @"hasOnlySecureContent" : @"webViewSecurityFeaturesDidChange",
574 #endif
575     @"loading" : @"webViewLoadingStateDidChange",
576     @"title" : @"webViewTitleDidChange",
577     @"URL" : @"webViewURLDidChange",
578   };
581 - (NSString*)requestGroupIDForUserAgent {
582 #if defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
583   return self.webStateImpl->GetRequestGroupID();
584 #else
585   return nil;
586 #endif
589 - (NSString*)activityIndicatorGroupID {
590   return [NSString stringWithFormat:
591       @"WKWebViewWebController.NetworkActivityIndicatorKey.%@",
592           self.webStateImpl->GetRequestGroupID()];
595 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
596 - (int)certGroupID {
597   DCHECK(self.webStateImpl);
598   // Request tracker IDs are used as certificate groups.
599   return self.webStateImpl->GetRequestTracker()->identifier();
601 #endif
603 - (web::WKWebViewConfigurationProvider&)webViewConfigurationProvider {
604   DCHECK(self.webStateImpl);
605   web::BrowserState* browserState = self.webStateImpl->GetBrowserState();
606   return web::WKWebViewConfigurationProvider::FromBrowserState(browserState);
609 - (void)ensureWebViewCreatedWithConfiguration:(WKWebViewConfiguration*)config {
610   if (!_wkWebView) {
611     // Use a separate userContentController for each web view.
612     // WKUserContentController does not allow adding multiple script message
613     // handlers for the same name, hence userContentController can't be shared
614     // between all web views.
615     config.userContentController = [self createUserContentController];
616     [self setWebView:[self createWebViewWithConfiguration:config]];
617     // Notify super class about created web view. -webViewDidChange is not
618     // called from -setWebView:scriptMessageRouter: as the latter used in unit
619     // tests with fake web view, which cannot be added to view hierarchy.
620     [self webViewDidChange];
621   }
624 - (WKWebView*)createWebViewWithConfiguration:(WKWebViewConfiguration*)config {
625   return [web::CreateWKWebView(CGRectZero, config,
626                                self.webStateImpl->GetBrowserState(),
627                                [self requestGroupIDForUserAgent],
628                                [self useDesktopUserAgent]) autorelease];
631 - (void)setWebView:(WKWebView*)webView {
632   DCHECK_NE(_wkWebView.get(), webView);
634   // Unwind the old web view.
635   WKUserContentController* oldContentController =
636       [[_wkWebView configuration] userContentController];
637   [oldContentController removeScriptMessageHandlerForName:kScriptMessageName];
638   [oldContentController removeScriptMessageHandlerForName:kScriptImmediateName];
639   [_wkWebView setNavigationDelegate:nil];
640   [_wkWebView setUIDelegate:nil];
641   for (NSString* keyPath in self.wkWebViewObservers) {
642     [_wkWebView removeObserver:self forKeyPath:keyPath];
643   }
644   [self clearActivityIndicatorTasks];
646   _wkWebView.reset([webView retain]);
648   // Set up the new web view.
649   WKUserContentController* newContentController =
650       [[_wkWebView configuration] userContentController];
651   [newContentController addScriptMessageHandler:self name:kScriptMessageName];
652   [newContentController addScriptMessageHandler:self name:kScriptImmediateName];
653   [_wkWebView setNavigationDelegate:self];
654   [_wkWebView setUIDelegate:self];
655   for (NSString* keyPath in self.wkWebViewObservers) {
656     [_wkWebView addObserver:self forKeyPath:keyPath options:0 context:nullptr];
657   }
658   _injectedScriptManagers.reset([[NSMutableSet alloc] init]);
659   _crashDetector.reset([self newCrashDetectorWithWebView:_wkWebView]);
660   _documentURL = [self defaultURL];
663 - (BOOL)isLinkNavigation:(WKNavigationType)navigationType {
664   switch (navigationType) {
665     case WKNavigationTypeLinkActivated:
666       return YES;
667     case WKNavigationTypeOther:
668       return [self userClickedRecently];
669     default:
670       return NO;
671   }
674 - (void)setPendingReferrerString:(NSString*)referrer {
675   _pendingReferrerString.reset([referrer copy]);
678 - (void)updatePendingReferrerFromNavigationAction:(WKNavigationAction*)action {
679   if (action.targetFrame.mainFrame)
680     [self setPendingReferrerString:GetRefererFromNavigationAction(action)];
683 - (void)commitPendingReferrerString {
684   _currentReferrerString.reset(_pendingReferrerString.release());
687 - (void)discardPendingReferrerString {
688   _pendingReferrerString.reset();
691 - (void)updatePendingNavigationTypeForMainFrameFromNavigationAction:
692     (WKNavigationAction*)action {
693   if (action.targetFrame.mainFrame)
694     _pendingNavigationTypeForMainFrame.reset(
695         new WKNavigationType(action.navigationType));
698 - (web::WKBackForwardListItemHolder*)currentBackForwardListItemHolder {
699   web::NavigationItem* item = [self currentSessionEntry].navigationItemImpl;
700   DCHECK(item);
701   web::WKBackForwardListItemHolder* holder =
702       web::WKBackForwardListItemHolder::FromNavigationItem(item);
703   DCHECK(holder);
704   return holder;
707 - (void)updateCurrentBackForwardListItemHolder {
708   web::WKBackForwardListItemHolder* holder =
709       [self currentBackForwardListItemHolder];
710   // If |decidePolicyForNavigationAction| gets called for every load,
711   // it should not necessary to perform this if check - just
712   // overwrite the holder with the newest data. See crbug.com/520279.
713   if (_pendingNavigationTypeForMainFrame) {
714     holder->set_back_forward_list_item(
715         [_wkWebView backForwardList].currentItem);
716     holder->set_navigation_type(*_pendingNavigationTypeForMainFrame);
717     _pendingNavigationTypeForMainFrame.reset();
718   }
721 - (BOOL)isBackForwardListItemValid:(WKBackForwardListItem*)item {
722   // The current back-forward list item MUST be in the WKWebView's back-forward
723   // list to be valid.
724   WKBackForwardList* list = [_wkWebView backForwardList];
725   return list.currentItem == item ||
726          [list.forwardList indexOfObject:item] != NSNotFound ||
727          [list.backList indexOfObject:item] != NSNotFound;
730 - (CRWWKWebViewCrashDetector*)newCrashDetectorWithWebView:(WKWebView*)webView {
731   // iOS9 provides crash detection API.
732   if (!webView || base::ios::IsRunningOnIOS9OrLater())
733     return nil;
735   base::WeakNSObject<CRWWKWebViewWebController> weakSelf(self);
736   id crashHandler = ^{
737     [weakSelf webViewWebProcessDidCrash];
738   };
739   return [[CRWWKWebViewCrashDetector alloc] initWithWebView:webView
740                                                crashHandler:crashHandler];
743 - (void)webViewWebProcessDidCrash {
744   if ([self.delegate respondsToSelector:
745           @selector(webControllerWebProcessDidCrash:)]) {
746     [self.delegate webControllerWebProcessDidCrash:self];
747   }
750 - (void)queryPageReferrerPolicy:(void(^)(NSString*))responseHandler {
751   DCHECK(responseHandler);
752   [self evaluateJavaScript:@"__gCrWeb.getPageReferrerPolicy()"
753        stringResultHandler:^(NSString* referrer, NSError* error) {
754       DCHECK_NE(error.code, WKErrorJavaScriptExceptionOccurred);
755       responseHandler(!error ? referrer : nil);
756   }];
759 - (void)didBlockPopupWithURL:(GURL)popupURL
760                    sourceURL:(GURL)sourceURL
761               referrerPolicy:(const std::string&)referrerPolicyString {
762   web::ReferrerPolicy referrerPolicy =
763       [self referrerPolicyFromString:referrerPolicyString];
764   web::Referrer referrer(sourceURL, referrerPolicy);
765   NSString* const kWindowName = @"";  // obsoleted
766   base::WeakNSObject<CRWWKWebViewWebController> weakSelf(self);
767   void(^showPopupHandler)() = ^{
768       // On Desktop cross-window comunication is not supported for unblocked
769       // popups; so it's ok to create a new independent page.
770       CRWWebController* child = [[weakSelf delegate]
771           webPageOrderedOpen:popupURL
772                     referrer:referrer
773                   windowName:kWindowName
774                 inBackground:NO];
775       DCHECK(!child || child.sessionController.openedByDOM);
776   };
778   web::BlockedPopupInfo info(popupURL, referrer, kWindowName, showPopupHandler);
779   [self.delegate webController:self didBlockPopup:info];
782 - (void)didBlockPopupWithURL:(GURL)popupURL sourceURL:(GURL)sourceURL {
783   if (![self.delegate respondsToSelector:
784       @selector(webController:didBlockPopup:)]) {
785     return;
786   }
788   base::WeakNSObject<CRWWKWebViewWebController> weakSelf(self);
789   dispatch_async(dispatch_get_main_queue(), ^{
790       [self queryPageReferrerPolicy:^(NSString* policy) {
791           [weakSelf didBlockPopupWithURL:popupURL
792                                sourceURL:sourceURL
793                           referrerPolicy:base::SysNSStringToUTF8(policy)];
794       }];
795   });
798 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
799 - (void)handleSSLError:(NSError*)error {
800   DCHECK(web::IsWKWebViewSSLError(error));
802   net::SSLInfo sslInfo;
803   web::GetSSLInfoFromWKWebViewSSLError(error, &sslInfo);
805   web::SSLStatus sslStatus;
806   sslStatus.security_style = web::SECURITY_STYLE_AUTHENTICATION_BROKEN;
807   sslStatus.cert_status = sslInfo.cert_status;
808   sslStatus.cert_id = web::CertStore::GetInstance()->StoreCert(
809       sslInfo.cert.get(), self.certGroupID);
811   [self.delegate presentSSLError:sslInfo
812                     forSSLStatus:sslStatus
813                      recoverable:NO
814                         callback:nullptr];
816 #endif  // #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
818 - (void)addActivityIndicatorTask {
819   [[CRWNetworkActivityIndicatorManager sharedInstance]
820       startNetworkTaskForGroup:[self activityIndicatorGroupID]];
823 - (void)clearActivityIndicatorTasks {
824   [[CRWNetworkActivityIndicatorManager sharedInstance]
825       clearNetworkTasksForGroup:[self activityIndicatorGroupID]];
828 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
829 - (void)updateSSLStatusForCurrentNavigationItem {
830   if ([self isBeingDestroyed])
831     return;
833   DCHECK(self.webStateImpl);
834   web::NavigationItem* item =
835       self.webStateImpl->GetNavigationManagerImpl().GetLastCommittedItem();
836   if (!item)
837     return;
839   web::SSLStatus previousSSLStatus = item->GetSSL();
840   web::SSLStatus& SSLStatus = item->GetSSL();
841   if (item->GetURL().SchemeIsCryptographic()) {
842     // TODO(eugenebut): Do not set security style to authenticated once
843     // proceeding with bad ssl cert is implemented.
844     SSLStatus.security_style = web::SECURITY_STYLE_AUTHENTICATED;
845     SSLStatus.content_status = [_wkWebView hasOnlySecureContent]
846                                    ? web::SSLStatus::NORMAL_CONTENT
847                                    : web::SSLStatus::DISPLAYED_INSECURE_CONTENT;
849     if (base::ios::IsRunningOnIOS9OrLater()) {
850       scoped_refptr<net::X509Certificate> cert(web::CreateCertFromChain(
851           [_wkWebView performSelector:@selector(certificateChain)]));
852       if (cert) {
853         SSLStatus.cert_id = web::CertStore::GetInstance()->StoreCert(
854             cert.get(), self.certGroupID);
855       } else {
856         SSLStatus.security_style = web::SECURITY_STYLE_UNAUTHENTICATED;
857         SSLStatus.cert_id = 0;
858       }
859     }
860   } else {
861     SSLStatus.security_style = web::SECURITY_STYLE_UNAUTHENTICATED;
862     SSLStatus.cert_id = 0;
863   }
865   if (!previousSSLStatus.Equals(SSLStatus)) {
866     [self didUpdateSSLStatusForCurrentNavigationItem];
867   }
869 #endif  // !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
871 - (void)registerLoadRequest:(const GURL&)url {
872   // If load request is registered via WKWebViewWebController, assume transition
873   // is link or client redirect as other transitions will already be registered
874   // by web controller or delegates.
875   // TODO(stuartmorgan): Remove guesswork and replace with information from
876   // decidePolicyForNavigationAction:.
877   ui::PageTransition transition = self.userInteractionRegistered
878                                       ? ui::PAGE_TRANSITION_LINK
879                                       : ui::PAGE_TRANSITION_CLIENT_REDIRECT;
880   // The referrer is not known yet, and will be updated later.
881   const web::Referrer emptyReferrer;
882   [self registerLoadRequest:url referrer:emptyReferrer transition:transition];
885 - (void)URLDidChangeWithoutDocumentChange:(const GURL&)newURL {
886   DCHECK(newURL == net::GURLWithNSURL([_wkWebView URL]));
887   _documentURL = newURL;
888   // If called during window.history.pushState or window.history.replaceState
889   // JavaScript evaluation, only update the document URL. This callback does not
890   // have any information about the state object and cannot create (or edit) the
891   // navigation entry for this page change. Web controller will sync with
892   // history changes when a window.history.didPushState or
893   // window.history.didReplaceState message is received, which should happen in
894   // the next runloop.
895   if (!_changingHistoryState) {
896     [self registerLoadRequest:_documentURL];
897     [self didStartLoadingURL:_documentURL updateHistory:YES];
898     [self didFinishNavigation];
899   }
902 - (WKUserContentController*)createUserContentController {
903   WKUserContentController* result =
904       [[[WKUserContentController alloc] init] autorelease];
905   base::scoped_nsobject<WKUserScript> script([[WKUserScript alloc]
906         initWithSource:web::GetEarlyPageScript(web::WK_WEB_VIEW_TYPE)
907          injectionTime:WKUserScriptInjectionTimeAtDocumentStart
908       forMainFrameOnly:YES]);
909   [result addUserScript:script];
910   return result;
913 - (void)userContentController:(WKUserContentController*)userContentController
914       didReceiveScriptMessage:(WKScriptMessage*)message {
915   // Broken out into separate method to catch errors.
916   // TODO(jyquinn): Evaluate whether this is necessary for WKWebView.
917   if (![self respondToWKScriptMessage:message]) {
918     DLOG(WARNING) << "Message from JS not handled due to invalid format";
919   }
922 - (BOOL)respondToWKScriptMessage:(WKScriptMessage*)scriptMessage {
923   CHECK(scriptMessage.frameInfo.mainFrame);
924   int errorCode = 0;
925   std::string errorMessage;
926   scoped_ptr<base::Value> inputJSONData(
927       base::JSONReader::ReadAndReturnError(
928           base::SysNSStringToUTF8(scriptMessage.body),
929           false,
930           &errorCode,
931           &errorMessage));
932   if (errorCode) {
933     DLOG(WARNING) << "JSON parse error: %s" << errorMessage.c_str();
934     return NO;
935   }
936   base::DictionaryValue* message = nullptr;
937   if (!inputJSONData->GetAsDictionary(&message)) {
938     return NO;
939   }
940   std::string windowID;
941   message->GetString("crwWindowId", &windowID);
942   // Check for correct windowID
943   if (![[self windowId] isEqualToString:base::SysUTF8ToNSString(windowID)]) {
944     DLOG(WARNING) << "Message from JS ignored due to non-matching windowID: "
945                   << [self windowId] << " != "
946                   << base::SysUTF8ToNSString(windowID);
947     return NO;
948   }
949   base::DictionaryValue* command = nullptr;
950   if (!message->GetDictionary("crwCommand", &command)) {
951     return NO;
952   }
953   if ([scriptMessage.name isEqualToString:kScriptImmediateName] ||
954       [scriptMessage.name isEqualToString:kScriptMessageName]) {
955     return [self respondToMessage:command
956                 userIsInteracting:[self userIsInteracting]
957                         originURL:net::GURLWithNSURL([_wkWebView URL])];
958   }
960   NOTREACHED();
961   return NO;
964 - (SEL)selectorToHandleJavaScriptCommand:(const std::string&)command {
965   static std::map<std::string, SEL>* handlers = nullptr;
966   static dispatch_once_t onceToken;
967   dispatch_once(&onceToken, ^{
968     handlers = new std::map<std::string, SEL>();
969     (*handlers)["window.history.didPushState"] =
970         @selector(handleWindowHistoryDidPushStateMessage:context:);
971     (*handlers)["window.history.didReplaceState"] =
972         @selector(handleWindowHistoryDidReplaceStateMessage:context:);
973     (*handlers)["window.history.willChangeState"] =
974         @selector(handleWindowHistoryWillChangeStateMessage:context:);
975   });
976   DCHECK(handlers);
977   auto iter = handlers->find(command);
978   return iter != handlers->end()
979              ? iter->second
980              : [super selectorToHandleJavaScriptCommand:command];
983 - (BOOL)shouldAbortLoadForCancelledError:(NSError*)error {
984   DCHECK_EQ(error.code, NSURLErrorCancelled);
985   // Do not abort the load if it is for an app specific URL, as such errors
986   // are produced during the app specific URL load process.
987   const GURL errorURL =
988       net::GURLWithNSURL(error.userInfo[NSURLErrorFailingURLErrorKey]);
989   if (web::GetWebClient()->IsAppSpecificURL(errorURL))
990     return NO;
991   // Don't abort NSURLErrorCancelled errors originating from navigation
992   // as the WKWebView will automatically retry these loads.
993   WKWebViewErrorSource source = WKWebViewErrorSourceFromError(error);
994   return source != NAVIGATION;
997 #pragma mark -
998 #pragma mark JavaScript message handlers
1000 - (BOOL)handleWindowHistoryWillChangeStateMessage:
1001     (base::DictionaryValue*)message
1002                                           context:(NSDictionary*)context {
1003   _changingHistoryState = YES;
1004   return
1005       [super handleWindowHistoryWillChangeStateMessage:message context:context];
1008 - (BOOL)handleWindowHistoryDidPushStateMessage:(base::DictionaryValue*)message
1009                                        context:(NSDictionary*)context {
1010   DCHECK(_changingHistoryState);
1011   _changingHistoryState = NO;
1012   return [super handleWindowHistoryDidPushStateMessage:message context:context];
1015 - (BOOL)handleWindowHistoryDidReplaceStateMessage:
1016     (base::DictionaryValue*)message
1017                                          context:(NSDictionary*)context {
1018   DCHECK(_changingHistoryState);
1019   _changingHistoryState = NO;
1020   return [super handleWindowHistoryDidReplaceStateMessage:message
1021                                                   context:context];
1024 #pragma mark -
1025 #pragma mark WebUI
1027 - (void)createWebUIForURL:(const GURL&)URL {
1028   [super createWebUIForURL:URL];
1029   _webUIManager.reset(
1030       [[CRWWebUIManager alloc] initWithWebState:self.webStateImpl]);
1033 - (void)clearWebUI {
1034   [super clearWebUI];
1035   _webUIManager.reset();
1038 #pragma mark -
1039 #pragma mark KVO Observation
1041 - (void)observeValueForKeyPath:(NSString*)keyPath
1042                       ofObject:(id)object
1043                         change:(NSDictionary*)change
1044                        context:(void*)context {
1045   NSString* dispatcherSelectorName = self.wkWebViewObservers[keyPath];
1046   DCHECK(dispatcherSelectorName);
1047   if (dispatcherSelectorName)
1048     [self performSelector:NSSelectorFromString(dispatcherSelectorName)];
1051 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
1052 // TODO(eugenebut): use WKWebView progress even if Chrome net stack is enabled.
1053 - (void)webViewEstimatedProgressDidChange {
1054   if ([self.delegate respondsToSelector:
1055           @selector(webController:didUpdateProgress:)]) {
1056     [self.delegate webController:self
1057                didUpdateProgress:[_wkWebView estimatedProgress]];
1058   }
1061 - (void)webViewSecurityFeaturesDidChange {
1062   if (self.loadPhase == web::LOAD_REQUESTED) {
1063     // Do not update SSL Status for pending load. It will be updated in
1064     // |webView:didCommitNavigation:| callback.
1065     return;
1066   }
1067   [self updateSSLStatusForCurrentNavigationItem];
1070 #endif  // !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
1072 - (void)webViewLoadingStateDidChange {
1073   if ([_wkWebView isLoading]) {
1074     [self addActivityIndicatorTask];
1075   } else {
1076     [self clearActivityIndicatorTasks];
1077   }
1080 - (void)webViewTitleDidChange {
1081   if ([self.delegate respondsToSelector:
1082           @selector(webController:titleDidChange:)]) {
1083     DCHECK(self.title);
1084     [self.delegate webController:self titleDidChange:self.title];
1085   }
1088 - (void)webViewURLDidChange {
1089   // TODO(stuartmorgan): Determine if there are any cases where this still
1090   // happens, and if so whether anything should be done when it does.
1091   if (![_wkWebView URL]) {
1092     DVLOG(1) << "Received nil URL callback";
1093     return;
1094   }
1095   GURL url(net::GURLWithNSURL([_wkWebView URL]));
1096   // URL changes happen at three points:
1097   // 1) When a load starts; at this point, the load is provisional, and
1098   //    it should be ignored until it's committed, since the document/window
1099   //    objects haven't changed yet.
1100   // 2) When a non-document-changing URL change happens (hash change,
1101   //    history.pushState, etc.). This URL change happens instantly, so should
1102   //    be reported.
1103   // 3) When a navigation error occurs after provisional navigation starts,
1104   //    the URL reverts to the previous URL without triggering a new navigation.
1105   //
1106   // If |isLoading| is NO, then it must be case 2 or 3. If the last committed
1107   // URL (_documentURL) matches the current URL, assume that it is a revert from
1108   // navigation failure and do nothing. If the URL does not match, assume it is
1109   // a non-document-changing URL change, and handle accordingly.
1110   //
1111   // If |isLoading| is YES, then it could either be case 1, or it could be
1112   // case 2 on a page that hasn't finished loading yet. If the domain of the
1113   // new URL matches the last committed URL, then check window.location.href,
1114   // and if it matches, trust it. The domain check ensures that if a site
1115   // somehow corrupts window.location.href it can't do a redirect to a
1116   // slow-loading target page while it is still loading to spoof the domain.
1117   // On a document-changing URL change, the window.location.href will match the
1118   // previous URL at this stage, not the web view's current URL.
1119   if (![_wkWebView isLoading]) {
1120     if (_documentURL == url)
1121       return;
1122     [self URLDidChangeWithoutDocumentChange:url];
1123   } else if (!_documentURL.host().empty() &&
1124              _documentURL.host() == url.host()) {
1125     [_wkWebView evaluateJavaScript:@"window.location.href"
1126                  completionHandler:^(id result, NSError* error) {
1127                      // If the web view has gone away, or the location
1128                      // couldn't be retrieved, abort.
1129                      if (!_wkWebView ||
1130                          ![result isKindOfClass:[NSString class]]) {
1131                        return;
1132                      }
1133                      GURL jsURL([result UTF8String]);
1134                      // Make sure that the URL is as expected, and re-check
1135                      // the host to prevent race conditions.
1136                      if (jsURL == url && _documentURL.host() == url.host()) {
1137                        [self URLDidChangeWithoutDocumentChange:url];
1138                      }
1139                  }];
1140   }
1143 #pragma mark -
1144 #pragma mark WKNavigationDelegate Methods
1146 - (void)webView:(WKWebView *)webView
1147     decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
1148                     decisionHandler:
1149         (void (^)(WKNavigationActionPolicy))decisionHandler {
1150   if (self.isBeingDestroyed) {
1151     decisionHandler(WKNavigationActionPolicyCancel);
1152     return;
1153   }
1155   NSURLRequest* request = navigationAction.request;
1156   GURL url = net::GURLWithNSURL(request.URL);
1158   // The page will not be changed until this navigation is commited, so the
1159   // retrieved referrer will be pending until |didCommitNavigation| callback.
1160   // Same for the last navigation type.
1161   [self updatePendingReferrerFromNavigationAction:navigationAction];
1162   [self updatePendingNavigationTypeForMainFrameFromNavigationAction:
1163             navigationAction];
1165   if (navigationAction.sourceFrame.mainFrame)
1166     self.documentMIMEType = nil;
1168   web::FrameInfo targetFrame(navigationAction.targetFrame.mainFrame);
1169   BOOL isLinkClick = [self isLinkNavigation:navigationAction.navigationType];
1170   BOOL allowLoad = [self shouldAllowLoadWithRequest:request
1171                                         targetFrame:&targetFrame
1172                                         isLinkClick:isLinkClick];
1174   allowLoad = allowLoad && self.webStateImpl->ShouldAllowRequest(request);
1176   decisionHandler(allowLoad ? WKNavigationActionPolicyAllow
1177                             : WKNavigationActionPolicyCancel);
1180 - (void)webView:(WKWebView *)webView
1181     decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse
1182                       decisionHandler:
1183                           (void (^)(WKNavigationResponsePolicy))handler {
1184   if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) {
1185     // Create HTTP headers from the response.
1186     // TODO(kkhorimoto): Due to the limited interface of NSHTTPURLResponse, some
1187     // data in the HttpResponseHeaders generated here is inexact.  Once
1188     // UIWebView is no longer supported, update WebState's implementation so
1189     // that the Content-Language and the MIME type can be set without using this
1190     // imperfect conversion.
1191     scoped_refptr<net::HttpResponseHeaders> HTTPHeaders =
1192         net::CreateHeadersFromNSHTTPURLResponse(
1193             static_cast<NSHTTPURLResponse*>(navigationResponse.response));
1194     self.webStateImpl->OnHttpResponseHeadersReceived(
1195         HTTPHeaders.get(), net::GURLWithNSURL(navigationResponse.response.URL));
1196   }
1197   if (navigationResponse.isForMainFrame)
1198     self.documentMIMEType = navigationResponse.response.MIMEType;
1200   BOOL allowNavigation =
1201       navigationResponse.canShowMIMEType &&
1202       self.webStateImpl->ShouldAllowResponse(navigationResponse.response);
1204   handler(allowNavigation ? WKNavigationResponsePolicyAllow
1205                           : WKNavigationResponsePolicyCancel);
1208 // TODO(stuartmorgan): Move all the guesswork around these states out of the
1209 // superclass, and wire these up to the remaining methods.
1210 - (void)webView:(WKWebView *)webView
1211     didStartProvisionalNavigation:(WKNavigation *)navigation {
1212   GURL webViewURL = net::GURLWithNSURL(webView.URL);
1213   if (webViewURL.is_empty() && base::ios::IsRunningOnIOS9OrLater()) {
1214     // May happen on iOS9, however in didCommitNavigation: callback the URL
1215     // will be "about:blank". TODO(eugenebut): File radar for this issue
1216     // (crbug.com/523549).
1217     webViewURL = GURL(url::kAboutBlankURL);
1218   }
1220   // Intercept renderer-initiated navigations. If this navigation has not yet
1221   // been registered, do so. loadPhase check is necessary because
1222   // lastRegisteredRequestURL may be the same as the webViewURL on a new tab
1223   // created by window.open (default is about::blank).
1224   // TODO(jyquinn): Audit [CRWWebController loadWithParams] for other tasks that
1225   // should be performed here.
1226   if (self.lastRegisteredRequestURL != webViewURL ||
1227       self.loadPhase != web::LOAD_REQUESTED) {
1228     // Reset current WebUI if one exists.
1229     [self clearWebUI];
1230     // Restart app specific URL loads to properly capture state.
1231     // TODO(jyquinn): Extract necessary tasks for app specific URL navigation
1232     // rather than restarting the load.
1233     if (web::GetWebClient()->IsAppSpecificURL(webViewURL)) {
1234       [self abortWebLoad];
1235       web::WebLoadParams params(webViewURL);
1236       [self loadWithParams:params];
1237       return;
1238     } else {
1239       [self registerLoadRequest:webViewURL];
1240     }
1241   }
1242   // Ensure the URL is registered and loadPhase is as expected.
1243   DCHECK(self.lastRegisteredRequestURL == webViewURL);
1244   DCHECK(self.loadPhase == web::LOAD_REQUESTED);
1247 - (void)webView:(WKWebView *)webView
1248     didReceiveServerRedirectForProvisionalNavigation:
1249         (WKNavigation *)navigation {
1250   [self registerLoadRequest:net::GURLWithNSURL(webView.URL)
1251                    referrer:[self currentReferrer]
1252                  transition:ui::PAGE_TRANSITION_SERVER_REDIRECT];
1255 - (void)webView:(WKWebView *)webView
1256     didFailProvisionalNavigation:(WKNavigation *)navigation
1257                        withError:(NSError *)error {
1258   [self discardPendingReferrerString];
1260   error = WKWebViewErrorWithSource(error, PROVISIONAL_LOAD);
1262 #if defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
1263   // For WKWebViews, the underlying errors for errors reported by the net stack
1264   // are not copied over when transferring the errors from the IO thread.  For
1265   // cancelled errors that trigger load abortions, translate the error early to
1266   // trigger |-discardNonCommittedEntries| from |-handleLoadError:inMainFrame:|.
1267   if (error.code == NSURLErrorCancelled &&
1268       [self shouldAbortLoadForCancelledError:error] &&
1269       !error.userInfo[NSUnderlyingErrorKey]) {
1270     error = web::NetErrorFromError(error);
1271   }
1272 #endif  // defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
1274 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
1275   if (web::IsWKWebViewSSLError(error))
1276     [self handleSSLError:error];
1277   else
1278 #endif
1279     [self handleLoadError:error inMainFrame:YES];
1282 - (void)webView:(WKWebView *)webView
1283     didCommitNavigation:(WKNavigation *)navigation {
1284   DCHECK_EQ(_wkWebView, webView);
1285   // This point should closely approximate the document object change, so reset
1286   // the list of injected scripts to those that are automatically injected.
1287   _injectedScriptManagers.reset([[NSMutableSet alloc] init]);
1288   [self injectWindowID];
1290   // The page has changed; commit the pending referrer.
1291   [self commitPendingReferrerString];
1293   // This is the point where the document's URL has actually changed.
1294   _documentURL = net::GURLWithNSURL([_wkWebView URL]);
1295   DCHECK(_documentURL == self.lastRegisteredRequestURL);
1296   [self webPageChanged];
1298   [self updateCurrentBackForwardListItemHolder];
1300 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
1301   [self updateSSLStatusForCurrentNavigationItem];
1302 #endif
1305 - (void)webView:(WKWebView *)webView
1306     didFinishNavigation:(WKNavigation *)navigation {
1307   DCHECK(!self.isHalted);
1308   // Trigger JavaScript driven post-document-load-completion tasks.
1309   // TODO(jyquinn): Investigate using WKUserScriptInjectionTimeAtDocumentEnd to
1310   // inject this material at the appropriate time rather than invoking here.
1311   web::EvaluateJavaScript(webView,
1312                           @"__gCrWeb.didFinishNavigation()", nil);
1313   [self didFinishNavigation];
1316 - (void)webView:(WKWebView *)webView
1317     didFailNavigation:(WKNavigation *)navigation
1318             withError:(NSError *)error {
1319   [self handleLoadError:WKWebViewErrorWithSource(error, NAVIGATION)
1320             inMainFrame:YES];
1323 - (void)webView:(WKWebView *)webView
1324     didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
1325                     completionHandler:
1326         (void (^)(NSURLSessionAuthChallengeDisposition disposition,
1327                   NSURLCredential *credential))completionHandler {
1328   NOTIMPLEMENTED();
1329   completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
1332 - (void)webViewWebContentProcessDidTerminate:(WKWebView*)webView {
1333   [self webViewWebProcessDidCrash];
1336 #pragma mark WKUIDelegate Methods
1338 - (WKWebView*)webView:(WKWebView*)webView
1339     createWebViewWithConfiguration:(WKWebViewConfiguration*)configuration
1340                forNavigationAction:(WKNavigationAction*)navigationAction
1341                     windowFeatures:(WKWindowFeatures*)windowFeatures {
1342   GURL requestURL = net::GURLWithNSURL(navigationAction.request.URL);
1343   NSString* referer = GetRefererFromNavigationAction(navigationAction);
1344   GURL referrerURL = referer ? GURL(base::SysNSStringToUTF8(referer)) :
1345                                [self currentURL];
1347   if (![self userIsInteracting] &&
1348       [self shouldBlockPopupWithURL:requestURL sourceURL:referrerURL]) {
1349     [self didBlockPopupWithURL:requestURL sourceURL:referrerURL];
1350     // Desktop Chrome does not return a window for the blocked popups;
1351     // follow the same approach by returning nil;
1352     return nil;
1353   }
1355   id child = [self createChildWebControllerWithReferrerURL:referrerURL];
1356   // WKWebView requires WKUIDelegate to return a child view created with
1357   // exactly the same |configuration| object (exception is raised if config is
1358   // different). |configuration| param and config returned by
1359   // WKWebViewConfigurationProvider are different objects because WKWebView
1360   // makes a shallow copy of the config inside init, so every WKWebView
1361   // owns a separate shallow copy of WKWebViewConfiguration.
1362   [child ensureWebViewCreatedWithConfiguration:configuration];
1363   return [child webView];
1366 - (void)webView:(WKWebView*)webView
1367     runJavaScriptAlertPanelWithMessage:(NSString*)message
1368                       initiatedByFrame:(WKFrameInfo*)frame
1369                      completionHandler:(void(^)())completionHandler {
1370   SEL alertSelector = @selector(webController:
1371            runJavaScriptAlertPanelWithMessage:
1372                                    requestURL:
1373                             completionHandler:);
1374   if ([self.UIDelegate respondsToSelector:alertSelector]) {
1375     [self.UIDelegate webController:self
1376         runJavaScriptAlertPanelWithMessage:message
1377                                 requestURL:net::GURLWithNSURL(frame.request.URL)
1378                          completionHandler:completionHandler];
1379   } else if (completionHandler) {
1380     completionHandler();
1381   }
1384 - (void)webView:(WKWebView*)webView
1385     runJavaScriptConfirmPanelWithMessage:(NSString*)message
1386                         initiatedByFrame:(WKFrameInfo*)frame
1387                        completionHandler:
1388         (void (^)(BOOL result))completionHandler {
1389   SEL confirmationSelector = @selector(webController:
1390                 runJavaScriptConfirmPanelWithMessage:
1391                                           requestURL:
1392                                    completionHandler:);
1393   if ([self.UIDelegate respondsToSelector:confirmationSelector]) {
1394     [self.UIDelegate webController:self
1395         runJavaScriptConfirmPanelWithMessage:message
1396                                   requestURL:
1397             net::GURLWithNSURL(frame.request.URL)
1398                            completionHandler:completionHandler];
1399   } else if (completionHandler) {
1400     completionHandler(NO);
1401   }
1404 - (void)webView:(WKWebView*)webView
1405     runJavaScriptTextInputPanelWithPrompt:(NSString*)prompt
1406                               defaultText:(NSString*)defaultText
1407                          initiatedByFrame:(WKFrameInfo*)frame
1408                         completionHandler:
1409         (void (^)(NSString *result))completionHandler {
1410   SEL textInputSelector = @selector(webController:
1411             runJavaScriptTextInputPanelWithPrompt:
1412                                   placeholderText:
1413                                        requestURL:
1414                                 completionHandler:);
1415   if ([self.UIDelegate respondsToSelector:textInputSelector]) {
1416     [self.UIDelegate webController:self
1417         runJavaScriptTextInputPanelWithPrompt:prompt
1418                               placeholderText:defaultText
1419                                    requestURL:
1420             net::GURLWithNSURL(frame.request.URL)
1421                             completionHandler:completionHandler];
1422   } else if (completionHandler) {
1423     completionHandler(nil);
1424   }
1427 @end