Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ios / web / web_state / ui / crw_wk_web_view_web_controller.mm
blob368587eab562249bab292466ff34a608391942d3
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 #import "ios/web/net/crw_cert_verification_controller.h"
23 #include "ios/web/public/cert_store.h"
24 #include "ios/web/public/navigation_item.h"
25 #include "ios/web/public/ssl_status.h"
26 #include "ios/web/public/web_client.h"
27 #import "ios/web/public/web_state/js/crw_js_injection_manager.h"
28 #import "ios/web/public/web_state/ui/crw_native_content_provider.h"
29 #import "ios/web/public/web_state/ui/crw_web_view_content_view.h"
30 #import "ios/web/ui_web_view_util.h"
31 #include "ios/web/web_state/blocked_popup_info.h"
32 #import "ios/web/web_state/error_translation_util.h"
33 #include "ios/web/web_state/frame_info.h"
34 #import "ios/web/web_state/js/crw_js_window_id_manager.h"
35 #import "ios/web/web_state/js/page_script_util.h"
36 #import "ios/web/web_state/ui/crw_web_controller+protected.h"
37 #import "ios/web/web_state/ui/crw_wk_web_view_crash_detector.h"
38 #import "ios/web/web_state/ui/web_view_js_utils.h"
39 #import "ios/web/web_state/ui/wk_back_forward_list_item_holder.h"
40 #import "ios/web/web_state/ui/wk_web_view_configuration_provider.h"
41 #import "ios/web/web_state/web_state_impl.h"
42 #import "ios/web/web_state/web_view_internal_creation_util.h"
43 #import "ios/web/web_state/wk_web_view_security_util.h"
44 #import "ios/web/webui/crw_web_ui_manager.h"
45 #include "net/cert/x509_certificate.h"
46 #import "net/base/mac/url_conversions.h"
47 #include "net/ssl/ssl_info.h"
48 #include "url/url_constants.h"
50 namespace {
51 // Extracts Referer value from WKNavigationAction request header.
52 NSString* GetRefererFromNavigationAction(WKNavigationAction* action) {
53   return [action.request valueForHTTPHeaderField:@"Referer"];
56 NSString* const kScriptMessageName = @"crwebinvoke";
57 NSString* const kScriptImmediateName = @"crwebinvokeimmediate";
59 // Utility functions for storing the source of NSErrors received by WKWebViews:
60 // - Errors received by |-webView:didFailProvisionalNavigation:withError:| are
61 //   recorded using WKWebViewErrorSource::PROVISIONAL_LOAD.  These should be
62 //   aborted.
63 // - Errors received by |-webView:didFailNavigation:withError:| are recorded
64 //   using WKWebViewsource::NAVIGATION.  These errors should not be aborted, as
65 //   the WKWebView will automatically retry the load.
66 NSString* const kWKWebViewErrorSourceKey = @"ErrorSource";
67 typedef enum { NONE = 0, PROVISIONAL_LOAD, NAVIGATION } WKWebViewErrorSource;
68 NSError* WKWebViewErrorWithSource(NSError* error, WKWebViewErrorSource source) {
69   DCHECK(error);
70   base::scoped_nsobject<NSMutableDictionary> userInfo(
71       [error.userInfo mutableCopy]);
72   [userInfo setObject:@(source) forKey:kWKWebViewErrorSourceKey];
73   return [NSError errorWithDomain:error.domain
74                              code:error.code
75                          userInfo:userInfo];
77 WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) {
78   DCHECK(error);
79   return static_cast<WKWebViewErrorSource>(
80       [error.userInfo[kWKWebViewErrorSourceKey] integerValue]);
83 }  // namespace
85 @interface CRWWKWebViewWebController () <WKNavigationDelegate,
86                                          WKScriptMessageHandler,
87                                          WKUIDelegate> {
88   // The WKWebView managed by this instance.
89   base::scoped_nsobject<WKWebView> _wkWebView;
91   // The Watch Dog that detects and reports WKWebView crashes.
92   base::scoped_nsobject<CRWWKWebViewCrashDetector> _crashDetector;
94   // The actual URL of the document object (i.e., the last committed URL).
95   // TODO(stuartmorgan): Remove this in favor of just updating the session
96   // controller and treating that as authoritative. For now, this allows sharing
97   // the flow that's currently in the superclass.
98   GURL _documentURL;
100   // A set of script managers whose scripts have been injected into the current
101   // page.
102   // TODO(stuartmorgan): Revisit this approach; it's intended only as a stopgap
103   // measure to make all the existing script managers work. Longer term, there
104   // should probably be a couple of points where managers can register to have
105   // things happen automatically based on page lifecycle, and if they don't want
106   // to use one of those fixed points, they should make their scripts internally
107   // idempotent.
108   base::scoped_nsobject<NSMutableSet> _injectedScriptManagers;
110   // Referrer pending for the next navigated page. Lifetime of this value starts
111   // at |decidePolicyForNavigationAction| where the referrer is extracted from
112   // the request and ends at |didCommitNavigation| where the request is
113   // committed.
114   base::scoped_nsobject<NSString> _pendingReferrerString;
116   // Referrer for the current page.
117   base::scoped_nsobject<NSString> _currentReferrerString;
119   // Backs the property of the same name.
120   base::scoped_nsobject<NSString> _documentMIMEType;
122   // Navigation type of the pending navigation action of the main frame. This
123   // value is assigned at |decidePolicyForNavigationAction| where the navigation
124   // type is extracted from the request and associated with a committed
125   // navigation item at |didCommitNavigation|.
126   scoped_ptr<WKNavigationType> _pendingNavigationTypeForMainFrame;
128   // Whether the web page is currently performing window.history.pushState or
129   // window.history.replaceState
130   // Set to YES on window.history.willChangeState message. To NO on
131   // window.history.didPushState or window.history.didReplaceState.
132   BOOL _changingHistoryState;
134   // CRWWebUIManager object for loading WebUI pages.
135   base::scoped_nsobject<CRWWebUIManager> _webUIManager;
137   // Controller used for certs verification to help with blocking requests with
138   // bad SSL cert, presenting SSL interstitials and determining SSL status for
139   // Navigation Items.
140   base::scoped_nsobject<CRWCertVerificationController>
141       _certVerificationController;
143   // Whether the pending navigation has been directly cancelled in
144   // |decidePolicyForNavigationAction| or |decidePolicyForNavigationResponse|.
145   // Cancelled navigations should be simply discarded without handling any
146   // specific error.
147   BOOL _pendingNavigationCancelled;
150 // Response's MIME type of the last known navigation.
151 @property(nonatomic, copy) NSString* documentMIMEType;
153 // Dictionary where keys are the names of WKWebView properties and values are
154 // selector names which should be called when a corresponding property has
155 // changed. e.g. @{ @"URL" : @"webViewURLDidChange" } means that
156 // -[self webViewURLDidChange] must be called every time when WKWebView.URL is
157 // changed.
158 @property(nonatomic, readonly) NSDictionary* wkWebViewObservers;
160 // Returns the string to use as the request group ID in the user agent. Returns
161 // nil unless the network stack is enabled.
162 @property(nonatomic, readonly) NSString* requestGroupIDForUserAgent;
164 // Activity indicator group ID for this web controller.
165 @property(nonatomic, readonly) NSString* activityIndicatorGroupID;
167 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
168 // Identifier used for storing and retrieving certificates.
169 @property(nonatomic, readonly) int certGroupID;
170 #endif  // #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
172 // Returns the WKWebViewConfigurationProvider associated with the web
173 // controller's BrowserState.
174 - (web::WKWebViewConfigurationProvider&)webViewConfigurationProvider;
176 // Creates a web view with given |config|. No-op if web view is already created.
177 - (void)ensureWebViewCreatedWithConfiguration:(WKWebViewConfiguration*)config;
179 // Returns a new autoreleased web view created with given configuration.
180 - (WKWebView*)createWebViewWithConfiguration:(WKWebViewConfiguration*)config;
182 // Sets the value of the webView property, and performs its basic setup.
183 - (void)setWebView:(WKWebView*)webView;
185 // Returns whether the given navigation is triggered by a user link click.
186 - (BOOL)isLinkNavigation:(WKNavigationType)navigationType;
188 // Sets value of the pendingReferrerString property.
189 - (void)setPendingReferrerString:(NSString*)referrer;
191 // Extracts the referrer value from WKNavigationAction and sets it as a pending.
192 // The referrer is known in |decidePolicyForNavigationAction| however it must
193 // be in a pending state until |didCommitNavigation| where it becames current.
194 - (void)updatePendingReferrerFromNavigationAction:(WKNavigationAction*)action;
196 // Replaces the current referrer with the pending one. Referrer becames current
197 // at |didCommitNavigation| callback.
198 - (void)commitPendingReferrerString;
200 // Discards the pending referrer.
201 - (void)discardPendingReferrerString;
203 // Extracts the current navigation type from WKNavigationAction and sets it as
204 // the pending navigation type. The value should be considered pending until it
205 // becomes associated with a navigation item at |didCommitNavigation|.
206 - (void)updatePendingNavigationTypeForMainFrameFromNavigationAction:
207     (WKNavigationAction*)action;
209 // Discards the pending navigation type.
210 - (void)discardPendingNavigationTypeForMainFrame;
212 // Returns the WKBackForwardListItemHolder for the current navigation item.
213 - (web::WKBackForwardListItemHolder*)currentBackForwardListItemHolder;
215 // Stores the current WKBackForwardListItem and the current navigation type
216 // with the current navigation item.
217 - (void)updateCurrentBackForwardListItemHolder;
219 // Returns YES if the given WKBackForwardListItem is valid to use for
220 // navigation.
221 - (BOOL)isBackForwardListItemValid:(WKBackForwardListItem*)item;
223 // Returns a new CRWWKWebViewCrashDetector created with the given |webView| or
224 // nil if |webView| is nil. Callers are responsible for releasing the object.
225 - (CRWWKWebViewCrashDetector*)newCrashDetectorWithWebView:(WKWebView*)webView;
227 // Called when web view process has been terminated.
228 - (void)webViewWebProcessDidCrash;
230 // Asynchronously returns the referrer policy for the current page.
231 - (void)queryPageReferrerPolicy:(void(^)(NSString*))responseHandler;
233 // Informs CWRWebDelegate that CRWWebController has detected and blocked a
234 // popup.
235 - (void)didBlockPopupWithURL:(GURL)popupURL
236                    sourceURL:(GURL)sourceURL
237               referrerPolicy:(const std::string&)referrerPolicyString;
239 // Convenience method to inform CWRWebDelegate about a blocked popup.
240 - (void)didBlockPopupWithURL:(GURL)popupURL sourceURL:(GURL)sourceURL;
242 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
243 // Called when a load ends in an SSL error.
244 - (void)handleSSLError:(NSError*)error;
245 #endif
247 // Adds an activity indicator tasks for this web controller.
248 - (void)addActivityIndicatorTask;
250 // Clears all activity indicator tasks for this web controller.
251 - (void)clearActivityIndicatorTasks;
253 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
254 // Updates SSL status for the current navigation item based on the information
255 // provided by web view.
256 - (void)updateSSLStatusForCurrentNavigationItem;
257 #endif
259 // Registers load request with empty referrer and link or client redirect
260 // transition based on user interaction state.
261 - (void)registerLoadRequest:(const GURL&)url;
263 // Called when a non-document-changing URL change occurs. Updates the
264 // _documentURL, and informs the superclass of the change.
265 - (void)URLDidChangeWithoutDocumentChange:(const GURL&)URL;
267 // Returns new autoreleased instance of WKUserContentController which has
268 // early page script.
269 - (WKUserContentController*)createUserContentController;
271 // Attempts to handle a script message. Returns YES on success, NO otherwise.
272 - (BOOL)respondToWKScriptMessage:(WKScriptMessage*)scriptMessage;
274 // Used to decide whether a load that generates errors with the
275 // NSURLErrorCancelled code should be cancelled.
276 - (BOOL)shouldAbortLoadForCancelledError:(NSError*)error;
278 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
279 // Called when WKWebView estimatedProgress has been changed.
280 - (void)webViewEstimatedProgressDidChange;
282 // Called when WKWebView certificateChain or hasOnlySecureContent property has
283 // changed.
284 - (void)webViewSecurityFeaturesDidChange;
285 #endif  // !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
287 // Called when WKWebView loading state has been changed.
288 - (void)webViewLoadingStateDidChange;
290 // Called when WKWebView title has been changed.
291 - (void)webViewTitleDidChange;
293 // Called when WKWebView URL has been changed.
294 - (void)webViewURLDidChange;
296 @end
298 @implementation CRWWKWebViewWebController
300 #pragma mark CRWWebController public methods
302 - (instancetype)initWithWebState:(scoped_ptr<web::WebStateImpl>)webState {
303   DCHECK(webState);
304   web::BrowserState* browserState = webState->GetBrowserState();
305   self = [super initWithWebState:webState.Pass()];
306   if (self) {
307     _certVerificationController.reset([[CRWCertVerificationController alloc]
308         initWithBrowserState:browserState]);
309   }
310   return self;
313 - (BOOL)keyboardDisplayRequiresUserAction {
314   // TODO(stuartmorgan): Find out whether YES or NO is correct; see comment
315   // in protected header.
316   NOTIMPLEMENTED();
317   return NO;
320 - (void)setKeyboardDisplayRequiresUserAction:(BOOL)requiresUserAction {
321   NOTIMPLEMENTED();
324 - (void)evaluateJavaScript:(NSString*)script
325        stringResultHandler:(web::JavaScriptCompletion)handler {
326   NSString* safeScript = [self scriptByAddingWindowIDCheckForScript:script];
327   web::EvaluateJavaScript(_wkWebView, safeScript, handler);
330 - (web::WebViewType)webViewType {
331   return web::WK_WEB_VIEW_TYPE;
334 - (void)evaluateUserJavaScript:(NSString*)script {
335   [self setUserInteractionRegistered:YES];
336   web::EvaluateJavaScript(_wkWebView, script, nil);
339 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
340 - (void)terminateNetworkActivity {
341   web::CertStore::GetInstance()->RemoveCertsForGroup(self.certGroupID);
342   [super terminateNetworkActivity];
344 #endif  // #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
346 - (void)setPageDialogOpenPolicy:(web::PageDialogOpenPolicy)policy {
347   // TODO(eugenebut): implement dialogs/windows suppression using
348   // WKNavigationDelegate methods where possible.
349   [super setPageDialogOpenPolicy:policy];
352 - (void)close {
353   [_certVerificationController shutDown];
354   [super close];
357 #pragma mark -
358 #pragma mark Testing-Only Methods
360 - (void)injectWebViewContentView:(CRWWebViewContentView*)webViewContentView {
361   [super injectWebViewContentView:webViewContentView];
362   [self setWebView:static_cast<WKWebView*>(webViewContentView.webView)];
365 #pragma mark - Protected property implementations
367 - (UIView*)webView {
368   return _wkWebView.get();
371 - (UIScrollView*)webScrollView {
372   return [_wkWebView scrollView];
375 - (BOOL)ignoreURLVerificationFailures {
376   return NO;
379 - (NSString*)title {
380   return [_wkWebView title];
383 - (NSString*)currentReferrerString {
384   return _currentReferrerString.get();
387 #pragma mark Protected method implementations
389 - (void)ensureWebViewCreated {
390   WKWebViewConfiguration* config =
391       [self webViewConfigurationProvider].GetWebViewConfiguration();
392   [self ensureWebViewCreatedWithConfiguration:config];
395 - (void)resetWebView {
396   [self setWebView:nil];
399 - (GURL)webURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel {
400   DCHECK(trustLevel);
401   *trustLevel = web::URLVerificationTrustLevel::kAbsolute;
402   return _documentURL;
405 // TODO(stuartmorgan): Remove this method and use the API for WKWebView,
406 // making the reset-on-each-load behavior specific to the UIWebView subclass.
407 - (void)registerUserAgent {
408   web::BuildAndRegisterUserAgentForUIWebView([self requestGroupIDForUserAgent],
409                                              [self useDesktopUserAgent]);
412 - (BOOL)isCurrentNavigationItemPOST {
413   // |_pendingNavigationTypeForMainFrame| will be nil if
414   // |decidePolicyForNavigationAction| was not reached.
415   WKNavigationType type =
416       (_pendingNavigationTypeForMainFrame)
417           ? *_pendingNavigationTypeForMainFrame
418           : [self currentBackForwardListItemHolder]->navigation_type();
419   return type == WKNavigationTypeFormSubmitted ||
420          type == WKNavigationTypeFormResubmitted;
423 // The core.js cannot pass messages back to obj-c  if it is injected
424 // to |WEB_VIEW_DOCUMENT| because it does not support iframe creation used
425 // by core.js to communicate back. That functionality is only supported
426 // by |WEB_VIEW_HTML_DOCUMENT|. |WEB_VIEW_DOCUMENT| is used when displaying
427 // non-HTML contents (e.g. PDF documents).
428 - (web::WebViewDocumentType)webViewDocumentType {
429   // This happens during tests.
430   if (!_wkWebView) {
431     return web::WEB_VIEW_DOCUMENT_TYPE_GENERIC;
432   }
434   if (!self.documentMIMEType) {
435     return web::WEB_VIEW_DOCUMENT_TYPE_UNKNOWN;
436   }
438   if ([self.documentMIMEType isEqualToString:@"text/html"] ||
439       [self.documentMIMEType isEqualToString:@"application/xhtml+xml"] ||
440       [self.documentMIMEType isEqualToString:@"application/xml"]) {
441     return web::WEB_VIEW_DOCUMENT_TYPE_HTML;
442   }
444   return web::WEB_VIEW_DOCUMENT_TYPE_GENERIC;
447 - (void)loadRequest:(NSMutableURLRequest*)request {
448   [_wkWebView loadRequest:request];
451 - (void)loadWebHTMLString:(NSString*)html forURL:(const GURL&)URL {
452   [_wkWebView loadHTMLString:html baseURL:net::NSURLWithGURL(URL)];
455 - (BOOL)scriptHasBeenInjectedForClass:(Class)jsInjectionManagerClass
456                        presenceBeacon:(NSString*)beacon {
457   return [_injectedScriptManagers containsObject:jsInjectionManagerClass];
460 - (void)injectScript:(NSString*)script forClass:(Class)JSInjectionManagerClass {
461   // Skip evaluation if there's no content (e.g., if what's being injected is
462   // an umbrella manager).
463   if ([script length]) {
464     [super injectScript:script forClass:JSInjectionManagerClass];
465     // Every injection except windowID requires windowID check.
466     if (JSInjectionManagerClass != [CRWJSWindowIdManager class])
467       script = [self scriptByAddingWindowIDCheckForScript:script];
468     web::EvaluateJavaScript(_wkWebView, script, nil);
469   }
470   [_injectedScriptManagers addObject:JSInjectionManagerClass];
473 - (void)willLoadCurrentURLInWebView {
474   // TODO(stuartmorgan): Get a WKWebView version of the request ID verification
475   // code working for debug builds.
478 - (void)loadRequestForCurrentNavigationItem {
479   DCHECK(self.webView && !self.nativeController);
481   ProceduralBlock defaultNavigationBlock = ^{
482     [self registerLoadRequest:[self currentNavigationURL]
483                      referrer:[self currentSessionEntryReferrer]
484                    transition:[self currentTransition]];
485     [self loadRequest:[self requestForCurrentNavigationItem]];
486   };
488   // If there is no corresponding WKBackForwardListItem, or the item is not in
489   // the current WKWebView's back-forward list, navigating using WKWebView API
490   // is not possible. In this case, fall back to the default navigation
491   // mechanism.
492   web::WKBackForwardListItemHolder* holder =
493       [self currentBackForwardListItemHolder];
494   if (!holder->back_forward_list_item() ||
495       ![self isBackForwardListItemValid:holder->back_forward_list_item()]) {
496     defaultNavigationBlock();
497     return;
498   }
500   ProceduralBlock webViewNavigationBlock = ^{
501     // If the current navigation URL is the same as the URL of the visible
502     // page, that means the user requested a reload. |goToBackForwardListItem|
503     // will be a no-op when it is passed the current back forward list item,
504     // so |reload| must be explicitly called.
505     [self registerLoadRequest:[self currentNavigationURL]
506                      referrer:[self currentSessionEntryReferrer]
507                    transition:[self currentTransition]];
508     if ([self currentNavigationURL] == net::GURLWithNSURL([_wkWebView URL])) {
509       [_wkWebView reload];
510     } else {
511       [_wkWebView goToBackForwardListItem:holder->back_forward_list_item()];
512     }
513   };
515   // If the request is not a form submission or resubmission, or the user
516   // doesn't need to confirm the load, then continue right away.
517   web::NavigationItemImpl* currentItem =
518       [self currentSessionEntry].navigationItemImpl;
519   if ((holder->navigation_type() != WKNavigationTypeFormResubmitted &&
520        holder->navigation_type() != WKNavigationTypeFormSubmitted) ||
521       currentItem->ShouldSkipResubmitDataConfirmation()) {
522     webViewNavigationBlock();
523     return;
524   }
526   // If the request is form submission or resubmission, then prompt the
527   // user before proceeding.
528   [self.delegate webController:self
529       onFormResubmissionForRequest:nil
530                      continueBlock:webViewNavigationBlock
531                        cancelBlock:defaultNavigationBlock];
534 // Overrides the hashchange workaround in the super class that manually
535 // triggers Javascript hashchange events. If navigating with native API,
536 // i.e. using a back forward list item, hashchange events will be triggered
537 // automatically, so no URL tampering is required.
538 - (GURL)URLForHistoryNavigationFromItem:(web::NavigationItem*)fromItem
539                                  toItem:(web::NavigationItem*)toItem {
540   web::WKBackForwardListItemHolder* holder =
541       web::WKBackForwardListItemHolder::FromNavigationItem(toItem);
543   if (holder->back_forward_list_item()) {
544     return toItem->GetURL();
545   }
546   return [super URLForHistoryNavigationFromItem:fromItem toItem:toItem];
549 - (void)setPageChangeProbability:(web::PageChangeProbability)probability {
550   // Nothing to do; no polling timer.
553 - (void)abortWebLoad {
554   [_wkWebView stopLoading];
557 - (void)resetLoadState {
558   // Nothing to do.
561 - (void)setSuppressDialogsWithHelperScript:(NSString*)script {
562   [self evaluateJavaScript:script stringResultHandler:nil];
565 - (void)applyWebViewScrollZoomScaleFromZoomState:
566     (const web::PageZoomState&)zoomState {
567   // After rendering a web page, WKWebView keeps the |minimumZoomScale| and
568   // |maximumZoomScale| properties of its scroll view constant while adjusting
569   // the |zoomScale| property accordingly.  The maximum-scale or minimum-scale
570   // meta tags of a page may have changed since the state was recorded, so clamp
571   // the zoom scale to the current range if necessary.
572   DCHECK(zoomState.IsValid());
573   // Legacy-format scroll states cannot be applied to WKWebViews.
574   if (zoomState.IsLegacyFormat())
575     return;
576   CGFloat zoomScale = zoomState.zoom_scale();
577   if (zoomScale < self.webScrollView.minimumZoomScale)
578     zoomScale = self.webScrollView.minimumZoomScale;
579   if (zoomScale > self.webScrollView.maximumZoomScale)
580     zoomScale = self.webScrollView.maximumZoomScale;
581   self.webScrollView.zoomScale = zoomScale;
584 - (void)handleCancelledError:(NSError*)error {
585   if ([self shouldAbortLoadForCancelledError:error]) {
586     // Do not abort the load for WKWebView, because calling stopLoading may
587     // stop the subsequent provisional load as well.
588     [self loadCancelled];
589     [[self sessionController] discardNonCommittedEntries];
590   }
593 #pragma mark Private methods
595 - (NSString*)documentMIMEType {
596   return _documentMIMEType.get();
599 - (void)setDocumentMIMEType:(NSString*)type {
600   _documentMIMEType.reset([type copy]);
603 - (NSDictionary*)wkWebViewObservers {
604   return @{
605 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
606     @"estimatedProgress" : @"webViewEstimatedProgressDidChange",
607     @"certificateChain" : @"webViewSecurityFeaturesDidChange",
608     @"hasOnlySecureContent" : @"webViewSecurityFeaturesDidChange",
609 #endif
610     @"loading" : @"webViewLoadingStateDidChange",
611     @"title" : @"webViewTitleDidChange",
612     @"URL" : @"webViewURLDidChange",
613   };
616 - (NSString*)requestGroupIDForUserAgent {
617 #if defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
618   return self.webStateImpl->GetRequestGroupID();
619 #else
620   return nil;
621 #endif
624 - (NSString*)activityIndicatorGroupID {
625   return [NSString stringWithFormat:
626       @"WKWebViewWebController.NetworkActivityIndicatorKey.%@",
627           self.webStateImpl->GetRequestGroupID()];
630 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
631 - (int)certGroupID {
632   DCHECK(self.webStateImpl);
633   // Request tracker IDs are used as certificate groups.
634   return self.webStateImpl->GetRequestTracker()->identifier();
636 #endif
638 - (web::WKWebViewConfigurationProvider&)webViewConfigurationProvider {
639   DCHECK(self.webStateImpl);
640   web::BrowserState* browserState = self.webStateImpl->GetBrowserState();
641   return web::WKWebViewConfigurationProvider::FromBrowserState(browserState);
644 - (void)ensureWebViewCreatedWithConfiguration:(WKWebViewConfiguration*)config {
645   if (!_wkWebView) {
646     // Use a separate userContentController for each web view.
647     // WKUserContentController does not allow adding multiple script message
648     // handlers for the same name, hence userContentController can't be shared
649     // between all web views.
650     config.userContentController = [self createUserContentController];
651     [self setWebView:[self createWebViewWithConfiguration:config]];
652     // Notify super class about created web view. -webViewDidChange is not
653     // called from -setWebView:scriptMessageRouter: as the latter used in unit
654     // tests with fake web view, which cannot be added to view hierarchy.
655     [self webViewDidChange];
656   }
659 - (WKWebView*)createWebViewWithConfiguration:(WKWebViewConfiguration*)config {
660   return [web::CreateWKWebView(CGRectZero, config,
661                                self.webStateImpl->GetBrowserState(),
662                                [self requestGroupIDForUserAgent],
663                                [self useDesktopUserAgent]) autorelease];
666 - (void)setWebView:(WKWebView*)webView {
667   DCHECK_NE(_wkWebView.get(), webView);
669   // Unwind the old web view.
670   WKUserContentController* oldContentController =
671       [[_wkWebView configuration] userContentController];
672   [oldContentController removeScriptMessageHandlerForName:kScriptMessageName];
673   [oldContentController removeScriptMessageHandlerForName:kScriptImmediateName];
674   [_wkWebView setNavigationDelegate:nil];
675   [_wkWebView setUIDelegate:nil];
676   for (NSString* keyPath in self.wkWebViewObservers) {
677     [_wkWebView removeObserver:self forKeyPath:keyPath];
678   }
679   [self clearActivityIndicatorTasks];
681   _wkWebView.reset([webView retain]);
683   // Set up the new web view.
684   WKUserContentController* newContentController =
685       [[_wkWebView configuration] userContentController];
686   [newContentController addScriptMessageHandler:self name:kScriptMessageName];
687   [newContentController addScriptMessageHandler:self name:kScriptImmediateName];
688   [_wkWebView setNavigationDelegate:self];
689   [_wkWebView setUIDelegate:self];
690   for (NSString* keyPath in self.wkWebViewObservers) {
691     [_wkWebView addObserver:self forKeyPath:keyPath options:0 context:nullptr];
692   }
693   _injectedScriptManagers.reset([[NSMutableSet alloc] init]);
694   _crashDetector.reset([self newCrashDetectorWithWebView:_wkWebView]);
695   _documentURL = [self defaultURL];
698 - (BOOL)isLinkNavigation:(WKNavigationType)navigationType {
699   switch (navigationType) {
700     case WKNavigationTypeLinkActivated:
701       return YES;
702     case WKNavigationTypeOther:
703       return [self userClickedRecently];
704     default:
705       return NO;
706   }
709 - (void)setPendingReferrerString:(NSString*)referrer {
710   _pendingReferrerString.reset([referrer copy]);
713 - (void)updatePendingReferrerFromNavigationAction:(WKNavigationAction*)action {
714   if (action.targetFrame.mainFrame)
715     [self setPendingReferrerString:GetRefererFromNavigationAction(action)];
718 - (void)commitPendingReferrerString {
719   _currentReferrerString.reset(_pendingReferrerString.release());
722 - (void)discardPendingReferrerString {
723   _pendingReferrerString.reset();
726 - (void)updatePendingNavigationTypeForMainFrameFromNavigationAction:
727     (WKNavigationAction*)action {
728   if (action.targetFrame.mainFrame)
729     _pendingNavigationTypeForMainFrame.reset(
730         new WKNavigationType(action.navigationType));
733 - (void)discardPendingNavigationTypeForMainFrame {
734   _pendingNavigationTypeForMainFrame.reset();
737 - (web::WKBackForwardListItemHolder*)currentBackForwardListItemHolder {
738   web::NavigationItem* item = [self currentSessionEntry].navigationItemImpl;
739   DCHECK(item);
740   web::WKBackForwardListItemHolder* holder =
741       web::WKBackForwardListItemHolder::FromNavigationItem(item);
742   DCHECK(holder);
743   return holder;
746 - (void)updateCurrentBackForwardListItemHolder {
747   web::WKBackForwardListItemHolder* holder =
748       [self currentBackForwardListItemHolder];
749   // If |decidePolicyForNavigationAction| gets called for every load,
750   // it should not necessary to perform this if check - just
751   // overwrite the holder with the newest data. See crbug.com/520279.
752   if (_pendingNavigationTypeForMainFrame) {
753     holder->set_back_forward_list_item(
754         [_wkWebView backForwardList].currentItem);
755     holder->set_navigation_type(*_pendingNavigationTypeForMainFrame);
756     _pendingNavigationTypeForMainFrame.reset();
757   }
760 - (BOOL)isBackForwardListItemValid:(WKBackForwardListItem*)item {
761   // The current back-forward list item MUST be in the WKWebView's back-forward
762   // list to be valid.
763   WKBackForwardList* list = [_wkWebView backForwardList];
764   return list.currentItem == item ||
765          [list.forwardList indexOfObject:item] != NSNotFound ||
766          [list.backList indexOfObject:item] != NSNotFound;
769 - (CRWWKWebViewCrashDetector*)newCrashDetectorWithWebView:(WKWebView*)webView {
770   // iOS9 provides crash detection API.
771   if (!webView || base::ios::IsRunningOnIOS9OrLater())
772     return nil;
774   base::WeakNSObject<CRWWKWebViewWebController> weakSelf(self);
775   id crashHandler = ^{
776     [weakSelf webViewWebProcessDidCrash];
777   };
778   return [[CRWWKWebViewCrashDetector alloc] initWithWebView:webView
779                                                crashHandler:crashHandler];
782 - (void)webViewWebProcessDidCrash {
783   if ([self.delegate respondsToSelector:
784           @selector(webControllerWebProcessDidCrash:)]) {
785     [self.delegate webControllerWebProcessDidCrash:self];
786   }
789 - (void)queryPageReferrerPolicy:(void(^)(NSString*))responseHandler {
790   DCHECK(responseHandler);
791   [self evaluateJavaScript:@"__gCrWeb.getPageReferrerPolicy()"
792        stringResultHandler:^(NSString* referrer, NSError* error) {
793       DCHECK_NE(error.code, WKErrorJavaScriptExceptionOccurred);
794       responseHandler(!error ? referrer : nil);
795   }];
798 - (void)didBlockPopupWithURL:(GURL)popupURL
799                    sourceURL:(GURL)sourceURL
800               referrerPolicy:(const std::string&)referrerPolicyString {
801   web::ReferrerPolicy referrerPolicy =
802       [self referrerPolicyFromString:referrerPolicyString];
803   web::Referrer referrer(sourceURL, referrerPolicy);
804   NSString* const kWindowName = @"";  // obsoleted
805   base::WeakNSObject<CRWWKWebViewWebController> weakSelf(self);
806   void(^showPopupHandler)() = ^{
807       // On Desktop cross-window comunication is not supported for unblocked
808       // popups; so it's ok to create a new independent page.
809       CRWWebController* child = [[weakSelf delegate]
810           webPageOrderedOpen:popupURL
811                     referrer:referrer
812                   windowName:kWindowName
813                 inBackground:NO];
814       DCHECK(!child || child.sessionController.openedByDOM);
815   };
817   web::BlockedPopupInfo info(popupURL, referrer, kWindowName, showPopupHandler);
818   [self.delegate webController:self didBlockPopup:info];
821 - (void)didBlockPopupWithURL:(GURL)popupURL sourceURL:(GURL)sourceURL {
822   if (![self.delegate respondsToSelector:
823       @selector(webController:didBlockPopup:)]) {
824     return;
825   }
827   base::WeakNSObject<CRWWKWebViewWebController> weakSelf(self);
828   dispatch_async(dispatch_get_main_queue(), ^{
829       [self queryPageReferrerPolicy:^(NSString* policy) {
830           [weakSelf didBlockPopupWithURL:popupURL
831                                sourceURL:sourceURL
832                           referrerPolicy:base::SysNSStringToUTF8(policy)];
833       }];
834   });
837 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
838 - (void)handleSSLError:(NSError*)error {
839   DCHECK(web::IsWKWebViewSSLError(error));
841   net::SSLInfo sslInfo;
842   web::GetSSLInfoFromWKWebViewSSLError(error, &sslInfo);
844   web::SSLStatus sslStatus;
845   sslStatus.security_style = web::SECURITY_STYLE_AUTHENTICATION_BROKEN;
846   sslStatus.cert_status = sslInfo.cert_status;
847   sslStatus.cert_id = web::CertStore::GetInstance()->StoreCert(
848       sslInfo.cert.get(), self.certGroupID);
850   [self.delegate presentSSLError:sslInfo
851                     forSSLStatus:sslStatus
852                      recoverable:NO
853                         callback:nullptr];
855 #endif  // #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
857 - (void)addActivityIndicatorTask {
858   [[CRWNetworkActivityIndicatorManager sharedInstance]
859       startNetworkTaskForGroup:[self activityIndicatorGroupID]];
862 - (void)clearActivityIndicatorTasks {
863   [[CRWNetworkActivityIndicatorManager sharedInstance]
864       clearNetworkTasksForGroup:[self activityIndicatorGroupID]];
867 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
868 - (void)updateSSLStatusForCurrentNavigationItem {
869   if ([self isBeingDestroyed])
870     return;
872   DCHECK(self.webStateImpl);
873   web::NavigationItem* item =
874       self.webStateImpl->GetNavigationManagerImpl().GetLastCommittedItem();
875   if (!item)
876     return;
878   web::SSLStatus previousSSLStatus = item->GetSSL();
879   web::SSLStatus& SSLStatus = item->GetSSL();
880   if (item->GetURL().SchemeIsCryptographic()) {
881     // TODO(eugenebut): Do not set security style to authenticated once
882     // proceeding with bad ssl cert is implemented.
883     SSLStatus.security_style = web::SECURITY_STYLE_AUTHENTICATED;
884     SSLStatus.content_status = [_wkWebView hasOnlySecureContent]
885                                    ? web::SSLStatus::NORMAL_CONTENT
886                                    : web::SSLStatus::DISPLAYED_INSECURE_CONTENT;
888     if (base::ios::IsRunningOnIOS9OrLater()) {
889       scoped_refptr<net::X509Certificate> cert(web::CreateCertFromChain(
890           [_wkWebView performSelector:@selector(certificateChain)]));
891       if (cert) {
892         SSLStatus.cert_id = web::CertStore::GetInstance()->StoreCert(
893             cert.get(), self.certGroupID);
894       } else {
895         SSLStatus.security_style = web::SECURITY_STYLE_UNAUTHENTICATED;
896         SSLStatus.cert_id = 0;
897       }
898     }
899   } else {
900     SSLStatus.security_style = web::SECURITY_STYLE_UNAUTHENTICATED;
901     SSLStatus.cert_id = 0;
902   }
904   if (!previousSSLStatus.Equals(SSLStatus)) {
905     [self didUpdateSSLStatusForCurrentNavigationItem];
906   }
908 #endif  // !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
910 - (void)registerLoadRequest:(const GURL&)url {
911   // If load request is registered via WKWebViewWebController, assume transition
912   // is link or client redirect as other transitions will already be registered
913   // by web controller or delegates.
914   // TODO(stuartmorgan): Remove guesswork and replace with information from
915   // decidePolicyForNavigationAction:.
916   ui::PageTransition transition = self.userInteractionRegistered
917                                       ? ui::PAGE_TRANSITION_LINK
918                                       : ui::PAGE_TRANSITION_CLIENT_REDIRECT;
919   // The referrer is not known yet, and will be updated later.
920   const web::Referrer emptyReferrer;
921   [self registerLoadRequest:url referrer:emptyReferrer transition:transition];
924 - (void)URLDidChangeWithoutDocumentChange:(const GURL&)newURL {
925   DCHECK(newURL == net::GURLWithNSURL([_wkWebView URL]));
926   _documentURL = newURL;
927   // If called during window.history.pushState or window.history.replaceState
928   // JavaScript evaluation, only update the document URL. This callback does not
929   // have any information about the state object and cannot create (or edit) the
930   // navigation entry for this page change. Web controller will sync with
931   // history changes when a window.history.didPushState or
932   // window.history.didReplaceState message is received, which should happen in
933   // the next runloop.
934   if (!_changingHistoryState) {
935     [self registerLoadRequest:_documentURL];
936     [self didStartLoadingURL:_documentURL updateHistory:YES];
937     [self didFinishNavigation];
938   }
941 - (WKUserContentController*)createUserContentController {
942   WKUserContentController* result =
943       [[[WKUserContentController alloc] init] autorelease];
944   base::scoped_nsobject<WKUserScript> script([[WKUserScript alloc]
945         initWithSource:web::GetEarlyPageScript(web::WK_WEB_VIEW_TYPE)
946          injectionTime:WKUserScriptInjectionTimeAtDocumentStart
947       forMainFrameOnly:YES]);
948   [result addUserScript:script];
949   return result;
952 - (void)userContentController:(WKUserContentController*)userContentController
953       didReceiveScriptMessage:(WKScriptMessage*)message {
954   // Broken out into separate method to catch errors.
955   // TODO(jyquinn): Evaluate whether this is necessary for WKWebView.
956   if (![self respondToWKScriptMessage:message]) {
957     DLOG(WARNING) << "Message from JS not handled due to invalid format";
958   }
961 - (BOOL)respondToWKScriptMessage:(WKScriptMessage*)scriptMessage {
962   CHECK(scriptMessage.frameInfo.mainFrame);
963   int errorCode = 0;
964   std::string errorMessage;
965   scoped_ptr<base::Value> inputJSONData(
966       base::JSONReader::ReadAndReturnError(
967           base::SysNSStringToUTF8(scriptMessage.body),
968           false,
969           &errorCode,
970           &errorMessage));
971   if (errorCode) {
972     DLOG(WARNING) << "JSON parse error: %s" << errorMessage.c_str();
973     return NO;
974   }
975   base::DictionaryValue* message = nullptr;
976   if (!inputJSONData->GetAsDictionary(&message)) {
977     return NO;
978   }
979   std::string windowID;
980   message->GetString("crwWindowId", &windowID);
981   // Check for correct windowID
982   if (![[self windowId] isEqualToString:base::SysUTF8ToNSString(windowID)]) {
983     DLOG(WARNING) << "Message from JS ignored due to non-matching windowID: "
984                   << [self windowId] << " != "
985                   << base::SysUTF8ToNSString(windowID);
986     return NO;
987   }
988   base::DictionaryValue* command = nullptr;
989   if (!message->GetDictionary("crwCommand", &command)) {
990     return NO;
991   }
992   if ([scriptMessage.name isEqualToString:kScriptImmediateName] ||
993       [scriptMessage.name isEqualToString:kScriptMessageName]) {
994     return [self respondToMessage:command
995                 userIsInteracting:[self userIsInteracting]
996                         originURL:net::GURLWithNSURL([_wkWebView URL])];
997   }
999   NOTREACHED();
1000   return NO;
1003 - (SEL)selectorToHandleJavaScriptCommand:(const std::string&)command {
1004   static std::map<std::string, SEL>* handlers = nullptr;
1005   static dispatch_once_t onceToken;
1006   dispatch_once(&onceToken, ^{
1007     handlers = new std::map<std::string, SEL>();
1008     (*handlers)["window.history.didPushState"] =
1009         @selector(handleWindowHistoryDidPushStateMessage:context:);
1010     (*handlers)["window.history.didReplaceState"] =
1011         @selector(handleWindowHistoryDidReplaceStateMessage:context:);
1012     (*handlers)["window.history.willChangeState"] =
1013         @selector(handleWindowHistoryWillChangeStateMessage:context:);
1014   });
1015   DCHECK(handlers);
1016   auto iter = handlers->find(command);
1017   return iter != handlers->end()
1018              ? iter->second
1019              : [super selectorToHandleJavaScriptCommand:command];
1022 - (BOOL)shouldAbortLoadForCancelledError:(NSError*)error {
1023   DCHECK_EQ(error.code, NSURLErrorCancelled);
1024   // Do not abort the load if it is for an app specific URL, as such errors
1025   // are produced during the app specific URL load process.
1026   const GURL errorURL =
1027       net::GURLWithNSURL(error.userInfo[NSURLErrorFailingURLErrorKey]);
1028   if (web::GetWebClient()->IsAppSpecificURL(errorURL))
1029     return NO;
1030   // Don't abort NSURLErrorCancelled errors originating from navigation
1031   // as the WKWebView will automatically retry these loads.
1032   WKWebViewErrorSource source = WKWebViewErrorSourceFromError(error);
1033   return source != NAVIGATION;
1036 #pragma mark -
1037 #pragma mark JavaScript message handlers
1039 - (BOOL)handleWindowHistoryWillChangeStateMessage:
1040     (base::DictionaryValue*)message
1041                                           context:(NSDictionary*)context {
1042   _changingHistoryState = YES;
1043   return
1044       [super handleWindowHistoryWillChangeStateMessage:message context:context];
1047 - (BOOL)handleWindowHistoryDidPushStateMessage:(base::DictionaryValue*)message
1048                                        context:(NSDictionary*)context {
1049   DCHECK(_changingHistoryState);
1050   _changingHistoryState = NO;
1051   return [super handleWindowHistoryDidPushStateMessage:message context:context];
1054 - (BOOL)handleWindowHistoryDidReplaceStateMessage:
1055     (base::DictionaryValue*)message
1056                                          context:(NSDictionary*)context {
1057   DCHECK(_changingHistoryState);
1058   _changingHistoryState = NO;
1059   return [super handleWindowHistoryDidReplaceStateMessage:message
1060                                                   context:context];
1063 #pragma mark -
1064 #pragma mark WebUI
1066 - (void)createWebUIForURL:(const GURL&)URL {
1067   [super createWebUIForURL:URL];
1068   _webUIManager.reset(
1069       [[CRWWebUIManager alloc] initWithWebState:self.webStateImpl]);
1072 - (void)clearWebUI {
1073   [super clearWebUI];
1074   _webUIManager.reset();
1077 #pragma mark -
1078 #pragma mark KVO Observation
1080 - (void)observeValueForKeyPath:(NSString*)keyPath
1081                       ofObject:(id)object
1082                         change:(NSDictionary*)change
1083                        context:(void*)context {
1084   NSString* dispatcherSelectorName = self.wkWebViewObservers[keyPath];
1085   DCHECK(dispatcherSelectorName);
1086   if (dispatcherSelectorName)
1087     [self performSelector:NSSelectorFromString(dispatcherSelectorName)];
1090 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
1091 // TODO(eugenebut): use WKWebView progress even if Chrome net stack is enabled.
1092 - (void)webViewEstimatedProgressDidChange {
1093   if ([self.delegate respondsToSelector:
1094           @selector(webController:didUpdateProgress:)]) {
1095     [self.delegate webController:self
1096                didUpdateProgress:[_wkWebView estimatedProgress]];
1097   }
1100 - (void)webViewSecurityFeaturesDidChange {
1101   if (self.loadPhase == web::LOAD_REQUESTED) {
1102     // Do not update SSL Status for pending load. It will be updated in
1103     // |webView:didCommitNavigation:| callback.
1104     return;
1105   }
1106   [self updateSSLStatusForCurrentNavigationItem];
1109 #endif  // !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
1111 - (void)webViewLoadingStateDidChange {
1112   if ([_wkWebView isLoading]) {
1113     [self addActivityIndicatorTask];
1114   } else {
1115     [self clearActivityIndicatorTasks];
1116   }
1119 - (void)webViewTitleDidChange {
1120   if ([self.delegate respondsToSelector:
1121           @selector(webController:titleDidChange:)]) {
1122     DCHECK(self.title);
1123     [self.delegate webController:self titleDidChange:self.title];
1124   }
1127 - (void)webViewURLDidChange {
1128   // TODO(stuartmorgan): Determine if there are any cases where this still
1129   // happens, and if so whether anything should be done when it does.
1130   if (![_wkWebView URL]) {
1131     DVLOG(1) << "Received nil URL callback";
1132     return;
1133   }
1134   GURL url(net::GURLWithNSURL([_wkWebView URL]));
1135   // URL changes happen at three points:
1136   // 1) When a load starts; at this point, the load is provisional, and
1137   //    it should be ignored until it's committed, since the document/window
1138   //    objects haven't changed yet.
1139   // 2) When a non-document-changing URL change happens (hash change,
1140   //    history.pushState, etc.). This URL change happens instantly, so should
1141   //    be reported.
1142   // 3) When a navigation error occurs after provisional navigation starts,
1143   //    the URL reverts to the previous URL without triggering a new navigation.
1144   //
1145   // If |isLoading| is NO, then it must be case 2 or 3. If the last committed
1146   // URL (_documentURL) matches the current URL, assume that it is a revert from
1147   // navigation failure and do nothing. If the URL does not match, assume it is
1148   // a non-document-changing URL change, and handle accordingly.
1149   //
1150   // If |isLoading| is YES, then it could either be case 1, or it could be
1151   // case 2 on a page that hasn't finished loading yet. If the domain of the
1152   // new URL matches the last committed URL, then check window.location.href,
1153   // and if it matches, trust it. The domain check ensures that if a site
1154   // somehow corrupts window.location.href it can't do a redirect to a
1155   // slow-loading target page while it is still loading to spoof the domain.
1156   // On a document-changing URL change, the window.location.href will match the
1157   // previous URL at this stage, not the web view's current URL.
1158   if (![_wkWebView isLoading]) {
1159     if (_documentURL == url)
1160       return;
1161     [self URLDidChangeWithoutDocumentChange:url];
1162   } else if (!_documentURL.host().empty() &&
1163              _documentURL.host() == url.host()) {
1164     [_wkWebView evaluateJavaScript:@"window.location.href"
1165                  completionHandler:^(id result, NSError* error) {
1166                      // If the web view has gone away, or the location
1167                      // couldn't be retrieved, abort.
1168                      if (!_wkWebView ||
1169                          ![result isKindOfClass:[NSString class]]) {
1170                        return;
1171                      }
1172                      GURL jsURL([result UTF8String]);
1173                      // Make sure that the URL is as expected, and re-check
1174                      // the host to prevent race conditions.
1175                      if (jsURL == url && _documentURL.host() == url.host()) {
1176                        [self URLDidChangeWithoutDocumentChange:url];
1177                      }
1178                  }];
1179   }
1182 #pragma mark -
1183 #pragma mark WKNavigationDelegate Methods
1185 - (void)webView:(WKWebView *)webView
1186     decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
1187                     decisionHandler:
1188         (void (^)(WKNavigationActionPolicy))decisionHandler {
1189   if (self.isBeingDestroyed) {
1190     decisionHandler(WKNavigationActionPolicyCancel);
1191     return;
1192   }
1194   NSURLRequest* request = navigationAction.request;
1195   GURL url = net::GURLWithNSURL(request.URL);
1197   // The page will not be changed until this navigation is commited, so the
1198   // retrieved referrer will be pending until |didCommitNavigation| callback.
1199   // Same for the last navigation type.
1200   [self updatePendingReferrerFromNavigationAction:navigationAction];
1201   [self updatePendingNavigationTypeForMainFrameFromNavigationAction:
1202             navigationAction];
1204   if (navigationAction.sourceFrame.mainFrame)
1205     self.documentMIMEType = nil;
1207   web::FrameInfo targetFrame(navigationAction.targetFrame.mainFrame);
1208   BOOL isLinkClick = [self isLinkNavigation:navigationAction.navigationType];
1209   BOOL allowLoad = [self shouldAllowLoadWithRequest:request
1210                                         targetFrame:&targetFrame
1211                                         isLinkClick:isLinkClick];
1213   if (allowLoad) {
1214     allowLoad = self.webStateImpl->ShouldAllowRequest(request);
1215     _pendingNavigationCancelled = !allowLoad;
1216   }
1218   decisionHandler(allowLoad ? WKNavigationActionPolicyAllow
1219                             : WKNavigationActionPolicyCancel);
1222 - (void)webView:(WKWebView *)webView
1223     decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse
1224                       decisionHandler:
1225                           (void (^)(WKNavigationResponsePolicy))handler {
1226   if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) {
1227     // Create HTTP headers from the response.
1228     // TODO(kkhorimoto): Due to the limited interface of NSHTTPURLResponse, some
1229     // data in the HttpResponseHeaders generated here is inexact.  Once
1230     // UIWebView is no longer supported, update WebState's implementation so
1231     // that the Content-Language and the MIME type can be set without using this
1232     // imperfect conversion.
1233     scoped_refptr<net::HttpResponseHeaders> HTTPHeaders =
1234         net::CreateHeadersFromNSHTTPURLResponse(
1235             static_cast<NSHTTPURLResponse*>(navigationResponse.response));
1236     self.webStateImpl->OnHttpResponseHeadersReceived(
1237         HTTPHeaders.get(), net::GURLWithNSURL(navigationResponse.response.URL));
1238   }
1239   if (navigationResponse.isForMainFrame)
1240     self.documentMIMEType = navigationResponse.response.MIMEType;
1242   BOOL allowNavigation = navigationResponse.canShowMIMEType;
1243   if (allowNavigation) {
1244     allowNavigation =
1245         self.webStateImpl->ShouldAllowResponse(navigationResponse.response);
1246     _pendingNavigationCancelled = !allowNavigation;
1247   }
1249   handler(allowNavigation ? WKNavigationResponsePolicyAllow
1250                           : WKNavigationResponsePolicyCancel);
1253 // TODO(stuartmorgan): Move all the guesswork around these states out of the
1254 // superclass, and wire these up to the remaining methods.
1255 - (void)webView:(WKWebView *)webView
1256     didStartProvisionalNavigation:(WKNavigation *)navigation {
1257   GURL webViewURL = net::GURLWithNSURL(webView.URL);
1258   if (webViewURL.is_empty() && base::ios::IsRunningOnIOS9OrLater()) {
1259     // May happen on iOS9, however in didCommitNavigation: callback the URL
1260     // will be "about:blank". TODO(eugenebut): File radar for this issue
1261     // (crbug.com/523549).
1262     webViewURL = GURL(url::kAboutBlankURL);
1263   }
1265   // Intercept renderer-initiated navigations. If this navigation has not yet
1266   // been registered, do so. loadPhase check is necessary because
1267   // lastRegisteredRequestURL may be the same as the webViewURL on a new tab
1268   // created by window.open (default is about::blank).
1269   // TODO(jyquinn): Audit [CRWWebController loadWithParams] for other tasks that
1270   // should be performed here.
1271   if (self.lastRegisteredRequestURL != webViewURL ||
1272       self.loadPhase != web::LOAD_REQUESTED) {
1273     // Reset current WebUI if one exists.
1274     [self clearWebUI];
1275     // Restart app specific URL loads to properly capture state.
1276     // TODO(jyquinn): Extract necessary tasks for app specific URL navigation
1277     // rather than restarting the load.
1278     if (web::GetWebClient()->IsAppSpecificURL(webViewURL)) {
1279       [self abortWebLoad];
1280       web::WebLoadParams params(webViewURL);
1281       [self loadWithParams:params];
1282       return;
1283     } else {
1284       [self registerLoadRequest:webViewURL];
1285     }
1286   }
1287   // Ensure the URL is registered and loadPhase is as expected.
1288   DCHECK(self.lastRegisteredRequestURL == webViewURL);
1289   DCHECK(self.loadPhase == web::LOAD_REQUESTED);
1292 - (void)webView:(WKWebView *)webView
1293     didReceiveServerRedirectForProvisionalNavigation:
1294         (WKNavigation *)navigation {
1295   [self registerLoadRequest:net::GURLWithNSURL(webView.URL)
1296                    referrer:[self currentReferrer]
1297                  transition:ui::PAGE_TRANSITION_SERVER_REDIRECT];
1300 - (void)webView:(WKWebView *)webView
1301     didFailProvisionalNavigation:(WKNavigation *)navigation
1302                        withError:(NSError *)error {
1303   [self discardPendingReferrerString];
1305   if (_pendingNavigationCancelled) {
1306     // Directly cancelled navigations are simply discarded without handling
1307     // their potential errors.
1308     _pendingNavigationCancelled = NO;
1309     return;
1310   }
1312   error = WKWebViewErrorWithSource(error, PROVISIONAL_LOAD);
1314 #if defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
1315   // For WKWebViews, the underlying errors for errors reported by the net stack
1316   // are not copied over when transferring the errors from the IO thread.  For
1317   // cancelled errors that trigger load abortions, translate the error early to
1318   // trigger |-discardNonCommittedEntries| from |-handleLoadError:inMainFrame:|.
1319   if (error.code == NSURLErrorCancelled &&
1320       [self shouldAbortLoadForCancelledError:error] &&
1321       !error.userInfo[NSUnderlyingErrorKey]) {
1322     error = web::NetErrorFromError(error);
1323   }
1324 #endif  // defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
1326 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
1327   if (web::IsWKWebViewSSLError(error))
1328     [self handleSSLError:error];
1329   else
1330 #endif
1331     [self handleLoadError:error inMainFrame:YES];
1333   [self discardPendingNavigationTypeForMainFrame];
1336 - (void)webView:(WKWebView *)webView
1337     didCommitNavigation:(WKNavigation *)navigation {
1338   DCHECK_EQ(_wkWebView, webView);
1339   // This point should closely approximate the document object change, so reset
1340   // the list of injected scripts to those that are automatically injected.
1341   _injectedScriptManagers.reset([[NSMutableSet alloc] init]);
1342   [self injectWindowID];
1344   // The page has changed; commit the pending referrer.
1345   [self commitPendingReferrerString];
1347   // This is the point where the document's URL has actually changed.
1348   _documentURL = net::GURLWithNSURL([_wkWebView URL]);
1349   DCHECK(_documentURL == self.lastRegisteredRequestURL);
1350   [self webPageChanged];
1352   [self updateCurrentBackForwardListItemHolder];
1354 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
1355   [self updateSSLStatusForCurrentNavigationItem];
1356 #endif
1359 - (void)webView:(WKWebView *)webView
1360     didFinishNavigation:(WKNavigation *)navigation {
1361   DCHECK(!self.isHalted);
1362   // Trigger JavaScript driven post-document-load-completion tasks.
1363   // TODO(jyquinn): Investigate using WKUserScriptInjectionTimeAtDocumentEnd to
1364   // inject this material at the appropriate time rather than invoking here.
1365   web::EvaluateJavaScript(webView,
1366                           @"__gCrWeb.didFinishNavigation()", nil);
1367   [self didFinishNavigation];
1370 - (void)webView:(WKWebView *)webView
1371     didFailNavigation:(WKNavigation *)navigation
1372             withError:(NSError *)error {
1373   [self handleLoadError:WKWebViewErrorWithSource(error, NAVIGATION)
1374             inMainFrame:YES];
1377 - (void)webView:(WKWebView *)webView
1378     didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
1379                     completionHandler:
1380         (void (^)(NSURLSessionAuthChallengeDisposition disposition,
1381                   NSURLCredential *credential))completionHandler {
1382   if (![challenge.protectionSpace.authenticationMethod
1383           isEqual:NSURLAuthenticationMethodServerTrust]) {
1384     completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
1385     return;
1386   }
1388   SecTrustRef trust = challenge.protectionSpace.serverTrust;
1389   scoped_refptr<net::X509Certificate> cert = web::CreateCertFromTrust(trust);
1390   [_certVerificationController
1391       decidePolicyForCert:cert
1392                      host:challenge.protectionSpace.host
1393         completionHandler:^(web::CertAcceptPolicy policy,
1394                             net::CertStatus status) {
1395           completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace,
1396                             nil);
1397         }];
1400 - (void)webViewWebContentProcessDidTerminate:(WKWebView*)webView {
1401   [self webViewWebProcessDidCrash];
1404 #pragma mark WKUIDelegate Methods
1406 - (WKWebView*)webView:(WKWebView*)webView
1407     createWebViewWithConfiguration:(WKWebViewConfiguration*)configuration
1408                forNavigationAction:(WKNavigationAction*)navigationAction
1409                     windowFeatures:(WKWindowFeatures*)windowFeatures {
1410   GURL requestURL = net::GURLWithNSURL(navigationAction.request.URL);
1411   NSString* referer = GetRefererFromNavigationAction(navigationAction);
1412   GURL referrerURL = referer ? GURL(base::SysNSStringToUTF8(referer)) :
1413                                [self currentURL];
1415   if (![self userIsInteracting] &&
1416       [self shouldBlockPopupWithURL:requestURL sourceURL:referrerURL]) {
1417     [self didBlockPopupWithURL:requestURL sourceURL:referrerURL];
1418     // Desktop Chrome does not return a window for the blocked popups;
1419     // follow the same approach by returning nil;
1420     return nil;
1421   }
1423   id child = [self createChildWebControllerWithReferrerURL:referrerURL];
1424   // WKWebView requires WKUIDelegate to return a child view created with
1425   // exactly the same |configuration| object (exception is raised if config is
1426   // different). |configuration| param and config returned by
1427   // WKWebViewConfigurationProvider are different objects because WKWebView
1428   // makes a shallow copy of the config inside init, so every WKWebView
1429   // owns a separate shallow copy of WKWebViewConfiguration.
1430   [child ensureWebViewCreatedWithConfiguration:configuration];
1431   return [child webView];
1434 - (void)webView:(WKWebView*)webView
1435     runJavaScriptAlertPanelWithMessage:(NSString*)message
1436                       initiatedByFrame:(WKFrameInfo*)frame
1437                      completionHandler:(void(^)())completionHandler {
1438   SEL alertSelector = @selector(webController:
1439            runJavaScriptAlertPanelWithMessage:
1440                                    requestURL:
1441                             completionHandler:);
1442   if ([self.UIDelegate respondsToSelector:alertSelector]) {
1443     [self.UIDelegate webController:self
1444         runJavaScriptAlertPanelWithMessage:message
1445                                 requestURL:net::GURLWithNSURL(frame.request.URL)
1446                          completionHandler:completionHandler];
1447   } else if (completionHandler) {
1448     completionHandler();
1449   }
1452 - (void)webView:(WKWebView*)webView
1453     runJavaScriptConfirmPanelWithMessage:(NSString*)message
1454                         initiatedByFrame:(WKFrameInfo*)frame
1455                        completionHandler:
1456         (void (^)(BOOL result))completionHandler {
1457   SEL confirmationSelector = @selector(webController:
1458                 runJavaScriptConfirmPanelWithMessage:
1459                                           requestURL:
1460                                    completionHandler:);
1461   if ([self.UIDelegate respondsToSelector:confirmationSelector]) {
1462     [self.UIDelegate webController:self
1463         runJavaScriptConfirmPanelWithMessage:message
1464                                   requestURL:
1465             net::GURLWithNSURL(frame.request.URL)
1466                            completionHandler:completionHandler];
1467   } else if (completionHandler) {
1468     completionHandler(NO);
1469   }
1472 - (void)webView:(WKWebView*)webView
1473     runJavaScriptTextInputPanelWithPrompt:(NSString*)prompt
1474                               defaultText:(NSString*)defaultText
1475                          initiatedByFrame:(WKFrameInfo*)frame
1476                         completionHandler:
1477         (void (^)(NSString *result))completionHandler {
1478   SEL textInputSelector = @selector(webController:
1479             runJavaScriptTextInputPanelWithPrompt:
1480                                   placeholderText:
1481                                        requestURL:
1482                                 completionHandler:);
1483   if ([self.UIDelegate respondsToSelector:textInputSelector]) {
1484     [self.UIDelegate webController:self
1485         runJavaScriptTextInputPanelWithPrompt:prompt
1486                               placeholderText:defaultText
1487                                    requestURL:
1488             net::GURLWithNSURL(frame.request.URL)
1489                             completionHandler:completionHandler];
1490   } else if (completionHandler) {
1491     completionHandler(nil);
1492   }
1495 @end