Fix breakages in https://codereview.chromium.org/1155713003/
[chromium-blink-merge.git] / ios / web / web_state / ui / crw_wk_web_view_web_controller.mm
blob9f4e3a934aa2dcc64480a8c998ab4d58efbd42a2
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"
42 #endif
44 namespace {
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";
53 }  // namespace
55 @interface CRWWKWebViewWebController () <WKNavigationDelegate,
56                                          WKScriptMessageHandler,
57                                          WKUIDelegate> {
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.
68   GURL _documentURL;
70   // A set of script managers whose scripts have been injected into the current
71   // page.
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
77   // idempotent.
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
83   // committed.
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
109 // changed.
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
162 // popup.
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;
173 #endif
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;
185 #endif
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;
224 @end
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.
237   NOTIMPLEMENTED();
238   return NO;
241 - (void)setKeyboardDisplayRequiresUserAction:(BOOL)requiresUserAction {
242   NOTIMPLEMENTED();
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];
273 #pragma mark -
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
283 - (UIView*)webView {
284   return _wkWebView.get();
287 - (UIScrollView*)webScrollView {
288   return [_wkWebView scrollView];
291 - (BOOL)ignoreURLVerificationFailures {
292   return NO;
295 - (NSString*)title {
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 {
316   DCHECK(trustLevel);
317   *trustLevel = web::URLVerificationTrustLevel::kAbsolute;
318   return _documentURL;
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.
335   if (!_wkWebView) {
336     return web::WEB_VIEW_DOCUMENT_TYPE_GENERIC;
337   }
339   if (!self.documentMIMEType) {
340     return web::WEB_VIEW_DOCUMENT_TYPE_UNKNOWN;
341   }
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;
347   }
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);
374   }
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 {
393   // Nothing to do.
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())
415     return;
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 {
435   return @{
436 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
437     @"estimatedProgress" : @"webViewEstimatedProgressDidChange",
438     @"hasOnlySecureContent" : @"webViewContentSecurityDidChange",
439 #endif
440     @"loading" : @"webViewLoadingStateDidChange",
441     @"title" : @"webViewTitleDidChange",
442     @"URL" : @"webViewURLDidChange",
443   };
446 - (NSString*)activityIndicatorGroupID {
447   return [NSString stringWithFormat:
448       @"WKWebViewWebController.NetworkActivityIndicatorKey.%@",
449           self.webStateImpl->GetRequestGroupID()];
452 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
453 - (int)certGroupID {
454   DCHECK(self.webStateImpl);
455   // Request tracker IDs are used as certificate groups.
456   return self.webStateImpl->GetRequestTracker()->identifier();
458 #endif
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 {
467   if (!_wkWebView) {
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];
478   }
481 - (WKWebView*)createWebViewWithConfiguration:(WKWebViewConfiguration*)config {
482   return [web::CreateWKWebView(
483               CGRectZero,
484               config,
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];
502   }
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];
516   }
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:
525       return YES;
526     case WKNavigationTypeOther:
527       return [self userClickedRecently];
528     default:
529       return NO;
530   }
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 {
551   if (!webView)
552     return nil;
554   base::WeakNSObject<CRWWKWebViewWebController> weakSelf(self);
555   id crashHandler = ^{
556     [weakSelf webViewWebProcessDidCrash];
557   };
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];
566   }
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);
575   }];
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
591                     referrer:referrer
592                   windowName:kWindowName
593                 inBackground:NO];
594       DCHECK(!child || child.sessionController.openedByDOM);
595   };
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:)]) {
604     return;
605   }
607   base::WeakNSObject<CRWWKWebViewWebController> weakSelf(self);
608   dispatch_async(dispatch_get_main_queue(), ^{
609       [self queryPageReferrerPolicy:^(NSString* policy) {
610           [weakSelf didBlockPopupWithURL:popupURL
611                                sourceURL:sourceURL
612                           referrerPolicy:base::SysNSStringToUTF8(policy)];
613       }];
614   });
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
632                      recoverable:NO
633                         callback:nullptr];
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();
652   if (!item)
653     return;
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
689   // the next runloop.
690   if (!_changingHistoryState) {
691     [self registerLoadRequest:_documentURL];
692     [self didStartLoadingURL:_documentURL updateHistory:YES];
693     [self didFinishNavigation];
694   }
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];
705   return result;
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";
714   }
717 - (BOOL)respondToWKScriptMessage:(WKScriptMessage*)scriptMessage {
718   CHECK(scriptMessage.frameInfo.mainFrame);
719   int errorCode = 0;
720   std::string errorMessage;
721   scoped_ptr<base::Value> inputJSONData(
722       base::JSONReader::ReadAndReturnError(
723           base::SysNSStringToUTF8(scriptMessage.body),
724           false,
725           &errorCode,
726           &errorMessage));
727   if (errorCode) {
728     DLOG(WARNING) << "JSON parse error: %s" << errorMessage.c_str();
729     return NO;
730   }
731   base::DictionaryValue* message = nullptr;
732   if (!inputJSONData->GetAsDictionary(&message)) {
733     return NO;
734   }
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);
742     return NO;
743   }
744   base::DictionaryValue* command = nullptr;
745   if (!message->GetDictionary("crwCommand", &command)) {
746     return NO;
747   }
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])];
753   }
755   NOTREACHED();
756   return NO;
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:);
770   });
771   DCHECK(handlers);
772   auto iter = handlers->find(command);
773   return iter != handlers->end()
774              ? iter->second
775              : [super selectorToHandleJavaScriptCommand:command];
778 #pragma mark -
779 #pragma mark JavaScript message handlers
781 - (BOOL)handleWindowHistoryWillChangeStateMessage:
782     (base::DictionaryValue*)message
783                                           context:(NSDictionary*)context {
784   _changingHistoryState = YES;
785   return 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
801                                                   context:context];
804 #pragma mark -
805 #pragma mark WebUI
807 - (void)createWebUIForURL:(const GURL&)URL {
808   [super createWebUIForURL:URL];
809   web_ui_manager_.reset(
810       [[CRWWebUIManager alloc] initWithWebState:self.webStateImpl]);
813 - (void)clearWebUI {
814   [super clearWebUI];
815   web_ui_manager_.reset();
818 #pragma mark -
819 #pragma mark KVO Observation
821 - (void)observeValueForKeyPath:(NSString*)keyPath
822                       ofObject:(id)object
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]];
838   }
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];
850   } else {
851     [self clearActivityIndicatorTasks];
852   }
855 - (void)webViewTitleDidChange {
856   if ([self.delegate respondsToSelector:
857           @selector(webController:titleDidChange:)]) {
858     DCHECK(self.title);
859     [self.delegate webController:self titleDidChange:self.title];
860   }
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";
868     return;
869   }
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
877   //    be reported.
878   // 3) When a navigation error occurs after provisional navigation starts,
879   //    the URL reverts to the previous URL without triggering a new navigation.
880   //
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.
885   //
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)
896       return;
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.
904                      if (!_wkWebView ||
905                          ![result isKindOfClass:[NSString class]]) {
906                        return;
907                      }
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];
913                      }
914                  }];
915   }
918 #pragma mark -
919 #pragma mark WKNavigationDelegate Methods
921 - (void)webView:(WKWebView *)webView
922     decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
923                     decisionHandler:
924         (void (^)(WKNavigationActionPolicy))decisionHandler {
925   if (self.isBeingDestroyed) {
926     decisionHandler(WKNavigationActionPolicyCancel);
927     return;
928   }
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
951                       decisionHandler:
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));
965   }
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.
985     [self clearWebUI];
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)) {
989       [self abortWebLoad];
990       web::WebLoadParams params(webViewURL);
991       [self loadWithParams:params];
992       return;
993     } else {
994       [self registerLoadRequest:webViewURL];
995     }
996   }
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];
1018   else
1019 #endif
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];
1041 #endif
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
1063                     completionHandler:
1064         (void (^)(NSURLSessionAuthChallengeDisposition disposition,
1065                   NSURLCredential *credential))completionHandler {
1066   NOTIMPLEMENTED();
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)) :
1079                                [self currentURL];
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;
1086     return nil;
1087   }
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:
1106                                    requestURL:
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();
1115   }
1118 - (void)webView:(WKWebView*)webView
1119     runJavaScriptConfirmPanelWithMessage:(NSString*)message
1120                         initiatedByFrame:(WKFrameInfo*)frame
1121                        completionHandler:
1122         (void (^)(BOOL result))completionHandler {
1123   SEL confirmationSelector = @selector(webController:
1124                 runJavaScriptConfirmPanelWithMessage:
1125                                           requestURL:
1126                                    completionHandler:);
1127   if ([self.UIDelegate respondsToSelector:confirmationSelector]) {
1128     [self.UIDelegate webController:self
1129         runJavaScriptConfirmPanelWithMessage:message
1130                                   requestURL:
1131             net::GURLWithNSURL(frame.request.URL)
1132                            completionHandler:completionHandler];
1133   } else if (completionHandler) {
1134     completionHandler(NO);
1135   }
1138 - (void)webView:(WKWebView*)webView
1139     runJavaScriptTextInputPanelWithPrompt:(NSString*)prompt
1140                               defaultText:(NSString*)defaultText
1141                          initiatedByFrame:(WKFrameInfo*)frame
1142                         completionHandler:
1143         (void (^)(NSString *result))completionHandler {
1144   SEL textInputSelector = @selector(webController:
1145             runJavaScriptTextInputPanelWithPrompt:
1146                                   placeholderText:
1147                                        requestURL:
1148                                 completionHandler:);
1149   if ([self.UIDelegate respondsToSelector:textInputSelector]) {
1150     [self.UIDelegate webController:self
1151         runJavaScriptTextInputPanelWithPrompt:prompt
1152                               placeholderText:defaultText
1153                                    requestURL:
1154             net::GURLWithNSURL(frame.request.URL)
1155                             completionHandler:completionHandler];
1156   } else if (completionHandler) {
1157     completionHandler(nil);
1158   }
1161 @end