Fix breakages in https://codereview.chromium.org/1155713003/
[chromium-blink-merge.git] / ios / web / web_state / ui / crw_web_controller.mm
blob2cc2e795d181e20c007abed5e5e52a90ad728bfc
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_native_content.h"
53 #import "ios/web/public/web_state/crw_native_content_provider.h"
54 #import "ios/web/public/web_state/crw_web_controller_observer.h"
55 #import "ios/web/public/web_state/crw_web_view_scroll_view_proxy.h"
56 #import "ios/web/public/web_state/js/crw_js_injection_manager.h"
57 #import "ios/web/public/web_state/js/crw_js_injection_receiver.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 }  // namespace web
113 namespace {
115 // A tag for the web view, so that tests can identify it. This is used instead
116 // of exposing a getter (and deliberately not exposed in the header) to make it
117 // *very* clear that this is a hack which should only be used as a last resort.
118 const NSUInteger kWebViewTag = 0x3eb71e3;
120 // Cancels touch events for the given gesture recognizer.
121 void CancelTouches(UIGestureRecognizer* gesture_recognizer) {
122   if (gesture_recognizer.enabled) {
123     gesture_recognizer.enabled = NO;
124     gesture_recognizer.enabled = YES;
125   }
128 // Cancels all touch events for web view (long presses, tapping, scrolling).
129 void CancelAllTouches(UIScrollView* web_scroll_view) {
130   // Disable web view scrolling.
131   CancelTouches(web_scroll_view.panGestureRecognizer);
133   // All user gestures are handled by a subview of web view scroll view
134   // (UIWebBrowserView for UIWebView and WKContentView for WKWebView).
135   for (UIView* subview in web_scroll_view.subviews) {
136     for (UIGestureRecognizer* recognizer in subview.gestureRecognizers) {
137       CancelTouches(recognizer);
138     }
139   }
142 }  // namespace
144 @interface CRWWebController () <CRWNativeContentDelegate> {
145   base::WeakNSProtocol<id<CRWWebDelegate>> _delegate;
146   base::WeakNSProtocol<id<CRWWebUserInterfaceDelegate>> _UIDelegate;
147   base::WeakNSProtocol<id<CRWNativeContentProvider>> _nativeProvider;
148   base::WeakNSProtocol<id<CRWSwipeRecognizerProvider>> _swipeRecognizerProvider;
149   base::scoped_nsobject<CRWWebControllerContainerView> _containerView;
150   // The CRWWebViewProxy is the wrapper to give components access to the
151   // web view in a controlled and limited way.
152   base::scoped_nsobject<CRWWebViewProxyImpl> _webViewProxy;
153   // If |_contentView| contains a native view rather than a web view, this
154   // is its controller. If it's a web view, this is nil.
155   base::scoped_nsprotocol<id<CRWNativeContent>> _nativeController;
156   BOOL _isHalted;  // YES if halted. Halting happens prior to destruction.
157   BOOL _isBeingDestroyed;  // YES if in the process of closing.
158   // All CRWWebControllerObservers attached to the CRWWebController. A
159   // specially-constructed set is used that does not retain its elements.
160   base::scoped_nsobject<NSMutableSet> _observers;
161   // Each observer in |_observers| is associated with a
162   // WebControllerObserverBridge in order to listen from WebState callbacks.
163   // TODO(droger): Remove |_observerBridges| when all CRWWebControllerObservers
164   // are converted to WebStateObservers.
165   ScopedVector<web::WebControllerObserverBridge> _observerBridges;
166   // |windowId| that is saved when a page changes. Used to detect refreshes.
167   base::scoped_nsobject<NSString> _lastSeenWindowID;
168   // YES if a user interaction has been registered at any time once the page has
169   // loaded.
170   BOOL _userInteractionRegistered;
171   // Last URL change reported to webWill/DidStartLoadingURL. Used to detect page
172   // location changes (client redirects) in practice.
173   GURL _lastRegisteredRequestURL;
174   // Last URL change reported to webDidStartLoadingURL. Used to detect page
175   // location changes in practice.
176   GURL _URLOnStartLoading;
177   // Page loading phase.
178   web::LoadPhase _loadPhase;
179   // The web::PageScrollState recorded when the page starts loading.
180   web::PageScrollState _scrollStateOnStartLoading;
181   // Actions to execute once the page load is complete.
182   base::scoped_nsobject<NSMutableArray> _pendingLoadCompleteActions;
183   // UIGestureRecognizers to add to the web view.
184   base::scoped_nsobject<NSMutableArray> _gestureRecognizers;
185   // Toolbars to add to the web view.
186   base::scoped_nsobject<NSMutableArray> _webViewToolbars;
187   // Flag to say if browsing is enabled.
188   BOOL _webUsageEnabled;
189   // Content view was reset due to low memory. Use the placeholder overlay on
190   // next creation.
191   BOOL _usePlaceholderOverlay;
192   // Overlay view used instead of webView.
193   base::scoped_nsobject<UIImageView> _placeholderOverlayView;
194   // The touch tracking recognizer allowing us to decide if a navigation is
195   // started by the user.
196   base::scoped_nsobject<CRWTouchTrackingRecognizer> _touchTrackingRecognizer;
197   // Long press recognizer that allows showing context menus.
198   base::scoped_nsobject<UILongPressGestureRecognizer> _contextMenuRecognizer;
199   // DOM element information for the point where the user made the last touch.
200   // Can be null if has not been calculated yet. Precalculation is necessary
201   // because retreiving DOM element relies on async API so element info can not
202   // be built on demand. May contain the following keys: "href", "src", "title",
203   // "referrerPolicy". All values are strings. Used for showing context menus.
204   scoped_ptr<base::DictionaryValue> _DOMElementForLastTouch;
205   // Whether a click is in progress.
206   BOOL _clickInProgress;
207   // The time of the last click, measured in seconds since Jan 1 2001.
208   CFAbsoluteTime _lastClickTimeInSeconds;
209   // The time of the last page transfer start, measured in seconds since Jan 1
210   // 2001.
211   CFAbsoluteTime _lastTransferTimeInSeconds;
212   // Default URL (about:blank).
213   GURL _defaultURL;
214   // Show overlay view, don't reload web page.
215   BOOL _overlayPreviewMode;
216   // If |YES|, call setSuppressDialogs when core.js is injected into the web
217   // view.
218   BOOL _setSuppressDialogsLater;
219   // If |YES|, call setSuppressDialogs when core.js is injected into the web
220   // view.
221   BOOL _setNotifyAboutDialogsLater;
222   // The URL of an expected future recreation of the |webView|. Valid
223   // only if the web view was discarded for non-user-visible reasons, such that
224   // if the next load request is for that URL, it should be treated as a
225   // reconstruction that should use cache aggressively.
226   GURL _expectedReconstructionURL;
228   scoped_ptr<web::NewWindowInfo> _externalRequest;
230   // The WebStateImpl instance associated with this CRWWebController.
231   scoped_ptr<WebStateImpl> _webStateImpl;
233   // A set of URLs opened in external applications; stored so that errors
234   // from the web view can be identified as resulting from these events.
235   base::scoped_nsobject<NSMutableSet> _openedApplicationURL;
237   // Object that manages all early script injection into the web view.
238   base::scoped_nsobject<CRWJSEarlyScriptManager> _earlyScriptManager;
240   // Script manager for setting the windowID.
241   base::scoped_nsobject<CRWJSWindowIdManager> _windowIDJSManager;
243   // The receiver of JavaScripts.
244   base::scoped_nsobject<CRWJSInjectionReceiver> _jsInjectionReceiver;
247 // The current page state of the web view. Writing to this property
248 // asynchronously applies the passed value to the current web view.
249 @property(nonatomic, readwrite) web::PageScrollState pageScrollState;
250 // Resets any state that is associated with a specific document object (e.g.,
251 // page interaction tracking).
252 - (void)resetDocumentSpecificState;
253 // Returns YES if the URL looks like it is one CRWWebController can show.
254 + (BOOL)webControllerCanShow:(const GURL&)url;
255 // Clear any interstitials being displayed.
256 - (void)clearInterstitials;
257 // Returns a lazily created CRWTouchTrackingRecognizer.
258 - (CRWTouchTrackingRecognizer*)touchTrackingRecognizer;
259 // Shows placeholder overlay.
260 - (void)addPlaceholderOverlay;
261 // Removes placeholder overlay.
262 - (void)removePlaceholderOverlay;
263 // Returns |YES| if |url| should be loaded in a native view.
264 - (BOOL)shouldLoadURLInNativeView:(const GURL&)url;
265 // Loads the HTML into the page at the given URL.
266 - (void)loadHTML:(NSString*)html forURL:(const GURL&)url;
267 // Loads the current nativeController in a native view. If a web view is
268 // present, removes it and swaps in the native view in its place.
269 - (void)loadNativeViewWithSuccess:(BOOL)loadSuccess;
270 // YES if the navigation to |url| should be treated as a reload.
271 - (BOOL)shouldReload:(const GURL&)destinationURL
272           transition:(ui::PageTransition)transition;
273 // Internal implementation of reload. Reloads without notifying the delegate.
274 // Most callers should use -reload instead.
275 - (void)reloadInternal;
276 // If YES, the page can be closed if the loading of the initial URL requires
277 // it (for example when an external URL is detected). After the initial URL is
278 // loaded, the page is not cancellable anymore.
279 - (BOOL)cancellable;
280 // Called after URL is finished loading and _loadPhase is set to PAGE_LOADED.
281 - (void)didFinishWithURL:(const GURL&)currentURL loadSuccess:(BOOL)loadSuccess;
282 // Informs the native controller if web usage is allowed or not.
283 - (void)setNativeControllerWebUsageEnabled:(BOOL)webUsageEnabled;
284 // Compares the two URLs being navigated between during a history navigation to
285 // determine if a # needs to be appended to endURL to trigger a hashchange
286 // event. If so, also saves the new endURL in the current CRWSessionEntry.
287 - (GURL)updateURLForHistoryNavigationFromURL:(const GURL&)startURL
288                                        toURL:(const GURL&)endURL;
289 // Evaluates the supplied JavaScript in the web view. Calls |handler| with
290 // results of the evaluation (which may be nil if the implementing object has no
291 // way to run the evaluation or the evaluation returns a nil value) or an
292 // NSError if there is an error. The |handler| can be nil.
293 - (void)evaluateJavaScript:(NSString*)script
294          JSONResultHandler:(void (^)(scoped_ptr<base::Value>, NSError*))handler;
295 // Generates the JavaScript string used to update the UIWebView's URL so that it
296 // matches the URL displayed in the omnibox and sets window.history.state to
297 // stateObject. Needed for history.pushState() and history.replaceState().
298 - (NSString*)javascriptToReplaceWebViewURL:(const GURL&)url
299                            stateObjectJSON:(NSString*)stateObject;
300 - (BOOL)isLoaded;
301 // Restores state of the web view's scroll view from |scrollState|.
302 // |isUserScalable| represents the value of user-scalable meta tag.
303 - (void)applyPageScrollState:(const web::PageScrollState&)scrollState
304                 userScalable:(BOOL)isUserScalable;
305 // Calls the zoom-preparation UIScrollViewDelegate callbacks on the web view.
306 // This is called before |-applyWebViewScrollZoomScaleFromScrollState:|.
307 - (void)prepareToApplyWebViewScrollZoomScale;
308 // Calls the zoom-completion UIScrollViewDelegate callbacks on the web view.
309 // This is called after |-applyWebViewScrollZoomScaleFromScrollState:|.
310 - (void)finishApplyingWebViewScrollZoomScale;
311 // Sets scroll offset value for webview scroll view from |scrollState|.
312 - (void)applyWebViewScrollOffsetFromScrollState:
313     (const web::PageScrollState&)scrollState;
314 // Asynchronously determines whether option |user-scalable| is on in the
315 // viewport meta of the current web page.
316 - (void)queryUserScalableProperty:(void (^)(BOOL))responseHandler;
317 // Asynchronously fetches full width of the rendered web page.
318 - (void)fetchWebPageWidthWithCompletionHandler:(void (^)(CGFloat))handler;
319 // Asynchronously fetches information about DOM element for the given point (in
320 // UIView coordinates). |handler| can not be nil. See |_DOMElementForLastTouch|
321 // for element format description.
322 - (void)fetchDOMElementAtPoint:(CGPoint)point
323              completionHandler:
324                  (void (^)(scoped_ptr<base::DictionaryValue>))handler;
325 // Extracts context menu information from the given DOM element.
326 // result keys are defined in crw_context_menu_provider.h.
327 - (NSDictionary*)contextMenuInfoForElement:(base::DictionaryValue*)element;
328 // Sets the value of |_DOMElementForLastTouch|.
329 - (void)setDOMElementForLastTouch:(scoped_ptr<base::DictionaryValue>)element;
330 // Called when the window has determined there was a long-press and context menu
331 // must be shown.
332 - (void)showContextMenu:(UIGestureRecognizer*)gestureRecognizer;
333 // YES if delegate supports showing context menu by responding to
334 // webController:runContextMenu:atPoint:inView: selector.
335 - (BOOL)supportsCustomContextMenu;
336 // Returns the referrer for the current page.
337 - (web::Referrer)currentReferrer;
338 // Presents an error to the user because the CRWWebController cannot verify the
339 // URL of the current page.
340 - (void)presentSpoofingError;
341 // Adds a new CRWSessionEntry with the given URL and state object to the history
342 // stack. A state object is a serialized generic JavaScript object that contains
343 // details of the UI's state for a given CRWSessionEntry/URL.
344 // TODO(stuartmorgan): Move the pushState/replaceState logic into
345 // NavigationManager.
346 - (void)pushStateWithPageURL:(const GURL&)pageUrl
347                  stateObject:(NSString*)stateObject;
348 // Assigns the given URL and state object to the current CRWSessionEntry.
349 - (void)replaceStateWithPageURL:(const GURL&)pageUrl
350                     stateObject:(NSString*)stateObject;
352 // Returns the current entry from the underlying session controller.
353 // TODO(stuartmorgan): Audit all calls to these methods; these are just wrappers
354 // around the same logic as GetActiveEntry, so should probably not be used for
355 // the same reason that GetActiveEntry is deprecated. (E.g., page operations
356 // should generally be dealing with the last commited entry, not a pending
357 // entry).
358 - (CRWSessionEntry*)currentSessionEntry;
359 - (web::NavigationItem*)currentNavItem;
360 // Returns the referrer for currentURL as a string. May return nil.
361 - (web::Referrer)currentSessionEntryReferrer;
362 // The data and HTTP headers associated to the current entry. These are nil
363 // unless the request was a POST.
364 - (NSData*)currentPOSTData;
365 - (NSDictionary*)currentHttpHeaders;
367 // Finds all the scrollviews in the view hierarchy and makes sure they do not
368 // interfere with scroll to top when tapping the statusbar.
369 - (void)optOutScrollsToTopForSubviews;
370 // Tears down the old native controller, and then replaces it with the new one.
371 - (void)setNativeController:(id<CRWNativeContent>)nativeController;
372 // Returns whether |url| should be opened.
373 - (BOOL)shouldOpenURL:(const GURL&)url
374       mainDocumentURL:(const GURL&)mainDocumentURL
375           linkClicked:(BOOL)linkClicked;
376 // Called when |url| needs to be opened in a matching native app.
377 // Returns YES if the url was succesfully opened in the native app.
378 - (BOOL)urlTriggersNativeAppLaunch:(const GURL&)url
379                          sourceURL:(const GURL&)sourceURL;
380 // Returns whether external |url| should be opened.
381 - (BOOL)shouldOpenExternalURL:(const GURL&)url;
382 // Called when a page updates its history stack using pushState or replaceState.
383 - (void)didUpdateHistoryStateWithPageURL:(const GURL&)url;
385 // Handlers for JavaScript messages. |message| contains a JavaScript command and
386 // data relevant to the message, and |context| contains contextual information
387 // about web view state needed for some handlers.
389 // Handles 'addPluginPlaceholders' message.
390 - (BOOL)handleAddPluginPlaceholdersMessage:(base::DictionaryValue*)message
391                                    context:(NSDictionary*)context;
392 // Handles 'chrome.send' message.
393 - (BOOL)handleChromeSendMessage:(base::DictionaryValue*)message
394                         context:(NSDictionary*)context;
395 // Handles 'console' message.
396 - (BOOL)handleConsoleMessage:(base::DictionaryValue*)message
397                      context:(NSDictionary*)context;
398 // Handles 'dialog.suppressed' message.
399 - (BOOL)handleDialogSuppressedMessage:(base::DictionaryValue*)message
400                               context:(NSDictionary*)context;
401 // Handles 'dialog.willShow' message.
402 - (BOOL)handleDialogWillShowMessage:(base::DictionaryValue*)message
403                             context:(NSDictionary*)context;
404 // Handles 'document.favicons' message.
405 - (BOOL)handleDocumentFaviconsMessage:(base::DictionaryValue*)message
406                               context:(NSDictionary*)context;
407 // Handles 'document.submit' message.
408 - (BOOL)handleDocumentSubmitMessage:(base::DictionaryValue*)message
409                             context:(NSDictionary*)context;
410 // Handles 'externalRequest' message.
411 - (BOOL)handleExternalRequestMessage:(base::DictionaryValue*)message
412                              context:(NSDictionary*)context;
413 // Handles 'form.activity' message.
414 - (BOOL)handleFormActivityMessage:(base::DictionaryValue*)message
415                           context:(NSDictionary*)context;
416 // Handles 'form.requestAutocomplete' message.
417 - (BOOL)handleFormRequestAutocompleteMessage:(base::DictionaryValue*)message
418                                      context:(NSDictionary*)context;
419 // Handles 'navigator.credentials.request' message.
420 - (BOOL)handleCredentialsRequestedMessage:(base::DictionaryValue*)message
421                                   context:(NSDictionary*)context;
422 // Handles 'navigator.credentials.notifySignedIn' message.
423 - (BOOL)handleSignedInMessage:(base::DictionaryValue*)message
424                       context:(NSDictionary*)context;
425 // Handles 'navigator.credentials.notifySignedOut' message.
426 - (BOOL)handleSignedOutMessage:(base::DictionaryValue*)message
427                        context:(NSDictionary*)context;
428 // Handles 'navigator.credentials.notifyFailedSignIn' message.
429 - (BOOL)handleSignInFailedMessage:(base::DictionaryValue*)message
430                           context:(NSDictionary*)context;
431 // Handles 'resetExternalRequest' message.
432 - (BOOL)handleResetExternalRequestMessage:(base::DictionaryValue*)message
433                                   context:(NSDictionary*)context;
434 // Handles 'window.close.self' message.
435 - (BOOL)handleWindowCloseSelfMessage:(base::DictionaryValue*)message
436                              context:(NSDictionary*)context;
437 // Handles 'window.error' message.
438 - (BOOL)handleWindowErrorMessage:(base::DictionaryValue*)message
439                          context:(NSDictionary*)context;
440 // Handles 'window.hashchange' message.
441 - (BOOL)handleWindowHashChangeMessage:(base::DictionaryValue*)message
442                               context:(NSDictionary*)context;
443 // Handles 'window.history.back' message.
444 - (BOOL)handleWindowHistoryBackMessage:(base::DictionaryValue*)message
445                                context:(NSDictionary*)context;
446 // Handles 'window.history.forward' message.
447 - (BOOL)handleWindowHistoryForwardMessage:(base::DictionaryValue*)message
448                                   context:(NSDictionary*)context;
449 // Handles 'window.history.go' message.
450 - (BOOL)handleWindowHistoryGoMessage:(base::DictionaryValue*)message
451                              context:(NSDictionary*)context;
452 @end
454 namespace {
456 NSString* const kReferrerHeaderName = @"Referer";  // [sic]
458 // Full screen experimental setting.
460 // The long press detection duration must be shorter than the UIWebView's
461 // long click gesture recognizer's minimum duration. That is 0.55s.
462 // If our detection duration is shorter, our gesture recognizer will fire
463 // first, and if it fails the long click gesture (processed simultaneously)
464 // still is able to complete.
465 const NSTimeInterval kLongPressDurationSeconds = 0.55 - 0.1;
466 const CGFloat kLongPressMoveDeltaPixels = 10.0;
468 // The duration of the period following a screen touch during which the user is
469 // still considered to be interacting with the page.
470 const NSTimeInterval kMaximumDelayForUserInteractionInSeconds = 2;
472 // Define missing symbols from WebKit.
473 // See WebKitErrors.h on Mac SDK.
474 NSString* const WebKitErrorDomain = @"WebKitErrorDomain";
476 enum {
477   WebKitErrorCannotShowMIMEType = 100,
478   WebKitErrorCannotShowURL = 101,
479   WebKitErrorFrameLoadInterruptedByPolicyChange = 102,
480   // iOS-specific WebKit error that isn't documented but seen on 4.0
481   // devices.
482   WebKitErrorPlugInLoadFailed = 204,
485 // Tag for the interstitial view so we can find it and dismiss it later.
486 enum {
487   kInterstitialViewTag = 1000,
490 // URLs that are fed into UIWebView as history push/replace get escaped,
491 // potentially changing their format. Code that attempts to determine whether a
492 // URL hasn't changed can be confused by those differences though, so method
493 // will round-trip a URL through the escaping process so that it can be adjusted
494 // pre-storing, to allow later comparisons to work as expected.
495 GURL URLEscapedForHistory(const GURL& url) {
496   // TODO(stuartmorgan): This is a very large hammer; see if limited unicode
497   // escaping would be sufficient.
498   return net::GURLWithNSURL(net::NSURLWithGURL(url));
501 // Parses a viewport tag content and returns the value of an attribute with
502 // the given |name|, or nil if the attribute is not present in the tag.
503 NSString* GetAttributeValueFromViewPortContent(NSString* attributeName,
504                                                NSString* viewPortContent) {
505   NSArray* contentItems = [viewPortContent componentsSeparatedByString:@","];
506   for (NSString* item in contentItems) {
507     NSArray* components = [item componentsSeparatedByString:@"="];
508     if ([components count] == 2) {
509       NSCharacterSet* spaceAndNewline =
510           [NSCharacterSet whitespaceAndNewlineCharacterSet];
511       NSString* currentAttributeName =
512           [components[0] stringByTrimmingCharactersInSet:spaceAndNewline];
513       if ([currentAttributeName isEqualToString:attributeName]) {
514         return [components[1] stringByTrimmingCharactersInSet:spaceAndNewline];
515       }
516     }
517   }
518   return nil;
521 // Parses a viewport tag content and returns the value of the user-scalable
522 // attribute or nil.
523 BOOL GetUserScalablePropertyFromViewPortContent(NSString* viewPortContent) {
524   NSString* value =
525       GetAttributeValueFromViewPortContent(@"user-scalable", viewPortContent);
526   if (!value) {
527     return YES;
528   }
529   return !([value isEqualToString:@"0"] ||
530            [value caseInsensitiveCompare:@"no"] == NSOrderedSame);
533 // Leave snapshot overlay up unless page loads.
534 const NSTimeInterval kSnapshotOverlayDelay = 1.5;
535 // Transition to fade snapshot overlay.
536 const NSTimeInterval kSnapshotOverlayTransition = 0.5;
538 }  // namespace
540 @implementation CRWWebController
542 @synthesize webUsageEnabled = _webUsageEnabled;
543 @synthesize usePlaceholderOverlay = _usePlaceholderOverlay;
544 @synthesize loadPhase = _loadPhase;
546 // Implemented by subclasses.
547 @dynamic keyboardDisplayRequiresUserAction;
549 + (instancetype)allocWithZone:(struct _NSZone*)zone {
550   if (self == [CRWWebController class]) {
551     // This is an abstract class which should not be instantiated directly.
552     // Callers should create concrete subclasses instead.
553     NOTREACHED();
554     return nil;
555   }
556   return [super allocWithZone:zone];
559 - (instancetype)initWithWebState:(scoped_ptr<WebStateImpl>)webState {
560   self = [super init];
561   if (self) {
562     _webStateImpl = webState.Pass();
563     DCHECK(_webStateImpl);
564     _webStateImpl->SetWebController(self);
565     _webStateImpl->InitializeRequestTracker(self);
566     // Load phase when no WebView present is 'loaded' because this represents
567     // the idle state.
568     _loadPhase = web::PAGE_LOADED;
569     // Content area is lazily instantiated.
570     _defaultURL = GURL(url::kAboutBlankURL);
571     _jsInjectionReceiver.reset(
572         [[CRWJSInjectionReceiver alloc] initWithEvaluator:self]);
573     _earlyScriptManager.reset([(CRWJSEarlyScriptManager*)[_jsInjectionReceiver
574         instanceOfClass:[CRWJSEarlyScriptManager class]] retain]);
575     _windowIDJSManager.reset([(CRWJSWindowIdManager*)[_jsInjectionReceiver
576         instanceOfClass:[CRWJSWindowIdManager class]] retain]);
577     _lastSeenWindowID.reset();
578     _webViewProxy.reset(
579         [[CRWWebViewProxyImpl alloc] initWithWebController:self]);
580     _gestureRecognizers.reset([[NSMutableArray alloc] init]);
581     _webViewToolbars.reset([[NSMutableArray alloc] init]);
582     _pendingLoadCompleteActions.reset([[NSMutableArray alloc] init]);
583   }
584   return self;
587 - (id<CRWNativeContentProvider>)nativeProvider {
588   return _nativeProvider.get();
591 - (void)setNativeProvider:(id<CRWNativeContentProvider>)nativeProvider {
592   _nativeProvider.reset(nativeProvider);
595 - (id<CRWSwipeRecognizerProvider>)swipeRecognizerProvider {
596   return _swipeRecognizerProvider.get();
599 - (void)setSwipeRecognizerProvider:
600     (id<CRWSwipeRecognizerProvider>)swipeRecognizerProvider {
601   _swipeRecognizerProvider.reset(swipeRecognizerProvider);
604 - (WebState*)webState {
605   return _webStateImpl.get();
608 - (WebStateImpl*)webStateImpl {
609   return _webStateImpl.get();
612 // WebStateImpl will delete the interstitial page object, which will in turn
613 // remove its view from |_contentView|.
614 - (void)clearInterstitials {
615   [_webViewProxy setWebView:self.webView scrollView:self.webScrollView];
616   if (_webStateImpl)
617     _webStateImpl->ClearWebInterstitialForNavigation();
620 // Attaches |interstitialView| to |_contentView|.  Note that this class never
621 // explicitly removes the interstitial from |_contentView|;
622 // web::WebStateImpl::DismissWebInterstitial() takes care of that.
623 - (void)displayInterstitialView:(UIView*)interstitialView
624                  withScrollView:(UIScrollView*)scrollView {
625   DCHECK(interstitialView);
626   DCHECK(scrollView);
627   [_webViewProxy setWebView:interstitialView scrollView:scrollView];
628   interstitialView.tag = kInterstitialViewTag;
629   [_containerView addSubview:interstitialView];
632 - (id<CRWWebDelegate>)delegate {
633   return _delegate.get();
636 - (void)setDelegate:(id<CRWWebDelegate>)delegate {
637   _delegate.reset(delegate);
638   if ([_nativeController respondsToSelector:@selector(setDelegate:)]) {
639     if ([_delegate respondsToSelector:@selector(webController:titleDidChange:)])
640       [_nativeController setDelegate:self];
641     else
642       [_nativeController setDelegate:nil];
643   }
646 - (id<CRWWebUserInterfaceDelegate>)UIDelegate {
647   return _UIDelegate.get();
650 - (void)setUIDelegate:(id<CRWWebUserInterfaceDelegate>)UIDelegate {
651   _UIDelegate.reset(UIDelegate);
654 - (NSString*)scriptByAddingWindowIDCheckForScript:(NSString*)script {
655   NSString* kTemplate = @"if (__gCrWeb['windowId'] === '%@') { %@; }";
656   return [NSString stringWithFormat:kTemplate, [self windowId], script];
659 - (void)removeWebViewAllowingCachedReconstruction:(BOOL)allowCache {
660   if (!self.webView)
661     return;
663   if (allowCache)
664     _expectedReconstructionURL = [self currentNavigationURL];
665   else
666     _expectedReconstructionURL = GURL();
668   [self abortLoad];
669   [self.webView removeFromSuperview];
670   [_webViewProxy setWebView:nil scrollView:nil];
671   [self resetWebView];
672   // Remove the web toolbars.
673   [_containerView removeAllToolbars];
676 - (void)dealloc {
677   DCHECK([NSThread isMainThread]);
678   DCHECK(_isBeingDestroyed);  // 'close' must have been called already.
679   DCHECK(!self.webView);
680   _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
681   [super dealloc];
684 - (BOOL)runUnloadListenerBeforeClosing {
685   // There's not much that can be done since there's limited access to WebKit.
686   // Always return that it's ok to close immediately.
687   return YES;
690 - (void)dismissKeyboard {
691   [self.webView endEditing:YES];
692   if ([_nativeController respondsToSelector:@selector(dismissKeyboard)])
693     [_nativeController dismissKeyboard];
696 - (id<CRWNativeContent>)nativeController {
697   return _nativeController.get();
700 - (void)setNativeController:(id<CRWNativeContent>)nativeController {
701   // Check for pointer equality.
702   if (_nativeController.get() == nativeController)
703     return;
705   // Unset the delegate on the previous instance.
706   if ([_nativeController respondsToSelector:@selector(setDelegate:)])
707     [_nativeController setDelegate:nil];
709   _nativeController.reset([nativeController retain]);
710   [self setNativeControllerWebUsageEnabled:_webUsageEnabled];
713 // NativeControllerDelegate method, called to inform that title has changed.
714 - (void)nativeContent:(id)content titleDidChange:(NSString*)title {
715   // Responsiveness to delegate method was checked in setDelegate:.
716   [_delegate webController:self titleDidChange:title];
719 - (void)setNativeControllerWebUsageEnabled:(BOOL)webUsageEnabled {
720   if ([_nativeController respondsToSelector:@selector(setWebUsageEnabled:)]) {
721     [_nativeController setWebUsageEnabled:webUsageEnabled];
722   }
725 - (void)setWebUsageEnabled:(BOOL)enabled {
726   if (_webUsageEnabled == enabled)
727     return;
728   _webUsageEnabled = enabled;
730   // WKWebView autoreleases its WKProcessPool on removal from superview.
731   // Deferring WKProcessPool deallocation may lead to issues with cookie
732   // clearing and and Browsing Data Partitioning implementation.
733   @autoreleasepool {
734     [self setNativeControllerWebUsageEnabled:_webUsageEnabled];
735     if (enabled) {
736       // Don't create the web view; let it be lazy created as needed.
737     } else {
738       [self clearInterstitials];
739       [self removeWebViewAllowingCachedReconstruction:YES];
740       _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
741       _touchTrackingRecognizer.reset();
742       _containerView.reset();
743     }
744   }
747 - (void)requirePageReconstruction {
748   [self removeWebViewAllowingCachedReconstruction:NO];
751 - (void)handleLowMemory {
752   [self removeWebViewAllowingCachedReconstruction:YES];
753   [self setNativeController:nil];
754   _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
755   _touchTrackingRecognizer.reset();
756   _containerView.reset();
757   _usePlaceholderOverlay = YES;
760 - (void)reinitializeWebViewAndReload:(BOOL)reload {
761   if (self.webView) {
762     [self removeWebViewAllowingCachedReconstruction:NO];
763     if (reload) {
764       [self loadCurrentURLInWebView];
765     } else {
766       // Clear the space for the web view to lazy load when needed.
767       _usePlaceholderOverlay = YES;
768       _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
769       _touchTrackingRecognizer.reset();
770       _containerView.reset();
771     }
772   }
775 - (void)childWindowClosed:(NSString*)windowName {
776   // Subclasses can override this method to be informed about a closed window.
779 - (BOOL)isViewAlive {
780   return self.webView || [_nativeController isViewAlive];
783 - (BOOL)contentIsHTML {
784   return [self webViewDocumentType] == web::WEB_VIEW_DOCUMENT_TYPE_HTML;
787 // Stop doing stuff, especially network stuff. Close the request tracker.
788 - (void)terminateNetworkActivity {
789   DCHECK(!_isHalted);
790   _isHalted = YES;
792   // Cancel all outstanding perform requests, and clear anything already queued
793   // (since this may be called from within the handling loop) to prevent any
794   // asynchronous JavaScript invocation handling from continuing.
795   [NSObject cancelPreviousPerformRequestsWithTarget:self];
796   _webStateImpl->CloseRequestTracker();
799 - (void)dismissModals {
800   if ([_nativeController respondsToSelector:@selector(dismissModals)])
801     [_nativeController dismissModals];
804 // Caller must reset the delegate before calling.
805 - (void)close {
806   self.nativeProvider = nil;
807   self.swipeRecognizerProvider = nil;
808   if ([_nativeController respondsToSelector:@selector(close)])
809     [_nativeController close];
811   base::scoped_nsobject<NSSet> observers([_observers copy]);
812   for (id it in observers.get()) {
813     if ([it respondsToSelector:@selector(webControllerWillClose:)])
814       [it webControllerWillClose:self];
815   }
817   if (!_isHalted) {
818     [self terminateNetworkActivity];
819   }
821   DCHECK(!_isBeingDestroyed);
822   DCHECK(!_delegate);  // Delegate should reset its association before closing.
823   // Mark the destruction sequence has started, in case someone else holds a
824   // strong reference and tries to continue using the tab.
825   _isBeingDestroyed = YES;
827   // Remove the web view now. Otherwise, delegate callbacks occur.
828   [self removeWebViewAllowingCachedReconstruction:NO];
830   // Tear down web ui (in case this is part of this tab) and web state now,
831   // since the timing of dealloc can't be guaranteed.
832   _webStateImpl.reset();
835 - (void)checkLinkPresenceUnderGesture:(UIGestureRecognizer*)gestureRecognizer
836                     completionHandler:(void (^)(BOOL))completionHandler {
837   CGPoint webViewPoint = [gestureRecognizer locationInView:self.webView];
838   base::WeakNSObject<CRWWebController> weakSelf(self);
839   [self fetchDOMElementAtPoint:webViewPoint
840              completionHandler:^(scoped_ptr<base::DictionaryValue> element) {
841                std::string link;
842                BOOL hasLink =
843                    element && element->GetString("href", &link) && link.size();
844                completionHandler(hasLink);
845              }];
848 - (void)setDOMElementForLastTouch:(scoped_ptr<base::DictionaryValue>)element {
849   _DOMElementForLastTouch = element.Pass();
852 - (void)showContextMenu:(UIGestureRecognizer*)gestureRecognizer {
853   // Calling this method if [self supportsCustomContextMenu] returned NO
854   // is a programmer error.
855   DCHECK([self supportsCustomContextMenu]);
857   // We don't want ongoing notification that the long press is held.
858   if ([gestureRecognizer state] != UIGestureRecognizerStateBegan)
859     return;
861   if (!_DOMElementForLastTouch || _DOMElementForLastTouch->empty())
862     return;
864   NSDictionary* info =
865       [self contextMenuInfoForElement:_DOMElementForLastTouch.get()];
866   CGPoint point = [gestureRecognizer locationInView:self.webView];
868   // Cancel all touches on the web view when showing custom context menu. This
869   // will suppress the system context menu and prevent further user interactions
870   // with web view (like scrolling the content and following links). This
871   // approach is similar to UIWebView and WKWebView workflow as both call
872   // -[UIApplication _cancelAllTouches] to cancel all touch events, once the
873   // long press is detected.
874   CancelAllTouches(self.webScrollView);
875   [self.UIDelegate webController:self
876                   runContextMenu:info
877                          atPoint:point
878                           inView:self.webView];
881 - (BOOL)supportsCustomContextMenu {
882   SEL runMenuSelector = @selector(webController:runContextMenu:atPoint:inView:);
883   return [self.UIDelegate respondsToSelector:runMenuSelector];
886 // TODO(shreyasv): This code is shared with SnapshotManager. Remove this and add
887 // it as part of WebDelegate delegate API such that a default image is returned
888 // immediately.
889 + (UIImage*)defaultSnapshotImage {
890   static UIImage* defaultImage = nil;
892   if (!defaultImage) {
893     CGRect frame = CGRectMake(0, 0, 2, 2);
894     UIGraphicsBeginImageContext(frame.size);
895     [[UIColor whiteColor] setFill];
896     CGContextFillRect(UIGraphicsGetCurrentContext(), frame);
898     UIImage* result = UIGraphicsGetImageFromCurrentImageContext();
899     UIGraphicsEndImageContext();
901     defaultImage =
902         [[result stretchableImageWithLeftCapWidth:1 topCapHeight:1] retain];
903   }
904   return defaultImage;
907 - (BOOL)canGoBack {
908   return _webStateImpl->GetNavigationManagerImpl().CanGoBack();
911 - (BOOL)canGoForward {
912   return _webStateImpl->GetNavigationManagerImpl().CanGoForward();
915 - (CGPoint)scrollPosition {
916   CGPoint position = CGPointMake(0.0, 0.0);
917   if (!self.webScrollView)
918     return position;
919   return self.webScrollView.contentOffset;
922 - (BOOL)atTop {
923   if (!self.webView)
924     return YES;
925   UIScrollView* scrollView = self.webScrollView;
926   return scrollView.contentOffset.y == -scrollView.contentInset.top;
929 - (void)presentSpoofingError {
930   UMA_HISTOGRAM_ENUMERATION("Web.URLVerificationFailure",
931                             [self webViewDocumentType],
932                             web::WEB_VIEW_DOCUMENT_TYPE_COUNT);
933   if (self.webView) {
934     [self removeWebViewAllowingCachedReconstruction:NO];
935     [_delegate presentSpoofingError];
936   }
939 - (GURL)currentURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel {
940   DCHECK(trustLevel) << "Verification of the trustLevel state is mandatory";
941   if (self.webView) {
942     GURL url([self webURLWithTrustLevel:trustLevel]);
943     // Web views treat all about: URLs as the same origin, which makes it
944     // possible for pages to document.write into about:<foo> pages, where <foo>
945     // can be something misleading. Report any about: URL as about:blank to
946     // prevent that. See crbug.com/326118
947     if (url.scheme() == url::kAboutScheme)
948       return GURL(url::kAboutBlankURL);
949     return url;
950   }
951   // Any non-web URL source is trusted.
952   *trustLevel = web::URLVerificationTrustLevel::kAbsolute;
953   if (_nativeController)
954     return [_nativeController url];
955   return [self currentNavigationURL];
958 - (GURL)currentURL {
959   web::URLVerificationTrustLevel trustLevel =
960       web::URLVerificationTrustLevel::kNone;
961   const GURL url([self currentURLWithTrustLevel:&trustLevel]);
963   // Check whether the spoofing warning needs to be displayed.
964   if (trustLevel == web::URLVerificationTrustLevel::kNone &&
965       ![self ignoreURLVerificationFailures]) {
966     dispatch_async(dispatch_get_main_queue(), ^{
967       if (!_isHalted) {
968         DCHECK_EQ(url, [self currentNavigationURL]);
969         [self presentSpoofingError];
970       }
971     });
972   }
974   return url;
977 - (web::Referrer)currentReferrer {
978   // Referrer string doesn't include the fragment, so in cases where the
979   // previous URL is equal to the current referrer plus the fragment the
980   // previous URL is returned as current referrer.
981   NSString* referrerString = self.currentReferrerString;
983   // In case of an error evaluating the JavaScript simply return empty string.
984   if ([referrerString length] == 0)
985     return web::Referrer();
987   NSString* previousURLString =
988       base::SysUTF8ToNSString([self currentNavigationURL].spec());
989   // Check if the referrer is equal to the previous URL minus the hash symbol.
990   // L'#' is used to convert the char '#' to a unichar.
991   if ([previousURLString length] > [referrerString length] &&
992       [previousURLString hasPrefix:referrerString] &&
993       [previousURLString characterAtIndex:[referrerString length]] == L'#') {
994     referrerString = previousURLString;
995   }
996   // Since referrer is being extracted from the destination page, the correct
997   // policy from the origin has *already* been applied. Since the extracted URL
998   // is the post-policy value, and the source policy is no longer available,
999   // the policy is set to Always so that whatever WebKit decided to send will be
1000   // re-sent when replaying the entry.
1001   // TODO(stuartmorgan): When possible, get the real referrer and policy in
1002   // advance and use that instead. https://crbug.com/227769.
1003   return web::Referrer(GURL(base::SysNSStringToUTF8(referrerString)),
1004                        web::ReferrerPolicyAlways);
1007 - (void)pushStateWithPageURL:(const GURL&)pageUrl
1008                  stateObject:(NSString*)stateObject {
1009   [[self sessionController] pushNewEntryWithURL:pageUrl
1010                                     stateObject:stateObject];
1011   [self didUpdateHistoryStateWithPageURL:pageUrl];
1014 - (void)replaceStateWithPageURL:(const GURL&)pageUrl
1015                     stateObject:(NSString*)stateObject {
1016   [[self sessionController] updateCurrentEntryWithURL:pageUrl
1017                                           stateObject:stateObject];
1018   [self didUpdateHistoryStateWithPageURL:pageUrl];
1021 - (void)injectEarlyInjectionScripts {
1022   DCHECK(self.webView);
1023   if (![_earlyScriptManager hasBeenInjected]) {
1024     [_earlyScriptManager inject];
1025     // If this assertion fires there has been an error parsing the core.js
1026     // object.
1027     DCHECK([_earlyScriptManager hasBeenInjected]);
1028   }
1029   [self injectWindowID];
1032 - (void)injectWindowID {
1033   if (![_windowIDJSManager hasBeenInjected]) {
1034     // If the window ID wasn't present, this is a new page.
1035     [self setPageChangeProbability:web::PAGE_CHANGE_PROBABILITY_LOW];
1036     // Default values for suppressDialogs and notifyAboutDialogs are NO,
1037     // so updating them only when necessary is a good optimization.
1038     if (_setSuppressDialogsLater || _setNotifyAboutDialogsLater) {
1039       [self setSuppressDialogs:_setSuppressDialogsLater
1040                         notify:_setNotifyAboutDialogsLater];
1041       _setSuppressDialogsLater = NO;
1042       _setNotifyAboutDialogsLater = NO;
1043     }
1045     [_windowIDJSManager inject];
1046     DCHECK([_windowIDJSManager hasBeenInjected]);
1047   }
1050 // Set the specified recognizer to take priority over any recognizers in the
1051 // view that have a description containing the specified text fragment.
1052 + (void)requireGestureRecognizerToFail:(UIGestureRecognizer*)recognizer
1053                                 inView:(UIView*)view
1054                  containingDescription:(NSString*)fragment {
1055   for (UIGestureRecognizer* iRecognizer in [view gestureRecognizers]) {
1056     if (iRecognizer != recognizer) {
1057       NSString* description = [iRecognizer description];
1058       if ([description rangeOfString:fragment].location != NSNotFound) {
1059         [iRecognizer requireGestureRecognizerToFail:recognizer];
1060         // requireGestureRecognizerToFail: doesn't retain the recognizer, so it
1061         // is possible for |iRecognizer| to outlive |recognizer| and end up with
1062         // a dangling pointer. Add a retaining associative reference to ensure
1063         // that the lifetimes work out.
1064         // Note that normally using the value as the key wouldn't make any
1065         // sense, but here it's fine since nothing needs to look up the value.
1066         objc_setAssociatedObject(view, recognizer, recognizer,
1067                                  OBJC_ASSOCIATION_RETAIN_NONATOMIC);
1068       }
1069     }
1070   }
1073 - (void)webViewDidChange {
1074   CHECK(_webUsageEnabled) << "Tried to create a web view while suspended!";
1076   UIView* webView = self.webView;
1077   DCHECK(webView);
1079   [webView setTag:kWebViewTag];
1080   [webView setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
1081                                UIViewAutoresizingFlexibleHeight];
1082   [webView setBackgroundColor:[UIColor colorWithWhite:0.2 alpha:1.0]];
1084   // Create a dependency between the |webView| pan gesture and BVC side swipe
1085   // gestures. Note: This needs to be added before the longPress recognizers
1086   // below, or the longPress appears to deadlock the remaining recognizers,
1087   // thereby breaking scroll.
1088   NSSet* recognizers = [_swipeRecognizerProvider swipeRecognizers];
1089   for (UISwipeGestureRecognizer* swipeRecognizer in recognizers) {
1090     [self.webScrollView.panGestureRecognizer
1091         requireGestureRecognizerToFail:swipeRecognizer];
1092   }
1094   // On iOS 4.x, there are two gesture recognizers on the UIWebView subclasses,
1095   // that have a minimum tap threshold of 0.12s and 0.75s.
1096   //
1097   // My theory is that the shorter threshold recognizer performs the link
1098   // highlight (grey highlight around links when it is tapped and held) while
1099   // the longer threshold one pops up the context menu.
1100   //
1101   // To override the context menu, this recognizer needs to react faster than
1102   // the 0.75s one. The below gesture recognizer is initialized with a
1103   // detection duration a little lower than that (see
1104   // kLongPressDurationSeconds). It also points the delegate to this class that
1105   // allows simultaneously operate along with the other recognizers.
1106   _contextMenuRecognizer.reset([[UILongPressGestureRecognizer alloc]
1107       initWithTarget:self
1108               action:@selector(showContextMenu:)]);
1109   [_contextMenuRecognizer setMinimumPressDuration:kLongPressDurationSeconds];
1110   [_contextMenuRecognizer setAllowableMovement:kLongPressMoveDeltaPixels];
1111   [_contextMenuRecognizer setDelegate:self];
1112   [webView addGestureRecognizer:_contextMenuRecognizer];
1113   // Certain system gesture handlers are known to conflict with our context
1114   // menu handler, causing extra events to fire when the context menu is active.
1116   // A number of solutions have been investigated. The lowest-risk solution
1117   // appears to be to recurse through the web controller's recognizers, looking
1118   // for fingerprints of the recognizers known to cause problems, which are then
1119   // de-prioritized (below our own long click handler).
1120   // Hunting for description fragments of system recognizers is undeniably
1121   // brittle for future versions of iOS. If it does break the context menu
1122   // events may leak (regressing b/5310177), but the app will otherwise work.
1123   [CRWWebController
1124       requireGestureRecognizerToFail:_contextMenuRecognizer
1125                               inView:webView
1126                containingDescription:@"action=_highlightLongPressRecognized:"];
1128   // Add all additional gesture recognizers to the web view.
1129   for (UIGestureRecognizer* recognizer in _gestureRecognizers.get()) {
1130     [webView addGestureRecognizer:recognizer];
1131   }
1133   webView.frame = [_containerView bounds];
1135   _URLOnStartLoading = _defaultURL;
1137   // Do final view setup.
1138   CGPoint initialOffset = CGPointMake(0, 0 - [self headerHeight]);
1139   [self.webScrollView setContentOffset:initialOffset];
1140   [_containerView addToolbars:_webViewToolbars];
1142   [_webViewProxy setWebView:self.webView scrollView:self.webScrollView];
1144   [_containerView addSubview:webView];
1147 - (CRWWebController*)createChildWebControllerWithReferrerURL:
1148     (const GURL&)referrerURL {
1149   web::Referrer referrer(referrerURL, web::ReferrerPolicyDefault);
1150   CRWWebController* result =
1151       [self.delegate webPageOrderedOpenBlankWithReferrer:referrer
1152                                             inBackground:NO];
1153   DCHECK(!result || result.sessionController.openedByDOM);
1154   return result;
1157 - (BOOL)canUseViewForGeneratingOverlayPlaceholderView {
1158   return _containerView != nil;
1161 - (UIView*)view {
1162   // Kick off the process of lazily creating the view and starting the load if
1163   // necessary; this creates _contentView if it doesn't exist.
1164   [self triggerPendingLoad];
1165   DCHECK(_containerView);
1166   return _containerView.get();
1169 - (id<CRWWebViewProxy>)webViewProxy {
1170   return _webViewProxy.get();
1173 - (UIView*)viewForPrinting {
1174   // TODO(ios): crbug.com/227944. Printing is not supported for native
1175   // controllers.
1176   return self.webView;
1179 - (void)loadRequest:(NSMutableURLRequest*)request {
1180   // Subclasses must implement this method.
1181   NOTREACHED();
1184 - (void)registerLoadRequest:(const GURL&)requestURL
1185                    referrer:(const web::Referrer&)referrer
1186                  transition:(ui::PageTransition)transition {
1187   // Transfer time is registered so that further transitions within the time
1188   // envelope are not also registered as links.
1189   _lastTransferTimeInSeconds = CFAbsoluteTimeGetCurrent();
1190   // Before changing phases, the delegate should be informed that any existing
1191   // request is being cancelled before completion.
1192   [self loadCancelled];
1193   DCHECK(_loadPhase == web::PAGE_LOADED);
1195   _loadPhase = web::LOAD_REQUESTED;
1196   [self resetLoadState];
1197   _lastRegisteredRequestURL = requestURL;
1199   if (!(transition & ui::PAGE_TRANSITION_IS_REDIRECT_MASK)) {
1200     // Record state of outgoing page.
1201     [self recordStateInHistory];
1202   }
1204   // If the web view had been discarded, and this request is to load that
1205   // URL again, then it's a rebuild and should use the cache.
1206   BOOL preferCache = _expectedReconstructionURL.is_valid() &&
1207                      _expectedReconstructionURL == requestURL;
1209   [_delegate webWillAddPendingURL:requestURL transition:transition];
1210   // Add or update pending url.
1211   if (_webStateImpl->GetNavigationManagerImpl().GetPendingItem()) {
1212     // Update the existing pending entry.
1213     // Typically on PAGE_TRANSITION_CLIENT_REDIRECT.
1214     [[self sessionController] updatePendingEntry:requestURL];
1215   } else {
1216     // A new session history entry needs to be created.
1217     [[self sessionController] addPendingEntry:requestURL
1218                                      referrer:referrer
1219                                    transition:transition
1220                             rendererInitiated:YES];
1221   }
1222   // Update the cache mode for all the network requests issued by this web view.
1223   // The mode is reset to CACHE_NORMAL after each page load.
1224   if (_webStateImpl->GetCacheMode() != net::RequestTracker::CACHE_NORMAL) {
1225     _webStateImpl->GetRequestTracker()->SetCacheModeFromUIThread(
1226         _webStateImpl->GetCacheMode());
1227   } else if (preferCache) {
1228     _webStateImpl->GetRequestTracker()->SetCacheModeFromUIThread(
1229         net::RequestTracker::CACHE_HISTORY);
1230   }
1231   _webStateImpl->SetIsLoading(true);
1232   [_delegate webDidAddPendingURL];
1233   _webStateImpl->OnProvisionalNavigationStarted(requestURL);
1236 - (NSString*)javascriptToReplaceWebViewURL:(const GURL&)url
1237                            stateObjectJSON:(NSString*)stateObject {
1238   std::string outURL;
1239   base::EscapeJSONString(url.spec(), true, &outURL);
1240   return
1241       [NSString stringWithFormat:@"__gCrWeb.replaceWebViewURL(%@, %@);",
1242                                  base::SysUTF8ToNSString(outURL), stateObject];
1245 - (void)finishPushStateNavigationToURL:(const GURL&)url
1246                        withStateObject:(NSString*)stateObject {
1247   // TODO(stuartmorgan): Make CRWSessionController manage this internally (or
1248   // remove it; it's not clear this matches other platforms' behavior).
1249   _webStateImpl->GetNavigationManagerImpl().OnNavigationItemCommitted();
1251   NSString* replaceWebViewUrlJS =
1252       [self javascriptToReplaceWebViewURL:url stateObjectJSON:stateObject];
1253   std::string outState;
1254   base::EscapeJSONString(base::SysNSStringToUTF8(stateObject), true, &outState);
1255   NSString* popstateJS =
1256       [NSString stringWithFormat:@"__gCrWeb.dispatchPopstateEvent(%@);",
1257                                  base::SysUTF8ToNSString(outState)];
1258   NSString* combinedJS =
1259       [NSString stringWithFormat:@"%@%@", replaceWebViewUrlJS, popstateJS];
1260   GURL urlCopy(url);
1261   base::WeakNSObject<CRWWebController> weakSelf(self);
1262   [self evaluateJavaScript:combinedJS
1263        stringResultHandler:^(NSString*, NSError*) {
1264          if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
1265            return;
1266          base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
1267          strongSelf.get()->_URLOnStartLoading = urlCopy;
1268          strongSelf.get()->_lastRegisteredRequestURL = urlCopy;
1269        }];
1272 // Load the current URL in a web view, first ensuring the web view is visible.
1273 // If a native controller is present, remove it and swap a new web view in
1274 // its place.
1275 - (void)loadCurrentURLInWebView {
1276   [self willLoadCurrentURLInWebView];
1278   // Re-register the user agent, because UIWebView sometimes loses it.
1279   // See crbug.com/228397.
1280   [self registerUserAgent];
1282   // Freeing the native controller removes its view from the view hierarchy.
1283   [self setNativeController:nil];
1285   // Clear the set of URLs opened in external applications.
1286   _openedApplicationURL.reset([[NSMutableSet alloc] init]);
1288   // Load the url. The UIWebView delegate callbacks take care of updating the
1289   // session history and UI.
1290   const GURL targetURL([self currentNavigationURL]);
1291   if (!targetURL.is_valid())
1292     return;
1294   // JavaScript should never be evaluated here. User-entered JS should be
1295   // evaluated via stringByEvaluatingUserJavaScriptFromString.
1296   DCHECK(!targetURL.SchemeIs(url::kJavaScriptScheme));
1297   [self ensureWebViewCreated];
1299   DCHECK(self.webView && !_nativeController);
1300   NSMutableURLRequest* request =
1301       [NSMutableURLRequest requestWithURL:net::NSURLWithGURL(targetURL)];
1302   const web::Referrer referrer([self currentSessionEntryReferrer]);
1303   if (referrer.url.is_valid()) {
1304     std::string referrerValue =
1305         web::ReferrerHeaderValueForNavigation(targetURL, referrer);
1306     if (!referrerValue.empty()) {
1307       [request setValue:base::SysUTF8ToNSString(referrerValue)
1308           forHTTPHeaderField:kReferrerHeaderName];
1309     }
1310   }
1312   // If there are headers in the current session entry add them to |request|.
1313   // Headers that would overwrite fields already present in |request| are
1314   // skipped.
1315   NSDictionary* headers = [self currentHttpHeaders];
1316   for (NSString* headerName in headers) {
1317     if (![request valueForHTTPHeaderField:headerName]) {
1318       [request setValue:[headers objectForKey:headerName]
1319           forHTTPHeaderField:headerName];
1320     }
1321   }
1323   NSData* postData = [self currentPOSTData];
1324   if (postData) {
1325     web::NavigationItemImpl* currentItem =
1326         [self currentSessionEntry].navigationItemImpl;
1327     if ([postData length] > 0 &&
1328         !(currentItem && currentItem->ShouldSkipResubmitDataConfirmation())) {
1329       id cancelBlock = ^{
1330         [self registerLoadRequest:[self currentNavigationURL]
1331                          referrer:[self currentSessionEntryReferrer]
1332                        transition:[self currentTransition]];
1333         [self loadRequest:request];
1334       };
1335       id continueBlock = ^{
1336         [request setHTTPMethod:@"POST"];
1337         [request setHTTPBody:[self currentPOSTData]];
1338         [request setAllHTTPHeaderFields:[self currentHttpHeaders]];
1339         [self registerLoadRequest:[self currentNavigationURL]
1340                          referrer:[self currentSessionEntryReferrer]
1341                        transition:[self currentTransition]];
1342         [self loadRequest:request];
1343       };
1344       [_delegate webController:self
1345           onFormResubmissionForRequest:request
1346                          continueBlock:continueBlock
1347                            cancelBlock:cancelBlock];
1348       return;
1349     } else {
1350       // The user does not need to confirm if POST data is empty.
1351       [request setHTTPMethod:@"POST"];
1352       [request setHTTPBody:postData];
1353       [request setAllHTTPHeaderFields:[self currentHttpHeaders]];
1354     }
1355   }
1357   // registerLoadRequest will be called when load is about to begin.
1358   // The phase at that point is guaranteed to be web::LOAD_REQUESTED.
1359   // However the delegate is not immediately called.
1360   [self registerLoadRequest:targetURL
1361                    referrer:referrer
1362                  transition:[self currentTransition]];
1363   [self loadRequest:request];
1366 - (void)loadNativeViewWithSuccess:(BOOL)loadSuccess {
1367   [_nativeController view].frame = [self visibleFrame];
1368   [_containerView addSubview:[_nativeController view]];
1369   [[_nativeController view] setNeedsUpdateConstraints];
1370   const GURL currentURL([self currentURL]);
1371   [self didStartLoadingURL:currentURL updateHistory:loadSuccess];
1372   _loadPhase = web::PAGE_LOADED;
1374   // Perform post-load-finished updates.
1375   [self didFinishWithURL:currentURL loadSuccess:loadSuccess];
1377   // Inform the embedder the title changed.
1378   if ([_delegate respondsToSelector:@selector(webController:titleDidChange:)]) {
1379     NSString* title = [_nativeController title];
1380     // If a title is present, notify the delegate.
1381     if (title)
1382       [_delegate webController:self titleDidChange:title];
1383     // If the controller handles title change notification, route those to the
1384     // delegate.
1385     if ([_nativeController respondsToSelector:@selector(setDelegate:)]) {
1386       [_nativeController setDelegate:self];
1387     }
1388   }
1391 - (void)loadErrorInNativeView:(NSError*)error {
1392   [self removeWebViewAllowingCachedReconstruction:NO];
1394   const GURL currentUrl = [self currentNavigationURL];
1395   BOOL isPost = [self currentPOSTData] != nil;
1397   [self setNativeController:[_nativeProvider controllerForURL:currentUrl
1398                                                     withError:error
1399                                                        isPost:isPost]];
1400   [self loadNativeViewWithSuccess:NO];
1403 // Load the current URL in a native controller, retrieved from the native
1404 // provider. Call |loadNativeViewWithSuccess:YES| to load the native controller.
1405 - (void)loadCurrentURLInNativeView {
1406   // Free the web view.
1407   [self removeWebViewAllowingCachedReconstruction:NO];
1409   const GURL targetURL = [self currentNavigationURL];
1410   const web::Referrer referrer;
1411   // Unlike the WebView case, always create a new controller and view.
1412   // TODO(pinkerton): What to do if this does return nil?
1413   [self setNativeController:[_nativeProvider controllerForURL:targetURL]];
1414   [self registerLoadRequest:targetURL
1415                    referrer:referrer
1416                  transition:[self currentTransition]];
1417   [self loadNativeViewWithSuccess:YES];
1420 - (void)loadWithParams:(const web::WebLoadParams&)originalParams {
1421   // Make a copy of |params|, as some of the delegate methods may modify it.
1422   web::WebLoadParams params(originalParams);
1424   // Initiating a navigation from the UI, record the current page state before
1425   // the new page loads. Don't record for back/forward, as the current entry
1426   // has already been moved to the next entry in the history. Do, however,
1427   // record it for general reload.
1428   // TODO(jimblackler): consider a single unified call to record state whenever
1429   // the page is about to be changed. This cannot currently be done after
1430   // addPendingEntry is called.
1432   [_delegate webWillInitiateLoadWithParams:params];
1434   GURL navUrl = params.url;
1435   ui::PageTransition transition = params.transition_type;
1437   BOOL initialNavigation = NO;
1438   BOOL forwardBack =
1439       PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD) &&
1440       (transition & ui::PAGE_TRANSITION_FORWARD_BACK);
1441   if (forwardBack) {
1442     // Setting these for back/forward is not supported.
1443     DCHECK(!params.extra_headers);
1444     DCHECK(!params.post_data);
1445   } else {
1446     // TODO(stuartmorgan): Why doesn't recordStateInHistory get called for
1447     // forward/back transitions?
1448     [self recordStateInHistory];
1450     CRWSessionController* history =
1451         _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1452     if (!self.currentSessionEntry)
1453       initialNavigation = YES;
1454     [history addPendingEntry:navUrl
1455                     referrer:params.referrer
1456                   transition:transition
1457            rendererInitiated:params.is_renderer_initiated];
1458     web::NavigationItemImpl* addedItem =
1459         [self currentSessionEntry].navigationItemImpl;
1460     DCHECK(addedItem);
1461     if (params.extra_headers)
1462       addedItem->AddHttpRequestHeaders(params.extra_headers);
1463     if (params.post_data) {
1464       DCHECK([addedItem->GetHttpRequestHeaders() objectForKey:@"Content-Type"])
1465           << "Post data should have an associated content type";
1466       addedItem->SetPostData(params.post_data);
1467       addedItem->SetShouldSkipResubmitDataConfirmation(true);
1468     }
1469   }
1471   [_delegate webDidUpdateSessionForLoadWithParams:params
1472                              wasInitialNavigation:initialNavigation];
1474   // If a non-default cache mode is passed in, it takes precedence over
1475   // |reload|.
1476   const BOOL reload = [self shouldReload:navUrl transition:transition];
1477   if (params.cache_mode != net::RequestTracker::CACHE_NORMAL) {
1478     _webStateImpl->SetCacheMode(params.cache_mode);
1479   } else if (reload) {
1480     _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_RELOAD);
1481   }
1483   [self loadCurrentURL];
1485   // Change the cache mode back to CACHE_NORMAL after a reload.
1486   _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1489 - (void)loadCurrentURL {
1490   // If the content view doesn't exist, the tab has either been evicted, or
1491   // never displayed. Bail, and let the URL be loaded when the tab is shown.
1492   if (!_containerView)
1493     return;
1495   // Reset current WebUI if one exists.
1496   [self clearWebUI];
1498   // Precaution, so that the outgoing URL is registered, to reduce the risk of
1499   // it being seen as a fresh URL later by the same method (and new page change
1500   // erroneously reported).
1501   [self checkForUnexpectedURLChange];
1503   // Abort any outstanding page load. This ensures the delegate gets informed
1504   // about the outgoing page, and further messages from the page are suppressed.
1505   if (_loadPhase != web::PAGE_LOADED)
1506     [self abortLoad];
1508   DCHECK(!_isHalted);
1509   // Remove the interstitial before doing anything else.
1510   [self clearInterstitials];
1512   const GURL currentURL = [self currentNavigationURL];
1513   // If it's a chrome URL, but not a native one, create the WebUI instance.
1514   if (web::GetWebClient()->IsAppSpecificURL(currentURL) &&
1515       ![_nativeProvider hasControllerForURL:currentURL]) {
1516     [self createWebUIForURL:currentURL];
1517   }
1519   // Loading a new url, must check here if it's a native chrome URL and
1520   // replace the appropriate view if so, or transition back to a web view from
1521   // a native view.
1522   if ([self shouldLoadURLInNativeView:currentURL]) {
1523     [self loadCurrentURLInNativeView];
1524   } else {
1525     [self loadCurrentURLInWebView];
1526   }
1528   // Once a URL has been loaded, any cached-based reconstruction state has
1529   // either been handled or obsoleted.
1530   _expectedReconstructionURL = GURL();
1533 - (BOOL)shouldLoadURLInNativeView:(const GURL&)url {
1534   // App-specific URLs that don't require WebUI are loaded in native views.
1535   return web::GetWebClient()->IsAppSpecificURL(url) &&
1536          !_webStateImpl->HasWebUI();
1539 - (void)triggerPendingLoad {
1540   if (!_containerView) {
1541     DCHECK(!_isBeingDestroyed);
1542     // Create the top-level parent view, which will contain the content (whether
1543     // native or web). Note, this needs to be created with a non-zero size
1544     // to allow for (native) subviews with autosize constraints to be correctly
1545     // processed.
1546     _containerView.reset([[CRWWebControllerContainerView alloc]
1547         initWithFrame:[[UIScreen mainScreen] bounds]]);
1548     [_containerView addGestureRecognizer:[self touchTrackingRecognizer]];
1549     [_containerView setAccessibilityIdentifier:web::kContainerViewID];
1550     // Is |currentUrl| a web scheme or native chrome scheme.
1551     BOOL isChromeScheme =
1552         web::GetWebClient()->IsAppSpecificURL([self currentNavigationURL]);
1554     // Don't immediately load the web page if in overlay mode. Always load if
1555     // native.
1556     if (isChromeScheme || !_overlayPreviewMode) {
1557       // TODO(jimblackler): end the practice of calling |loadCurrentURL| when it
1558       // is possible there is no current URL. If the call performs necessary
1559       // initialization, break that out.
1560       [self loadCurrentURL];
1561     }
1563     // Display overlay view until current url has finished loading or delay and
1564     // then transition away.
1565     if ((_overlayPreviewMode || _usePlaceholderOverlay) && !isChromeScheme)
1566       [self addPlaceholderOverlay];
1568     // Don't reset the overlay flag if in preview mode.
1569     if (!_overlayPreviewMode)
1570       _usePlaceholderOverlay = NO;
1571   }
1574 - (BOOL)shouldReload:(const GURL&)destinationURL
1575           transition:(ui::PageTransition)transition {
1576   // Do a reload if the user hits enter in the address bar or re-types a URL.
1577   CRWSessionController* sessionController =
1578       _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1579   web::NavigationItem* item =
1580       _webStateImpl->GetNavigationManagerImpl().GetVisibleItem();
1581   return (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) && item &&
1582          (destinationURL == item->GetURL() ||
1583           destinationURL == [sessionController currentEntry].originalUrl);
1586 // Reload either the web view or the native content depending on which is
1587 // displayed.
1588 - (void)reloadInternal {
1589   web::RecordAction(UserMetricsAction("Reload"));
1590   if (self.webView) {
1591     // Just as we don't use the WebView native back and forward navigation
1592     // (preferring to load the URLs manually) we don't use the native reload.
1593     // This ensures state processing and delegate calls are consistent.
1594     [self loadCurrentURL];
1595   } else {
1596     [_nativeController reload];
1597   }
1600 - (void)reload {
1601   [_delegate webWillReload];
1603   _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_RELOAD);
1604   [self reloadInternal];
1605   _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1608 - (void)loadCancelled {
1609   // Current load will not complete; this should be communicated upstream to the
1610   // delegate.
1611   switch (_loadPhase) {
1612     case web::LOAD_REQUESTED:
1613       // Load phase after abort is always PAGE_LOADED.
1614       _loadPhase = web::PAGE_LOADED;
1615       if (!_isHalted) {
1616         _webStateImpl->SetIsLoading(false);
1617       }
1618       [_delegate webCancelStartLoadingRequest];
1619       break;
1620     case web::PAGE_LOADING:
1621       // The previous load never fully completed before this page change. The
1622       // loadPhase is changed to PAGE_LOADED to indicate the cycle is complete,
1623       // and the delegate is called.
1624       _loadPhase = web::PAGE_LOADED;
1625       if (!_isHalted) {
1626         // RequestTracker expects StartPageLoad to be followed by
1627         // FinishPageLoad, passing the exact same URL.
1628         self.webStateImpl->GetRequestTracker()->FinishPageLoad(
1629             _URLOnStartLoading, false);
1630       }
1631       [_delegate webLoadCancelled:_URLOnStartLoading];
1632       break;
1633     case web::PAGE_LOADED:
1634       break;
1635   }
1638 - (void)abortLoad {
1639   [self abortWebLoad];
1640   [self loadCancelled];
1643 - (void)prepareForGoBack {
1644   // Make sure any transitions that may have occurred have been seen and acted
1645   // on by the CRWWebController, so the history stack and state of the
1646   // CRWWebController is 100% up to date before the stack navigation starts.
1647   if (self.webView) {
1648     [self injectEarlyInjectionScripts];
1649     [self checkForUnexpectedURLChange];
1650   }
1651   // Discard any outstanding pending entries before adjusting the navigation
1652   // index.
1653   CRWSessionController* sessionController =
1654       _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1655   [sessionController discardNonCommittedEntries];
1657   bool wasShowingInterstitial = _webStateImpl->IsShowingWebInterstitial();
1659   // Call into the delegate before |recordStateInHistory|.
1660   // TODO(rohitrao): Can this be reordered after |recordStateInHistory|?
1661   [_delegate webDidPrepareForGoBack];
1663   // Before changing the current session history entry, record the tab state.
1664   if (!wasShowingInterstitial) {
1665     [self recordStateInHistory];
1666   }
1669 - (void)goBack {
1670   [self goDelta:-1];
1673 - (void)goForward {
1674   [self goDelta:1];
1677 - (void)goDelta:(int)delta {
1678   if (delta == 0) {
1679     [self reload];
1680     return;
1681   }
1683   // Abort if there is nothing next in the history.
1684   // Note that it is NOT checked that the history depth is at least |delta|.
1685   if ((delta < 0 && ![self canGoBack]) || (delta > 0 && ![self canGoForward])) {
1686     return;
1687   }
1689   if (delta < 0) {
1690     [self prepareForGoBack];
1691   } else {
1692     // Before changing the current session history entry, record the tab state.
1693     [self recordStateInHistory];
1694   }
1696   [_delegate webWillGoDelta:delta];
1698   CRWSessionController* sessionController =
1699       _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1700   CRWSessionEntry* fromEntry = [sessionController currentEntry];
1701   [sessionController goDelta:delta];
1702   if (fromEntry) {
1703     _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_HISTORY);
1704     [self finishHistoryNavigationFromEntry:fromEntry];
1705     _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1706   }
1709 - (BOOL)isLoaded {
1710   return _loadPhase == web::PAGE_LOADED;
1713 - (void)loadCompleteWithSuccess:(BOOL)loadSuccess {
1714   [self removePlaceholderOverlay];
1715   // The webView may have been torn down (or replaced by a native view). Be
1716   // safe and do nothing if that's happened.
1717   if (_loadPhase != web::PAGE_LOADING)
1718     return;
1720   DCHECK(self.webView);
1722   const GURL currentURL([self currentURL]);
1724   [self resetLoadState];
1725   _loadPhase = web::PAGE_LOADED;
1727   [self optOutScrollsToTopForSubviews];
1729   // Ensure the URL is as expected (and already reported to the delegate).
1730   DCHECK(currentURL == _lastRegisteredRequestURL)
1731       << std::endl
1732       << "currentURL = [" << currentURL << "]" << std::endl
1733       << "_lastRegisteredRequestURL = [" << _lastRegisteredRequestURL << "]";
1735   // Perform post-load-finished updates.
1736   [self didFinishWithURL:currentURL loadSuccess:loadSuccess];
1738   // Execute the pending LoadCompleteActions.
1739   for (ProceduralBlock action in _pendingLoadCompleteActions.get()) {
1740     action();
1741   }
1742   [_pendingLoadCompleteActions removeAllObjects];
1745 - (void)didFinishWithURL:(const GURL&)currentURL loadSuccess:(BOOL)loadSuccess {
1746   DCHECK(_loadPhase == web::PAGE_LOADED);
1747   _webStateImpl->GetRequestTracker()->FinishPageLoad(currentURL, loadSuccess);
1748   // Reset the navigation type to the default value.
1749   // Note: it is possible that the web view has already started loading the
1750   // next page when this is called. In that case the cache mode can leak to
1751   // (some of) the requests of the next page. It's expected to be an edge case,
1752   // but if it becomes a problem it should be possible to notice it afterwards
1753   // and react to it (by warning the user or reloading the page for example).
1754   _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1755   _webStateImpl->GetRequestTracker()->SetCacheModeFromUIThread(
1756       _webStateImpl->GetCacheMode());
1758   [self restoreStateFromHistory];
1759   _webStateImpl->OnPageLoaded(currentURL, loadSuccess);
1760   _webStateImpl->SetIsLoading(false);
1761   // Inform the embedder the load completed.
1762   [_delegate webDidFinishWithURL:currentURL loadSuccess:loadSuccess];
1765 - (void)finishHistoryNavigationFromEntry:(CRWSessionEntry*)fromEntry {
1766   [_delegate webWillFinishHistoryNavigationFromEntry:fromEntry];
1768   // Check if toEntry was created by a JavaScript window.history.pushState()
1769   // call from fromEntry. If it was, don't load the URL. Instead update
1770   // UIWebView's URL and dispatch a popstate event.
1771   if ([_webStateImpl->GetNavigationManagerImpl().GetSessionController()
1772           isPushStateNavigationBetweenEntry:fromEntry
1773                                    andEntry:self.currentSessionEntry]) {
1774     NSString* state = [self currentSessionEntry]
1775                           .navigationItemImpl->GetSerializedStateObject();
1776     [self finishPushStateNavigationToURL:[self currentNavigationURL]
1777                          withStateObject:state];
1778   } else {
1779     GURL activeURL = [self currentNavigationURL];
1780     GURL fromURL = fromEntry.navigationItem->GetURL();
1781     GURL endURL =
1782         [self updateURLForHistoryNavigationFromURL:fromURL toURL:activeURL];
1783     web::NavigationItem* currentItem =
1784         _webStateImpl->GetNavigationManagerImpl().GetVisibleItem();
1785     ui::PageTransition transition = ui::PageTransitionFromInt(
1786         ui::PAGE_TRANSITION_RELOAD | ui::PAGE_TRANSITION_FORWARD_BACK);
1788     web::WebLoadParams params(endURL);
1789     if (currentItem) {
1790       params.referrer = currentItem->GetReferrer();
1791     }
1792     params.transition_type = transition;
1793     [self loadWithParams:params];
1794   }
1797 - (GURL)updateURLForHistoryNavigationFromURL:(const GURL&)startURL
1798                                        toURL:(const GURL&)endURL {
1799   // Check the state of the fragments on both URLs (aka, is there a '#' in the
1800   // url or not).
1801   if (!startURL.has_ref() || endURL.has_ref()) {
1802     return endURL;
1803   }
1805   // startURL contains a fragment and endURL doesn't. Remove the fragment from
1806   // startURL and compare the resulting string to endURL. If they are equal, add
1807   // # to endURL to cause a hashchange event.
1808   GURL hashless = web::GURLByRemovingRefFromGURL(startURL);
1810   if (hashless != endURL)
1811     return endURL;
1813   url::StringPieceReplacements<std::string> emptyRef;
1814   emptyRef.SetRefStr("");
1815   GURL newEndURL = endURL.ReplaceComponents(emptyRef);
1816   web::NavigationItem* item =
1817       _webStateImpl->GetNavigationManagerImpl().GetVisibleItem();
1818   if (item)
1819     item->SetURL(newEndURL);
1820   return newEndURL;
1823 - (void)evaluateJavaScript:(NSString*)script
1824          JSONResultHandler:
1825              (void (^)(scoped_ptr<base::Value>, NSError*))handler {
1826   [self evaluateJavaScript:script
1827        stringResultHandler:^(NSString* stringResult, NSError* error) {
1828          if (handler) {
1829            scoped_ptr<base::Value> result(
1830                base::JSONReader::Read(base::SysNSStringToUTF8(stringResult)));
1831            DCHECK(result || error);
1832            handler(result.Pass(), error);
1833          }
1834        }];
1837 - (void)addGestureRecognizerToWebView:(UIGestureRecognizer*)recognizer {
1838   if ([_gestureRecognizers containsObject:recognizer])
1839     return;
1841   [self.webView addGestureRecognizer:recognizer];
1842   [_gestureRecognizers addObject:recognizer];
1845 - (void)removeGestureRecognizerFromWebView:(UIGestureRecognizer*)recognizer {
1846   if (![_gestureRecognizers containsObject:recognizer])
1847     return;
1849   [self.webView removeGestureRecognizer:recognizer];
1850   [_gestureRecognizers removeObject:recognizer];
1853 - (void)addToolbarViewToWebView:(UIView*)toolbarView {
1854   DCHECK(toolbarView);
1855   if ([_webViewToolbars containsObject:toolbarView])
1856     return;
1857   [_webViewToolbars addObject:toolbarView];
1858   if (self.webView)
1859     [_containerView addToolbar:toolbarView];
1862 - (void)removeToolbarViewFromWebView:(UIView*)toolbarView {
1863   if (![_webViewToolbars containsObject:toolbarView])
1864     return;
1865   [_webViewToolbars removeObject:toolbarView];
1866   if (self.webView)
1867     [_containerView removeToolbar:toolbarView];
1870 - (CRWJSInjectionReceiver*)jsInjectionReceiver {
1871   return _jsInjectionReceiver;
1874 - (BOOL)cancellable {
1875   return self.sessionController.openedByDOM &&
1876          !self.sessionController.lastCommittedEntry;
1879 - (BOOL)isBeingDestroyed {
1880   return _isBeingDestroyed;
1883 - (BOOL)isHalted {
1884   return _isHalted;
1887 - (web::ReferrerPolicy)referrerPolicyFromString:(const std::string&)policy {
1888   // TODO(stuartmorgan): Remove this temporary bridge to the helper function
1889   // once the referrer handling moves into the subclasses.
1890   return web::ReferrerPolicyFromString(policy);
1893 #pragma mark -
1894 #pragma mark CRWJSInjectionEvaluator Methods
1896 - (void)evaluateJavaScript:(NSString*)script
1897        stringResultHandler:(web::JavaScriptCompletion)handler {
1898   // Subclasses must implement this method.
1899   NOTREACHED();
1902 - (BOOL)scriptHasBeenInjectedForClass:(Class)jsInjectionManagerClass
1903                        presenceBeacon:(NSString*)beacon {
1904   // Subclasses must implement this method.
1905   NOTREACHED();
1906   return NO;
1909 - (void)injectScript:(NSString*)script forClass:(Class)JSInjectionManagerClass {
1910   // Make sure that CRWJSEarlyScriptManager has been injected.
1911   BOOL ealyScriptInjected =
1912       [self scriptHasBeenInjectedForClass:[CRWJSEarlyScriptManager class]
1913                            presenceBeacon:[_earlyScriptManager presenceBeacon]];
1914   if (!ealyScriptInjected &&
1915       JSInjectionManagerClass != [CRWJSEarlyScriptManager class]) {
1916     [_earlyScriptManager inject];
1917   }
1920 - (web::WebViewType)webViewType {
1921   // Subclasses must implement this method.
1922   NOTREACHED();
1923   return web::UI_WEB_VIEW_TYPE;
1926 #pragma mark -
1928 - (void)evaluateUserJavaScript:(NSString*)script {
1929   // Subclasses must implement this method.
1930   NOTREACHED();
1933 - (void)didFinishNavigation {
1934   // This can be called at multiple times after the document has loaded. Do
1935   // nothing if the document has already loaded.
1936   if (_loadPhase == web::PAGE_LOADED)
1937     return;
1938   [self loadCompleteWithSuccess:YES];
1941 - (BOOL)respondToMessage:(base::DictionaryValue*)message
1942        userIsInteracting:(BOOL)userIsInteracting
1943                originURL:(const GURL&)originURL {
1944   std::string command;
1945   if (!message->GetString("command", &command)) {
1946     DLOG(WARNING) << "JS message parameter not found: command";
1947     return NO;
1948   }
1950   SEL handler = [self selectorToHandleJavaScriptCommand:command];
1951   if (!handler) {
1952     if (!self.webStateImpl->OnScriptCommandReceived(
1953             command, *message, originURL, userIsInteracting)) {
1954       // Message was either unexpected or not correctly handled.
1955       // Page is reset as a precaution.
1956       DLOG(WARNING) << "Unexpected message received: " << command;
1957       return NO;
1958     }
1959     return YES;
1960   }
1962   typedef BOOL (*HandlerType)(id, SEL, base::DictionaryValue*, NSDictionary*);
1963   HandlerType handlerImplementation =
1964       reinterpret_cast<HandlerType>([self methodForSelector:handler]);
1965   DCHECK(handlerImplementation);
1966   NSMutableDictionary* context =
1967       [NSMutableDictionary dictionaryWithObject:@(userIsInteracting)
1968                                          forKey:web::kUserIsInteractingKey];
1969   NSURL* originNSURL = net::NSURLWithGURL(originURL);
1970   if (originNSURL)
1971     context[web::kOriginURLKey] = originNSURL;
1972   return handlerImplementation(self, handler, message, context);
1975 - (SEL)selectorToHandleJavaScriptCommand:(const std::string&)command {
1976   static std::map<std::string, SEL>* handlers = nullptr;
1977   static dispatch_once_t onceToken;
1978   dispatch_once(&onceToken, ^{
1979     handlers = new std::map<std::string, SEL>();
1980     (*handlers)["addPluginPlaceholders"] =
1981         @selector(handleAddPluginPlaceholdersMessage:context:);
1982     (*handlers)["chrome.send"] = @selector(handleChromeSendMessage:context:);
1983     (*handlers)["console"] = @selector(handleConsoleMessage:context:);
1984     (*handlers)["dialog.suppressed"] =
1985         @selector(handleDialogSuppressedMessage:context:);
1986     (*handlers)["dialog.willShow"] =
1987         @selector(handleDialogWillShowMessage:context:);
1988     (*handlers)["document.favicons"] =
1989         @selector(handleDocumentFaviconsMessage:context:);
1990     (*handlers)["document.retitled"] =
1991         @selector(handleDocumentRetitledMessage:context:);
1992     (*handlers)["document.submit"] =
1993         @selector(handleDocumentSubmitMessage:context:);
1994     (*handlers)["externalRequest"] =
1995         @selector(handleExternalRequestMessage:context:);
1996     (*handlers)["form.activity"] =
1997         @selector(handleFormActivityMessage:context:);
1998     (*handlers)["form.requestAutocomplete"] =
1999         @selector(handleFormRequestAutocompleteMessage:context:);
2000     (*handlers)["navigator.credentials.request"] =
2001         @selector(handleCredentialsRequestedMessage:context:);
2002     (*handlers)["navigator.credentials.notifySignedIn"] =
2003         @selector(handleSignedInMessage:context:);
2004     (*handlers)["navigator.credentials.notifySignedOut"] =
2005         @selector(handleSignedOutMessage:context:);
2006     (*handlers)["navigator.credentials.notifyFailedSignIn"] =
2007         @selector(handleSignInFailedMessage:context:);
2008     (*handlers)["resetExternalRequest"] =
2009         @selector(handleResetExternalRequestMessage:context:);
2010     (*handlers)["window.close.self"] =
2011         @selector(handleWindowCloseSelfMessage:context:);
2012     (*handlers)["window.error"] = @selector(handleWindowErrorMessage:context:);
2013     (*handlers)["window.hashchange"] =
2014         @selector(handleWindowHashChangeMessage:context:);
2015     (*handlers)["window.history.back"] =
2016         @selector(handleWindowHistoryBackMessage:context:);
2017     (*handlers)["window.history.didPushState"] =
2018         @selector(handleWindowHistoryDidPushStateMessage:context:);
2019     (*handlers)["window.history.didReplaceState"] =
2020         @selector(handleWindowHistoryDidReplaceStateMessage:context:);
2021     (*handlers)["window.history.forward"] =
2022         @selector(handleWindowHistoryForwardMessage:context:);
2023     (*handlers)["window.history.go"] =
2024         @selector(handleWindowHistoryGoMessage:context:);
2025   });
2026   DCHECK(handlers);
2027   auto iter = handlers->find(command);
2028   return iter != handlers->end() ? iter->second : nullptr;
2031 #pragma mark -
2032 #pragma mark JavaScript message handlers
2034 - (BOOL)handleAddPluginPlaceholdersMessage:(base::DictionaryValue*)message
2035                                    context:(NSDictionary*)context {
2036   // Inject the script that adds the plugin placeholders.
2037   [[_jsInjectionReceiver
2038       instanceOfClass:[CRWJSPluginPlaceholderManager class]] inject];
2039   return YES;
2042 - (BOOL)handleChromeSendMessage:(base::DictionaryValue*)message
2043                         context:(NSDictionary*)context {
2044   if (_webStateImpl->HasWebUI()) {
2045     const GURL currentURL([self currentURL]);
2046     if (web::GetWebClient()->IsAppSpecificURL(currentURL)) {
2047       std::string messageContent;
2048       base::ListValue* arguments = nullptr;
2049       if (!message->GetString("message", &messageContent)) {
2050         DLOG(WARNING) << "JS message parameter not found: message";
2051         return NO;
2052       }
2053       if (!message->GetList("arguments", &arguments)) {
2054         DLOG(WARNING) << "JS message parameter not found: arguments";
2055         return NO;
2056       }
2057       _webStateImpl->OnScriptCommandReceived(
2058           messageContent, *message, currentURL,
2059           context[web::kUserIsInteractingKey]);
2060       _webStateImpl->ProcessWebUIMessage(currentURL, messageContent,
2061                                          *arguments);
2062       return YES;
2063     }
2064   }
2066   DLOG(WARNING)
2067       << "chrome.send message not handled because WebUI was not found.";
2068   return NO;
2071 - (BOOL)handleConsoleMessage:(base::DictionaryValue*)message
2072                      context:(NSDictionary*)context {
2073   // Do not log if JS logging is off.
2074   if (![[NSUserDefaults standardUserDefaults] boolForKey:web::kLogJavaScript]) {
2075     return YES;
2076   }
2078   std::string method;
2079   if (!message->GetString("method", &method)) {
2080     DLOG(WARNING) << "JS message parameter not found: method";
2081     return NO;
2082   }
2083   std::string consoleMessage;
2084   if (!message->GetString("message", &consoleMessage)) {
2085     DLOG(WARNING) << "JS message parameter not found: message";
2086     return NO;
2087   }
2088   std::string origin;
2089   if (!message->GetString("origin", &origin)) {
2090     DLOG(WARNING) << "JS message parameter not found: origin";
2091     return NO;
2092   }
2094   DVLOG(0) << origin << " [" << method << "] " << consoleMessage;
2095   return YES;
2098 - (BOOL)handleDialogSuppressedMessage:(base::DictionaryValue*)message
2099                               context:(NSDictionary*)context {
2100   if ([_delegate
2101           respondsToSelector:@selector(webControllerDidSuppressDialog:)]) {
2102     [_delegate webControllerDidSuppressDialog:self];
2103   }
2104   return YES;
2107 - (BOOL)handleDialogWillShowMessage:(base::DictionaryValue*)message
2108                             context:(NSDictionary*)context {
2109   if ([_delegate respondsToSelector:@selector(webControllerWillShowDialog:)]) {
2110     [_delegate webControllerWillShowDialog:self];
2111   }
2112   return YES;
2115 - (BOOL)handleDocumentFaviconsMessage:(base::DictionaryValue*)message
2116                               context:(NSDictionary*)context {
2117   base::ListValue* favicons = nullptr;
2118   if (!message->GetList("favicons", &favicons)) {
2119     DLOG(WARNING) << "JS message parameter not found: favicons";
2120     return NO;
2121   }
2122   std::vector<web::FaviconURL> urls;
2123   for (size_t fav_idx = 0; fav_idx != favicons->GetSize(); ++fav_idx) {
2124     base::DictionaryValue* favicon = nullptr;
2125     if (!favicons->GetDictionary(fav_idx, &favicon))
2126       return NO;
2127     std::string href;
2128     std::string rel;
2129     if (!favicon->GetString("href", &href)) {
2130       DLOG(WARNING) << "JS message parameter not found: href";
2131       return NO;
2132     }
2133     if (!favicon->GetString("rel", &rel)) {
2134       DLOG(WARNING) << "JS message parameter not found: rel";
2135       return NO;
2136     }
2137     web::FaviconURL::IconType icon_type = web::FaviconURL::FAVICON;
2138     if (rel == "apple-touch-icon")
2139       icon_type = web::FaviconURL::TOUCH_ICON;
2140     else if (rel == "apple-touch-icon-precomposed")
2141       icon_type = web::FaviconURL::TOUCH_PRECOMPOSED_ICON;
2142     urls.push_back(
2143         web::FaviconURL(GURL(href), icon_type, std::vector<gfx::Size>()));
2144   }
2145   if (!urls.empty())
2146     _webStateImpl->OnFaviconUrlUpdated(urls);
2147   return YES;
2150 - (BOOL)handleDocumentSubmitMessage:(base::DictionaryValue*)message
2151                             context:(NSDictionary*)context {
2152   std::string href;
2153   if (!message->GetString("href", &href)) {
2154     DLOG(WARNING) << "JS message parameter not found: href";
2155     return NO;
2156   }
2157   const GURL targetURL(href);
2158   const GURL currentURL([self currentURL]);
2159   bool targetsFrame = false;
2160   message->GetBoolean("targetsFrame", &targetsFrame);
2161   if (!targetsFrame && web::UrlHasWebScheme(targetURL)) {
2162     // The referrer is not known yet, and will be updated later.
2163     const web::Referrer emptyReferrer;
2164     [self registerLoadRequest:targetURL
2165                      referrer:emptyReferrer
2166                    transition:ui::PAGE_TRANSITION_FORM_SUBMIT];
2167   }
2168   std::string formName;
2169   message->GetString("formName", &formName);
2170   base::scoped_nsobject<NSSet> observers([_observers copy]);
2171   // We decide the form is user-submitted if the user has interacted with
2172   // the main page (using logic from the popup blocker), or if the keyboard
2173   // is visible.
2174   BOOL submittedByUser = [context[web::kUserIsInteractingKey] boolValue] ||
2175                          [_webViewProxy getKeyboardAccessory];
2176   _webStateImpl->OnDocumentSubmitted(formName, submittedByUser);
2177   return YES;
2180 - (BOOL)handleExternalRequestMessage:(base::DictionaryValue*)message
2181                              context:(NSDictionary*)context {
2182   std::string href;
2183   std::string target;
2184   std::string referrerPolicy;
2185   if (!message->GetString("href", &href)) {
2186     DLOG(WARNING) << "JS message parameter not found: href";
2187     return NO;
2188   }
2189   if (!message->GetString("target", &target)) {
2190     DLOG(WARNING) << "JS message parameter not found: target";
2191     return NO;
2192   }
2193   if (!message->GetString("referrerPolicy", &referrerPolicy)) {
2194     DLOG(WARNING) << "JS message parameter not found: referrerPolicy";
2195     return NO;
2196   }
2197   // Round-trip the href through NSURL; this URL will be compared as a
2198   // string against a UIWebView-provided NSURL later, and must match exactly
2199   // for the new window to trigger, so the escaping needs to be NSURL-style.
2200   // TODO(stuartmorgan): Comparing against a URL whose exact formatting we
2201   // don't control is fundamentally fragile; try to find another
2202   // way of handling this.
2203   DCHECK(context[web::kUserIsInteractingKey]);
2204   NSString* windowName =
2205       base::SysUTF8ToNSString(href + web::kWindowNameSeparator + target);
2206   _externalRequest.reset(new web::NewWindowInfo(
2207       net::GURLWithNSURL(net::NSURLWithGURL(GURL(href))), windowName,
2208       web::ReferrerPolicyFromString(referrerPolicy),
2209       [context[web::kUserIsInteractingKey] boolValue]));
2210   return YES;
2213 - (BOOL)handleFormActivityMessage:(base::DictionaryValue*)message
2214                           context:(NSDictionary*)context {
2215   std::string formName;
2216   std::string fieldName;
2217   std::string type;
2218   std::string value;
2219   int keyCode = web::WebStateObserver::kInvalidFormKeyCode;
2220   bool inputMissing = false;
2221   if (!message->GetString("formName", &formName) ||
2222       !message->GetString("fieldName", &fieldName) ||
2223       !message->GetString("type", &type) ||
2224       !message->GetString("value", &value)) {
2225     inputMissing = true;
2226   }
2228   if (!message->GetInteger("keyCode", &keyCode) || keyCode < 0)
2229     keyCode = web::WebStateObserver::kInvalidFormKeyCode;
2230   _webStateImpl->OnFormActivityRegistered(formName, fieldName, type, value,
2231                                           keyCode, inputMissing);
2232   return YES;
2235 - (BOOL)handleFormRequestAutocompleteMessage:(base::DictionaryValue*)message
2236                                      context:(NSDictionary*)context {
2237   std::string formName;
2238   if (!message->GetString("formName", &formName)) {
2239     DLOG(WARNING) << "JS message parameter not found: formName";
2240     return NO;
2241   }
2242   DCHECK(context[web::kUserIsInteractingKey]);
2243   _webStateImpl->OnAutocompleteRequested(
2244       net::GURLWithNSURL(context[web::kOriginURLKey]), formName,
2245       [context[web::kUserIsInteractingKey] boolValue]);
2246   return YES;
2249 - (BOOL)handleCredentialsRequestedMessage:(base::DictionaryValue*)message
2250                                   context:(NSDictionary*)context {
2251   int request_id = -1;
2252   if (!message->GetInteger("requestId", &request_id)) {
2253     DLOG(WARNING) << "JS message parameter not found: requestId";
2254     return NO;
2255   }
2256   bool suppress_ui = false;
2257   if (!message->GetBoolean("suppressUI", &suppress_ui)) {
2258     DLOG(WARNING) << "JS message parameter not found: suppressUI";
2259     return NO;
2260   }
2261   base::ListValue* federations_value = nullptr;
2262   if (!message->GetList("federations", &federations_value)) {
2263     DLOG(WARNING) << "JS message parameter not found: federations";
2264     return NO;
2265   }
2266   std::vector<std::string> federations;
2267   for (auto federation_value : *federations_value) {
2268     std::string federation;
2269     if (!federation_value->GetAsString(&federation)) {
2270       DLOG(WARNING) << "JS message parameter 'federations' contains wrong type";
2271       return NO;
2272     }
2273     federations.push_back(federation);
2274   }
2275   DCHECK(context[web::kUserIsInteractingKey]);
2276   _webStateImpl->OnCredentialsRequested(
2277       request_id, net::GURLWithNSURL(context[web::kOriginURLKey]), suppress_ui,
2278       federations, [context[web::kUserIsInteractingKey] boolValue]);
2279   return YES;
2282 - (BOOL)handleSignedInMessage:(base::DictionaryValue*)message
2283                       context:(NSDictionary*)context {
2284   int request_id = -1;
2285   if (!message->GetInteger("requestId", &request_id)) {
2286     DLOG(WARNING) << "JS message parameter not found: requestId";
2287     return NO;
2288   }
2289   base::DictionaryValue* credential_data = nullptr;
2290   web::Credential credential;
2291   if (message->GetDictionary("credential", &credential_data)) {
2292     if (!web::DictionaryValueToCredential(*credential_data, &credential)) {
2293       DLOG(WARNING) << "JS message parameter 'credential' is invalid";
2294       return NO;
2295     }
2296     _webStateImpl->OnSignedIn(request_id,
2297                               net::GURLWithNSURL(context[web::kOriginURLKey]),
2298                               credential);
2299   } else {
2300     _webStateImpl->OnSignedIn(request_id,
2301                               net::GURLWithNSURL(context[web::kOriginURLKey]));
2302   }
2303   return YES;
2306 - (BOOL)handleSignedOutMessage:(base::DictionaryValue*)message
2307                        context:(NSDictionary*)context {
2308   int request_id = -1;
2309   if (!message->GetInteger("requestId", &request_id)) {
2310     DLOG(WARNING) << "JS message parameter not found: requestId";
2311     return NO;
2312   }
2313   _webStateImpl->OnSignedOut(request_id,
2314                              net::GURLWithNSURL(context[web::kOriginURLKey]));
2315   return YES;
2318 - (BOOL)handleSignInFailedMessage:(base::DictionaryValue*)message
2319                           context:(NSDictionary*)context {
2320   int request_id = -1;
2321   if (!message->GetInteger("requestId", &request_id)) {
2322     DLOG(WARNING) << "JS message parameter not found: requestId";
2323     return NO;
2324   }
2325   base::DictionaryValue* credential_data = nullptr;
2326   web::Credential credential;
2327   if (message->GetDictionary("credential", &credential_data)) {
2328     if (!web::DictionaryValueToCredential(*credential_data, &credential)) {
2329       DLOG(WARNING) << "JS message parameter 'credential' is invalid";
2330       return NO;
2331     }
2332     _webStateImpl->OnSignInFailed(
2333         request_id, net::GURLWithNSURL(context[web::kOriginURLKey]),
2334         credential);
2335   } else {
2336     _webStateImpl->OnSignInFailed(
2337         request_id, net::GURLWithNSURL(context[web::kOriginURLKey]));
2338   }
2339   return YES;
2342 - (BOOL)handleResetExternalRequestMessage:(base::DictionaryValue*)message
2343                                   context:(NSDictionary*)context {
2344   _externalRequest.reset();
2345   return YES;
2348 - (BOOL)handleWindowCloseSelfMessage:(base::DictionaryValue*)message
2349                              context:(NSDictionary*)context {
2350   [self orderClose];
2351   return YES;
2354 - (BOOL)handleWindowErrorMessage:(base::DictionaryValue*)message
2355                          context:(NSDictionary*)context {
2356   std::string errorMessage;
2357   if (!message->GetString("message", &errorMessage)) {
2358     DLOG(WARNING) << "JS message parameter not found: message";
2359     return NO;
2360   }
2361   DLOG(ERROR) << "JavaScript error: " << errorMessage
2362               << " URL:" << [self currentURL].spec();
2363   return YES;
2366 - (BOOL)handleWindowHashChangeMessage:(base::DictionaryValue*)message
2367                               context:(NSDictionary*)context {
2368   [self checkForUnexpectedURLChange];
2370   // Notify the observers.
2371   _webStateImpl->OnUrlHashChanged();
2372   return YES;
2375 - (BOOL)handleWindowHistoryBackMessage:(base::DictionaryValue*)message
2376                                context:(NSDictionary*)context {
2377   [self goBack];
2378   return YES;
2381 - (BOOL)handleWindowHistoryForwardMessage:(base::DictionaryValue*)message
2382                                   context:(NSDictionary*)context {
2383   [self goForward];
2384   return YES;
2387 - (BOOL)handleWindowHistoryGoMessage:(base::DictionaryValue*)message
2388                              context:(NSDictionary*)context {
2389   int delta;
2390   message->GetInteger("value", &delta);
2391   [self goDelta:delta];
2392   return YES;
2395 - (BOOL)handleWindowHistoryDidPushStateMessage:(base::DictionaryValue*)message
2396                                        context:(NSDictionary*)context {
2397   std::string pageURL;
2398   std::string baseURL;
2399   if (!message->GetString("pageUrl", &pageURL) ||
2400       !message->GetString("baseUrl", &baseURL)) {
2401     DLOG(WARNING) << "JS message parameter not found: pageUrl or baseUrl";
2402     return NO;
2403   }
2404   GURL pushURL = web::history_state_util::GetHistoryStateChangeUrl(
2405       [self currentURL], GURL(baseURL), pageURL);
2406   // UIWebView seems to choke on unicode characters that haven't been
2407   // escaped; escape the URL now so the expected load URL is correct.
2408   pushURL = URLEscapedForHistory(pushURL);
2409   if (!pushURL.is_valid())
2410     return YES;
2411   const NavigationManagerImpl& navigationManager =
2412       _webStateImpl->GetNavigationManagerImpl();
2413   web::NavigationItem* navItem = [self currentNavItem];
2414   // PushState happened before first navigation entry or called right after
2415   // window.open when the url is empty.
2416   if (!navItem ||
2417       (navigationManager.GetEntryCount() <= 1 && navItem->GetURL().is_empty()))
2418     return YES;
2419   if (!web::history_state_util::IsHistoryStateChangeValid(navItem->GetURL(),
2420                                                           pushURL)) {
2421     // A redirect may have occurred just prior to the pushState. Check if
2422     // the URL needs to be updated.
2423     // TODO(bdibello): Investigate how the pushState() is handled before the
2424     // redirect and after core.js injection.
2425     [self checkForUnexpectedURLChange];
2426   }
2427   if (!web::history_state_util::IsHistoryStateChangeValid(
2428           [self currentNavItem]->GetURL(), pushURL)) {
2429     // If the current session entry URL origin still doesn't match pushURL's
2430     // origin, ignore the pushState. This can happen if a new URL is loaded
2431     // just before the pushState.
2432     return YES;
2433   }
2434   std::string stateObjectJSON;
2435   if (!message->GetString("stateObject", &stateObjectJSON)) {
2436     DLOG(WARNING) << "JS message parameter not found: stateObject";
2437     return NO;
2438   }
2439   NSString* stateObject = base::SysUTF8ToNSString(stateObjectJSON);
2440   _URLOnStartLoading = pushURL;
2441   _lastRegisteredRequestURL = pushURL;
2442   [self pushStateWithPageURL:pushURL stateObject:stateObject];
2444   NSString* replaceWebViewJS =
2445       [self javascriptToReplaceWebViewURL:pushURL stateObjectJSON:stateObject];
2446   base::WeakNSObject<CRWWebController> weakSelf(self);
2447   [self evaluateJavaScript:replaceWebViewJS
2448        stringResultHandler:^(NSString*, NSError*) {
2449          if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
2450            return;
2451          base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
2452          [strongSelf optOutScrollsToTopForSubviews];
2453          // Notify the observers.
2454          strongSelf.get()->_webStateImpl->OnHistoryStateChanged();
2455          [strongSelf didFinishNavigation];
2456        }];
2457   return YES;
2460 - (BOOL)handleWindowHistoryDidReplaceStateMessage:
2461     (base::DictionaryValue*)message
2462                                           context:(NSDictionary*)context {
2463   std::string pageURL;
2464   std::string baseURL;
2465   if (!message->GetString("pageUrl", &pageURL) ||
2466       !message->GetString("baseUrl", &baseURL)) {
2467     DLOG(WARNING) << "JS message parameter not found: pageUrl or baseUrl";
2468     return NO;
2469   }
2470   GURL replaceURL = web::history_state_util::GetHistoryStateChangeUrl(
2471       [self currentURL], GURL(baseURL), pageURL);
2472   // UIWebView seems to choke on unicode characters that haven't been
2473   // escaped; escape the URL now so the expected load URL is correct.
2474   replaceURL = URLEscapedForHistory(replaceURL);
2475   if (!replaceURL.is_valid())
2476     return YES;
2477   const NavigationManagerImpl& navigationManager =
2478       _webStateImpl->GetNavigationManagerImpl();
2479   web::NavigationItem* navItem = [self currentNavItem];
2480   // ReplaceState happened before first navigation entry or called right
2481   // after window.open when the url is empty/not valid.
2482   if (!navItem ||
2483       (navigationManager.GetEntryCount() <= 1 && navItem->GetURL().is_empty()))
2484     return YES;
2485   if (!web::history_state_util::IsHistoryStateChangeValid(navItem->GetURL(),
2486                                                           replaceURL)) {
2487     // A redirect may have occurred just prior to the replaceState. Check if
2488     // the URL needs to be updated.
2489     [self checkForUnexpectedURLChange];
2490   }
2491   if (!web::history_state_util::IsHistoryStateChangeValid(
2492           [self currentNavItem]->GetURL(), replaceURL)) {
2493     // If the current session entry URL origin still doesn't match
2494     // replaceURL's origin, ignore the replaceState. This can happen if a
2495     // new URL is loaded just before the replaceState.
2496     return YES;
2497   }
2498   std::string stateObjectJSON;
2499   if (!message->GetString("stateObject", &stateObjectJSON)) {
2500     DLOG(WARNING) << "JS message parameter not found: stateObject";
2501     return NO;
2502   }
2503   NSString* stateObject = base::SysUTF8ToNSString(stateObjectJSON);
2504   _URLOnStartLoading = replaceURL;
2505   _lastRegisteredRequestURL = replaceURL;
2506   [self replaceStateWithPageURL:replaceURL stateObject:stateObject];
2507   NSString* replaceStateJS = [self javascriptToReplaceWebViewURL:replaceURL
2508                                                  stateObjectJSON:stateObject];
2509   base::WeakNSObject<CRWWebController> weakSelf(self);
2510   [self evaluateJavaScript:replaceStateJS
2511        stringResultHandler:^(NSString*, NSError*) {
2512          if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
2513            return;
2514          base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
2515          [strongSelf didFinishNavigation];
2516        }];
2517   return YES;
2520 #pragma mark -
2522 - (BOOL)wantsKeyboardShield {
2523   if (_nativeController &&
2524       [_nativeController respondsToSelector:@selector(wantsKeyboardShield)]) {
2525     return [_nativeController wantsKeyboardShield];
2526   }
2527   return YES;
2530 - (BOOL)wantsLocationBarHintText {
2531   if (_nativeController &&
2532       [_nativeController
2533           respondsToSelector:@selector(wantsLocationBarHintText)]) {
2534     return [_nativeController wantsLocationBarHintText];
2535   }
2536   return YES;
2539 // TODO(stuartmorgan): This method conflates document changes and URL changes;
2540 // we should be distinguishing better, and be clear about the expected
2541 // WebDelegate and WCO callbacks in each case.
2542 - (void)webPageChanged {
2543   DCHECK(_loadPhase == web::LOAD_REQUESTED);
2545   const GURL currentURL([self currentURL]);
2546   web::Referrer referrer = [self currentReferrer];
2547   // If no referrer was known in advance, record it now. (If there was one,
2548   // keep it since it will have a more accurate URL and policy than what can
2549   // be extracted from the landing page.)
2550   web::NavigationItem* currentItem = [self currentNavItem];
2551   if (!currentItem->GetReferrer().url.is_valid()) {
2552     currentItem->SetReferrer(referrer);
2553   }
2555   // TODO(stuartmorgan): This shouldn't be called for hash state or
2556   // push/replaceState.
2557   [self resetDocumentSpecificState];
2559   [self didStartLoadingURL:currentURL updateHistory:YES];
2562 - (void)resetDocumentSpecificState {
2563   _lastClickTimeInSeconds = -DBL_MAX;
2564   _clickInProgress = NO;
2566   _lastSeenWindowID.reset([[_windowIDJSManager windowId] copy]);
2569 - (void)didStartLoadingURL:(const GURL&)url updateHistory:(BOOL)updateHistory {
2570   _loadPhase = web::PAGE_LOADING;
2571   _URLOnStartLoading = url;
2572   _scrollStateOnStartLoading = self.pageScrollState;
2574   _userInteractionRegistered = NO;
2576   [[self sessionController] commitPendingEntry];
2577   _webStateImpl->GetRequestTracker()->StartPageLoad(
2578       url, [[self sessionController] currentEntry]);
2579   [_delegate webDidStartLoadingURL:url shouldUpdateHistory:updateHistory];
2582 - (BOOL)checkForUnexpectedURLChange {
2583   // Subclasses may override this method to check for and handle URL changes.
2584   return NO;
2587 - (void)wasShown {
2588   if (_nativeController &&
2589       [_nativeController respondsToSelector:@selector(wasShown)]) {
2590     [_nativeController wasShown];
2591   }
2594 - (void)wasHidden {
2595   if (_isHalted)
2596     return;
2597   if (_nativeController &&
2598       [_nativeController respondsToSelector:@selector(wasHidden)]) {
2599     [_nativeController wasHidden];
2600   }
2603 + (BOOL)webControllerCanShow:(const GURL&)url {
2604   return web::UrlHasWebScheme(url) ||
2605          web::GetWebClient()->IsAppSpecificURL(url) ||
2606          url.SchemeIs(url::kFileScheme) || url.SchemeIs(url::kAboutScheme);
2609 - (void)setUserInteractionRegistered:(BOOL)flag {
2610   _userInteractionRegistered = flag;
2613 - (BOOL)userInteractionRegistered {
2614   return _userInteractionRegistered;
2617 - (BOOL)useDesktopUserAgent {
2618   web::NavigationItem* item = [self currentNavItem];
2619   return item && item->IsOverridingUserAgent();
2622 - (void)cachePOSTDataForRequest:(NSURLRequest*)request
2623                  inSessionEntry:(CRWSessionEntry*)currentSessionEntry {
2624   NSUInteger maxPOSTDataSizeInBytes = 4096;
2625   NSString* cookieHeaderName = @"cookie";
2627   web::NavigationItemImpl* currentItem = currentSessionEntry.navigationItemImpl;
2628   DCHECK(currentItem);
2629   const bool shouldUpdateEntry =
2630       ui::PageTransitionCoreTypeIs(currentItem->GetTransitionType(),
2631                                    ui::PAGE_TRANSITION_FORM_SUBMIT) &&
2632       ![request HTTPBodyStream] &&  // Don't cache streams.
2633       !currentItem->HasPostData() &&
2634       currentItem->GetURL() == net::GURLWithNSURL([request URL]);
2635   const bool belowSizeCap =
2636       [[request HTTPBody] length] < maxPOSTDataSizeInBytes;
2637   DLOG_IF(WARNING, shouldUpdateEntry && !belowSizeCap)
2638       << "Data in POST request exceeds the size cap (" << maxPOSTDataSizeInBytes
2639       << " bytes), and will not be cached.";
2641   if (shouldUpdateEntry && belowSizeCap) {
2642     currentItem->SetPostData([request HTTPBody]);
2643     currentItem->ResetHttpRequestHeaders();
2644     currentItem->AddHttpRequestHeaders([request allHTTPHeaderFields]);
2645     // Don't cache the "Cookie" header.
2646     // According to NSURLRequest documentation, |-valueForHTTPHeaderField:| is
2647     // case insensitive, so it's enough to test the lower case only.
2648     if ([request valueForHTTPHeaderField:cookieHeaderName]) {
2649       // Case insensitive search in |headers|.
2650       NSSet* cookieKeys = [currentItem->GetHttpRequestHeaders()
2651           keysOfEntriesPassingTest:^(id key, id obj, BOOL* stop) {
2652             NSString* header = (NSString*)key;
2653             const BOOL found =
2654                 [header caseInsensitiveCompare:cookieHeaderName] ==
2655                 NSOrderedSame;
2656             *stop = found;
2657             return found;
2658           }];
2659       DCHECK_EQ(1u, [cookieKeys count]);
2660       currentItem->RemoveHttpRequestHeaderForKey([cookieKeys anyObject]);
2661     }
2662   }
2665 // TODO(stuartmorgan): This is mostly logic from the original UIWebView delegate
2666 // method, which provides less information than the WKWebView version. Audit
2667 // this for things that should be handled in the subclass instead.
2668 - (BOOL)shouldAllowLoadWithRequest:(NSURLRequest*)request
2669                        targetFrame:(const web::FrameInfo*)targetFrame
2670                        isLinkClick:(BOOL)isLinkClick {
2671   GURL requestURL = net::GURLWithNSURL(request.URL);
2673   // Check if the request should be delayed.
2674   if (_externalRequest && _externalRequest->url == requestURL) {
2675     // Links that can't be shown in a tab by Chrome but can be handled by
2676     // external apps (e.g. tel:, mailto:) are opened directly despite the target
2677     // attribute on the link. We don't open a new tab for them because Mobile
2678     // Safari doesn't do that (and sites are expecting us to do the same) and
2679     // also because there would be nothing shown in that new tab; it would
2680     // remain on about:blank (see crbug.com/240178)
2681     if ([CRWWebController webControllerCanShow:requestURL] ||
2682         ![_delegate openExternalURL:requestURL]) {
2683       web::NewWindowInfo windowInfo = *_externalRequest;
2684       dispatch_async(dispatch_get_main_queue(), ^{
2685         [self openPopupWithInfo:windowInfo];
2686       });
2687     }
2688     _externalRequest.reset();
2689     return NO;
2690   }
2692   BOOL shouldCheckNativeApp = [self cancellable];
2694   // Check if the link navigation leads to a launch of an external app.
2695   // TODO(shreyasv): Change this such that handling/stealing of link navigations
2696   // is delegated to the WebDelegate and the logic around external app launching
2697   // is moved there as well.
2698   if (shouldCheckNativeApp || isLinkClick) {
2699     // Check If the URL is handled by a native app.
2700     if ([self urlTriggersNativeAppLaunch:requestURL
2701                                sourceURL:[self currentNavigationURL]]) {
2702       // External app has been launched successfully. Stop the current page
2703       // load operation (e.g. notifying all observers) and record the URL so
2704       // that errors reported following the 'NO' reply can be safely ignored.
2705       if ([self cancellable])
2706         [_delegate webPageOrderedClose];
2707       [self abortLoad];
2708       [_openedApplicationURL addObject:request.URL];
2709       return NO;
2710     }
2711   }
2713   // The WebDelegate may instruct the CRWWebController to stop loading, and
2714   // instead instruct the next page to be loaded in an animation.
2715   GURL mainDocumentURL = net::GURLWithNSURL(request.mainDocumentURL);
2716   DCHECK(self.webView);
2717   if (![self shouldOpenURL:requestURL
2718            mainDocumentURL:mainDocumentURL
2719                linkClicked:isLinkClick]) {
2720     return NO;
2721   }
2723   // If the URL doesn't look like one we can show, try to open the link with an
2724   // external application.
2725   // TODO(droger):  Check transition type before opening an external
2726   // application? For example, only allow it for TYPED and LINK transitions.
2727   if (![CRWWebController webControllerCanShow:requestURL]) {
2728     if (![self shouldOpenExternalURL:requestURL]) {
2729       return NO;
2730     }
2732     // Abort load if navigation is hapenning on the main frame. If |targetFrame|
2733     // is unknown use heuristic to guess the target frame by comparing
2734     // documentURL and navigation URL. This heuristic may have false positives.
2735     bool shouldAbortLoad = targetFrame ? targetFrame->is_main_frame
2736                                        : requestURL == mainDocumentURL;
2737     if (shouldAbortLoad)
2738       [self abortLoad];
2740     if ([_delegate openExternalURL:requestURL]) {
2741       // Record the URL so that errors reported following the 'NO' reply can be
2742       // safely ignored.
2743       [_openedApplicationURL addObject:request.URL];
2744       return NO;
2745     }
2746     return NO;
2747   }
2749   if ([[request HTTPMethod] isEqualToString:@"POST"]) {
2750     [self cachePOSTDataForRequest:request
2751                    inSessionEntry:[self currentSessionEntry]];
2752   }
2754   return YES;
2757 - (void)restoreStateAfterURLRejection {
2758   [[self sessionController] discardNonCommittedEntries];
2760   // Re-register the user agent, because UIWebView will sometimes try to read
2761   // the agent again from a saved search result page in which no other page has
2762   // yet been loaded. See crbug.com/260370.
2763   [self registerUserAgent];
2765   // Reset |_lastRegisteredRequestURL| so that it reflects the URL from before
2766   // the load was rejected. This value may be out of sync because
2767   // |_lastRegisteredRequestURL| may have already been updated before the load
2768   // was rejected.
2769   _lastRegisteredRequestURL = [self currentURL];
2770   _loadPhase = web::PAGE_LOADING;
2771   [self didFinishNavigation];
2774 - (void)handleLoadError:(NSError*)error inMainFrame:(BOOL)inMainFrame {
2775   // Attempt to translate iOS errors into their corresponding net errors.
2776   error = web::NetErrorFromError(error);
2778   if ([error code] == NSURLErrorUnsupportedURL)
2779     return;
2780   // In cases where a Plug-in handles the load do not take any further action.
2781   if ([[error domain] isEqual:WebKitErrorDomain] &&
2782       ([error code] == WebKitErrorPlugInLoadFailed ||
2783        [error code] == WebKitErrorCannotShowURL))
2784     return;
2786   // Continue processing only if the error is on the main request or is the
2787   // result of a user interaction.
2788   NSDictionary* userInfo = [error userInfo];
2789   // |userinfo| contains the request creation date as a NSDate.
2790   NSTimeInterval requestCreationDate =
2791       [[userInfo objectForKey:@"CreationDate"] timeIntervalSinceReferenceDate];
2792   bool userInteracted = false;
2793   if (requestCreationDate != 0.0) {
2794     NSTimeInterval timeSinceInteraction =
2795         requestCreationDate - _lastClickTimeInSeconds;
2796     // The error is considered to be the result of a user interaction if any
2797     // interaction happened just before the request was made.
2798     // TODO(droger): If the user interacted with the page after the request was
2799     // made (i.e. creationTimeSinceLastInteraction < 0), then
2800     // |_lastClickTimeInSeconds| has been overridden. The current behavior is to
2801     // discard the interstitial in that case. A better decision could be made if
2802     // we had a history of all the user interactions instead of just the last
2803     // one.
2804     userInteracted =
2805         timeSinceInteraction < kMaximumDelayForUserInteractionInSeconds &&
2806         _lastClickTimeInSeconds > _lastTransferTimeInSeconds &&
2807         timeSinceInteraction >= 0.0;
2808   } else {
2809     // If the error does not have timing information, check if the user
2810     // interacted with the page recently.
2811     userInteracted = [self userIsInteracting];
2812   }
2813   if (!inMainFrame && !userInteracted)
2814     return;
2816   NSURL* errorURL = [NSURL
2817       URLWithString:[userInfo objectForKey:NSURLErrorFailingURLStringErrorKey]];
2818   const GURL errorGURL = net::GURLWithNSURL(errorURL);
2820   // Handles Frame Load Interrupted errors from WebView.
2821   if ([[error domain] isEqualToString:WebKitErrorDomain] &&
2822       [error code] == WebKitErrorFrameLoadInterruptedByPolicyChange) {
2823     // See if the delegate wants to handle this case.
2824     if (errorGURL.is_valid() &&
2825         [_delegate
2826             respondsToSelector:@selector(
2827                                    controllerForUnhandledContentAtURL:)]) {
2828       id<CRWNativeContent> controller =
2829           [_delegate controllerForUnhandledContentAtURL:errorGURL];
2830       if (controller) {
2831         [self loadCompleteWithSuccess:NO];
2832         [self removeWebViewAllowingCachedReconstruction:NO];
2833         [self setNativeController:controller];
2834         [self loadNativeViewWithSuccess:YES];
2835         return;
2836       }
2837     }
2839     // Otherwise, handle the error normally.
2840     if ([_openedApplicationURL containsObject:errorURL])
2841       return;
2842     // Certain frame errors don't have URL information for some reason; for
2843     // those cases (so far the only known case is plugin content loaded directly
2844     // in a frame) just ignore the error. See crbug.com/414295
2845     if (!errorURL) {
2846       DCHECK(!inMainFrame);
2847       return;
2848     }
2849     // The wrapper error uses the URL of the error and not the requested URL
2850     // (which can be different in case of a redirect) to match desktop Chrome
2851     // behavior.
2852     NSError* wrapperError = [NSError
2853         errorWithDomain:[error domain]
2854                    code:[error code]
2855                userInfo:@{
2856                  NSURLErrorFailingURLStringErrorKey : [errorURL absoluteString],
2857                  NSUnderlyingErrorKey : error
2858                }];
2859     [self loadCompleteWithSuccess:NO];
2860     [self loadErrorInNativeView:wrapperError];
2861     return;
2862   }
2864   // Ignore cancelled errors.
2865   if ([error code] == NSURLErrorCancelled) {
2866     NSError* underlyingError = [userInfo objectForKey:NSUnderlyingErrorKey];
2867     if (underlyingError) {
2868       DCHECK([underlyingError isKindOfClass:[NSError class]]);
2870       // The Error contains an NSUnderlyingErrorKey so it's being generated
2871       // in the Chrome network stack. Aborting the load in this case.
2872       [self abortLoad];
2874       switch ([underlyingError code]) {
2875         case net::ERR_ABORTED:
2876           // |NSURLErrorCancelled| errors with underlying net error code
2877           // |net::ERR_ABORTED| are used by the Chrome network stack to
2878           // indicate that the current load should be aborted and the pending
2879           // entry should be discarded.
2880           [[self sessionController] discardNonCommittedEntries];
2881           break;
2882         case net::ERR_BLOCKED_BY_CLIENT:
2883           // |NSURLErrorCancelled| errors with underlying net error code
2884           // |net::ERR_BLOCKED_BY_CLIENT| are used by the Chrome network stack
2885           // to indicate that the current load should be aborted and the pending
2886           // entry should be kept.
2887           break;
2888         default:
2889           NOTREACHED();
2890       }
2891     }
2892     return;
2893   }
2895   [self loadCompleteWithSuccess:NO];
2896   [self loadErrorInNativeView:error];
2899 #pragma mark -
2900 #pragma mark WebUI
2902 - (void)createWebUIForURL:(const GURL&)URL {
2903   _webStateImpl->CreateWebUI(URL);
2906 - (void)clearWebUI {
2907   _webStateImpl->ClearWebUI();
2910 #pragma mark -
2911 #pragma mark UIGestureRecognizerDelegate
2913 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2914     shouldRecognizeSimultaneouslyWithGestureRecognizer:
2915         (UIGestureRecognizer*)otherGestureRecognizer {
2916   // Allows the custom UILongPressGestureRecognizer to fire simultaneously with
2917   // other recognizers.
2918   return YES;
2921 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2922        shouldReceiveTouch:(UITouch*)touch {
2923   // Expect only _contextMenuRecognizer.
2924   DCHECK([gestureRecognizer isEqual:_contextMenuRecognizer]);
2925   if (![self supportsCustomContextMenu]) {
2926     // Fetching context menu info is not a free operation, early return if a
2927     // context menu should not be shown.
2928     return YES;
2929   }
2931   // This is custom long press gesture recognizer. By the time the gesture is
2932   // recognized the web controller needs to know if there is a link under the
2933   // touch. If there a link, the web controller will reject system's context
2934   // menu and show another one. If for some reason context menu info is not
2935   // fetched - system context menu will be shown.
2936   [self setDOMElementForLastTouch:nullptr];
2937   base::WeakNSObject<CRWWebController> weakSelf(self);
2938   [self fetchDOMElementAtPoint:[touch locationInView:self.webView]
2939              completionHandler:^(scoped_ptr<base::DictionaryValue> element) {
2940                [weakSelf setDOMElementForLastTouch:element.Pass()];
2941              }];
2942   return YES;
2945 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer {
2946   // Expect only _contextMenuRecognizer.
2947   DCHECK([gestureRecognizer isEqual:_contextMenuRecognizer]);
2948   if (!self.webView || ![self supportsCustomContextMenu]) {
2949     // Show the context menu iff currently displaying a web view.
2950     // Do nothing for native views.
2951     return NO;
2952   }
2954   UMA_HISTOGRAM_BOOLEAN("WebController.FetchContextMenuInfoAsyncSucceeded",
2955                         _DOMElementForLastTouch);
2957   return _DOMElementForLastTouch && !_DOMElementForLastTouch->empty();
2960 #pragma mark -
2961 #pragma mark CRWRequestTrackerDelegate
2963 - (BOOL)isForStaticFileRequests {
2964   return NO;
2967 - (void)updatedSSLStatus:(const web::SSLStatus&)sslStatus
2968               forPageUrl:(const GURL&)url
2969                 userInfo:(id)userInfo {
2970   // |userInfo| is a CRWSessionEntry.
2971   web::NavigationItem* item =
2972       [static_cast<CRWSessionEntry*>(userInfo) navigationItem];
2973   if (!item)
2974     return;  // This is a request update for an entry that no longer exists.
2976   // This condition happens infrequently when a page load is misinterpreted as
2977   // a resource load from a previous page. This can happen when moving quickly
2978   // back and forth through history, the notifications from the web view on the
2979   // UI thread and the one from the requests at the net layer may get out of
2980   // sync. This catches this case and prevent updating an entry with the wrong
2981   // SSL data.
2982   if (item->GetURL().GetOrigin() != url.GetOrigin())
2983     return;
2985   if (item->GetSSL().Equals(sslStatus))
2986     return;  // No need to update with the same data.
2988   item->GetSSL() = sslStatus;
2990   // Notify the UI it needs to refresh if the updated entry is the current
2991   // entry.
2992   if (userInfo == self.currentSessionEntry) {
2993     [self didUpdateSSLStatusForCurrentNavigationItem];
2994   }
2997 - (void)handleResponseHeaders:(net::HttpResponseHeaders*)headers
2998                    requestUrl:(const GURL&)requestUrl {
2999   _webStateImpl->OnHttpResponseHeadersReceived(headers, requestUrl);
3002 - (void)presentSSLError:(const net::SSLInfo&)info
3003            forSSLStatus:(const web::SSLStatus&)status
3004                   onUrl:(const GURL&)url
3005             recoverable:(BOOL)recoverable
3006                callback:(SSLErrorCallback)shouldContinue {
3007   DCHECK(_delegate);
3008   DCHECK_EQ(url, [self currentNavigationURL]);
3009   [_delegate presentSSLError:info
3010                 forSSLStatus:status
3011                  recoverable:recoverable
3012                     callback:shouldContinue];
3015 - (void)updatedProgress:(float)progress {
3016   if ([_delegate
3017           respondsToSelector:@selector(webController:didUpdateProgress:)]) {
3018     [_delegate webController:self didUpdateProgress:progress];
3019   }
3022 - (void)certificateUsed:(net::X509Certificate*)certificate
3023                 forHost:(const std::string&)host
3024                  status:(net::CertStatus)status {
3025   [[[self sessionController] sessionCertificatePolicyManager]
3026       registerAllowedCertificate:certificate
3027                          forHost:host
3028                           status:status];
3031 - (void)clearCertificates {
3032   [[[self sessionController] sessionCertificatePolicyManager]
3033       clearCertificates];
3036 #pragma mark -
3037 #pragma mark Popup handling
3039 - (void)openPopupWithInfo:(const web::NewWindowInfo&)windowInfo {
3040   const GURL url(windowInfo.url);
3041   const GURL currentURL([self currentNavigationURL]);
3042   NSString* windowName = windowInfo.window_name.get();
3043   web::Referrer referrer(currentURL, windowInfo.referrer_policy);
3044   base::WeakNSObject<CRWWebController> weakSelf(self);
3045   void (^showPopupHandler)() = ^{
3046     CRWWebController* child = [[weakSelf delegate] webPageOrderedOpen:url
3047                                                              referrer:referrer
3048                                                            windowName:windowName
3049                                                          inBackground:NO];
3050     DCHECK(!child || child.sessionController.openedByDOM);
3051   };
3053   BOOL showPopup = windowInfo.user_is_interacting ||
3054                    (![self shouldBlockPopupWithURL:url sourceURL:currentURL]);
3055   if (showPopup) {
3056     showPopupHandler();
3057   } else if ([_delegate
3058                  respondsToSelector:@selector(webController:didBlockPopup:)]) {
3059     web::BlockedPopupInfo blockedPopupInfo(url, referrer, windowName,
3060                                            showPopupHandler);
3061     [_delegate webController:self didBlockPopup:blockedPopupInfo];
3062   }
3065 #pragma mark -
3066 #pragma mark TouchTracking
3068 - (void)touched:(BOOL)touched {
3069   _clickInProgress = touched;
3070   if (touched) {
3071     _userInteractionRegistered = YES;
3072     _lastClickTimeInSeconds = CFAbsoluteTimeGetCurrent();
3073   }
3076 - (CRWTouchTrackingRecognizer*)touchTrackingRecognizer {
3077   if (!_touchTrackingRecognizer) {
3078     _touchTrackingRecognizer.reset(
3079         [[CRWTouchTrackingRecognizer alloc] initWithDelegate:self]);
3080   }
3081   return _touchTrackingRecognizer.get();
3084 - (BOOL)userIsInteracting {
3085   // If page transfer started after last click, user is deemed to be no longer
3086   // interacting.
3087   if (_lastTransferTimeInSeconds > _lastClickTimeInSeconds)
3088     return NO;
3089   return [self userClickedRecently];
3092 - (BOOL)userClickedRecently {
3093   return _clickInProgress ||
3094          ((CFAbsoluteTimeGetCurrent() - _lastClickTimeInSeconds) <
3095           kMaximumDelayForUserInteractionInSeconds);
3098 #pragma mark Placeholder Overlay Methods
3100 - (void)addPlaceholderOverlay {
3101   if (!_overlayPreviewMode) {
3102     // Create |kSnapshotOverlayDelay| second timer to remove image with
3103     // transition.
3104     [self performSelector:@selector(removePlaceholderOverlay)
3105                withObject:nil
3106                afterDelay:kSnapshotOverlayDelay];
3107   }
3109   // Add overlay image.
3110   _placeholderOverlayView.reset([[UIImageView alloc] init]);
3111   CGRect frame = [self visibleFrame];
3112   [_placeholderOverlayView setFrame:frame];
3113   [_placeholderOverlayView
3114       setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
3115                           UIViewAutoresizingFlexibleHeight];
3116   [_placeholderOverlayView setContentMode:UIViewContentModeScaleAspectFill];
3117   [_containerView addSubview:_placeholderOverlayView];
3119   id callback = ^(UIImage* image) {
3120     [_placeholderOverlayView setImage:image];
3121   };
3122   [_delegate webController:self retrievePlaceholderOverlayImage:callback];
3124   if (!_placeholderOverlayView.get().image) {
3125     // TODO(justincohen): This is just a blank white image. Consider fading in
3126     // the snapshot when it comes in instead.
3127     // TODO(shreyasv): This is just a blank white image. Consider adding an API
3128     // so that the delegate can return something immediately for the default
3129     // overlay image.
3130     _placeholderOverlayView.get().image = [[self class] defaultSnapshotImage];
3131   }
3134 - (void)removePlaceholderOverlay {
3135   if (!_placeholderOverlayView || _overlayPreviewMode)
3136     return;
3138   [NSObject cancelPreviousPerformRequestsWithTarget:self
3139                                            selector:@selector(removeOverlay)
3140                                              object:nil];
3141   // Remove overlay with transition.
3142   [UIView animateWithDuration:kSnapshotOverlayTransition
3143       animations:^{
3144         [_placeholderOverlayView setAlpha:0.0f];
3145       }
3146       completion:^(BOOL finished) {
3147         [_placeholderOverlayView removeFromSuperview];
3148         _placeholderOverlayView.reset();
3149       }];
3152 - (void)setOverlayPreviewMode:(BOOL)overlayPreviewMode {
3153   _overlayPreviewMode = overlayPreviewMode;
3155   // If we were showing the preview, remove it.
3156   if (!_overlayPreviewMode && _placeholderOverlayView) {
3157     _containerView.reset();
3158     // Reset |_placeholderOverlayView| directly instead of calling
3159     // -removePlaceholderOverlay, which removes |_placeholderOverlayView| in an
3160     // animation.
3161     [_placeholderOverlayView removeFromSuperview];
3162     _placeholderOverlayView.reset();
3163     // There are cases when resetting the contentView, above, may happen after
3164     // the web view has been created. Re-add it here, rather than
3165     // relying on a subsequent call to loadCurrentURLInWebView.
3166     if (self.webView) {
3167       [[self view] addSubview:self.webView];
3168     }
3169   }
3172 - (void)internalSuppressDialogs:(BOOL)suppressFlag notify:(BOOL)notifyFlag {
3173   NSString* const kSetSuppressDialogs =
3174       [NSString stringWithFormat:@"__gCrWeb.setSuppressDialogs(%d, %d);",
3175                                  suppressFlag, notifyFlag];
3176   [self setSuppressDialogsWithHelperScript:kSetSuppressDialogs];
3179 - (void)setPageDialogOpenPolicy:(web::PageDialogOpenPolicy)policy {
3180   switch (policy) {
3181     case web::DIALOG_POLICY_ALLOW:
3182       [self setSuppressDialogs:NO notify:NO];
3183       return;
3184     case web::DIALOG_POLICY_NOTIFY_FIRST:
3185       [self setSuppressDialogs:NO notify:YES];
3186       return;
3187     case web::DIALOG_POLICY_SUPPRESS:
3188       [self setSuppressDialogs:YES notify:YES];
3189       return;
3190   }
3191   NOTREACHED();
3194 - (void)setSuppressDialogs:(BOOL)suppressFlag notify:(BOOL)notifyFlag {
3195   if (self.webView && [_earlyScriptManager hasBeenInjected]) {
3196     [self internalSuppressDialogs:suppressFlag notify:notifyFlag];
3197   } else {
3198     _setSuppressDialogsLater = suppressFlag;
3199     _setNotifyAboutDialogsLater = notifyFlag;
3200   }
3203 #pragma mark -
3204 #pragma mark Session Information
3206 - (CRWSessionController*)sessionController {
3207   DCHECK(_webStateImpl);
3208   return _webStateImpl->GetNavigationManagerImpl().GetSessionController();
3211 - (CRWSessionEntry*)currentSessionEntry {
3212   return [[self sessionController] currentEntry];
3215 - (web::NavigationItem*)currentNavItem {
3216   // This goes through the legacy Session* interface rather than Navigation*
3217   // because it is itself a legacy method that should not exist, and this
3218   // avoids needing to add a GetActiveItem to NavigationManager. If/when this
3219   // method chain becomes a blocker to eliminating SessionController, the logic
3220   // can be moved here, using public NavigationManager getters. That's not
3221   // done now in order to avoid code duplication.
3222   return [[self currentSessionEntry] navigationItem];
3225 - (const GURL&)currentNavigationURL {
3226   // TODO(stuartmorgan): Fix the fact that this method doesn't have clear usage
3227   // delination that would allow changing to one of the non-deprecated URL
3228   // calls.
3229   web::NavigationItem* item = [self currentNavItem];
3230   return item ? item->GetVirtualURL() : GURL::EmptyGURL();
3233 - (ui::PageTransition)currentTransition {
3234   if ([self currentNavItem])
3235     return [self currentNavItem]->GetTransitionType();
3236   else
3237     return ui::PageTransitionFromInt(0);
3240 - (web::Referrer)currentSessionEntryReferrer {
3241   web::NavigationItem* currentItem = [self currentNavItem];
3242   return currentItem ? currentItem->GetReferrer() : web::Referrer();
3245 - (NSData*)currentPOSTData {
3246   DCHECK([self currentSessionEntry]);
3247   return [self currentSessionEntry].navigationItemImpl->GetPostData();
3250 - (NSDictionary*)currentHttpHeaders {
3251   DCHECK([self currentSessionEntry]);
3252   return [self currentSessionEntry].navigationItem->GetHttpRequestHeaders();
3255 #pragma mark -
3256 #pragma mark Page State
3258 - (void)recordStateInHistory {
3259   // Check that the url in the web view matches the url in the history entry.
3260   CRWSessionEntry* current = [self currentSessionEntry];
3261   if (current && [current navigationItem]->GetURL() == [self currentURL])
3262     [current navigationItem]->SetPageScrollState(self.pageScrollState);
3265 - (void)restoreStateFromHistory {
3266   CRWSessionEntry* current = [self currentSessionEntry];
3267   if ([current navigationItem])
3268     self.pageScrollState = [current navigationItem]->GetPageScrollState();
3271 - (web::PageScrollState)pageScrollState {
3272   web::PageScrollState scrollState;
3273   if (self.webView) {
3274     CGPoint scrollOffset = [self scrollPosition];
3275     scrollState.set_scroll_offset_x(std::floor(scrollOffset.x));
3276     scrollState.set_scroll_offset_y(std::floor(scrollOffset.y));
3277     UIScrollView* scrollView = self.webScrollView;
3278     scrollState.set_minimum_zoom_scale(scrollView.minimumZoomScale);
3279     scrollState.set_maximum_zoom_scale(scrollView.maximumZoomScale);
3280     scrollState.set_zoom_scale(scrollView.zoomScale);
3281   } else {
3282     // TODO(kkhorimoto): Handle native views.
3283   }
3284   return scrollState;
3287 - (void)setPageScrollState:(web::PageScrollState)pageScrollState {
3288   if (!pageScrollState.IsValid())
3289     return;
3290   if (self.webView) {
3291     // Page state is restored after a page load completes.  If the user has
3292     // scrolled or changed the zoom scale while the page is still loading, don't
3293     // restore any state since it will confuse the user.  Since the web view's
3294     // zoom scale range may have changed during rendering, check the absolute
3295     // zoom scale rather than doing a simple equality comparison.
3296     web::PageScrollState currentScrollState = self.pageScrollState;
3297     if (currentScrollState.scroll_offset_x() ==
3298             _scrollStateOnStartLoading.scroll_offset_x() &&
3299         currentScrollState.scroll_offset_y() ==
3300             _scrollStateOnStartLoading.scroll_offset_y() &&
3301         [self absoluteZoomScaleForScrollState:currentScrollState] ==
3302             [self absoluteZoomScaleForScrollState:_scrollStateOnStartLoading]) {
3303       base::WeakNSObject<CRWWebController> weakSelf(self);
3304       [self queryUserScalableProperty:^(BOOL isUserScalable) {
3305         base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
3306         [strongSelf applyPageScrollState:pageScrollState
3307                             userScalable:isUserScalable];
3308       }];
3309     }
3310   }
3313 - (void)applyPageScrollState:(const web::PageScrollState&)scrollState
3314                 userScalable:(BOOL)isUserScalable {
3315   DCHECK(scrollState.IsValid());
3316   if (isUserScalable) {
3317     [self prepareToApplyWebViewScrollZoomScale];
3318     [self applyWebViewScrollZoomScaleFromScrollState:scrollState];
3319     [self finishApplyingWebViewScrollZoomScale];
3320   }
3321   [self applyWebViewScrollOffsetFromScrollState:scrollState];
3324 - (void)prepareToApplyWebViewScrollZoomScale {
3325   id webView = self.webView;
3326   if (![webView respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
3327     return;
3328   }
3330   UIView* contentView = [webView viewForZoomingInScrollView:self.webScrollView];
3332   if ([webView
3333           respondsToSelector:@selector(scrollViewWillBeginZooming:withView:)]) {
3334     [webView scrollViewWillBeginZooming:self.webScrollView
3335                                withView:contentView];
3336   }
3339 - (void)finishApplyingWebViewScrollZoomScale {
3340   id webView = self.webView;
3341   if ([webView respondsToSelector:@selector(scrollViewDidEndZooming:
3342                                                            withView:
3343                                                             atScale:)] &&
3344       [webView respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
3345     // This correctly sets the content's frame in the scroll view to
3346     // fit the web page and upscales the content so that it isn't
3347     // blurry.
3348     UIView* contentView =
3349         [webView viewForZoomingInScrollView:self.webScrollView];
3350     [webView scrollViewDidEndZooming:self.webScrollView
3351                             withView:contentView
3352                              atScale:self.webScrollView.zoomScale];
3353   }
3356 - (void)applyWebViewScrollZoomScaleFromScrollState:
3357     (const web::PageScrollState&)scrollState {
3358   // Subclasses must implement this method.
3359   NOTREACHED();
3362 - (void)applyWebViewScrollOffsetFromScrollState:
3363     (const web::PageScrollState&)scrollState {
3364   DCHECK(scrollState.IsValid());
3365   CGPoint scrollOffset =
3366       CGPointMake(scrollState.scroll_offset_x(), scrollState.scroll_offset_y());
3367   if (_loadPhase == web::PAGE_LOADED) {
3368     // If the page is loaded, update the scroll immediately.
3369     [self.webScrollView setContentOffset:scrollOffset];
3370   } else {
3371     // If the page isn't loaded, store the action to update the scroll
3372     // when the page finishes loading.
3373     base::WeakNSObject<UIScrollView> weakScrollView(self.webScrollView);
3374     base::scoped_nsprotocol<ProceduralBlock> action([^{
3375       [weakScrollView setContentOffset:scrollOffset];
3376     } copy]);
3377     [_pendingLoadCompleteActions addObject:action];
3378   }
3381 #pragma mark -
3382 #pragma mark Web Page Features
3384 // TODO(eugenebut): move JS parsing code to a separate file.
3385 - (void)queryUserScalableProperty:(void (^)(BOOL))responseHandler {
3386   NSString* const kViewPortContentQuery =
3387       @"var viewport = document.querySelector('meta[name=\"viewport\"]');"
3388        "viewport ? viewport.content : '';";
3389   [self evaluateJavaScript:kViewPortContentQuery
3390        stringResultHandler:^(NSString* viewPortContent, NSError* error) {
3391          responseHandler(
3392              GetUserScalablePropertyFromViewPortContent(viewPortContent));
3393        }];
3396 - (void)fetchWebPageWidthWithCompletionHandler:(void (^)(CGFloat))handler {
3397   if (!self.webView) {
3398     handler(0);
3399     return;
3400   }
3402   [self evaluateJavaScript:@"__gCrWeb.getPageWidth();"
3403        stringResultHandler:^(NSString* pageWidthAsString, NSError*) {
3404          handler([pageWidthAsString floatValue]);
3405        }];
3408 - (void)fetchDOMElementAtPoint:(CGPoint)point
3409              completionHandler:
3410                  (void (^)(scoped_ptr<base::DictionaryValue>))handler {
3411   DCHECK(handler);
3412   // Convert point into web page's coordinate system (which may be scaled and/or
3413   // scrolled).
3414   CGPoint scrollOffset = self.scrollPosition;
3415   CGFloat webViewContentWidth = self.webScrollView.contentSize.width;
3416   base::WeakNSObject<CRWWebController> weakSelf(self);
3417   [self fetchWebPageWidthWithCompletionHandler:^(CGFloat pageWidth) {
3418     CGFloat scale = pageWidth / webViewContentWidth;
3419     CGPoint localPoint = CGPointMake((point.x + scrollOffset.x) * scale,
3420                                      (point.y + scrollOffset.y) * scale);
3421     NSString* const kGetElementScript =
3422         [NSString stringWithFormat:@"__gCrWeb.getElementFromPoint(%g, %g);",
3423                                    localPoint.x, localPoint.y];
3424     [weakSelf evaluateJavaScript:kGetElementScript
3425                JSONResultHandler:^(scoped_ptr<base::Value> element, NSError*) {
3426                  // Release raw element and call handler with DictionaryValue.
3427                  scoped_ptr<base::DictionaryValue> elementAsDict;
3428                  if (element) {
3429                    base::DictionaryValue* elementAsDictPtr = nullptr;
3430                    element.release()->GetAsDictionary(&elementAsDictPtr);
3431                    // |rawElement| and |elementPtr| now point to the same
3432                    // memory. |elementPtr| ownership will be transferred to
3433                    // |element| scoped_ptr.
3434                    elementAsDict.reset(elementAsDictPtr);
3435                  }
3436                  handler(elementAsDict.Pass());
3437                }];
3438   }];
3441 - (NSDictionary*)contextMenuInfoForElement:(base::DictionaryValue*)element {
3442   DCHECK(element);
3443   NSMutableDictionary* mutableInfo = [NSMutableDictionary dictionary];
3444   NSString* title = nil;
3445   std::string href;
3446   if (element->GetString("href", &href)) {
3447     mutableInfo[web::kContextLinkURLString] = base::SysUTF8ToNSString(href);
3448     GURL linkURL(href);
3449     if (linkURL.SchemeIs(url::kJavaScriptScheme)) {
3450       title = @"JavaScript";
3451     } else {
3452       DCHECK(web::GetWebClient());
3453       const std::string& acceptLangs = web::GetWebClient()->GetAcceptLangs(
3454           self.webStateImpl->GetBrowserState());
3455       base::string16 urlText = net::FormatUrl(GURL(href), acceptLangs);
3456       title = base::SysUTF16ToNSString(urlText);
3457     }
3458   }
3459   std::string src;
3460   if (element->GetString("src", &src)) {
3461     mutableInfo[web::kContextImageURLString] = base::SysUTF8ToNSString(src);
3462     if (!title)
3463       title = base::SysUTF8ToNSString(src);
3464     if ([title hasPrefix:@"data:"])
3465       title = @"";
3466   }
3467   std::string titleAttribute;
3468   if (element->GetString("title", &titleAttribute))
3469     title = base::SysUTF8ToNSString(titleAttribute);
3470   std::string referrerPolicy;
3471   element->GetString("referrerPolicy", &referrerPolicy);
3472   mutableInfo[web::kContextLinkReferrerPolicy] =
3473       @([self referrerPolicyFromString:referrerPolicy]);
3474   if (title)
3475     mutableInfo[web::kContextTitle] = title;
3476   return [[mutableInfo copy] autorelease];
3479 #pragma mark -
3480 #pragma mark Fullscreen
3482 - (CGRect)visibleFrame {
3483   CGRect frame = [_containerView bounds];
3484   CGFloat headerHeight = [self headerHeight];
3485   frame.origin.y = headerHeight;
3486   frame.size.height -= headerHeight;
3487   return frame;
3490 - (void)optOutScrollsToTopForSubviews {
3491   NSMutableArray* stack =
3492       [NSMutableArray arrayWithArray:[self.webScrollView subviews]];
3493   while (stack.count) {
3494     UIView* current = [stack lastObject];
3495     [stack removeLastObject];
3496     [stack addObjectsFromArray:[current subviews]];
3497     if ([current isKindOfClass:[UIScrollView class]])
3498       static_cast<UIScrollView*>(current).scrollsToTop = NO;
3499   }
3502 #pragma mark -
3503 #pragma mark WebDelegate Calls
3505 - (BOOL)shouldOpenURL:(const GURL&)url
3506       mainDocumentURL:(const GURL&)mainDocumentURL
3507           linkClicked:(BOOL)linkClicked {
3508   if (![_delegate respondsToSelector:@selector(webController:
3509                                                shouldOpenURL:
3510                                              mainDocumentURL:
3511                                                  linkClicked:)]) {
3512     return YES;
3513   }
3514   return [_delegate webController:self
3515                     shouldOpenURL:url
3516                   mainDocumentURL:mainDocumentURL
3517                       linkClicked:linkClicked];
3520 - (BOOL)shouldOpenExternalURL:(const GURL&)url {
3521   return [_delegate respondsToSelector:@selector(webController:
3522                                            shouldOpenExternalURL:)] &&
3523          [_delegate webController:self shouldOpenExternalURL:url];
3526 - (BOOL)urlTriggersNativeAppLaunch:(const GURL&)url
3527                          sourceURL:(const GURL&)sourceURL {
3528   return
3529       [_delegate respondsToSelector:@selector(urlTriggersNativeAppLaunch:
3530                                                                sourceURL:)] &&
3531       [_delegate urlTriggersNativeAppLaunch:url sourceURL:sourceURL];
3534 - (CGFloat)headerHeight {
3535   if (![_delegate respondsToSelector:@selector(headerHeightForWebController:)])
3536     return 0.0f;
3537   return [_delegate headerHeightForWebController:self];
3540 - (BOOL)shouldBlockPopupWithURL:(const GURL&)popupURL
3541                       sourceURL:(const GURL&)sourceURL {
3542   if (![_delegate respondsToSelector:@selector(webController:
3543                                          shouldBlockPopupWithURL:
3544                                                        sourceURL:)]) {
3545     return NO;
3546   }
3547   return [_delegate webController:self
3548           shouldBlockPopupWithURL:popupURL
3549                         sourceURL:sourceURL];
3552 - (void)didUpdateHistoryStateWithPageURL:(const GURL&)url {
3553   _webStateImpl->GetRequestTracker()->HistoryStateChange(url);
3554   [_delegate webDidUpdateHistoryStateWithPageURL:url];
3557 - (void)didUpdateSSLStatusForCurrentNavigationItem {
3558   if ([_delegate respondsToSelector:
3559           @selector(
3560               webControllerDidUpdateSSLStatusForCurrentNavigationItem:)]) {
3561     [_delegate webControllerDidUpdateSSLStatusForCurrentNavigationItem:self];
3562   }
3565 #pragma mark CRWWebControllerScripting Methods
3567 - (void)loadHTML:(NSString*)html {
3568   [self loadHTML:html forURL:GURL(url::kAboutBlankURL)];
3571 - (void)loadHTMLForCurrentURL:(NSString*)html {
3572   [self loadHTML:html forURL:self.currentURL];
3575 - (void)loadHTML:(NSString*)html forURL:(const GURL&)url {
3576   // Remove the interstitial before doing anything else.
3577   [self clearInterstitials];
3579   DLOG_IF(WARNING, !self.webView)
3580       << "self.webView null while trying to load HTML";
3581   _loadPhase = web::LOAD_REQUESTED;
3582   [self loadWebHTMLString:html forURL:url];
3585 - (void)loadHTML:(NSString*)HTML forAppSpecificURL:(const GURL&)URL {
3586   CHECK(web::GetWebClient()->IsAppSpecificURL(URL));
3587   [self loadHTML:HTML forURL:URL];
3590 - (void)stopLoading {
3591   web::RecordAction(UserMetricsAction("Stop"));
3592   // Discard the pending and transient entried before notifying the tab model
3593   // observers of the change via |-abortLoad|.
3594   [[self sessionController] discardNonCommittedEntries];
3595   [self abortLoad];
3596   // If discarding the non-committed entries results in an app-specific URL,
3597   // reload it in its native view.
3598   if (!_nativeController &&
3599       [self shouldLoadURLInNativeView:[self currentNavigationURL]]) {
3600     [self loadCurrentURLInNativeView];
3601   }
3604 - (void)orderClose {
3605   if (self.sessionController.openedByDOM) {
3606     [_delegate webPageOrderedClose];
3607   }
3610 #pragma mark -
3611 #pragma mark Testing-Only Methods
3613 - (void)injectWebView:(id)webView {
3614   [self removeWebViewAllowingCachedReconstruction:NO];
3616   _lastRegisteredRequestURL = _defaultURL;
3617   CHECK([webView respondsToSelector:@selector(scrollView)]);
3618   [_webViewProxy setWebView:webView
3619                  scrollView:[static_cast<id>(webView) scrollView]];
3622 - (void)resetInjectedWebView {
3623   [self resetWebView];
3626 - (void)addObserver:(id<CRWWebControllerObserver>)observer {
3627   DCHECK(observer);
3628   if (!_observers) {
3629     // We don't want our observer set to block dealloc on the observers. For the
3630     // observer container, make an object compatible with NSMutableSet that does
3631     // not perform retain or release on the contained objects (weak references).
3632     CFSetCallBacks callbacks =
3633         {0, NULL, NULL, CFCopyDescription, CFEqual, CFHash};
3634     _observers.reset(base::mac::CFToNSCast(
3635         CFSetCreateMutable(kCFAllocatorDefault, 1, &callbacks)));
3636   }
3637   DCHECK(![_observers containsObject:observer]);
3638   [_observers addObject:observer];
3639   _observerBridges.push_back(
3640       new web::WebControllerObserverBridge(observer, self.webStateImpl, self));
3642   if ([observer respondsToSelector:@selector(setWebViewProxy:controller:)])
3643     [observer setWebViewProxy:_webViewProxy controller:self];
3646 - (void)removeObserver:(id<CRWWebControllerObserver>)observer {
3647   // TODO(jimblackler): make _observers use NSMapTable. crbug.com/367992
3648   DCHECK([_observers containsObject:observer]);
3649   [_observers removeObject:observer];
3650   // Remove the associated WebControllerObserverBridge.
3651   auto it = std::find_if(_observerBridges.begin(), _observerBridges.end(),
3652                          [observer](web::WebControllerObserverBridge* bridge) {
3653                            return bridge->web_controller_observer() == observer;
3654                          });
3655   DCHECK(it != _observerBridges.end());
3656   _observerBridges.erase(it);
3659 - (NSUInteger)observerCount {
3660   DCHECK_EQ(_observerBridges.size(), [_observers count]);
3661   return [_observers count];
3664 - (NSString*)windowId {
3665   return [_windowIDJSManager windowId];
3668 - (void)setWindowId:(NSString*)windowId {
3669   return [_windowIDJSManager setWindowId:windowId];
3672 - (NSString*)lastSeenWindowID {
3673   return _lastSeenWindowID;
3676 - (void)setURLOnStartLoading:(const GURL&)url {
3677   _URLOnStartLoading = url;
3680 - (const GURL&)defaultURL {
3681   return _defaultURL;
3684 - (GURL)URLOnStartLoading {
3685   return _URLOnStartLoading;
3688 - (GURL)lastRegisteredRequestURL {
3689   return _lastRegisteredRequestURL;
3692 - (void)simulateLoadRequestWithURL:(const GURL&)URL {
3693   _lastRegisteredRequestURL = URL;
3694   _loadPhase = web::LOAD_REQUESTED;
3697 - (NSString*)externalRequestWindowName {
3698   if (!_externalRequest || !_externalRequest->window_name)
3699     return @"";
3700   return _externalRequest->window_name;
3703 - (void)resetExternalRequest {
3704   _externalRequest.reset();
3707 @end