Simplify external protocol blocking logic
[chromium-blink-merge.git] / ios / web / web_state / ui / crw_web_controller.mm
blob1b630ee104c503556ff1de1559dd27e0cc201f41
1 // Copyright 2012 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_web_controller.h"
7 #import <objc/runtime.h>
8 #include <cmath>
10 #include "base/ios/block_types.h"
11 #include "base/ios/weak_nsobject.h"
12 #include "base/json/json_reader.h"
13 #include "base/json/json_writer.h"
14 #include "base/json/string_escape.h"
15 #include "base/logging.h"
16 #include "base/mac/bundle_locations.h"
17 #include "base/mac/foundation_util.h"
18 #include "base/mac/objc_property_releaser.h"
19 #include "base/mac/scoped_cftyperef.h"
20 #include "base/mac/scoped_nsobject.h"
21 #include "base/memory/scoped_ptr.h"
22 #include "base/metrics/histogram.h"
23 #include "base/metrics/user_metrics_action.h"
24 #include "base/prefs/pref_service.h"
25 #include "base/strings/string_util.h"
26 #include "base/strings/sys_string_conversions.h"
27 #include "base/strings/utf_string_conversions.h"
28 #include "base/time/time.h"
29 #include "base/values.h"
30 #import "ios/net/nsurlrequest_util.h"
31 #include "ios/public/provider/web/web_ui_ios.h"
32 #import "ios/web/history_state_util.h"
33 #include "ios/web/interstitials/web_interstitial_impl.h"
34 #import "ios/web/navigation/crw_session_certificate_policy_manager.h"
35 #import "ios/web/navigation/crw_session_controller.h"
36 #import "ios/web/navigation/crw_session_entry.h"
37 #import "ios/web/navigation/navigation_item_impl.h"
38 #import "ios/web/navigation/navigation_manager_impl.h"
39 #import "ios/web/navigation/web_load_params.h"
40 #include "ios/web/net/request_group_util.h"
41 #include "ios/web/public/browser_state.h"
42 #include "ios/web/public/favicon_url.h"
43 #include "ios/web/public/navigation_item.h"
44 #include "ios/web/public/referrer.h"
45 #include "ios/web/public/referrer_util.h"
46 #include "ios/web/public/ssl_status.h"
47 #include "ios/web/public/url_scheme_util.h"
48 #include "ios/web/public/url_util.h"
49 #include "ios/web/public/user_metrics.h"
50 #include "ios/web/public/web_client.h"
51 #include "ios/web/public/web_state/credential.h"
52 #import "ios/web/public/web_state/crw_web_controller_observer.h"
53 #import "ios/web/public/web_state/crw_web_view_scroll_view_proxy.h"
54 #import "ios/web/public/web_state/js/crw_js_injection_manager.h"
55 #import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
56 #import "ios/web/public/web_state/ui/crw_native_content.h"
57 #import "ios/web/public/web_state/ui/crw_native_content_provider.h"
58 #include "ios/web/public/web_state/url_verification_constants.h"
59 #include "ios/web/public/web_state/web_state.h"
60 #include "ios/web/web_state/blocked_popup_info.h"
61 #import "ios/web/web_state/crw_web_view_proxy_impl.h"
62 #import "ios/web/web_state/error_translation_util.h"
63 #include "ios/web/web_state/frame_info.h"
64 #import "ios/web/web_state/js/credential_util.h"
65 #import "ios/web/web_state/js/crw_js_early_script_manager.h"
66 #import "ios/web/web_state/js/crw_js_plugin_placeholder_manager.h"
67 #import "ios/web/web_state/js/crw_js_window_id_manager.h"
68 #import "ios/web/web_state/ui/crw_context_menu_provider.h"
69 #import "ios/web/web_state/ui/crw_swipe_recognizer_provider.h"
70 #import "ios/web/web_state/ui/crw_ui_web_view_web_controller.h"
71 #import "ios/web/web_state/ui/crw_web_controller+protected.h"
72 #import "ios/web/web_state/ui/crw_web_controller_container_view.h"
73 #import "ios/web/web_state/ui/crw_wk_web_view_web_controller.h"
74 #import "ios/web/web_state/web_controller_observer_bridge.h"
75 #include "ios/web/web_state/web_state_facade_delegate.h"
76 #import "ios/web/web_state/web_state_impl.h"
77 #import "net/base/mac/url_conversions.h"
78 #include "net/base/net_errors.h"
79 #include "net/base/net_util.h"
80 #import "ui/base/ios/cru_context_menu_holder.h"
81 #include "ui/base/page_transition_types.h"
82 #include "url/gurl.h"
83 #include "url/url_constants.h"
85 using base::UserMetricsAction;
86 using web::NavigationManagerImpl;
87 using web::WebState;
88 using web::WebStateImpl;
90 namespace web {
92 NSString* const kContainerViewID = @"Container View";
93 const char* kWindowNameSeparator = "#";
94 NSString* const kUserIsInteractingKey = @"userIsInteracting";
95 NSString* const kOriginURLKey = @"originURL";
96 NSString* const kLogJavaScript = @"LogJavascript";
98 NewWindowInfo::NewWindowInfo(GURL target_url,
99                              NSString* target_window_name,
100                              web::ReferrerPolicy target_referrer_policy,
101                              bool target_user_is_interacting)
102     : url(target_url),
103       window_name([target_window_name copy]),
104       referrer_policy(target_referrer_policy),
105       user_is_interacting(target_user_is_interacting) {
108 NewWindowInfo::~NewWindowInfo() {
111 // Struct to capture data about a user interaction. Records the time of the
112 // interaction and the main document URL at that time.
113 struct UserInteractionEvent {
114   UserInteractionEvent(GURL url)
115       : main_document_url(url), time(CFAbsoluteTimeGetCurrent()) {}
116   // Main document URL at the time the interaction occurred.
117   GURL main_document_url;
118   // Time that the interaction occured, measured in seconds since Jan 1 2001.
119   CFAbsoluteTime time;
122 }  // namespace web
124 namespace {
126 // A tag for the web view, so that tests can identify it. This is used instead
127 // of exposing a getter (and deliberately not exposed in the header) to make it
128 // *very* clear that this is a hack which should only be used as a last resort.
129 const NSUInteger kWebViewTag = 0x3eb71e3;
131 // Cancels touch events for the given gesture recognizer.
132 void CancelTouches(UIGestureRecognizer* gesture_recognizer) {
133   if (gesture_recognizer.enabled) {
134     gesture_recognizer.enabled = NO;
135     gesture_recognizer.enabled = YES;
136   }
139 // Cancels all touch events for web view (long presses, tapping, scrolling).
140 void CancelAllTouches(UIScrollView* web_scroll_view) {
141   // Disable web view scrolling.
142   CancelTouches(web_scroll_view.panGestureRecognizer);
144   // All user gestures are handled by a subview of web view scroll view
145   // (UIWebBrowserView for UIWebView and WKContentView for WKWebView).
146   for (UIView* subview in web_scroll_view.subviews) {
147     for (UIGestureRecognizer* recognizer in subview.gestureRecognizers) {
148       CancelTouches(recognizer);
149     }
150   }
153 }  // namespace
155 @interface CRWWebController () <CRWNativeContentDelegate,
156                                 CRWWebViewScrollViewProxyObserver> {
157   base::WeakNSProtocol<id<CRWWebDelegate>> _delegate;
158   base::WeakNSProtocol<id<CRWWebUserInterfaceDelegate>> _UIDelegate;
159   base::WeakNSProtocol<id<CRWNativeContentProvider>> _nativeProvider;
160   base::WeakNSProtocol<id<CRWSwipeRecognizerProvider>> _swipeRecognizerProvider;
161   base::scoped_nsobject<CRWWebControllerContainerView> _containerView;
162   // The CRWWebViewProxy is the wrapper to give components access to the
163   // web view in a controlled and limited way.
164   base::scoped_nsobject<CRWWebViewProxyImpl> _webViewProxy;
165   // If |_contentView| contains a native view rather than a web view, this
166   // is its controller. If it's a web view, this is nil.
167   base::scoped_nsprotocol<id<CRWNativeContent>> _nativeController;
168   BOOL _isHalted;  // YES if halted. Halting happens prior to destruction.
169   BOOL _isBeingDestroyed;  // YES if in the process of closing.
170   // All CRWWebControllerObservers attached to the CRWWebController. A
171   // specially-constructed set is used that does not retain its elements.
172   base::scoped_nsobject<NSMutableSet> _observers;
173   // Each observer in |_observers| is associated with a
174   // WebControllerObserverBridge in order to listen from WebState callbacks.
175   // TODO(droger): Remove |_observerBridges| when all CRWWebControllerObservers
176   // are converted to WebStateObservers.
177   ScopedVector<web::WebControllerObserverBridge> _observerBridges;
178   // |windowId| that is saved when a page changes. Used to detect refreshes.
179   base::scoped_nsobject<NSString> _lastSeenWindowID;
180   // YES if a user interaction has been registered at any time once the page has
181   // loaded.
182   BOOL _userInteractionRegistered;
183   // Last URL change reported to webWill/DidStartLoadingURL. Used to detect page
184   // location changes (client redirects) in practice.
185   GURL _lastRegisteredRequestURL;
186   // Last URL change reported to webDidStartLoadingURL. Used to detect page
187   // location changes in practice.
188   GURL _URLOnStartLoading;
189   // Page loading phase.
190   web::LoadPhase _loadPhase;
191   // The web::PageDisplayState recorded when the page starts loading.
192   web::PageDisplayState _displayStateOnStartLoading;
193   // Whether or not the page has zoomed since the current navigation has been
194   // committed, either by user interaction or via |-restoreStateFromHistory|.
195   BOOL _pageHasZoomed;
196   // Actions to execute once the page load is complete.
197   base::scoped_nsobject<NSMutableArray> _pendingLoadCompleteActions;
198   // UIGestureRecognizers to add to the web view.
199   base::scoped_nsobject<NSMutableArray> _gestureRecognizers;
200   // Toolbars to add to the web view.
201   base::scoped_nsobject<NSMutableArray> _webViewToolbars;
202   // Flag to say if browsing is enabled.
203   BOOL _webUsageEnabled;
204   // Content view was reset due to low memory. Use the placeholder overlay on
205   // next creation.
206   BOOL _usePlaceholderOverlay;
207   // Overlay view used instead of webView.
208   base::scoped_nsobject<UIImageView> _placeholderOverlayView;
209   // The touch tracking recognizer allowing us to decide if a navigation is
210   // started by the user.
211   base::scoped_nsobject<CRWTouchTrackingRecognizer> _touchTrackingRecognizer;
212   // Long press recognizer that allows showing context menus.
213   base::scoped_nsobject<UILongPressGestureRecognizer> _contextMenuRecognizer;
214   // DOM element information for the point where the user made the last touch.
215   // Can be null if has not been calculated yet. Precalculation is necessary
216   // because retreiving DOM element relies on async API so element info can not
217   // be built on demand. May contain the following keys: "href", "src", "title",
218   // "referrerPolicy". All values are strings. Used for showing context menus.
219   scoped_ptr<base::DictionaryValue> _DOMElementForLastTouch;
220   // Whether a click is in progress.
221   BOOL _clickInProgress;
222   // Data on the recorded last user interaction.
223   scoped_ptr<web::UserInteractionEvent> _lastUserInteraction;
224   // The time of the last page transfer start, measured in seconds since Jan 1
225   // 2001.
226   CFAbsoluteTime _lastTransferTimeInSeconds;
227   // Default URL (about:blank).
228   GURL _defaultURL;
229   // Show overlay view, don't reload web page.
230   BOOL _overlayPreviewMode;
231   // If |YES|, call setSuppressDialogs when core.js is injected into the web
232   // view.
233   BOOL _setSuppressDialogsLater;
234   // If |YES|, call setSuppressDialogs when core.js is injected into the web
235   // view.
236   BOOL _setNotifyAboutDialogsLater;
237   // The URL of an expected future recreation of the |webView|. Valid
238   // only if the web view was discarded for non-user-visible reasons, such that
239   // if the next load request is for that URL, it should be treated as a
240   // reconstruction that should use cache aggressively.
241   GURL _expectedReconstructionURL;
243   scoped_ptr<web::NewWindowInfo> _externalRequest;
245   // The WebStateImpl instance associated with this CRWWebController.
246   scoped_ptr<WebStateImpl> _webStateImpl;
248   // A set of URLs opened in external applications; stored so that errors
249   // from the web view can be identified as resulting from these events.
250   base::scoped_nsobject<NSMutableSet> _openedApplicationURL;
252   // Object that manages all early script injection into the web view.
253   base::scoped_nsobject<CRWJSEarlyScriptManager> _earlyScriptManager;
255   // Script manager for setting the windowID.
256   base::scoped_nsobject<CRWJSWindowIdManager> _windowIDJSManager;
258   // The receiver of JavaScripts.
259   base::scoped_nsobject<CRWJSInjectionReceiver> _jsInjectionReceiver;
262 // The current page state of the web view. Writing to this property
263 // asynchronously applies the passed value to the current web view.
264 @property(nonatomic, readwrite) web::PageDisplayState pageDisplayState;
265 // Resets any state that is associated with a specific document object (e.g.,
266 // page interaction tracking).
267 - (void)resetDocumentSpecificState;
268 // Returns YES if the URL looks like it is one CRWWebController can show.
269 + (BOOL)webControllerCanShow:(const GURL&)url;
270 // Clear any interstitials being displayed.
271 - (void)clearInterstitials;
272 // Returns a lazily created CRWTouchTrackingRecognizer.
273 - (CRWTouchTrackingRecognizer*)touchTrackingRecognizer;
274 // Shows placeholder overlay.
275 - (void)addPlaceholderOverlay;
276 // Removes placeholder overlay.
277 - (void)removePlaceholderOverlay;
278 // Returns |YES| if |url| should be loaded in a native view.
279 - (BOOL)shouldLoadURLInNativeView:(const GURL&)url;
280 // Loads the HTML into the page at the given URL.
281 - (void)loadHTML:(NSString*)html forURL:(const GURL&)url;
282 // Loads the current nativeController in a native view. If a web view is
283 // present, removes it and swaps in the native view in its place.
284 - (void)loadNativeViewWithSuccess:(BOOL)loadSuccess;
285 // YES if the navigation to |url| should be treated as a reload.
286 - (BOOL)shouldReload:(const GURL&)destinationURL
287           transition:(ui::PageTransition)transition;
288 // Internal implementation of reload. Reloads without notifying the delegate.
289 // Most callers should use -reload instead.
290 - (void)reloadInternal;
291 // If YES, the page can be closed if the loading of the initial URL requires
292 // it (for example when an external URL is detected). After the initial URL is
293 // loaded, the page is not cancellable anymore.
294 - (BOOL)cancellable;
295 // Called after URL is finished loading and _loadPhase is set to PAGE_LOADED.
296 - (void)didFinishWithURL:(const GURL&)currentURL loadSuccess:(BOOL)loadSuccess;
297 // Informs the native controller if web usage is allowed or not.
298 - (void)setNativeControllerWebUsageEnabled:(BOOL)webUsageEnabled;
299 // Compares the two URLs being navigated between during a history navigation to
300 // determine if a # needs to be appended to endURL to trigger a hashchange
301 // event. If so, also saves the new endURL in the current CRWSessionEntry.
302 - (GURL)updateURLForHistoryNavigationFromURL:(const GURL&)startURL
303                                        toURL:(const GURL&)endURL;
304 // Evaluates the supplied JavaScript in the web view. Calls |handler| with
305 // results of the evaluation (which may be nil if the implementing object has no
306 // way to run the evaluation or the evaluation returns a nil value) or an
307 // NSError if there is an error. The |handler| can be nil.
308 - (void)evaluateJavaScript:(NSString*)script
309          JSONResultHandler:(void (^)(scoped_ptr<base::Value>, NSError*))handler;
310 // Generates the JavaScript string used to update the UIWebView's URL so that it
311 // matches the URL displayed in the omnibox and sets window.history.state to
312 // stateObject. Needed for history.pushState() and history.replaceState().
313 - (NSString*)javascriptToReplaceWebViewURL:(const GURL&)url
314                            stateObjectJSON:(NSString*)stateObject;
315 - (BOOL)isLoaded;
316 // Called by NSNotificationCenter upon orientation changes.
317 - (void)orientationDidChange;
318 // Queries the web view for the user-scalable meta tag and calls
319 // |-applyPageDisplayState:userScalable:| with the result.
320 - (void)applyPageDisplayState:(const web::PageDisplayState&)displayState;
321 // Restores state of the web view's scroll view from |scrollState|.
322 // |isUserScalable| represents the value of user-scalable meta tag.
323 - (void)applyPageDisplayState:(const web::PageDisplayState&)displayState
324                  userScalable:(BOOL)isUserScalable;
325 // Calls the zoom-preparation UIScrollViewDelegate callbacks on the web view.
326 // This is called before |-applyWebViewScrollZoomScaleFromScrollState:|.
327 - (void)prepareToApplyWebViewScrollZoomScale;
328 // Calls the zoom-completion UIScrollViewDelegate callbacks on the web view.
329 // This is called after |-applyWebViewScrollZoomScaleFromScrollState:|.
330 - (void)finishApplyingWebViewScrollZoomScale;
331 // Sets scroll offset value for webview scroll view from |scrollState|.
332 - (void)applyWebViewScrollOffsetFromScrollState:
333     (const web::PageScrollState&)scrollState;
334 // Asynchronously determines whether option |user-scalable| is on in the
335 // viewport meta of the current web page.
336 - (void)queryUserScalableProperty:(void (^)(BOOL))responseHandler;
337 // Asynchronously fetches full width of the rendered web page.
338 - (void)fetchWebPageWidthWithCompletionHandler:(void (^)(CGFloat))handler;
339 // Asynchronously fetches information about DOM element for the given point (in
340 // UIView coordinates). |handler| can not be nil. See |_DOMElementForLastTouch|
341 // for element format description.
342 - (void)fetchDOMElementAtPoint:(CGPoint)point
343              completionHandler:
344                  (void (^)(scoped_ptr<base::DictionaryValue>))handler;
345 // Extracts context menu information from the given DOM element.
346 // result keys are defined in crw_context_menu_provider.h.
347 - (NSDictionary*)contextMenuInfoForElement:(base::DictionaryValue*)element;
348 // Sets the value of |_DOMElementForLastTouch|.
349 - (void)setDOMElementForLastTouch:(scoped_ptr<base::DictionaryValue>)element;
350 // Called when the window has determined there was a long-press and context menu
351 // must be shown.
352 - (void)showContextMenu:(UIGestureRecognizer*)gestureRecognizer;
353 // YES if delegate supports showing context menu by responding to
354 // webController:runContextMenu:atPoint:inView: selector.
355 - (BOOL)supportsCustomContextMenu;
356 // Returns the referrer for the current page.
357 - (web::Referrer)currentReferrer;
358 // Presents an error to the user because the CRWWebController cannot verify the
359 // URL of the current page.
360 - (void)presentSpoofingError;
361 // Adds a new CRWSessionEntry with the given URL and state object to the history
362 // stack. A state object is a serialized generic JavaScript object that contains
363 // details of the UI's state for a given CRWSessionEntry/URL.
364 // TODO(stuartmorgan): Move the pushState/replaceState logic into
365 // NavigationManager.
366 - (void)pushStateWithPageURL:(const GURL&)pageUrl
367                  stateObject:(NSString*)stateObject;
368 // Assigns the given URL and state object to the current CRWSessionEntry.
369 - (void)replaceStateWithPageURL:(const GURL&)pageUrl
370                     stateObject:(NSString*)stateObject;
372 // Returns the current entry from the underlying session controller.
373 // TODO(stuartmorgan): Audit all calls to these methods; these are just wrappers
374 // around the same logic as GetActiveEntry, so should probably not be used for
375 // the same reason that GetActiveEntry is deprecated. (E.g., page operations
376 // should generally be dealing with the last commited entry, not a pending
377 // entry).
378 - (CRWSessionEntry*)currentSessionEntry;
379 - (web::NavigationItem*)currentNavItem;
380 // Returns the referrer for currentURL as a string. May return nil.
381 - (web::Referrer)currentSessionEntryReferrer;
382 // The data and HTTP headers associated to the current entry. These are nil
383 // unless the request was a POST.
384 - (NSData*)currentPOSTData;
385 - (NSDictionary*)currentHttpHeaders;
387 // Finds all the scrollviews in the view hierarchy and makes sure they do not
388 // interfere with scroll to top when tapping the statusbar.
389 - (void)optOutScrollsToTopForSubviews;
390 // Tears down the old native controller, and then replaces it with the new one.
391 - (void)setNativeController:(id<CRWNativeContent>)nativeController;
392 // Returns whether |url| should be opened.
393 - (BOOL)shouldOpenURL:(const GURL&)url
394       mainDocumentURL:(const GURL&)mainDocumentURL
395           linkClicked:(BOOL)linkClicked;
396 // Called when |url| needs to be opened in a matching native app.
397 // Returns YES if the url was succesfully opened in the native app.
398 - (BOOL)urlTriggersNativeAppLaunch:(const GURL&)url
399                          sourceURL:(const GURL&)sourceURL;
400 // Returns whether external URL request should be opened.
401 - (BOOL)shouldOpenExternalURLRequest:(NSURLRequest*)request
402                          targetFrame:(const web::FrameInfo*)targetFrame;
403 // Called when a page updates its history stack using pushState or replaceState.
404 - (void)didUpdateHistoryStateWithPageURL:(const GURL&)url;
406 // Handlers for JavaScript messages. |message| contains a JavaScript command and
407 // data relevant to the message, and |context| contains contextual information
408 // about web view state needed for some handlers.
410 // Handles 'addPluginPlaceholders' message.
411 - (BOOL)handleAddPluginPlaceholdersMessage:(base::DictionaryValue*)message
412                                    context:(NSDictionary*)context;
413 // Handles 'chrome.send' message.
414 - (BOOL)handleChromeSendMessage:(base::DictionaryValue*)message
415                         context:(NSDictionary*)context;
416 // Handles 'console' message.
417 - (BOOL)handleConsoleMessage:(base::DictionaryValue*)message
418                      context:(NSDictionary*)context;
419 // Handles 'dialog.suppressed' message.
420 - (BOOL)handleDialogSuppressedMessage:(base::DictionaryValue*)message
421                               context:(NSDictionary*)context;
422 // Handles 'dialog.willShow' message.
423 - (BOOL)handleDialogWillShowMessage:(base::DictionaryValue*)message
424                             context:(NSDictionary*)context;
425 // Handles 'document.favicons' message.
426 - (BOOL)handleDocumentFaviconsMessage:(base::DictionaryValue*)message
427                               context:(NSDictionary*)context;
428 // Handles 'document.submit' message.
429 - (BOOL)handleDocumentSubmitMessage:(base::DictionaryValue*)message
430                             context:(NSDictionary*)context;
431 // Handles 'externalRequest' message.
432 - (BOOL)handleExternalRequestMessage:(base::DictionaryValue*)message
433                              context:(NSDictionary*)context;
434 // Handles 'form.activity' message.
435 - (BOOL)handleFormActivityMessage:(base::DictionaryValue*)message
436                           context:(NSDictionary*)context;
437 // Handles 'form.requestAutocomplete' message.
438 - (BOOL)handleFormRequestAutocompleteMessage:(base::DictionaryValue*)message
439                                      context:(NSDictionary*)context;
440 // Handles 'navigator.credentials.request' message.
441 - (BOOL)handleCredentialsRequestedMessage:(base::DictionaryValue*)message
442                                   context:(NSDictionary*)context;
443 // Handles 'navigator.credentials.notifySignedIn' message.
444 - (BOOL)handleSignedInMessage:(base::DictionaryValue*)message
445                       context:(NSDictionary*)context;
446 // Handles 'navigator.credentials.notifySignedOut' message.
447 - (BOOL)handleSignedOutMessage:(base::DictionaryValue*)message
448                        context:(NSDictionary*)context;
449 // Handles 'navigator.credentials.notifyFailedSignIn' message.
450 - (BOOL)handleSignInFailedMessage:(base::DictionaryValue*)message
451                           context:(NSDictionary*)context;
452 // Handles 'resetExternalRequest' message.
453 - (BOOL)handleResetExternalRequestMessage:(base::DictionaryValue*)message
454                                   context:(NSDictionary*)context;
455 // Handles 'window.close.self' message.
456 - (BOOL)handleWindowCloseSelfMessage:(base::DictionaryValue*)message
457                              context:(NSDictionary*)context;
458 // Handles 'window.error' message.
459 - (BOOL)handleWindowErrorMessage:(base::DictionaryValue*)message
460                          context:(NSDictionary*)context;
461 // Handles 'window.hashchange' message.
462 - (BOOL)handleWindowHashChangeMessage:(base::DictionaryValue*)message
463                               context:(NSDictionary*)context;
464 // Handles 'window.history.back' message.
465 - (BOOL)handleWindowHistoryBackMessage:(base::DictionaryValue*)message
466                                context:(NSDictionary*)context;
467 // Handles 'window.history.forward' message.
468 - (BOOL)handleWindowHistoryForwardMessage:(base::DictionaryValue*)message
469                                   context:(NSDictionary*)context;
470 // Handles 'window.history.go' message.
471 - (BOOL)handleWindowHistoryGoMessage:(base::DictionaryValue*)message
472                              context:(NSDictionary*)context;
473 @end
475 namespace {
477 NSString* const kReferrerHeaderName = @"Referer";  // [sic]
479 // Full screen experimental setting.
481 // The long press detection duration must be shorter than the UIWebView's
482 // long click gesture recognizer's minimum duration. That is 0.55s.
483 // If our detection duration is shorter, our gesture recognizer will fire
484 // first, and if it fails the long click gesture (processed simultaneously)
485 // still is able to complete.
486 const NSTimeInterval kLongPressDurationSeconds = 0.55 - 0.1;
487 const CGFloat kLongPressMoveDeltaPixels = 10.0;
489 // The duration of the period following a screen touch during which the user is
490 // still considered to be interacting with the page.
491 const NSTimeInterval kMaximumDelayForUserInteractionInSeconds = 2;
493 // Define missing symbols from WebKit.
494 // See WebKitErrors.h on Mac SDK.
495 NSString* const WebKitErrorDomain = @"WebKitErrorDomain";
497 enum {
498   WebKitErrorCannotShowMIMEType = 100,
499   WebKitErrorCannotShowURL = 101,
500   WebKitErrorFrameLoadInterruptedByPolicyChange = 102,
501   // iOS-specific WebKit error that isn't documented but seen on 4.0
502   // devices.
503   WebKitErrorPlugInLoadFailed = 204,
506 // Tag for the interstitial view so we can find it and dismiss it later.
507 enum {
508   kInterstitialViewTag = 1000,
511 // URLs that are fed into UIWebView as history push/replace get escaped,
512 // potentially changing their format. Code that attempts to determine whether a
513 // URL hasn't changed can be confused by those differences though, so method
514 // will round-trip a URL through the escaping process so that it can be adjusted
515 // pre-storing, to allow later comparisons to work as expected.
516 GURL URLEscapedForHistory(const GURL& url) {
517   // TODO(stuartmorgan): This is a very large hammer; see if limited unicode
518   // escaping would be sufficient.
519   return net::GURLWithNSURL(net::NSURLWithGURL(url));
522 // Parses a viewport tag content and returns the value of an attribute with
523 // the given |name|, or nil if the attribute is not present in the tag.
524 NSString* GetAttributeValueFromViewPortContent(NSString* attributeName,
525                                                NSString* viewPortContent) {
526   NSArray* contentItems = [viewPortContent componentsSeparatedByString:@","];
527   for (NSString* item in contentItems) {
528     NSArray* components = [item componentsSeparatedByString:@"="];
529     if ([components count] == 2) {
530       NSCharacterSet* spaceAndNewline =
531           [NSCharacterSet whitespaceAndNewlineCharacterSet];
532       NSString* currentAttributeName =
533           [components[0] stringByTrimmingCharactersInSet:spaceAndNewline];
534       if ([currentAttributeName isEqualToString:attributeName]) {
535         return [components[1] stringByTrimmingCharactersInSet:spaceAndNewline];
536       }
537     }
538   }
539   return nil;
542 // Parses a viewport tag content and returns the value of the user-scalable
543 // attribute or nil.
544 BOOL GetUserScalablePropertyFromViewPortContent(NSString* viewPortContent) {
545   NSString* value =
546       GetAttributeValueFromViewPortContent(@"user-scalable", viewPortContent);
547   if (!value) {
548     return YES;
549   }
550   return !([value isEqualToString:@"0"] ||
551            [value caseInsensitiveCompare:@"no"] == NSOrderedSame);
554 // Leave snapshot overlay up unless page loads.
555 const NSTimeInterval kSnapshotOverlayDelay = 1.5;
556 // Transition to fade snapshot overlay.
557 const NSTimeInterval kSnapshotOverlayTransition = 0.5;
559 }  // namespace
561 @implementation CRWWebController
563 @synthesize webUsageEnabled = _webUsageEnabled;
564 @synthesize usePlaceholderOverlay = _usePlaceholderOverlay;
565 @synthesize loadPhase = _loadPhase;
567 // Implemented by subclasses.
568 @dynamic keyboardDisplayRequiresUserAction;
570 + (instancetype)allocWithZone:(struct _NSZone*)zone {
571   if (self == [CRWWebController class]) {
572     // This is an abstract class which should not be instantiated directly.
573     // Callers should create concrete subclasses instead.
574     NOTREACHED();
575     return nil;
576   }
577   return [super allocWithZone:zone];
580 - (instancetype)initWithWebState:(scoped_ptr<WebStateImpl>)webState {
581   self = [super init];
582   if (self) {
583     _webStateImpl = webState.Pass();
584     DCHECK(_webStateImpl);
585     _webStateImpl->SetWebController(self);
586     _webStateImpl->InitializeRequestTracker(self);
587     // Load phase when no WebView present is 'loaded' because this represents
588     // the idle state.
589     _loadPhase = web::PAGE_LOADED;
590     // Content area is lazily instantiated.
591     _defaultURL = GURL(url::kAboutBlankURL);
592     _jsInjectionReceiver.reset(
593         [[CRWJSInjectionReceiver alloc] initWithEvaluator:self]);
594     _earlyScriptManager.reset([(CRWJSEarlyScriptManager*)[_jsInjectionReceiver
595         instanceOfClass:[CRWJSEarlyScriptManager class]] retain]);
596     _windowIDJSManager.reset([(CRWJSWindowIdManager*)[_jsInjectionReceiver
597         instanceOfClass:[CRWJSWindowIdManager class]] retain]);
598     _lastSeenWindowID.reset();
599     _webViewProxy.reset(
600         [[CRWWebViewProxyImpl alloc] initWithWebController:self]);
601     [[_webViewProxy scrollViewProxy] addObserver:self];
602     _gestureRecognizers.reset([[NSMutableArray alloc] init]);
603     _webViewToolbars.reset([[NSMutableArray alloc] init]);
604     _pendingLoadCompleteActions.reset([[NSMutableArray alloc] init]);
605     [[NSNotificationCenter defaultCenter]
606         addObserver:self
607            selector:@selector(orientationDidChange)
608                name:UIApplicationDidChangeStatusBarOrientationNotification
609              object:nil];
610   }
611   return self;
614 - (id<CRWNativeContentProvider>)nativeProvider {
615   return _nativeProvider.get();
618 - (void)setNativeProvider:(id<CRWNativeContentProvider>)nativeProvider {
619   _nativeProvider.reset(nativeProvider);
622 - (id<CRWSwipeRecognizerProvider>)swipeRecognizerProvider {
623   return _swipeRecognizerProvider.get();
626 - (void)setSwipeRecognizerProvider:
627     (id<CRWSwipeRecognizerProvider>)swipeRecognizerProvider {
628   _swipeRecognizerProvider.reset(swipeRecognizerProvider);
631 - (WebState*)webState {
632   return _webStateImpl.get();
635 - (WebStateImpl*)webStateImpl {
636   return _webStateImpl.get();
639 // WebStateImpl will delete the interstitial page object, which will in turn
640 // remove its view from |_contentView|.
641 - (void)clearInterstitials {
642   [_webViewProxy setWebView:self.webView scrollView:self.webScrollView];
643   if (_webStateImpl)
644     _webStateImpl->ClearWebInterstitialForNavigation();
647 // Attaches |interstitialView| to |_contentView|.  Note that this class never
648 // explicitly removes the interstitial from |_contentView|;
649 // web::WebStateImpl::DismissWebInterstitial() takes care of that.
650 - (void)displayInterstitialView:(UIView*)interstitialView
651                  withScrollView:(UIScrollView*)scrollView {
652   DCHECK(interstitialView);
653   DCHECK(scrollView);
654   [_webViewProxy setWebView:interstitialView scrollView:scrollView];
655   interstitialView.tag = kInterstitialViewTag;
656   [_containerView addSubview:interstitialView];
659 - (id<CRWWebDelegate>)delegate {
660   return _delegate.get();
663 - (void)setDelegate:(id<CRWWebDelegate>)delegate {
664   _delegate.reset(delegate);
665   if ([_nativeController respondsToSelector:@selector(setDelegate:)]) {
666     if ([_delegate respondsToSelector:@selector(webController:titleDidChange:)])
667       [_nativeController setDelegate:self];
668     else
669       [_nativeController setDelegate:nil];
670   }
673 - (id<CRWWebUserInterfaceDelegate>)UIDelegate {
674   return _UIDelegate.get();
677 - (void)setUIDelegate:(id<CRWWebUserInterfaceDelegate>)UIDelegate {
678   _UIDelegate.reset(UIDelegate);
681 - (NSString*)scriptByAddingWindowIDCheckForScript:(NSString*)script {
682   NSString* kTemplate = @"if (__gCrWeb['windowId'] === '%@') { %@; }";
683   return [NSString stringWithFormat:kTemplate, [self windowId], script];
686 - (void)removeWebViewAllowingCachedReconstruction:(BOOL)allowCache {
687   if (!self.webView)
688     return;
690   if (allowCache)
691     _expectedReconstructionURL = [self currentNavigationURL];
692   else
693     _expectedReconstructionURL = GURL();
695   [self abortLoad];
696   [self.webView removeFromSuperview];
697   [_webViewProxy setWebView:nil scrollView:nil];
698   [self resetWebView];
699   // Remove the web toolbars.
700   [_containerView removeAllToolbars];
703 - (void)dealloc {
704   DCHECK([NSThread isMainThread]);
705   DCHECK(_isBeingDestroyed);  // 'close' must have been called already.
706   DCHECK(!self.webView);
707   _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
708   [[_webViewProxy scrollViewProxy] removeObserver:self];
709   [[NSNotificationCenter defaultCenter] removeObserver:self];
710   [super dealloc];
713 - (BOOL)runUnloadListenerBeforeClosing {
714   // There's not much that can be done since there's limited access to WebKit.
715   // Always return that it's ok to close immediately.
716   return YES;
719 - (void)dismissKeyboard {
720   [self.webView endEditing:YES];
721   if ([_nativeController respondsToSelector:@selector(dismissKeyboard)])
722     [_nativeController dismissKeyboard];
725 - (id<CRWNativeContent>)nativeController {
726   return _nativeController.get();
729 - (void)setNativeController:(id<CRWNativeContent>)nativeController {
730   // Check for pointer equality.
731   if (_nativeController.get() == nativeController)
732     return;
734   // Unset the delegate on the previous instance.
735   if ([_nativeController respondsToSelector:@selector(setDelegate:)])
736     [_nativeController setDelegate:nil];
738   _nativeController.reset([nativeController retain]);
739   [self setNativeControllerWebUsageEnabled:_webUsageEnabled];
742 // NativeControllerDelegate method, called to inform that title has changed.
743 - (void)nativeContent:(id)content titleDidChange:(NSString*)title {
744   // Responsiveness to delegate method was checked in setDelegate:.
745   [_delegate webController:self titleDidChange:title];
748 - (void)setNativeControllerWebUsageEnabled:(BOOL)webUsageEnabled {
749   if ([_nativeController respondsToSelector:@selector(setWebUsageEnabled:)]) {
750     [_nativeController setWebUsageEnabled:webUsageEnabled];
751   }
754 - (void)setWebUsageEnabled:(BOOL)enabled {
755   if (_webUsageEnabled == enabled)
756     return;
757   _webUsageEnabled = enabled;
759   // WKWebView autoreleases its WKProcessPool on removal from superview.
760   // Deferring WKProcessPool deallocation may lead to issues with cookie
761   // clearing and and Browsing Data Partitioning implementation.
762   @autoreleasepool {
763     [self setNativeControllerWebUsageEnabled:_webUsageEnabled];
764     if (enabled) {
765       // Don't create the web view; let it be lazy created as needed.
766     } else {
767       [self clearInterstitials];
768       [self removeWebViewAllowingCachedReconstruction:YES];
769       _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
770       _touchTrackingRecognizer.reset();
771       _containerView.reset();
772     }
773   }
776 - (void)requirePageReconstruction {
777   [self removeWebViewAllowingCachedReconstruction:NO];
780 - (void)handleLowMemory {
781   [self removeWebViewAllowingCachedReconstruction:YES];
782   [self setNativeController:nil];
783   _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
784   _touchTrackingRecognizer.reset();
785   _containerView.reset();
786   _usePlaceholderOverlay = YES;
789 - (void)reinitializeWebViewAndReload:(BOOL)reload {
790   if (self.webView) {
791     [self removeWebViewAllowingCachedReconstruction:NO];
792     if (reload) {
793       [self loadCurrentURLInWebView];
794     } else {
795       // Clear the space for the web view to lazy load when needed.
796       _usePlaceholderOverlay = YES;
797       _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
798       _touchTrackingRecognizer.reset();
799       _containerView.reset();
800     }
801   }
804 - (void)childWindowClosed:(NSString*)windowName {
805   // Subclasses can override this method to be informed about a closed window.
808 - (BOOL)isViewAlive {
809   return self.webView || [_nativeController isViewAlive];
812 - (BOOL)contentIsHTML {
813   return [self webViewDocumentType] == web::WEB_VIEW_DOCUMENT_TYPE_HTML;
816 // Stop doing stuff, especially network stuff. Close the request tracker.
817 - (void)terminateNetworkActivity {
818   DCHECK(!_isHalted);
819   _isHalted = YES;
821   // Cancel all outstanding perform requests, and clear anything already queued
822   // (since this may be called from within the handling loop) to prevent any
823   // asynchronous JavaScript invocation handling from continuing.
824   [NSObject cancelPreviousPerformRequestsWithTarget:self];
825   _webStateImpl->CloseRequestTracker();
828 - (void)dismissModals {
829   if ([_nativeController respondsToSelector:@selector(dismissModals)])
830     [_nativeController dismissModals];
833 // Caller must reset the delegate before calling.
834 - (void)close {
835   self.nativeProvider = nil;
836   self.swipeRecognizerProvider = nil;
837   if ([_nativeController respondsToSelector:@selector(close)])
838     [_nativeController close];
840   base::scoped_nsobject<NSSet> observers([_observers copy]);
841   for (id it in observers.get()) {
842     if ([it respondsToSelector:@selector(webControllerWillClose:)])
843       [it webControllerWillClose:self];
844   }
846   if (!_isHalted) {
847     [self terminateNetworkActivity];
848   }
850   DCHECK(!_isBeingDestroyed);
851   DCHECK(!_delegate);  // Delegate should reset its association before closing.
852   // Mark the destruction sequence has started, in case someone else holds a
853   // strong reference and tries to continue using the tab.
854   _isBeingDestroyed = YES;
856   // Remove the web view now. Otherwise, delegate callbacks occur.
857   [self removeWebViewAllowingCachedReconstruction:NO];
859   // Tear down web ui (in case this is part of this tab) and web state now,
860   // since the timing of dealloc can't be guaranteed.
861   _webStateImpl.reset();
864 - (void)checkLinkPresenceUnderGesture:(UIGestureRecognizer*)gestureRecognizer
865                     completionHandler:(void (^)(BOOL))completionHandler {
866   CGPoint webViewPoint = [gestureRecognizer locationInView:self.webView];
867   base::WeakNSObject<CRWWebController> weakSelf(self);
868   [self fetchDOMElementAtPoint:webViewPoint
869              completionHandler:^(scoped_ptr<base::DictionaryValue> element) {
870                std::string link;
871                BOOL hasLink =
872                    element && element->GetString("href", &link) && link.size();
873                completionHandler(hasLink);
874              }];
877 - (void)setDOMElementForLastTouch:(scoped_ptr<base::DictionaryValue>)element {
878   _DOMElementForLastTouch = element.Pass();
881 - (void)showContextMenu:(UIGestureRecognizer*)gestureRecognizer {
882   // Calling this method if [self supportsCustomContextMenu] returned NO
883   // is a programmer error.
884   DCHECK([self supportsCustomContextMenu]);
886   // We don't want ongoing notification that the long press is held.
887   if ([gestureRecognizer state] != UIGestureRecognizerStateBegan)
888     return;
890   if (!_DOMElementForLastTouch || _DOMElementForLastTouch->empty())
891     return;
893   NSDictionary* info =
894       [self contextMenuInfoForElement:_DOMElementForLastTouch.get()];
895   CGPoint point = [gestureRecognizer locationInView:self.webView];
897   // Cancel all touches on the web view when showing custom context menu. This
898   // will suppress the system context menu and prevent further user interactions
899   // with web view (like scrolling the content and following links). This
900   // approach is similar to UIWebView and WKWebView workflow as both call
901   // -[UIApplication _cancelAllTouches] to cancel all touch events, once the
902   // long press is detected.
903   CancelAllTouches(self.webScrollView);
904   [self.UIDelegate webController:self
905                   runContextMenu:info
906                          atPoint:point
907                           inView:self.webView];
910 - (BOOL)supportsCustomContextMenu {
911   SEL runMenuSelector = @selector(webController:runContextMenu:atPoint:inView:);
912   return [self.UIDelegate respondsToSelector:runMenuSelector];
915 // TODO(shreyasv): This code is shared with SnapshotManager. Remove this and add
916 // it as part of WebDelegate delegate API such that a default image is returned
917 // immediately.
918 + (UIImage*)defaultSnapshotImage {
919   static UIImage* defaultImage = nil;
921   if (!defaultImage) {
922     CGRect frame = CGRectMake(0, 0, 2, 2);
923     UIGraphicsBeginImageContext(frame.size);
924     [[UIColor whiteColor] setFill];
925     CGContextFillRect(UIGraphicsGetCurrentContext(), frame);
927     UIImage* result = UIGraphicsGetImageFromCurrentImageContext();
928     UIGraphicsEndImageContext();
930     defaultImage =
931         [[result stretchableImageWithLeftCapWidth:1 topCapHeight:1] retain];
932   }
933   return defaultImage;
936 - (BOOL)canGoBack {
937   return _webStateImpl->GetNavigationManagerImpl().CanGoBack();
940 - (BOOL)canGoForward {
941   return _webStateImpl->GetNavigationManagerImpl().CanGoForward();
944 - (CGPoint)scrollPosition {
945   CGPoint position = CGPointMake(0.0, 0.0);
946   if (!self.webScrollView)
947     return position;
948   return self.webScrollView.contentOffset;
951 - (BOOL)atTop {
952   if (!self.webView)
953     return YES;
954   UIScrollView* scrollView = self.webScrollView;
955   return scrollView.contentOffset.y == -scrollView.contentInset.top;
958 - (void)presentSpoofingError {
959   UMA_HISTOGRAM_ENUMERATION("Web.URLVerificationFailure",
960                             [self webViewDocumentType],
961                             web::WEB_VIEW_DOCUMENT_TYPE_COUNT);
962   if (self.webView) {
963     [self removeWebViewAllowingCachedReconstruction:NO];
964     [_delegate presentSpoofingError];
965   }
968 - (GURL)currentURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel {
969   DCHECK(trustLevel) << "Verification of the trustLevel state is mandatory";
970   if (self.webView) {
971     GURL url([self webURLWithTrustLevel:trustLevel]);
972     // Web views treat all about: URLs as the same origin, which makes it
973     // possible for pages to document.write into about:<foo> pages, where <foo>
974     // can be something misleading. Report any about: URL as about:blank to
975     // prevent that. See crbug.com/326118
976     if (url.scheme() == url::kAboutScheme)
977       return GURL(url::kAboutBlankURL);
978     return url;
979   }
980   // Any non-web URL source is trusted.
981   *trustLevel = web::URLVerificationTrustLevel::kAbsolute;
982   if (_nativeController)
983     return [_nativeController url];
984   return [self currentNavigationURL];
987 - (GURL)currentURL {
988   web::URLVerificationTrustLevel trustLevel =
989       web::URLVerificationTrustLevel::kNone;
990   const GURL url([self currentURLWithTrustLevel:&trustLevel]);
992   // Check whether the spoofing warning needs to be displayed.
993   if (trustLevel == web::URLVerificationTrustLevel::kNone &&
994       ![self ignoreURLVerificationFailures]) {
995     dispatch_async(dispatch_get_main_queue(), ^{
996       if (!_isHalted) {
997         DCHECK_EQ(url, [self currentNavigationURL]);
998         [self presentSpoofingError];
999       }
1000     });
1001   }
1003   return url;
1006 - (web::Referrer)currentReferrer {
1007   // Referrer string doesn't include the fragment, so in cases where the
1008   // previous URL is equal to the current referrer plus the fragment the
1009   // previous URL is returned as current referrer.
1010   NSString* referrerString = self.currentReferrerString;
1012   // In case of an error evaluating the JavaScript simply return empty string.
1013   if ([referrerString length] == 0)
1014     return web::Referrer();
1016   NSString* previousURLString =
1017       base::SysUTF8ToNSString([self currentNavigationURL].spec());
1018   // Check if the referrer is equal to the previous URL minus the hash symbol.
1019   // L'#' is used to convert the char '#' to a unichar.
1020   if ([previousURLString length] > [referrerString length] &&
1021       [previousURLString hasPrefix:referrerString] &&
1022       [previousURLString characterAtIndex:[referrerString length]] == L'#') {
1023     referrerString = previousURLString;
1024   }
1025   // Since referrer is being extracted from the destination page, the correct
1026   // policy from the origin has *already* been applied. Since the extracted URL
1027   // is the post-policy value, and the source policy is no longer available,
1028   // the policy is set to Always so that whatever WebKit decided to send will be
1029   // re-sent when replaying the entry.
1030   // TODO(stuartmorgan): When possible, get the real referrer and policy in
1031   // advance and use that instead. https://crbug.com/227769.
1032   return web::Referrer(GURL(base::SysNSStringToUTF8(referrerString)),
1033                        web::ReferrerPolicyAlways);
1036 - (void)pushStateWithPageURL:(const GURL&)pageUrl
1037                  stateObject:(NSString*)stateObject {
1038   [[self sessionController] pushNewEntryWithURL:pageUrl
1039                                     stateObject:stateObject];
1040   [self didUpdateHistoryStateWithPageURL:pageUrl];
1043 - (void)replaceStateWithPageURL:(const GURL&)pageUrl
1044                     stateObject:(NSString*)stateObject {
1045   [[self sessionController] updateCurrentEntryWithURL:pageUrl
1046                                           stateObject:stateObject];
1047   [self didUpdateHistoryStateWithPageURL:pageUrl];
1050 - (void)injectEarlyInjectionScripts {
1051   DCHECK(self.webView);
1052   if (![_earlyScriptManager hasBeenInjected]) {
1053     [_earlyScriptManager inject];
1054     // If this assertion fires there has been an error parsing the core.js
1055     // object.
1056     DCHECK([_earlyScriptManager hasBeenInjected]);
1057   }
1058   [self injectWindowID];
1061 - (void)injectWindowID {
1062   if (![_windowIDJSManager hasBeenInjected]) {
1063     // If the window ID wasn't present, this is a new page.
1064     [self setPageChangeProbability:web::PAGE_CHANGE_PROBABILITY_LOW];
1065     // Default values for suppressDialogs and notifyAboutDialogs are NO,
1066     // so updating them only when necessary is a good optimization.
1067     if (_setSuppressDialogsLater || _setNotifyAboutDialogsLater) {
1068       [self setSuppressDialogs:_setSuppressDialogsLater
1069                         notify:_setNotifyAboutDialogsLater];
1070       _setSuppressDialogsLater = NO;
1071       _setNotifyAboutDialogsLater = NO;
1072     }
1074     [_windowIDJSManager inject];
1075     DCHECK([_windowIDJSManager hasBeenInjected]);
1076   }
1079 // Set the specified recognizer to take priority over any recognizers in the
1080 // view that have a description containing the specified text fragment.
1081 + (void)requireGestureRecognizerToFail:(UIGestureRecognizer*)recognizer
1082                                 inView:(UIView*)view
1083                  containingDescription:(NSString*)fragment {
1084   for (UIGestureRecognizer* iRecognizer in [view gestureRecognizers]) {
1085     if (iRecognizer != recognizer) {
1086       NSString* description = [iRecognizer description];
1087       if ([description rangeOfString:fragment].location != NSNotFound) {
1088         [iRecognizer requireGestureRecognizerToFail:recognizer];
1089         // requireGestureRecognizerToFail: doesn't retain the recognizer, so it
1090         // is possible for |iRecognizer| to outlive |recognizer| and end up with
1091         // a dangling pointer. Add a retaining associative reference to ensure
1092         // that the lifetimes work out.
1093         // Note that normally using the value as the key wouldn't make any
1094         // sense, but here it's fine since nothing needs to look up the value.
1095         objc_setAssociatedObject(view, recognizer, recognizer,
1096                                  OBJC_ASSOCIATION_RETAIN_NONATOMIC);
1097       }
1098     }
1099   }
1102 - (void)webViewDidChange {
1103   CHECK(_webUsageEnabled) << "Tried to create a web view while suspended!";
1105   UIView* webView = self.webView;
1106   DCHECK(webView);
1108   [webView setTag:kWebViewTag];
1109   [webView setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
1110                                UIViewAutoresizingFlexibleHeight];
1111   [webView setBackgroundColor:[UIColor colorWithWhite:0.2 alpha:1.0]];
1113   // Create a dependency between the |webView| pan gesture and BVC side swipe
1114   // gestures. Note: This needs to be added before the longPress recognizers
1115   // below, or the longPress appears to deadlock the remaining recognizers,
1116   // thereby breaking scroll.
1117   NSSet* recognizers = [_swipeRecognizerProvider swipeRecognizers];
1118   for (UISwipeGestureRecognizer* swipeRecognizer in recognizers) {
1119     [self.webScrollView.panGestureRecognizer
1120         requireGestureRecognizerToFail:swipeRecognizer];
1121   }
1123   // On iOS 4.x, there are two gesture recognizers on the UIWebView subclasses,
1124   // that have a minimum tap threshold of 0.12s and 0.75s.
1125   //
1126   // My theory is that the shorter threshold recognizer performs the link
1127   // highlight (grey highlight around links when it is tapped and held) while
1128   // the longer threshold one pops up the context menu.
1129   //
1130   // To override the context menu, this recognizer needs to react faster than
1131   // the 0.75s one. The below gesture recognizer is initialized with a
1132   // detection duration a little lower than that (see
1133   // kLongPressDurationSeconds). It also points the delegate to this class that
1134   // allows simultaneously operate along with the other recognizers.
1135   _contextMenuRecognizer.reset([[UILongPressGestureRecognizer alloc]
1136       initWithTarget:self
1137               action:@selector(showContextMenu:)]);
1138   [_contextMenuRecognizer setMinimumPressDuration:kLongPressDurationSeconds];
1139   [_contextMenuRecognizer setAllowableMovement:kLongPressMoveDeltaPixels];
1140   [_contextMenuRecognizer setDelegate:self];
1141   [webView addGestureRecognizer:_contextMenuRecognizer];
1142   // Certain system gesture handlers are known to conflict with our context
1143   // menu handler, causing extra events to fire when the context menu is active.
1145   // A number of solutions have been investigated. The lowest-risk solution
1146   // appears to be to recurse through the web controller's recognizers, looking
1147   // for fingerprints of the recognizers known to cause problems, which are then
1148   // de-prioritized (below our own long click handler).
1149   // Hunting for description fragments of system recognizers is undeniably
1150   // brittle for future versions of iOS. If it does break the context menu
1151   // events may leak (regressing b/5310177), but the app will otherwise work.
1152   [CRWWebController
1153       requireGestureRecognizerToFail:_contextMenuRecognizer
1154                               inView:webView
1155                containingDescription:@"action=_highlightLongPressRecognized:"];
1157   // Add all additional gesture recognizers to the web view.
1158   for (UIGestureRecognizer* recognizer in _gestureRecognizers.get()) {
1159     [webView addGestureRecognizer:recognizer];
1160   }
1162   webView.frame = [_containerView bounds];
1164   _URLOnStartLoading = _defaultURL;
1166   // Do final view setup.
1167   CGPoint initialOffset = CGPointMake(0, 0 - [self headerHeight]);
1168   [self.webScrollView setContentOffset:initialOffset];
1169   [_containerView addToolbars:_webViewToolbars];
1171   [_webViewProxy setWebView:self.webView scrollView:self.webScrollView];
1173   [_containerView addSubview:webView];
1176 - (CRWWebController*)createChildWebControllerWithReferrerURL:
1177     (const GURL&)referrerURL {
1178   web::Referrer referrer(referrerURL, web::ReferrerPolicyDefault);
1179   CRWWebController* result =
1180       [self.delegate webPageOrderedOpenBlankWithReferrer:referrer
1181                                             inBackground:NO];
1182   DCHECK(!result || result.sessionController.openedByDOM);
1183   return result;
1186 - (BOOL)canUseViewForGeneratingOverlayPlaceholderView {
1187   return _containerView != nil;
1190 - (UIView*)view {
1191   // Kick off the process of lazily creating the view and starting the load if
1192   // necessary; this creates _contentView if it doesn't exist.
1193   [self triggerPendingLoad];
1194   DCHECK(_containerView);
1195   return _containerView.get();
1198 - (id<CRWWebViewProxy>)webViewProxy {
1199   return _webViewProxy.get();
1202 - (UIView*)viewForPrinting {
1203   // TODO(ios): crbug.com/227944. Printing is not supported for native
1204   // controllers.
1205   return self.webView;
1208 - (void)loadRequest:(NSMutableURLRequest*)request {
1209   // Subclasses must implement this method.
1210   NOTREACHED();
1213 - (void)registerLoadRequest:(const GURL&)requestURL
1214                    referrer:(const web::Referrer&)referrer
1215                  transition:(ui::PageTransition)transition {
1216   // Transfer time is registered so that further transitions within the time
1217   // envelope are not also registered as links.
1218   _lastTransferTimeInSeconds = CFAbsoluteTimeGetCurrent();
1219   // Before changing phases, the delegate should be informed that any existing
1220   // request is being cancelled before completion.
1221   [self loadCancelled];
1222   DCHECK(_loadPhase == web::PAGE_LOADED);
1224   _loadPhase = web::LOAD_REQUESTED;
1225   [self resetLoadState];
1226   _lastRegisteredRequestURL = requestURL;
1228   if (!(transition & ui::PAGE_TRANSITION_IS_REDIRECT_MASK)) {
1229     // Record state of outgoing page.
1230     [self recordStateInHistory];
1231   }
1233   // If the web view had been discarded, and this request is to load that
1234   // URL again, then it's a rebuild and should use the cache.
1235   BOOL preferCache = _expectedReconstructionURL.is_valid() &&
1236                      _expectedReconstructionURL == requestURL;
1238   [_delegate webWillAddPendingURL:requestURL transition:transition];
1239   // Add or update pending url.
1240   if (_webStateImpl->GetNavigationManagerImpl().GetPendingItem()) {
1241     // Update the existing pending entry.
1242     // Typically on PAGE_TRANSITION_CLIENT_REDIRECT.
1243     [[self sessionController] updatePendingEntry:requestURL];
1244   } else {
1245     // A new session history entry needs to be created.
1246     [[self sessionController] addPendingEntry:requestURL
1247                                      referrer:referrer
1248                                    transition:transition
1249                             rendererInitiated:YES];
1250   }
1251   // Update the cache mode for all the network requests issued by this web view.
1252   // The mode is reset to CACHE_NORMAL after each page load.
1253   if (_webStateImpl->GetCacheMode() != net::RequestTracker::CACHE_NORMAL) {
1254     _webStateImpl->GetRequestTracker()->SetCacheModeFromUIThread(
1255         _webStateImpl->GetCacheMode());
1256   } else if (preferCache) {
1257     _webStateImpl->GetRequestTracker()->SetCacheModeFromUIThread(
1258         net::RequestTracker::CACHE_HISTORY);
1259   }
1260   _webStateImpl->SetIsLoading(true);
1261   [_delegate webDidAddPendingURL];
1262   _webStateImpl->OnProvisionalNavigationStarted(requestURL);
1265 - (NSString*)javascriptToReplaceWebViewURL:(const GURL&)url
1266                            stateObjectJSON:(NSString*)stateObject {
1267   std::string outURL;
1268   base::EscapeJSONString(url.spec(), true, &outURL);
1269   return
1270       [NSString stringWithFormat:@"__gCrWeb.replaceWebViewURL(%@, %@);",
1271                                  base::SysUTF8ToNSString(outURL), stateObject];
1274 - (void)finishPushStateNavigationToURL:(const GURL&)url
1275                        withStateObject:(NSString*)stateObject {
1276   // TODO(stuartmorgan): Make CRWSessionController manage this internally (or
1277   // remove it; it's not clear this matches other platforms' behavior).
1278   _webStateImpl->GetNavigationManagerImpl().OnNavigationItemCommitted();
1280   NSString* replaceWebViewUrlJS =
1281       [self javascriptToReplaceWebViewURL:url stateObjectJSON:stateObject];
1282   std::string outState;
1283   base::EscapeJSONString(base::SysNSStringToUTF8(stateObject), true, &outState);
1284   NSString* popstateJS =
1285       [NSString stringWithFormat:@"__gCrWeb.dispatchPopstateEvent(%@);",
1286                                  base::SysUTF8ToNSString(outState)];
1287   NSString* combinedJS =
1288       [NSString stringWithFormat:@"%@%@", replaceWebViewUrlJS, popstateJS];
1289   GURL urlCopy(url);
1290   base::WeakNSObject<CRWWebController> weakSelf(self);
1291   [self evaluateJavaScript:combinedJS
1292        stringResultHandler:^(NSString*, NSError*) {
1293          if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
1294            return;
1295          base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
1296          strongSelf.get()->_URLOnStartLoading = urlCopy;
1297          strongSelf.get()->_lastRegisteredRequestURL = urlCopy;
1298        }];
1301 // Load the current URL in a web view, first ensuring the web view is visible.
1302 // If a native controller is present, remove it and swap a new web view in
1303 // its place.
1304 - (void)loadCurrentURLInWebView {
1305   [self willLoadCurrentURLInWebView];
1307   // Re-register the user agent, because UIWebView sometimes loses it.
1308   // See crbug.com/228397.
1309   [self registerUserAgent];
1311   // Freeing the native controller removes its view from the view hierarchy.
1312   [self setNativeController:nil];
1314   // Clear the set of URLs opened in external applications.
1315   _openedApplicationURL.reset([[NSMutableSet alloc] init]);
1317   // Load the url. The UIWebView delegate callbacks take care of updating the
1318   // session history and UI.
1319   const GURL targetURL([self currentNavigationURL]);
1320   if (!targetURL.is_valid())
1321     return;
1323   // JavaScript should never be evaluated here. User-entered JS should be
1324   // evaluated via stringByEvaluatingUserJavaScriptFromString.
1325   DCHECK(!targetURL.SchemeIs(url::kJavaScriptScheme));
1326   [self ensureWebViewCreated];
1328   DCHECK(self.webView && !_nativeController);
1329   NSMutableURLRequest* request =
1330       [NSMutableURLRequest requestWithURL:net::NSURLWithGURL(targetURL)];
1331   const web::Referrer referrer([self currentSessionEntryReferrer]);
1332   if (referrer.url.is_valid()) {
1333     std::string referrerValue =
1334         web::ReferrerHeaderValueForNavigation(targetURL, referrer);
1335     if (!referrerValue.empty()) {
1336       [request setValue:base::SysUTF8ToNSString(referrerValue)
1337           forHTTPHeaderField:kReferrerHeaderName];
1338     }
1339   }
1341   // If there are headers in the current session entry add them to |request|.
1342   // Headers that would overwrite fields already present in |request| are
1343   // skipped.
1344   NSDictionary* headers = [self currentHttpHeaders];
1345   for (NSString* headerName in headers) {
1346     if (![request valueForHTTPHeaderField:headerName]) {
1347       [request setValue:[headers objectForKey:headerName]
1348           forHTTPHeaderField:headerName];
1349     }
1350   }
1352   NSData* postData = [self currentPOSTData];
1353   if (postData) {
1354     web::NavigationItemImpl* currentItem =
1355         [self currentSessionEntry].navigationItemImpl;
1356     if ([postData length] > 0 &&
1357         !(currentItem && currentItem->ShouldSkipResubmitDataConfirmation())) {
1358       id cancelBlock = ^{
1359         [self registerLoadRequest:[self currentNavigationURL]
1360                          referrer:[self currentSessionEntryReferrer]
1361                        transition:[self currentTransition]];
1362         [self loadRequest:request];
1363       };
1364       id continueBlock = ^{
1365         [request setHTTPMethod:@"POST"];
1366         [request setHTTPBody:[self currentPOSTData]];
1367         [request setAllHTTPHeaderFields:[self currentHttpHeaders]];
1368         [self registerLoadRequest:[self currentNavigationURL]
1369                          referrer:[self currentSessionEntryReferrer]
1370                        transition:[self currentTransition]];
1371         [self loadRequest:request];
1372       };
1373       [_delegate webController:self
1374           onFormResubmissionForRequest:request
1375                          continueBlock:continueBlock
1376                            cancelBlock:cancelBlock];
1377       return;
1378     } else {
1379       // The user does not need to confirm if POST data is empty.
1380       [request setHTTPMethod:@"POST"];
1381       [request setHTTPBody:postData];
1382       [request setAllHTTPHeaderFields:[self currentHttpHeaders]];
1383     }
1384   }
1386   // registerLoadRequest will be called when load is about to begin.
1387   // The phase at that point is guaranteed to be web::LOAD_REQUESTED.
1388   // However the delegate is not immediately called.
1389   [self registerLoadRequest:targetURL
1390                    referrer:referrer
1391                  transition:[self currentTransition]];
1392   [self loadRequest:request];
1395 - (void)loadNativeViewWithSuccess:(BOOL)loadSuccess {
1396   [_nativeController view].frame = [self visibleFrame];
1397   [_containerView addSubview:[_nativeController view]];
1398   [[_nativeController view] setNeedsUpdateConstraints];
1399   const GURL currentURL([self currentURL]);
1400   [self didStartLoadingURL:currentURL updateHistory:loadSuccess];
1401   _loadPhase = web::PAGE_LOADED;
1403   // Perform post-load-finished updates.
1404   [self didFinishWithURL:currentURL loadSuccess:loadSuccess];
1406   // Inform the embedder the title changed.
1407   if ([_delegate respondsToSelector:@selector(webController:titleDidChange:)]) {
1408     NSString* title = [_nativeController title];
1409     // If a title is present, notify the delegate.
1410     if (title)
1411       [_delegate webController:self titleDidChange:title];
1412     // If the controller handles title change notification, route those to the
1413     // delegate.
1414     if ([_nativeController respondsToSelector:@selector(setDelegate:)]) {
1415       [_nativeController setDelegate:self];
1416     }
1417   }
1420 - (void)loadErrorInNativeView:(NSError*)error {
1421   [self removeWebViewAllowingCachedReconstruction:NO];
1423   const GURL currentUrl = [self currentNavigationURL];
1424   BOOL isPost = [self currentPOSTData] != nil;
1426   [self setNativeController:[_nativeProvider controllerForURL:currentUrl
1427                                                     withError:error
1428                                                        isPost:isPost]];
1429   [self loadNativeViewWithSuccess:NO];
1432 // Load the current URL in a native controller, retrieved from the native
1433 // provider. Call |loadNativeViewWithSuccess:YES| to load the native controller.
1434 - (void)loadCurrentURLInNativeView {
1435   // Free the web view.
1436   [self removeWebViewAllowingCachedReconstruction:NO];
1438   const GURL targetURL = [self currentNavigationURL];
1439   const web::Referrer referrer;
1440   // Unlike the WebView case, always create a new controller and view.
1441   // TODO(pinkerton): What to do if this does return nil?
1442   [self setNativeController:[_nativeProvider controllerForURL:targetURL]];
1443   [self registerLoadRequest:targetURL
1444                    referrer:referrer
1445                  transition:[self currentTransition]];
1446   [self loadNativeViewWithSuccess:YES];
1449 - (void)loadWithParams:(const web::WebLoadParams&)originalParams {
1450   // Make a copy of |params|, as some of the delegate methods may modify it.
1451   web::WebLoadParams params(originalParams);
1453   // Initiating a navigation from the UI, record the current page state before
1454   // the new page loads. Don't record for back/forward, as the current entry
1455   // has already been moved to the next entry in the history. Do, however,
1456   // record it for general reload.
1457   // TODO(jimblackler): consider a single unified call to record state whenever
1458   // the page is about to be changed. This cannot currently be done after
1459   // addPendingEntry is called.
1461   [_delegate webWillInitiateLoadWithParams:params];
1463   GURL navUrl = params.url;
1464   ui::PageTransition transition = params.transition_type;
1466   BOOL initialNavigation = NO;
1467   BOOL forwardBack =
1468       PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD) &&
1469       (transition & ui::PAGE_TRANSITION_FORWARD_BACK);
1470   if (forwardBack) {
1471     // Setting these for back/forward is not supported.
1472     DCHECK(!params.extra_headers);
1473     DCHECK(!params.post_data);
1474   } else {
1475     // TODO(stuartmorgan): Why doesn't recordStateInHistory get called for
1476     // forward/back transitions?
1477     [self recordStateInHistory];
1479     CRWSessionController* history =
1480         _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1481     if (!self.currentSessionEntry)
1482       initialNavigation = YES;
1483     [history addPendingEntry:navUrl
1484                     referrer:params.referrer
1485                   transition:transition
1486            rendererInitiated:params.is_renderer_initiated];
1487     web::NavigationItemImpl* addedItem =
1488         [self currentSessionEntry].navigationItemImpl;
1489     DCHECK(addedItem);
1490     if (params.extra_headers)
1491       addedItem->AddHttpRequestHeaders(params.extra_headers);
1492     if (params.post_data) {
1493       DCHECK([addedItem->GetHttpRequestHeaders() objectForKey:@"Content-Type"])
1494           << "Post data should have an associated content type";
1495       addedItem->SetPostData(params.post_data);
1496       addedItem->SetShouldSkipResubmitDataConfirmation(true);
1497     }
1498   }
1500   [_delegate webDidUpdateSessionForLoadWithParams:params
1501                              wasInitialNavigation:initialNavigation];
1503   // If a non-default cache mode is passed in, it takes precedence over
1504   // |reload|.
1505   const BOOL reload = [self shouldReload:navUrl transition:transition];
1506   if (params.cache_mode != net::RequestTracker::CACHE_NORMAL) {
1507     _webStateImpl->SetCacheMode(params.cache_mode);
1508   } else if (reload) {
1509     _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_RELOAD);
1510   }
1512   [self loadCurrentURL];
1514   // Change the cache mode back to CACHE_NORMAL after a reload.
1515   _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1518 - (void)loadCurrentURL {
1519   // If the content view doesn't exist, the tab has either been evicted, or
1520   // never displayed. Bail, and let the URL be loaded when the tab is shown.
1521   if (!_containerView)
1522     return;
1524   // Reset current WebUI if one exists.
1525   [self clearWebUI];
1527   // Precaution, so that the outgoing URL is registered, to reduce the risk of
1528   // it being seen as a fresh URL later by the same method (and new page change
1529   // erroneously reported).
1530   [self checkForUnexpectedURLChange];
1532   // Abort any outstanding page load. This ensures the delegate gets informed
1533   // about the outgoing page, and further messages from the page are suppressed.
1534   if (_loadPhase != web::PAGE_LOADED)
1535     [self abortLoad];
1537   DCHECK(!_isHalted);
1538   // Remove the interstitial before doing anything else.
1539   [self clearInterstitials];
1541   const GURL currentURL = [self currentNavigationURL];
1542   // If it's a chrome URL, but not a native one, create the WebUI instance.
1543   if (web::GetWebClient()->IsAppSpecificURL(currentURL) &&
1544       ![_nativeProvider hasControllerForURL:currentURL]) {
1545     [self createWebUIForURL:currentURL];
1546   }
1548   // Loading a new url, must check here if it's a native chrome URL and
1549   // replace the appropriate view if so, or transition back to a web view from
1550   // a native view.
1551   if ([self shouldLoadURLInNativeView:currentURL]) {
1552     [self loadCurrentURLInNativeView];
1553   } else {
1554     [self loadCurrentURLInWebView];
1555   }
1557   // Once a URL has been loaded, any cached-based reconstruction state has
1558   // either been handled or obsoleted.
1559   _expectedReconstructionURL = GURL();
1562 - (BOOL)shouldLoadURLInNativeView:(const GURL&)url {
1563   // App-specific URLs that don't require WebUI are loaded in native views.
1564   return web::GetWebClient()->IsAppSpecificURL(url) &&
1565          !_webStateImpl->HasWebUI();
1568 - (void)triggerPendingLoad {
1569   if (!_containerView) {
1570     DCHECK(!_isBeingDestroyed);
1571     // Create the top-level parent view, which will contain the content (whether
1572     // native or web). Note, this needs to be created with a non-zero size
1573     // to allow for (native) subviews with autosize constraints to be correctly
1574     // processed.
1575     _containerView.reset([[CRWWebControllerContainerView alloc]
1576         initWithFrame:[[UIScreen mainScreen] bounds]]);
1577     [_containerView addGestureRecognizer:[self touchTrackingRecognizer]];
1578     [_containerView setAccessibilityIdentifier:web::kContainerViewID];
1579     // Is |currentUrl| a web scheme or native chrome scheme.
1580     BOOL isChromeScheme =
1581         web::GetWebClient()->IsAppSpecificURL([self currentNavigationURL]);
1583     // Don't immediately load the web page if in overlay mode. Always load if
1584     // native.
1585     if (isChromeScheme || !_overlayPreviewMode) {
1586       // TODO(jimblackler): end the practice of calling |loadCurrentURL| when it
1587       // is possible there is no current URL. If the call performs necessary
1588       // initialization, break that out.
1589       [self loadCurrentURL];
1590     }
1592     // Display overlay view until current url has finished loading or delay and
1593     // then transition away.
1594     if ((_overlayPreviewMode || _usePlaceholderOverlay) && !isChromeScheme)
1595       [self addPlaceholderOverlay];
1597     // Don't reset the overlay flag if in preview mode.
1598     if (!_overlayPreviewMode)
1599       _usePlaceholderOverlay = NO;
1600   }
1603 - (BOOL)shouldReload:(const GURL&)destinationURL
1604           transition:(ui::PageTransition)transition {
1605   // Do a reload if the user hits enter in the address bar or re-types a URL.
1606   CRWSessionController* sessionController =
1607       _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1608   web::NavigationItem* item =
1609       _webStateImpl->GetNavigationManagerImpl().GetVisibleItem();
1610   return (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) && item &&
1611          (destinationURL == item->GetURL() ||
1612           destinationURL == [sessionController currentEntry].originalUrl);
1615 // Reload either the web view or the native content depending on which is
1616 // displayed.
1617 - (void)reloadInternal {
1618   web::RecordAction(UserMetricsAction("Reload"));
1619   if (self.webView) {
1620     // Just as we don't use the WebView native back and forward navigation
1621     // (preferring to load the URLs manually) we don't use the native reload.
1622     // This ensures state processing and delegate calls are consistent.
1623     [self loadCurrentURL];
1624   } else {
1625     [_nativeController reload];
1626   }
1629 - (void)reload {
1630   [_delegate webWillReload];
1632   _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_RELOAD);
1633   [self reloadInternal];
1634   _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1637 - (void)loadCancelled {
1638   // Current load will not complete; this should be communicated upstream to the
1639   // delegate.
1640   switch (_loadPhase) {
1641     case web::LOAD_REQUESTED:
1642       // Load phase after abort is always PAGE_LOADED.
1643       _loadPhase = web::PAGE_LOADED;
1644       if (!_isHalted) {
1645         _webStateImpl->SetIsLoading(false);
1646       }
1647       [_delegate webCancelStartLoadingRequest];
1648       break;
1649     case web::PAGE_LOADING:
1650       // The previous load never fully completed before this page change. The
1651       // loadPhase is changed to PAGE_LOADED to indicate the cycle is complete,
1652       // and the delegate is called.
1653       _loadPhase = web::PAGE_LOADED;
1654       if (!_isHalted) {
1655         // RequestTracker expects StartPageLoad to be followed by
1656         // FinishPageLoad, passing the exact same URL.
1657         self.webStateImpl->GetRequestTracker()->FinishPageLoad(
1658             _URLOnStartLoading, false);
1659       }
1660       [_delegate webLoadCancelled:_URLOnStartLoading];
1661       break;
1662     case web::PAGE_LOADED:
1663       break;
1664   }
1667 - (void)abortLoad {
1668   [self abortWebLoad];
1669   [self loadCancelled];
1672 - (void)prepareForGoBack {
1673   // Make sure any transitions that may have occurred have been seen and acted
1674   // on by the CRWWebController, so the history stack and state of the
1675   // CRWWebController is 100% up to date before the stack navigation starts.
1676   if (self.webView) {
1677     [self injectEarlyInjectionScripts];
1678     [self checkForUnexpectedURLChange];
1679   }
1680   // Discard any outstanding pending entries before adjusting the navigation
1681   // index.
1682   CRWSessionController* sessionController =
1683       _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1684   [sessionController discardNonCommittedEntries];
1686   bool wasShowingInterstitial = _webStateImpl->IsShowingWebInterstitial();
1688   // Call into the delegate before |recordStateInHistory|.
1689   // TODO(rohitrao): Can this be reordered after |recordStateInHistory|?
1690   [_delegate webDidPrepareForGoBack];
1692   // Before changing the current session history entry, record the tab state.
1693   if (!wasShowingInterstitial) {
1694     [self recordStateInHistory];
1695   }
1698 - (void)goBack {
1699   [self goDelta:-1];
1702 - (void)goForward {
1703   [self goDelta:1];
1706 - (void)goDelta:(int)delta {
1707   if (delta == 0) {
1708     [self reload];
1709     return;
1710   }
1712   // Abort if there is nothing next in the history.
1713   // Note that it is NOT checked that the history depth is at least |delta|.
1714   if ((delta < 0 && ![self canGoBack]) || (delta > 0 && ![self canGoForward])) {
1715     return;
1716   }
1718   if (delta < 0) {
1719     [self prepareForGoBack];
1720   } else {
1721     // Before changing the current session history entry, record the tab state.
1722     [self recordStateInHistory];
1723   }
1725   [_delegate webWillGoDelta:delta];
1727   CRWSessionController* sessionController =
1728       _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1729   CRWSessionEntry* fromEntry = [sessionController currentEntry];
1730   [sessionController goDelta:delta];
1731   if (fromEntry) {
1732     _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_HISTORY);
1733     [self finishHistoryNavigationFromEntry:fromEntry];
1734     _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1735   }
1738 - (BOOL)isLoaded {
1739   return _loadPhase == web::PAGE_LOADED;
1742 - (void)loadCompleteWithSuccess:(BOOL)loadSuccess {
1743   [self removePlaceholderOverlay];
1744   // The webView may have been torn down (or replaced by a native view). Be
1745   // safe and do nothing if that's happened.
1746   if (_loadPhase != web::PAGE_LOADING)
1747     return;
1749   DCHECK(self.webView);
1751   const GURL currentURL([self currentURL]);
1753   [self resetLoadState];
1754   _loadPhase = web::PAGE_LOADED;
1756   [self optOutScrollsToTopForSubviews];
1758   // Ensure the URL is as expected (and already reported to the delegate).
1759   DCHECK(currentURL == _lastRegisteredRequestURL)
1760       << std::endl
1761       << "currentURL = [" << currentURL << "]" << std::endl
1762       << "_lastRegisteredRequestURL = [" << _lastRegisteredRequestURL << "]";
1764   // Perform post-load-finished updates.
1765   [self didFinishWithURL:currentURL loadSuccess:loadSuccess];
1767   // Execute the pending LoadCompleteActions.
1768   for (ProceduralBlock action in _pendingLoadCompleteActions.get()) {
1769     action();
1770   }
1771   [_pendingLoadCompleteActions removeAllObjects];
1774 - (void)didFinishWithURL:(const GURL&)currentURL loadSuccess:(BOOL)loadSuccess {
1775   DCHECK(_loadPhase == web::PAGE_LOADED);
1776   _webStateImpl->GetRequestTracker()->FinishPageLoad(currentURL, loadSuccess);
1777   // Reset the navigation type to the default value.
1778   // Note: it is possible that the web view has already started loading the
1779   // next page when this is called. In that case the cache mode can leak to
1780   // (some of) the requests of the next page. It's expected to be an edge case,
1781   // but if it becomes a problem it should be possible to notice it afterwards
1782   // and react to it (by warning the user or reloading the page for example).
1783   _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1784   _webStateImpl->GetRequestTracker()->SetCacheModeFromUIThread(
1785       _webStateImpl->GetCacheMode());
1787   [self restoreStateFromHistory];
1788   _webStateImpl->OnPageLoaded(currentURL, loadSuccess);
1789   _webStateImpl->SetIsLoading(false);
1790   // Inform the embedder the load completed.
1791   [_delegate webDidFinishWithURL:currentURL loadSuccess:loadSuccess];
1794 - (void)finishHistoryNavigationFromEntry:(CRWSessionEntry*)fromEntry {
1795   [_delegate webWillFinishHistoryNavigationFromEntry:fromEntry];
1797   // Check if toEntry was created by a JavaScript window.history.pushState()
1798   // call from fromEntry. If it was, don't load the URL. Instead update
1799   // UIWebView's URL and dispatch a popstate event.
1800   if ([_webStateImpl->GetNavigationManagerImpl().GetSessionController()
1801           isPushStateNavigationBetweenEntry:fromEntry
1802                                    andEntry:self.currentSessionEntry]) {
1803     NSString* state = [self currentSessionEntry]
1804                           .navigationItemImpl->GetSerializedStateObject();
1805     [self finishPushStateNavigationToURL:[self currentNavigationURL]
1806                          withStateObject:state];
1807   } else {
1808     GURL activeURL = [self currentNavigationURL];
1809     GURL fromURL = fromEntry.navigationItem->GetURL();
1810     GURL endURL =
1811         [self updateURLForHistoryNavigationFromURL:fromURL toURL:activeURL];
1812     web::NavigationItem* currentItem =
1813         _webStateImpl->GetNavigationManagerImpl().GetVisibleItem();
1814     ui::PageTransition transition = ui::PageTransitionFromInt(
1815         ui::PAGE_TRANSITION_RELOAD | ui::PAGE_TRANSITION_FORWARD_BACK);
1817     web::WebLoadParams params(endURL);
1818     if (currentItem) {
1819       params.referrer = currentItem->GetReferrer();
1820     }
1821     params.transition_type = transition;
1822     [self loadWithParams:params];
1823   }
1826 - (GURL)updateURLForHistoryNavigationFromURL:(const GURL&)startURL
1827                                        toURL:(const GURL&)endURL {
1828   // Check the state of the fragments on both URLs (aka, is there a '#' in the
1829   // url or not).
1830   if (!startURL.has_ref() || endURL.has_ref()) {
1831     return endURL;
1832   }
1834   // startURL contains a fragment and endURL doesn't. Remove the fragment from
1835   // startURL and compare the resulting string to endURL. If they are equal, add
1836   // # to endURL to cause a hashchange event.
1837   GURL hashless = web::GURLByRemovingRefFromGURL(startURL);
1839   if (hashless != endURL)
1840     return endURL;
1842   url::StringPieceReplacements<std::string> emptyRef;
1843   emptyRef.SetRefStr("");
1844   GURL newEndURL = endURL.ReplaceComponents(emptyRef);
1845   web::NavigationItem* item =
1846       _webStateImpl->GetNavigationManagerImpl().GetVisibleItem();
1847   if (item)
1848     item->SetURL(newEndURL);
1849   return newEndURL;
1852 - (void)evaluateJavaScript:(NSString*)script
1853          JSONResultHandler:
1854              (void (^)(scoped_ptr<base::Value>, NSError*))handler {
1855   [self evaluateJavaScript:script
1856        stringResultHandler:^(NSString* stringResult, NSError* error) {
1857          if (handler) {
1858            scoped_ptr<base::Value> result(
1859                base::JSONReader::Read(base::SysNSStringToUTF8(stringResult)));
1860            DCHECK(result || error);
1861            handler(result.Pass(), error);
1862          }
1863        }];
1866 - (void)addGestureRecognizerToWebView:(UIGestureRecognizer*)recognizer {
1867   if ([_gestureRecognizers containsObject:recognizer])
1868     return;
1870   [self.webView addGestureRecognizer:recognizer];
1871   [_gestureRecognizers addObject:recognizer];
1874 - (void)removeGestureRecognizerFromWebView:(UIGestureRecognizer*)recognizer {
1875   if (![_gestureRecognizers containsObject:recognizer])
1876     return;
1878   [self.webView removeGestureRecognizer:recognizer];
1879   [_gestureRecognizers removeObject:recognizer];
1882 - (void)addToolbarViewToWebView:(UIView*)toolbarView {
1883   DCHECK(toolbarView);
1884   if ([_webViewToolbars containsObject:toolbarView])
1885     return;
1886   [_webViewToolbars addObject:toolbarView];
1887   if (self.webView)
1888     [_containerView addToolbar:toolbarView];
1891 - (void)removeToolbarViewFromWebView:(UIView*)toolbarView {
1892   if (![_webViewToolbars containsObject:toolbarView])
1893     return;
1894   [_webViewToolbars removeObject:toolbarView];
1895   if (self.webView)
1896     [_containerView removeToolbar:toolbarView];
1899 - (CRWJSInjectionReceiver*)jsInjectionReceiver {
1900   return _jsInjectionReceiver;
1903 - (BOOL)cancellable {
1904   return self.sessionController.openedByDOM &&
1905          !self.sessionController.lastCommittedEntry;
1908 - (BOOL)isBeingDestroyed {
1909   return _isBeingDestroyed;
1912 - (BOOL)isHalted {
1913   return _isHalted;
1916 - (web::ReferrerPolicy)referrerPolicyFromString:(const std::string&)policy {
1917   // TODO(stuartmorgan): Remove this temporary bridge to the helper function
1918   // once the referrer handling moves into the subclasses.
1919   return web::ReferrerPolicyFromString(policy);
1922 #pragma mark -
1923 #pragma mark CRWJSInjectionEvaluator Methods
1925 - (void)evaluateJavaScript:(NSString*)script
1926        stringResultHandler:(web::JavaScriptCompletion)handler {
1927   // Subclasses must implement this method.
1928   NOTREACHED();
1931 - (BOOL)scriptHasBeenInjectedForClass:(Class)jsInjectionManagerClass
1932                        presenceBeacon:(NSString*)beacon {
1933   // Subclasses must implement this method.
1934   NOTREACHED();
1935   return NO;
1938 - (void)injectScript:(NSString*)script forClass:(Class)JSInjectionManagerClass {
1939   // Make sure that CRWJSEarlyScriptManager has been injected.
1940   BOOL ealyScriptInjected =
1941       [self scriptHasBeenInjectedForClass:[CRWJSEarlyScriptManager class]
1942                            presenceBeacon:[_earlyScriptManager presenceBeacon]];
1943   if (!ealyScriptInjected &&
1944       JSInjectionManagerClass != [CRWJSEarlyScriptManager class]) {
1945     [_earlyScriptManager inject];
1946   }
1949 - (web::WebViewType)webViewType {
1950   // Subclasses must implement this method.
1951   NOTREACHED();
1952   return web::UI_WEB_VIEW_TYPE;
1955 #pragma mark -
1957 - (void)evaluateUserJavaScript:(NSString*)script {
1958   // Subclasses must implement this method.
1959   NOTREACHED();
1962 - (void)didFinishNavigation {
1963   // This can be called at multiple times after the document has loaded. Do
1964   // nothing if the document has already loaded.
1965   if (_loadPhase == web::PAGE_LOADED)
1966     return;
1967   [self loadCompleteWithSuccess:YES];
1970 - (BOOL)respondToMessage:(base::DictionaryValue*)message
1971        userIsInteracting:(BOOL)userIsInteracting
1972                originURL:(const GURL&)originURL {
1973   std::string command;
1974   if (!message->GetString("command", &command)) {
1975     DLOG(WARNING) << "JS message parameter not found: command";
1976     return NO;
1977   }
1979   SEL handler = [self selectorToHandleJavaScriptCommand:command];
1980   if (!handler) {
1981     if (!self.webStateImpl->OnScriptCommandReceived(
1982             command, *message, originURL, userIsInteracting)) {
1983       // Message was either unexpected or not correctly handled.
1984       // Page is reset as a precaution.
1985       DLOG(WARNING) << "Unexpected message received: " << command;
1986       return NO;
1987     }
1988     return YES;
1989   }
1991   typedef BOOL (*HandlerType)(id, SEL, base::DictionaryValue*, NSDictionary*);
1992   HandlerType handlerImplementation =
1993       reinterpret_cast<HandlerType>([self methodForSelector:handler]);
1994   DCHECK(handlerImplementation);
1995   NSMutableDictionary* context =
1996       [NSMutableDictionary dictionaryWithObject:@(userIsInteracting)
1997                                          forKey:web::kUserIsInteractingKey];
1998   NSURL* originNSURL = net::NSURLWithGURL(originURL);
1999   if (originNSURL)
2000     context[web::kOriginURLKey] = originNSURL;
2001   return handlerImplementation(self, handler, message, context);
2004 - (SEL)selectorToHandleJavaScriptCommand:(const std::string&)command {
2005   static std::map<std::string, SEL>* handlers = nullptr;
2006   static dispatch_once_t onceToken;
2007   dispatch_once(&onceToken, ^{
2008     handlers = new std::map<std::string, SEL>();
2009     (*handlers)["addPluginPlaceholders"] =
2010         @selector(handleAddPluginPlaceholdersMessage:context:);
2011     (*handlers)["chrome.send"] = @selector(handleChromeSendMessage:context:);
2012     (*handlers)["console"] = @selector(handleConsoleMessage:context:);
2013     (*handlers)["dialog.suppressed"] =
2014         @selector(handleDialogSuppressedMessage:context:);
2015     (*handlers)["dialog.willShow"] =
2016         @selector(handleDialogWillShowMessage:context:);
2017     (*handlers)["document.favicons"] =
2018         @selector(handleDocumentFaviconsMessage:context:);
2019     (*handlers)["document.retitled"] =
2020         @selector(handleDocumentRetitledMessage:context:);
2021     (*handlers)["document.submit"] =
2022         @selector(handleDocumentSubmitMessage:context:);
2023     (*handlers)["externalRequest"] =
2024         @selector(handleExternalRequestMessage:context:);
2025     (*handlers)["form.activity"] =
2026         @selector(handleFormActivityMessage:context:);
2027     (*handlers)["form.requestAutocomplete"] =
2028         @selector(handleFormRequestAutocompleteMessage:context:);
2029     (*handlers)["navigator.credentials.request"] =
2030         @selector(handleCredentialsRequestedMessage:context:);
2031     (*handlers)["navigator.credentials.notifySignedIn"] =
2032         @selector(handleSignedInMessage:context:);
2033     (*handlers)["navigator.credentials.notifySignedOut"] =
2034         @selector(handleSignedOutMessage:context:);
2035     (*handlers)["navigator.credentials.notifyFailedSignIn"] =
2036         @selector(handleSignInFailedMessage:context:);
2037     (*handlers)["resetExternalRequest"] =
2038         @selector(handleResetExternalRequestMessage:context:);
2039     (*handlers)["window.close.self"] =
2040         @selector(handleWindowCloseSelfMessage:context:);
2041     (*handlers)["window.error"] = @selector(handleWindowErrorMessage:context:);
2042     (*handlers)["window.hashchange"] =
2043         @selector(handleWindowHashChangeMessage:context:);
2044     (*handlers)["window.history.back"] =
2045         @selector(handleWindowHistoryBackMessage:context:);
2046     (*handlers)["window.history.willChangeState"] =
2047         @selector(handleWindowHistoryWillChangeStateMessage:context:);
2048     (*handlers)["window.history.didPushState"] =
2049         @selector(handleWindowHistoryDidPushStateMessage:context:);
2050     (*handlers)["window.history.didReplaceState"] =
2051         @selector(handleWindowHistoryDidReplaceStateMessage:context:);
2052     (*handlers)["window.history.forward"] =
2053         @selector(handleWindowHistoryForwardMessage:context:);
2054     (*handlers)["window.history.go"] =
2055         @selector(handleWindowHistoryGoMessage:context:);
2056   });
2057   DCHECK(handlers);
2058   auto iter = handlers->find(command);
2059   return iter != handlers->end() ? iter->second : nullptr;
2062 #pragma mark -
2063 #pragma mark JavaScript message handlers
2065 - (BOOL)handleAddPluginPlaceholdersMessage:(base::DictionaryValue*)message
2066                                    context:(NSDictionary*)context {
2067   // Inject the script that adds the plugin placeholders.
2068   [[_jsInjectionReceiver
2069       instanceOfClass:[CRWJSPluginPlaceholderManager class]] inject];
2070   return YES;
2073 - (BOOL)handleChromeSendMessage:(base::DictionaryValue*)message
2074                         context:(NSDictionary*)context {
2075   if (_webStateImpl->HasWebUI()) {
2076     const GURL currentURL([self currentURL]);
2077     if (web::GetWebClient()->IsAppSpecificURL(currentURL)) {
2078       std::string messageContent;
2079       base::ListValue* arguments = nullptr;
2080       if (!message->GetString("message", &messageContent)) {
2081         DLOG(WARNING) << "JS message parameter not found: message";
2082         return NO;
2083       }
2084       if (!message->GetList("arguments", &arguments)) {
2085         DLOG(WARNING) << "JS message parameter not found: arguments";
2086         return NO;
2087       }
2088       _webStateImpl->OnScriptCommandReceived(
2089           messageContent, *message, currentURL,
2090           context[web::kUserIsInteractingKey]);
2091       _webStateImpl->ProcessWebUIMessage(currentURL, messageContent,
2092                                          *arguments);
2093       return YES;
2094     }
2095   }
2097   DLOG(WARNING)
2098       << "chrome.send message not handled because WebUI was not found.";
2099   return NO;
2102 - (BOOL)handleConsoleMessage:(base::DictionaryValue*)message
2103                      context:(NSDictionary*)context {
2104   // Do not log if JS logging is off.
2105   if (![[NSUserDefaults standardUserDefaults] boolForKey:web::kLogJavaScript]) {
2106     return YES;
2107   }
2109   std::string method;
2110   if (!message->GetString("method", &method)) {
2111     DLOG(WARNING) << "JS message parameter not found: method";
2112     return NO;
2113   }
2114   std::string consoleMessage;
2115   if (!message->GetString("message", &consoleMessage)) {
2116     DLOG(WARNING) << "JS message parameter not found: message";
2117     return NO;
2118   }
2119   std::string origin;
2120   if (!message->GetString("origin", &origin)) {
2121     DLOG(WARNING) << "JS message parameter not found: origin";
2122     return NO;
2123   }
2125   DVLOG(0) << origin << " [" << method << "] " << consoleMessage;
2126   return YES;
2129 - (BOOL)handleDialogSuppressedMessage:(base::DictionaryValue*)message
2130                               context:(NSDictionary*)context {
2131   if ([_delegate
2132           respondsToSelector:@selector(webControllerDidSuppressDialog:)]) {
2133     [_delegate webControllerDidSuppressDialog:self];
2134   }
2135   return YES;
2138 - (BOOL)handleDialogWillShowMessage:(base::DictionaryValue*)message
2139                             context:(NSDictionary*)context {
2140   if ([_delegate respondsToSelector:@selector(webControllerWillShowDialog:)]) {
2141     [_delegate webControllerWillShowDialog:self];
2142   }
2143   return YES;
2146 - (BOOL)handleDocumentFaviconsMessage:(base::DictionaryValue*)message
2147                               context:(NSDictionary*)context {
2148   base::ListValue* favicons = nullptr;
2149   if (!message->GetList("favicons", &favicons)) {
2150     DLOG(WARNING) << "JS message parameter not found: favicons";
2151     return NO;
2152   }
2153   std::vector<web::FaviconURL> urls;
2154   for (size_t fav_idx = 0; fav_idx != favicons->GetSize(); ++fav_idx) {
2155     base::DictionaryValue* favicon = nullptr;
2156     if (!favicons->GetDictionary(fav_idx, &favicon))
2157       return NO;
2158     std::string href;
2159     std::string rel;
2160     if (!favicon->GetString("href", &href)) {
2161       DLOG(WARNING) << "JS message parameter not found: href";
2162       return NO;
2163     }
2164     if (!favicon->GetString("rel", &rel)) {
2165       DLOG(WARNING) << "JS message parameter not found: rel";
2166       return NO;
2167     }
2168     web::FaviconURL::IconType icon_type = web::FaviconURL::FAVICON;
2169     if (rel == "apple-touch-icon")
2170       icon_type = web::FaviconURL::TOUCH_ICON;
2171     else if (rel == "apple-touch-icon-precomposed")
2172       icon_type = web::FaviconURL::TOUCH_PRECOMPOSED_ICON;
2173     urls.push_back(
2174         web::FaviconURL(GURL(href), icon_type, std::vector<gfx::Size>()));
2175   }
2176   if (!urls.empty())
2177     _webStateImpl->OnFaviconUrlUpdated(urls);
2178   return YES;
2181 - (BOOL)handleDocumentSubmitMessage:(base::DictionaryValue*)message
2182                             context:(NSDictionary*)context {
2183   std::string href;
2184   if (!message->GetString("href", &href)) {
2185     DLOG(WARNING) << "JS message parameter not found: href";
2186     return NO;
2187   }
2188   const GURL targetURL(href);
2189   const GURL currentURL([self currentURL]);
2190   bool targetsFrame = false;
2191   message->GetBoolean("targetsFrame", &targetsFrame);
2192   if (!targetsFrame && web::UrlHasWebScheme(targetURL)) {
2193     // The referrer is not known yet, and will be updated later.
2194     const web::Referrer emptyReferrer;
2195     [self registerLoadRequest:targetURL
2196                      referrer:emptyReferrer
2197                    transition:ui::PAGE_TRANSITION_FORM_SUBMIT];
2198   }
2199   std::string formName;
2200   message->GetString("formName", &formName);
2201   base::scoped_nsobject<NSSet> observers([_observers copy]);
2202   // We decide the form is user-submitted if the user has interacted with
2203   // the main page (using logic from the popup blocker), or if the keyboard
2204   // is visible.
2205   BOOL submittedByUser = [context[web::kUserIsInteractingKey] boolValue] ||
2206                          [_webViewProxy getKeyboardAccessory];
2207   _webStateImpl->OnDocumentSubmitted(formName, submittedByUser);
2208   return YES;
2211 - (BOOL)handleExternalRequestMessage:(base::DictionaryValue*)message
2212                              context:(NSDictionary*)context {
2213   std::string href;
2214   std::string target;
2215   std::string referrerPolicy;
2216   if (!message->GetString("href", &href)) {
2217     DLOG(WARNING) << "JS message parameter not found: href";
2218     return NO;
2219   }
2220   if (!message->GetString("target", &target)) {
2221     DLOG(WARNING) << "JS message parameter not found: target";
2222     return NO;
2223   }
2224   if (!message->GetString("referrerPolicy", &referrerPolicy)) {
2225     DLOG(WARNING) << "JS message parameter not found: referrerPolicy";
2226     return NO;
2227   }
2228   // Round-trip the href through NSURL; this URL will be compared as a
2229   // string against a UIWebView-provided NSURL later, and must match exactly
2230   // for the new window to trigger, so the escaping needs to be NSURL-style.
2231   // TODO(stuartmorgan): Comparing against a URL whose exact formatting we
2232   // don't control is fundamentally fragile; try to find another
2233   // way of handling this.
2234   DCHECK(context[web::kUserIsInteractingKey]);
2235   NSString* windowName =
2236       base::SysUTF8ToNSString(href + web::kWindowNameSeparator + target);
2237   _externalRequest.reset(new web::NewWindowInfo(
2238       net::GURLWithNSURL(net::NSURLWithGURL(GURL(href))), windowName,
2239       web::ReferrerPolicyFromString(referrerPolicy),
2240       [context[web::kUserIsInteractingKey] boolValue]));
2241   return YES;
2244 - (BOOL)handleFormActivityMessage:(base::DictionaryValue*)message
2245                           context:(NSDictionary*)context {
2246   std::string formName;
2247   std::string fieldName;
2248   std::string type;
2249   std::string value;
2250   int keyCode = web::WebStateObserver::kInvalidFormKeyCode;
2251   bool inputMissing = false;
2252   if (!message->GetString("formName", &formName) ||
2253       !message->GetString("fieldName", &fieldName) ||
2254       !message->GetString("type", &type) ||
2255       !message->GetString("value", &value)) {
2256     inputMissing = true;
2257   }
2259   if (!message->GetInteger("keyCode", &keyCode) || keyCode < 0)
2260     keyCode = web::WebStateObserver::kInvalidFormKeyCode;
2261   _webStateImpl->OnFormActivityRegistered(formName, fieldName, type, value,
2262                                           keyCode, inputMissing);
2263   return YES;
2266 - (BOOL)handleFormRequestAutocompleteMessage:(base::DictionaryValue*)message
2267                                      context:(NSDictionary*)context {
2268   std::string formName;
2269   if (!message->GetString("formName", &formName)) {
2270     DLOG(WARNING) << "JS message parameter not found: formName";
2271     return NO;
2272   }
2273   DCHECK(context[web::kUserIsInteractingKey]);
2274   _webStateImpl->OnAutocompleteRequested(
2275       net::GURLWithNSURL(context[web::kOriginURLKey]), formName,
2276       [context[web::kUserIsInteractingKey] boolValue]);
2277   return YES;
2280 - (BOOL)handleCredentialsRequestedMessage:(base::DictionaryValue*)message
2281                                   context:(NSDictionary*)context {
2282   int request_id = -1;
2283   if (!message->GetInteger("requestId", &request_id)) {
2284     DLOG(WARNING) << "JS message parameter not found: requestId";
2285     return NO;
2286   }
2287   bool suppress_ui = false;
2288   if (!message->GetBoolean("suppressUI", &suppress_ui)) {
2289     DLOG(WARNING) << "JS message parameter not found: suppressUI";
2290     return NO;
2291   }
2292   base::ListValue* federations_value = nullptr;
2293   if (!message->GetList("federations", &federations_value)) {
2294     DLOG(WARNING) << "JS message parameter not found: federations";
2295     return NO;
2296   }
2297   std::vector<std::string> federations;
2298   for (auto federation_value : *federations_value) {
2299     std::string federation;
2300     if (!federation_value->GetAsString(&federation)) {
2301       DLOG(WARNING) << "JS message parameter 'federations' contains wrong type";
2302       return NO;
2303     }
2304     federations.push_back(federation);
2305   }
2306   DCHECK(context[web::kUserIsInteractingKey]);
2307   _webStateImpl->OnCredentialsRequested(
2308       request_id, net::GURLWithNSURL(context[web::kOriginURLKey]), suppress_ui,
2309       federations, [context[web::kUserIsInteractingKey] boolValue]);
2310   return YES;
2313 - (BOOL)handleSignedInMessage:(base::DictionaryValue*)message
2314                       context:(NSDictionary*)context {
2315   int request_id = -1;
2316   if (!message->GetInteger("requestId", &request_id)) {
2317     DLOG(WARNING) << "JS message parameter not found: requestId";
2318     return NO;
2319   }
2320   base::DictionaryValue* credential_data = nullptr;
2321   web::Credential credential;
2322   if (message->GetDictionary("credential", &credential_data)) {
2323     if (!web::DictionaryValueToCredential(*credential_data, &credential)) {
2324       DLOG(WARNING) << "JS message parameter 'credential' is invalid";
2325       return NO;
2326     }
2327     _webStateImpl->OnSignedIn(request_id,
2328                               net::GURLWithNSURL(context[web::kOriginURLKey]),
2329                               credential);
2330   } else {
2331     _webStateImpl->OnSignedIn(request_id,
2332                               net::GURLWithNSURL(context[web::kOriginURLKey]));
2333   }
2334   return YES;
2337 - (BOOL)handleSignedOutMessage:(base::DictionaryValue*)message
2338                        context:(NSDictionary*)context {
2339   int request_id = -1;
2340   if (!message->GetInteger("requestId", &request_id)) {
2341     DLOG(WARNING) << "JS message parameter not found: requestId";
2342     return NO;
2343   }
2344   _webStateImpl->OnSignedOut(request_id,
2345                              net::GURLWithNSURL(context[web::kOriginURLKey]));
2346   return YES;
2349 - (BOOL)handleSignInFailedMessage:(base::DictionaryValue*)message
2350                           context:(NSDictionary*)context {
2351   int request_id = -1;
2352   if (!message->GetInteger("requestId", &request_id)) {
2353     DLOG(WARNING) << "JS message parameter not found: requestId";
2354     return NO;
2355   }
2356   base::DictionaryValue* credential_data = nullptr;
2357   web::Credential credential;
2358   if (message->GetDictionary("credential", &credential_data)) {
2359     if (!web::DictionaryValueToCredential(*credential_data, &credential)) {
2360       DLOG(WARNING) << "JS message parameter 'credential' is invalid";
2361       return NO;
2362     }
2363     _webStateImpl->OnSignInFailed(
2364         request_id, net::GURLWithNSURL(context[web::kOriginURLKey]),
2365         credential);
2366   } else {
2367     _webStateImpl->OnSignInFailed(
2368         request_id, net::GURLWithNSURL(context[web::kOriginURLKey]));
2369   }
2370   return YES;
2373 - (BOOL)handleResetExternalRequestMessage:(base::DictionaryValue*)message
2374                                   context:(NSDictionary*)context {
2375   _externalRequest.reset();
2376   return YES;
2379 - (BOOL)handleWindowCloseSelfMessage:(base::DictionaryValue*)message
2380                              context:(NSDictionary*)context {
2381   [self orderClose];
2382   return YES;
2385 - (BOOL)handleWindowErrorMessage:(base::DictionaryValue*)message
2386                          context:(NSDictionary*)context {
2387   std::string errorMessage;
2388   if (!message->GetString("message", &errorMessage)) {
2389     DLOG(WARNING) << "JS message parameter not found: message";
2390     return NO;
2391   }
2392   DLOG(ERROR) << "JavaScript error: " << errorMessage
2393               << " URL:" << [self currentURL].spec();
2394   return YES;
2397 - (BOOL)handleWindowHashChangeMessage:(base::DictionaryValue*)message
2398                               context:(NSDictionary*)context {
2399   [self checkForUnexpectedURLChange];
2401   // Notify the observers.
2402   _webStateImpl->OnUrlHashChanged();
2403   return YES;
2406 - (BOOL)handleWindowHistoryBackMessage:(base::DictionaryValue*)message
2407                                context:(NSDictionary*)context {
2408   [self goBack];
2409   return YES;
2412 - (BOOL)handleWindowHistoryForwardMessage:(base::DictionaryValue*)message
2413                                   context:(NSDictionary*)context {
2414   [self goForward];
2415   return YES;
2418 - (BOOL)handleWindowHistoryGoMessage:(base::DictionaryValue*)message
2419                              context:(NSDictionary*)context {
2420   int delta;
2421   message->GetInteger("value", &delta);
2422   [self goDelta:delta];
2423   return YES;
2426 - (BOOL)handleWindowHistoryWillChangeStateMessage:(base::DictionaryValue*)unused
2427                                           context:(NSDictionary*)unusedContext {
2428   // This dummy handler is a workaround for crbug.com/490673. Issue was
2429   // happening when two sequential calls of window.history.pushState were
2430   // performed by the page. In that case state was changed twice before
2431   // first change was reported to embedder (and first URL change was reported
2432   // incorrectly).
2434   // Using dummy handler for window.history.willChangeState message holds
2435   // second state change until the first change is reported, because messages
2436   // are queued. This is essentially a sleep, and not the real fix of the
2437   // problem. TODO(eugenebut): refactor handleWindowHistoryDidPushStateMessage:
2438   // to avoid this "sleep".
2439   return YES;
2442 - (BOOL)handleWindowHistoryDidPushStateMessage:(base::DictionaryValue*)message
2443                                        context:(NSDictionary*)context {
2444   std::string pageURL;
2445   std::string baseURL;
2446   if (!message->GetString("pageUrl", &pageURL) ||
2447       !message->GetString("baseUrl", &baseURL)) {
2448     DLOG(WARNING) << "JS message parameter not found: pageUrl or baseUrl";
2449     return NO;
2450   }
2451   GURL pushURL = web::history_state_util::GetHistoryStateChangeUrl(
2452       [self currentURL], GURL(baseURL), pageURL);
2453   // UIWebView seems to choke on unicode characters that haven't been
2454   // escaped; escape the URL now so the expected load URL is correct.
2455   pushURL = URLEscapedForHistory(pushURL);
2456   if (!pushURL.is_valid())
2457     return YES;
2458   const NavigationManagerImpl& navigationManager =
2459       _webStateImpl->GetNavigationManagerImpl();
2460   web::NavigationItem* navItem = [self currentNavItem];
2461   // PushState happened before first navigation entry or called right after
2462   // window.open when the url is empty.
2463   if (!navItem ||
2464       (navigationManager.GetEntryCount() <= 1 && navItem->GetURL().is_empty()))
2465     return YES;
2466   if (!web::history_state_util::IsHistoryStateChangeValid(navItem->GetURL(),
2467                                                           pushURL)) {
2468     // A redirect may have occurred just prior to the pushState. Check if
2469     // the URL needs to be updated.
2470     // TODO(bdibello): Investigate how the pushState() is handled before the
2471     // redirect and after core.js injection.
2472     [self checkForUnexpectedURLChange];
2473   }
2474   if (!web::history_state_util::IsHistoryStateChangeValid(
2475           [self currentNavItem]->GetURL(), pushURL)) {
2476     // If the current session entry URL origin still doesn't match pushURL's
2477     // origin, ignore the pushState. This can happen if a new URL is loaded
2478     // just before the pushState.
2479     return YES;
2480   }
2481   std::string stateObjectJSON;
2482   if (!message->GetString("stateObject", &stateObjectJSON)) {
2483     DLOG(WARNING) << "JS message parameter not found: stateObject";
2484     return NO;
2485   }
2486   NSString* stateObject = base::SysUTF8ToNSString(stateObjectJSON);
2487   _URLOnStartLoading = pushURL;
2488   _lastRegisteredRequestURL = pushURL;
2489   [self pushStateWithPageURL:pushURL stateObject:stateObject];
2491   NSString* replaceWebViewJS =
2492       [self javascriptToReplaceWebViewURL:pushURL stateObjectJSON:stateObject];
2493   base::WeakNSObject<CRWWebController> weakSelf(self);
2494   [self evaluateJavaScript:replaceWebViewJS
2495        stringResultHandler:^(NSString*, NSError*) {
2496          if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
2497            return;
2498          base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
2499          [strongSelf optOutScrollsToTopForSubviews];
2500          // Notify the observers.
2501          strongSelf.get()->_webStateImpl->OnHistoryStateChanged();
2502          [strongSelf didFinishNavigation];
2503        }];
2504   return YES;
2507 - (BOOL)handleWindowHistoryDidReplaceStateMessage:
2508     (base::DictionaryValue*)message
2509                                           context:(NSDictionary*)context {
2510   std::string pageURL;
2511   std::string baseURL;
2512   if (!message->GetString("pageUrl", &pageURL) ||
2513       !message->GetString("baseUrl", &baseURL)) {
2514     DLOG(WARNING) << "JS message parameter not found: pageUrl or baseUrl";
2515     return NO;
2516   }
2517   GURL replaceURL = web::history_state_util::GetHistoryStateChangeUrl(
2518       [self currentURL], GURL(baseURL), pageURL);
2519   // UIWebView seems to choke on unicode characters that haven't been
2520   // escaped; escape the URL now so the expected load URL is correct.
2521   replaceURL = URLEscapedForHistory(replaceURL);
2522   if (!replaceURL.is_valid())
2523     return YES;
2524   const NavigationManagerImpl& navigationManager =
2525       _webStateImpl->GetNavigationManagerImpl();
2526   web::NavigationItem* navItem = [self currentNavItem];
2527   // ReplaceState happened before first navigation entry or called right
2528   // after window.open when the url is empty/not valid.
2529   if (!navItem ||
2530       (navigationManager.GetEntryCount() <= 1 && navItem->GetURL().is_empty()))
2531     return YES;
2532   if (!web::history_state_util::IsHistoryStateChangeValid(navItem->GetURL(),
2533                                                           replaceURL)) {
2534     // A redirect may have occurred just prior to the replaceState. Check if
2535     // the URL needs to be updated.
2536     [self checkForUnexpectedURLChange];
2537   }
2538   if (!web::history_state_util::IsHistoryStateChangeValid(
2539           [self currentNavItem]->GetURL(), replaceURL)) {
2540     // If the current session entry URL origin still doesn't match
2541     // replaceURL's origin, ignore the replaceState. This can happen if a
2542     // new URL is loaded just before the replaceState.
2543     return YES;
2544   }
2545   std::string stateObjectJSON;
2546   if (!message->GetString("stateObject", &stateObjectJSON)) {
2547     DLOG(WARNING) << "JS message parameter not found: stateObject";
2548     return NO;
2549   }
2550   NSString* stateObject = base::SysUTF8ToNSString(stateObjectJSON);
2551   _URLOnStartLoading = replaceURL;
2552   _lastRegisteredRequestURL = replaceURL;
2553   [self replaceStateWithPageURL:replaceURL stateObject:stateObject];
2554   NSString* replaceStateJS = [self javascriptToReplaceWebViewURL:replaceURL
2555                                                  stateObjectJSON:stateObject];
2556   base::WeakNSObject<CRWWebController> weakSelf(self);
2557   [self evaluateJavaScript:replaceStateJS
2558        stringResultHandler:^(NSString*, NSError*) {
2559          if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
2560            return;
2561          base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
2562          [strongSelf didFinishNavigation];
2563        }];
2564   return YES;
2567 #pragma mark -
2569 - (BOOL)wantsKeyboardShield {
2570   if (_nativeController &&
2571       [_nativeController respondsToSelector:@selector(wantsKeyboardShield)]) {
2572     return [_nativeController wantsKeyboardShield];
2573   }
2574   return YES;
2577 - (BOOL)wantsLocationBarHintText {
2578   if (_nativeController &&
2579       [_nativeController
2580           respondsToSelector:@selector(wantsLocationBarHintText)]) {
2581     return [_nativeController wantsLocationBarHintText];
2582   }
2583   return YES;
2586 // TODO(stuartmorgan): This method conflates document changes and URL changes;
2587 // we should be distinguishing better, and be clear about the expected
2588 // WebDelegate and WCO callbacks in each case.
2589 - (void)webPageChanged {
2590   DCHECK(_loadPhase == web::LOAD_REQUESTED);
2592   const GURL currentURL([self currentURL]);
2593   web::Referrer referrer = [self currentReferrer];
2594   // If no referrer was known in advance, record it now. (If there was one,
2595   // keep it since it will have a more accurate URL and policy than what can
2596   // be extracted from the landing page.)
2597   web::NavigationItem* currentItem = [self currentNavItem];
2598   if (!currentItem->GetReferrer().url.is_valid()) {
2599     currentItem->SetReferrer(referrer);
2600   }
2602   // TODO(stuartmorgan): This shouldn't be called for hash state or
2603   // push/replaceState.
2604   [self resetDocumentSpecificState];
2606   [self didStartLoadingURL:currentURL updateHistory:YES];
2609 - (void)resetDocumentSpecificState {
2610   _lastUserInteraction.reset();
2611   _clickInProgress = NO;
2612   _lastSeenWindowID.reset([[_windowIDJSManager windowId] copy]);
2615 - (void)didStartLoadingURL:(const GURL&)url updateHistory:(BOOL)updateHistory {
2616   _loadPhase = web::PAGE_LOADING;
2617   _URLOnStartLoading = url;
2618   _displayStateOnStartLoading = self.pageDisplayState;
2620   _userInteractionRegistered = NO;
2621   _pageHasZoomed = NO;
2623   [[self sessionController] commitPendingEntry];
2624   _webStateImpl->GetRequestTracker()->StartPageLoad(
2625       url, [[self sessionController] currentEntry]);
2626   [_delegate webDidStartLoadingURL:url shouldUpdateHistory:updateHistory];
2629 - (BOOL)checkForUnexpectedURLChange {
2630   // Subclasses may override this method to check for and handle URL changes.
2631   return NO;
2634 - (void)wasShown {
2635   if (_nativeController &&
2636       [_nativeController respondsToSelector:@selector(wasShown)]) {
2637     [_nativeController wasShown];
2638   }
2641 - (void)wasHidden {
2642   if (_isHalted)
2643     return;
2644   if (_nativeController &&
2645       [_nativeController respondsToSelector:@selector(wasHidden)]) {
2646     [_nativeController wasHidden];
2647   }
2650 + (BOOL)webControllerCanShow:(const GURL&)url {
2651   return web::UrlHasWebScheme(url) ||
2652          web::GetWebClient()->IsAppSpecificURL(url) ||
2653          url.SchemeIs(url::kFileScheme) || url.SchemeIs(url::kAboutScheme);
2656 - (void)setUserInteractionRegistered:(BOOL)flag {
2657   _userInteractionRegistered = flag;
2660 - (BOOL)userInteractionRegistered {
2661   return _userInteractionRegistered;
2664 - (BOOL)useDesktopUserAgent {
2665   web::NavigationItem* item = [self currentNavItem];
2666   return item && item->IsOverridingUserAgent();
2669 - (void)cachePOSTDataForRequest:(NSURLRequest*)request
2670                  inSessionEntry:(CRWSessionEntry*)currentSessionEntry {
2671   NSUInteger maxPOSTDataSizeInBytes = 4096;
2672   NSString* cookieHeaderName = @"cookie";
2674   web::NavigationItemImpl* currentItem = currentSessionEntry.navigationItemImpl;
2675   DCHECK(currentItem);
2676   const bool shouldUpdateEntry =
2677       ui::PageTransitionCoreTypeIs(currentItem->GetTransitionType(),
2678                                    ui::PAGE_TRANSITION_FORM_SUBMIT) &&
2679       ![request HTTPBodyStream] &&  // Don't cache streams.
2680       !currentItem->HasPostData() &&
2681       currentItem->GetURL() == net::GURLWithNSURL([request URL]);
2682   const bool belowSizeCap =
2683       [[request HTTPBody] length] < maxPOSTDataSizeInBytes;
2684   DLOG_IF(WARNING, shouldUpdateEntry && !belowSizeCap)
2685       << "Data in POST request exceeds the size cap (" << maxPOSTDataSizeInBytes
2686       << " bytes), and will not be cached.";
2688   if (shouldUpdateEntry && belowSizeCap) {
2689     currentItem->SetPostData([request HTTPBody]);
2690     currentItem->ResetHttpRequestHeaders();
2691     currentItem->AddHttpRequestHeaders([request allHTTPHeaderFields]);
2692     // Don't cache the "Cookie" header.
2693     // According to NSURLRequest documentation, |-valueForHTTPHeaderField:| is
2694     // case insensitive, so it's enough to test the lower case only.
2695     if ([request valueForHTTPHeaderField:cookieHeaderName]) {
2696       // Case insensitive search in |headers|.
2697       NSSet* cookieKeys = [currentItem->GetHttpRequestHeaders()
2698           keysOfEntriesPassingTest:^(id key, id obj, BOOL* stop) {
2699             NSString* header = (NSString*)key;
2700             const BOOL found =
2701                 [header caseInsensitiveCompare:cookieHeaderName] ==
2702                 NSOrderedSame;
2703             *stop = found;
2704             return found;
2705           }];
2706       DCHECK_EQ(1u, [cookieKeys count]);
2707       currentItem->RemoveHttpRequestHeaderForKey([cookieKeys anyObject]);
2708     }
2709   }
2712 // TODO(stuartmorgan): This is mostly logic from the original UIWebView delegate
2713 // method, which provides less information than the WKWebView version. Audit
2714 // this for things that should be handled in the subclass instead.
2715 - (BOOL)shouldAllowLoadWithRequest:(NSURLRequest*)request
2716                        targetFrame:(const web::FrameInfo*)targetFrame
2717                        isLinkClick:(BOOL)isLinkClick {
2718   GURL requestURL = net::GURLWithNSURL(request.URL);
2720   // Check if the request should be delayed.
2721   if (_externalRequest && _externalRequest->url == requestURL) {
2722     // Links that can't be shown in a tab by Chrome but can be handled by
2723     // external apps (e.g. tel:, mailto:) are opened directly despite the target
2724     // attribute on the link. We don't open a new tab for them because Mobile
2725     // Safari doesn't do that (and sites are expecting us to do the same) and
2726     // also because there would be nothing shown in that new tab; it would
2727     // remain on about:blank (see crbug.com/240178)
2728     if ([CRWWebController webControllerCanShow:requestURL] ||
2729         ![_delegate openExternalURL:requestURL]) {
2730       web::NewWindowInfo windowInfo = *_externalRequest;
2731       dispatch_async(dispatch_get_main_queue(), ^{
2732         [self openPopupWithInfo:windowInfo];
2733       });
2734     }
2735     _externalRequest.reset();
2736     return NO;
2737   }
2739   BOOL shouldCheckNativeApp = [self cancellable];
2741   // Check if the link navigation leads to a launch of an external app.
2742   // TODO(shreyasv): Change this such that handling/stealing of link navigations
2743   // is delegated to the WebDelegate and the logic around external app launching
2744   // is moved there as well.
2745   if (shouldCheckNativeApp || isLinkClick) {
2746     // Check If the URL is handled by a native app.
2747     if ([self urlTriggersNativeAppLaunch:requestURL
2748                                sourceURL:[self currentNavigationURL]]) {
2749       // External app has been launched successfully. Stop the current page
2750       // load operation (e.g. notifying all observers) and record the URL so
2751       // that errors reported following the 'NO' reply can be safely ignored.
2752       if ([self cancellable])
2753         [_delegate webPageOrderedClose];
2754       [self abortLoad];
2755       [_openedApplicationURL addObject:request.URL];
2756       return NO;
2757     }
2758   }
2760   // The WebDelegate may instruct the CRWWebController to stop loading, and
2761   // instead instruct the next page to be loaded in an animation.
2762   GURL mainDocumentURL = net::GURLWithNSURL(request.mainDocumentURL);
2763   DCHECK(self.webView);
2764   if (![self shouldOpenURL:requestURL
2765            mainDocumentURL:mainDocumentURL
2766                linkClicked:isLinkClick]) {
2767     return NO;
2768   }
2770   // If the URL doesn't look like one we can show, try to open the link with an
2771   // external application.
2772   // TODO(droger):  Check transition type before opening an external
2773   // application? For example, only allow it for TYPED and LINK transitions.
2774   if (![CRWWebController webControllerCanShow:requestURL]) {
2775     if (![self shouldOpenExternalURLRequest:request targetFrame:targetFrame]) {
2776       return NO;
2777     }
2779     // Abort load if navigation is hapenning on the main frame. If |targetFrame|
2780     // is unknown use heuristic to guess the target frame by comparing
2781     // documentURL and navigation URL. This heuristic may have false positives.
2782     bool shouldAbortLoad = targetFrame ? targetFrame->is_main_frame
2783                                        : requestURL == mainDocumentURL;
2784     if (shouldAbortLoad)
2785       [self abortLoad];
2787     if ([_delegate openExternalURL:requestURL]) {
2788       // Record the URL so that errors reported following the 'NO' reply can be
2789       // safely ignored.
2790       [_openedApplicationURL addObject:request.URL];
2791       if ([self cancellable])
2792         [_delegate webPageOrderedClose];
2793     }
2794     return NO;
2795   }
2797   if ([[request HTTPMethod] isEqualToString:@"POST"]) {
2798     [self cachePOSTDataForRequest:request
2799                    inSessionEntry:[self currentSessionEntry]];
2800   }
2802   return YES;
2805 - (void)restoreStateAfterURLRejection {
2806   [[self sessionController] discardNonCommittedEntries];
2808   // Re-register the user agent, because UIWebView will sometimes try to read
2809   // the agent again from a saved search result page in which no other page has
2810   // yet been loaded. See crbug.com/260370.
2811   [self registerUserAgent];
2813   // Reset |_lastRegisteredRequestURL| so that it reflects the URL from before
2814   // the load was rejected. This value may be out of sync because
2815   // |_lastRegisteredRequestURL| may have already been updated before the load
2816   // was rejected.
2817   _lastRegisteredRequestURL = [self currentURL];
2818   _loadPhase = web::PAGE_LOADING;
2819   [self didFinishNavigation];
2822 - (void)handleLoadError:(NSError*)error inMainFrame:(BOOL)inMainFrame {
2823   // Attempt to translate iOS errors into their corresponding net errors.
2824   error = web::NetErrorFromError(error);
2826   if ([error code] == NSURLErrorUnsupportedURL)
2827     return;
2828   // In cases where a Plug-in handles the load do not take any further action.
2829   if ([[error domain] isEqual:WebKitErrorDomain] &&
2830       ([error code] == WebKitErrorPlugInLoadFailed ||
2831        [error code] == WebKitErrorCannotShowURL))
2832     return;
2834   // Continue processing only if the error is on the main request or is the
2835   // result of a user interaction.
2836   NSDictionary* userInfo = [error userInfo];
2837   // |userinfo| contains the request creation date as a NSDate.
2838   NSTimeInterval requestCreationDate =
2839       [[userInfo objectForKey:@"CreationDate"] timeIntervalSinceReferenceDate];
2840   bool userInteracted = false;
2841   if (requestCreationDate != 0.0 && _lastUserInteraction) {
2842     NSTimeInterval timeSinceInteraction =
2843         requestCreationDate - _lastUserInteraction->time;
2844     // The error is considered to be the result of a user interaction if any
2845     // interaction happened just before the request was made.
2846     // TODO(droger): If the user interacted with the page after the request was
2847     // made (i.e. creationTimeSinceLastInteraction < 0), then
2848     // |_lastUserInteraction| has been overridden. The current behavior is to
2849     // discard the interstitial in that case. A better decision could be made if
2850     // we had a history of all the user interactions instead of just the last
2851     // one.
2852     userInteracted =
2853         timeSinceInteraction < kMaximumDelayForUserInteractionInSeconds &&
2854         _lastUserInteraction->time > _lastTransferTimeInSeconds &&
2855         timeSinceInteraction >= 0.0;
2856   } else {
2857     // If the error does not have timing information, check if the user
2858     // interacted with the page recently.
2859     userInteracted = [self userIsInteracting];
2860   }
2861   if (!inMainFrame && !userInteracted)
2862     return;
2864   NSURL* errorURL = [NSURL
2865       URLWithString:[userInfo objectForKey:NSURLErrorFailingURLStringErrorKey]];
2866   const GURL errorGURL = net::GURLWithNSURL(errorURL);
2868   // Handles Frame Load Interrupted errors from WebView.
2869   if ([[error domain] isEqualToString:WebKitErrorDomain] &&
2870       [error code] == WebKitErrorFrameLoadInterruptedByPolicyChange) {
2871     // See if the delegate wants to handle this case.
2872     if (errorGURL.is_valid() &&
2873         [_delegate
2874             respondsToSelector:@selector(
2875                                    controllerForUnhandledContentAtURL:)]) {
2876       id<CRWNativeContent> controller =
2877           [_delegate controllerForUnhandledContentAtURL:errorGURL];
2878       if (controller) {
2879         [self loadCompleteWithSuccess:NO];
2880         [self removeWebViewAllowingCachedReconstruction:NO];
2881         [self setNativeController:controller];
2882         [self loadNativeViewWithSuccess:YES];
2883         return;
2884       }
2885     }
2887     // Otherwise, handle the error normally.
2888     if ([_openedApplicationURL containsObject:errorURL])
2889       return;
2890     // Certain frame errors don't have URL information for some reason; for
2891     // those cases (so far the only known case is plugin content loaded directly
2892     // in a frame) just ignore the error. See crbug.com/414295
2893     if (!errorURL) {
2894       DCHECK(!inMainFrame);
2895       return;
2896     }
2897     // The wrapper error uses the URL of the error and not the requested URL
2898     // (which can be different in case of a redirect) to match desktop Chrome
2899     // behavior.
2900     NSError* wrapperError = [NSError
2901         errorWithDomain:[error domain]
2902                    code:[error code]
2903                userInfo:@{
2904                  NSURLErrorFailingURLStringErrorKey : [errorURL absoluteString],
2905                  NSUnderlyingErrorKey : error
2906                }];
2907     [self loadCompleteWithSuccess:NO];
2908     [self loadErrorInNativeView:wrapperError];
2909     return;
2910   }
2912   // TODO(ios): Audit comments and behavior below regarding error origin. The
2913   // error has been translated and may appear to have originated in the Chrome
2914   // network stack when that is not true (crbug.com/496972)
2915   // Ignore cancelled errors.
2916   if ([error code] == NSURLErrorCancelled) {
2917     NSError* underlyingError = [userInfo objectForKey:NSUnderlyingErrorKey];
2918     if (underlyingError && [self shouldAbortLoadForCancelledURL:errorGURL]) {
2919       DCHECK([underlyingError isKindOfClass:[NSError class]]);
2921       // The Error contains an NSUnderlyingErrorKey so it's being generated
2922       // in the Chrome network stack. Aborting the load in this case.
2923       [self abortLoad];
2925       switch ([underlyingError code]) {
2926         case net::ERR_ABORTED:
2927           // |NSURLErrorCancelled| errors with underlying net error code
2928           // |net::ERR_ABORTED| are used by the Chrome network stack to
2929           // indicate that the current load should be aborted and the pending
2930           // entry should be discarded.
2931           [[self sessionController] discardNonCommittedEntries];
2932           break;
2933         case net::ERR_BLOCKED_BY_CLIENT:
2934           // |NSURLErrorCancelled| errors with underlying net error code
2935           // |net::ERR_BLOCKED_BY_CLIENT| are used by the Chrome network stack
2936           // to indicate that the current load should be aborted and the pending
2937           // entry should be kept.
2938           break;
2939         default:
2940           NOTREACHED();
2941       }
2942     }
2943     return;
2944   }
2946   [self loadCompleteWithSuccess:NO];
2947   [self loadErrorInNativeView:error];
2950 - (BOOL)shouldAbortLoadForCancelledURL:(const GURL &)cancelledURL {
2951   // Subclasses must implement this method.
2952   NOTREACHED();
2953   return YES;
2956 #pragma mark -
2957 #pragma mark WebUI
2959 - (void)createWebUIForURL:(const GURL&)URL {
2960   _webStateImpl->CreateWebUI(URL);
2963 - (void)clearWebUI {
2964   _webStateImpl->ClearWebUI();
2967 #pragma mark -
2968 #pragma mark UIGestureRecognizerDelegate
2970 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2971     shouldRecognizeSimultaneouslyWithGestureRecognizer:
2972         (UIGestureRecognizer*)otherGestureRecognizer {
2973   // Allows the custom UILongPressGestureRecognizer to fire simultaneously with
2974   // other recognizers.
2975   return YES;
2978 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2979        shouldReceiveTouch:(UITouch*)touch {
2980   // Expect only _contextMenuRecognizer.
2981   DCHECK([gestureRecognizer isEqual:_contextMenuRecognizer]);
2982   if (![self supportsCustomContextMenu]) {
2983     // Fetching context menu info is not a free operation, early return if a
2984     // context menu should not be shown.
2985     return YES;
2986   }
2988   // This is custom long press gesture recognizer. By the time the gesture is
2989   // recognized the web controller needs to know if there is a link under the
2990   // touch. If there a link, the web controller will reject system's context
2991   // menu and show another one. If for some reason context menu info is not
2992   // fetched - system context menu will be shown.
2993   [self setDOMElementForLastTouch:nullptr];
2994   base::WeakNSObject<CRWWebController> weakSelf(self);
2995   [self fetchDOMElementAtPoint:[touch locationInView:self.webView]
2996              completionHandler:^(scoped_ptr<base::DictionaryValue> element) {
2997                [weakSelf setDOMElementForLastTouch:element.Pass()];
2998              }];
2999   return YES;
3002 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer {
3003   // Expect only _contextMenuRecognizer.
3004   DCHECK([gestureRecognizer isEqual:_contextMenuRecognizer]);
3005   if (!self.webView || ![self supportsCustomContextMenu]) {
3006     // Show the context menu iff currently displaying a web view.
3007     // Do nothing for native views.
3008     return NO;
3009   }
3011   UMA_HISTOGRAM_BOOLEAN("WebController.FetchContextMenuInfoAsyncSucceeded",
3012                         _DOMElementForLastTouch);
3014   return _DOMElementForLastTouch && !_DOMElementForLastTouch->empty();
3017 #pragma mark -
3018 #pragma mark CRWRequestTrackerDelegate
3020 - (BOOL)isForStaticFileRequests {
3021   return NO;
3024 - (void)updatedSSLStatus:(const web::SSLStatus&)sslStatus
3025               forPageUrl:(const GURL&)url
3026                 userInfo:(id)userInfo {
3027   // |userInfo| is a CRWSessionEntry.
3028   web::NavigationItem* item =
3029       [static_cast<CRWSessionEntry*>(userInfo) navigationItem];
3030   if (!item)
3031     return;  // This is a request update for an entry that no longer exists.
3033   // This condition happens infrequently when a page load is misinterpreted as
3034   // a resource load from a previous page. This can happen when moving quickly
3035   // back and forth through history, the notifications from the web view on the
3036   // UI thread and the one from the requests at the net layer may get out of
3037   // sync. This catches this case and prevent updating an entry with the wrong
3038   // SSL data.
3039   if (item->GetURL().GetOrigin() != url.GetOrigin())
3040     return;
3042   if (item->GetSSL().Equals(sslStatus))
3043     return;  // No need to update with the same data.
3045   item->GetSSL() = sslStatus;
3047   // Notify the UI it needs to refresh if the updated entry is the current
3048   // entry.
3049   if (userInfo == self.currentSessionEntry) {
3050     [self didUpdateSSLStatusForCurrentNavigationItem];
3051   }
3054 - (void)handleResponseHeaders:(net::HttpResponseHeaders*)headers
3055                    requestUrl:(const GURL&)requestUrl {
3056   _webStateImpl->OnHttpResponseHeadersReceived(headers, requestUrl);
3059 - (void)presentSSLError:(const net::SSLInfo&)info
3060            forSSLStatus:(const web::SSLStatus&)status
3061                   onUrl:(const GURL&)url
3062             recoverable:(BOOL)recoverable
3063                callback:(SSLErrorCallback)shouldContinue {
3064   DCHECK(_delegate);
3065   DCHECK_EQ(url, [self currentNavigationURL]);
3066   [_delegate presentSSLError:info
3067                 forSSLStatus:status
3068                  recoverable:recoverable
3069                     callback:shouldContinue];
3072 - (void)updatedProgress:(float)progress {
3073   if ([_delegate
3074           respondsToSelector:@selector(webController:didUpdateProgress:)]) {
3075     [_delegate webController:self didUpdateProgress:progress];
3076   }
3079 - (void)certificateUsed:(net::X509Certificate*)certificate
3080                 forHost:(const std::string&)host
3081                  status:(net::CertStatus)status {
3082   [[[self sessionController] sessionCertificatePolicyManager]
3083       registerAllowedCertificate:certificate
3084                          forHost:host
3085                           status:status];
3088 - (void)clearCertificates {
3089   [[[self sessionController] sessionCertificatePolicyManager]
3090       clearCertificates];
3093 #pragma mark -
3094 #pragma mark Popup handling
3096 - (void)openPopupWithInfo:(const web::NewWindowInfo&)windowInfo {
3097   const GURL url(windowInfo.url);
3098   const GURL currentURL([self currentNavigationURL]);
3099   NSString* windowName = windowInfo.window_name.get();
3100   web::Referrer referrer(currentURL, windowInfo.referrer_policy);
3101   base::WeakNSObject<CRWWebController> weakSelf(self);
3102   void (^showPopupHandler)() = ^{
3103     CRWWebController* child = [[weakSelf delegate] webPageOrderedOpen:url
3104                                                              referrer:referrer
3105                                                            windowName:windowName
3106                                                          inBackground:NO];
3107     DCHECK(!child || child.sessionController.openedByDOM);
3108   };
3110   BOOL showPopup = windowInfo.user_is_interacting ||
3111                    (![self shouldBlockPopupWithURL:url sourceURL:currentURL]);
3112   if (showPopup) {
3113     showPopupHandler();
3114   } else if ([_delegate
3115                  respondsToSelector:@selector(webController:didBlockPopup:)]) {
3116     web::BlockedPopupInfo blockedPopupInfo(url, referrer, windowName,
3117                                            showPopupHandler);
3118     [_delegate webController:self didBlockPopup:blockedPopupInfo];
3119   }
3122 #pragma mark -
3123 #pragma mark TouchTracking
3125 - (void)touched:(BOOL)touched {
3126   _clickInProgress = touched;
3127   if (touched) {
3128     _userInteractionRegistered = YES;
3129     if (_isBeingDestroyed)
3130       return;
3131     const web::NavigationManagerImpl& navigationManager =
3132         self.webStateImpl->GetNavigationManagerImpl();
3133     GURL mainDocumentURL =
3134         navigationManager.GetEntryCount()
3135             ? navigationManager.GetLastCommittedItem()->GetURL()
3136             : [self currentURL];
3137     _lastUserInteraction.reset(new web::UserInteractionEvent(mainDocumentURL));
3138   }
3141 - (CRWTouchTrackingRecognizer*)touchTrackingRecognizer {
3142   if (!_touchTrackingRecognizer) {
3143     _touchTrackingRecognizer.reset(
3144         [[CRWTouchTrackingRecognizer alloc] initWithDelegate:self]);
3145   }
3146   return _touchTrackingRecognizer.get();
3149 - (BOOL)userIsInteracting {
3150   // If page transfer started after last click, user is deemed to be no longer
3151   // interacting.
3152   if (!_lastUserInteraction ||
3153       _lastTransferTimeInSeconds > _lastUserInteraction->time) {
3154     return NO;
3155   }
3156   return [self userClickedRecently];
3159 - (BOOL)userClickedRecently {
3160   if (!_lastUserInteraction)
3161     return NO;
3162   return _clickInProgress ||
3163          ((CFAbsoluteTimeGetCurrent() - _lastUserInteraction->time) <
3164           kMaximumDelayForUserInteractionInSeconds);
3167 #pragma mark Placeholder Overlay Methods
3169 - (void)addPlaceholderOverlay {
3170   if (!_overlayPreviewMode) {
3171     // Create |kSnapshotOverlayDelay| second timer to remove image with
3172     // transition.
3173     [self performSelector:@selector(removePlaceholderOverlay)
3174                withObject:nil
3175                afterDelay:kSnapshotOverlayDelay];
3176   }
3178   // Add overlay image.
3179   _placeholderOverlayView.reset([[UIImageView alloc] init]);
3180   CGRect frame = [self visibleFrame];
3181   [_placeholderOverlayView setFrame:frame];
3182   [_placeholderOverlayView
3183       setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
3184                           UIViewAutoresizingFlexibleHeight];
3185   [_placeholderOverlayView setContentMode:UIViewContentModeScaleAspectFill];
3186   [_containerView addSubview:_placeholderOverlayView];
3188   id callback = ^(UIImage* image) {
3189     [_placeholderOverlayView setImage:image];
3190   };
3191   [_delegate webController:self retrievePlaceholderOverlayImage:callback];
3193   if (!_placeholderOverlayView.get().image) {
3194     // TODO(justincohen): This is just a blank white image. Consider fading in
3195     // the snapshot when it comes in instead.
3196     // TODO(shreyasv): This is just a blank white image. Consider adding an API
3197     // so that the delegate can return something immediately for the default
3198     // overlay image.
3199     _placeholderOverlayView.get().image = [[self class] defaultSnapshotImage];
3200   }
3203 - (void)removePlaceholderOverlay {
3204   if (!_placeholderOverlayView || _overlayPreviewMode)
3205     return;
3207   [NSObject cancelPreviousPerformRequestsWithTarget:self
3208                                            selector:@selector(removeOverlay)
3209                                              object:nil];
3210   // Remove overlay with transition.
3211   [UIView animateWithDuration:kSnapshotOverlayTransition
3212       animations:^{
3213         [_placeholderOverlayView setAlpha:0.0f];
3214       }
3215       completion:^(BOOL finished) {
3216         [_placeholderOverlayView removeFromSuperview];
3217         _placeholderOverlayView.reset();
3218       }];
3221 - (void)setOverlayPreviewMode:(BOOL)overlayPreviewMode {
3222   _overlayPreviewMode = overlayPreviewMode;
3224   // If we were showing the preview, remove it.
3225   if (!_overlayPreviewMode && _placeholderOverlayView) {
3226     _containerView.reset();
3227     // Reset |_placeholderOverlayView| directly instead of calling
3228     // -removePlaceholderOverlay, which removes |_placeholderOverlayView| in an
3229     // animation.
3230     [_placeholderOverlayView removeFromSuperview];
3231     _placeholderOverlayView.reset();
3232     // There are cases when resetting the contentView, above, may happen after
3233     // the web view has been created. Re-add it here, rather than
3234     // relying on a subsequent call to loadCurrentURLInWebView.
3235     if (self.webView) {
3236       [[self view] addSubview:self.webView];
3237     }
3238   }
3241 - (void)internalSuppressDialogs:(BOOL)suppressFlag notify:(BOOL)notifyFlag {
3242   NSString* const kSetSuppressDialogs =
3243       [NSString stringWithFormat:@"__gCrWeb.setSuppressDialogs(%d, %d);",
3244                                  suppressFlag, notifyFlag];
3245   [self setSuppressDialogsWithHelperScript:kSetSuppressDialogs];
3248 - (void)setPageDialogOpenPolicy:(web::PageDialogOpenPolicy)policy {
3249   switch (policy) {
3250     case web::DIALOG_POLICY_ALLOW:
3251       [self setSuppressDialogs:NO notify:NO];
3252       return;
3253     case web::DIALOG_POLICY_NOTIFY_FIRST:
3254       [self setSuppressDialogs:NO notify:YES];
3255       return;
3256     case web::DIALOG_POLICY_SUPPRESS:
3257       [self setSuppressDialogs:YES notify:YES];
3258       return;
3259   }
3260   NOTREACHED();
3263 - (void)setSuppressDialogs:(BOOL)suppressFlag notify:(BOOL)notifyFlag {
3264   if (self.webView && [_earlyScriptManager hasBeenInjected]) {
3265     [self internalSuppressDialogs:suppressFlag notify:notifyFlag];
3266   } else {
3267     _setSuppressDialogsLater = suppressFlag;
3268     _setNotifyAboutDialogsLater = notifyFlag;
3269   }
3272 #pragma mark -
3273 #pragma mark Session Information
3275 - (CRWSessionController*)sessionController {
3276   return _webStateImpl
3277       ? _webStateImpl->GetNavigationManagerImpl().GetSessionController()
3278       : nil;
3281 - (CRWSessionEntry*)currentSessionEntry {
3282   return [[self sessionController] currentEntry];
3285 - (web::NavigationItem*)currentNavItem {
3286   // This goes through the legacy Session* interface rather than Navigation*
3287   // because it is itself a legacy method that should not exist, and this
3288   // avoids needing to add a GetActiveItem to NavigationManager. If/when this
3289   // method chain becomes a blocker to eliminating SessionController, the logic
3290   // can be moved here, using public NavigationManager getters. That's not
3291   // done now in order to avoid code duplication.
3292   return [[self currentSessionEntry] navigationItem];
3295 - (const GURL&)currentNavigationURL {
3296   // TODO(stuartmorgan): Fix the fact that this method doesn't have clear usage
3297   // delination that would allow changing to one of the non-deprecated URL
3298   // calls.
3299   web::NavigationItem* item = [self currentNavItem];
3300   return item ? item->GetVirtualURL() : GURL::EmptyGURL();
3303 - (ui::PageTransition)currentTransition {
3304   if ([self currentNavItem])
3305     return [self currentNavItem]->GetTransitionType();
3306   else
3307     return ui::PageTransitionFromInt(0);
3310 - (web::Referrer)currentSessionEntryReferrer {
3311   web::NavigationItem* currentItem = [self currentNavItem];
3312   return currentItem ? currentItem->GetReferrer() : web::Referrer();
3315 - (NSData*)currentPOSTData {
3316   DCHECK([self currentSessionEntry]);
3317   return [self currentSessionEntry].navigationItemImpl->GetPostData();
3320 - (NSDictionary*)currentHttpHeaders {
3321   DCHECK([self currentSessionEntry]);
3322   return [self currentSessionEntry].navigationItem->GetHttpRequestHeaders();
3325 #pragma mark -
3326 #pragma mark CRWWebViewScrollViewProxyObserver
3328 - (void)webViewScrollViewDidZoom:
3329         (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
3330   _pageHasZoomed = YES;
3333 #pragma mark -
3334 #pragma mark Page State
3336 - (void)recordStateInHistory {
3337   // Check that the url in the web view matches the url in the history entry.
3338   CRWSessionEntry* current = [self currentSessionEntry];
3339   if (current && [current navigationItem]->GetURL() == [self currentURL])
3340     [current navigationItem]->SetPageDisplayState(self.pageDisplayState);
3343 - (void)restoreStateFromHistory {
3344   CRWSessionEntry* current = [self currentSessionEntry];
3345   if ([current navigationItem])
3346     self.pageDisplayState = [current navigationItem]->GetPageDisplayState();
3349 - (web::PageDisplayState)pageDisplayState {
3350   web::PageDisplayState displayState;
3351   if (self.webView) {
3352     CGPoint scrollOffset = [self scrollPosition];
3353     displayState.scroll_state().set_offset_x(std::floor(scrollOffset.x));
3354     displayState.scroll_state().set_offset_y(std::floor(scrollOffset.y));
3355     UIScrollView* scrollView = self.webScrollView;
3356     displayState.zoom_state().set_minimum_zoom_scale(
3357         scrollView.minimumZoomScale);
3358     displayState.zoom_state().set_maximum_zoom_scale(
3359         scrollView.maximumZoomScale);
3360     displayState.zoom_state().set_zoom_scale(scrollView.zoomScale);
3361   } else {
3362     // TODO(kkhorimoto): Handle native views.
3363   }
3364   return displayState;
3367 - (void)setPageDisplayState:(web::PageDisplayState)displayState {
3368   if (!displayState.IsValid())
3369     return;
3370   if (self.webView) {
3371     // Page state is restored after a page load completes.  If the user has
3372     // scrolled or changed the zoom scale while the page is still loading, don't
3373     // restore any state since it will confuse the user.
3374     web::PageDisplayState currentPageDisplayState = self.pageDisplayState;
3375     if (currentPageDisplayState.scroll_state().offset_x() ==
3376             _displayStateOnStartLoading.scroll_state().offset_x() &&
3377         currentPageDisplayState.scroll_state().offset_y() ==
3378             _displayStateOnStartLoading.scroll_state().offset_y() &&
3379         !_pageHasZoomed) {
3380       [self applyPageDisplayState:displayState];
3381     }
3382   }
3385 - (void)orientationDidChange {
3386   // When rotating, the available zoom scale range may change, zoomScale's
3387   // percentage into this range should remain constant.  However, there are
3388   // two known bugs with respect to adjusting the zoomScale on rotation:
3389   // - WKWebView sometimes erroneously resets the scroll view's zoom scale to
3390   // an incorrect value ( rdar://20100815 ).
3391   // - After zooming occurs in a UIWebView that's displaying a page with a hard-
3392   // coded viewport width, the zoom will not be updated upon rotation
3393   // ( crbug.com/485055 ).
3394   if (!self.webView)
3395     return;
3396   web::NavigationItem* currentItem = self.currentNavItem;
3397   if (!currentItem)
3398     return;
3399   web::PageDisplayState displayState = currentItem->GetPageDisplayState();
3400   if (!displayState.IsValid())
3401     return;
3402   CGFloat zoomPercentage = (displayState.zoom_state().zoom_scale() -
3403                             displayState.zoom_state().minimum_zoom_scale()) /
3404                            displayState.zoom_state().GetMinMaxZoomDifference();
3405   displayState.zoom_state().set_minimum_zoom_scale(
3406       self.webScrollView.minimumZoomScale);
3407   displayState.zoom_state().set_maximum_zoom_scale(
3408       self.webScrollView.maximumZoomScale);
3409   displayState.zoom_state().set_zoom_scale(
3410       displayState.zoom_state().minimum_zoom_scale() +
3411       zoomPercentage * displayState.zoom_state().GetMinMaxZoomDifference());
3412   currentItem->SetPageDisplayState(displayState);
3413   [self applyPageDisplayState:currentItem->GetPageDisplayState()];
3416 - (void)applyPageDisplayState:(const web::PageDisplayState&)displayState {
3417   if (!displayState.IsValid())
3418     return;
3419   base::WeakNSObject<CRWWebController> weakSelf(self);
3420   web::PageDisplayState displayStateCopy = displayState;
3421   [self queryUserScalableProperty:^(BOOL isUserScalable) {
3422     base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
3423     [strongSelf applyPageDisplayState:displayStateCopy
3424                          userScalable:isUserScalable];
3425   }];
3428 - (void)applyPageDisplayState:(const web::PageDisplayState&)displayState
3429                  userScalable:(BOOL)isUserScalable {
3430   // Early return if |scrollState| doesn't match the current NavigationItem.
3431   // This can sometimes occur in tests, as navigation occurs programmatically
3432   // and |-applyPageScrollState:| is asynchronous.
3433   web::NavigationItem* currentItem = [self currentSessionEntry].navigationItem;
3434   if (currentItem && currentItem->GetPageDisplayState() != displayState)
3435     return;
3436   DCHECK(displayState.IsValid());
3437   if (isUserScalable) {
3438     [self prepareToApplyWebViewScrollZoomScale];
3439     [self applyWebViewScrollZoomScaleFromZoomState:displayState.zoom_state()];
3440     [self finishApplyingWebViewScrollZoomScale];
3441   }
3442   [self applyWebViewScrollOffsetFromScrollState:displayState.scroll_state()];
3445 - (void)prepareToApplyWebViewScrollZoomScale {
3446   id webView = self.webView;
3447   if (![webView respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
3448     return;
3449   }
3451   UIView* contentView = [webView viewForZoomingInScrollView:self.webScrollView];
3453   if ([webView
3454           respondsToSelector:@selector(scrollViewWillBeginZooming:withView:)]) {
3455     [webView scrollViewWillBeginZooming:self.webScrollView
3456                                withView:contentView];
3457   }
3460 - (void)finishApplyingWebViewScrollZoomScale {
3461   id webView = self.webView;
3462   if ([webView respondsToSelector:@selector(scrollViewDidEndZooming:
3463                                                            withView:
3464                                                             atScale:)] &&
3465       [webView respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
3466     // This correctly sets the content's frame in the scroll view to
3467     // fit the web page and upscales the content so that it isn't
3468     // blurry.
3469     UIView* contentView =
3470         [webView viewForZoomingInScrollView:self.webScrollView];
3471     [webView scrollViewDidEndZooming:self.webScrollView
3472                             withView:contentView
3473                              atScale:self.webScrollView.zoomScale];
3474   }
3477 - (void)applyWebViewScrollZoomScaleFromZoomState:
3478     (const web::PageZoomState&)zoomState {
3479   // Subclasses must implement this method.
3480   NOTREACHED();
3483 - (void)applyWebViewScrollOffsetFromScrollState:
3484     (const web::PageScrollState&)scrollState {
3485   DCHECK(scrollState.IsValid());
3486   CGPoint scrollOffset =
3487       CGPointMake(scrollState.offset_x(), scrollState.offset_y());
3488   if (_loadPhase == web::PAGE_LOADED) {
3489     // If the page is loaded, update the scroll immediately.
3490     [self.webScrollView setContentOffset:scrollOffset];
3491   } else {
3492     // If the page isn't loaded, store the action to update the scroll
3493     // when the page finishes loading.
3494     base::WeakNSObject<UIScrollView> weakScrollView(self.webScrollView);
3495     base::scoped_nsprotocol<ProceduralBlock> action([^{
3496       [weakScrollView setContentOffset:scrollOffset];
3497     } copy]);
3498     [_pendingLoadCompleteActions addObject:action];
3499   }
3502 #pragma mark -
3503 #pragma mark Web Page Features
3505 // TODO(eugenebut): move JS parsing code to a separate file.
3506 - (void)queryUserScalableProperty:(void (^)(BOOL))responseHandler {
3507   NSString* const kViewPortContentQuery =
3508       @"var viewport = document.querySelector('meta[name=\"viewport\"]');"
3509        "viewport ? viewport.content : '';";
3510   [self evaluateJavaScript:kViewPortContentQuery
3511        stringResultHandler:^(NSString* viewPortContent, NSError* error) {
3512          responseHandler(
3513              GetUserScalablePropertyFromViewPortContent(viewPortContent));
3514        }];
3517 - (void)fetchWebPageWidthWithCompletionHandler:(void (^)(CGFloat))handler {
3518   if (!self.webView) {
3519     handler(0);
3520     return;
3521   }
3523   [self evaluateJavaScript:@"__gCrWeb.getPageWidth();"
3524        stringResultHandler:^(NSString* pageWidthAsString, NSError*) {
3525          handler([pageWidthAsString floatValue]);
3526        }];
3529 - (void)fetchDOMElementAtPoint:(CGPoint)point
3530              completionHandler:
3531                  (void (^)(scoped_ptr<base::DictionaryValue>))handler {
3532   DCHECK(handler);
3533   // Convert point into web page's coordinate system (which may be scaled and/or
3534   // scrolled).
3535   CGPoint scrollOffset = self.scrollPosition;
3536   CGFloat webViewContentWidth = self.webScrollView.contentSize.width;
3537   base::WeakNSObject<CRWWebController> weakSelf(self);
3538   [self fetchWebPageWidthWithCompletionHandler:^(CGFloat pageWidth) {
3539     CGFloat scale = pageWidth / webViewContentWidth;
3540     CGPoint localPoint = CGPointMake((point.x + scrollOffset.x) * scale,
3541                                      (point.y + scrollOffset.y) * scale);
3542     NSString* const kGetElementScript =
3543         [NSString stringWithFormat:@"__gCrWeb.getElementFromPoint(%g, %g);",
3544                                    localPoint.x, localPoint.y];
3545     [weakSelf evaluateJavaScript:kGetElementScript
3546                JSONResultHandler:^(scoped_ptr<base::Value> element, NSError*) {
3547                  // Release raw element and call handler with DictionaryValue.
3548                  scoped_ptr<base::DictionaryValue> elementAsDict;
3549                  if (element) {
3550                    base::DictionaryValue* elementAsDictPtr = nullptr;
3551                    element.release()->GetAsDictionary(&elementAsDictPtr);
3552                    // |rawElement| and |elementPtr| now point to the same
3553                    // memory. |elementPtr| ownership will be transferred to
3554                    // |element| scoped_ptr.
3555                    elementAsDict.reset(elementAsDictPtr);
3556                  }
3557                  handler(elementAsDict.Pass());
3558                }];
3559   }];
3562 - (NSDictionary*)contextMenuInfoForElement:(base::DictionaryValue*)element {
3563   DCHECK(element);
3564   NSMutableDictionary* mutableInfo = [NSMutableDictionary dictionary];
3565   NSString* title = nil;
3566   std::string href;
3567   if (element->GetString("href", &href)) {
3568     mutableInfo[web::kContextLinkURLString] = base::SysUTF8ToNSString(href);
3569     GURL linkURL(href);
3570     if (linkURL.SchemeIs(url::kJavaScriptScheme)) {
3571       title = @"JavaScript";
3572     } else {
3573       DCHECK(web::GetWebClient());
3574       const std::string& acceptLangs = web::GetWebClient()->GetAcceptLangs(
3575           self.webStateImpl->GetBrowserState());
3576       base::string16 urlText = net::FormatUrl(GURL(href), acceptLangs);
3577       title = base::SysUTF16ToNSString(urlText);
3578     }
3579   }
3580   std::string src;
3581   if (element->GetString("src", &src)) {
3582     mutableInfo[web::kContextImageURLString] = base::SysUTF8ToNSString(src);
3583     if (!title)
3584       title = base::SysUTF8ToNSString(src);
3585     if ([title hasPrefix:@"data:"])
3586       title = @"";
3587   }
3588   std::string titleAttribute;
3589   if (element->GetString("title", &titleAttribute))
3590     title = base::SysUTF8ToNSString(titleAttribute);
3591   std::string referrerPolicy;
3592   element->GetString("referrerPolicy", &referrerPolicy);
3593   mutableInfo[web::kContextLinkReferrerPolicy] =
3594       @([self referrerPolicyFromString:referrerPolicy]);
3595   if (title)
3596     mutableInfo[web::kContextTitle] = title;
3597   return [[mutableInfo copy] autorelease];
3600 #pragma mark -
3601 #pragma mark Fullscreen
3603 - (CGRect)visibleFrame {
3604   CGRect frame = [_containerView bounds];
3605   CGFloat headerHeight = [self headerHeight];
3606   frame.origin.y = headerHeight;
3607   frame.size.height -= headerHeight;
3608   return frame;
3611 - (void)optOutScrollsToTopForSubviews {
3612   NSMutableArray* stack =
3613       [NSMutableArray arrayWithArray:[self.webScrollView subviews]];
3614   while (stack.count) {
3615     UIView* current = [stack lastObject];
3616     [stack removeLastObject];
3617     [stack addObjectsFromArray:[current subviews]];
3618     if ([current isKindOfClass:[UIScrollView class]])
3619       static_cast<UIScrollView*>(current).scrollsToTop = NO;
3620   }
3623 #pragma mark -
3624 #pragma mark WebDelegate Calls
3626 - (BOOL)shouldOpenURL:(const GURL&)url
3627       mainDocumentURL:(const GURL&)mainDocumentURL
3628           linkClicked:(BOOL)linkClicked {
3629   if (![_delegate respondsToSelector:@selector(webController:
3630                                                shouldOpenURL:
3631                                              mainDocumentURL:
3632                                                  linkClicked:)]) {
3633     return YES;
3634   }
3635   return [_delegate webController:self
3636                     shouldOpenURL:url
3637                   mainDocumentURL:mainDocumentURL
3638                       linkClicked:linkClicked];
3641 - (BOOL)shouldOpenExternalURLRequest:(NSURLRequest*)request
3642                          targetFrame:(const web::FrameInfo*)targetFrame {
3643   // If targetFrame information is not provided, the request originated from the
3644   // main frame if (a) the request's URL matches the request's main document URL
3645   // or (b) if the current pending entry matches the request's main document
3646   // URL, as this is a redirect from an in-progress main frame load.
3647   BOOL isMainFrame = targetFrame
3648                          ? targetFrame->is_main_frame
3649                          : [request.URL isEqual:request.mainDocumentURL];
3650   if (!targetFrame && !isMainFrame) {
3651     web::NavigationItem* pendingItem =
3652         [self webStateImpl]->GetNavigationManager()->GetPendingItem();
3653     if (pendingItem) {
3654       isMainFrame =
3655           pendingItem->GetURL() == net::GURLWithNSURL(request.mainDocumentURL);
3656     }
3657   }
3659   // If the request's main document URL differs from that at the time of the
3660   // last user interaction, then the page has changed since the user last
3661   // interacted.
3662   BOOL userHasInteractedWithCurrentPage =
3663       _lastUserInteraction &&
3664       net::GURLWithNSURL(request.mainDocumentURL) ==
3665           _lastUserInteraction->main_document_url;
3667   // Prevent subframe requests from opening an external URL if the user has not
3668   // interacted with the page.
3669   if (!isMainFrame && !userHasInteractedWithCurrentPage)
3670     return NO;
3672   GURL requestURL = net::GURLWithNSURL(request.URL);
3673   return [_delegate respondsToSelector:@selector(webController:
3674                                            shouldOpenExternalURL:)] &&
3675          [_delegate webController:self shouldOpenExternalURL:requestURL];
3678 - (BOOL)urlTriggersNativeAppLaunch:(const GURL&)url
3679                          sourceURL:(const GURL&)sourceURL {
3680   return
3681       [_delegate respondsToSelector:@selector(urlTriggersNativeAppLaunch:
3682                                                                sourceURL:)] &&
3683       [_delegate urlTriggersNativeAppLaunch:url sourceURL:sourceURL];
3686 - (CGFloat)headerHeight {
3687   if (![_delegate respondsToSelector:@selector(headerHeightForWebController:)])
3688     return 0.0f;
3689   return [_delegate headerHeightForWebController:self];
3692 - (BOOL)shouldBlockPopupWithURL:(const GURL&)popupURL
3693                       sourceURL:(const GURL&)sourceURL {
3694   if (![_delegate respondsToSelector:@selector(webController:
3695                                          shouldBlockPopupWithURL:
3696                                                        sourceURL:)]) {
3697     return NO;
3698   }
3699   return [_delegate webController:self
3700           shouldBlockPopupWithURL:popupURL
3701                         sourceURL:sourceURL];
3704 - (void)didUpdateHistoryStateWithPageURL:(const GURL&)url {
3705   _webStateImpl->GetRequestTracker()->HistoryStateChange(url);
3706   [_delegate webDidUpdateHistoryStateWithPageURL:url];
3709 - (void)didUpdateSSLStatusForCurrentNavigationItem {
3710   if ([_delegate respondsToSelector:
3711           @selector(
3712               webControllerDidUpdateSSLStatusForCurrentNavigationItem:)]) {
3713     [_delegate webControllerDidUpdateSSLStatusForCurrentNavigationItem:self];
3714   }
3717 #pragma mark CRWWebControllerScripting Methods
3719 - (void)loadHTML:(NSString*)html {
3720   [self loadHTML:html forURL:GURL(url::kAboutBlankURL)];
3723 - (void)loadHTMLForCurrentURL:(NSString*)html {
3724   [self loadHTML:html forURL:self.currentURL];
3727 - (void)loadHTML:(NSString*)html forURL:(const GURL&)url {
3728   // Remove the interstitial before doing anything else.
3729   [self clearInterstitials];
3731   DLOG_IF(WARNING, !self.webView)
3732       << "self.webView null while trying to load HTML";
3733   _loadPhase = web::LOAD_REQUESTED;
3734   [self loadWebHTMLString:html forURL:url];
3737 - (void)loadHTML:(NSString*)HTML forAppSpecificURL:(const GURL&)URL {
3738   CHECK(web::GetWebClient()->IsAppSpecificURL(URL));
3739   [self loadHTML:HTML forURL:URL];
3742 - (void)stopLoading {
3743   web::RecordAction(UserMetricsAction("Stop"));
3744   // Discard the pending and transient entried before notifying the tab model
3745   // observers of the change via |-abortLoad|.
3746   [[self sessionController] discardNonCommittedEntries];
3747   [self abortLoad];
3748   // If discarding the non-committed entries results in an app-specific URL,
3749   // reload it in its native view.
3750   if (!_nativeController &&
3751       [self shouldLoadURLInNativeView:[self currentNavigationURL]]) {
3752     [self loadCurrentURLInNativeView];
3753   }
3756 - (void)orderClose {
3757   if (self.sessionController.openedByDOM) {
3758     [_delegate webPageOrderedClose];
3759   }
3762 #pragma mark -
3763 #pragma mark Testing-Only Methods
3765 - (void)injectWebView:(id)webView {
3766   [self removeWebViewAllowingCachedReconstruction:NO];
3768   _lastRegisteredRequestURL = _defaultURL;
3769   CHECK([webView respondsToSelector:@selector(scrollView)]);
3770   [_webViewProxy setWebView:webView
3771                  scrollView:[static_cast<id>(webView) scrollView]];
3774 - (void)resetInjectedWebView {
3775   [self resetWebView];
3778 - (void)addObserver:(id<CRWWebControllerObserver>)observer {
3779   DCHECK(observer);
3780   if (!_observers) {
3781     // We don't want our observer set to block dealloc on the observers. For the
3782     // observer container, make an object compatible with NSMutableSet that does
3783     // not perform retain or release on the contained objects (weak references).
3784     CFSetCallBacks callbacks =
3785         {0, NULL, NULL, CFCopyDescription, CFEqual, CFHash};
3786     _observers.reset(base::mac::CFToNSCast(
3787         CFSetCreateMutable(kCFAllocatorDefault, 1, &callbacks)));
3788   }
3789   DCHECK(![_observers containsObject:observer]);
3790   [_observers addObject:observer];
3791   _observerBridges.push_back(
3792       new web::WebControllerObserverBridge(observer, self.webStateImpl, self));
3794   if ([observer respondsToSelector:@selector(setWebViewProxy:controller:)])
3795     [observer setWebViewProxy:_webViewProxy controller:self];
3798 - (void)removeObserver:(id<CRWWebControllerObserver>)observer {
3799   // TODO(jimblackler): make _observers use NSMapTable. crbug.com/367992
3800   DCHECK([_observers containsObject:observer]);
3801   [_observers removeObject:observer];
3802   // Remove the associated WebControllerObserverBridge.
3803   auto it = std::find_if(_observerBridges.begin(), _observerBridges.end(),
3804                          [observer](web::WebControllerObserverBridge* bridge) {
3805                            return bridge->web_controller_observer() == observer;
3806                          });
3807   DCHECK(it != _observerBridges.end());
3808   _observerBridges.erase(it);
3811 - (NSUInteger)observerCount {
3812   DCHECK_EQ(_observerBridges.size(), [_observers count]);
3813   return [_observers count];
3816 - (NSString*)windowId {
3817   return [_windowIDJSManager windowId];
3820 - (void)setWindowId:(NSString*)windowId {
3821   return [_windowIDJSManager setWindowId:windowId];
3824 - (NSString*)lastSeenWindowID {
3825   return _lastSeenWindowID;
3828 - (void)setURLOnStartLoading:(const GURL&)url {
3829   _URLOnStartLoading = url;
3832 - (const GURL&)defaultURL {
3833   return _defaultURL;
3836 - (GURL)URLOnStartLoading {
3837   return _URLOnStartLoading;
3840 - (GURL)lastRegisteredRequestURL {
3841   return _lastRegisteredRequestURL;
3844 - (void)simulateLoadRequestWithURL:(const GURL&)URL {
3845   _lastRegisteredRequestURL = URL;
3846   _loadPhase = web::LOAD_REQUESTED;
3849 - (NSString*)externalRequestWindowName {
3850   if (!_externalRequest || !_externalRequest->window_name)
3851     return @"";
3852   return _externalRequest->window_name;
3855 - (void)resetExternalRequest {
3856   _externalRequest.reset();
3859 @end