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