Upstream WebController/WebStateImpl in ios/web/
[chromium-blink-merge.git] / ios / web / web_state / ui / crw_web_controller.mm
blob2ab2910edd6fe1478aa2312c141628c68cd87dbc
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/js/credential_util.h"
63 #import "ios/web/web_state/js/crw_js_early_script_manager.h"
64 #import "ios/web/web_state/js/crw_js_plugin_placeholder_manager.h"
65 #import "ios/web/web_state/js/crw_js_window_id_manager.h"
66 #import "ios/web/web_state/ui/crw_context_menu_provider.h"
67 #import "ios/web/web_state/ui/crw_swipe_recognizer_provider.h"
68 #import "ios/web/web_state/ui/crw_ui_web_view_web_controller.h"
69 #import "ios/web/web_state/ui/crw_web_controller+protected.h"
70 #import "ios/web/web_state/ui/crw_web_controller_container_view.h"
71 #import "ios/web/web_state/ui/crw_wk_web_view_web_controller.h"
72 #import "ios/web/web_state/web_controller_observer_bridge.h"
73 #include "ios/web/web_state/web_state_facade_delegate.h"
74 #import "ios/web/web_state/web_state_impl.h"
75 #import "net/base/mac/url_conversions.h"
76 #include "net/base/net_errors.h"
77 #include "net/base/net_util.h"
78 #import "ui/base/ios/cru_context_menu_holder.h"
79 #include "ui/base/page_transition_types.h"
80 #include "url/gurl.h"
81 #include "url/url_constants.h"
83 using base::UserMetricsAction;
84 using web::NavigationManagerImpl;
85 using web::WebState;
86 using web::WebStateImpl;
88 namespace web {
90 NSString* const kPageChangedNotification = @"kPageChangedNotification";
91 NSString* const kContainerViewID = @"Container View";
92 const char* kWindowNameSeparator = "#";
93 NSString* const kUserIsInteractingKey = @"userIsInteracting";
94 NSString* const kOriginURLKey = @"originURL";
95 NSString* const kLogJavaScript = @"LogJavascript";
97 NewWindowInfo::NewWindowInfo(GURL target_url,
98                              NSString* target_window_name,
99                              web::ReferrerPolicy target_referrer_policy,
100                              bool target_user_is_interacting)
101     : url(target_url),
102       window_name([target_window_name copy]),
103       referrer_policy(target_referrer_policy),
104       user_is_interacting(target_user_is_interacting) {
107 NewWindowInfo::~NewWindowInfo() {
110 }  // namespace web
112 namespace {
114 // A tag for the web view, so that tests can identify it. This is used instead
115 // of exposing a getter (and deliberately not exposed in the header) to make it
116 // *very* clear that this is a hack which should only be used as a last resort.
117 const NSUInteger kWebViewTag = 0x3eb71e3;
119 // Cancels touch events for the given gesture recognizer.
120 void CancelTouches(UIGestureRecognizer* gesture_recognizer) {
121   if (gesture_recognizer.enabled) {
122     gesture_recognizer.enabled = NO;
123     gesture_recognizer.enabled = YES;
124   }
127 // Cancels all touch events for web view (long presses, tapping, scrolling).
128 void CancelAllTouches(UIScrollView* web_scroll_view) {
129   // Disable web view scrolling.
130   CancelTouches(web_scroll_view.panGestureRecognizer);
132   // All user gestures are handled by a subview of web view scroll view
133   // (UIWebBrowserView for UIWebView and WKContentView for WKWebView).
134   for (UIView* subview in web_scroll_view.subviews) {
135     for (UIGestureRecognizer* recognizer in subview.gestureRecognizers) {
136       CancelTouches(recognizer);
137     }
138   }
141 }  // namespace
143 @interface CRWWebController () <CRWNativeContentDelegate> {
144   base::WeakNSProtocol<id<CRWWebDelegate>> _delegate;
145   base::WeakNSProtocol<id<CRWWebUserInterfaceDelegate>> _UIDelegate;
146   base::WeakNSProtocol<id<CRWNativeContentProvider>> _nativeProvider;
147   base::WeakNSProtocol<id<CRWSwipeRecognizerProvider>> _swipeRecognizerProvider;
148   base::scoped_nsobject<CRWWebControllerContainerView> _containerView;
149   // The CRWWebViewProxy is the wrapper to give components access to the
150   // web view in a controlled and limited way.
151   base::scoped_nsobject<CRWWebViewProxyImpl> _webViewProxy;
152   // If |_contentView| contains a native view rather than a web view, this
153   // is its controller. If it's a web view, this is nil.
154   base::scoped_nsprotocol<id<CRWNativeContent>> _nativeController;
155   BOOL _isHalted;  // YES if halted. Halting happens prior to destruction.
156   BOOL _isBeingDestroyed;  // YES if in the process of closing.
157   // All CRWWebControllerObservers attached to the CRWWebController. A
158   // specially-constructed set is used that does not retain its elements.
159   base::scoped_nsobject<NSMutableSet> _observers;
160   // Each observer in |_observers| is associated with a
161   // WebControllerObserverBridge in order to listen from WebState callbacks.
162   // TODO(droger): Remove |_observerBridges| when all CRWWebControllerObservers
163   // are converted to WebStateObservers.
164   ScopedVector<web::WebControllerObserverBridge> _observerBridges;
165   // |windowId| that is saved when a page changes. Used to detect refreshes.
166   base::scoped_nsobject<NSString> _lastSeenWindowID;
167   // YES if a user interaction has been registered at any time once the page has
168   // loaded.
169   BOOL _userInteractionRegistered;
170   // Last URL change reported to webWill/DidStartLoadingURL. Used to detect page
171   // location changes (client redirects) in practice.
172   GURL _lastRegisteredRequestURL;
173   // Last URL change reported to webDidStartLoadingURL. Used to detect page
174   // location changes in practice.
175   GURL _URLOnStartLoading;
176   // Page loading phase.
177   web::LoadPhase _loadPhase;
178   // The web::PageScrollState recorded when the page starts loading.
179   web::PageScrollState _scrollStateOnStartLoading;
180   // Actions to execute once the page load is complete.
181   base::scoped_nsobject<NSMutableArray> _pendingLoadCompleteActions;
182   // UIGestureRecognizers to add to the web view.
183   base::scoped_nsobject<NSMutableArray> _gestureRecognizers;
184   // Toolbars to add to the web view.
185   base::scoped_nsobject<NSMutableArray> _webViewToolbars;
186   // Flag to say if browsing is enabled.
187   BOOL _webUsageEnabled;
188   // Content view was reset due to low memory. Use the placeholder overlay on
189   // next creation.
190   BOOL _usePlaceholderOverlay;
191   // Overlay view used instead of webView.
192   base::scoped_nsobject<UIImageView> _placeholderOverlayView;
193   // The touch tracking recognizer allowing us to decide if a navigation is
194   // started by the user.
195   base::scoped_nsobject<CRWTouchTrackingRecognizer> _touchTrackingRecognizer;
196   // Long press recognizer that allows showing context menus.
197   base::scoped_nsobject<UILongPressGestureRecognizer> _contextMenuRecognizer;
198   // DOM element information for the point where the user made the last touch.
199   // Can be null if has not been calculated yet. Precalculation is necessary
200   // because retreiving DOM element relies on async API so element info can not
201   // be built on demand. May contain the following keys: "href", "src", "title",
202   // "referrerPolicy". All values are strings. Used for showing context menus.
203   scoped_ptr<base::DictionaryValue> _DOMElementForLastTouch;
204   // Whether a click is in progress.
205   BOOL _clickInProgress;
206   // The time of the last click, measured in seconds since Jan 1 2001.
207   CFAbsoluteTime _lastClickTimeInSeconds;
208   // The time of the last page transfer start, measured in seconds since Jan 1
209   // 2001.
210   CFAbsoluteTime _lastTransferTimeInSeconds;
211   // Default URL (about:blank).
212   GURL _defaultURL;
213   // Show overlay view, don't reload web page.
214   BOOL _overlayPreviewMode;
215   // If |YES|, call setSuppressDialogs when core.js is injected into the web
216   // view.
217   BOOL _setSuppressDialogsLater;
218   // If |YES|, call setSuppressDialogs when core.js is injected into the web
219   // view.
220   BOOL _setNotifyAboutDialogsLater;
221   // The URL of an expected future recreation of the |webView|. Valid
222   // only if the web view was discarded for non-user-visible reasons, such that
223   // if the next load request is for that URL, it should be treated as a
224   // reconstruction that should use cache aggressively.
225   GURL _expectedReconstructionURL;
227   scoped_ptr<web::NewWindowInfo> _externalRequest;
229   // The WebStateImpl instance associated with this CRWWebController.
230   scoped_ptr<WebStateImpl> _webStateImpl;
232   // A set of URLs opened in external applications; stored so that errors
233   // from the web view can be identified as resulting from these events.
234   base::scoped_nsobject<NSMutableSet> _openedApplicationURL;
236   // Object that manages all early script injection into the web view.
237   base::scoped_nsobject<CRWJSEarlyScriptManager> _earlyScriptManager;
239   // Script manager for setting the windowID.
240   base::scoped_nsobject<CRWJSWindowIdManager> _windowIDJSManager;
242   // The receiver of JavaScripts.
243   base::scoped_nsobject<CRWJSInjectionReceiver> _jsInjectionReceiver;
246 // The current page state of the web view. Writing to this property
247 // asynchronously applies the passed value to the current web view.
248 @property(nonatomic, readwrite) web::PageScrollState pageScrollState;
249 // Resets any state that is associated with a specific document object (e.g.,
250 // page interaction tracking).
251 - (void)resetDocumentSpecificState;
252 // Returns YES if the URL looks like it is one CRWWebController can show.
253 + (BOOL)webControllerCanShow:(const GURL&)url;
254 // Clear any interstitials being displayed.
255 - (void)clearInterstitials;
256 // Returns a lazily created CRWTouchTrackingRecognizer.
257 - (CRWTouchTrackingRecognizer*)touchTrackingRecognizer;
258 // Shows placeholder overlay.
259 - (void)addPlaceholderOverlay;
260 // Removes placeholder overlay.
261 - (void)removePlaceholderOverlay;
262 // Returns |YES| if |url| should be loaded in a native view.
263 - (BOOL)shouldLoadURLInNativeView:(const GURL&)url;
264 // Loads the HTML into the page at the given URL.
265 - (void)loadHTML:(NSString*)html forURL:(const GURL&)url;
266 // Loads the current nativeController in a native view. If a web view is
267 // present, removes it and swaps in the native view in its place.
268 - (void)loadNativeViewWithSuccess:(BOOL)loadSuccess;
269 // YES if the navigation to |url| should be treated as a reload.
270 - (BOOL)shouldReload:(const GURL&)destinationURL
271           transition:(ui::PageTransition)transition;
272 // Internal implementation of reload. Reloads without notifying the delegate.
273 // Most callers should use -reload instead.
274 - (void)reloadInternal;
275 // If YES, the page can be closed if the loading of the initial URL requires
276 // it (for example when an external URL is detected). After the initial URL is
277 // loaded, the page is not cancellable anymore.
278 - (BOOL)cancellable;
279 // Called after URL is finished loading and _loadPhase is set to PAGE_LOADED.
280 - (void)didFinishWithURL:(const GURL&)currentURL loadSuccess:(BOOL)loadSuccess;
281 // Informs the native controller if web usage is allowed or not.
282 - (void)setNativeControllerWebUsageEnabled:(BOOL)webUsageEnabled;
283 // Compares the two URLs being navigated between during a history navigation to
284 // determine if a # needs to be appended to endURL to trigger a hashchange
285 // event. If so, also saves the new endURL in the current CRWSessionEntry.
286 - (GURL)updateURLForHistoryNavigationFromURL:(const GURL&)startURL
287                                        toURL:(const GURL&)endURL;
288 // Evaluates the supplied JavaScript in the web view. Calls |handler| with
289 // results of the evaluation (which may be nil if the implementing object has no
290 // way to run the evaluation or the evaluation returns a nil value) or an
291 // NSError if there is an error. The |handler| can be nil.
292 - (void)evaluateJavaScript:(NSString*)script
293          JSONResultHandler:(void (^)(scoped_ptr<base::Value>, NSError*))handler;
294 // Generates the JavaScript string used to update the UIWebView's URL so that it
295 // matches the URL displayed in the omnibox and sets window.history.state to
296 // stateObject. Needed for history.pushState() and history.replaceState().
297 - (NSString*)javascriptToReplaceWebViewURL:(const GURL&)url
298                            stateObjectJSON:(NSString*)stateObject;
299 - (BOOL)isLoaded;
300 // Restores state of the web view's scroll view from |scrollState|.
301 // |isUserScalable| represents the value of user-scalable meta tag.
302 - (void)applyPageScrollState:(const web::PageScrollState&)scrollState
303                 userScalable:(BOOL)isUserScalable;
304 // Calls the zoom-preparation UIScrollViewDelegate callbacks on the web view.
305 // This is called before |-applyWebViewScrollZoomScaleFromScrollState:|.
306 - (void)prepareToApplyWebViewScrollZoomScale;
307 // Calls the zoom-completion UIScrollViewDelegate callbacks on the web view.
308 // This is called after |-applyWebViewScrollZoomScaleFromScrollState:|.
309 - (void)finishApplyingWebViewScrollZoomScale;
310 // Sets scroll offset value for webview scroll view from |scrollState|.
311 - (void)applyWebViewScrollOffsetFromScrollState:
312     (const web::PageScrollState&)scrollState;
313 // Asynchronously determines whether option |user-scalable| is on in the
314 // viewport meta of the current web page.
315 - (void)queryUserScalableProperty:(void (^)(BOOL))responseHandler;
316 // Asynchronously fetches full width of the rendered web page.
317 - (void)fetchWebPageWidthWithCompletionHandler:(void (^)(CGFloat))handler;
318 // Asynchronously fetches information about DOM element for the given point (in
319 // UIView coordinates). |handler| can not be nil. See |_DOMElementForLastTouch|
320 // for element format description.
321 - (void)fetchDOMElementAtPoint:(CGPoint)point
322              completionHandler:
323                  (void (^)(scoped_ptr<base::DictionaryValue>))handler;
324 // Extracts context menu information from the given DOM element.
325 // result keys are defined in crw_context_menu_provider.h.
326 - (NSDictionary*)contextMenuInfoForElement:(base::DictionaryValue*)element;
327 // Sets the value of |_DOMElementForLastTouch|.
328 - (void)setDOMElementForLastTouch:(scoped_ptr<base::DictionaryValue>)element;
329 // Called when the window has determined there was a long-press and context menu
330 // must be shown.
331 - (void)showContextMenu:(UIGestureRecognizer*)gestureRecognizer;
332 // YES if delegate supports showing context menu by responding to
333 // webController:runContextMenu:atPoint:inView: selector.
334 - (BOOL)supportsCustomContextMenu;
335 // Returns the referrer for the current page.
336 - (web::Referrer)currentReferrer;
337 // Presents an error to the user because the CRWWebController cannot verify the
338 // URL of the current page.
339 - (void)presentSpoofingError;
340 // Adds a new CRWSessionEntry with the given URL and state object to the history
341 // stack. A state object is a serialized generic JavaScript object that contains
342 // details of the UI's state for a given CRWSessionEntry/URL.
343 // TODO(stuartmorgan): Move the pushState/replaceState logic into
344 // NavigationManager.
345 - (void)pushStateWithPageURL:(const GURL&)pageUrl
346                  stateObject:(NSString*)stateObject;
347 // Assigns the given URL and state object to the current CRWSessionEntry.
348 - (void)replaceStateWithPageURL:(const GURL&)pageUrl
349                     stateObject:(NSString*)stateObject;
351 // Returns the current entry from the underlying session controller.
352 // TODO(stuartmorgan): Audit all calls to these methods; these are just wrappers
353 // around the same logic as GetActiveEntry, so should probably not be used for
354 // the same reason that GetActiveEntry is deprecated. (E.g., page operations
355 // should generally be dealing with the last commited entry, not a pending
356 // entry).
357 - (CRWSessionEntry*)currentSessionEntry;
358 - (web::NavigationItem*)currentNavItem;
359 // Returns the referrer for currentURL as a string. May return nil.
360 - (web::Referrer)currentSessionEntryReferrer;
361 // The data and HTTP headers associated to the current entry. These are nil
362 // unless the request was a POST.
363 - (NSData*)currentPOSTData;
364 - (NSDictionary*)currentHttpHeaders;
366 // Have the current web view load the request. If needed, it will add
367 // information to let the network stack access the requestGroupID.
368 - (void)loadRequest:(NSMutableURLRequest*)request;
369 // Finds all the scrollviews in the view hierarchy and makes sure they do not
370 // interfere with scroll to top when tapping the statusbar.
371 - (void)optOutScrollsToTopForSubviews;
372 // Tears down the old native controller, and then replaces it with the new one.
373 - (void)setNativeController:(id<CRWNativeContent>)nativeController;
374 // Returns whether |url| should be opened.
375 - (BOOL)shouldOpenURL:(const GURL&)url
376       mainDocumentURL:(const GURL&)mainDocumentURL
377           linkClicked:(BOOL)linkClicked;
378 // Called when |url| needs to be opened in a matching native app.
379 // Returns YES if the url was succesfully opened in the native app.
380 - (BOOL)urlTriggersNativeAppLaunch:(const GURL&)url
381                          sourceURL:(const GURL&)sourceURL;
382 // Returns whether external |url| should be opened.
383 - (BOOL)shouldOpenExternalURL:(const GURL&)url;
384 // Called when a page updates its history stack using pushState or replaceState.
385 - (void)didUpdateHistoryStateWithPageURL:(const GURL&)url;
387 // Handlers for JavaScript messages. |message| contains a JavaScript command and
388 // data relevant to the message, and |context| contains contextual information
389 // about web view state needed for some handlers.
391 // Handles 'addPluginPlaceholders' message.
392 - (BOOL)handleAddPluginPlaceholdersMessage:(base::DictionaryValue*)message
393                                    context:(NSDictionary*)context;
394 // Handles 'chrome.send' message.
395 - (BOOL)handleChromeSendMessage:(base::DictionaryValue*)message
396                         context:(NSDictionary*)context;
397 // Handles 'console' message.
398 - (BOOL)handleConsoleMessage:(base::DictionaryValue*)message
399                      context:(NSDictionary*)context;
400 // Handles 'dialog.suppressed' message.
401 - (BOOL)handleDialogSuppressedMessage:(base::DictionaryValue*)message
402                               context:(NSDictionary*)context;
403 // Handles 'dialog.willShow' message.
404 - (BOOL)handleDialogWillShowMessage:(base::DictionaryValue*)message
405                             context:(NSDictionary*)context;
406 // Handles 'document.favicons' message.
407 - (BOOL)handleDocumentFaviconsMessage:(base::DictionaryValue*)message
408                               context:(NSDictionary*)context;
409 // Handles 'document.submit' message.
410 - (BOOL)handleDocumentSubmitMessage:(base::DictionaryValue*)message
411                             context:(NSDictionary*)context;
412 // Handles 'externalRequest' message.
413 - (BOOL)handleExternalRequestMessage:(base::DictionaryValue*)message
414                              context:(NSDictionary*)context;
415 // Handles 'form.activity' message.
416 - (BOOL)handleFormActivityMessage:(base::DictionaryValue*)message
417                           context:(NSDictionary*)context;
418 // Handles 'form.requestAutocomplete' message.
419 - (BOOL)handleFormRequestAutocompleteMessage:(base::DictionaryValue*)message
420                                      context:(NSDictionary*)context;
421 // Handles 'navigator.credentials.request' message.
422 - (BOOL)handleCredentialsRequestedMessage:(base::DictionaryValue*)message
423                                   context:(NSDictionary*)context;
424 // Handles 'navigator.credentials.notifySignedIn' message.
425 - (BOOL)handleSignedInMessage:(base::DictionaryValue*)message
426                       context:(NSDictionary*)context;
427 // Handles 'navigator.credentials.notifySignedOut' message.
428 - (BOOL)handleSignedOutMessage:(base::DictionaryValue*)message
429                        context:(NSDictionary*)context;
430 // Handles 'navigator.credentials.notifyFailedSignIn' message.
431 - (BOOL)handleSignInFailedMessage:(base::DictionaryValue*)message
432                           context:(NSDictionary*)context;
433 // Handles 'resetExternalRequest' message.
434 - (BOOL)handleResetExternalRequestMessage:(base::DictionaryValue*)message
435                                   context:(NSDictionary*)context;
436 // Handles 'window.close.self' message.
437 - (BOOL)handleWindowCloseSelfMessage:(base::DictionaryValue*)message
438                              context:(NSDictionary*)context;
439 // Handles 'window.error' message.
440 - (BOOL)handleWindowErrorMessage:(base::DictionaryValue*)message
441                          context:(NSDictionary*)context;
442 // Handles 'window.hashchange' message.
443 - (BOOL)handleWindowHashChangeMessage:(base::DictionaryValue*)message
444                               context:(NSDictionary*)context;
445 // Handles 'window.history.back' message.
446 - (BOOL)handleWindowHistoryBackMessage:(base::DictionaryValue*)message
447                                context:(NSDictionary*)context;
448 // Handles 'window.history.forward' message.
449 - (BOOL)handleWindowHistoryForwardMessage:(base::DictionaryValue*)message
450                                   context:(NSDictionary*)context;
451 // Handles 'window.history.go' message.
452 - (BOOL)handleWindowHistoryGoMessage:(base::DictionaryValue*)message
453                              context:(NSDictionary*)context;
454 @end
456 namespace {
458 NSString* const kReferrerHeaderName = @"Referer";  // [sic]
460 // Full screen experimental setting.
462 // The long press detection duration must be shorter than the UIWebView's
463 // long click gesture recognizer's minimum duration. That is 0.55s.
464 // If our detection duration is shorter, our gesture recognizer will fire
465 // first, and if it fails the long click gesture (processed simultaneously)
466 // still is able to complete.
467 const NSTimeInterval kLongPressDurationSeconds = 0.55 - 0.1;
468 const CGFloat kLongPressMoveDeltaPixels = 10.0;
470 // The duration of the period following a screen touch during which the user is
471 // still considered to be interacting with the page.
472 const NSTimeInterval kMaximumDelayForUserInteractionInSeconds = 2;
474 // Define missing symbols from WebKit.
475 // See WebKitErrors.h on Mac SDK.
476 NSString* const WebKitErrorDomain = @"WebKitErrorDomain";
478 enum {
479   WebKitErrorCannotShowMIMEType = 100,
480   WebKitErrorCannotShowURL = 101,
481   WebKitErrorFrameLoadInterruptedByPolicyChange = 102,
482   // iOS-specific WebKit error that isn't documented but seen on 4.0
483   // devices.
484   WebKitErrorPlugInLoadFailed = 204,
487 // Tag for the interstitial view so we can find it and dismiss it later.
488 enum {
489   kInterstitialViewTag = 1000,
492 // URLs that are fed into UIWebView as history push/replace get escaped,
493 // potentially changing their format. Code that attempts to determine whether a
494 // URL hasn't changed can be confused by those differences though, so method
495 // will round-trip a URL through the escaping process so that it can be adjusted
496 // pre-storing, to allow later comparisons to work as expected.
497 GURL URLEscapedForHistory(const GURL& url) {
498   // TODO(stuartmorgan): This is a very large hammer; see if limited unicode
499   // escaping would be sufficient.
500   return net::GURLWithNSURL(net::NSURLWithGURL(url));
503 // Parses a viewport tag content and returns the value of an attribute with
504 // the given |name|, or nil if the attribute is not present in the tag.
505 NSString* GetAttributeValueFromViewPortContent(NSString* attributeName,
506                                                NSString* viewPortContent) {
507   NSArray* contentItems = [viewPortContent componentsSeparatedByString:@","];
508   for (NSString* item in contentItems) {
509     NSArray* components = [item componentsSeparatedByString:@"="];
510     if ([components count] == 2) {
511       NSCharacterSet* spaceAndNewline =
512           [NSCharacterSet whitespaceAndNewlineCharacterSet];
513       NSString* currentAttributeName =
514           [components[0] stringByTrimmingCharactersInSet:spaceAndNewline];
515       if ([currentAttributeName isEqualToString:attributeName]) {
516         return [components[1] stringByTrimmingCharactersInSet:spaceAndNewline];
517       }
518     }
519   }
520   return nil;
523 // Parses a viewport tag content and returns the value of the user-scalable
524 // attribute or nil.
525 BOOL GetUserScalablePropertyFromViewPortContent(NSString* viewPortContent) {
526   NSString* value =
527       GetAttributeValueFromViewPortContent(@"user-scalable", viewPortContent);
528   if (!value) {
529     return YES;
530   }
531   return !([value isEqualToString:@"0"] ||
532            [value caseInsensitiveCompare:@"no"] == NSOrderedSame);
535 // Leave snapshot overlay up unless page loads.
536 const NSTimeInterval kSnapshotOverlayDelay = 1.5;
537 // Transition to fade snapshot overlay.
538 const NSTimeInterval kSnapshotOverlayTransition = 0.5;
540 }  // namespace
542 @implementation CRWWebController
544 @synthesize webUsageEnabled = _webUsageEnabled;
545 @synthesize usePlaceholderOverlay = _usePlaceholderOverlay;
546 @synthesize loadPhase = _loadPhase;
548 // Implemented by subclasses.
549 @dynamic keyboardDisplayRequiresUserAction;
551 + (instancetype)allocWithZone:(struct _NSZone*)zone {
552   if (self == [CRWWebController class]) {
553     // This is an abstract class which should not be instantiated directly.
554     // Callers should create concrete subclasses instead.
555     NOTREACHED();
556     return nil;
557   }
558   return [super allocWithZone:zone];
561 - (instancetype)initWithWebState:(scoped_ptr<WebStateImpl>)webState {
562   self = [super init];
563   if (self) {
564     _webStateImpl = webState.Pass();
565     DCHECK(_webStateImpl);
566     _webStateImpl->SetWebController(self);
567     _webStateImpl->InitializeRequestTracker(self);
568     // Load phase when no WebView present is 'loaded' because this represents
569     // the idle state.
570     _loadPhase = web::PAGE_LOADED;
571     // Content area is lazily instantiated.
572     _defaultURL = GURL(url::kAboutBlankURL);
573     _jsInjectionReceiver.reset(
574         [[CRWJSInjectionReceiver alloc] initWithEvaluator:self]);
575     _earlyScriptManager.reset([(CRWJSEarlyScriptManager*)[_jsInjectionReceiver
576         instanceOfClass:[CRWJSEarlyScriptManager class]] retain]);
577     _windowIDJSManager.reset([(CRWJSWindowIdManager*)[_jsInjectionReceiver
578         instanceOfClass:[CRWJSWindowIdManager class]] retain]);
579     _lastSeenWindowID.reset();
580     _webViewProxy.reset(
581         [[CRWWebViewProxyImpl alloc] initWithWebController:self]);
582     _gestureRecognizers.reset([[NSMutableArray alloc] init]);
583     _webViewToolbars.reset([[NSMutableArray alloc] init]);
584     _pendingLoadCompleteActions.reset([[NSMutableArray alloc] init]);
585   }
586   return self;
589 - (id<CRWNativeContentProvider>)nativeProvider {
590   return _nativeProvider.get();
593 - (void)setNativeProvider:(id<CRWNativeContentProvider>)nativeProvider {
594   _nativeProvider.reset(nativeProvider);
597 - (id<CRWSwipeRecognizerProvider>)swipeRecognizerProvider {
598   return _swipeRecognizerProvider.get();
601 - (void)setSwipeRecognizerProvider:
602     (id<CRWSwipeRecognizerProvider>)swipeRecognizerProvider {
603   _swipeRecognizerProvider.reset(swipeRecognizerProvider);
606 - (WebState*)webState {
607   return _webStateImpl.get();
610 - (WebStateImpl*)webStateImpl {
611   return _webStateImpl.get();
614 // WebStateImpl will delete the interstitial page object, which will in turn
615 // remove its view from |_contentView|.
616 - (void)clearInterstitials {
617   [_webViewProxy setWebView:self.webView scrollView:self.webScrollView];
618   if (_webStateImpl)
619     _webStateImpl->ClearWebInterstitialForNavigation();
622 // Attaches |interstitialView| to |_contentView|.  Note that this class never
623 // explicitly removes the interstitial from |_contentView|;
624 // web::WebStateImpl::DismissWebInterstitial() takes care of that.
625 - (void)displayInterstitialView:(UIView*)interstitialView
626                  withScrollView:(UIScrollView*)scrollView {
627   DCHECK(interstitialView);
628   DCHECK(scrollView);
629   [_webViewProxy setWebView:interstitialView scrollView:scrollView];
630   interstitialView.tag = kInterstitialViewTag;
631   [_containerView addSubview:interstitialView];
634 - (id<CRWWebDelegate>)delegate {
635   return _delegate.get();
638 - (void)setDelegate:(id<CRWWebDelegate>)delegate {
639   _delegate.reset(delegate);
640   if ([_nativeController respondsToSelector:@selector(setDelegate:)]) {
641     if ([_delegate respondsToSelector:@selector(webController:titleDidChange:)])
642       [_nativeController setDelegate:self];
643     else
644       [_nativeController setDelegate:nil];
645   }
648 - (id<CRWWebUserInterfaceDelegate>)UIDelegate {
649   return _UIDelegate.get();
652 - (void)setUIDelegate:(id<CRWWebUserInterfaceDelegate>)UIDelegate {
653   _UIDelegate.reset(UIDelegate);
656 - (NSString*)scriptByAddingWindowIDCheckForScript:(NSString*)script {
657   NSString* kTemplate = @"if (__gCrWeb['windowId'] === '%@') { %@; }";
658   return [NSString stringWithFormat:kTemplate, [self windowId], script];
661 - (void)removeWebViewAllowingCachedReconstruction:(BOOL)allowCache {
662   if (!self.webView)
663     return;
665   if (allowCache)
666     _expectedReconstructionURL = [self currentNavigationURL];
667   else
668     _expectedReconstructionURL = GURL();
670   [self abortLoad];
671   [self.webView removeFromSuperview];
672   [_webViewProxy setWebView:nil scrollView:nil];
673   [self resetWebView];
674   // Remove the web toolbars.
675   [_containerView removeAllToolbars];
678 - (void)dealloc {
679   DCHECK([NSThread isMainThread]);
680   DCHECK(_isBeingDestroyed);  // 'close' must have been called already.
681   DCHECK(!self.webView);
682   _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
683   [super dealloc];
686 - (BOOL)runUnloadListenerBeforeClosing {
687   // There's not much that can be done since there's limited access to WebKit.
688   // Always return that it's ok to close immediately.
689   return YES;
692 - (void)dismissKeyboard {
693   [self.webView endEditing:YES];
694   if ([_nativeController respondsToSelector:@selector(dismissKeyboard)])
695     [_nativeController dismissKeyboard];
698 - (id<CRWNativeContent>)nativeController {
699   return _nativeController.get();
702 - (void)setNativeController:(id<CRWNativeContent>)nativeController {
703   // Check for pointer equality.
704   if (_nativeController.get() == nativeController)
705     return;
707   // Unset the delegate on the previous instance.
708   if ([_nativeController respondsToSelector:@selector(setDelegate:)])
709     [_nativeController setDelegate:nil];
711   _nativeController.reset([nativeController retain]);
712   [self setNativeControllerWebUsageEnabled:_webUsageEnabled];
715 // NativeControllerDelegate method, called to inform that title has changed.
716 - (void)nativeContent:(id)content titleDidChange:(NSString*)title {
717   // Responsiveness to delegate method was checked in setDelegate:.
718   [_delegate webController:self titleDidChange:title];
721 - (void)setNativeControllerWebUsageEnabled:(BOOL)webUsageEnabled {
722   if ([_nativeController respondsToSelector:@selector(setWebUsageEnabled:)]) {
723     [_nativeController setWebUsageEnabled:webUsageEnabled];
724   }
727 - (void)setWebUsageEnabled:(BOOL)enabled {
728   if (_webUsageEnabled == enabled)
729     return;
730   _webUsageEnabled = enabled;
732   // WKWebView autoreleases its WKProcessPool on removal from superview.
733   // Deferring WKProcessPool deallocation may lead to issues with cookie
734   // clearing and and Browsing Data Partitioning implementation.
735   @autoreleasepool {
736     [self setNativeControllerWebUsageEnabled:_webUsageEnabled];
737     if (enabled) {
738       // Don't create the web view; let it be lazy created as needed.
739     } else {
740       [self clearInterstitials];
741       [self removeWebViewAllowingCachedReconstruction:YES];
742       _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
743       _touchTrackingRecognizer.reset();
744       _containerView.reset();
745     }
746   }
749 - (void)requirePageReconstruction {
750   [self removeWebViewAllowingCachedReconstruction:NO];
753 - (void)handleLowMemory {
754   [self removeWebViewAllowingCachedReconstruction:YES];
755   [self setNativeController:nil];
756   _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
757   _touchTrackingRecognizer.reset();
758   _containerView.reset();
759   _usePlaceholderOverlay = YES;
762 - (void)reinitializeWebViewAndReload:(BOOL)reload {
763   if (self.webView) {
764     [self removeWebViewAllowingCachedReconstruction:NO];
765     if (reload) {
766       [self loadCurrentURLInWebView];
767     } else {
768       // Clear the space for the web view to lazy load when needed.
769       _usePlaceholderOverlay = YES;
770       _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
771       _touchTrackingRecognizer.reset();
772       _containerView.reset();
773     }
774   }
777 - (void)childWindowClosed:(NSString*)windowName {
778   // Subclasses can override this method to be informed about a closed window.
781 - (BOOL)isViewAlive {
782   return self.webView || [_nativeController isViewAlive];
785 - (BOOL)contentIsHTML {
786   return [self webViewDocumentType] == web::WEB_VIEW_DOCUMENT_TYPE_HTML;
789 // Stop doing stuff, especially network stuff. Close the request tracker.
790 - (void)terminateNetworkActivity {
791   DCHECK(!_isHalted);
792   _isHalted = YES;
794   // Cancel all outstanding perform requests, and clear anything already queued
795   // (since this may be called from within the handling loop) to prevent any
796   // asynchronous JavaScript invocation handling from continuing.
797   [NSObject cancelPreviousPerformRequestsWithTarget:self];
798   _webStateImpl->CloseRequestTracker();
801 - (void)dismissModals {
802   if ([_nativeController respondsToSelector:@selector(dismissModals)])
803     [_nativeController dismissModals];
806 // Caller must reset the delegate before calling.
807 - (void)close {
808   self.nativeProvider = nil;
809   self.swipeRecognizerProvider = nil;
810   if ([_nativeController respondsToSelector:@selector(close)])
811     [_nativeController close];
813   base::scoped_nsobject<NSSet> observers([_observers copy]);
814   for (id it in observers.get()) {
815     if ([it respondsToSelector:@selector(webControllerWillClose:)])
816       [it webControllerWillClose:self];
817   }
819   if (!_isHalted) {
820     [self terminateNetworkActivity];
821   }
823   DCHECK(!_isBeingDestroyed);
824   DCHECK(!_delegate);  // Delegate should reset its association before closing.
825   // Mark the destruction sequence has started, in case someone else holds a
826   // strong reference and tries to continue using the tab.
827   _isBeingDestroyed = YES;
829   // Remove the web view now. Otherwise, delegate callbacks occur.
830   [self removeWebViewAllowingCachedReconstruction:NO];
832   // Tear down web ui (in case this is part of this tab) and web state now,
833   // since the timing of dealloc can't be guaranteed.
834   _webStateImpl.reset();
837 - (void)checkLinkPresenceUnderGesture:(UIGestureRecognizer*)gestureRecognizer
838                     completionHandler:(void (^)(BOOL))completionHandler {
839   CGPoint webViewPoint = [gestureRecognizer locationInView:self.webView];
840   base::WeakNSObject<CRWWebController> weakSelf(self);
841   [self fetchDOMElementAtPoint:webViewPoint
842              completionHandler:^(scoped_ptr<base::DictionaryValue> element) {
843                std::string link;
844                BOOL hasLink =
845                    element && element->GetString("href", &link) && link.size();
846                completionHandler(hasLink);
847              }];
850 - (void)setDOMElementForLastTouch:(scoped_ptr<base::DictionaryValue>)element {
851   _DOMElementForLastTouch = element.Pass();
854 - (void)showContextMenu:(UIGestureRecognizer*)gestureRecognizer {
855   // Calling this method if [self supportsCustomContextMenu] returned NO
856   // is a programmer error.
857   DCHECK([self supportsCustomContextMenu]);
859   // We don't want ongoing notification that the long press is held.
860   if ([gestureRecognizer state] != UIGestureRecognizerStateBegan)
861     return;
863   if (!_DOMElementForLastTouch || _DOMElementForLastTouch->empty())
864     return;
866   NSDictionary* info =
867       [self contextMenuInfoForElement:_DOMElementForLastTouch.get()];
868   CGPoint point = [gestureRecognizer locationInView:self.webView];
870   // Cancel all touches on the web view when showing custom context menu. This
871   // will suppress the system context menu and prevent further user interactions
872   // with web view (like scrolling the content and following links). This
873   // approach is similar to UIWebView and WKWebView workflow as both call
874   // -[UIApplication _cancelAllTouches] to cancel all touch events, once the
875   // long press is detected.
876   CancelAllTouches(self.webScrollView);
877   [self.UIDelegate webController:self
878                   runContextMenu:info
879                          atPoint:point
880                           inView:self.webView];
883 - (BOOL)supportsCustomContextMenu {
884   SEL runMenuSelector = @selector(webController:runContextMenu:atPoint:inView:);
885   return [self.UIDelegate respondsToSelector:runMenuSelector];
888 // TODO(shreyasv): This code is shared with SnapshotManager. Remove this and add
889 // it as part of WebDelegate delegate API such that a default image is returned
890 // immediately.
891 + (UIImage*)defaultSnapshotImage {
892   static UIImage* defaultImage = nil;
894   if (!defaultImage) {
895     CGRect frame = CGRectMake(0, 0, 2, 2);
896     UIGraphicsBeginImageContext(frame.size);
897     [[UIColor whiteColor] setFill];
898     CGContextFillRect(UIGraphicsGetCurrentContext(), frame);
900     UIImage* result = UIGraphicsGetImageFromCurrentImageContext();
901     UIGraphicsEndImageContext();
903     defaultImage =
904         [[result stretchableImageWithLeftCapWidth:1 topCapHeight:1] retain];
905   }
906   return defaultImage;
909 - (BOOL)canGoBack {
910   return _webStateImpl->GetNavigationManagerImpl().CanGoBack();
913 - (BOOL)canGoForward {
914   return _webStateImpl->GetNavigationManagerImpl().CanGoForward();
917 - (CGPoint)scrollPosition {
918   CGPoint position = CGPointMake(0.0, 0.0);
919   if (!self.webScrollView)
920     return position;
921   return self.webScrollView.contentOffset;
924 - (BOOL)atTop {
925   if (!self.webView)
926     return YES;
927   UIScrollView* scrollView = self.webScrollView;
928   return scrollView.contentOffset.y == -scrollView.contentInset.top;
931 - (void)presentSpoofingError {
932   UMA_HISTOGRAM_ENUMERATION("Web.URLVerificationFailure",
933                             [self webViewDocumentType],
934                             web::WEB_VIEW_DOCUMENT_TYPE_COUNT);
935   if (self.webView) {
936     [self removeWebViewAllowingCachedReconstruction:NO];
937     [_delegate presentSpoofingError];
938   }
941 - (GURL)currentURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel {
942   DCHECK(trustLevel) << "Verification of the trustLevel state is mandatory";
943   if (self.webView) {
944     GURL url([self webURLWithTrustLevel:trustLevel]);
945     // Web views treat all about: URLs as the same origin, which makes it
946     // possible for pages to document.write into about:<foo> pages, where <foo>
947     // can be something misleading. Report any about: URL as about:blank to
948     // prevent that. See crbug.com/326118
949     if (url.scheme() == url::kAboutScheme)
950       return GURL(url::kAboutBlankURL);
951     return url;
952   }
953   // Any non-web URL source is trusted.
954   *trustLevel = web::URLVerificationTrustLevel::kAbsolute;
955   if (_nativeController)
956     return [_nativeController url];
957   return [self currentNavigationURL];
960 - (GURL)currentURL {
961   web::URLVerificationTrustLevel trustLevel =
962       web::URLVerificationTrustLevel::kNone;
963   const GURL url([self currentURLWithTrustLevel:&trustLevel]);
965   // Check whether the spoofing warning needs to be displayed.
966   if (trustLevel == web::URLVerificationTrustLevel::kNone &&
967       ![self ignoreURLVerificationFailures]) {
968     dispatch_async(dispatch_get_main_queue(), ^{
969       if (!_isHalted) {
970         DCHECK_EQ(url, [self currentNavigationURL]);
971         [self presentSpoofingError];
972       }
973     });
974   }
976   return url;
979 - (web::Referrer)currentReferrer {
980   // Referrer string doesn't include the fragment, so in cases where the
981   // previous URL is equal to the current referrer plus the fragment the
982   // previous URL is returned as current referrer.
983   NSString* referrerString = self.currentReferrerString;
985   // In case of an error evaluating the JavaScript simply return empty string.
986   if ([referrerString length] == 0)
987     return web::Referrer();
989   NSString* previousURLString =
990       base::SysUTF8ToNSString([self currentNavigationURL].spec());
991   // Check if the referrer is equal to the previous URL minus the hash symbol.
992   // L'#' is used to convert the char '#' to a unichar.
993   if ([previousURLString length] > [referrerString length] &&
994       [previousURLString hasPrefix:referrerString] &&
995       [previousURLString characterAtIndex:[referrerString length]] == L'#') {
996     referrerString = previousURLString;
997   }
998   // Since referrer is being extracted from the destination page, the correct
999   // policy from the origin has *already* been applied. Since the extracted URL
1000   // is the post-policy value, and the source policy is no longer available,
1001   // the policy is set to Always so that whatever WebKit decided to send will be
1002   // re-sent when replaying the entry.
1003   // TODO(stuartmorgan): When possible, get the real referrer and policy in
1004   // advance and use that instead. https://crbug.com/227769.
1005   return web::Referrer(GURL(base::SysNSStringToUTF8(referrerString)),
1006                        web::ReferrerPolicyAlways);
1009 - (void)pushStateWithPageURL:(const GURL&)pageUrl
1010                  stateObject:(NSString*)stateObject {
1011   [[self sessionController] pushNewEntryWithURL:pageUrl
1012                                     stateObject:stateObject];
1013   [self didUpdateHistoryStateWithPageURL:pageUrl];
1016 - (void)replaceStateWithPageURL:(const GURL&)pageUrl
1017                     stateObject:(NSString*)stateObject {
1018   [[self sessionController] updateCurrentEntryWithURL:pageUrl
1019                                           stateObject:stateObject];
1020   [self didUpdateHistoryStateWithPageURL:pageUrl];
1023 - (void)injectEarlyInjectionScripts {
1024   DCHECK(self.webView);
1025   if (![_earlyScriptManager hasBeenInjected]) {
1026     [_earlyScriptManager inject];
1027     // If this assertion fires there has been an error parsing the core.js
1028     // object.
1029     DCHECK([_earlyScriptManager hasBeenInjected]);
1030   }
1031   [self injectWindowID];
1034 - (void)injectWindowID {
1035   if (![_windowIDJSManager hasBeenInjected]) {
1036     // If the window ID wasn't present, this is a new page.
1037     [self setPageChangeProbability:web::PAGE_CHANGE_PROBABILITY_LOW];
1038     // Default values for suppressDialogs and notifyAboutDialogs are NO,
1039     // so updating them only when necessary is a good optimization.
1040     if (_setSuppressDialogsLater || _setNotifyAboutDialogsLater) {
1041       [self setSuppressDialogs:_setSuppressDialogsLater
1042                         notify:_setNotifyAboutDialogsLater];
1043       _setSuppressDialogsLater = NO;
1044       _setNotifyAboutDialogsLater = NO;
1045     }
1047     [_windowIDJSManager inject];
1048     DCHECK([_windowIDJSManager hasBeenInjected]);
1049   }
1052 // Set the specified recognizer to take priority over any recognizers in the
1053 // view that have a description containing the specified text fragment.
1054 + (void)requireGestureRecognizerToFail:(UIGestureRecognizer*)recognizer
1055                                 inView:(UIView*)view
1056                  containingDescription:(NSString*)fragment {
1057   for (UIGestureRecognizer* iRecognizer in [view gestureRecognizers]) {
1058     if (iRecognizer != recognizer) {
1059       NSString* description = [iRecognizer description];
1060       if ([description rangeOfString:fragment].location != NSNotFound) {
1061         [iRecognizer requireGestureRecognizerToFail:recognizer];
1062         // requireGestureRecognizerToFail: doesn't retain the recognizer, so it
1063         // is possible for |iRecognizer| to outlive |recognizer| and end up with
1064         // a dangling pointer. Add a retaining associative reference to ensure
1065         // that the lifetimes work out.
1066         // Note that normally using the value as the key wouldn't make any
1067         // sense, but here it's fine since nothing needs to look up the value.
1068         objc_setAssociatedObject(view, recognizer, recognizer,
1069                                  OBJC_ASSOCIATION_RETAIN_NONATOMIC);
1070       }
1071     }
1072   }
1075 - (void)webViewDidChange {
1076   CHECK(_webUsageEnabled) << "Tried to create a web view while suspended!";
1078   UIView* webView = self.webView;
1079   DCHECK(webView);
1081   [webView setTag:kWebViewTag];
1082   [webView setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
1083                                UIViewAutoresizingFlexibleHeight];
1084   [webView setBackgroundColor:[UIColor colorWithWhite:0.2 alpha:1.0]];
1086   // Create a dependency between the |webView| pan gesture and BVC side swipe
1087   // gestures. Note: This needs to be added before the longPress recognizers
1088   // below, or the longPress appears to deadlock the remaining recognizers,
1089   // thereby breaking scroll.
1090   NSSet* recognizers = [_swipeRecognizerProvider swipeRecognizers];
1091   for (UISwipeGestureRecognizer* swipeRecognizer in recognizers) {
1092     [self.webScrollView.panGestureRecognizer
1093         requireGestureRecognizerToFail:swipeRecognizer];
1094   }
1096   // On iOS 4.x, there are two gesture recognizers on the UIWebView subclasses,
1097   // that have a minimum tap threshold of 0.12s and 0.75s.
1098   //
1099   // My theory is that the shorter threshold recognizer performs the link
1100   // highlight (grey highlight around links when it is tapped and held) while
1101   // the longer threshold one pops up the context menu.
1102   //
1103   // To override the context menu, this recognizer needs to react faster than
1104   // the 0.75s one. The below gesture recognizer is initialized with a
1105   // detection duration a little lower than that (see
1106   // kLongPressDurationSeconds). It also points the delegate to this class that
1107   // allows simultaneously operate along with the other recognizers.
1108   _contextMenuRecognizer.reset([[UILongPressGestureRecognizer alloc]
1109       initWithTarget:self
1110               action:@selector(showContextMenu:)]);
1111   [_contextMenuRecognizer setMinimumPressDuration:kLongPressDurationSeconds];
1112   [_contextMenuRecognizer setAllowableMovement:kLongPressMoveDeltaPixels];
1113   [_contextMenuRecognizer setDelegate:self];
1114   [webView addGestureRecognizer:_contextMenuRecognizer];
1115   // Certain system gesture handlers are known to conflict with our context
1116   // menu handler, causing extra events to fire when the context menu is active.
1118   // A number of solutions have been investigated. The lowest-risk solution
1119   // appears to be to recurse through the web controller's recognizers, looking
1120   // for fingerprints of the recognizers known to cause problems, which are then
1121   // de-prioritized (below our own long click handler).
1122   // Hunting for description fragments of system recognizers is undeniably
1123   // brittle for future versions of iOS. If it does break the context menu
1124   // events may leak (regressing b/5310177), but the app will otherwise work.
1125   [CRWWebController
1126       requireGestureRecognizerToFail:_contextMenuRecognizer
1127                               inView:webView
1128                containingDescription:@"action=_highlightLongPressRecognized:"];
1130   // Add all additional gesture recognizers to the web view.
1131   for (UIGestureRecognizer* recognizer in _gestureRecognizers.get()) {
1132     [webView addGestureRecognizer:recognizer];
1133   }
1135   webView.frame = [_containerView bounds];
1137   _URLOnStartLoading = _defaultURL;
1139   // Do final view setup.
1140   CGPoint initialOffset = CGPointMake(0, 0 - [self headerHeight]);
1141   [self.webScrollView setContentOffset:initialOffset];
1142   [_containerView addToolbars:_webViewToolbars];
1144   [_webViewProxy setWebView:self.webView scrollView:self.webScrollView];
1146   [_containerView addSubview:webView];
1149 - (CRWWebController*)createChildWebControllerWithReferrerURL:
1150     (const GURL&)referrerURL {
1151   web::Referrer referrer(referrerURL, web::ReferrerPolicyDefault);
1152   CRWWebController* result =
1153       [self.delegate webPageOrderedOpenBlankWithReferrer:referrer
1154                                             inBackground:NO];
1155   DCHECK(!result || result.sessionController.openedByDOM);
1156   return result;
1159 - (BOOL)canUseViewForGeneratingOverlayPlaceholderView {
1160   return _containerView != nil;
1163 - (UIView*)view {
1164   // Kick off the process of lazily creating the view and starting the load if
1165   // necessary; this creates _contentView if it doesn't exist.
1166   [self triggerPendingLoad];
1167   DCHECK(_containerView);
1168   return _containerView.get();
1171 - (id<CRWWebViewProxy>)webViewProxy {
1172   return _webViewProxy.get();
1175 - (UIView*)viewForPrinting {
1176   // TODO(ios): crbug.com/227944. Printing is not supported for native
1177   // controllers.
1178   return self.webView;
1181 - (void)loadRequest:(NSMutableURLRequest*)request {
1182   DCHECK(web::GetWebClient());
1183   GURL url = net::GURLWithNSURL(request.URL);
1184   // TODO(stuartmorgan): See if WKWebView has the same issue; if not, this can
1185   // move into the subclass.
1186   if (web::GetWebClient()->IsAppSpecificURL(url)) {
1187     // Sub requests of a chrome:// page will not contain the user agent.
1188     // Instead use the username part of the URL to allow the network stack to
1189     // associate a request to the correct tab.
1190     request.URL = web::AddRequestGroupIDToURL(
1191         request.URL, _webStateImpl->GetRequestGroupID());
1192   }
1193   [self loadWebRequest:request];
1196 - (void)registerLoadRequest:(const GURL&)requestURL
1197                    referrer:(const web::Referrer&)referrer
1198                  transition:(ui::PageTransition)transition {
1199   // Transfer time is registered so that further transitions within the time
1200   // envelope are not also registered as links.
1201   _lastTransferTimeInSeconds = CFAbsoluteTimeGetCurrent();
1202   // Before changing phases, the delegate should be informed that any existing
1203   // request is being cancelled before completion.
1204   [self loadCancelled];
1205   DCHECK(_loadPhase == web::PAGE_LOADED);
1207   _loadPhase = web::LOAD_REQUESTED;
1208   [self resetLoadState];
1209   _lastRegisteredRequestURL = requestURL;
1211   if (!(transition & ui::PAGE_TRANSITION_IS_REDIRECT_MASK)) {
1212     // Record state of outgoing page.
1213     [self recordStateInHistory];
1214   }
1216   // If the web view had been discarded, and this request is to load that
1217   // URL again, then it's a rebuild and should use the cache.
1218   BOOL preferCache = _expectedReconstructionURL.is_valid() &&
1219                      _expectedReconstructionURL == requestURL;
1221   [_delegate webWillAddPendingURL:requestURL transition:transition];
1222   // Add or update pending url.
1223   if (_webStateImpl->GetNavigationManagerImpl().GetPendingItem()) {
1224     // Update the existing pending entry.
1225     // Typically on PAGE_TRANSITION_CLIENT_REDIRECT.
1226     [[self sessionController] updatePendingEntry:requestURL];
1227   } else {
1228     // A new session history entry needs to be created.
1229     [[self sessionController] addPendingEntry:requestURL
1230                                      referrer:referrer
1231                                    transition:transition
1232                             rendererInitiated:YES];
1233   }
1234   // Update the cache mode for all the network requests issued by this web view.
1235   // The mode is reset to CACHE_NORMAL after each page load.
1236   if (_webStateImpl->GetCacheMode() != net::RequestTracker::CACHE_NORMAL) {
1237     _webStateImpl->GetRequestTracker()->SetCacheModeFromUIThread(
1238         _webStateImpl->GetCacheMode());
1239   } else if (preferCache) {
1240     _webStateImpl->GetRequestTracker()->SetCacheModeFromUIThread(
1241         net::RequestTracker::CACHE_HISTORY);
1242   }
1243   _webStateImpl->SetIsLoading(true);
1244   [_delegate webDidAddPendingURL];
1245   _webStateImpl->OnProvisionalNavigationStarted(requestURL);
1248 - (NSString*)javascriptToReplaceWebViewURL:(const GURL&)url
1249                            stateObjectJSON:(NSString*)stateObject {
1250   std::string outURL;
1251   base::EscapeJSONString(url.spec(), true, &outURL);
1252   return
1253       [NSString stringWithFormat:@"__gCrWeb.replaceWebViewURL(%@, %@);",
1254                                  base::SysUTF8ToNSString(outURL), stateObject];
1257 - (void)finishPushStateNavigationToURL:(const GURL&)url
1258                        withStateObject:(NSString*)stateObject {
1259   // TODO(stuartmorgan): Make CRWSessionController manage this internally (or
1260   // remove it; it's not clear this matches other platforms' behavior).
1261   _webStateImpl->GetNavigationManagerImpl().OnNavigationItemCommitted();
1263   NSString* replaceWebViewUrlJS =
1264       [self javascriptToReplaceWebViewURL:url stateObjectJSON:stateObject];
1265   std::string outState;
1266   base::EscapeJSONString(base::SysNSStringToUTF8(stateObject), true, &outState);
1267   NSString* popstateJS =
1268       [NSString stringWithFormat:@"__gCrWeb.dispatchPopstateEvent(%@);",
1269                                  base::SysUTF8ToNSString(outState)];
1270   NSString* combinedJS =
1271       [NSString stringWithFormat:@"%@%@", replaceWebViewUrlJS, popstateJS];
1272   GURL urlCopy(url);
1273   base::WeakNSObject<CRWWebController> weakSelf(self);
1274   [self evaluateJavaScript:combinedJS
1275        stringResultHandler:^(NSString*, NSError*) {
1276          if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
1277            return;
1278          base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
1279          strongSelf.get()->_URLOnStartLoading = urlCopy;
1280          strongSelf.get()->_lastRegisteredRequestURL = urlCopy;
1281        }];
1284 // Load the current URL in a web view, first ensuring the web view is visible.
1285 // If a native controller is present, remove it and swap a new web view in
1286 // its place.
1287 - (void)loadCurrentURLInWebView {
1288   [self willLoadCurrentURLInWebView];
1290   // Re-register the user agent, because UIWebView sometimes loses it.
1291   // See crbug.com/228397.
1292   [self registerUserAgent];
1294   // Freeing the native controller removes its view from the view hierarchy.
1295   [self setNativeController:nil];
1297   // Clear the set of URLs opened in external applications.
1298   _openedApplicationURL.reset([[NSMutableSet alloc] init]);
1300   // Load the url. The UIWebView delegate callbacks take care of updating the
1301   // session history and UI.
1302   const GURL targetURL([self currentNavigationURL]);
1303   if (!targetURL.is_valid())
1304     return;
1306   // JavaScript should never be evaluated here. User-entered JS should be
1307   // evaluated via stringByEvaluatingUserJavaScriptFromString.
1308   DCHECK(!targetURL.SchemeIs(url::kJavaScriptScheme));
1309   [self ensureWebViewCreated];
1311   DCHECK(self.webView && !_nativeController);
1312   NSMutableURLRequest* request =
1313       [NSMutableURLRequest requestWithURL:net::NSURLWithGURL(targetURL)];
1314   const web::Referrer referrer([self currentSessionEntryReferrer]);
1315   if (referrer.url.is_valid()) {
1316     std::string referrerValue =
1317         web::ReferrerHeaderValueForNavigation(targetURL, referrer);
1318     if (!referrerValue.empty()) {
1319       [request setValue:base::SysUTF8ToNSString(referrerValue)
1320           forHTTPHeaderField:kReferrerHeaderName];
1321     }
1322   }
1324   // If there are headers in the current session entry add them to |request|.
1325   // Headers that would overwrite fields already present in |request| are
1326   // skipped.
1327   NSDictionary* headers = [self currentHttpHeaders];
1328   for (NSString* headerName in headers) {
1329     if (![request valueForHTTPHeaderField:headerName]) {
1330       [request setValue:[headers objectForKey:headerName]
1331           forHTTPHeaderField:headerName];
1332     }
1333   }
1335   NSData* postData = [self currentPOSTData];
1336   if (postData) {
1337     web::NavigationItemImpl* currentItem =
1338         [self currentSessionEntry].navigationItemImpl;
1339     if ([postData length] > 0 &&
1340         !(currentItem && currentItem->ShouldSkipResubmitDataConfirmation())) {
1341       id cancelBlock = ^{
1342         [self registerLoadRequest:[self currentNavigationURL]
1343                          referrer:[self currentSessionEntryReferrer]
1344                        transition:[self currentTransition]];
1345         [self loadRequest:request];
1346       };
1347       id continueBlock = ^{
1348         [request setHTTPMethod:@"POST"];
1349         [request setHTTPBody:[self currentPOSTData]];
1350         [request setAllHTTPHeaderFields:[self currentHttpHeaders]];
1351         [self registerLoadRequest:[self currentNavigationURL]
1352                          referrer:[self currentSessionEntryReferrer]
1353                        transition:[self currentTransition]];
1354         [self loadRequest:request];
1355       };
1356       [_delegate webController:self
1357           onFormResubmissionForRequest:request
1358                          continueBlock:continueBlock
1359                            cancelBlock:cancelBlock];
1360       return;
1361     } else {
1362       // The user does not need to confirm if POST data is empty.
1363       [request setHTTPMethod:@"POST"];
1364       [request setHTTPBody:postData];
1365       [request setAllHTTPHeaderFields:[self currentHttpHeaders]];
1366     }
1367   }
1369   // registerLoadRequest will be called when load is about to begin.
1370   // The phase at that point is guaranteed to be web::LOAD_REQUESTED.
1371   // However the delegate is not immediately called.
1372   [self registerLoadRequest:targetURL
1373                    referrer:referrer
1374                  transition:[self currentTransition]];
1375   [self loadRequest:request];
1378 - (void)loadNativeViewWithSuccess:(BOOL)loadSuccess {
1379   [_nativeController view].frame = [self visibleFrame];
1380   [_containerView addSubview:[_nativeController view]];
1381   const GURL currentURL([self currentURL]);
1382   [self didStartLoadingURL:currentURL updateHistory:loadSuccess];
1383   _loadPhase = web::PAGE_LOADED;
1385   // Perform post-load-finished updates.
1386   [self didFinishWithURL:currentURL loadSuccess:loadSuccess];
1388   // Inform the embedder the title changed.
1389   if ([_delegate respondsToSelector:@selector(webController:titleDidChange:)]) {
1390     NSString* title = [_nativeController title];
1391     // If a title is present, notify the delegate.
1392     if (title)
1393       [_delegate webController:self titleDidChange:title];
1394     // If the controller handles title change notification, route those to the
1395     // delegate.
1396     if ([_nativeController respondsToSelector:@selector(setDelegate:)]) {
1397       [_nativeController setDelegate:self];
1398     }
1399   }
1402 - (void)loadErrorInNativeView:(NSError*)error {
1403   [self removeWebViewAllowingCachedReconstruction:NO];
1405   const GURL currentUrl = [self currentNavigationURL];
1406   BOOL isPost = [self currentPOSTData] != nil;
1408   [self setNativeController:[_nativeProvider controllerForURL:currentUrl
1409                                                     withError:error
1410                                                        isPost:isPost]];
1411   [self loadNativeViewWithSuccess:NO];
1414 // Load the current URL in a native controller, retrieved from the native
1415 // provider. Call |loadNativeViewWithSuccess:YES| to load the native controller.
1416 - (void)loadCurrentURLInNativeView {
1417   // Free the web view.
1418   [self removeWebViewAllowingCachedReconstruction:NO];
1420   const GURL targetURL = [self currentNavigationURL];
1421   const web::Referrer referrer;
1422   // Unlike the WebView case, always create a new controller and view.
1423   // TODO(pinkerton): What to do if this does return nil?
1424   [self setNativeController:[_nativeProvider controllerForURL:targetURL]];
1425   [self registerLoadRequest:targetURL
1426                    referrer:referrer
1427                  transition:[self currentTransition]];
1428   [self loadNativeViewWithSuccess:YES];
1431 - (void)loadWithParams:(const web::WebLoadParams&)originalParams {
1432   // Make a copy of |params|, as some of the delegate methods may modify it.
1433   web::WebLoadParams params(originalParams);
1435   // Initiating a navigation from the UI, record the current page state before
1436   // the new page loads. Don't record for back/forward, as the current entry
1437   // has already been moved to the next entry in the history. Do, however,
1438   // record it for general reload.
1439   // TODO(jimblackler): consider a single unified call to record state whenever
1440   // the page is about to be changed. This cannot currently be done after
1441   // addPendingEntry is called.
1443   [_delegate webWillInitiateLoadWithParams:params];
1445   GURL navUrl = params.url;
1446   ui::PageTransition transition = params.transition_type;
1448   BOOL initialNavigation = NO;
1449   BOOL forwardBack =
1450       PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD) &&
1451       (transition & ui::PAGE_TRANSITION_FORWARD_BACK);
1452   if (forwardBack) {
1453     // Setting these for back/forward is not supported.
1454     DCHECK(!params.extra_headers);
1455     DCHECK(!params.post_data);
1456   } else {
1457     // TODO(stuartmorgan): Why doesn't recordStateInHistory get called for
1458     // forward/back transitions?
1459     [self recordStateInHistory];
1461     CRWSessionController* history =
1462         _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1463     if (!self.currentSessionEntry)
1464       initialNavigation = YES;
1465     [history addPendingEntry:navUrl
1466                     referrer:params.referrer
1467                   transition:transition
1468            rendererInitiated:params.is_renderer_initiated];
1469     web::NavigationItemImpl* addedItem =
1470         [self currentSessionEntry].navigationItemImpl;
1471     DCHECK(addedItem);
1472     if (params.extra_headers)
1473       addedItem->AddHttpRequestHeaders(params.extra_headers);
1474     if (params.post_data) {
1475       DCHECK([addedItem->GetHttpRequestHeaders() objectForKey:@"Content-Type"])
1476           << "Post data should have an associated content type";
1477       addedItem->SetPostData(params.post_data);
1478       addedItem->SetShouldSkipResubmitDataConfirmation(true);
1479     }
1480   }
1482   [_delegate webDidUpdateSessionForLoadWithParams:params
1483                              wasInitialNavigation:initialNavigation];
1485   // If a non-default cache mode is passed in, it takes precedence over
1486   // |reload|.
1487   const BOOL reload = [self shouldReload:navUrl transition:transition];
1488   if (params.cache_mode != net::RequestTracker::CACHE_NORMAL) {
1489     _webStateImpl->SetCacheMode(params.cache_mode);
1490   } else if (reload) {
1491     _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_RELOAD);
1492   }
1494   [self loadCurrentURL];
1496   // Change the cache mode back to CACHE_NORMAL after a reload.
1497   _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1500 - (void)loadCurrentURL {
1501   // If the content view doesn't exist, the tab has either been evicted, or
1502   // never displayed. Bail, and let the URL be loaded when the tab is shown.
1503   if (!_containerView)
1504     return;
1506   // Reset current WebUI if one exists.
1507   _webStateImpl->ClearWebUI();
1509   // Precaution, so that the outgoing URL is registered, to reduce the risk of
1510   // it being seen as a fresh URL later by the same method (and new page change
1511   // erroneously reported).
1512   [self checkForUnexpectedURLChange];
1514   // Abort any outstanding page load. This ensures the delegate gets informed
1515   // about the outgoing page, and further messages from the page are suppressed.
1516   if (_loadPhase != web::PAGE_LOADED)
1517     [self abortLoad];
1519   DCHECK(!_isHalted);
1520   // Remove the interstitial before doing anything else.
1521   [self clearInterstitials];
1523   const GURL currentUrl = [self currentNavigationURL];
1524   // If it's a chrome URL, but not a native one, create the WebUI instance.
1525   if (web::GetWebClient()->IsAppSpecificURL(currentUrl) &&
1526       ![_nativeProvider hasControllerForURL:currentUrl]) {
1527     _webStateImpl->CreateWebUI(currentUrl);
1528   }
1530   // Loading a new url, must check here if it's a native chrome URL and
1531   // replace the appropriate view if so, or transition back to a web view from
1532   // a native view.
1533   if ([self shouldLoadURLInNativeView:currentUrl]) {
1534     [self loadCurrentURLInNativeView];
1535   } else {
1536     [self loadCurrentURLInWebView];
1537   }
1539   // Once a URL has been loaded, any cached-based reconstruction state has
1540   // either been handled or obsoleted.
1541   _expectedReconstructionURL = GURL();
1544 - (BOOL)shouldLoadURLInNativeView:(const GURL&)url {
1545   // App-specific URLs that don't require WebUI are loaded in native views.
1546   return web::GetWebClient()->IsAppSpecificURL(url) &&
1547          !_webStateImpl->HasWebUI();
1550 - (void)triggerPendingLoad {
1551   if (!_containerView) {
1552     DCHECK(!_isBeingDestroyed);
1553     // Create the top-level parent view, which will contain the content (whether
1554     // native or web). Note, this needs to be created with a non-zero size
1555     // to allow for (native) subviews with autosize constraints to be correctly
1556     // processed.
1557     _containerView.reset([[CRWWebControllerContainerView alloc]
1558         initWithFrame:[[UIScreen mainScreen] bounds]]);
1559     [_containerView addGestureRecognizer:[self touchTrackingRecognizer]];
1560     [_containerView setAccessibilityIdentifier:web::kContainerViewID];
1561     // Is |currentUrl| a web scheme or native chrome scheme.
1562     BOOL isChromeScheme =
1563         web::GetWebClient()->IsAppSpecificURL([self currentNavigationURL]);
1565     // Don't immediately load the web page if in overlay mode. Always load if
1566     // native.
1567     if (isChromeScheme || !_overlayPreviewMode) {
1568       // TODO(jimblackler): end the practice of calling |loadCurrentURL| when it
1569       // is possible there is no current URL. If the call performs necessary
1570       // initialization, break that out.
1571       [self loadCurrentURL];
1572     }
1574     // Display overlay view until current url has finished loading or delay and
1575     // then transition away.
1576     if ((_overlayPreviewMode || _usePlaceholderOverlay) && !isChromeScheme)
1577       [self addPlaceholderOverlay];
1579     // Don't reset the overlay flag if in preview mode.
1580     if (!_overlayPreviewMode)
1581       _usePlaceholderOverlay = NO;
1582   }
1585 - (BOOL)shouldReload:(const GURL&)destinationURL
1586           transition:(ui::PageTransition)transition {
1587   // Do a reload if the user hits enter in the address bar or re-types a URL.
1588   CRWSessionController* sessionController =
1589       _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1590   web::NavigationItem* item =
1591       _webStateImpl->GetNavigationManagerImpl().GetVisibleItem();
1592   return (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) && item &&
1593          (destinationURL == item->GetURL() ||
1594           destinationURL == [sessionController currentEntry].originalUrl);
1597 // Reload either the web view or the native content depending on which is
1598 // displayed.
1599 - (void)reloadInternal {
1600   web::RecordAction(UserMetricsAction("Reload"));
1601   if (self.webView) {
1602     // Just as we don't use the WebView native back and forward navigation
1603     // (preferring to load the URLs manually) we don't use the native reload.
1604     // This ensures state processing and delegate calls are consistent.
1605     [self loadCurrentURL];
1606   } else {
1607     [_nativeController reload];
1608   }
1611 - (void)reload {
1612   [_delegate webWillReload];
1614   _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_RELOAD);
1615   [self reloadInternal];
1616   _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1619 - (void)loadCancelled {
1620   // Current load will not complete; this should be communicated upstream to the
1621   // delegate.
1622   switch (_loadPhase) {
1623     case web::LOAD_REQUESTED:
1624       // Load phase after abort is always PAGE_LOADED.
1625       _loadPhase = web::PAGE_LOADED;
1626       if (!_isHalted) {
1627         _webStateImpl->SetIsLoading(false);
1628       }
1629       [_delegate webCancelStartLoadingRequest];
1630       break;
1631     case web::PAGE_LOADING:
1632       // The previous load never fully completed before this page change. The
1633       // loadPhase is changed to PAGE_LOADED to indicate the cycle is complete,
1634       // and the delegate is called.
1635       _loadPhase = web::PAGE_LOADED;
1636       if (!_isHalted) {
1637         // RequestTracker expects StartPageLoad to be followed by
1638         // FinishPageLoad, passing the exact same URL.
1639         self.webStateImpl->GetRequestTracker()->FinishPageLoad(
1640             _URLOnStartLoading, false);
1641       }
1642       [_delegate webLoadCancelled:_URLOnStartLoading];
1643       break;
1644     case web::PAGE_LOADED:
1645       break;
1646   }
1649 - (void)abortLoad {
1650   [self abortWebLoad];
1651   [self loadCancelled];
1654 - (void)prepareForGoBack {
1655   // Make sure any transitions that may have occurred have been seen and acted
1656   // on by the CRWWebController, so the history stack and state of the
1657   // CRWWebController is 100% up to date before the stack navigation starts.
1658   if (self.webView) {
1659     [self injectEarlyInjectionScripts];
1660     [self checkForUnexpectedURLChange];
1661   }
1662   // Discard any outstanding pending entries before adjusting the navigation
1663   // index.
1664   CRWSessionController* sessionController =
1665       _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1666   [sessionController discardNonCommittedEntries];
1668   bool wasShowingInterstitial = _webStateImpl->IsShowingWebInterstitial();
1670   // Call into the delegate before |recordStateInHistory|.
1671   // TODO(rohitrao): Can this be reordered after |recordStateInHistory|?
1672   [_delegate webDidPrepareForGoBack];
1674   // Before changing the current session history entry, record the tab state.
1675   if (!wasShowingInterstitial) {
1676     [self recordStateInHistory];
1677   }
1680 - (void)goBack {
1681   [self goDelta:-1];
1684 - (void)goForward {
1685   [self goDelta:1];
1688 - (void)goDelta:(int)delta {
1689   if (delta == 0) {
1690     [self reload];
1691     return;
1692   }
1694   // Abort if there is nothing next in the history.
1695   // Note that it is NOT checked that the history depth is at least |delta|.
1696   if ((delta < 0 && ![self canGoBack]) || (delta > 0 && ![self canGoForward])) {
1697     return;
1698   }
1700   if (delta < 0) {
1701     [self prepareForGoBack];
1702   } else {
1703     // Before changing the current session history entry, record the tab state.
1704     [self recordStateInHistory];
1705   }
1707   [_delegate webWillGoDelta:delta];
1709   CRWSessionController* sessionController =
1710       _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1711   CRWSessionEntry* fromEntry = [sessionController currentEntry];
1712   [sessionController goDelta:delta];
1713   if (fromEntry) {
1714     _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_HISTORY);
1715     [self finishHistoryNavigationFromEntry:fromEntry];
1716     _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1717   }
1720 - (BOOL)isLoaded {
1721   return _loadPhase == web::PAGE_LOADED;
1724 - (void)loadCompleteWithSuccess:(BOOL)loadSuccess {
1725   [self removePlaceholderOverlay];
1726   // The webView may have been torn down (or replaced by a native view). Be
1727   // safe and do nothing if that's happened.
1728   if (_loadPhase != web::PAGE_LOADING)
1729     return;
1731   DCHECK(self.webView);
1733   const GURL currentURL([self currentURL]);
1735   [self resetLoadState];
1736   _loadPhase = web::PAGE_LOADED;
1738   [self optOutScrollsToTopForSubviews];
1740   // Ensure the URL is as expected (and already reported to the delegate).
1741   DCHECK(currentURL == _lastRegisteredRequestURL)
1742       << std::endl
1743       << "currentURL = [" << currentURL << "]" << std::endl
1744       << "_lastRegisteredRequestURL = [" << _lastRegisteredRequestURL << "]";
1746   // Perform post-load-finished updates.
1747   [self didFinishWithURL:currentURL loadSuccess:loadSuccess];
1749   // Execute the pending LoadCompleteActions.
1750   for (ProceduralBlock action in _pendingLoadCompleteActions.get()) {
1751     action();
1752   }
1753   [_pendingLoadCompleteActions removeAllObjects];
1756 - (void)didFinishWithURL:(const GURL&)currentURL loadSuccess:(BOOL)loadSuccess {
1757   DCHECK(_loadPhase == web::PAGE_LOADED);
1758   _webStateImpl->GetRequestTracker()->FinishPageLoad(currentURL, loadSuccess);
1759   // Reset the navigation type to the default value.
1760   // Note: it is possible that the web view has already started loading the
1761   // next page when this is called. In that case the cache mode can leak to
1762   // (some of) the requests of the next page. It's expected to be an edge case,
1763   // but if it becomes a problem it should be possible to notice it afterwards
1764   // and react to it (by warning the user or reloading the page for example).
1765   _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1766   _webStateImpl->GetRequestTracker()->SetCacheModeFromUIThread(
1767       _webStateImpl->GetCacheMode());
1769   [self restoreStateFromHistory];
1770   _webStateImpl->OnPageLoaded(currentURL, loadSuccess);
1771   _webStateImpl->SetIsLoading(false);
1772   // Inform the embedder the load completed.
1773   [_delegate webDidFinishWithURL:currentURL loadSuccess:loadSuccess];
1776 - (void)finishHistoryNavigationFromEntry:(CRWSessionEntry*)fromEntry {
1777   [_delegate webWillFinishHistoryNavigationFromEntry:fromEntry];
1779   // Check if toEntry was created by a JavaScript window.history.pushState()
1780   // call from fromEntry. If it was, don't load the URL. Instead update
1781   // UIWebView's URL and dispatch a popstate event.
1782   if ([_webStateImpl->GetNavigationManagerImpl().GetSessionController()
1783           isPushStateNavigationBetweenEntry:fromEntry
1784                                    andEntry:self.currentSessionEntry]) {
1785     NSString* state = [self currentSessionEntry]
1786                           .navigationItemImpl->GetSerializedStateObject();
1787     [self finishPushStateNavigationToURL:[self currentNavigationURL]
1788                          withStateObject:state];
1789   } else {
1790     GURL activeURL = [self currentNavigationURL];
1791     GURL fromURL = fromEntry.navigationItem->GetURL();
1792     GURL endURL =
1793         [self updateURLForHistoryNavigationFromURL:fromURL toURL:activeURL];
1794     web::NavigationItem* currentItem =
1795         _webStateImpl->GetNavigationManagerImpl().GetVisibleItem();
1796     ui::PageTransition transition = ui::PageTransitionFromInt(
1797         ui::PAGE_TRANSITION_RELOAD | ui::PAGE_TRANSITION_FORWARD_BACK);
1799     web::WebLoadParams params(endURL);
1800     if (currentItem) {
1801       params.referrer = currentItem->GetReferrer();
1802     }
1803     params.transition_type = transition;
1804     [self loadWithParams:params];
1805   }
1808 - (GURL)updateURLForHistoryNavigationFromURL:(const GURL&)startURL
1809                                        toURL:(const GURL&)endURL {
1810   // Check the state of the fragments on both URLs (aka, is there a '#' in the
1811   // url or not).
1812   if (!startURL.has_ref() || endURL.has_ref()) {
1813     return endURL;
1814   }
1816   // startURL contains a fragment and endURL doesn't. Remove the fragment from
1817   // startURL and compare the resulting string to endURL. If they are equal, add
1818   // # to endURL to cause a hashchange event.
1819   GURL hashless = web::GURLByRemovingRefFromGURL(startURL);
1821   if (hashless != endURL)
1822     return endURL;
1824   url::StringPieceReplacements<std::string> emptyRef;
1825   emptyRef.SetRefStr("");
1826   GURL newEndURL = endURL.ReplaceComponents(emptyRef);
1827   web::NavigationItem* item =
1828       _webStateImpl->GetNavigationManagerImpl().GetVisibleItem();
1829   if (item)
1830     item->SetURL(newEndURL);
1831   return newEndURL;
1834 - (void)evaluateJavaScript:(NSString*)script
1835          JSONResultHandler:
1836              (void (^)(scoped_ptr<base::Value>, NSError*))handler {
1837   [self evaluateJavaScript:script
1838        stringResultHandler:^(NSString* stringResult, NSError* error) {
1839          if (handler) {
1840            scoped_ptr<base::Value> result(
1841                base::JSONReader::Read(base::SysNSStringToUTF8(stringResult)));
1842            DCHECK(result || error);
1843            handler(result.Pass(), error);
1844          }
1845        }];
1848 - (void)addGestureRecognizerToWebView:(UIGestureRecognizer*)recognizer {
1849   if ([_gestureRecognizers containsObject:recognizer])
1850     return;
1852   [self.webView addGestureRecognizer:recognizer];
1853   [_gestureRecognizers addObject:recognizer];
1856 - (void)removeGestureRecognizerFromWebView:(UIGestureRecognizer*)recognizer {
1857   if (![_gestureRecognizers containsObject:recognizer])
1858     return;
1860   [self.webView removeGestureRecognizer:recognizer];
1861   [_gestureRecognizers removeObject:recognizer];
1864 - (void)addToolbarViewToWebView:(UIView*)toolbarView {
1865   DCHECK(toolbarView);
1866   if ([_webViewToolbars containsObject:toolbarView])
1867     return;
1868   [_webViewToolbars addObject:toolbarView];
1869   if (self.webView)
1870     [_containerView addToolbar:toolbarView];
1873 - (void)removeToolbarViewFromWebView:(UIView*)toolbarView {
1874   if (![_webViewToolbars containsObject:toolbarView])
1875     return;
1876   [_webViewToolbars removeObject:toolbarView];
1877   if (self.webView)
1878     [_containerView removeToolbar:toolbarView];
1881 - (CRWJSInjectionReceiver*)jsInjectionReceiver {
1882   return _jsInjectionReceiver;
1885 - (BOOL)cancellable {
1886   return self.sessionController.openedByDOM &&
1887          !self.sessionController.lastCommittedEntry;
1890 - (BOOL)isBeingDestroyed {
1891   return _isBeingDestroyed;
1894 - (BOOL)isHalted {
1895   return _isHalted;
1898 - (web::ReferrerPolicy)referrerPolicyFromString:(const std::string&)policy {
1899   // TODO(stuartmorgan): Remove this temporary bridge to the helper function
1900   // once the referrer handling moves into the subclasses.
1901   return web::ReferrerPolicyFromString(policy);
1904 #pragma mark -
1905 #pragma mark CRWJSInjectionEvaluator Methods
1907 - (void)evaluateJavaScript:(NSString*)script
1908        stringResultHandler:(web::JavaScriptCompletion)handler {
1909   // Subclasses must implement this method.
1910   NOTREACHED();
1913 - (BOOL)scriptHasBeenInjectedForClass:(Class)jsInjectionManagerClass
1914                        presenceBeacon:(NSString*)beacon {
1915   // Subclasses must implement this method.
1916   NOTREACHED();
1917   return NO;
1920 - (void)injectScript:(NSString*)script forClass:(Class)JSInjectionManagerClass {
1921   // Make sure that CRWJSEarlyScriptManager has been injected.
1922   BOOL ealyScriptInjected =
1923       [self scriptHasBeenInjectedForClass:[CRWJSEarlyScriptManager class]
1924                            presenceBeacon:[_earlyScriptManager presenceBeacon]];
1925   if (!ealyScriptInjected &&
1926       JSInjectionManagerClass != [CRWJSEarlyScriptManager class]) {
1927     [_earlyScriptManager inject];
1928   }
1931 - (web::WebViewType)webViewType {
1932   // Subclasses must implement this method.
1933   NOTREACHED();
1934   return web::UI_WEB_VIEW_TYPE;
1937 #pragma mark -
1939 - (void)evaluateUserJavaScript:(NSString*)script {
1940   // Subclasses must implement this method.
1941   NOTREACHED();
1944 - (void)didFinishNavigation {
1945   // This can be called at multiple times after the document has loaded. Do
1946   // nothing if the document has already loaded.
1947   if (_loadPhase == web::PAGE_LOADED)
1948     return;
1949   [self loadCompleteWithSuccess:YES];
1952 - (BOOL)respondToMessage:(base::DictionaryValue*)message
1953        userIsInteracting:(BOOL)userIsInteracting
1954                originURL:(const GURL&)originURL {
1955   std::string command;
1956   if (!message->GetString("command", &command)) {
1957     DLOG(WARNING) << "JS message parameter not found: command";
1958     return NO;
1959   }
1961   SEL handler = [self selectorToHandleJavaScriptCommand:command];
1962   if (!handler) {
1963     if (!self.webStateImpl->OnScriptCommandReceived(
1964             command, *message, originURL, userIsInteracting)) {
1965       // Message was either unexpected or not correctly handled.
1966       // Page is reset as a precaution.
1967       DLOG(WARNING) << "Unexpected message received: " << command;
1968       return NO;
1969     }
1970     return YES;
1971   }
1973   typedef BOOL (*HandlerType)(id, SEL, base::DictionaryValue*, NSDictionary*);
1974   HandlerType handlerImplementation =
1975       reinterpret_cast<HandlerType>([self methodForSelector:handler]);
1976   DCHECK(handlerImplementation);
1977   NSMutableDictionary* context =
1978       [NSMutableDictionary dictionaryWithObject:@(userIsInteracting)
1979                                          forKey:web::kUserIsInteractingKey];
1980   NSURL* originNSURL = net::NSURLWithGURL(originURL);
1981   if (originNSURL)
1982     context[web::kOriginURLKey] = originNSURL;
1983   return handlerImplementation(self, handler, message, context);
1986 - (SEL)selectorToHandleJavaScriptCommand:(const std::string&)command {
1987   static std::map<std::string, SEL>* handlers = nullptr;
1988   static dispatch_once_t onceToken;
1989   dispatch_once(&onceToken, ^{
1990     handlers = new std::map<std::string, SEL>();
1991     (*handlers)["addPluginPlaceholders"] =
1992         @selector(handleAddPluginPlaceholdersMessage:context:);
1993     (*handlers)["chrome.send"] = @selector(handleChromeSendMessage:context:);
1994     (*handlers)["console"] = @selector(handleConsoleMessage:context:);
1995     (*handlers)["dialog.suppressed"] =
1996         @selector(handleDialogSuppressedMessage:context:);
1997     (*handlers)["dialog.willShow"] =
1998         @selector(handleDialogWillShowMessage:context:);
1999     (*handlers)["document.favicons"] =
2000         @selector(handleDocumentFaviconsMessage:context:);
2001     (*handlers)["document.retitled"] =
2002         @selector(handleDocumentRetitledMessage:context:);
2003     (*handlers)["document.submit"] =
2004         @selector(handleDocumentSubmitMessage:context:);
2005     (*handlers)["externalRequest"] =
2006         @selector(handleExternalRequestMessage:context:);
2007     (*handlers)["form.activity"] =
2008         @selector(handleFormActivityMessage:context:);
2009     (*handlers)["form.requestAutocomplete"] =
2010         @selector(handleFormRequestAutocompleteMessage:context:);
2011     (*handlers)["navigator.credentials.request"] =
2012         @selector(handleCredentialsRequestedMessage:context:);
2013     (*handlers)["navigator.credentials.notifySignedIn"] =
2014         @selector(handleSignedInMessage:context:);
2015     (*handlers)["navigator.credentials.notifySignedOut"] =
2016         @selector(handleSignedOutMessage:context:);
2017     (*handlers)["navigator.credentials.notifyFailedSignIn"] =
2018         @selector(handleSignInFailedMessage:context:);
2019     (*handlers)["resetExternalRequest"] =
2020         @selector(handleResetExternalRequestMessage:context:);
2021     (*handlers)["window.close.self"] =
2022         @selector(handleWindowCloseSelfMessage:context:);
2023     (*handlers)["window.error"] = @selector(handleWindowErrorMessage:context:);
2024     (*handlers)["window.hashchange"] =
2025         @selector(handleWindowHashChangeMessage:context:);
2026     (*handlers)["window.history.back"] =
2027         @selector(handleWindowHistoryBackMessage:context:);
2028     (*handlers)["window.history.didPushState"] =
2029         @selector(handleWindowHistoryDidPushStateMessage:context:);
2030     (*handlers)["window.history.didReplaceState"] =
2031         @selector(handleWindowHistoryDidReplaceStateMessage:context:);
2032     (*handlers)["window.history.forward"] =
2033         @selector(handleWindowHistoryForwardMessage:context:);
2034     (*handlers)["window.history.go"] =
2035         @selector(handleWindowHistoryGoMessage:context:);
2036   });
2037   DCHECK(handlers);
2038   auto iter = handlers->find(command);
2039   return iter != handlers->end() ? iter->second : nullptr;
2042 #pragma mark -
2043 #pragma mark JavaScript message handlers
2045 - (BOOL)handleAddPluginPlaceholdersMessage:(base::DictionaryValue*)message
2046                                    context:(NSDictionary*)context {
2047   // Inject the script that adds the plugin placeholders.
2048   [[_jsInjectionReceiver
2049       instanceOfClass:[CRWJSPluginPlaceholderManager class]] inject];
2050   return YES;
2053 - (BOOL)handleChromeSendMessage:(base::DictionaryValue*)message
2054                         context:(NSDictionary*)context {
2055   if (_webStateImpl->HasWebUI()) {
2056     const GURL currentURL([self currentURL]);
2057     if (web::GetWebClient()->IsAppSpecificURL(currentURL)) {
2058       std::string messageContent;
2059       base::ListValue* arguments = nullptr;
2060       if (!message->GetString("message", &messageContent)) {
2061         DLOG(WARNING) << "JS message parameter not found: message";
2062         return NO;
2063       }
2064       if (!message->GetList("arguments", &arguments)) {
2065         DLOG(WARNING) << "JS message parameter not found: arguments";
2066         return NO;
2067       }
2068       _webStateImpl->ProcessWebUIMessage(currentURL, messageContent,
2069                                          *arguments);
2070     }
2071   }
2072   return YES;
2075 - (BOOL)handleConsoleMessage:(base::DictionaryValue*)message
2076                      context:(NSDictionary*)context {
2077   // Do not log if JS logging is off.
2078   if (![[NSUserDefaults standardUserDefaults] boolForKey:web::kLogJavaScript]) {
2079     return YES;
2080   }
2082   std::string method;
2083   if (!message->GetString("method", &method)) {
2084     DLOG(WARNING) << "JS message parameter not found: method";
2085     return NO;
2086   }
2087   std::string consoleMessage;
2088   if (!message->GetString("message", &consoleMessage)) {
2089     DLOG(WARNING) << "JS message parameter not found: message";
2090     return NO;
2091   }
2092   std::string origin;
2093   if (!message->GetString("origin", &origin)) {
2094     DLOG(WARNING) << "JS message parameter not found: origin";
2095     return NO;
2096   }
2098   DVLOG(0) << origin << " [" << method << "] " << consoleMessage;
2099   return YES;
2102 - (BOOL)handleDialogSuppressedMessage:(base::DictionaryValue*)message
2103                               context:(NSDictionary*)context {
2104   if ([_delegate
2105           respondsToSelector:@selector(webControllerDidSuppressDialog:)]) {
2106     [_delegate webControllerDidSuppressDialog:self];
2107   }
2108   return YES;
2111 - (BOOL)handleDialogWillShowMessage:(base::DictionaryValue*)message
2112                             context:(NSDictionary*)context {
2113   if ([_delegate respondsToSelector:@selector(webControllerWillShowDialog:)]) {
2114     [_delegate webControllerWillShowDialog:self];
2115   }
2116   return YES;
2119 - (BOOL)handleDocumentFaviconsMessage:(base::DictionaryValue*)message
2120                               context:(NSDictionary*)context {
2121   base::ListValue* favicons = nullptr;
2122   if (!message->GetList("favicons", &favicons)) {
2123     DLOG(WARNING) << "JS message parameter not found: favicons";
2124     return NO;
2125   }
2126   std::vector<web::FaviconURL> urls;
2127   for (size_t fav_idx = 0; fav_idx != favicons->GetSize(); ++fav_idx) {
2128     base::DictionaryValue* favicon = nullptr;
2129     if (!favicons->GetDictionary(fav_idx, &favicon))
2130       return NO;
2131     std::string href;
2132     std::string rel;
2133     if (!favicon->GetString("href", &href)) {
2134       DLOG(WARNING) << "JS message parameter not found: href";
2135       return NO;
2136     }
2137     if (!favicon->GetString("rel", &rel)) {
2138       DLOG(WARNING) << "JS message parameter not found: rel";
2139       return NO;
2140     }
2141     web::FaviconURL::IconType icon_type = web::FaviconURL::FAVICON;
2142     if (rel == "apple-touch-icon")
2143       icon_type = web::FaviconURL::TOUCH_ICON;
2144     else if (rel == "apple-touch-icon-precomposed")
2145       icon_type = web::FaviconURL::TOUCH_PRECOMPOSED_ICON;
2146     urls.push_back(
2147         web::FaviconURL(GURL(href), icon_type, std::vector<gfx::Size>()));
2148   }
2149   if (!urls.empty())
2150     _webStateImpl->OnFaviconUrlUpdated(urls);
2151   return YES;
2154 - (BOOL)handleDocumentSubmitMessage:(base::DictionaryValue*)message
2155                             context:(NSDictionary*)context {
2156   std::string href;
2157   if (!message->GetString("href", &href)) {
2158     DLOG(WARNING) << "JS message parameter not found: href";
2159     return NO;
2160   }
2161   const GURL targetURL(href);
2162   const GURL currentURL([self currentURL]);
2163   bool targetsFrame = false;
2164   message->GetBoolean("targetsFrame", &targetsFrame);
2165   if (!targetsFrame && web::UrlHasWebScheme(targetURL)) {
2166     // The referrer is not known yet, and will be updated later.
2167     const web::Referrer emptyReferrer;
2168     [self registerLoadRequest:targetURL
2169                      referrer:emptyReferrer
2170                    transition:ui::PAGE_TRANSITION_FORM_SUBMIT];
2171   }
2172   std::string formName;
2173   message->GetString("formName", &formName);
2174   base::scoped_nsobject<NSSet> observers([_observers copy]);
2175   // We decide the form is user-submitted if the user has interacted with
2176   // the main page (using logic from the popup blocker), or if the keyboard
2177   // is visible.
2178   BOOL submittedByUser = [context[web::kUserIsInteractingKey] boolValue] ||
2179                          [_webViewProxy getKeyboardAccessory];
2180   _webStateImpl->OnDocumentSubmitted(formName, submittedByUser);
2181   return YES;
2184 - (BOOL)handleExternalRequestMessage:(base::DictionaryValue*)message
2185                              context:(NSDictionary*)context {
2186   std::string href;
2187   std::string target;
2188   std::string referrerPolicy;
2189   if (!message->GetString("href", &href)) {
2190     DLOG(WARNING) << "JS message parameter not found: href";
2191     return NO;
2192   }
2193   if (!message->GetString("target", &target)) {
2194     DLOG(WARNING) << "JS message parameter not found: target";
2195     return NO;
2196   }
2197   if (!message->GetString("referrerPolicy", &referrerPolicy)) {
2198     DLOG(WARNING) << "JS message parameter not found: referrerPolicy";
2199     return NO;
2200   }
2201   // Round-trip the href through NSURL; this URL will be compared as a
2202   // string against a UIWebView-provided NSURL later, and must match exactly
2203   // for the new window to trigger, so the escaping needs to be NSURL-style.
2204   // TODO(stuartmorgan): Comparing against a URL whose exact formatting we
2205   // don't control is fundamentally fragile; try to find another
2206   // way of handling this.
2207   DCHECK(context[web::kUserIsInteractingKey]);
2208   NSString* windowName =
2209       base::SysUTF8ToNSString(href + web::kWindowNameSeparator + target);
2210   _externalRequest.reset(new web::NewWindowInfo(
2211       net::GURLWithNSURL(net::NSURLWithGURL(GURL(href))), windowName,
2212       web::ReferrerPolicyFromString(referrerPolicy),
2213       [context[web::kUserIsInteractingKey] boolValue]));
2214   return YES;
2217 - (BOOL)handleFormActivityMessage:(base::DictionaryValue*)message
2218                           context:(NSDictionary*)context {
2219   std::string formName;
2220   std::string fieldName;
2221   std::string type;
2222   std::string value;
2223   int keyCode = web::WebStateObserver::kInvalidFormKeyCode;
2224   bool inputMissing = false;
2225   if (!message->GetString("formName", &formName) ||
2226       !message->GetString("fieldName", &fieldName) ||
2227       !message->GetString("type", &type) ||
2228       !message->GetString("value", &value)) {
2229     inputMissing = true;
2230   }
2232   if (!message->GetInteger("keyCode", &keyCode) || keyCode < 0)
2233     keyCode = web::WebStateObserver::kInvalidFormKeyCode;
2234   _webStateImpl->OnFormActivityRegistered(formName, fieldName, type, value,
2235                                           keyCode, inputMissing);
2236   return YES;
2239 - (BOOL)handleFormRequestAutocompleteMessage:(base::DictionaryValue*)message
2240                                      context:(NSDictionary*)context {
2241   std::string formName;
2242   if (!message->GetString("formName", &formName)) {
2243     DLOG(WARNING) << "JS message parameter not found: formName";
2244     return NO;
2245   }
2246   DCHECK(context[web::kUserIsInteractingKey]);
2247   _webStateImpl->OnAutocompleteRequested(
2248       net::GURLWithNSURL(context[web::kOriginURLKey]), formName,
2249       [context[web::kUserIsInteractingKey] boolValue]);
2250   return YES;
2253 - (BOOL)handleCredentialsRequestedMessage:(base::DictionaryValue*)message
2254                                   context:(NSDictionary*)context {
2255   int request_id = -1;
2256   if (!message->GetInteger("requestId", &request_id)) {
2257     DLOG(WARNING) << "JS message parameter not found: requestId";
2258     return NO;
2259   }
2260   bool suppress_ui = false;
2261   if (!message->GetBoolean("suppressUI", &suppress_ui)) {
2262     DLOG(WARNING) << "JS message parameter not found: suppressUI";
2263     return NO;
2264   }
2265   base::ListValue* federations_value = nullptr;
2266   if (!message->GetList("federations", &federations_value)) {
2267     DLOG(WARNING) << "JS message parameter not found: federations";
2268     return NO;
2269   }
2270   std::vector<std::string> federations;
2271   for (auto federation_value : *federations_value) {
2272     std::string federation;
2273     if (!federation_value->GetAsString(&federation)) {
2274       DLOG(WARNING) << "JS message parameter 'federations' contains wrong type";
2275       return NO;
2276     }
2277     federations.push_back(federation);
2278   }
2279   DCHECK(context[web::kUserIsInteractingKey]);
2280   _webStateImpl->OnCredentialsRequested(
2281       request_id, net::GURLWithNSURL(context[web::kOriginURLKey]), suppress_ui,
2282       federations, [context[web::kUserIsInteractingKey] boolValue]);
2283   return YES;
2286 - (BOOL)handleSignedInMessage:(base::DictionaryValue*)message
2287                       context:(NSDictionary*)context {
2288   int request_id = -1;
2289   if (!message->GetInteger("requestId", &request_id)) {
2290     DLOG(WARNING) << "JS message parameter not found: requestId";
2291     return NO;
2292   }
2293   base::DictionaryValue* credential_data = nullptr;
2294   web::Credential credential;
2295   if (message->GetDictionary("credential", &credential_data)) {
2296     if (!web::DictionaryValueToCredential(*credential_data, &credential)) {
2297       DLOG(WARNING) << "JS message parameter 'credential' is invalid";
2298       return NO;
2299     }
2300     _webStateImpl->OnSignedIn(request_id,
2301                               net::GURLWithNSURL(context[web::kOriginURLKey]),
2302                               credential);
2303   } else {
2304     _webStateImpl->OnSignedIn(request_id,
2305                               net::GURLWithNSURL(context[web::kOriginURLKey]));
2306   }
2307   return YES;
2310 - (BOOL)handleSignedOutMessage:(base::DictionaryValue*)message
2311                        context:(NSDictionary*)context {
2312   int request_id = -1;
2313   if (!message->GetInteger("requestId", &request_id)) {
2314     DLOG(WARNING) << "JS message parameter not found: requestId";
2315     return NO;
2316   }
2317   _webStateImpl->OnSignedOut(request_id,
2318                              net::GURLWithNSURL(context[web::kOriginURLKey]));
2319   return YES;
2322 - (BOOL)handleSignInFailedMessage:(base::DictionaryValue*)message
2323                           context:(NSDictionary*)context {
2324   int request_id = -1;
2325   if (!message->GetInteger("requestId", &request_id)) {
2326     DLOG(WARNING) << "JS message parameter not found: requestId";
2327     return NO;
2328   }
2329   base::DictionaryValue* credential_data = nullptr;
2330   web::Credential credential;
2331   if (message->GetDictionary("credential", &credential_data)) {
2332     if (!web::DictionaryValueToCredential(*credential_data, &credential)) {
2333       DLOG(WARNING) << "JS message parameter 'credential' is invalid";
2334       return NO;
2335     }
2336     _webStateImpl->OnSignInFailed(
2337         request_id, net::GURLWithNSURL(context[web::kOriginURLKey]),
2338         credential);
2339   } else {
2340     _webStateImpl->OnSignInFailed(
2341         request_id, net::GURLWithNSURL(context[web::kOriginURLKey]));
2342   }
2343   return YES;
2346 - (BOOL)handleResetExternalRequestMessage:(base::DictionaryValue*)message
2347                                   context:(NSDictionary*)context {
2348   _externalRequest.reset();
2349   return YES;
2352 - (BOOL)handleWindowCloseSelfMessage:(base::DictionaryValue*)message
2353                              context:(NSDictionary*)context {
2354   [self orderClose];
2355   return YES;
2358 - (BOOL)handleWindowErrorMessage:(base::DictionaryValue*)message
2359                          context:(NSDictionary*)context {
2360   std::string errorMessage;
2361   if (!message->GetString("message", &errorMessage)) {
2362     DLOG(WARNING) << "JS message parameter not found: message";
2363     return NO;
2364   }
2365   DLOG(ERROR) << "JavaScript error: " << errorMessage
2366               << " URL:" << [self currentURL].spec();
2367   return YES;
2370 - (BOOL)handleWindowHashChangeMessage:(base::DictionaryValue*)message
2371                               context:(NSDictionary*)context {
2372   [self checkForUnexpectedURLChange];
2374   // Notify the observers.
2375   _webStateImpl->OnUrlHashChanged();
2376   return YES;
2379 - (BOOL)handleWindowHistoryBackMessage:(base::DictionaryValue*)message
2380                                context:(NSDictionary*)context {
2381   [self goBack];
2382   return YES;
2385 - (BOOL)handleWindowHistoryForwardMessage:(base::DictionaryValue*)message
2386                                   context:(NSDictionary*)context {
2387   [self goForward];
2388   return YES;
2391 - (BOOL)handleWindowHistoryGoMessage:(base::DictionaryValue*)message
2392                              context:(NSDictionary*)context {
2393   int delta;
2394   message->GetInteger("value", &delta);
2395   [self goDelta:delta];
2396   return YES;
2399 - (BOOL)handleWindowHistoryDidPushStateMessage:(base::DictionaryValue*)message
2400                                        context:(NSDictionary*)context {
2401   std::string pageURL;
2402   std::string baseURL;
2403   if (!message->GetString("pageUrl", &pageURL) ||
2404       !message->GetString("baseUrl", &baseURL)) {
2405     DLOG(WARNING) << "JS message parameter not found: pageUrl or baseUrl";
2406     return NO;
2407   }
2408   GURL pushURL = web::history_state_util::GetHistoryStateChangeUrl(
2409       [self currentURL], GURL(baseURL), pageURL);
2410   // UIWebView seems to choke on unicode characters that haven't been
2411   // escaped; escape the URL now so the expected load URL is correct.
2412   pushURL = URLEscapedForHistory(pushURL);
2413   if (!pushURL.is_valid())
2414     return YES;
2415   const NavigationManagerImpl& navigationManager =
2416       _webStateImpl->GetNavigationManagerImpl();
2417   web::NavigationItem* navItem = [self currentNavItem];
2418   // PushState happened before first navigation entry or called right after
2419   // window.open when the url is empty.
2420   if (!navItem ||
2421       (navigationManager.GetEntryCount() <= 1 && navItem->GetURL().is_empty()))
2422     return YES;
2423   if (!web::history_state_util::IsHistoryStateChangeValid(navItem->GetURL(),
2424                                                           pushURL)) {
2425     // A redirect may have occurred just prior to the pushState. Check if
2426     // the URL needs to be updated.
2427     // TODO(bdibello): Investigate how the pushState() is handled before the
2428     // redirect and after core.js injection.
2429     [self checkForUnexpectedURLChange];
2430   }
2431   if (!web::history_state_util::IsHistoryStateChangeValid(
2432           [self currentNavItem]->GetURL(), pushURL)) {
2433     // If the current session entry URL origin still doesn't match pushURL's
2434     // origin, ignore the pushState. This can happen if a new URL is loaded
2435     // just before the pushState.
2436     return YES;
2437   }
2438   std::string stateObjectJSON;
2439   if (!message->GetString("stateObject", &stateObjectJSON)) {
2440     DLOG(WARNING) << "JS message parameter not found: stateObject";
2441     return NO;
2442   }
2443   NSString* stateObject = base::SysUTF8ToNSString(stateObjectJSON);
2444   _URLOnStartLoading = pushURL;
2445   _lastRegisteredRequestURL = pushURL;
2446   [self pushStateWithPageURL:pushURL stateObject:stateObject];
2448   NSString* replaceWebViewJS =
2449       [self javascriptToReplaceWebViewURL:pushURL stateObjectJSON:stateObject];
2450   base::WeakNSObject<CRWWebController> weakSelf(self);
2451   [self evaluateJavaScript:replaceWebViewJS
2452        stringResultHandler:^(NSString*, NSError*) {
2453          if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
2454            return;
2455          base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
2456          [strongSelf optOutScrollsToTopForSubviews];
2457          // Notify the observers.
2458          strongSelf.get()->_webStateImpl->OnHistoryStateChanged();
2459          [strongSelf didFinishNavigation];
2460        }];
2461   return YES;
2464 - (BOOL)handleWindowHistoryDidReplaceStateMessage:
2465     (base::DictionaryValue*)message
2466                                           context:(NSDictionary*)context {
2467   std::string pageURL;
2468   std::string baseURL;
2469   if (!message->GetString("pageUrl", &pageURL) ||
2470       !message->GetString("baseUrl", &baseURL)) {
2471     DLOG(WARNING) << "JS message parameter not found: pageUrl or baseUrl";
2472     return NO;
2473   }
2474   GURL replaceURL = web::history_state_util::GetHistoryStateChangeUrl(
2475       [self currentURL], GURL(baseURL), pageURL);
2476   // UIWebView seems to choke on unicode characters that haven't been
2477   // escaped; escape the URL now so the expected load URL is correct.
2478   replaceURL = URLEscapedForHistory(replaceURL);
2479   if (!replaceURL.is_valid())
2480     return YES;
2481   const NavigationManagerImpl& navigationManager =
2482       _webStateImpl->GetNavigationManagerImpl();
2483   web::NavigationItem* navItem = [self currentNavItem];
2484   // ReplaceState happened before first navigation entry or called right
2485   // after window.open when the url is empty/not valid.
2486   if (!navItem ||
2487       (navigationManager.GetEntryCount() <= 1 && navItem->GetURL().is_empty()))
2488     return YES;
2489   if (!web::history_state_util::IsHistoryStateChangeValid(navItem->GetURL(),
2490                                                           replaceURL)) {
2491     // A redirect may have occurred just prior to the replaceState. Check if
2492     // the URL needs to be updated.
2493     [self checkForUnexpectedURLChange];
2494   }
2495   if (!web::history_state_util::IsHistoryStateChangeValid(
2496           [self currentNavItem]->GetURL(), replaceURL)) {
2497     // If the current session entry URL origin still doesn't match
2498     // replaceURL's origin, ignore the replaceState. This can happen if a
2499     // new URL is loaded just before the replaceState.
2500     return YES;
2501   }
2502   std::string stateObjectJSON;
2503   if (!message->GetString("stateObject", &stateObjectJSON)) {
2504     DLOG(WARNING) << "JS message parameter not found: stateObject";
2505     return NO;
2506   }
2507   NSString* stateObject = base::SysUTF8ToNSString(stateObjectJSON);
2508   _URLOnStartLoading = replaceURL;
2509   _lastRegisteredRequestURL = replaceURL;
2510   [self replaceStateWithPageURL:replaceURL stateObject:stateObject];
2511   NSString* replaceStateJS = [self javascriptToReplaceWebViewURL:replaceURL
2512                                                  stateObjectJSON:stateObject];
2513   base::WeakNSObject<CRWWebController> weakSelf(self);
2514   [self evaluateJavaScript:replaceStateJS
2515        stringResultHandler:^(NSString*, NSError*) {
2516          if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
2517            return;
2518          base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
2519          [strongSelf didFinishNavigation];
2520        }];
2521   return YES;
2524 #pragma mark -
2526 - (BOOL)wantsKeyboardShield {
2527   if (_nativeController &&
2528       [_nativeController respondsToSelector:@selector(wantsKeyboardShield)]) {
2529     return [_nativeController wantsKeyboardShield];
2530   }
2531   return YES;
2534 - (BOOL)wantsLocationBarHintText {
2535   if (_nativeController &&
2536       [_nativeController
2537           respondsToSelector:@selector(wantsLocationBarHintText)]) {
2538     return [_nativeController wantsLocationBarHintText];
2539   }
2540   return YES;
2543 // TODO(stuartmorgan): This method conflates document changes and URL changes;
2544 // we should be distinguishing better, and be clear about the expected
2545 // WebDelegate and WCO callbacks in each case.
2546 - (void)webPageChanged {
2547   DCHECK(_loadPhase == web::LOAD_REQUESTED);
2549   const GURL currentURL([self currentURL]);
2550   web::Referrer referrer = [self currentReferrer];
2551   // If no referrer was known in advance, record it now. (If there was one,
2552   // keep it since it will have a more accurate URL and policy than what can
2553   // be extracted from the landing page.)
2554   web::NavigationItem* currentItem = [self currentNavItem];
2555   if (!currentItem->GetReferrer().url.is_valid()) {
2556     currentItem->SetReferrer(referrer);
2557   }
2559   // TODO(stuartmorgan): This shouldn't be called for hash state or
2560   // push/replaceState.
2561   [self resetDocumentSpecificState];
2563   [self didStartLoadingURL:currentURL updateHistory:YES];
2565   // TODO(stuartmorgan): Eliminate this; interested parties should be direct
2566   // delegates/observers.
2567   [[NSNotificationCenter defaultCenter]
2568       postNotificationName:web::kPageChangedNotification
2569                     object:self];
2572 - (void)resetDocumentSpecificState {
2573   _lastClickTimeInSeconds = -DBL_MAX;
2574   _clickInProgress = NO;
2576   _lastSeenWindowID.reset([[_windowIDJSManager windowId] copy]);
2579 - (void)didStartLoadingURL:(const GURL&)url updateHistory:(BOOL)updateHistory {
2580   _loadPhase = web::PAGE_LOADING;
2581   _URLOnStartLoading = url;
2582   _scrollStateOnStartLoading = self.pageScrollState;
2584   _userInteractionRegistered = NO;
2586   [[self sessionController] commitPendingEntry];
2587   _webStateImpl->GetRequestTracker()->StartPageLoad(
2588       url, [[self sessionController] currentEntry]);
2589   [_delegate webDidStartLoadingURL:url shouldUpdateHistory:updateHistory];
2592 - (BOOL)checkForUnexpectedURLChange {
2593   // Subclasses may override this method to check for and handle URL changes.
2594   return NO;
2597 - (void)wasShown {
2598   if (_nativeController &&
2599       [_nativeController respondsToSelector:@selector(wasShown)]) {
2600     [_nativeController wasShown];
2601   }
2604 - (void)wasHidden {
2605   if (_isHalted)
2606     return;
2607   if (_nativeController &&
2608       [_nativeController respondsToSelector:@selector(wasHidden)]) {
2609     [_nativeController wasHidden];
2610   }
2613 + (BOOL)webControllerCanShow:(const GURL&)url {
2614   return web::UrlHasWebScheme(url) ||
2615          web::GetWebClient()->IsAppSpecificURL(url) ||
2616          url.SchemeIs(url::kFileScheme) || url.SchemeIs(url::kAboutScheme);
2619 - (void)setUserInteractionRegistered:(BOOL)flag {
2620   _userInteractionRegistered = flag;
2623 - (BOOL)userInteractionRegistered {
2624   return _userInteractionRegistered;
2627 - (BOOL)useDesktopUserAgent {
2628   web::NavigationItem* item = [self currentNavItem];
2629   return item && item->IsOverridingUserAgent();
2632 - (void)cachePOSTDataForRequest:(NSURLRequest*)request
2633                  inSessionEntry:(CRWSessionEntry*)currentSessionEntry {
2634   NSUInteger maxPOSTDataSizeInBytes = 4096;
2635   NSString* cookieHeaderName = @"cookie";
2637   web::NavigationItemImpl* currentItem = currentSessionEntry.navigationItemImpl;
2638   DCHECK(currentItem);
2639   const bool shouldUpdateEntry =
2640       ui::PageTransitionCoreTypeIs(currentItem->GetTransitionType(),
2641                                    ui::PAGE_TRANSITION_FORM_SUBMIT) &&
2642       ![request HTTPBodyStream] &&  // Don't cache streams.
2643       !currentItem->HasPostData() &&
2644       currentItem->GetURL() == net::GURLWithNSURL([request URL]);
2645   const bool belowSizeCap =
2646       [[request HTTPBody] length] < maxPOSTDataSizeInBytes;
2647   DLOG_IF(WARNING, shouldUpdateEntry && !belowSizeCap)
2648       << "Data in POST request exceeds the size cap (" << maxPOSTDataSizeInBytes
2649       << " bytes), and will not be cached.";
2651   if (shouldUpdateEntry && belowSizeCap) {
2652     currentItem->SetPostData([request HTTPBody]);
2653     currentItem->ResetHttpRequestHeaders();
2654     currentItem->AddHttpRequestHeaders([request allHTTPHeaderFields]);
2655     // Don't cache the "Cookie" header.
2656     // According to NSURLRequest documentation, |-valueForHTTPHeaderField:| is
2657     // case insensitive, so it's enough to test the lower case only.
2658     if ([request valueForHTTPHeaderField:cookieHeaderName]) {
2659       // Case insensitive search in |headers|.
2660       NSSet* cookieKeys = [currentItem->GetHttpRequestHeaders()
2661           keysOfEntriesPassingTest:^(id key, id obj, BOOL* stop) {
2662             NSString* header = (NSString*)key;
2663             const BOOL found =
2664                 [header caseInsensitiveCompare:cookieHeaderName] ==
2665                 NSOrderedSame;
2666             *stop = found;
2667             return found;
2668           }];
2669       DCHECK_EQ(1u, [cookieKeys count]);
2670       currentItem->RemoveHttpRequestHeaderForKey([cookieKeys anyObject]);
2671     }
2672   }
2675 // TODO(stuartmorgan): This is mostly logic from the original UIWebView delegate
2676 // method, which provides less information than the WKWebView version. Audit
2677 // this for things that should be handled in the subclass instead.
2678 - (BOOL)shouldAllowLoadWithRequest:(NSURLRequest*)request
2679                        isLinkClick:(BOOL)isLinkClick {
2680   NSURL* nsurl = request.URL;
2681   GURL url = net::GURLWithNSURL(request.URL);
2683   // Check if the request should be delayed.
2684   if (_externalRequest && _externalRequest->url == url) {
2685     // Links that can't be shown in a tab by Chrome but can be handled by
2686     // external apps (e.g. tel:, mailto:) are opened directly despite the target
2687     // attribute on the link. We don't open a new tab for them because Mobile
2688     // Safari doesn't do that (and sites are expecting us to do the same) and
2689     // also because there would be nothing shown in that new tab; it would
2690     // remain on about:blank (see crbug.com/240178)
2691     if ([CRWWebController webControllerCanShow:url] ||
2692         ![_delegate openExternalURL:url]) {
2693       web::NewWindowInfo windowInfo = *_externalRequest;
2694       dispatch_async(dispatch_get_main_queue(), ^{
2695         [self openPopupWithInfo:windowInfo];
2696       });
2697     }
2698     _externalRequest.reset();
2699     return NO;
2700   }
2702   BOOL shouldCheckNativeApp = [self cancellable];
2704   // Check if the link navigation leads to a launch of an external app.
2705   // TODO(shreyasv): Change this such that handling/stealing of link navigations
2706   // is delegated to the WebDelegate and the logic around external app launching
2707   // is moved there as well.
2708   if (shouldCheckNativeApp || isLinkClick) {
2709     // Check If the URL is handled by a native app.
2710     if ([self urlTriggersNativeAppLaunch:url
2711                                sourceURL:[self currentNavigationURL]]) {
2712       // External app has been launched successfully. Stop the current page
2713       // load operation (e.g. notifying all observers) and record the URL so
2714       // that errors reported following the 'NO' reply can be safely ignored.
2715       if ([self cancellable])
2716         [_delegate webPageOrderedClose];
2717       [self abortLoad];
2718       [_openedApplicationURL addObject:nsurl];
2719       return NO;
2720     }
2721   }
2723   // The WebDelegate may instruct the CRWWebController to stop loading, and
2724   // instead instruct the next page to be loaded in an animation.
2725   DCHECK(self.webView);
2726   if (![self shouldOpenURL:url
2727            mainDocumentURL:net::GURLWithNSURL(request.mainDocumentURL)
2728                linkClicked:isLinkClick]) {
2729     return NO;
2730   }
2732   // If the URL doesn't look like one we can show, try to open the link with an
2733   // external application.
2734   // TODO(droger):  Check transition type before opening an external
2735   // application? For example, only allow it for TYPED and LINK transitions.
2736   if (![CRWWebController webControllerCanShow:url]) {
2737     if (![self shouldOpenExternalURL:url]) {
2738       return NO;
2739     }
2740     // TODO(jimblackler): investigate possible side-effects of this where
2741     // navigation to the unknown scheme was performed by a script or in an
2742     // iFrame.
2743     [self abortLoad];
2744     if ([_delegate openExternalURL:url]) {
2745       // Record the URL so that errors reported following the 'NO' reply can be
2746       // safely ignored.
2747       [_openedApplicationURL addObject:nsurl];
2748       return NO;
2749     }
2750     return NO;
2751   }
2753   if ([[request HTTPMethod] isEqualToString:@"POST"]) {
2754     [self cachePOSTDataForRequest:request
2755                    inSessionEntry:[self currentSessionEntry]];
2756   }
2758   return YES;
2761 - (void)restoreStateAfterURLRejection {
2762   [[self sessionController] discardNonCommittedEntries];
2764   // Re-register the user agent, because UIWebView will sometimes try to read
2765   // the agent again from a saved search result page in which no other page has
2766   // yet been loaded. See crbug.com/260370.
2767   [self registerUserAgent];
2769   // Reset |_lastRegisteredRequestURL| so that it reflects the URL from before
2770   // the load was rejected. This value may be out of sync because
2771   // |_lastRegisteredRequestURL| may have already been updated before the load
2772   // was rejected.
2773   _lastRegisteredRequestURL = [self currentURL];
2774   _loadPhase = web::PAGE_LOADING;
2775   [self didFinishNavigation];
2778 - (void)handleLoadError:(NSError*)error inMainFrame:(BOOL)inMainFrame {
2779   if ([error code] == NSURLErrorUnsupportedURL)
2780     return;
2781   // In cases where a Plug-in handles the load do not take any further action.
2782   if ([[error domain] isEqual:WebKitErrorDomain] &&
2783       ([error code] == WebKitErrorPlugInLoadFailed ||
2784        [error code] == WebKitErrorCannotShowURL))
2785     return;
2787   // Continue processing only if the error is on the main request or is the
2788   // result of a user interaction.
2789   NSDictionary* userInfo = [error userInfo];
2790   // |userinfo| contains the request creation date as a NSDate.
2791   NSTimeInterval requestCreationDate =
2792       [[userInfo objectForKey:@"CreationDate"] timeIntervalSinceReferenceDate];
2793   bool userInteracted = false;
2794   if (requestCreationDate != 0.0) {
2795     NSTimeInterval timeSinceInteraction =
2796         requestCreationDate - _lastClickTimeInSeconds;
2797     // The error is considered to be the result of a user interaction if any
2798     // interaction happened just before the request was made.
2799     // TODO(droger): If the user interacted with the page after the request was
2800     // made (i.e. creationTimeSinceLastInteraction < 0), then
2801     // |_lastClickTimeInSeconds| has been overridden. The current behavior is to
2802     // discard the interstitial in that case. A better decision could be made if
2803     // we had a history of all the user interactions instead of just the last
2804     // one.
2805     userInteracted =
2806         timeSinceInteraction < kMaximumDelayForUserInteractionInSeconds &&
2807         _lastClickTimeInSeconds > _lastTransferTimeInSeconds &&
2808         timeSinceInteraction >= 0.0;
2809   } else {
2810     // If the error does not have timing information, check if the user
2811     // interacted with the page recently.
2812     userInteracted = [self userIsInteracting];
2813   }
2814   if (!inMainFrame && !userInteracted)
2815     return;
2817   NSURL* errorURL = [NSURL
2818       URLWithString:[userInfo objectForKey:NSURLErrorFailingURLStringErrorKey]];
2819   const GURL errorGURL = net::GURLWithNSURL(errorURL);
2821   // Handles Frame Load Interrupted errors from WebView.
2822   if ([[error domain] isEqualToString:WebKitErrorDomain] &&
2823       [error code] == WebKitErrorFrameLoadInterruptedByPolicyChange) {
2824     // See if the delegate wants to handle this case.
2825     if (errorGURL.is_valid() &&
2826         [_delegate
2827             respondsToSelector:@selector(
2828                                    controllerForUnhandledContentAtURL:)]) {
2829       id<CRWNativeContent> controller =
2830           [_delegate controllerForUnhandledContentAtURL:errorGURL];
2831       if (controller) {
2832         [self loadCompleteWithSuccess:NO];
2833         [self removeWebViewAllowingCachedReconstruction:NO];
2834         [self setNativeController:controller];
2835         [self loadNativeViewWithSuccess:YES];
2836         return;
2837       }
2838     }
2840     // Otherwise, handle the error normally.
2841     if ([_openedApplicationURL containsObject:errorURL])
2842       return;
2843     // Certain frame errors don't have URL information for some reason; for
2844     // those cases (so far the only known case is plugin content loaded directly
2845     // in a frame) just ignore the error. See crbug.com/414295
2846     if (!errorURL) {
2847       DCHECK(!inMainFrame);
2848       return;
2849     }
2850     // The wrapper error uses the URL of the error and not the requested URL
2851     // (which can be different in case of a redirect) to match desktop Chrome
2852     // behavior.
2853     NSError* wrapperError = [NSError
2854         errorWithDomain:[error domain]
2855                    code:[error code]
2856                userInfo:@{
2857                  NSURLErrorFailingURLStringErrorKey : [errorURL absoluteString],
2858                  NSUnderlyingErrorKey : error
2859                }];
2860     [self loadCompleteWithSuccess:NO];
2861     [self loadErrorInNativeView:wrapperError];
2862     return;
2863   }
2865   // Ignore cancelled errors.
2866   if ([error code] == NSURLErrorCancelled) {
2867     NSError* underlyingError = [userInfo objectForKey:NSUnderlyingErrorKey];
2868     if (underlyingError) {
2869       DCHECK([underlyingError isKindOfClass:[NSError class]]);
2871       // The Error contains an NSUnderlyingErrorKey so it's being generated
2872       // in the Chrome network stack. Aborting the load in this case.
2873       [self abortLoad];
2875       switch ([underlyingError code]) {
2876         case net::ERR_ABORTED:
2877           // |NSURLErrorCancelled| errors with underlying net error code
2878           // |net::ERR_ABORTED| are used by the Chrome network stack to
2879           // indicate that the current load should be aborted and the pending
2880           // entry should be discarded.
2881           [[self sessionController] discardNonCommittedEntries];
2882           break;
2883         case net::ERR_BLOCKED_BY_CLIENT:
2884           // |NSURLErrorCancelled| errors with underlying net error code
2885           // |net::ERR_BLOCKED_BY_CLIENT| are used by the Chrome network stack
2886           // to indicate that the current load should be aborted and the pending
2887           // entry should be kept.
2888           break;
2889         default:
2890           NOTREACHED();
2891       }
2892     }
2893     return;
2894   }
2896   [self loadCompleteWithSuccess:NO];
2897   [self loadErrorInNativeView:error];
2900 #pragma mark -
2901 #pragma mark UIGestureRecognizerDelegate
2903 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2904     shouldRecognizeSimultaneouslyWithGestureRecognizer:
2905         (UIGestureRecognizer*)otherGestureRecognizer {
2906   // Allows the custom UILongPressGestureRecognizer to fire simultaneously with
2907   // other recognizers.
2908   return YES;
2911 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2912        shouldReceiveTouch:(UITouch*)touch {
2913   // Expect only _contextMenuRecognizer.
2914   DCHECK([gestureRecognizer isEqual:_contextMenuRecognizer]);
2915   if (![self supportsCustomContextMenu]) {
2916     // Fetching context menu info is not a free operation, early return if a
2917     // context menu should not be shown.
2918     return YES;
2919   }
2921   // This is custom long press gesture recognizer. By the time the gesture is
2922   // recognized the web controller needs to know if there is a link under the
2923   // touch. If there a link, the web controller will reject system's context
2924   // menu and show another one. If for some reason context menu info is not
2925   // fetched - system context menu will be shown.
2926   [self setDOMElementForLastTouch:nullptr];
2927   base::WeakNSObject<CRWWebController> weakSelf(self);
2928   [self fetchDOMElementAtPoint:[touch locationInView:self.webView]
2929              completionHandler:^(scoped_ptr<base::DictionaryValue> element) {
2930                [weakSelf setDOMElementForLastTouch:element.Pass()];
2931              }];
2932   return YES;
2935 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer {
2936   // Expect only _contextMenuRecognizer.
2937   DCHECK([gestureRecognizer isEqual:_contextMenuRecognizer]);
2938   if (!self.webView || ![self supportsCustomContextMenu]) {
2939     // Show the context menu iff currently displaying a web view.
2940     // Do nothing for native views.
2941     return NO;
2942   }
2944   UMA_HISTOGRAM_BOOLEAN("WebController.FetchContextMenuInfoAsyncSucceeded",
2945                         _DOMElementForLastTouch);
2947   return _DOMElementForLastTouch && !_DOMElementForLastTouch->empty();
2950 #pragma mark -
2951 #pragma mark CRWRequestTrackerDelegate
2953 - (BOOL)isForStaticFileRequests {
2954   return NO;
2957 - (void)updatedSSLStatus:(const web::SSLStatus&)sslStatus
2958               forPageUrl:(const GURL&)url
2959                 userInfo:(id)userInfo {
2960   // |userInfo| is a CRWSessionEntry.
2961   web::NavigationItem* item =
2962       [static_cast<CRWSessionEntry*>(userInfo) navigationItem];
2963   if (!item)
2964     return;  // This is a request update for an entry that no longer exists.
2966   // This condition happens infrequently when a page load is misinterpreted as
2967   // a resource load from a previous page. This can happen when moving quickly
2968   // back and forth through history, the notifications from the web view on the
2969   // UI thread and the one from the requests at the net layer may get out of
2970   // sync. This catches this case and prevent updating an entry with the wrong
2971   // SSL data.
2972   if (item->GetURL().GetOrigin() != url.GetOrigin())
2973     return;
2975   if (item->GetSSL().Equals(sslStatus))
2976     return;  // No need to update with the same data.
2978   item->GetSSL() = sslStatus;
2980   // Notify the UI it needs to refresh if the updated entry is the current
2981   // entry.
2982   if (userInfo == self.currentSessionEntry) {
2983     [self didUpdateSSLStatusForCurrentNavigationItem];
2984   }
2987 - (void)handleResponseHeaders:(net::HttpResponseHeaders*)headers
2988                    requestUrl:(const GURL&)requestUrl {
2989   _webStateImpl->OnHttpResponseHeadersReceived(headers, requestUrl);
2992 - (void)presentSSLError:(const net::SSLInfo&)info
2993            forSSLStatus:(const web::SSLStatus&)status
2994                   onUrl:(const GURL&)url
2995             recoverable:(BOOL)recoverable
2996                callback:(SSLErrorCallback)shouldContinue {
2997   DCHECK(_delegate);
2998   DCHECK_EQ(url, [self currentNavigationURL]);
2999   [_delegate presentSSLError:info
3000                 forSSLStatus:status
3001                  recoverable:recoverable
3002                     callback:shouldContinue];
3005 - (void)updatedProgress:(float)progress {
3006   if ([_delegate
3007           respondsToSelector:@selector(webController:didUpdateProgress:)]) {
3008     [_delegate webController:self didUpdateProgress:progress];
3009   }
3012 - (void)certificateUsed:(net::X509Certificate*)certificate
3013                 forHost:(const std::string&)host
3014                  status:(net::CertStatus)status {
3015   [[[self sessionController] sessionCertificatePolicyManager]
3016       registerAllowedCertificate:certificate
3017                          forHost:host
3018                           status:status];
3021 - (void)clearCertificates {
3022   [[[self sessionController] sessionCertificatePolicyManager]
3023       clearCertificates];
3026 #pragma mark -
3027 #pragma mark Popup handling
3029 - (void)openPopupWithInfo:(const web::NewWindowInfo&)windowInfo {
3030   const GURL url(windowInfo.url);
3031   const GURL currentURL([self currentNavigationURL]);
3032   NSString* windowName = windowInfo.window_name.get();
3033   web::Referrer referrer(currentURL, windowInfo.referrer_policy);
3034   base::WeakNSObject<CRWWebController> weakSelf(self);
3035   void (^showPopupHandler)() = ^{
3036     CRWWebController* child = [[weakSelf delegate] webPageOrderedOpen:url
3037                                                              referrer:referrer
3038                                                            windowName:windowName
3039                                                          inBackground:NO];
3040     DCHECK(!child || child.sessionController.openedByDOM);
3041   };
3043   BOOL showPopup = windowInfo.user_is_interacting ||
3044                    (![self shouldBlockPopupWithURL:url sourceURL:currentURL]);
3045   if (showPopup) {
3046     showPopupHandler();
3047   } else if ([_delegate
3048                  respondsToSelector:@selector(webController:didBlockPopup:)]) {
3049     web::BlockedPopupInfo blockedPopupInfo(url, referrer, windowName,
3050                                            showPopupHandler);
3051     [_delegate webController:self didBlockPopup:blockedPopupInfo];
3052   }
3055 #pragma mark -
3056 #pragma mark TouchTracking
3058 - (void)touched:(BOOL)touched {
3059   _clickInProgress = touched;
3060   if (touched) {
3061     _userInteractionRegistered = YES;
3062     _lastClickTimeInSeconds = CFAbsoluteTimeGetCurrent();
3063   }
3066 - (CRWTouchTrackingRecognizer*)touchTrackingRecognizer {
3067   if (!_touchTrackingRecognizer) {
3068     _touchTrackingRecognizer.reset(
3069         [[CRWTouchTrackingRecognizer alloc] initWithDelegate:self]);
3070   }
3071   return _touchTrackingRecognizer.get();
3074 - (BOOL)userIsInteracting {
3075   // If page transfer started after last click, user is deemed to be no longer
3076   // interacting.
3077   if (_lastTransferTimeInSeconds > _lastClickTimeInSeconds)
3078     return NO;
3079   return [self userClickedRecently];
3082 - (BOOL)userClickedRecently {
3083   return _clickInProgress ||
3084          ((CFAbsoluteTimeGetCurrent() - _lastClickTimeInSeconds) <
3085           kMaximumDelayForUserInteractionInSeconds);
3088 #pragma mark Placeholder Overlay Methods
3090 - (void)addPlaceholderOverlay {
3091   if (!_overlayPreviewMode) {
3092     // Create |kSnapshotOverlayDelay| second timer to remove image with
3093     // transition.
3094     [self performSelector:@selector(removePlaceholderOverlay)
3095                withObject:nil
3096                afterDelay:kSnapshotOverlayDelay];
3097   }
3099   // Add overlay image.
3100   _placeholderOverlayView.reset([[UIImageView alloc] init]);
3101   CGRect frame = [self visibleFrame];
3102   [_placeholderOverlayView setFrame:frame];
3103   [_placeholderOverlayView
3104       setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
3105                           UIViewAutoresizingFlexibleHeight];
3106   [_placeholderOverlayView setContentMode:UIViewContentModeScaleAspectFill];
3107   [_containerView addSubview:_placeholderOverlayView];
3109   id callback = ^(UIImage* image) {
3110     [_placeholderOverlayView setImage:image];
3111   };
3112   [_delegate webController:self retrievePlaceholderOverlayImage:callback];
3114   if (!_placeholderOverlayView.get().image) {
3115     // TODO(justincohen): This is just a blank white image. Consider fading in
3116     // the snapshot when it comes in instead.
3117     // TODO(shreyasv): This is just a blank white image. Consider adding an API
3118     // so that the delegate can return something immediately for the default
3119     // overlay image.
3120     _placeholderOverlayView.get().image = [[self class] defaultSnapshotImage];
3121   }
3124 - (void)removePlaceholderOverlay {
3125   if (!_placeholderOverlayView || _overlayPreviewMode)
3126     return;
3128   [NSObject cancelPreviousPerformRequestsWithTarget:self
3129                                            selector:@selector(removeOverlay)
3130                                              object:nil];
3131   // Remove overlay with transition.
3132   [UIView animateWithDuration:kSnapshotOverlayTransition
3133       animations:^{
3134         [_placeholderOverlayView setAlpha:0.0f];
3135       }
3136       completion:^(BOOL finished) {
3137         [_placeholderOverlayView removeFromSuperview];
3138         _placeholderOverlayView.reset();
3139       }];
3142 - (void)setOverlayPreviewMode:(BOOL)overlayPreviewMode {
3143   _overlayPreviewMode = overlayPreviewMode;
3145   // If we were showing the preview, remove it.
3146   if (!_overlayPreviewMode && _placeholderOverlayView) {
3147     _containerView.reset();
3148     // Reset |_placeholderOverlayView| directly instead of calling
3149     // -removePlaceholderOverlay, which removes |_placeholderOverlayView| in an
3150     // animation.
3151     [_placeholderOverlayView removeFromSuperview];
3152     _placeholderOverlayView.reset();
3153     // There are cases when resetting the contentView, above, may happen after
3154     // the web view has been created. Re-add it here, rather than
3155     // relying on a subsequent call to loadCurrentURLInWebView.
3156     if (self.webView) {
3157       [[self view] addSubview:self.webView];
3158     }
3159   }
3162 - (void)internalSuppressDialogs:(BOOL)suppressFlag notify:(BOOL)notifyFlag {
3163   NSString* const kSetSuppressDialogs =
3164       [NSString stringWithFormat:@"__gCrWeb.setSuppressDialogs(%d, %d);",
3165                                  suppressFlag, notifyFlag];
3166   [self setSuppressDialogsWithHelperScript:kSetSuppressDialogs];
3169 - (void)setPageDialogOpenPolicy:(web::PageDialogOpenPolicy)policy {
3170   switch (policy) {
3171     case web::DIALOG_POLICY_ALLOW:
3172       [self setSuppressDialogs:NO notify:NO];
3173       return;
3174     case web::DIALOG_POLICY_NOTIFY_FIRST:
3175       [self setSuppressDialogs:NO notify:YES];
3176       return;
3177     case web::DIALOG_POLICY_SUPPRESS:
3178       [self setSuppressDialogs:YES notify:YES];
3179       return;
3180   }
3181   NOTREACHED();
3184 - (void)setSuppressDialogs:(BOOL)suppressFlag notify:(BOOL)notifyFlag {
3185   if (self.webView && [_earlyScriptManager hasBeenInjected]) {
3186     [self internalSuppressDialogs:suppressFlag notify:notifyFlag];
3187   } else {
3188     _setSuppressDialogsLater = suppressFlag;
3189     _setNotifyAboutDialogsLater = notifyFlag;
3190   }
3193 #pragma mark -
3194 #pragma mark Session Information
3196 - (CRWSessionController*)sessionController {
3197   DCHECK(_webStateImpl);
3198   return _webStateImpl->GetNavigationManagerImpl().GetSessionController();
3201 - (CRWSessionEntry*)currentSessionEntry {
3202   return [[self sessionController] currentEntry];
3205 - (web::NavigationItem*)currentNavItem {
3206   // This goes through the legacy Session* interface rather than Navigation*
3207   // because it is itself a legacy method that should not exist, and this
3208   // avoids needing to add a GetActiveItem to NavigationManager. If/when this
3209   // method chain becomes a blocker to eliminating SessionController, the logic
3210   // can be moved here, using public NavigationManager getters. That's not
3211   // done now in order to avoid code duplication.
3212   return [[self currentSessionEntry] navigationItem];
3215 - (const GURL&)currentNavigationURL {
3216   // TODO(stuartmorgan): Fix the fact that this method doesn't have clear usage
3217   // delination that would allow changing to one of the non-deprecated URL
3218   // calls.
3219   web::NavigationItem* item = [self currentNavItem];
3220   return item ? item->GetVirtualURL() : GURL::EmptyGURL();
3223 - (ui::PageTransition)currentTransition {
3224   if ([self currentNavItem])
3225     return [self currentNavItem]->GetTransitionType();
3226   else
3227     return ui::PageTransitionFromInt(0);
3230 - (web::Referrer)currentSessionEntryReferrer {
3231   web::NavigationItem* currentItem = [self currentNavItem];
3232   return currentItem ? currentItem->GetReferrer() : web::Referrer();
3235 - (NSData*)currentPOSTData {
3236   DCHECK([self currentSessionEntry]);
3237   return [self currentSessionEntry].navigationItemImpl->GetPostData();
3240 - (NSDictionary*)currentHttpHeaders {
3241   DCHECK([self currentSessionEntry]);
3242   return [self currentSessionEntry].navigationItem->GetHttpRequestHeaders();
3245 #pragma mark -
3246 #pragma mark Page State
3248 - (void)recordStateInHistory {
3249   // Check that the url in the web view matches the url in the history entry.
3250   CRWSessionEntry* current = [self currentSessionEntry];
3251   if (current && [current navigationItem]->GetURL() == [self currentURL])
3252     [current navigationItem]->SetPageScrollState(self.pageScrollState);
3255 - (void)restoreStateFromHistory {
3256   CRWSessionEntry* current = [self currentSessionEntry];
3257   if ([current navigationItem])
3258     self.pageScrollState = [current navigationItem]->GetPageScrollState();
3261 - (web::PageScrollState)pageScrollState {
3262   web::PageScrollState scrollState;
3263   if (self.webView) {
3264     CGPoint scrollOffset = [self scrollPosition];
3265     scrollState.set_scroll_offset_x(std::floor(scrollOffset.x));
3266     scrollState.set_scroll_offset_y(std::floor(scrollOffset.y));
3267     UIScrollView* scrollView = self.webScrollView;
3268     scrollState.set_minimum_zoom_scale(scrollView.minimumZoomScale);
3269     scrollState.set_maximum_zoom_scale(scrollView.maximumZoomScale);
3270     scrollState.set_zoom_scale(scrollView.zoomScale);
3271   } else {
3272     // TODO(kkhorimoto): Handle native views.
3273   }
3274   return scrollState;
3277 - (void)setPageScrollState:(web::PageScrollState)pageScrollState {
3278   if (!pageScrollState.IsValid())
3279     return;
3280   if (self.webView) {
3281     // Page state is restored after a page load completes.  If the user has
3282     // scrolled or changed the zoom scale while the page is still loading, don't
3283     // restore any state since it will confuse the user.  Since the web view's
3284     // zoom scale range may have changed during rendering, check the absolute
3285     // zoom scale rather than doing a simple equality comparison.
3286     web::PageScrollState currentScrollState = self.pageScrollState;
3287     if (currentScrollState.scroll_offset_x() ==
3288             _scrollStateOnStartLoading.scroll_offset_x() &&
3289         currentScrollState.scroll_offset_y() ==
3290             _scrollStateOnStartLoading.scroll_offset_y() &&
3291         [self absoluteZoomScaleForScrollState:currentScrollState] ==
3292             [self absoluteZoomScaleForScrollState:_scrollStateOnStartLoading]) {
3293       base::WeakNSObject<CRWWebController> weakSelf(self);
3294       [self queryUserScalableProperty:^(BOOL isUserScalable) {
3295         base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
3296         [strongSelf applyPageScrollState:pageScrollState
3297                             userScalable:isUserScalable];
3298       }];
3299     }
3300   }
3303 - (void)applyPageScrollState:(const web::PageScrollState&)scrollState
3304                 userScalable:(BOOL)isUserScalable {
3305   DCHECK(scrollState.IsValid());
3306   if (isUserScalable) {
3307     [self prepareToApplyWebViewScrollZoomScale];
3308     [self applyWebViewScrollZoomScaleFromScrollState:scrollState];
3309     [self finishApplyingWebViewScrollZoomScale];
3310   }
3311   [self applyWebViewScrollOffsetFromScrollState:scrollState];
3314 - (void)prepareToApplyWebViewScrollZoomScale {
3315   id webView = self.webView;
3316   if (![webView respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
3317     return;
3318   }
3320   UIView* contentView = [webView viewForZoomingInScrollView:self.webScrollView];
3322   if ([webView
3323           respondsToSelector:@selector(scrollViewWillBeginZooming:withView:)]) {
3324     [webView scrollViewWillBeginZooming:self.webScrollView
3325                                withView:contentView];
3326   }
3329 - (void)finishApplyingWebViewScrollZoomScale {
3330   id webView = self.webView;
3331   if ([webView respondsToSelector:@selector(scrollViewDidEndZooming:
3332                                                            withView:
3333                                                             atScale:)] &&
3334       [webView respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
3335     // This correctly sets the content's frame in the scroll view to
3336     // fit the web page and upscales the content so that it isn't
3337     // blurry.
3338     UIView* contentView =
3339         [webView viewForZoomingInScrollView:self.webScrollView];
3340     [webView scrollViewDidEndZooming:self.webScrollView
3341                             withView:contentView
3342                              atScale:self.webScrollView.zoomScale];
3343   }
3346 - (void)applyWebViewScrollZoomScaleFromScrollState:
3347     (const web::PageScrollState&)scrollState {
3348   // Subclasses must implement this method.
3349   NOTREACHED();
3352 - (void)applyWebViewScrollOffsetFromScrollState:
3353     (const web::PageScrollState&)scrollState {
3354   DCHECK(scrollState.IsValid());
3355   CGPoint scrollOffset =
3356       CGPointMake(scrollState.scroll_offset_x(), scrollState.scroll_offset_y());
3357   if (_loadPhase == web::PAGE_LOADED) {
3358     // If the page is loaded, update the scroll immediately.
3359     [self.webScrollView setContentOffset:scrollOffset];
3360   } else {
3361     // If the page isn't loaded, store the action to update the scroll
3362     // when the page finishes loading.
3363     base::WeakNSObject<UIScrollView> weakScrollView(self.webScrollView);
3364     base::scoped_nsprotocol<ProceduralBlock> action([^{
3365       [weakScrollView setContentOffset:scrollOffset];
3366     } copy]);
3367     [_pendingLoadCompleteActions addObject:action];
3368   }
3371 #pragma mark -
3372 #pragma mark Web Page Features
3374 // TODO(eugenebut): move JS parsing code to a separate file.
3375 - (void)queryUserScalableProperty:(void (^)(BOOL))responseHandler {
3376   NSString* const kViewPortContentQuery =
3377       @"var viewport = document.querySelector('meta[name=\"viewport\"]');"
3378        "viewport ? viewport.content : '';";
3379   [self evaluateJavaScript:kViewPortContentQuery
3380        stringResultHandler:^(NSString* viewPortContent, NSError* error) {
3381          responseHandler(
3382              GetUserScalablePropertyFromViewPortContent(viewPortContent));
3383        }];
3386 - (void)fetchWebPageWidthWithCompletionHandler:(void (^)(CGFloat))handler {
3387   if (!self.webView) {
3388     handler(0);
3389     return;
3390   }
3392   [self evaluateJavaScript:@"__gCrWeb.getPageWidth();"
3393        stringResultHandler:^(NSString* pageWidthAsString, NSError*) {
3394          handler([pageWidthAsString floatValue]);
3395        }];
3398 - (void)fetchDOMElementAtPoint:(CGPoint)point
3399              completionHandler:
3400                  (void (^)(scoped_ptr<base::DictionaryValue>))handler {
3401   DCHECK(handler);
3402   // Convert point into web page's coordinate system (which may be scaled and/or
3403   // scrolled).
3404   CGPoint scrollOffset = self.scrollPosition;
3405   CGFloat webViewContentWidth = self.webScrollView.contentSize.width;
3406   base::WeakNSObject<CRWWebController> weakSelf(self);
3407   [self fetchWebPageWidthWithCompletionHandler:^(CGFloat pageWidth) {
3408     CGFloat scale = pageWidth / webViewContentWidth;
3409     CGPoint localPoint = CGPointMake((point.x + scrollOffset.x) * scale,
3410                                      (point.y + scrollOffset.y) * scale);
3411     NSString* const kGetElementScript =
3412         [NSString stringWithFormat:@"__gCrWeb.getElementFromPoint(%g, %g);",
3413                                    localPoint.x, localPoint.y];
3414     [weakSelf evaluateJavaScript:kGetElementScript
3415                JSONResultHandler:^(scoped_ptr<base::Value> element, NSError*) {
3416                  // Release raw element and call handler with DictionaryValue.
3417                  scoped_ptr<base::DictionaryValue> elementAsDict;
3418                  if (element) {
3419                    base::DictionaryValue* elementAsDictPtr = nullptr;
3420                    element.release()->GetAsDictionary(&elementAsDictPtr);
3421                    // |rawElement| and |elementPtr| now point to the same
3422                    // memory. |elementPtr| ownership will be transferred to
3423                    // |element| scoped_ptr.
3424                    elementAsDict.reset(elementAsDictPtr);
3425                  }
3426                  handler(elementAsDict.Pass());
3427                }];
3428   }];
3431 - (NSDictionary*)contextMenuInfoForElement:(base::DictionaryValue*)element {
3432   DCHECK(element);
3433   NSMutableDictionary* mutableInfo = [NSMutableDictionary dictionary];
3434   NSString* title = nil;
3435   std::string href;
3436   if (element->GetString("href", &href)) {
3437     mutableInfo[web::kContextLinkURLString] = base::SysUTF8ToNSString(href);
3438     GURL linkURL(href);
3439     if (linkURL.SchemeIs(url::kJavaScriptScheme)) {
3440       title = @"JavaScript";
3441     } else {
3442       DCHECK(web::GetWebClient());
3443       const std::string& acceptLangs = web::GetWebClient()->GetAcceptLangs(
3444           self.webStateImpl->GetBrowserState());
3445       base::string16 urlText = net::FormatUrl(GURL(href), acceptLangs);
3446       title = base::SysUTF16ToNSString(urlText);
3447     }
3448   }
3449   std::string src;
3450   if (element->GetString("src", &src)) {
3451     mutableInfo[web::kContextImageURLString] = base::SysUTF8ToNSString(src);
3452     if (!title)
3453       title = base::SysUTF8ToNSString(src);
3454     if ([title hasPrefix:@"data:"])
3455       title = @"";
3456   }
3457   std::string titleAttribute;
3458   if (element->GetString("title", &titleAttribute))
3459     title = base::SysUTF8ToNSString(titleAttribute);
3460   std::string referrerPolicy;
3461   element->GetString("referrerPolicy", &referrerPolicy);
3462   mutableInfo[web::kContextLinkReferrerPolicy] =
3463       @([self referrerPolicyFromString:referrerPolicy]);
3464   if (title)
3465     mutableInfo[web::kContextTitle] = title;
3466   return [[mutableInfo copy] autorelease];
3469 #pragma mark -
3470 #pragma mark Fullscreen
3472 - (CGRect)visibleFrame {
3473   CGRect frame = [_containerView bounds];
3474   CGFloat headerHeight = [self headerHeight];
3475   frame.origin.y = headerHeight;
3476   frame.size.height -= headerHeight;
3477   return frame;
3480 - (void)optOutScrollsToTopForSubviews {
3481   NSMutableArray* stack =
3482       [NSMutableArray arrayWithArray:[self.webScrollView subviews]];
3483   while (stack.count) {
3484     UIView* current = [stack lastObject];
3485     [stack removeLastObject];
3486     [stack addObjectsFromArray:[current subviews]];
3487     if ([current isKindOfClass:[UIScrollView class]])
3488       static_cast<UIScrollView*>(current).scrollsToTop = NO;
3489   }
3492 #pragma mark -
3493 #pragma mark WebDelegate Calls
3495 - (BOOL)shouldOpenURL:(const GURL&)url
3496       mainDocumentURL:(const GURL&)mainDocumentURL
3497           linkClicked:(BOOL)linkClicked {
3498   if (![_delegate respondsToSelector:@selector(webController:
3499                                                shouldOpenURL:
3500                                              mainDocumentURL:
3501                                                  linkClicked:)]) {
3502     return YES;
3503   }
3504   return [_delegate webController:self
3505                     shouldOpenURL:url
3506                   mainDocumentURL:mainDocumentURL
3507                       linkClicked:linkClicked];
3510 - (BOOL)shouldOpenExternalURL:(const GURL&)url {
3511   return [_delegate respondsToSelector:@selector(webController:
3512                                            shouldOpenExternalURL:)] &&
3513          [_delegate webController:self shouldOpenExternalURL:url];
3516 - (BOOL)urlTriggersNativeAppLaunch:(const GURL&)url
3517                          sourceURL:(const GURL&)sourceURL {
3518   return
3519       [_delegate respondsToSelector:@selector(urlTriggersNativeAppLaunch:
3520                                                                sourceURL:)] &&
3521       [_delegate urlTriggersNativeAppLaunch:url sourceURL:sourceURL];
3524 - (CGFloat)headerHeight {
3525   if (![_delegate respondsToSelector:@selector(headerHeightForWebController:)])
3526     return 0.0f;
3527   return [_delegate headerHeightForWebController:self];
3530 - (BOOL)shouldBlockPopupWithURL:(const GURL&)popupURL
3531                       sourceURL:(const GURL&)sourceURL {
3532   if (![_delegate respondsToSelector:@selector(webController:
3533                                          shouldBlockPopupWithURL:
3534                                                        sourceURL:)]) {
3535     return NO;
3536   }
3537   return [_delegate webController:self
3538           shouldBlockPopupWithURL:popupURL
3539                         sourceURL:sourceURL];
3542 - (void)didUpdateHistoryStateWithPageURL:(const GURL&)url {
3543   _webStateImpl->GetRequestTracker()->HistoryStateChange(url);
3544   [_delegate webDidUpdateHistoryStateWithPageURL:url];
3547 - (void)didUpdateSSLStatusForCurrentNavigationItem {
3548   if ([_delegate respondsToSelector:
3549           @selector(
3550               webControllerDidUpdateSSLStatusForCurrentNavigationItem:)]) {
3551     [_delegate webControllerDidUpdateSSLStatusForCurrentNavigationItem:self];
3552   }
3555 #pragma mark CRWWebControllerScripting Methods
3557 - (void)loadHTML:(NSString*)html {
3558   [self loadHTML:html forURL:GURL(url::kAboutBlankURL)];
3561 - (void)loadHTMLForCurrentURL:(NSString*)html {
3562   [self loadHTML:html forURL:self.currentURL];
3565 - (void)loadHTML:(NSString*)html forURL:(const GURL&)url {
3566   // Remove the interstitial before doing anything else.
3567   [self clearInterstitials];
3569   DLOG_IF(WARNING, !self.webView)
3570       << "self.webView null while trying to load HTML";
3571   _loadPhase = web::LOAD_REQUESTED;
3572   [self loadWebHTMLString:html forURL:url];
3575 - (void)stopLoading {
3576   web::RecordAction(UserMetricsAction("Stop"));
3577   // Discard the pending and transient entried before notifying the tab model
3578   // observers of the change via |-abortLoad|.
3579   [[self sessionController] discardNonCommittedEntries];
3580   [self abortLoad];
3581   // If discarding the non-committed entries results in an app-specific URL,
3582   // reload it in its native view.
3583   if (!_nativeController &&
3584       [self shouldLoadURLInNativeView:[self currentNavigationURL]]) {
3585     [self loadCurrentURLInNativeView];
3586   }
3589 - (void)orderClose {
3590   if (self.sessionController.openedByDOM) {
3591     [_delegate webPageOrderedClose];
3592   }
3595 #pragma mark -
3596 #pragma mark Testing-Only Methods
3598 - (void)injectWebView:(id)webView {
3599   [self removeWebViewAllowingCachedReconstruction:NO];
3601   _lastRegisteredRequestURL = _defaultURL;
3602   CHECK([webView respondsToSelector:@selector(scrollView)]);
3603   [_webViewProxy setWebView:webView
3604                  scrollView:[static_cast<id>(webView) scrollView]];
3607 - (void)resetInjectedWebView {
3608   [self resetWebView];
3611 - (void)addObserver:(id<CRWWebControllerObserver>)observer {
3612   DCHECK(observer);
3613   if (!_observers) {
3614     // We don't want our observer set to block dealloc on the observers. For the
3615     // observer container, make an object compatible with NSMutableSet that does
3616     // not perform retain or release on the contained objects (weak references).
3617     CFSetCallBacks callbacks =
3618         {0, NULL, NULL, CFCopyDescription, CFEqual, CFHash};
3619     _observers.reset(base::mac::CFToNSCast(
3620         CFSetCreateMutable(kCFAllocatorDefault, 1, &callbacks)));
3621   }
3622   DCHECK(![_observers containsObject:observer]);
3623   [_observers addObject:observer];
3624   _observerBridges.push_back(
3625       new web::WebControllerObserverBridge(observer, self.webStateImpl, self));
3627   if ([observer respondsToSelector:@selector(setWebViewProxy:controller:)])
3628     [observer setWebViewProxy:_webViewProxy controller:self];
3631 - (void)removeObserver:(id<CRWWebControllerObserver>)observer {
3632   // TODO(jimblackler): make _observers use NSMapTable. crbug.com/367992
3633   DCHECK([_observers containsObject:observer]);
3634   [_observers removeObject:observer];
3635   // Remove the associated WebControllerObserverBridge.
3636   auto it = std::find_if(_observerBridges.begin(), _observerBridges.end(),
3637                          [observer](web::WebControllerObserverBridge* bridge) {
3638                            return bridge->web_controller_observer() == observer;
3639                          });
3640   DCHECK(it != _observerBridges.end());
3641   _observerBridges.erase(it);
3644 - (NSUInteger)observerCount {
3645   DCHECK_EQ(_observerBridges.size(), [_observers count]);
3646   return [_observers count];
3649 - (NSString*)windowId {
3650   return [_windowIDJSManager windowId];
3653 - (void)setWindowId:(NSString*)windowId {
3654   return [_windowIDJSManager setWindowId:windowId];
3657 - (NSString*)lastSeenWindowID {
3658   return _lastSeenWindowID;
3661 - (void)setURLOnStartLoading:(const GURL&)url {
3662   _URLOnStartLoading = url;
3665 - (const GURL&)defaultURL {
3666   return _defaultURL;
3669 - (GURL)URLOnStartLoading {
3670   return _URLOnStartLoading;
3673 - (GURL)lastRegisteredRequestURL {
3674   return _lastRegisteredRequestURL;
3677 - (void)simulateLoadRequestWithURL:(const GURL&)URL {
3678   _lastRegisteredRequestURL = URL;
3679   _loadPhase = web::LOAD_REQUESTED;
3682 - (NSString*)externalRequestWindowName {
3683   if (!_externalRequest || !_externalRequest->window_name)
3684     return @"";
3685   return _externalRequest->window_name;
3688 - (void)resetExternalRequest {
3689   _externalRequest.reset();
3692 @end