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/weak_nsobject.h"
10 #include "base/json/json_reader.h"
11 #import "base/mac/scoped_nsobject.h"
12 #include "base/macros.h"
13 #include "base/strings/sys_string_conversions.h"
14 #include "base/values.h"
15 #import "ios/net/http_response_headers_util.h"
16 #import "ios/web/crw_network_activity_indicator_manager.h"
17 #import "ios/web/navigation/crw_session_controller.h"
18 #include "ios/web/navigation/web_load_params.h"
19 #include "ios/web/public/web_client.h"
20 #import "ios/web/public/web_state/crw_native_content_provider.h"
21 #import "ios/web/public/web_state/js/crw_js_injection_manager.h"
22 #import "ios/web/ui_web_view_util.h"
23 #include "ios/web/web_state/blocked_popup_info.h"
24 #include "ios/web/web_state/frame_info.h"
25 #import "ios/web/web_state/js/crw_js_window_id_manager.h"
26 #import "ios/web/web_state/js/page_script_util.h"
27 #import "ios/web/web_state/ui/crw_web_controller+protected.h"
28 #import "ios/web/web_state/ui/crw_wk_web_view_crash_detector.h"
29 #import "ios/web/web_state/ui/web_view_js_utils.h"
30 #import "ios/web/web_state/ui/wk_web_view_configuration_provider.h"
31 #import "ios/web/web_state/web_state_impl.h"
32 #import "ios/web/web_state/web_view_creation_utils.h"
33 #import "ios/web/webui/crw_web_ui_manager.h"
34 #import "net/base/mac/url_conversions.h"
36 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
37 #include "ios/web/public/cert_store.h"
38 #include "ios/web/public/navigation_item.h"
39 #include "ios/web/public/ssl_status.h"
40 #import "ios/web/web_state/wk_web_view_ssl_error_util.h"
41 #include "net/ssl/ssl_info.h"
45 // Extracts Referer value from WKNavigationAction request header.
46 NSString* GetRefererFromNavigationAction(WKNavigationAction* action) {
47 return [action.request valueForHTTPHeaderField:@"Referer"];
50 NSString* const kScriptMessageName = @"crwebinvoke";
51 NSString* const kScriptImmediateName = @"crwebinvokeimmediate";
55 @interface CRWWKWebViewWebController () <WKNavigationDelegate,
56 WKScriptMessageHandler,
58 // The WKWebView managed by this instance.
59 base::scoped_nsobject<WKWebView> _wkWebView;
61 // The Watch Dog that detects and reports WKWebView crashes.
62 base::scoped_nsobject<CRWWKWebViewCrashDetector> _crashDetector;
64 // The actual URL of the document object (i.e., the last committed URL).
65 // TODO(stuartmorgan): Remove this in favor of just updating the session
66 // controller and treating that as authoritative. For now, this allows sharing
67 // the flow that's currently in the superclass.
70 // A set of script managers whose scripts have been injected into the current
72 // TODO(stuartmorgan): Revisit this approach; it's intended only as a stopgap
73 // measure to make all the existing script managers work. Longer term, there
74 // should probably be a couple of points where managers can register to have
75 // things happen automatically based on page lifecycle, and if they don't want
76 // to use one of those fixed points, they should make their scripts internally
78 base::scoped_nsobject<NSMutableSet> _injectedScriptManagers;
80 // Referrer pending for the next navigated page. Lifetime of this value starts
81 // at |decidePolicyForNavigationAction| where the referrer is extracted from
82 // the request and ends at |didCommitNavigation| where the request is
84 base::scoped_nsobject<NSString> _pendingReferrerString;
86 // Referrer for the current page.
87 base::scoped_nsobject<NSString> _currentReferrerString;
89 // Backs the property of the same name.
90 base::scoped_nsobject<NSString> _documentMIMEType;
92 // Whether the web page is currently performing window.history.pushState or
93 // window.history.replaceState
94 // Set to YES on window.history.willChangeState message. To NO on
95 // window.history.didPushState or window.history.didReplaceState.
96 BOOL _changingHistoryState;
98 // CRWWebUIManager object for loading WebUI pages.
99 base::scoped_nsobject<CRWWebUIManager> web_ui_manager_;
102 // Response's MIME type of the last known navigation.
103 @property(nonatomic, copy) NSString* documentMIMEType;
105 // Dictionary where keys are the names of WKWebView properties and values are
106 // selector names which should be called when a corresponding property has
107 // changed. e.g. @{ @"URL" : @"webViewURLDidChange" } means that
108 // -[self webViewURLDidChange] must be called every time when WKWebView.URL is
110 @property(nonatomic, readonly) NSDictionary* wkWebViewObservers;
112 // Activity indicator group ID for this web controller.
113 @property(nonatomic, readonly) NSString* activityIndicatorGroupID;
115 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
116 // Identifier used for storing and retrieving certificates.
117 @property(nonatomic, readonly) int certGroupID;
118 #endif // #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
120 // Returns the WKWebViewConfigurationProvider associated with the web
121 // controller's BrowserState.
122 - (web::WKWebViewConfigurationProvider&)webViewConfigurationProvider;
124 // Creates a web view with given |config|. No-op if web view is already created.
125 - (void)ensureWebViewCreatedWithConfiguration:(WKWebViewConfiguration*)config;
127 // Returns a new autoreleased web view created with given configuration.
128 - (WKWebView*)createWebViewWithConfiguration:(WKWebViewConfiguration*)config;
130 // Sets the value of the webView property, and performs its basic setup.
131 - (void)setWebView:(WKWebView*)webView;
133 // Returns whether the given navigation is triggered by a user link click.
134 - (BOOL)isLinkNavigation:(WKNavigationType)navigationType;
136 // Sets value of the pendingReferrerString property.
137 - (void)setPendingReferrerString:(NSString*)referrer;
139 // Extracts the referrer value from WKNavigationAction and sets it as a pending.
140 // The referrer is known in |decidePolicyForNavigationAction| however it must
141 // be in a pending state until |didCommitNavigation| where it becames current.
142 - (void)updatePendingReferrerFromNavigationAction:(WKNavigationAction*)action;
144 // Replaces the current referrer with the pending one. Referrer becames current
145 // at |didCommitNavigation| callback.
146 - (void)commitPendingReferrerString;
148 // Discards the pending referrer.
149 - (void)discardPendingReferrerString;
151 // Returns a new CRWWKWebViewCrashDetector created with the given |webView| or
152 // nil if |webView| is nil. Callers are responsible for releasing the object.
153 - (CRWWKWebViewCrashDetector*)newCrashDetectorWithWebView:(WKWebView*)webView;
155 // Called when web view process has been terminated.
156 - (void)webViewWebProcessDidCrash;
158 // Asynchronously returns the referrer policy for the current page.
159 - (void)queryPageReferrerPolicy:(void(^)(NSString*))responseHandler;
161 // Informs CWRWebDelegate that CRWWebController has detected and blocked a
163 - (void)didBlockPopupWithURL:(GURL)popupURL
164 sourceURL:(GURL)sourceURL
165 referrerPolicy:(const std::string&)referrerPolicyString;
167 // Convenience method to inform CWRWebDelegate about a blocked popup.
168 - (void)didBlockPopupWithURL:(GURL)popupURL sourceURL:(GURL)sourceURL;
170 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
171 // Called when a load ends in an SSL error.
172 - (void)handleSSLError:(NSError*)error;
175 // Adds an activity indicator tasks for this web controller.
176 - (void)addActivityIndicatorTask;
178 // Clears all activity indicator tasks for this web controller.
179 - (void)clearActivityIndicatorTasks;
181 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
182 // Updates SSL status for the current navigation item based on the information
183 // provided by web view.
184 - (void)updateSSLStatusForCurrentNavigationItem;
187 // Registers load request with empty referrer and link or client redirect
188 // transition based on user interaction state.
189 - (void)registerLoadRequest:(const GURL&)url;
191 // Called when a non-document-changing URL change occurs. Updates the
192 // _documentURL, and informs the superclass of the change.
193 - (void)URLDidChangeWithoutDocumentChange:(const GURL&)URL;
195 // Returns new autoreleased instance of WKUserContentController which has
196 // early page script.
197 - (WKUserContentController*)createUserContentController;
199 // Attempts to handle a script message. Returns YES on success, NO otherwise.
200 - (BOOL)respondToWKScriptMessage:(WKScriptMessage*)scriptMessage;
202 // Handles 'window.history.willChangeState' message.
203 - (BOOL)handleWindowHistoryWillChangeStateMessage:
204 (base::DictionaryValue*)message
205 context:(NSDictionary*)context;
207 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
208 // Called when WKWebView estimatedProgress has been changed.
209 - (void)webViewEstimatedProgressDidChange;
211 // Called when WKWebView hasOnlySecureContent property has changed.
212 - (void)webViewContentSecurityDidChange;
213 #endif // !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
215 // Called when WKWebView loading state has been changed.
216 - (void)webViewLoadingStateDidChange;
218 // Called when WKWebView title has been changed.
219 - (void)webViewTitleDidChange;
221 // Called when WKWebView URL has been changed.
222 - (void)webViewURLDidChange;
226 @implementation CRWWKWebViewWebController
228 #pragma mark CRWWebController public methods
230 - (instancetype)initWithWebState:(scoped_ptr<web::WebStateImpl>)webState {
231 return [super initWithWebState:webState.Pass()];
234 - (BOOL)keyboardDisplayRequiresUserAction {
235 // TODO(stuartmorgan): Find out whether YES or NO is correct; see comment
236 // in protected header.
241 - (void)setKeyboardDisplayRequiresUserAction:(BOOL)requiresUserAction {
245 - (void)evaluateJavaScript:(NSString*)script
246 stringResultHandler:(web::JavaScriptCompletion)handler {
247 NSString* safeScript = [self scriptByAddingWindowIDCheckForScript:script];
248 web::EvaluateJavaScript(_wkWebView, safeScript, handler);
251 - (web::WebViewType)webViewType {
252 return web::WK_WEB_VIEW_TYPE;
255 - (void)evaluateUserJavaScript:(NSString*)script {
256 [self setUserInteractionRegistered:YES];
257 web::EvaluateJavaScript(_wkWebView, script, nil);
260 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
261 - (void)terminateNetworkActivity {
262 web::CertStore::GetInstance()->RemoveCertsForGroup(self.certGroupID);
263 [super terminateNetworkActivity];
265 #endif // #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
267 - (void)setPageDialogOpenPolicy:(web::PageDialogOpenPolicy)policy {
268 // TODO(eugenebut): implement dialogs/windows suppression using
269 // WKNavigationDelegate methods where possible.
270 [super setPageDialogOpenPolicy:policy];
274 #pragma mark Testing-Only Methods
276 - (void)injectWebView:(id)webView {
277 [super injectWebView:webView];
278 [self setWebView:webView];
281 #pragma mark - Protected property implementations
284 return _wkWebView.get();
287 - (UIScrollView*)webScrollView {
288 return [_wkWebView scrollView];
291 - (BOOL)ignoreURLVerificationFailures {
296 return [_wkWebView title];
299 - (NSString*)currentReferrerString {
300 return _currentReferrerString.get();
303 #pragma mark Protected method implementations
305 - (void)ensureWebViewCreated {
306 WKWebViewConfiguration* config =
307 [self webViewConfigurationProvider].GetWebViewConfiguration();
308 [self ensureWebViewCreatedWithConfiguration:config];
311 - (void)resetWebView {
312 [self setWebView:nil];
315 - (GURL)webURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel {
317 *trustLevel = web::URLVerificationTrustLevel::kAbsolute;
321 - (void)registerUserAgent {
322 // TODO(stuartmorgan): Rename this method, since it works for both.
323 web::BuildAndRegisterUserAgentForUIWebView(
324 self.webStateImpl->GetRequestGroupID(),
325 [self useDesktopUserAgent]);
328 // The core.js cannot pass messages back to obj-c if it is injected
329 // to |WEB_VIEW_DOCUMENT| because it does not support iframe creation used
330 // by core.js to communicate back. That functionality is only supported
331 // by |WEB_VIEW_HTML_DOCUMENT|. |WEB_VIEW_DOCUMENT| is used when displaying
332 // non-HTML contents (e.g. PDF documents).
333 - (web::WebViewDocumentType)webViewDocumentType {
334 // This happens during tests.
336 return web::WEB_VIEW_DOCUMENT_TYPE_GENERIC;
339 if (!self.documentMIMEType) {
340 return web::WEB_VIEW_DOCUMENT_TYPE_UNKNOWN;
343 if ([self.documentMIMEType isEqualToString:@"text/html"] ||
344 [self.documentMIMEType isEqualToString:@"application/xhtml+xml"] ||
345 [self.documentMIMEType isEqualToString:@"application/xml"]) {
346 return web::WEB_VIEW_DOCUMENT_TYPE_HTML;
349 return web::WEB_VIEW_DOCUMENT_TYPE_GENERIC;
352 - (void)loadRequest:(NSMutableURLRequest*)request {
353 [_wkWebView loadRequest:request];
356 - (void)loadWebHTMLString:(NSString*)html forURL:(const GURL&)URL {
357 [_wkWebView loadHTMLString:html baseURL:net::NSURLWithGURL(URL)];
360 - (BOOL)scriptHasBeenInjectedForClass:(Class)jsInjectionManagerClass
361 presenceBeacon:(NSString*)beacon {
362 return [_injectedScriptManagers containsObject:jsInjectionManagerClass];
365 - (void)injectScript:(NSString*)script forClass:(Class)JSInjectionManagerClass {
366 // Skip evaluation if there's no content (e.g., if what's being injected is
367 // an umbrella manager).
368 if ([script length]) {
369 [super injectScript:script forClass:JSInjectionManagerClass];
370 // Every injection except windowID requires windowID check.
371 if (JSInjectionManagerClass != [CRWJSWindowIdManager class])
372 script = [self scriptByAddingWindowIDCheckForScript:script];
373 web::EvaluateJavaScript(_wkWebView, script, nil);
375 [_injectedScriptManagers addObject:JSInjectionManagerClass];
378 - (void)willLoadCurrentURLInWebView {
379 // TODO(stuartmorgan): Get a WKWebView version of the request ID verification
380 // code working for debug builds.
384 - (void)setPageChangeProbability:(web::PageChangeProbability)probability {
385 // Nothing to do; no polling timer.
388 - (void)abortWebLoad {
389 [_wkWebView stopLoading];
392 - (void)resetLoadState {
396 - (void)setSuppressDialogsWithHelperScript:(NSString*)script {
397 [self evaluateJavaScript:script stringResultHandler:nil];
400 - (CGFloat)absoluteZoomScaleForScrollState:
401 (const web::PageScrollState&)scrollState {
402 return scrollState.zoom_scale();
405 - (void)applyWebViewScrollZoomScaleFromScrollState:
406 (const web::PageScrollState&)scrollState {
407 // After rendering a web page, WKWebView keeps the |minimumZoomScale| and
408 // |maximumZoomScale| properties of its scroll view constant while adjusting
409 // the |zoomScale| property accordingly. The maximum-scale or minimum-scale
410 // meta tags of a page may have changed since the state was recorded, so clamp
411 // the zoom scale to the current range if necessary.
412 DCHECK(scrollState.IsZoomScaleValid());
413 // Legacy-format scroll states cannot be applied to WKWebViews.
414 if (scrollState.IsZoomScaleLegacyFormat())
416 CGFloat zoomScale = scrollState.zoom_scale();
417 if (zoomScale < self.webScrollView.minimumZoomScale)
418 zoomScale = self.webScrollView.minimumZoomScale;
419 if (zoomScale > self.webScrollView.maximumZoomScale)
420 zoomScale = self.webScrollView.maximumZoomScale;
421 self.webScrollView.zoomScale = zoomScale;
424 #pragma mark Private methods
426 - (NSString*)documentMIMEType {
427 return _documentMIMEType.get();
430 - (void)setDocumentMIMEType:(NSString*)type {
431 _documentMIMEType.reset([type copy]);
434 - (NSDictionary*)wkWebViewObservers {
436 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
437 @"estimatedProgress" : @"webViewEstimatedProgressDidChange",
438 @"hasOnlySecureContent" : @"webViewContentSecurityDidChange",
440 @"loading" : @"webViewLoadingStateDidChange",
441 @"title" : @"webViewTitleDidChange",
442 @"URL" : @"webViewURLDidChange",
446 - (NSString*)activityIndicatorGroupID {
447 return [NSString stringWithFormat:
448 @"WKWebViewWebController.NetworkActivityIndicatorKey.%@",
449 self.webStateImpl->GetRequestGroupID()];
452 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
454 DCHECK(self.webStateImpl);
455 // Request tracker IDs are used as certificate groups.
456 return self.webStateImpl->GetRequestTracker()->identifier();
460 - (web::WKWebViewConfigurationProvider&)webViewConfigurationProvider {
461 DCHECK(self.webStateImpl);
462 web::BrowserState* browserState = self.webStateImpl->GetBrowserState();
463 return web::WKWebViewConfigurationProvider::FromBrowserState(browserState);
466 - (void)ensureWebViewCreatedWithConfiguration:(WKWebViewConfiguration*)config {
468 // Use a separate userContentController for each web view.
469 // WKUserContentController does not allow adding multiple script message
470 // handlers for the same name, hence userContentController can't be shared
471 // between all web views.
472 config.userContentController = [self createUserContentController];
473 [self setWebView:[self createWebViewWithConfiguration:config]];
474 // Notify super class about created web view. -webViewDidChange is not
475 // called from -setWebView:scriptMessageRouter: as the latter used in unit
476 // tests with fake web view, which cannot be added to view hierarchy.
477 [self webViewDidChange];
481 - (WKWebView*)createWebViewWithConfiguration:(WKWebViewConfiguration*)config {
482 return [web::CreateWKWebView(
485 self.webStateImpl->GetBrowserState(),
486 self.webStateImpl->GetRequestGroupID(),
487 [self useDesktopUserAgent]) autorelease];
490 - (void)setWebView:(WKWebView*)webView {
491 DCHECK_NE(_wkWebView.get(), webView);
493 // Unwind the old web view.
494 WKUserContentController* oldContentController =
495 [[_wkWebView configuration] userContentController];
496 [oldContentController removeScriptMessageHandlerForName:kScriptMessageName];
497 [oldContentController removeScriptMessageHandlerForName:kScriptImmediateName];
498 [_wkWebView setNavigationDelegate:nil];
499 [_wkWebView setUIDelegate:nil];
500 for (NSString* keyPath in self.wkWebViewObservers) {
501 [_wkWebView removeObserver:self forKeyPath:keyPath];
503 [self clearActivityIndicatorTasks];
505 _wkWebView.reset([webView retain]);
507 // Set up the new web view.
508 WKUserContentController* newContentController =
509 [[_wkWebView configuration] userContentController];
510 [newContentController addScriptMessageHandler:self name:kScriptMessageName];
511 [newContentController addScriptMessageHandler:self name:kScriptImmediateName];
512 [_wkWebView setNavigationDelegate:self];
513 [_wkWebView setUIDelegate:self];
514 for (NSString* keyPath in self.wkWebViewObservers) {
515 [_wkWebView addObserver:self forKeyPath:keyPath options:0 context:nullptr];
517 _injectedScriptManagers.reset([[NSMutableSet alloc] init]);
518 _crashDetector.reset([self newCrashDetectorWithWebView:_wkWebView]);
519 _documentURL = [self defaultURL];
522 - (BOOL)isLinkNavigation:(WKNavigationType)navigationType {
523 switch (navigationType) {
524 case WKNavigationTypeLinkActivated:
526 case WKNavigationTypeOther:
527 return [self userClickedRecently];
533 - (void)setPendingReferrerString:(NSString*)referrer {
534 _pendingReferrerString.reset([referrer copy]);
537 - (void)updatePendingReferrerFromNavigationAction:(WKNavigationAction*)action {
538 if (action.targetFrame.isMainFrame)
539 [self setPendingReferrerString:GetRefererFromNavigationAction(action)];
542 - (void)commitPendingReferrerString {
543 _currentReferrerString.reset(_pendingReferrerString.release());
546 - (void)discardPendingReferrerString {
547 _pendingReferrerString.reset();
550 - (CRWWKWebViewCrashDetector*)newCrashDetectorWithWebView:(WKWebView*)webView {
554 base::WeakNSObject<CRWWKWebViewWebController> weakSelf(self);
556 [weakSelf webViewWebProcessDidCrash];
558 return [[CRWWKWebViewCrashDetector alloc] initWithWebView:webView
559 crashHandler:crashHandler];
562 - (void)webViewWebProcessDidCrash {
563 if ([self.delegate respondsToSelector:
564 @selector(webControllerWebProcessDidCrash:)]) {
565 [self.delegate webControllerWebProcessDidCrash:self];
569 - (void)queryPageReferrerPolicy:(void(^)(NSString*))responseHandler {
570 DCHECK(responseHandler);
571 [self evaluateJavaScript:@"__gCrWeb.getPageReferrerPolicy()"
572 stringResultHandler:^(NSString* referrer, NSError* error) {
573 DCHECK_NE(error.code, WKErrorJavaScriptExceptionOccurred);
574 responseHandler(!error ? referrer : nil);
578 - (void)didBlockPopupWithURL:(GURL)popupURL
579 sourceURL:(GURL)sourceURL
580 referrerPolicy:(const std::string&)referrerPolicyString {
581 web::ReferrerPolicy referrerPolicy =
582 [self referrerPolicyFromString:referrerPolicyString];
583 web::Referrer referrer(sourceURL, referrerPolicy);
584 NSString* const kWindowName = @""; // obsoleted
585 base::WeakNSObject<CRWWKWebViewWebController> weakSelf(self);
586 void(^showPopupHandler)() = ^{
587 // On Desktop cross-window comunication is not supported for unblocked
588 // popups; so it's ok to create a new independent page.
589 CRWWebController* child = [[weakSelf delegate]
590 webPageOrderedOpen:popupURL
592 windowName:kWindowName
594 DCHECK(!child || child.sessionController.openedByDOM);
597 web::BlockedPopupInfo info(popupURL, referrer, kWindowName, showPopupHandler);
598 [self.delegate webController:self didBlockPopup:info];
601 - (void)didBlockPopupWithURL:(GURL)popupURL sourceURL:(GURL)sourceURL {
602 if (![self.delegate respondsToSelector:
603 @selector(webController:didBlockPopup:)]) {
607 base::WeakNSObject<CRWWKWebViewWebController> weakSelf(self);
608 dispatch_async(dispatch_get_main_queue(), ^{
609 [self queryPageReferrerPolicy:^(NSString* policy) {
610 [weakSelf didBlockPopupWithURL:popupURL
612 referrerPolicy:base::SysNSStringToUTF8(policy)];
617 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
618 - (void)handleSSLError:(NSError*)error {
619 DCHECK(web::IsWKWebViewSSLError(error));
621 net::SSLInfo sslInfo;
622 web::GetSSLInfoFromWKWebViewSSLError(error, &sslInfo);
624 web::SSLStatus sslStatus;
625 sslStatus.security_style = web::SECURITY_STYLE_AUTHENTICATION_BROKEN;
626 sslStatus.cert_status = sslInfo.cert_status;
627 sslStatus.cert_id = web::CertStore::GetInstance()->StoreCert(
628 sslInfo.cert.get(), self.certGroupID);
630 [self.delegate presentSSLError:sslInfo
631 forSSLStatus:sslStatus
635 #endif // #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
637 - (void)addActivityIndicatorTask {
638 [[CRWNetworkActivityIndicatorManager sharedInstance]
639 startNetworkTaskForGroup:[self activityIndicatorGroupID]];
642 - (void)clearActivityIndicatorTasks {
643 [[CRWNetworkActivityIndicatorManager sharedInstance]
644 clearNetworkTasksForGroup:[self activityIndicatorGroupID]];
647 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
648 - (void)updateSSLStatusForCurrentNavigationItem {
649 DCHECK(self.webStateImpl);
650 web::NavigationItem* item =
651 self.webStateImpl->GetNavigationManagerImpl().GetLastCommittedItem();
655 // WKWebView will not load unauthenticated content.
656 item->GetSSL().security_style = item->GetURL().SchemeIs(url::kHttpsScheme) ?
657 web::SECURITY_STYLE_AUTHENTICATED : web::SECURITY_STYLE_UNAUTHENTICATED;
658 int contentStatus = [_wkWebView hasOnlySecureContent] ?
659 web::SSLStatus::NORMAL_CONTENT :
660 web::SSLStatus::DISPLAYED_INSECURE_CONTENT;
661 item->GetSSL().content_status = contentStatus;
662 [self didUpdateSSLStatusForCurrentNavigationItem];
664 #endif // !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
666 - (void)registerLoadRequest:(const GURL&)url {
667 // If load request is registered via WKWebViewWebController, assume transition
668 // is link or client redirect as other transitions will already be registered
669 // by web controller or delegates.
670 // TODO(stuartmorgan): Remove guesswork and replace with information from
671 // decidePolicyForNavigationAction:.
672 ui::PageTransition transition = self.userInteractionRegistered
673 ? ui::PAGE_TRANSITION_LINK
674 : ui::PAGE_TRANSITION_CLIENT_REDIRECT;
675 // The referrer is not known yet, and will be updated later.
676 const web::Referrer emptyReferrer;
677 [self registerLoadRequest:url referrer:emptyReferrer transition:transition];
680 - (void)URLDidChangeWithoutDocumentChange:(const GURL&)newURL {
681 DCHECK(newURL == net::GURLWithNSURL([_wkWebView URL]));
682 _documentURL = newURL;
683 // If called during window.history.pushState or window.history.replaceState
684 // JavaScript evaluation, only update the document URL. This callback does not
685 // have any information about the state object and cannot create (or edit) the
686 // navigation entry for this page change. Web controller will sync with
687 // history changes when a window.history.didPushState or
688 // window.history.didReplaceState message is received, which should happen in
690 if (!_changingHistoryState) {
691 [self registerLoadRequest:_documentURL];
692 [self didStartLoadingURL:_documentURL updateHistory:YES];
693 [self didFinishNavigation];
697 - (WKUserContentController*)createUserContentController {
698 WKUserContentController* result =
699 [[[WKUserContentController alloc] init] autorelease];
700 base::scoped_nsobject<WKUserScript> script([[WKUserScript alloc]
701 initWithSource:web::GetEarlyPageScript(web::WK_WEB_VIEW_TYPE)
702 injectionTime:WKUserScriptInjectionTimeAtDocumentStart
703 forMainFrameOnly:YES]);
704 [result addUserScript:script];
708 - (void)userContentController:(WKUserContentController*)userContentController
709 didReceiveScriptMessage:(WKScriptMessage*)message {
710 // Broken out into separate method to catch errors.
711 // TODO(jyquinn): Evaluate whether this is necessary for WKWebView.
712 if (![self respondToWKScriptMessage:message]) {
713 DLOG(WARNING) << "Message from JS not handled due to invalid format";
717 - (BOOL)respondToWKScriptMessage:(WKScriptMessage*)scriptMessage {
718 CHECK(scriptMessage.frameInfo.mainFrame);
720 std::string errorMessage;
721 scoped_ptr<base::Value> inputJSONData(
722 base::JSONReader::ReadAndReturnError(
723 base::SysNSStringToUTF8(scriptMessage.body),
728 DLOG(WARNING) << "JSON parse error: %s" << errorMessage.c_str();
731 base::DictionaryValue* message = nullptr;
732 if (!inputJSONData->GetAsDictionary(&message)) {
735 std::string windowID;
736 message->GetString("crwWindowId", &windowID);
737 // Check for correct windowID
738 if (![[self windowId] isEqualToString:base::SysUTF8ToNSString(windowID)]) {
739 DLOG(WARNING) << "Message from JS ignored due to non-matching windowID: "
740 << [self windowId] << " != "
741 << base::SysUTF8ToNSString(windowID);
744 base::DictionaryValue* command = nullptr;
745 if (!message->GetDictionary("crwCommand", &command)) {
748 if ([scriptMessage.name isEqualToString:kScriptImmediateName] ||
749 [scriptMessage.name isEqualToString:kScriptMessageName]) {
750 return [self respondToMessage:command
751 userIsInteracting:[self userIsInteracting]
752 originURL:net::GURLWithNSURL([_wkWebView URL])];
759 - (SEL)selectorToHandleJavaScriptCommand:(const std::string&)command {
760 static std::map<std::string, SEL>* handlers = nullptr;
761 static dispatch_once_t onceToken;
762 dispatch_once(&onceToken, ^{
763 handlers = new std::map<std::string, SEL>();
764 (*handlers)["window.history.didPushState"] =
765 @selector(handleWindowHistoryDidPushStateMessage:context:);
766 (*handlers)["window.history.didReplaceState"] =
767 @selector(handleWindowHistoryDidReplaceStateMessage:context:);
768 (*handlers)["window.history.willChangeState"] =
769 @selector(handleWindowHistoryWillChangeStateMessage:context:);
772 auto iter = handlers->find(command);
773 return iter != handlers->end()
775 : [super selectorToHandleJavaScriptCommand:command];
779 #pragma mark JavaScript message handlers
781 - (BOOL)handleWindowHistoryWillChangeStateMessage:
782 (base::DictionaryValue*)message
783 context:(NSDictionary*)context {
784 _changingHistoryState = YES;
788 - (BOOL)handleWindowHistoryDidPushStateMessage:(base::DictionaryValue*)message
789 context:(NSDictionary*)context {
790 DCHECK(_changingHistoryState);
791 _changingHistoryState = NO;
792 return [super handleWindowHistoryDidPushStateMessage:message context:context];
795 - (BOOL)handleWindowHistoryDidReplaceStateMessage:
796 (base::DictionaryValue*)message
797 context:(NSDictionary*)context {
798 DCHECK(_changingHistoryState);
799 _changingHistoryState = NO;
800 return [super handleWindowHistoryDidReplaceStateMessage:message
807 - (void)createWebUIForURL:(const GURL&)URL {
808 [super createWebUIForURL:URL];
809 web_ui_manager_.reset(
810 [[CRWWebUIManager alloc] initWithWebState:self.webStateImpl]);
815 web_ui_manager_.reset();
819 #pragma mark KVO Observation
821 - (void)observeValueForKeyPath:(NSString*)keyPath
823 change:(NSDictionary*)change
824 context:(void*)context {
825 NSString* dispatcherSelectorName = self.wkWebViewObservers[keyPath];
826 DCHECK(dispatcherSelectorName);
827 if (dispatcherSelectorName)
828 [self performSelector:NSSelectorFromString(dispatcherSelectorName)];
831 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
832 // TODO(eugenebut): use WKWebView progress even if Chrome net stack is enabled.
833 - (void)webViewEstimatedProgressDidChange {
834 if ([self.delegate respondsToSelector:
835 @selector(webController:didUpdateProgress:)]) {
836 [self.delegate webController:self
837 didUpdateProgress:[_wkWebView estimatedProgress]];
841 - (void)webViewContentSecurityDidChange {
842 [self updateSSLStatusForCurrentNavigationItem];
845 #endif // !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
847 - (void)webViewLoadingStateDidChange {
848 if ([_wkWebView isLoading]) {
849 [self addActivityIndicatorTask];
851 [self clearActivityIndicatorTasks];
855 - (void)webViewTitleDidChange {
856 if ([self.delegate respondsToSelector:
857 @selector(webController:titleDidChange:)]) {
859 [self.delegate webController:self titleDidChange:self.title];
863 - (void)webViewURLDidChange {
864 // TODO(stuartmorgan): Determine if there are any cases where this still
865 // happens, and if so whether anything should be done when it does.
866 if (![_wkWebView URL]) {
867 DVLOG(1) << "Received nil URL callback";
870 GURL url(net::GURLWithNSURL([_wkWebView URL]));
871 // URL changes happen at three points:
872 // 1) When a load starts; at this point, the load is provisional, and
873 // it should be ignored until it's committed, since the document/window
874 // objects haven't changed yet.
875 // 2) When a non-document-changing URL change happens (hash change,
876 // history.pushState, etc.). This URL change happens instantly, so should
878 // 3) When a navigation error occurs after provisional navigation starts,
879 // the URL reverts to the previous URL without triggering a new navigation.
881 // If |isLoading| is NO, then it must be case 2 or 3. If the last committed
882 // URL (_documentURL) matches the current URL, assume that it is a revert from
883 // navigation failure and do nothing. If the URL does not match, assume it is
884 // a non-document-changing URL change, and handle accordingly.
886 // If |isLoading| is YES, then it could either be case 1, or it could be
887 // case 2 on a page that hasn't finished loading yet. If the domain of the
888 // new URL matches the last committed URL, then check window.location.href,
889 // and if it matches, trust it. The domain check ensures that if a site
890 // somehow corrupts window.location.href it can't do a redirect to a
891 // slow-loading target page while it is still loading to spoof the domain.
892 // On a document-changing URL change, the window.location.href will match the
893 // previous URL at this stage, not the web view's current URL.
894 if (![_wkWebView isLoading]) {
895 if (_documentURL == url)
897 [self URLDidChangeWithoutDocumentChange:url];
898 } else if (!_documentURL.host().empty() &&
899 _documentURL.host() == url.host()) {
900 [_wkWebView evaluateJavaScript:@"window.location.href"
901 completionHandler:^(id result, NSError* error) {
902 // If the web view has gone away, or the location
903 // couldn't be retrieved, abort.
905 ![result isKindOfClass:[NSString class]]) {
908 GURL jsURL([result UTF8String]);
909 // Make sure that the URL is as expected, and re-check
910 // the host to prevent race conditions.
911 if (jsURL == url && _documentURL.host() == url.host()) {
912 [self URLDidChangeWithoutDocumentChange:url];
919 #pragma mark WKNavigationDelegate Methods
921 - (void)webView:(WKWebView *)webView
922 decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
924 (void (^)(WKNavigationActionPolicy))decisionHandler {
925 if (self.isBeingDestroyed) {
926 decisionHandler(WKNavigationActionPolicyCancel);
930 NSURLRequest* request = navigationAction.request;
931 GURL url = net::GURLWithNSURL(request.URL);
933 // The page will not be changed until this navigation is commited, so the
934 // retrieved referrer will be pending until |didCommitNavigation| callback.
935 [self updatePendingReferrerFromNavigationAction:navigationAction];
937 if (navigationAction.sourceFrame.isMainFrame)
938 self.documentMIMEType = nil;
940 web::FrameInfo targetFrame(navigationAction.targetFrame.isMainFrame);
941 BOOL isLinkClick = [self isLinkNavigation:navigationAction.navigationType];
942 BOOL allowLoad = [self shouldAllowLoadWithRequest:request
943 targetFrame:&targetFrame
944 isLinkClick:isLinkClick];
945 decisionHandler(allowLoad ? WKNavigationActionPolicyAllow
946 : WKNavigationActionPolicyCancel);
949 - (void)webView:(WKWebView *)webView
950 decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse
952 (void (^)(WKNavigationResponsePolicy))handler {
953 if ([navigationResponse.response isKindOfClass:[NSHTTPURLResponse class]]) {
954 // Create HTTP headers from the response.
955 // TODO(kkhorimoto): Due to the limited interface of NSHTTPURLResponse, some
956 // data in the HttpResponseHeaders generated here is inexact. Once
957 // UIWebView is no longer supported, update WebState's implementation so
958 // that the Content-Language and the MIME type can be set without using this
959 // imperfect conversion.
960 scoped_refptr<net::HttpResponseHeaders> HTTPHeaders =
961 net::CreateHeadersFromNSHTTPURLResponse(
962 static_cast<NSHTTPURLResponse*>(navigationResponse.response));
963 self.webStateImpl->OnHttpResponseHeadersReceived(
964 HTTPHeaders.get(), net::GURLWithNSURL(navigationResponse.response.URL));
966 if (navigationResponse.isForMainFrame)
967 self.documentMIMEType = navigationResponse.response.MIMEType;
968 handler(navigationResponse.canShowMIMEType ? WKNavigationResponsePolicyAllow :
969 WKNavigationResponsePolicyCancel);
972 // TODO(stuartmorgan): Move all the guesswork around these states out of the
973 // superclass, and wire these up to the remaining methods.
974 - (void)webView:(WKWebView *)webView
975 didStartProvisionalNavigation:(WKNavigation *)navigation {
976 GURL webViewURL = net::GURLWithNSURL(webView.URL);
977 // If this navigation has not yet been registered, do so. loadPhase check is
978 // necessary because lastRegisteredRequestURL may be the same as the
979 // webViewURL on a new tab created by window.open (default is about::blank).
980 // TODO(jyquinn): Audit [CRWWebController loadCurrentURL] for other tasks that
981 // should be performed here.
982 if (self.lastRegisteredRequestURL != webViewURL ||
983 self.loadPhase != web::LOAD_REQUESTED) {
984 // Reset current WebUI if one exists.
986 // If webViewURL is a chrome URL, abort the current load and initialize the
987 // load from the web controller.
988 if (web::GetWebClient()->IsAppSpecificURL(webViewURL)) {
990 web::WebLoadParams params(webViewURL);
991 [self loadWithParams:params];
994 [self registerLoadRequest:webViewURL];
997 // Ensure the URL is registered and loadPhase is as expected.
998 DCHECK(self.lastRegisteredRequestURL == webViewURL);
999 DCHECK(self.loadPhase == web::LOAD_REQUESTED);
1002 - (void)webView:(WKWebView *)webView
1003 didReceiveServerRedirectForProvisionalNavigation:
1004 (WKNavigation *)navigation {
1005 [self registerLoadRequest:net::GURLWithNSURL(webView.URL)
1006 referrer:[self currentReferrer]
1007 transition:ui::PAGE_TRANSITION_SERVER_REDIRECT];
1010 - (void)webView:(WKWebView *)webView
1011 didFailProvisionalNavigation:(WKNavigation *)navigation
1012 withError:(NSError *)error {
1013 [self discardPendingReferrerString];
1015 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
1016 if (web::IsWKWebViewSSLError(error))
1017 [self handleSSLError:error];
1020 [self handleLoadError:error inMainFrame:YES];
1023 - (void)webView:(WKWebView *)webView
1024 didCommitNavigation:(WKNavigation *)navigation {
1025 DCHECK_EQ(_wkWebView, webView);
1026 // This point should closely approximate the document object change, so reset
1027 // the list of injected scripts to those that are automatically injected.
1028 _injectedScriptManagers.reset([[NSMutableSet alloc] init]);
1029 [self injectWindowID];
1031 // The page has changed; commit the pending referrer.
1032 [self commitPendingReferrerString];
1034 // This is the point where the document's URL has actually changed.
1035 _documentURL = net::GURLWithNSURL([_wkWebView URL]);
1036 DCHECK(_documentURL == self.lastRegisteredRequestURL);
1037 [self webPageChanged];
1039 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
1040 [self updateSSLStatusForCurrentNavigationItem];
1044 - (void)webView:(WKWebView *)webView
1045 didFinishNavigation:(WKNavigation *)navigation {
1046 DCHECK(!self.isHalted);
1047 // Trigger JavaScript driven post-document-load-completion tasks.
1048 // TODO(jyquinn): Investigate using WKUserScriptInjectionTimeAtDocumentEnd to
1049 // inject this material at the appropriate time rather than invoking here.
1050 web::EvaluateJavaScript(webView,
1051 @"__gCrWeb.didFinishNavigation()", nil);
1052 [self didFinishNavigation];
1055 - (void)webView:(WKWebView *)webView
1056 didFailNavigation:(WKNavigation *)navigation
1057 withError:(NSError *)error {
1058 [self handleLoadError:error inMainFrame:YES];
1061 - (void)webView:(WKWebView *)webView
1062 didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
1064 (void (^)(NSURLSessionAuthChallengeDisposition disposition,
1065 NSURLCredential *credential))completionHandler {
1067 completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
1070 #pragma mark WKUIDelegate Methods
1072 - (WKWebView*)webView:(WKWebView*)webView
1073 createWebViewWithConfiguration:(WKWebViewConfiguration*)configuration
1074 forNavigationAction:(WKNavigationAction*)navigationAction
1075 windowFeatures:(WKWindowFeatures*)windowFeatures {
1076 GURL requestURL = net::GURLWithNSURL(navigationAction.request.URL);
1077 NSString* referer = GetRefererFromNavigationAction(navigationAction);
1078 GURL referrerURL = referer ? GURL(base::SysNSStringToUTF8(referer)) :
1081 if (![self userIsInteracting] &&
1082 [self shouldBlockPopupWithURL:requestURL sourceURL:referrerURL]) {
1083 [self didBlockPopupWithURL:requestURL sourceURL:referrerURL];
1084 // Desktop Chrome does not return a window for the blocked popups;
1085 // follow the same approach by returning nil;
1089 id child = [self createChildWebControllerWithReferrerURL:referrerURL];
1090 // WKWebView requires WKUIDelegate to return a child view created with
1091 // exactly the same |configuration| object (exception is raised if config is
1092 // different). |configuration| param and config returned by
1093 // WKWebViewConfigurationProvider are different objects because WKWebView
1094 // makes a shallow copy of the config inside init, so every WKWebView
1095 // owns a separate shallow copy of WKWebViewConfiguration.
1096 [child ensureWebViewCreatedWithConfiguration:configuration];
1097 return [child webView];
1100 - (void)webView:(WKWebView*)webView
1101 runJavaScriptAlertPanelWithMessage:(NSString*)message
1102 initiatedByFrame:(WKFrameInfo*)frame
1103 completionHandler:(void(^)())completionHandler {
1104 SEL alertSelector = @selector(webController:
1105 runJavaScriptAlertPanelWithMessage:
1107 completionHandler:);
1108 if ([self.UIDelegate respondsToSelector:alertSelector]) {
1109 [self.UIDelegate webController:self
1110 runJavaScriptAlertPanelWithMessage:message
1111 requestURL:net::GURLWithNSURL(frame.request.URL)
1112 completionHandler:completionHandler];
1113 } else if (completionHandler) {
1114 completionHandler();
1118 - (void)webView:(WKWebView*)webView
1119 runJavaScriptConfirmPanelWithMessage:(NSString*)message
1120 initiatedByFrame:(WKFrameInfo*)frame
1122 (void (^)(BOOL result))completionHandler {
1123 SEL confirmationSelector = @selector(webController:
1124 runJavaScriptConfirmPanelWithMessage:
1126 completionHandler:);
1127 if ([self.UIDelegate respondsToSelector:confirmationSelector]) {
1128 [self.UIDelegate webController:self
1129 runJavaScriptConfirmPanelWithMessage:message
1131 net::GURLWithNSURL(frame.request.URL)
1132 completionHandler:completionHandler];
1133 } else if (completionHandler) {
1134 completionHandler(NO);
1138 - (void)webView:(WKWebView*)webView
1139 runJavaScriptTextInputPanelWithPrompt:(NSString*)prompt
1140 defaultText:(NSString*)defaultText
1141 initiatedByFrame:(WKFrameInfo*)frame
1143 (void (^)(NSString *result))completionHandler {
1144 SEL textInputSelector = @selector(webController:
1145 runJavaScriptTextInputPanelWithPrompt:
1148 completionHandler:);
1149 if ([self.UIDelegate respondsToSelector:textInputSelector]) {
1150 [self.UIDelegate webController:self
1151 runJavaScriptTextInputPanelWithPrompt:prompt
1152 placeholderText:defaultText
1154 net::GURLWithNSURL(frame.request.URL)
1155 completionHandler:completionHandler];
1156 } else if (completionHandler) {
1157 completionHandler(nil);