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"
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
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) {
70 base::scoped_nsobject<NSMutableDictionary> userInfo(
71 [error.userInfo mutableCopy]);
72 [userInfo setObject:@(source) forKey:kWKWebViewErrorSourceKey];
73 return [NSError errorWithDomain:error.domain
77 WKWebViewErrorSource WKWebViewErrorSourceFromError(NSError* error) {
79 return static_cast<WKWebViewErrorSource>(
80 [error.userInfo[kWKWebViewErrorSourceKey] integerValue]);
85 @interface CRWWKWebViewWebController () <WKNavigationDelegate,
86 WKScriptMessageHandler,
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.
100 // A set of script managers whose scripts have been injected into the current
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
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
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
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
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
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
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
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;
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;
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
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;
298 @implementation CRWWKWebViewWebController
300 #pragma mark CRWWebController public methods
302 - (instancetype)initWithWebState:(scoped_ptr<web::WebStateImpl>)webState {
304 web::BrowserState* browserState = webState->GetBrowserState();
305 self = [super initWithWebState:webState.Pass()];
307 _certVerificationController.reset([[CRWCertVerificationController alloc]
308 initWithBrowserState:browserState]);
313 - (BOOL)keyboardDisplayRequiresUserAction {
314 // TODO(stuartmorgan): Find out whether YES or NO is correct; see comment
315 // in protected header.
320 - (void)setKeyboardDisplayRequiresUserAction:(BOOL)requiresUserAction {
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];
353 [_certVerificationController shutDown];
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
368 return _wkWebView.get();
371 - (UIScrollView*)webScrollView {
372 return [_wkWebView scrollView];
375 - (BOOL)ignoreURLVerificationFailures {
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 {
401 *trustLevel = web::URLVerificationTrustLevel::kAbsolute;
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.
431 return web::WEB_VIEW_DOCUMENT_TYPE_GENERIC;
434 if (!self.documentMIMEType) {
435 return web::WEB_VIEW_DOCUMENT_TYPE_UNKNOWN;
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;
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);
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]];
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
492 web::WKBackForwardListItemHolder* holder =
493 [self currentBackForwardListItemHolder];
494 if (!holder->back_forward_list_item() ||
495 ![self isBackForwardListItemValid:holder->back_forward_list_item()]) {
496 defaultNavigationBlock();
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])) {
511 [_wkWebView goToBackForwardListItem:holder->back_forward_list_item()];
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();
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();
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 {
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())
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];
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 {
605 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
606 @"estimatedProgress" : @"webViewEstimatedProgressDidChange",
607 @"certificateChain" : @"webViewSecurityFeaturesDidChange",
608 @"hasOnlySecureContent" : @"webViewSecurityFeaturesDidChange",
610 @"loading" : @"webViewLoadingStateDidChange",
611 @"title" : @"webViewTitleDidChange",
612 @"URL" : @"webViewURLDidChange",
616 - (NSString*)requestGroupIDForUserAgent {
617 #if defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
618 return self.webStateImpl->GetRequestGroupID();
624 - (NSString*)activityIndicatorGroupID {
625 return [NSString stringWithFormat:
626 @"WKWebViewWebController.NetworkActivityIndicatorKey.%@",
627 self.webStateImpl->GetRequestGroupID()];
630 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
632 DCHECK(self.webStateImpl);
633 // Request tracker IDs are used as certificate groups.
634 return self.webStateImpl->GetRequestTracker()->identifier();
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 {
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];
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];
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];
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:
702 case WKNavigationTypeOther:
703 return [self userClickedRecently];
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;
740 web::WKBackForwardListItemHolder* holder =
741 web::WKBackForwardListItemHolder::FromNavigationItem(item);
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();
760 - (BOOL)isBackForwardListItemValid:(WKBackForwardListItem*)item {
761 // The current back-forward list item MUST be in the WKWebView's back-forward
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())
774 base::WeakNSObject<CRWWKWebViewWebController> weakSelf(self);
776 [weakSelf webViewWebProcessDidCrash];
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];
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);
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
812 windowName:kWindowName
814 DCHECK(!child || child.sessionController.openedByDOM);
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:)]) {
827 base::WeakNSObject<CRWWKWebViewWebController> weakSelf(self);
828 dispatch_async(dispatch_get_main_queue(), ^{
829 [self queryPageReferrerPolicy:^(NSString* policy) {
830 [weakSelf didBlockPopupWithURL:popupURL
832 referrerPolicy:base::SysNSStringToUTF8(policy)];
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
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])
872 DCHECK(self.webStateImpl);
873 web::NavigationItem* item =
874 self.webStateImpl->GetNavigationManagerImpl().GetLastCommittedItem();
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)]));
892 SSLStatus.cert_id = web::CertStore::GetInstance()->StoreCert(
893 cert.get(), self.certGroupID);
895 SSLStatus.security_style = web::SECURITY_STYLE_UNAUTHENTICATED;
896 SSLStatus.cert_id = 0;
900 SSLStatus.security_style = web::SECURITY_STYLE_UNAUTHENTICATED;
901 SSLStatus.cert_id = 0;
904 if (!previousSSLStatus.Equals(SSLStatus)) {
905 [self didUpdateSSLStatusForCurrentNavigationItem];
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
934 if (!_changingHistoryState) {
935 [self registerLoadRequest:_documentURL];
936 [self didStartLoadingURL:_documentURL updateHistory:YES];
937 [self didFinishNavigation];
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];
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";
961 - (BOOL)respondToWKScriptMessage:(WKScriptMessage*)scriptMessage {
962 CHECK(scriptMessage.frameInfo.mainFrame);
964 std::string errorMessage;
965 scoped_ptr<base::Value> inputJSONData(
966 base::JSONReader::ReadAndReturnError(
967 base::SysNSStringToUTF8(scriptMessage.body),
972 DLOG(WARNING) << "JSON parse error: %s" << errorMessage.c_str();
975 base::DictionaryValue* message = nullptr;
976 if (!inputJSONData->GetAsDictionary(&message)) {
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);
988 base::DictionaryValue* command = nullptr;
989 if (!message->GetDictionary("crwCommand", &command)) {
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])];
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:);
1016 auto iter = handlers->find(command);
1017 return iter != handlers->end()
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))
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;
1037 #pragma mark JavaScript message handlers
1039 - (BOOL)handleWindowHistoryWillChangeStateMessage:
1040 (base::DictionaryValue*)message
1041 context:(NSDictionary*)context {
1042 _changingHistoryState = YES;
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
1066 - (void)createWebUIForURL:(const GURL&)URL {
1067 [super createWebUIForURL:URL];
1068 _webUIManager.reset(
1069 [[CRWWebUIManager alloc] initWithWebState:self.webStateImpl]);
1072 - (void)clearWebUI {
1074 _webUIManager.reset();
1078 #pragma mark KVO Observation
1080 - (void)observeValueForKeyPath:(NSString*)keyPath
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]];
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.
1106 [self updateSSLStatusForCurrentNavigationItem];
1109 #endif // !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
1111 - (void)webViewLoadingStateDidChange {
1112 if ([_wkWebView isLoading]) {
1113 [self addActivityIndicatorTask];
1115 [self clearActivityIndicatorTasks];
1119 - (void)webViewTitleDidChange {
1120 if ([self.delegate respondsToSelector:
1121 @selector(webController:titleDidChange:)]) {
1123 [self.delegate webController:self titleDidChange:self.title];
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";
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
1142 // 3) When a navigation error occurs after provisional navigation starts,
1143 // the URL reverts to the previous URL without triggering a new navigation.
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.
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)
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.
1169 ![result isKindOfClass:[NSString class]]) {
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];
1183 #pragma mark WKNavigationDelegate Methods
1185 - (void)webView:(WKWebView *)webView
1186 decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
1188 (void (^)(WKNavigationActionPolicy))decisionHandler {
1189 if (self.isBeingDestroyed) {
1190 decisionHandler(WKNavigationActionPolicyCancel);
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:
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];
1214 allowLoad = self.webStateImpl->ShouldAllowRequest(request);
1215 _pendingNavigationCancelled = !allowLoad;
1218 decisionHandler(allowLoad ? WKNavigationActionPolicyAllow
1219 : WKNavigationActionPolicyCancel);
1222 - (void)webView:(WKWebView *)webView
1223 decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse
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));
1239 if (navigationResponse.isForMainFrame)
1240 self.documentMIMEType = navigationResponse.response.MIMEType;
1242 BOOL allowNavigation = navigationResponse.canShowMIMEType;
1243 if (allowNavigation) {
1245 self.webStateImpl->ShouldAllowResponse(navigationResponse.response);
1246 _pendingNavigationCancelled = !allowNavigation;
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);
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.
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];
1284 [self registerLoadRequest:webViewURL];
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;
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);
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];
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];
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)
1377 - (void)webView:(WKWebView *)webView
1378 didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
1380 (void (^)(NSURLSessionAuthChallengeDisposition disposition,
1381 NSURLCredential *credential))completionHandler {
1382 if (![challenge.protectionSpace.authenticationMethod
1383 isEqual:NSURLAuthenticationMethodServerTrust]) {
1384 completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
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,
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)) :
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;
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:
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();
1452 - (void)webView:(WKWebView*)webView
1453 runJavaScriptConfirmPanelWithMessage:(NSString*)message
1454 initiatedByFrame:(WKFrameInfo*)frame
1456 (void (^)(BOOL result))completionHandler {
1457 SEL confirmationSelector = @selector(webController:
1458 runJavaScriptConfirmPanelWithMessage:
1460 completionHandler:);
1461 if ([self.UIDelegate respondsToSelector:confirmationSelector]) {
1462 [self.UIDelegate webController:self
1463 runJavaScriptConfirmPanelWithMessage:message
1465 net::GURLWithNSURL(frame.request.URL)
1466 completionHandler:completionHandler];
1467 } else if (completionHandler) {
1468 completionHandler(NO);
1472 - (void)webView:(WKWebView*)webView
1473 runJavaScriptTextInputPanelWithPrompt:(NSString*)prompt
1474 defaultText:(NSString*)defaultText
1475 initiatedByFrame:(WKFrameInfo*)frame
1477 (void (^)(NSString *result))completionHandler {
1478 SEL textInputSelector = @selector(webController:
1479 runJavaScriptTextInputPanelWithPrompt:
1482 completionHandler:);
1483 if ([self.UIDelegate respondsToSelector:textInputSelector]) {
1484 [self.UIDelegate webController:self
1485 runJavaScriptTextInputPanelWithPrompt:prompt
1486 placeholderText:defaultText
1488 net::GURLWithNSURL(frame.request.URL)
1489 completionHandler:completionHandler];
1490 } else if (completionHandler) {
1491 completionHandler(nil);