Always set the serialized state object for history navigations.
[chromium-blink-merge.git] / ios / web / web_state / ui / crw_web_controller.mm
blobd95db1dabe5641c956b231f5a927c4a8576410c1
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 #import "base/ios/ns_error_util.h"
12 #include "base/ios/weak_nsobject.h"
13 #include "base/json/json_reader.h"
14 #include "base/json/json_writer.h"
15 #include "base/json/string_escape.h"
16 #include "base/logging.h"
17 #include "base/mac/bundle_locations.h"
18 #include "base/mac/foundation_util.h"
19 #include "base/mac/objc_property_releaser.h"
20 #include "base/mac/scoped_cftyperef.h"
21 #include "base/mac/scoped_nsobject.h"
22 #include "base/memory/scoped_ptr.h"
23 #include "base/metrics/histogram.h"
24 #include "base/metrics/user_metrics_action.h"
25 #include "base/prefs/pref_service.h"
26 #include "base/strings/string_util.h"
27 #include "base/strings/sys_string_conversions.h"
28 #include "base/strings/utf_string_conversions.h"
29 #include "base/time/time.h"
30 #include "base/values.h"
31 #include "components/url_formatter/url_formatter.h"
32 #import "ios/net/nsurlrequest_util.h"
33 #include "ios/public/provider/web/web_ui_ios.h"
34 #import "ios/web/history_state_util.h"
35 #include "ios/web/interstitials/web_interstitial_impl.h"
36 #import "ios/web/navigation/crw_session_certificate_policy_manager.h"
37 #import "ios/web/navigation/crw_session_controller.h"
38 #import "ios/web/navigation/crw_session_entry.h"
39 #import "ios/web/navigation/navigation_item_impl.h"
40 #import "ios/web/navigation/navigation_manager_impl.h"
41 #import "ios/web/navigation/web_load_params.h"
42 #include "ios/web/net/request_group_util.h"
43 #include "ios/web/public/browser_state.h"
44 #include "ios/web/public/favicon_url.h"
45 #include "ios/web/public/navigation_item.h"
46 #include "ios/web/public/referrer.h"
47 #include "ios/web/public/referrer_util.h"
48 #include "ios/web/public/ssl_status.h"
49 #include "ios/web/public/url_scheme_util.h"
50 #include "ios/web/public/url_util.h"
51 #include "ios/web/public/user_metrics.h"
52 #include "ios/web/public/web_client.h"
53 #include "ios/web/public/web_kit_constants.h"
54 #include "ios/web/public/web_state/credential.h"
55 #import "ios/web/public/web_state/crw_web_controller_observer.h"
56 #import "ios/web/public/web_state/crw_web_view_scroll_view_proxy.h"
57 #import "ios/web/public/web_state/js/crw_js_injection_manager.h"
58 #import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
59 #import "ios/web/public/web_state/ui/crw_content_view.h"
60 #import "ios/web/public/web_state/ui/crw_native_content.h"
61 #import "ios/web/public/web_state/ui/crw_native_content_provider.h"
62 #import "ios/web/public/web_state/ui/crw_web_view_content_view.h"
63 #include "ios/web/public/web_state/url_verification_constants.h"
64 #include "ios/web/public/web_state/web_state.h"
65 #include "ios/web/web_state/blocked_popup_info.h"
66 #import "ios/web/web_state/crw_web_view_proxy_impl.h"
67 #import "ios/web/web_state/error_translation_util.h"
68 #include "ios/web/web_state/frame_info.h"
69 #import "ios/web/web_state/js/credential_util.h"
70 #import "ios/web/web_state/js/crw_js_early_script_manager.h"
71 #import "ios/web/web_state/js/crw_js_plugin_placeholder_manager.h"
72 #import "ios/web/web_state/js/crw_js_window_id_manager.h"
73 #import "ios/web/web_state/ui/crw_context_menu_provider.h"
74 #import "ios/web/web_state/ui/crw_swipe_recognizer_provider.h"
75 #import "ios/web/web_state/ui/crw_ui_web_view_web_controller.h"
76 #import "ios/web/web_state/ui/crw_web_controller+protected.h"
77 #import "ios/web/web_state/ui/crw_web_controller_container_view.h"
78 #import "ios/web/web_state/ui/crw_wk_web_view_web_controller.h"
79 #import "ios/web/web_state/web_controller_observer_bridge.h"
80 #include "ios/web/web_state/web_state_facade_delegate.h"
81 #import "ios/web/web_state/web_state_impl.h"
82 #import "net/base/mac/url_conversions.h"
83 #include "net/base/net_errors.h"
84 #import "ui/base/ios/cru_context_menu_holder.h"
85 #include "ui/base/page_transition_types.h"
86 #include "url/gurl.h"
87 #include "url/url_constants.h"
89 using base::UserMetricsAction;
90 using web::NavigationManagerImpl;
91 using web::WebState;
92 using web::WebStateImpl;
94 namespace web {
96 NSString* const kContainerViewID = @"Container View";
97 const char* kWindowNameSeparator = "#";
98 NSString* const kUserIsInteractingKey = @"userIsInteracting";
99 NSString* const kOriginURLKey = @"originURL";
100 NSString* const kLogJavaScript = @"LogJavascript";
102 NewWindowInfo::NewWindowInfo(GURL target_url,
103                              NSString* target_window_name,
104                              web::ReferrerPolicy target_referrer_policy,
105                              bool target_user_is_interacting)
106     : url(target_url),
107       window_name([target_window_name copy]),
108       referrer_policy(target_referrer_policy),
109       user_is_interacting(target_user_is_interacting) {
112 NewWindowInfo::~NewWindowInfo() {
115 // Struct to capture data about a user interaction. Records the time of the
116 // interaction and the main document URL at that time.
117 struct UserInteractionEvent {
118   UserInteractionEvent(GURL url)
119       : main_document_url(url), time(CFAbsoluteTimeGetCurrent()) {}
120   // Main document URL at the time the interaction occurred.
121   GURL main_document_url;
122   // Time that the interaction occured, measured in seconds since Jan 1 2001.
123   CFAbsoluteTime time;
126 }  // namespace web
128 namespace {
130 // A tag for the web view, so that tests can identify it. This is used instead
131 // of exposing a getter (and deliberately not exposed in the header) to make it
132 // *very* clear that this is a hack which should only be used as a last resort.
133 const NSUInteger kWebViewTag = 0x3eb71e3;
135 // States for external URL requests. This enum is used in UMA and
136 // entries should not be re-ordered or deleted.
137 enum ExternalURLRequestStatus {
138   MAIN_FRAME_ALLOWED = 0,
139   SUBFRAME_ALLOWED,
140   SUBFRAME_BLOCKED,
141   NUM_EXTERNAL_URL_REQUEST_STATUS
144 // Cancels touch events for the given gesture recognizer.
145 void CancelTouches(UIGestureRecognizer* gesture_recognizer) {
146   if (gesture_recognizer.enabled) {
147     gesture_recognizer.enabled = NO;
148     gesture_recognizer.enabled = YES;
149   }
152 // Cancels all touch events for web view (long presses, tapping, scrolling).
153 void CancelAllTouches(UIScrollView* web_scroll_view) {
154   // Disable web view scrolling.
155   CancelTouches(web_scroll_view.panGestureRecognizer);
157   // All user gestures are handled by a subview of web view scroll view
158   // (UIWebBrowserView for UIWebView and WKContentView for WKWebView).
159   for (UIView* subview in web_scroll_view.subviews) {
160     for (UIGestureRecognizer* recognizer in subview.gestureRecognizers) {
161       CancelTouches(recognizer);
162     }
163   }
166 }  // namespace
168 @interface CRWWebController () <CRWNativeContentDelegate,
169                                 CRWWebViewScrollViewProxyObserver> {
170   base::WeakNSProtocol<id<CRWWebDelegate>> _delegate;
171   base::WeakNSProtocol<id<CRWWebUserInterfaceDelegate>> _UIDelegate;
172   base::WeakNSProtocol<id<CRWNativeContentProvider>> _nativeProvider;
173   base::WeakNSProtocol<id<CRWSwipeRecognizerProvider>> _swipeRecognizerProvider;
174   base::scoped_nsobject<CRWWebControllerContainerView> _containerView;
175   // The CRWWebViewProxy is the wrapper to give components access to the
176   // web view in a controlled and limited way.
177   base::scoped_nsobject<CRWWebViewProxyImpl> _webViewProxy;
178   // If |_contentView| contains a native view rather than a web view, this
179   // is its controller. If it's a web view, this is nil.
180   base::scoped_nsprotocol<id<CRWNativeContent>> _nativeController;
181   BOOL _isHalted;  // YES if halted. Halting happens prior to destruction.
182   BOOL _isBeingDestroyed;  // YES if in the process of closing.
183   // All CRWWebControllerObservers attached to the CRWWebController. A
184   // specially-constructed set is used that does not retain its elements.
185   base::scoped_nsobject<NSMutableSet> _observers;
186   // Each observer in |_observers| is associated with a
187   // WebControllerObserverBridge in order to listen from WebState callbacks.
188   // TODO(droger): Remove |_observerBridges| when all CRWWebControllerObservers
189   // are converted to WebStateObservers.
190   ScopedVector<web::WebControllerObserverBridge> _observerBridges;
191   // |windowId| that is saved when a page changes. Used to detect refreshes.
192   base::scoped_nsobject<NSString> _lastSeenWindowID;
193   // YES if a user interaction has been registered at any time once the page has
194   // loaded.
195   BOOL _userInteractionRegistered;
196   // Last URL change reported to webWill/DidStartLoadingURL. Used to detect page
197   // location changes (client redirects) in practice.
198   GURL _lastRegisteredRequestURL;
199   // Last URL change reported to webDidStartLoadingURL. Used to detect page
200   // location changes in practice.
201   GURL _URLOnStartLoading;
202   // Page loading phase.
203   web::LoadPhase _loadPhase;
204   // The web::PageDisplayState recorded when the page starts loading.
205   web::PageDisplayState _displayStateOnStartLoading;
206   // Whether or not the page has zoomed since the current navigation has been
207   // committed, either by user interaction or via |-restoreStateFromHistory|.
208   BOOL _pageHasZoomed;
209   // Actions to execute once the page load is complete.
210   base::scoped_nsobject<NSMutableArray> _pendingLoadCompleteActions;
211   // UIGestureRecognizers to add to the web view.
212   base::scoped_nsobject<NSMutableArray> _gestureRecognizers;
213   // Toolbars to add to the web view.
214   base::scoped_nsobject<NSMutableArray> _webViewToolbars;
215   // Flag to say if browsing is enabled.
216   BOOL _webUsageEnabled;
217   // Content view was reset due to low memory. Use the placeholder overlay on
218   // next creation.
219   BOOL _usePlaceholderOverlay;
220   // The next time the view is requested, reload the page (using the placeholder
221   // overlay until it's loaded).
222   BOOL _requireReloadOnDisplay;
223   // Overlay view used instead of webView.
224   base::scoped_nsobject<UIImageView> _placeholderOverlayView;
225   // The touch tracking recognizer allowing us to decide if a navigation is
226   // started by the user.
227   base::scoped_nsobject<CRWTouchTrackingRecognizer> _touchTrackingRecognizer;
228   // Long press recognizer that allows showing context menus.
229   base::scoped_nsobject<UILongPressGestureRecognizer> _contextMenuRecognizer;
230   // DOM element information for the point where the user made the last touch.
231   // Can be null if has not been calculated yet. Precalculation is necessary
232   // because retreiving DOM element relies on async API so element info can not
233   // be built on demand. May contain the following keys: "href", "src", "title",
234   // "referrerPolicy". All values are strings. Used for showing context menus.
235   scoped_ptr<base::DictionaryValue> _DOMElementForLastTouch;
236   // Whether a click is in progress.
237   BOOL _clickInProgress;
238   // Data on the recorded last user interaction.
239   scoped_ptr<web::UserInteractionEvent> _lastUserInteraction;
240   // YES if there has been user interaction with views owned by this controller.
241   BOOL _userInteractedWithWebController;
242   // The time of the last page transfer start, measured in seconds since Jan 1
243   // 2001.
244   CFAbsoluteTime _lastTransferTimeInSeconds;
245   // Default URL (about:blank).
246   GURL _defaultURL;
247   // Show overlay view, don't reload web page.
248   BOOL _overlayPreviewMode;
249   // If |YES|, call setSuppressDialogs when core.js is injected into the web
250   // view.
251   BOOL _setSuppressDialogsLater;
252   // If |YES|, call setSuppressDialogs when core.js is injected into the web
253   // view.
254   BOOL _setNotifyAboutDialogsLater;
255   // The URL of an expected future recreation of the |webView|. Valid
256   // only if the web view was discarded for non-user-visible reasons, such that
257   // if the next load request is for that URL, it should be treated as a
258   // reconstruction that should use cache aggressively.
259   GURL _expectedReconstructionURL;
261   scoped_ptr<web::NewWindowInfo> _externalRequest;
263   // The WebStateImpl instance associated with this CRWWebController.
264   scoped_ptr<WebStateImpl> _webStateImpl;
266   // A set of URLs opened in external applications; stored so that errors
267   // from the web view can be identified as resulting from these events.
268   base::scoped_nsobject<NSMutableSet> _openedApplicationURL;
270   // Object that manages all early script injection into the web view.
271   base::scoped_nsobject<CRWJSEarlyScriptManager> _earlyScriptManager;
273   // Script manager for setting the windowID.
274   base::scoped_nsobject<CRWJSWindowIdManager> _windowIDJSManager;
276   // The receiver of JavaScripts.
277   base::scoped_nsobject<CRWJSInjectionReceiver> _jsInjectionReceiver;
280 // The container view.  The container view should be accessed through this
281 // property rather than |self.view| from within this class, as |self.view|
282 // triggers creation while |self.containerView| will return nil if the view
283 // hasn't been instantiated.
284 @property(nonatomic, retain, readonly)
285     CRWWebControllerContainerView* containerView;
286 // The current page state of the web view. Writing to this property
287 // asynchronously applies the passed value to the current web view.
288 @property(nonatomic, readwrite) web::PageDisplayState pageDisplayState;
289 // The currently displayed native controller, if any.
290 @property(nonatomic, readwrite) id<CRWNativeContent> nativeController;
291 // Removes the container view from the hierarchy and resets the ivar.
292 - (void)resetContainerView;
293 // Resets any state that is associated with a specific document object (e.g.,
294 // page interaction tracking).
295 - (void)resetDocumentSpecificState;
296 // Returns YES if the URL looks like it is one CRWWebController can show.
297 + (BOOL)webControllerCanShow:(const GURL&)url;
298 // Clears the currently-displayed transient content view.
299 - (void)clearTransientContentView;
300 // Returns a lazily created CRWTouchTrackingRecognizer.
301 - (CRWTouchTrackingRecognizer*)touchTrackingRecognizer;
302 // Shows placeholder overlay.
303 - (void)addPlaceholderOverlay;
304 // Removes placeholder overlay.
305 - (void)removePlaceholderOverlay;
306 // Returns |YES| if |url| should be loaded in a native view.
307 - (BOOL)shouldLoadURLInNativeView:(const GURL&)url;
308 // Loads the HTML into the page at the given URL.
309 - (void)loadHTML:(NSString*)html forURL:(const GURL&)url;
310 // Loads the current nativeController in a native view. If a web view is
311 // present, removes it and swaps in the native view in its place.
312 - (void)loadNativeViewWithSuccess:(BOOL)loadSuccess;
313 // YES if the navigation to |url| should be treated as a reload.
314 - (BOOL)shouldReload:(const GURL&)destinationURL
315           transition:(ui::PageTransition)transition;
316 // Internal implementation of reload. Reloads without notifying the delegate.
317 // Most callers should use -reload instead.
318 - (void)reloadInternal;
319 // If YES, the page should be closed if it successfully redirects to a native
320 // application, for example if a new tab redirects to the App Store.
321 - (BOOL)shouldClosePageOnNativeApplicationLoad;
322 // Called after URL is finished loading and _loadPhase is set to PAGE_LOADED.
323 - (void)didFinishWithURL:(const GURL&)currentURL loadSuccess:(BOOL)loadSuccess;
324 // Informs the native controller if web usage is allowed or not.
325 - (void)setNativeControllerWebUsageEnabled:(BOOL)webUsageEnabled;
326 // Evaluates the supplied JavaScript in the web view. Calls |handler| with
327 // results of the evaluation (which may be nil if the implementing object has no
328 // way to run the evaluation or the evaluation returns a nil value) or an
329 // NSError if there is an error. The |handler| can be nil.
330 - (void)evaluateJavaScript:(NSString*)script
331          JSONResultHandler:(void (^)(scoped_ptr<base::Value>, NSError*))handler;
332 // Generates the JavaScript string used to update the UIWebView's URL so that it
333 // matches the URL displayed in the omnibox and sets window.history.state to
334 // stateObject. Needed for history.pushState() and history.replaceState().
335 - (NSString*)javascriptToReplaceWebViewURL:(const GURL&)URL
336                            stateObjectJSON:(NSString*)stateObject;
337 // Injects JavaScript into the web view to update the URL to |URL|, to set
338 // window.history.state to |stateObject|, and to trigger a popstate() event.
339 // Upon the scripts completion, resets |urlOnStartLoading_| and
340 // |_lastRegisteredRequestURL| to |URL|.  This is necessary so that sites that
341 // depend on URL params/fragments continue to work correctly and that checks for
342 // the URL don't incorrectly trigger |-pageChanged| calls.
343 - (void)setPushedOrReplacedURL:(const GURL&)URL
344                    stateObject:(NSString*)stateObject;
345 - (BOOL)isLoaded;
346 // Called by NSNotificationCenter upon orientation changes.
347 - (void)orientationDidChange;
348 // Queries the web view for the user-scalable meta tag and calls
349 // |-applyPageDisplayState:userScalable:| with the result.
350 - (void)applyPageDisplayState:(const web::PageDisplayState&)displayState;
351 // Restores state of the web view's scroll view from |scrollState|.
352 // |isUserScalable| represents the value of user-scalable meta tag.
353 - (void)applyPageDisplayState:(const web::PageDisplayState&)displayState
354                  userScalable:(BOOL)isUserScalable;
355 // Calls the zoom-preparation UIScrollViewDelegate callbacks on the web view.
356 // This is called before |-applyWebViewScrollZoomScaleFromScrollState:|.
357 - (void)prepareToApplyWebViewScrollZoomScale;
358 // Calls the zoom-completion UIScrollViewDelegate callbacks on the web view.
359 // This is called after |-applyWebViewScrollZoomScaleFromScrollState:|.
360 - (void)finishApplyingWebViewScrollZoomScale;
361 // Sets scroll offset value for webview scroll view from |scrollState|.
362 - (void)applyWebViewScrollOffsetFromScrollState:
363     (const web::PageScrollState&)scrollState;
364 // Asynchronously determines whether option |user-scalable| is on in the
365 // viewport meta of the current web page.
366 - (void)queryUserScalableProperty:(void (^)(BOOL))responseHandler;
367 // Asynchronously fetches full width of the rendered web page.
368 - (void)fetchWebPageWidthWithCompletionHandler:(void (^)(CGFloat))handler;
369 // Asynchronously fetches information about DOM element for the given point (in
370 // UIView coordinates). |handler| can not be nil. See |_DOMElementForLastTouch|
371 // for element format description.
372 - (void)fetchDOMElementAtPoint:(CGPoint)point
373              completionHandler:
374                  (void (^)(scoped_ptr<base::DictionaryValue>))handler;
375 // Extracts context menu information from the given DOM element.
376 // result keys are defined in crw_context_menu_provider.h.
377 - (NSDictionary*)contextMenuInfoForElement:(base::DictionaryValue*)element;
378 // Sets the value of |_DOMElementForLastTouch|.
379 - (void)setDOMElementForLastTouch:(scoped_ptr<base::DictionaryValue>)element;
380 // Called when the window has determined there was a long-press and context menu
381 // must be shown.
382 - (void)showContextMenu:(UIGestureRecognizer*)gestureRecognizer;
383 // YES if delegate supports showing context menu by responding to
384 // webController:runContextMenu:atPoint:inView: selector.
385 - (BOOL)supportsCustomContextMenu;
386 // Returns the referrer for the current page.
387 - (web::Referrer)currentReferrer;
388 // Presents an error to the user because the CRWWebController cannot verify the
389 // URL of the current page.
390 - (void)presentSpoofingError;
391 // Adds a new CRWSessionEntry with the given URL and state object to the history
392 // stack. A state object is a serialized generic JavaScript object that contains
393 // details of the UI's state for a given CRWSessionEntry/URL.
394 // TODO(stuartmorgan): Move the pushState/replaceState logic into
395 // NavigationManager.
396 - (void)pushStateWithPageURL:(const GURL&)pageURL
397                  stateObject:(NSString*)stateObject
398                   transition:(ui::PageTransition)transition;
399 // Assigns the given URL and state object to the current CRWSessionEntry.
400 - (void)replaceStateWithPageURL:(const GURL&)pageUrl
401                     stateObject:(NSString*)stateObject;
403 // Finds all the scrollviews in the view hierarchy and makes sure they do not
404 // interfere with scroll to top when tapping the statusbar.
405 - (void)optOutScrollsToTopForSubviews;
406 // Tears down the old native controller, and then replaces it with the new one.
407 - (void)setNativeController:(id<CRWNativeContent>)nativeController;
408 // Returns whether |url| should be opened.
409 - (BOOL)shouldOpenURL:(const GURL&)url
410       mainDocumentURL:(const GURL&)mainDocumentURL
411           linkClicked:(BOOL)linkClicked;
412 // Called when |url| needs to be opened in a matching native app.
413 // Returns YES if the url was succesfully opened in the native app.
414 - (BOOL)urlTriggersNativeAppLaunch:(const GURL&)url
415                          sourceURL:(const GURL&)sourceURL;
416 // Best guess as to whether the request is a main frame request. This method
417 // should not be assumed correct for security evaluations, as it is possible to
418 // spoof.
419 - (BOOL)isPutativeMainFrameRequest:(NSURLRequest*)request
420                        targetFrame:(const web::FrameInfo*)targetFrame;
421 // Returns whether external URL request should be opened.
422 - (BOOL)shouldOpenExternalURLRequest:(NSURLRequest*)request
423                          targetFrame:(const web::FrameInfo*)targetFrame;
424 // Called when a page updates its history stack using pushState or replaceState.
425 - (void)didUpdateHistoryStateWithPageURL:(const GURL&)url;
427 // Handlers for JavaScript messages. |message| contains a JavaScript command and
428 // data relevant to the message, and |context| contains contextual information
429 // about web view state needed for some handlers.
431 // Handles 'addPluginPlaceholders' message.
432 - (BOOL)handleAddPluginPlaceholdersMessage:(base::DictionaryValue*)message
433                                    context:(NSDictionary*)context;
434 // Handles 'chrome.send' message.
435 - (BOOL)handleChromeSendMessage:(base::DictionaryValue*)message
436                         context:(NSDictionary*)context;
437 // Handles 'console' message.
438 - (BOOL)handleConsoleMessage:(base::DictionaryValue*)message
439                      context:(NSDictionary*)context;
440 // Handles 'dialog.suppressed' message.
441 - (BOOL)handleDialogSuppressedMessage:(base::DictionaryValue*)message
442                               context:(NSDictionary*)context;
443 // Handles 'dialog.willShow' message.
444 - (BOOL)handleDialogWillShowMessage:(base::DictionaryValue*)message
445                             context:(NSDictionary*)context;
446 // Handles 'document.favicons' message.
447 - (BOOL)handleDocumentFaviconsMessage:(base::DictionaryValue*)message
448                               context:(NSDictionary*)context;
449 // Handles 'document.submit' message.
450 - (BOOL)handleDocumentSubmitMessage:(base::DictionaryValue*)message
451                             context:(NSDictionary*)context;
452 // Handles 'externalRequest' message.
453 - (BOOL)handleExternalRequestMessage:(base::DictionaryValue*)message
454                              context:(NSDictionary*)context;
455 // Handles 'form.activity' message.
456 - (BOOL)handleFormActivityMessage:(base::DictionaryValue*)message
457                           context:(NSDictionary*)context;
458 // Handles 'navigator.credentials.request' message.
459 - (BOOL)handleCredentialsRequestedMessage:(base::DictionaryValue*)message
460                                   context:(NSDictionary*)context;
461 // Handles 'navigator.credentials.notifySignedIn' message.
462 - (BOOL)handleSignedInMessage:(base::DictionaryValue*)message
463                       context:(NSDictionary*)context;
464 // Handles 'navigator.credentials.notifySignedOut' message.
465 - (BOOL)handleSignedOutMessage:(base::DictionaryValue*)message
466                        context:(NSDictionary*)context;
467 // Handles 'navigator.credentials.notifyFailedSignIn' message.
468 - (BOOL)handleSignInFailedMessage:(base::DictionaryValue*)message
469                           context:(NSDictionary*)context;
470 // Handles 'resetExternalRequest' message.
471 - (BOOL)handleResetExternalRequestMessage:(base::DictionaryValue*)message
472                                   context:(NSDictionary*)context;
473 // Handles 'window.close.self' message.
474 - (BOOL)handleWindowCloseSelfMessage:(base::DictionaryValue*)message
475                              context:(NSDictionary*)context;
476 // Handles 'window.error' message.
477 - (BOOL)handleWindowErrorMessage:(base::DictionaryValue*)message
478                          context:(NSDictionary*)context;
479 // Handles 'window.hashchange' message.
480 - (BOOL)handleWindowHashChangeMessage:(base::DictionaryValue*)message
481                               context:(NSDictionary*)context;
482 // Handles 'window.history.back' message.
483 - (BOOL)handleWindowHistoryBackMessage:(base::DictionaryValue*)message
484                                context:(NSDictionary*)context;
485 // Handles 'window.history.forward' message.
486 - (BOOL)handleWindowHistoryForwardMessage:(base::DictionaryValue*)message
487                                   context:(NSDictionary*)context;
488 // Handles 'window.history.go' message.
489 - (BOOL)handleWindowHistoryGoMessage:(base::DictionaryValue*)message
490                              context:(NSDictionary*)context;
491 @end
493 namespace {
495 NSString* const kReferrerHeaderName = @"Referer";  // [sic]
497 // Full screen experimental setting.
499 // The long press detection duration must be shorter than the UIWebView's
500 // long click gesture recognizer's minimum duration. That is 0.55s.
501 // If our detection duration is shorter, our gesture recognizer will fire
502 // first, and if it fails the long click gesture (processed simultaneously)
503 // still is able to complete.
504 const NSTimeInterval kLongPressDurationSeconds = 0.55 - 0.1;
505 const CGFloat kLongPressMoveDeltaPixels = 10.0;
507 // The duration of the period following a screen touch during which the user is
508 // still considered to be interacting with the page.
509 const NSTimeInterval kMaximumDelayForUserInteractionInSeconds = 2;
511 // URLs that are fed into UIWebView as history push/replace get escaped,
512 // potentially changing their format. Code that attempts to determine whether a
513 // URL hasn't changed can be confused by those differences though, so method
514 // will round-trip a URL through the escaping process so that it can be adjusted
515 // pre-storing, to allow later comparisons to work as expected.
516 GURL URLEscapedForHistory(const GURL& url) {
517   // TODO(stuartmorgan): This is a very large hammer; see if limited unicode
518   // escaping would be sufficient.
519   return net::GURLWithNSURL(net::NSURLWithGURL(url));
522 // Parses a viewport tag content and returns the value of an attribute with
523 // the given |name|, or nil if the attribute is not present in the tag.
524 NSString* GetAttributeValueFromViewPortContent(NSString* attributeName,
525                                                NSString* viewPortContent) {
526   NSArray* contentItems = [viewPortContent componentsSeparatedByString:@","];
527   for (NSString* item in contentItems) {
528     NSArray* components = [item componentsSeparatedByString:@"="];
529     if ([components count] == 2) {
530       NSCharacterSet* spaceAndNewline =
531           [NSCharacterSet whitespaceAndNewlineCharacterSet];
532       NSString* currentAttributeName =
533           [components[0] stringByTrimmingCharactersInSet:spaceAndNewline];
534       if ([currentAttributeName isEqualToString:attributeName]) {
535         return [components[1] stringByTrimmingCharactersInSet:spaceAndNewline];
536       }
537     }
538   }
539   return nil;
542 // Parses a viewport tag content and returns the value of the user-scalable
543 // attribute or nil.
544 BOOL GetUserScalablePropertyFromViewPortContent(NSString* viewPortContent) {
545   NSString* value =
546       GetAttributeValueFromViewPortContent(@"user-scalable", viewPortContent);
547   if (!value) {
548     return YES;
549   }
550   return !([value isEqualToString:@"0"] ||
551            [value caseInsensitiveCompare:@"no"] == NSOrderedSame);
554 // Leave snapshot overlay up unless page loads.
555 const NSTimeInterval kSnapshotOverlayDelay = 1.5;
556 // Transition to fade snapshot overlay.
557 const NSTimeInterval kSnapshotOverlayTransition = 0.5;
559 }  // namespace
561 @implementation CRWWebController
563 @synthesize webUsageEnabled = _webUsageEnabled;
564 @synthesize usePlaceholderOverlay = _usePlaceholderOverlay;
565 @synthesize loadPhase = _loadPhase;
567 // Implemented by subclasses.
568 @dynamic keyboardDisplayRequiresUserAction;
570 + (instancetype)allocWithZone:(struct _NSZone*)zone {
571   if (self == [CRWWebController class]) {
572     // This is an abstract class which should not be instantiated directly.
573     // Callers should create concrete subclasses instead.
574     NOTREACHED();
575     return nil;
576   }
577   return [super allocWithZone:zone];
580 - (instancetype)initWithWebState:(scoped_ptr<WebStateImpl>)webState {
581   self = [super init];
582   if (self) {
583     _webStateImpl = webState.Pass();
584     DCHECK(_webStateImpl);
585     _webStateImpl->SetWebController(self);
586     _webStateImpl->InitializeRequestTracker(self);
587     // Load phase when no WebView present is 'loaded' because this represents
588     // the idle state.
589     _loadPhase = web::PAGE_LOADED;
590     // Content area is lazily instantiated.
591     _defaultURL = GURL(url::kAboutBlankURL);
592     _jsInjectionReceiver.reset(
593         [[CRWJSInjectionReceiver alloc] initWithEvaluator:self]);
594     _earlyScriptManager.reset([(CRWJSEarlyScriptManager*)[_jsInjectionReceiver
595         instanceOfClass:[CRWJSEarlyScriptManager class]] retain]);
596     _windowIDJSManager.reset([(CRWJSWindowIdManager*)[_jsInjectionReceiver
597         instanceOfClass:[CRWJSWindowIdManager class]] retain]);
598     _lastSeenWindowID.reset();
599     _webViewProxy.reset(
600         [[CRWWebViewProxyImpl alloc] initWithWebController:self]);
601     [[_webViewProxy scrollViewProxy] addObserver:self];
602     _gestureRecognizers.reset([[NSMutableArray alloc] init]);
603     _webViewToolbars.reset([[NSMutableArray alloc] init]);
604     _pendingLoadCompleteActions.reset([[NSMutableArray alloc] init]);
605     [[NSNotificationCenter defaultCenter]
606         addObserver:self
607            selector:@selector(orientationDidChange)
608                name:UIApplicationDidChangeStatusBarOrientationNotification
609              object:nil];
610   }
611   return self;
614 - (id<CRWNativeContentProvider>)nativeProvider {
615   return _nativeProvider.get();
618 - (void)setNativeProvider:(id<CRWNativeContentProvider>)nativeProvider {
619   _nativeProvider.reset(nativeProvider);
622 - (id<CRWSwipeRecognizerProvider>)swipeRecognizerProvider {
623   return _swipeRecognizerProvider.get();
626 - (void)setSwipeRecognizerProvider:
627     (id<CRWSwipeRecognizerProvider>)swipeRecognizerProvider {
628   _swipeRecognizerProvider.reset(swipeRecognizerProvider);
631 - (WebState*)webState {
632   return _webStateImpl.get();
635 - (WebStateImpl*)webStateImpl {
636   return _webStateImpl.get();
639 - (void)clearTransientContentView {
640   // Early return if there is no transient content view.
641   if (!self.containerView.transientContentView)
642     return;
644   // Remove the transient content view from the hierarchy.
645   [self.containerView clearTransientContentView];
647   // Notify the WebState so it can perform any required state cleanup.
648   if (_webStateImpl)
649     _webStateImpl->ClearTransientContentView();
652 - (void)showTransientContentView:(CRWContentView*)contentView {
653   DCHECK(contentView);
654   DCHECK(contentView.scrollView);
655   DCHECK([contentView.scrollView isDescendantOfView:contentView]);
656   [self.containerView displayTransientContent:contentView];
659 - (id<CRWWebDelegate>)delegate {
660   return _delegate.get();
663 - (void)setDelegate:(id<CRWWebDelegate>)delegate {
664   _delegate.reset(delegate);
665   if ([self.nativeController respondsToSelector:@selector(setDelegate:)]) {
666     if ([_delegate respondsToSelector:@selector(webController:titleDidChange:)])
667       [self.nativeController setDelegate:self];
668     else
669       [self.nativeController setDelegate:nil];
670   }
673 - (id<CRWWebUserInterfaceDelegate>)UIDelegate {
674   return _UIDelegate.get();
677 - (void)setUIDelegate:(id<CRWWebUserInterfaceDelegate>)UIDelegate {
678   _UIDelegate.reset(UIDelegate);
681 - (NSString*)scriptByAddingWindowIDCheckForScript:(NSString*)script {
682   NSString* kTemplate = @"if (__gCrWeb['windowId'] === '%@') { %@; }";
683   return [NSString stringWithFormat:kTemplate, [self windowId], script];
686 - (void)removeWebViewAllowingCachedReconstruction:(BOOL)allowCache {
687   if (!self.webView)
688     return;
690   if (allowCache)
691     _expectedReconstructionURL = [self currentNavigationURL];
692   else
693     _expectedReconstructionURL = GURL();
695   [self abortLoad];
696   [self.webView removeFromSuperview];
697   [self.containerView resetContent];
698   [self resetWebView];
701 - (void)dealloc {
702   DCHECK([NSThread isMainThread]);
703   DCHECK(_isBeingDestroyed);  // 'close' must have been called already.
704   DCHECK(!self.webView);
705   _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
706   [[_webViewProxy scrollViewProxy] removeObserver:self];
707   [[NSNotificationCenter defaultCenter] removeObserver:self];
708   [super dealloc];
711 - (BOOL)runUnloadListenerBeforeClosing {
712   // There's not much that can be done since there's limited access to WebKit.
713   // Always return that it's ok to close immediately.
714   return YES;
717 - (void)dismissKeyboard {
718   [self.webView endEditing:YES];
719   if ([self.nativeController respondsToSelector:@selector(dismissKeyboard)])
720     [self.nativeController dismissKeyboard];
723 - (id<CRWNativeContent>)nativeController {
724   return self.containerView.nativeController;
727 - (void)setNativeController:(id<CRWNativeContent>)nativeController {
728   // Check for pointer equality.
729   if (self.nativeController == nativeController)
730     return;
732   // Unset the delegate on the previous instance.
733   if ([self.nativeController respondsToSelector:@selector(setDelegate:)])
734     [self.nativeController setDelegate:nil];
736   [self.containerView displayNativeContent:nativeController];
737   [self setNativeControllerWebUsageEnabled:_webUsageEnabled];
740 // NativeControllerDelegate method, called to inform that title has changed.
741 - (void)nativeContent:(id)content titleDidChange:(NSString*)title {
742   // Responsiveness to delegate method was checked in setDelegate:.
743   [_delegate webController:self titleDidChange:title];
746 - (void)setNativeControllerWebUsageEnabled:(BOOL)webUsageEnabled {
747   if ([self.nativeController
748           respondsToSelector:@selector(setWebUsageEnabled:)]) {
749     [self.nativeController setWebUsageEnabled:webUsageEnabled];
750   }
753 - (void)setWebUsageEnabled:(BOOL)enabled {
754   if (_webUsageEnabled == enabled)
755     return;
756   _webUsageEnabled = enabled;
758   // WKWebView autoreleases its WKProcessPool on removal from superview.
759   // Deferring WKProcessPool deallocation may lead to issues with cookie
760   // clearing and and Browsing Data Partitioning implementation.
761   @autoreleasepool {
762     [self setNativeControllerWebUsageEnabled:_webUsageEnabled];
763     if (enabled) {
764       // Don't create the web view; let it be lazy created as needed.
765     } else {
766       [self clearTransientContentView];
767       [self removeWebViewAllowingCachedReconstruction:YES];
768       _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
769       _touchTrackingRecognizer.reset();
770       [self resetContainerView];
771     }
772   }
775 - (void)requirePageReconstruction {
776   [self removeWebViewAllowingCachedReconstruction:NO];
779 - (void)requirePageReload {
780   _requireReloadOnDisplay = YES;
783 - (void)resetContainerView {
784   [self.containerView removeFromSuperview];
785   _containerView.reset();
788 - (void)handleLowMemory {
789   [self removeWebViewAllowingCachedReconstruction:YES];
790   _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
791   _touchTrackingRecognizer.reset();
792   [self resetContainerView];
793   _usePlaceholderOverlay = YES;
796 - (void)reinitializeWebViewAndReload:(BOOL)reload {
797   if (self.webView) {
798     [self removeWebViewAllowingCachedReconstruction:NO];
799     if (reload) {
800       [self loadCurrentURLInWebView];
801     } else {
802       // Clear the space for the web view to lazy load when needed.
803       _usePlaceholderOverlay = YES;
804       _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
805       _touchTrackingRecognizer.reset();
806       [self resetContainerView];
807     }
808   }
811 - (void)childWindowClosed:(NSString*)windowName {
812   // Subclasses can override this method to be informed about a closed window.
815 - (BOOL)isViewAlive {
816   return [self.containerView isViewAlive];
819 - (BOOL)contentIsHTML {
820   return [self webViewDocumentType] == web::WEB_VIEW_DOCUMENT_TYPE_HTML;
823 // Stop doing stuff, especially network stuff. Close the request tracker.
824 - (void)terminateNetworkActivity {
825   DCHECK(!_isHalted);
826   _isHalted = YES;
828   // Cancel all outstanding perform requests, and clear anything already queued
829   // (since this may be called from within the handling loop) to prevent any
830   // asynchronous JavaScript invocation handling from continuing.
831   [NSObject cancelPreviousPerformRequestsWithTarget:self];
832   _webStateImpl->CloseRequestTracker();
835 - (void)dismissModals {
836   if ([self.nativeController respondsToSelector:@selector(dismissModals)])
837     [self.nativeController dismissModals];
840 // Caller must reset the delegate before calling.
841 - (void)close {
842   self.nativeProvider = nil;
843   self.swipeRecognizerProvider = nil;
844   if ([self.nativeController respondsToSelector:@selector(close)])
845     [self.nativeController close];
847   base::scoped_nsobject<NSSet> observers([_observers copy]);
848   for (id it in observers.get()) {
849     if ([it respondsToSelector:@selector(webControllerWillClose:)])
850       [it webControllerWillClose:self];
851   }
853   if (!_isHalted) {
854     [self terminateNetworkActivity];
855   }
857   DCHECK(!_isBeingDestroyed);
858   DCHECK(!_delegate);  // Delegate should reset its association before closing.
859   // Mark the destruction sequence has started, in case someone else holds a
860   // strong reference and tries to continue using the tab.
861   _isBeingDestroyed = YES;
863   // Remove the web view now. Otherwise, delegate callbacks occur.
864   [self removeWebViewAllowingCachedReconstruction:NO];
866   // Tear down web ui (in case this is part of this tab) and web state now,
867   // since the timing of dealloc can't be guaranteed.
868   _webStateImpl.reset();
871 - (void)checkLinkPresenceUnderGesture:(UIGestureRecognizer*)gestureRecognizer
872                     completionHandler:(void (^)(BOOL))completionHandler {
873   CGPoint webViewPoint = [gestureRecognizer locationInView:self.webView];
874   base::WeakNSObject<CRWWebController> weakSelf(self);
875   [self fetchDOMElementAtPoint:webViewPoint
876              completionHandler:^(scoped_ptr<base::DictionaryValue> element) {
877                std::string link;
878                BOOL hasLink =
879                    element && element->GetString("href", &link) && link.size();
880                completionHandler(hasLink);
881              }];
884 - (void)setDOMElementForLastTouch:(scoped_ptr<base::DictionaryValue>)element {
885   _DOMElementForLastTouch = element.Pass();
888 - (void)showContextMenu:(UIGestureRecognizer*)gestureRecognizer {
889   // Calling this method if [self supportsCustomContextMenu] returned NO
890   // is a programmer error.
891   DCHECK([self supportsCustomContextMenu]);
893   // We don't want ongoing notification that the long press is held.
894   if ([gestureRecognizer state] != UIGestureRecognizerStateBegan)
895     return;
897   if (!_DOMElementForLastTouch || _DOMElementForLastTouch->empty())
898     return;
900   NSDictionary* info =
901       [self contextMenuInfoForElement:_DOMElementForLastTouch.get()];
902   CGPoint point = [gestureRecognizer locationInView:self.webView];
904   // Cancel all touches on the web view when showing custom context menu. This
905   // will suppress the system context menu and prevent further user interactions
906   // with web view (like scrolling the content and following links). This
907   // approach is similar to UIWebView and WKWebView workflow as both call
908   // -[UIApplication _cancelAllTouches] to cancel all touch events, once the
909   // long press is detected.
910   CancelAllTouches(self.webScrollView);
911   [self.UIDelegate webController:self
912                   runContextMenu:info
913                          atPoint:point
914                           inView:self.webView];
917 - (BOOL)supportsCustomContextMenu {
918   SEL runMenuSelector = @selector(webController:runContextMenu:atPoint:inView:);
919   return [self.UIDelegate respondsToSelector:runMenuSelector];
922 // TODO(shreyasv): This code is shared with SnapshotManager. Remove this and add
923 // it as part of WebDelegate delegate API such that a default image is returned
924 // immediately.
925 + (UIImage*)defaultSnapshotImage {
926   static UIImage* defaultImage = nil;
928   if (!defaultImage) {
929     CGRect frame = CGRectMake(0, 0, 2, 2);
930     UIGraphicsBeginImageContext(frame.size);
931     [[UIColor whiteColor] setFill];
932     CGContextFillRect(UIGraphicsGetCurrentContext(), frame);
934     UIImage* result = UIGraphicsGetImageFromCurrentImageContext();
935     UIGraphicsEndImageContext();
937     defaultImage =
938         [[result stretchableImageWithLeftCapWidth:1 topCapHeight:1] retain];
939   }
940   return defaultImage;
943 - (BOOL)canGoBack {
944   return _webStateImpl->GetNavigationManagerImpl().CanGoBack();
947 - (BOOL)canGoForward {
948   return _webStateImpl->GetNavigationManagerImpl().CanGoForward();
951 - (CGPoint)scrollPosition {
952   CGPoint position = CGPointMake(0.0, 0.0);
953   if (!self.webScrollView)
954     return position;
955   return self.webScrollView.contentOffset;
958 - (BOOL)atTop {
959   if (!self.webView)
960     return YES;
961   UIScrollView* scrollView = self.webScrollView;
962   return scrollView.contentOffset.y == -scrollView.contentInset.top;
965 - (void)presentSpoofingError {
966   UMA_HISTOGRAM_ENUMERATION("Web.URLVerificationFailure",
967                             [self webViewDocumentType],
968                             web::WEB_VIEW_DOCUMENT_TYPE_COUNT);
969   if (self.webView) {
970     [self removeWebViewAllowingCachedReconstruction:NO];
971     [_delegate presentSpoofingError];
972   }
975 - (GURL)currentURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel {
976   DCHECK(trustLevel) << "Verification of the trustLevel state is mandatory";
977   if (self.webView) {
978     GURL url([self webURLWithTrustLevel:trustLevel]);
979     // Web views treat all about: URLs as the same origin, which makes it
980     // possible for pages to document.write into about:<foo> pages, where <foo>
981     // can be something misleading. Report any about: URL as about:blank to
982     // prevent that. See crbug.com/326118
983     if (url.scheme() == url::kAboutScheme)
984       return GURL(url::kAboutBlankURL);
985     return url;
986   }
987   // Any non-web URL source is trusted.
988   *trustLevel = web::URLVerificationTrustLevel::kAbsolute;
989   if (self.nativeController)
990     return [self.nativeController url];
991   return [self currentNavigationURL];
994 - (GURL)currentURL {
995   web::URLVerificationTrustLevel trustLevel =
996       web::URLVerificationTrustLevel::kNone;
997   const GURL url([self currentURLWithTrustLevel:&trustLevel]);
999   // Check whether the spoofing warning needs to be displayed.
1000   if (trustLevel == web::URLVerificationTrustLevel::kNone &&
1001       ![self ignoreURLVerificationFailures]) {
1002     dispatch_async(dispatch_get_main_queue(), ^{
1003       if (!_isHalted) {
1004         DCHECK_EQ(url, [self currentNavigationURL]);
1005         [self presentSpoofingError];
1006       }
1007     });
1008   }
1010   return url;
1013 - (web::Referrer)currentReferrer {
1014   // Referrer string doesn't include the fragment, so in cases where the
1015   // previous URL is equal to the current referrer plus the fragment the
1016   // previous URL is returned as current referrer.
1017   NSString* referrerString = self.currentReferrerString;
1019   // In case of an error evaluating the JavaScript simply return empty string.
1020   if ([referrerString length] == 0)
1021     return web::Referrer();
1023   NSString* previousURLString =
1024       base::SysUTF8ToNSString([self currentNavigationURL].spec());
1025   // Check if the referrer is equal to the previous URL minus the hash symbol.
1026   // L'#' is used to convert the char '#' to a unichar.
1027   if ([previousURLString length] > [referrerString length] &&
1028       [previousURLString hasPrefix:referrerString] &&
1029       [previousURLString characterAtIndex:[referrerString length]] == L'#') {
1030     referrerString = previousURLString;
1031   }
1032   // Since referrer is being extracted from the destination page, the correct
1033   // policy from the origin has *already* been applied. Since the extracted URL
1034   // is the post-policy value, and the source policy is no longer available,
1035   // the policy is set to Always so that whatever WebKit decided to send will be
1036   // re-sent when replaying the entry.
1037   // TODO(stuartmorgan): When possible, get the real referrer and policy in
1038   // advance and use that instead. https://crbug.com/227769.
1039   return web::Referrer(GURL(base::SysNSStringToUTF8(referrerString)),
1040                        web::ReferrerPolicyAlways);
1043 - (void)pushStateWithPageURL:(const GURL&)pageURL
1044                  stateObject:(NSString*)stateObject
1045                   transition:(ui::PageTransition)transition {
1046   [[self sessionController] pushNewEntryWithURL:pageURL
1047                                     stateObject:stateObject
1048                                      transition:transition];
1049   [self didUpdateHistoryStateWithPageURL:pageURL];
1052 - (void)replaceStateWithPageURL:(const GURL&)pageUrl
1053                     stateObject:(NSString*)stateObject {
1054   [[self sessionController] updateCurrentEntryWithURL:pageUrl
1055                                           stateObject:stateObject];
1056   [self didUpdateHistoryStateWithPageURL:pageUrl];
1059 - (void)injectEarlyInjectionScripts {
1060   DCHECK(self.webView);
1061   if (![_earlyScriptManager hasBeenInjected]) {
1062     [_earlyScriptManager inject];
1063     // If this assertion fires there has been an error parsing the core.js
1064     // object.
1065     DCHECK([_earlyScriptManager hasBeenInjected]);
1066   }
1067   [self injectWindowID];
1070 - (void)injectWindowID {
1071   if (![_windowIDJSManager hasBeenInjected]) {
1072     // If the window ID wasn't present, this is a new page.
1073     [self setPageChangeProbability:web::PAGE_CHANGE_PROBABILITY_LOW];
1074     // Default values for suppressDialogs and notifyAboutDialogs are NO,
1075     // so updating them only when necessary is a good optimization.
1076     if (_setSuppressDialogsLater || _setNotifyAboutDialogsLater) {
1077       [self setSuppressDialogs:_setSuppressDialogsLater
1078                         notify:_setNotifyAboutDialogsLater];
1079       _setSuppressDialogsLater = NO;
1080       _setNotifyAboutDialogsLater = NO;
1081     }
1083     [_windowIDJSManager inject];
1084     DCHECK([_windowIDJSManager hasBeenInjected]);
1085   }
1088 // Set the specified recognizer to take priority over any recognizers in the
1089 // view that have a description containing the specified text fragment.
1090 + (void)requireGestureRecognizerToFail:(UIGestureRecognizer*)recognizer
1091                                 inView:(UIView*)view
1092                  containingDescription:(NSString*)fragment {
1093   for (UIGestureRecognizer* iRecognizer in [view gestureRecognizers]) {
1094     if (iRecognizer != recognizer) {
1095       NSString* description = [iRecognizer description];
1096       if ([description rangeOfString:fragment].location != NSNotFound) {
1097         [iRecognizer requireGestureRecognizerToFail:recognizer];
1098         // requireGestureRecognizerToFail: doesn't retain the recognizer, so it
1099         // is possible for |iRecognizer| to outlive |recognizer| and end up with
1100         // a dangling pointer. Add a retaining associative reference to ensure
1101         // that the lifetimes work out.
1102         // Note that normally using the value as the key wouldn't make any
1103         // sense, but here it's fine since nothing needs to look up the value.
1104         objc_setAssociatedObject(view, recognizer, recognizer,
1105                                  OBJC_ASSOCIATION_RETAIN_NONATOMIC);
1106       }
1107     }
1108   }
1111 - (void)webViewDidChange {
1112   CHECK(_webUsageEnabled) << "Tried to create a web view while suspended!";
1114   UIView* webView = self.webView;
1115   DCHECK(webView);
1117   [webView setTag:kWebViewTag];
1118   [webView setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
1119                                UIViewAutoresizingFlexibleHeight];
1120   [webView setBackgroundColor:[UIColor colorWithWhite:0.2 alpha:1.0]];
1122   // Create a dependency between the |webView| pan gesture and BVC side swipe
1123   // gestures. Note: This needs to be added before the longPress recognizers
1124   // below, or the longPress appears to deadlock the remaining recognizers,
1125   // thereby breaking scroll.
1126   NSSet* recognizers = [_swipeRecognizerProvider swipeRecognizers];
1127   for (UISwipeGestureRecognizer* swipeRecognizer in recognizers) {
1128     [self.webScrollView.panGestureRecognizer
1129         requireGestureRecognizerToFail:swipeRecognizer];
1130   }
1132   // On iOS 4.x, there are two gesture recognizers on the UIWebView subclasses,
1133   // that have a minimum tap threshold of 0.12s and 0.75s.
1134   //
1135   // My theory is that the shorter threshold recognizer performs the link
1136   // highlight (grey highlight around links when it is tapped and held) while
1137   // the longer threshold one pops up the context menu.
1138   //
1139   // To override the context menu, this recognizer needs to react faster than
1140   // the 0.75s one. The below gesture recognizer is initialized with a
1141   // detection duration a little lower than that (see
1142   // kLongPressDurationSeconds). It also points the delegate to this class that
1143   // allows simultaneously operate along with the other recognizers.
1144   _contextMenuRecognizer.reset([[UILongPressGestureRecognizer alloc]
1145       initWithTarget:self
1146               action:@selector(showContextMenu:)]);
1147   [_contextMenuRecognizer setMinimumPressDuration:kLongPressDurationSeconds];
1148   [_contextMenuRecognizer setAllowableMovement:kLongPressMoveDeltaPixels];
1149   [_contextMenuRecognizer setDelegate:self];
1150   [webView addGestureRecognizer:_contextMenuRecognizer];
1151   // Certain system gesture handlers are known to conflict with our context
1152   // menu handler, causing extra events to fire when the context menu is active.
1154   // A number of solutions have been investigated. The lowest-risk solution
1155   // appears to be to recurse through the web controller's recognizers, looking
1156   // for fingerprints of the recognizers known to cause problems, which are then
1157   // de-prioritized (below our own long click handler).
1158   // Hunting for description fragments of system recognizers is undeniably
1159   // brittle for future versions of iOS. If it does break the context menu
1160   // events may leak (regressing b/5310177), but the app will otherwise work.
1161   [CRWWebController
1162       requireGestureRecognizerToFail:_contextMenuRecognizer
1163                               inView:webView
1164                containingDescription:@"action=_highlightLongPressRecognized:"];
1166   // Add all additional gesture recognizers to the web view.
1167   for (UIGestureRecognizer* recognizer in _gestureRecognizers.get()) {
1168     [webView addGestureRecognizer:recognizer];
1169   }
1171   _URLOnStartLoading = _defaultURL;
1173   // Add the web toolbars.
1174   [self.containerView addToolbars:_webViewToolbars];
1176   base::scoped_nsobject<CRWWebViewContentView> webViewContentView(
1177       [[CRWWebViewContentView alloc] initWithWebView:self.webView
1178                                           scrollView:self.webScrollView]);
1179   [self.containerView displayWebViewContentView:webViewContentView];
1182 - (CRWWebController*)createChildWebControllerWithReferrerURL:
1183     (const GURL&)referrerURL {
1184   web::Referrer referrer(referrerURL, web::ReferrerPolicyDefault);
1185   CRWWebController* result =
1186       [self.delegate webPageOrderedOpenBlankWithReferrer:referrer
1187                                             inBackground:NO];
1188   DCHECK(!result || result.sessionController.openedByDOM);
1189   return result;
1192 - (BOOL)canUseViewForGeneratingOverlayPlaceholderView {
1193   return self.containerView != nil;
1196 - (UIView*)view {
1197   // Kick off the process of lazily creating the view and starting the load if
1198   // necessary; this creates _containerView if it doesn't exist.
1199   [self triggerPendingLoad];
1200   DCHECK(self.containerView);
1201   return self.containerView;
1204 - (CRWWebControllerContainerView*)containerView {
1205   return _containerView.get();
1208 - (id<CRWWebViewProxy>)webViewProxy {
1209   return _webViewProxy.get();
1212 - (UIView*)viewForPrinting {
1213   // TODO(ios): crbug.com/227944. Printing is not supported for native
1214   // controllers.
1215   return self.webView;
1218 - (void)loadRequest:(NSMutableURLRequest*)request {
1219   // Subclasses must implement this method.
1220   NOTREACHED();
1223 - (void)registerLoadRequest:(const GURL&)requestURL
1224                    referrer:(const web::Referrer&)referrer
1225                  transition:(ui::PageTransition)transition {
1226   // Transfer time is registered so that further transitions within the time
1227   // envelope are not also registered as links.
1228   _lastTransferTimeInSeconds = CFAbsoluteTimeGetCurrent();
1229   // Before changing phases, the delegate should be informed that any existing
1230   // request is being cancelled before completion.
1231   [self loadCancelled];
1232   DCHECK(_loadPhase == web::PAGE_LOADED);
1234   _loadPhase = web::LOAD_REQUESTED;
1235   [self resetLoadState];
1236   _lastRegisteredRequestURL = requestURL;
1238   if (!(transition & ui::PAGE_TRANSITION_IS_REDIRECT_MASK)) {
1239     // Record state of outgoing page.
1240     [self recordStateInHistory];
1241   }
1243   // If the web view had been discarded, and this request is to load that
1244   // URL again, then it's a rebuild and should use the cache.
1245   BOOL preferCache = _expectedReconstructionURL.is_valid() &&
1246                      _expectedReconstructionURL == requestURL;
1248   [_delegate webWillAddPendingURL:requestURL transition:transition];
1249   // Add or update pending url.
1250   if (_webStateImpl->GetNavigationManagerImpl().GetPendingItem()) {
1251     // Update the existing pending entry.
1252     // Typically on PAGE_TRANSITION_CLIENT_REDIRECT.
1253     [[self sessionController] updatePendingEntry:requestURL];
1254   } else {
1255     // A new session history entry needs to be created.
1256     [[self sessionController] addPendingEntry:requestURL
1257                                      referrer:referrer
1258                                    transition:transition
1259                             rendererInitiated:YES];
1260   }
1261   // Update the cache mode for all the network requests issued by this web view.
1262   // The mode is reset to CACHE_NORMAL after each page load.
1263   if (_webStateImpl->GetCacheMode() != net::RequestTracker::CACHE_NORMAL) {
1264     _webStateImpl->GetRequestTracker()->SetCacheModeFromUIThread(
1265         _webStateImpl->GetCacheMode());
1266   } else if (preferCache) {
1267     _webStateImpl->GetRequestTracker()->SetCacheModeFromUIThread(
1268         net::RequestTracker::CACHE_HISTORY);
1269   }
1270   _webStateImpl->SetIsLoading(true);
1271   [_delegate webDidAddPendingURL];
1272   _webStateImpl->OnProvisionalNavigationStarted(requestURL);
1275 - (NSString*)javascriptToReplaceWebViewURL:(const GURL&)URL
1276                            stateObjectJSON:(NSString*)stateObject {
1277   std::string outURL;
1278   base::EscapeJSONString(URL.spec(), true, &outURL);
1279   return
1280       [NSString stringWithFormat:@"__gCrWeb.replaceWebViewURL(%@, %@);",
1281                                  base::SysUTF8ToNSString(outURL), stateObject];
1284 - (void)setPushedOrReplacedURL:(const GURL&)URL
1285                    stateObject:(NSString*)stateObject {
1286   // TODO(stuartmorgan): Make CRWSessionController manage this internally (or
1287   // remove it; it's not clear this matches other platforms' behavior).
1288   _webStateImpl->GetNavigationManagerImpl().OnNavigationItemCommitted();
1290   NSString* replaceWebViewUrlJS =
1291       [self javascriptToReplaceWebViewURL:URL stateObjectJSON:stateObject];
1292   std::string outState;
1293   base::EscapeJSONString(base::SysNSStringToUTF8(stateObject), true, &outState);
1294   NSString* popstateJS =
1295       [NSString stringWithFormat:@"__gCrWeb.dispatchPopstateEvent(%@);",
1296                                  base::SysUTF8ToNSString(outState)];
1297   NSString* combinedJS =
1298       [NSString stringWithFormat:@"%@%@", replaceWebViewUrlJS, popstateJS];
1299   GURL urlCopy(URL);
1300   base::WeakNSObject<CRWWebController> weakSelf(self);
1301   [self evaluateJavaScript:combinedJS
1302        stringResultHandler:^(NSString*, NSError*) {
1303          if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
1304            return;
1305          base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
1306          strongSelf.get()->_URLOnStartLoading = urlCopy;
1307          strongSelf.get()->_lastRegisteredRequestURL = urlCopy;
1308        }];
1311 // Load the current URL in a web view, first ensuring the web view is visible.
1312 - (void)loadCurrentURLInWebView {
1313   [self willLoadCurrentURLInWebView];
1315   // Re-register the user agent, because UIWebView sometimes loses it.
1316   // See crbug.com/228397.
1317   [self registerUserAgent];
1319   // Clear the set of URLs opened in external applications.
1320   _openedApplicationURL.reset([[NSMutableSet alloc] init]);
1322   // Load the url. The UIWebView delegate callbacks take care of updating the
1323   // session history and UI.
1324   const GURL targetURL([self currentNavigationURL]);
1325   if (!targetURL.is_valid())
1326     return;
1328   // JavaScript should never be evaluated here. User-entered JS should be
1329   // evaluated via stringByEvaluatingUserJavaScriptFromString.
1330   DCHECK(!targetURL.SchemeIs(url::kJavaScriptScheme));
1332   [self ensureWebViewCreated];
1334   [self loadRequestForCurrentNavigationItem];
1337 - (void)loadRequestForCurrentNavigationItem {
1338   // Handled differently by UIWebView and WKWebView subclasses.
1339   NOTIMPLEMENTED();
1342 - (NSMutableURLRequest*)requestForCurrentNavigationItem {
1343   const GURL currentNavigationURL([self currentNavigationURL]);
1344   NSMutableURLRequest* request = [NSMutableURLRequest
1345       requestWithURL:net::NSURLWithGURL(currentNavigationURL)];
1346   const web::Referrer referrer([self currentSessionEntryReferrer]);
1347   if (referrer.url.is_valid()) {
1348     std::string referrerValue =
1349         web::ReferrerHeaderValueForNavigation(currentNavigationURL, referrer);
1350     if (!referrerValue.empty()) {
1351       [request setValue:base::SysUTF8ToNSString(referrerValue)
1352           forHTTPHeaderField:kReferrerHeaderName];
1353     }
1354   }
1356   // If there are headers in the current session entry add them to |request|.
1357   // Headers that would overwrite fields already present in |request| are
1358   // skipped.
1359   NSDictionary* headers = [self currentHTTPHeaders];
1360   for (NSString* headerName in headers) {
1361     if (![request valueForHTTPHeaderField:headerName]) {
1362       [request setValue:[headers objectForKey:headerName]
1363           forHTTPHeaderField:headerName];
1364     }
1365   }
1367   return request;
1370 - (void)loadNativeViewWithSuccess:(BOOL)loadSuccess {
1371   const GURL currentURL([self currentURL]);
1372   [self didStartLoadingURL:currentURL updateHistory:loadSuccess];
1373   _loadPhase = web::PAGE_LOADED;
1375   // Perform post-load-finished updates.
1376   [self didFinishWithURL:currentURL loadSuccess:loadSuccess];
1378   // Inform the embedder the title changed.
1379   if ([_delegate respondsToSelector:@selector(webController:titleDidChange:)]) {
1380     NSString* title = [self.nativeController title];
1381     // If a title is present, notify the delegate.
1382     if (title)
1383       [_delegate webController:self titleDidChange:title];
1384     // If the controller handles title change notification, route those to the
1385     // delegate.
1386     if ([self.nativeController respondsToSelector:@selector(setDelegate:)]) {
1387       [self.nativeController setDelegate:self];
1388     }
1389   }
1392 - (void)loadErrorInNativeView:(NSError*)error {
1393   [self removeWebViewAllowingCachedReconstruction:NO];
1395   const GURL currentUrl = [self currentNavigationURL];
1397   error = web::NetErrorFromError(error);
1398   BOOL isPost = [self isCurrentNavigationItemPOST];
1399   [self setNativeController:[_nativeProvider controllerForURL:currentUrl
1400                                                     withError:error
1401                                                        isPost:isPost]];
1402   [self loadNativeViewWithSuccess:NO];
1405 // Load the current URL in a native controller, retrieved from the native
1406 // provider. Call |loadNativeViewWithSuccess:YES| to load the native controller.
1407 - (void)loadCurrentURLInNativeView {
1408   // Free the web view.
1409   [self removeWebViewAllowingCachedReconstruction:NO];
1411   const GURL targetURL = [self currentNavigationURL];
1412   const web::Referrer referrer;
1413   // Unlike the WebView case, always create a new controller and view.
1414   // TODO(pinkerton): What to do if this does return nil?
1415   [self setNativeController:[_nativeProvider controllerForURL:targetURL]];
1416   [self registerLoadRequest:targetURL
1417                    referrer:referrer
1418                  transition:[self currentTransition]];
1419   [self loadNativeViewWithSuccess:YES];
1422 - (void)loadWithParams:(const web::WebLoadParams&)originalParams {
1423   // Make a copy of |params|, as some of the delegate methods may modify it.
1424   web::WebLoadParams params(originalParams);
1426   // Initiating a navigation from the UI, record the current page state before
1427   // the new page loads. Don't record for back/forward, as the current entry
1428   // has already been moved to the next entry in the history. Do, however,
1429   // record it for general reload.
1430   // TODO(jimblackler): consider a single unified call to record state whenever
1431   // the page is about to be changed. This cannot currently be done after
1432   // addPendingEntry is called.
1434   [_delegate webWillInitiateLoadWithParams:params];
1436   GURL navUrl = params.url;
1437   ui::PageTransition transition = params.transition_type;
1439   BOOL initialNavigation = NO;
1440   BOOL forwardBack =
1441       PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD) &&
1442       (transition & ui::PAGE_TRANSITION_FORWARD_BACK);
1443   if (forwardBack) {
1444     // Setting these for back/forward is not supported.
1445     DCHECK(!params.extra_headers);
1446     DCHECK(!params.post_data);
1447   } else {
1448     // Clear transient view before making any changes to history and navigation
1449     // manager. TODO(stuartmorgan): Drive Transient Item clearing from
1450     // navigation system, rather than from WebController.
1451     [self clearTransientContentView];
1453     // TODO(stuartmorgan): Why doesn't recordStateInHistory get called for
1454     // forward/back transitions?
1455     [self recordStateInHistory];
1457     CRWSessionController* history =
1458         _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1459     if (!self.currentSessionEntry)
1460       initialNavigation = YES;
1461     [history addPendingEntry:navUrl
1462                     referrer:params.referrer
1463                   transition:transition
1464            rendererInitiated:params.is_renderer_initiated];
1465     web::NavigationItemImpl* addedItem =
1466         [self currentSessionEntry].navigationItemImpl;
1467     DCHECK(addedItem);
1468     if (params.extra_headers)
1469       addedItem->AddHttpRequestHeaders(params.extra_headers);
1470     if (params.post_data) {
1471       DCHECK([addedItem->GetHttpRequestHeaders() objectForKey:@"Content-Type"])
1472           << "Post data should have an associated content type";
1473       addedItem->SetPostData(params.post_data);
1474       addedItem->SetShouldSkipResubmitDataConfirmation(true);
1475     }
1476   }
1478   [_delegate webDidUpdateSessionForLoadWithParams:params
1479                              wasInitialNavigation:initialNavigation];
1481   // If a non-default cache mode is passed in, it takes precedence over
1482   // |reload|.
1483   const BOOL reload = [self shouldReload:navUrl transition:transition];
1484   if (params.cache_mode != net::RequestTracker::CACHE_NORMAL) {
1485     _webStateImpl->SetCacheMode(params.cache_mode);
1486   } else if (reload) {
1487     _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_RELOAD);
1488   }
1490   [self loadCurrentURL];
1492   // Change the cache mode back to CACHE_NORMAL after a reload.
1493   _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1496 - (void)loadCurrentURL {
1497   // If the content view doesn't exist, the tab has either been evicted, or
1498   // never displayed. Bail, and let the URL be loaded when the tab is shown.
1499   if (!self.containerView)
1500     return;
1502   // Reset current WebUI if one exists.
1503   [self clearWebUI];
1505   // Precaution, so that the outgoing URL is registered, to reduce the risk of
1506   // it being seen as a fresh URL later by the same method (and new page change
1507   // erroneously reported).
1508   [self checkForUnexpectedURLChange];
1510   // Abort any outstanding page load. This ensures the delegate gets informed
1511   // about the outgoing page, and further messages from the page are suppressed.
1512   if (_loadPhase != web::PAGE_LOADED)
1513     [self abortLoad];
1515   DCHECK(!_isHalted);
1516   // Remove the transient content view.
1517   [self clearTransientContentView];
1519   const GURL currentURL = [self currentNavigationURL];
1520   // If it's a chrome URL, but not a native one, create the WebUI instance.
1521   if (web::GetWebClient()->IsAppSpecificURL(currentURL) &&
1522       ![_nativeProvider hasControllerForURL:currentURL]) {
1523     [self createWebUIForURL:currentURL];
1524   }
1526   // Loading a new url, must check here if it's a native chrome URL and
1527   // replace the appropriate view if so, or transition back to a web view from
1528   // a native view.
1529   if ([self shouldLoadURLInNativeView:currentURL]) {
1530     [self loadCurrentURLInNativeView];
1531   } else {
1532     [self loadCurrentURLInWebView];
1533   }
1535   // Once a URL has been loaded, any cached-based reconstruction state has
1536   // either been handled or obsoleted.
1537   _expectedReconstructionURL = GURL();
1540 - (BOOL)shouldLoadURLInNativeView:(const GURL&)url {
1541   // App-specific URLs that don't require WebUI are loaded in native views.
1542   return web::GetWebClient()->IsAppSpecificURL(url) &&
1543          !_webStateImpl->HasWebUI();
1546 - (void)triggerPendingLoad {
1547   if (!self.containerView) {
1548     DCHECK(!_isBeingDestroyed);
1549     // Create the top-level parent view, which will contain the content (whether
1550     // native or web). Note, this needs to be created with a non-zero size
1551     // to allow for (native) subviews with autosize constraints to be correctly
1552     // processed.
1553     _containerView.reset([[CRWWebControllerContainerView alloc]
1554         initWithContentViewProxy:_webViewProxy]);
1555     self.containerView.frame = [[UIScreen mainScreen] bounds];
1556     [self.containerView addGestureRecognizer:[self touchTrackingRecognizer]];
1557     [self.containerView setAccessibilityIdentifier:web::kContainerViewID];
1558     // Is |currentUrl| a web scheme or native chrome scheme.
1559     BOOL isChromeScheme =
1560         web::GetWebClient()->IsAppSpecificURL([self currentNavigationURL]);
1562     // Don't immediately load the web page if in overlay mode. Always load if
1563     // native.
1564     if (isChromeScheme || !_overlayPreviewMode) {
1565       // TODO(jimblackler): end the practice of calling |loadCurrentURL| when it
1566       // is possible there is no current URL. If the call performs necessary
1567       // initialization, break that out.
1568       [self loadCurrentURL];
1569     }
1571     // Display overlay view until current url has finished loading or delay and
1572     // then transition away.
1573     if ((_overlayPreviewMode || _usePlaceholderOverlay) && !isChromeScheme)
1574       [self addPlaceholderOverlay];
1576     // Don't reset the overlay flag if in preview mode.
1577     if (!_overlayPreviewMode)
1578       _usePlaceholderOverlay = NO;
1579   } else if (_requireReloadOnDisplay && self.webView) {
1580     [self addPlaceholderOverlay];
1581     [self loadCurrentURL];
1582     _requireReloadOnDisplay = NO;
1583   }
1586 - (BOOL)shouldReload:(const GURL&)destinationURL
1587           transition:(ui::PageTransition)transition {
1588   // Do a reload if the user hits enter in the address bar or re-types a URL.
1589   CRWSessionController* sessionController =
1590       _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1591   web::NavigationItem* item =
1592       _webStateImpl->GetNavigationManagerImpl().GetVisibleItem();
1593   return (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) && item &&
1594          (destinationURL == item->GetURL() ||
1595           destinationURL == [sessionController currentEntry].originalUrl);
1598 // Reload either the web view or the native content depending on which is
1599 // displayed.
1600 - (void)reloadInternal {
1601   // Clear last user interaction.
1602   // TODO(jyquinn): Move to after the load commits, in the subclass
1603   // implementation. This will be inaccurate if the reload fails or is
1604   // cancelled.
1605   _lastUserInteraction.reset();
1606   web::RecordAction(UserMetricsAction("Reload"));
1607   if (self.webView) {
1608     // Just as we don't use the WebView native back and forward navigation
1609     // (preferring to load the URLs manually) we don't use the native reload.
1610     // This ensures state processing and delegate calls are consistent.
1611     [self loadCurrentURL];
1612   } else {
1613     [self.nativeController reload];
1614   }
1617 - (void)reload {
1618   [_delegate webWillReload];
1620   _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_RELOAD);
1621   [self reloadInternal];
1622   _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1625 - (void)loadCancelled {
1626   // Current load will not complete; this should be communicated upstream to the
1627   // delegate.
1628   switch (_loadPhase) {
1629     case web::LOAD_REQUESTED:
1630       // Load phase after abort is always PAGE_LOADED.
1631       _loadPhase = web::PAGE_LOADED;
1632       if (!_isHalted) {
1633         _webStateImpl->SetIsLoading(false);
1634       }
1635       [_delegate webCancelStartLoadingRequest];
1636       break;
1637     case web::PAGE_LOADING:
1638       // The previous load never fully completed before this page change. The
1639       // loadPhase is changed to PAGE_LOADED to indicate the cycle is complete,
1640       // and the delegate is called.
1641       _loadPhase = web::PAGE_LOADED;
1642       if (!_isHalted) {
1643         // RequestTracker expects StartPageLoad to be followed by
1644         // FinishPageLoad, passing the exact same URL.
1645         self.webStateImpl->GetRequestTracker()->FinishPageLoad(
1646             _URLOnStartLoading, false);
1647       }
1648       [_delegate webLoadCancelled:_URLOnStartLoading];
1649       break;
1650     case web::PAGE_LOADED:
1651       break;
1652   }
1655 - (void)abortLoad {
1656   [self abortWebLoad];
1657   [self loadCancelled];
1660 - (void)prepareForGoBack {
1661   // Make sure any transitions that may have occurred have been seen and acted
1662   // on by the CRWWebController, so the history stack and state of the
1663   // CRWWebController is 100% up to date before the stack navigation starts.
1664   if (self.webView) {
1665     [self injectEarlyInjectionScripts];
1666     [self checkForUnexpectedURLChange];
1667   }
1668   // Discard any outstanding pending entries before adjusting the navigation
1669   // index.
1670   CRWSessionController* sessionController =
1671       _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1672   [sessionController discardNonCommittedEntries];
1674   bool wasShowingInterstitial = _webStateImpl->IsShowingWebInterstitial();
1676   // Call into the delegate before |recordStateInHistory|.
1677   // TODO(rohitrao): Can this be reordered after |recordStateInHistory|?
1678   [_delegate webDidPrepareForGoBack];
1680   // Before changing the current session history entry, record the tab state.
1681   if (!wasShowingInterstitial) {
1682     [self recordStateInHistory];
1683   }
1686 - (void)goBack {
1687   [self goDelta:-1];
1690 - (void)goForward {
1691   [self goDelta:1];
1694 - (void)goDelta:(int)delta {
1695   if (delta == 0) {
1696     [self reload];
1697     return;
1698   }
1700   // Abort if there is nothing next in the history.
1701   // Note that it is NOT checked that the history depth is at least |delta|.
1702   if ((delta < 0 && ![self canGoBack]) || (delta > 0 && ![self canGoForward])) {
1703     return;
1704   }
1706   if (delta < 0) {
1707     [self prepareForGoBack];
1708   } else {
1709     // Before changing the current session history entry, record the tab state.
1710     [self recordStateInHistory];
1711   }
1713   [_delegate webWillGoDelta:delta];
1715   CRWSessionController* sessionController =
1716       _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1717   CRWSessionEntry* fromEntry = [sessionController currentEntry];
1718   [sessionController goDelta:delta];
1719   if (fromEntry) {
1720     _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_HISTORY);
1721     [self finishHistoryNavigationFromEntry:fromEntry];
1722     _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1723   }
1726 - (BOOL)isLoaded {
1727   return _loadPhase == web::PAGE_LOADED;
1730 - (void)loadCompleteWithSuccess:(BOOL)loadSuccess {
1731   [self removePlaceholderOverlay];
1732   // The webView may have been torn down (or replaced by a native view). Be
1733   // safe and do nothing if that's happened.
1734   if (_loadPhase != web::PAGE_LOADING)
1735     return;
1737   DCHECK(self.webView);
1739   const GURL currentURL([self currentURL]);
1741   [self resetLoadState];
1742   _loadPhase = web::PAGE_LOADED;
1744   [self optOutScrollsToTopForSubviews];
1746   // Ensure the URL is as expected (and already reported to the delegate).
1747   DCHECK(currentURL == _lastRegisteredRequestURL)
1748       << std::endl
1749       << "currentURL = [" << currentURL << "]" << std::endl
1750       << "_lastRegisteredRequestURL = [" << _lastRegisteredRequestURL << "]";
1752   // Perform post-load-finished updates.
1753   [self didFinishWithURL:currentURL loadSuccess:loadSuccess];
1755   // Execute the pending LoadCompleteActions.
1756   for (ProceduralBlock action in _pendingLoadCompleteActions.get()) {
1757     action();
1758   }
1759   [_pendingLoadCompleteActions removeAllObjects];
1762 - (void)didFinishWithURL:(const GURL&)currentURL loadSuccess:(BOOL)loadSuccess {
1763   DCHECK(_loadPhase == web::PAGE_LOADED);
1764   _webStateImpl->GetRequestTracker()->FinishPageLoad(currentURL, loadSuccess);
1765   // Reset the navigation type to the default value.
1766   // Note: it is possible that the web view has already started loading the
1767   // next page when this is called. In that case the cache mode can leak to
1768   // (some of) the requests of the next page. It's expected to be an edge case,
1769   // but if it becomes a problem it should be possible to notice it afterwards
1770   // and react to it (by warning the user or reloading the page for example).
1771   _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1772   _webStateImpl->GetRequestTracker()->SetCacheModeFromUIThread(
1773       _webStateImpl->GetCacheMode());
1775   [self restoreStateFromHistory];
1776   _webStateImpl->OnPageLoaded(currentURL, loadSuccess);
1777   _webStateImpl->SetIsLoading(false);
1778   // Inform the embedder the load completed.
1779   [_delegate webDidFinishWithURL:currentURL loadSuccess:loadSuccess];
1782 - (void)finishHistoryNavigationFromEntry:(CRWSessionEntry*)fromEntry {
1783   [_delegate webWillFinishHistoryNavigationFromEntry:fromEntry];
1785   // If the current entry has a serialized state object, inject its state into
1786   // the web view.
1787   web::NavigationItemImpl* currentItem =
1788       self.currentSessionEntry.navigationItemImpl;
1789   NSString* state = currentItem->GetSerializedStateObject();
1790   if (state.length)
1791     [self setPushedOrReplacedURL:currentItem->GetURL() stateObject:state];
1792   // Only load the new URL if the current entry was not created by a JavaScript
1793   // window.history.pushState() call from |fromEntry|.
1794   if (![_webStateImpl->GetNavigationManagerImpl().GetSessionController()
1795           isPushStateNavigationBetweenEntry:fromEntry
1796                                    andEntry:self.currentSessionEntry]) {
1797     web::NavigationItem* currentItem =
1798         _webStateImpl->GetNavigationManagerImpl().GetVisibleItem();
1799     GURL endURL = [self URLForHistoryNavigationFromItem:fromEntry.navigationItem
1800                                                  toItem:currentItem];
1801     ui::PageTransition transition = ui::PageTransitionFromInt(
1802         ui::PAGE_TRANSITION_RELOAD | ui::PAGE_TRANSITION_FORWARD_BACK);
1804     web::WebLoadParams params(endURL);
1805     if (currentItem) {
1806       params.referrer = currentItem->GetReferrer();
1807     }
1808     params.transition_type = transition;
1809     [self loadWithParams:params];
1810   }
1813 - (GURL)URLForHistoryNavigationFromItem:(web::NavigationItem*)fromItem
1814                                  toItem:(web::NavigationItem*)toItem {
1815   const GURL& startURL = fromItem->GetURL();
1816   const GURL& endURL = toItem->GetURL();
1818   // Check the state of the fragments on both URLs (aka, is there a '#' in the
1819   // url or not).
1820   if (!startURL.has_ref() || endURL.has_ref()) {
1821     return endURL;
1822   }
1824   // startURL contains a fragment and endURL doesn't. Remove the fragment from
1825   // startURL and compare the resulting string to endURL. If they are equal, add
1826   // # to endURL to cause a hashchange event.
1827   GURL hashless = web::GURLByRemovingRefFromGURL(startURL);
1829   if (hashless != endURL)
1830     return endURL;
1832   url::StringPieceReplacements<std::string> emptyRef;
1833   emptyRef.SetRefStr("");
1834   GURL newEndURL = endURL.ReplaceComponents(emptyRef);
1835   toItem->SetURL(newEndURL);
1836   return newEndURL;
1839 - (void)evaluateJavaScript:(NSString*)script
1840          JSONResultHandler:
1841              (void (^)(scoped_ptr<base::Value>, NSError*))handler {
1842   [self evaluateJavaScript:script
1843        stringResultHandler:^(NSString* stringResult, NSError* error) {
1844          DCHECK(stringResult || error);
1845          if (handler) {
1846            scoped_ptr<base::Value> result(
1847                base::JSONReader::Read(base::SysNSStringToUTF8(stringResult)));
1848            handler(result.Pass(), error);
1849          }
1850        }];
1853 - (void)addGestureRecognizerToWebView:(UIGestureRecognizer*)recognizer {
1854   if ([_gestureRecognizers containsObject:recognizer])
1855     return;
1857   [self.webView addGestureRecognizer:recognizer];
1858   [_gestureRecognizers addObject:recognizer];
1861 - (void)removeGestureRecognizerFromWebView:(UIGestureRecognizer*)recognizer {
1862   if (![_gestureRecognizers containsObject:recognizer])
1863     return;
1865   [self.webView removeGestureRecognizer:recognizer];
1866   [_gestureRecognizers removeObject:recognizer];
1869 - (void)addToolbarViewToWebView:(UIView*)toolbarView {
1870   DCHECK(toolbarView);
1871   if ([_webViewToolbars containsObject:toolbarView])
1872     return;
1873   [_webViewToolbars addObject:toolbarView];
1874   if (self.webView)
1875     [self.containerView addToolbar:toolbarView];
1878 - (void)removeToolbarViewFromWebView:(UIView*)toolbarView {
1879   if (![_webViewToolbars containsObject:toolbarView])
1880     return;
1881   [_webViewToolbars removeObject:toolbarView];
1882   if (self.webView)
1883     [self.containerView removeToolbar:toolbarView];
1886 - (CRWJSInjectionReceiver*)jsInjectionReceiver {
1887   return _jsInjectionReceiver;
1890 - (BOOL)shouldClosePageOnNativeApplicationLoad {
1891   // The page should be closed if it was initiated by the DOM and there has been
1892   // no user interaction with the page since the web view was created.
1893   return self.sessionController.openedByDOM &&
1894          !_userInteractedWithWebController;
1897 - (BOOL)isBeingDestroyed {
1898   return _isBeingDestroyed;
1901 - (BOOL)isHalted {
1902   return _isHalted;
1905 - (web::ReferrerPolicy)referrerPolicyFromString:(const std::string&)policy {
1906   // TODO(stuartmorgan): Remove this temporary bridge to the helper function
1907   // once the referrer handling moves into the subclasses.
1908   return web::ReferrerPolicyFromString(policy);
1911 #pragma mark -
1912 #pragma mark CRWJSInjectionEvaluator Methods
1914 - (void)evaluateJavaScript:(NSString*)script
1915        stringResultHandler:(web::JavaScriptCompletion)handler {
1916   // Subclasses must implement this method.
1917   NOTREACHED();
1920 - (BOOL)scriptHasBeenInjectedForClass:(Class)jsInjectionManagerClass
1921                        presenceBeacon:(NSString*)beacon {
1922   // Subclasses must implement this method.
1923   NOTREACHED();
1924   return NO;
1927 - (void)injectScript:(NSString*)script forClass:(Class)JSInjectionManagerClass {
1928   // Make sure that CRWJSEarlyScriptManager has been injected.
1929   BOOL ealyScriptInjected =
1930       [self scriptHasBeenInjectedForClass:[CRWJSEarlyScriptManager class]
1931                            presenceBeacon:[_earlyScriptManager presenceBeacon]];
1932   if (!ealyScriptInjected &&
1933       JSInjectionManagerClass != [CRWJSEarlyScriptManager class]) {
1934     [_earlyScriptManager inject];
1935   }
1938 - (web::WebViewType)webViewType {
1939   // Subclasses must implement this method.
1940   NOTREACHED();
1941   return web::UI_WEB_VIEW_TYPE;
1944 #pragma mark -
1946 - (void)evaluateUserJavaScript:(NSString*)script {
1947   // Subclasses must implement this method.
1948   NOTREACHED();
1951 - (void)didFinishNavigation {
1952   // This can be called at multiple times after the document has loaded. Do
1953   // nothing if the document has already loaded.
1954   if (_loadPhase == web::PAGE_LOADED)
1955     return;
1956   [self loadCompleteWithSuccess:YES];
1959 - (BOOL)respondToMessage:(base::DictionaryValue*)message
1960        userIsInteracting:(BOOL)userIsInteracting
1961                originURL:(const GURL&)originURL {
1962   std::string command;
1963   if (!message->GetString("command", &command)) {
1964     DLOG(WARNING) << "JS message parameter not found: command";
1965     return NO;
1966   }
1968   SEL handler = [self selectorToHandleJavaScriptCommand:command];
1969   if (!handler) {
1970     if (!self.webStateImpl->OnScriptCommandReceived(
1971             command, *message, originURL, userIsInteracting)) {
1972       // Message was either unexpected or not correctly handled.
1973       // Page is reset as a precaution.
1974       DLOG(WARNING) << "Unexpected message received: " << command;
1975       return NO;
1976     }
1977     return YES;
1978   }
1980   typedef BOOL (*HandlerType)(id, SEL, base::DictionaryValue*, NSDictionary*);
1981   HandlerType handlerImplementation =
1982       reinterpret_cast<HandlerType>([self methodForSelector:handler]);
1983   DCHECK(handlerImplementation);
1984   NSMutableDictionary* context =
1985       [NSMutableDictionary dictionaryWithObject:@(userIsInteracting)
1986                                          forKey:web::kUserIsInteractingKey];
1987   NSURL* originNSURL = net::NSURLWithGURL(originURL);
1988   if (originNSURL)
1989     context[web::kOriginURLKey] = originNSURL;
1990   return handlerImplementation(self, handler, message, context);
1993 - (SEL)selectorToHandleJavaScriptCommand:(const std::string&)command {
1994   static std::map<std::string, SEL>* handlers = nullptr;
1995   static dispatch_once_t onceToken;
1996   dispatch_once(&onceToken, ^{
1997     handlers = new std::map<std::string, SEL>();
1998     (*handlers)["addPluginPlaceholders"] =
1999         @selector(handleAddPluginPlaceholdersMessage:context:);
2000     (*handlers)["chrome.send"] = @selector(handleChromeSendMessage:context:);
2001     (*handlers)["console"] = @selector(handleConsoleMessage:context:);
2002     (*handlers)["dialog.suppressed"] =
2003         @selector(handleDialogSuppressedMessage:context:);
2004     (*handlers)["dialog.willShow"] =
2005         @selector(handleDialogWillShowMessage:context:);
2006     (*handlers)["document.favicons"] =
2007         @selector(handleDocumentFaviconsMessage:context:);
2008     (*handlers)["document.retitled"] =
2009         @selector(handleDocumentRetitledMessage:context:);
2010     (*handlers)["document.submit"] =
2011         @selector(handleDocumentSubmitMessage:context:);
2012     (*handlers)["externalRequest"] =
2013         @selector(handleExternalRequestMessage:context:);
2014     (*handlers)["form.activity"] =
2015         @selector(handleFormActivityMessage:context:);
2016     (*handlers)["navigator.credentials.request"] =
2017         @selector(handleCredentialsRequestedMessage:context:);
2018     (*handlers)["navigator.credentials.notifySignedIn"] =
2019         @selector(handleSignedInMessage:context:);
2020     (*handlers)["navigator.credentials.notifySignedOut"] =
2021         @selector(handleSignedOutMessage:context:);
2022     (*handlers)["navigator.credentials.notifyFailedSignIn"] =
2023         @selector(handleSignInFailedMessage:context:);
2024     (*handlers)["resetExternalRequest"] =
2025         @selector(handleResetExternalRequestMessage:context:);
2026     (*handlers)["window.close.self"] =
2027         @selector(handleWindowCloseSelfMessage:context:);
2028     (*handlers)["window.error"] = @selector(handleWindowErrorMessage:context:);
2029     (*handlers)["window.hashchange"] =
2030         @selector(handleWindowHashChangeMessage:context:);
2031     (*handlers)["window.history.back"] =
2032         @selector(handleWindowHistoryBackMessage:context:);
2033     (*handlers)["window.history.willChangeState"] =
2034         @selector(handleWindowHistoryWillChangeStateMessage:context:);
2035     (*handlers)["window.history.didPushState"] =
2036         @selector(handleWindowHistoryDidPushStateMessage:context:);
2037     (*handlers)["window.history.didReplaceState"] =
2038         @selector(handleWindowHistoryDidReplaceStateMessage:context:);
2039     (*handlers)["window.history.forward"] =
2040         @selector(handleWindowHistoryForwardMessage:context:);
2041     (*handlers)["window.history.go"] =
2042         @selector(handleWindowHistoryGoMessage:context:);
2043   });
2044   DCHECK(handlers);
2045   auto iter = handlers->find(command);
2046   return iter != handlers->end() ? iter->second : nullptr;
2049 #pragma mark -
2050 #pragma mark JavaScript message handlers
2052 - (BOOL)handleAddPluginPlaceholdersMessage:(base::DictionaryValue*)message
2053                                    context:(NSDictionary*)context {
2054   // Inject the script that adds the plugin placeholders.
2055   [[_jsInjectionReceiver
2056       instanceOfClass:[CRWJSPluginPlaceholderManager class]] inject];
2057   return YES;
2060 - (BOOL)handleChromeSendMessage:(base::DictionaryValue*)message
2061                         context:(NSDictionary*)context {
2062   if (_webStateImpl->HasWebUI()) {
2063     const GURL currentURL([self currentURL]);
2064     if (web::GetWebClient()->IsAppSpecificURL(currentURL)) {
2065       std::string messageContent;
2066       base::ListValue* arguments = nullptr;
2067       if (!message->GetString("message", &messageContent)) {
2068         DLOG(WARNING) << "JS message parameter not found: message";
2069         return NO;
2070       }
2071       if (!message->GetList("arguments", &arguments)) {
2072         DLOG(WARNING) << "JS message parameter not found: arguments";
2073         return NO;
2074       }
2075       _webStateImpl->OnScriptCommandReceived(
2076           messageContent, *message, currentURL,
2077           context[web::kUserIsInteractingKey]);
2078       _webStateImpl->ProcessWebUIMessage(currentURL, messageContent,
2079                                          *arguments);
2080       return YES;
2081     }
2082   }
2084   DLOG(WARNING)
2085       << "chrome.send message not handled because WebUI was not found.";
2086   return NO;
2089 - (BOOL)handleConsoleMessage:(base::DictionaryValue*)message
2090                      context:(NSDictionary*)context {
2091   // Do not log if JS logging is off.
2092   if (![[NSUserDefaults standardUserDefaults] boolForKey:web::kLogJavaScript]) {
2093     return YES;
2094   }
2096   std::string method;
2097   if (!message->GetString("method", &method)) {
2098     DLOG(WARNING) << "JS message parameter not found: method";
2099     return NO;
2100   }
2101   std::string consoleMessage;
2102   if (!message->GetString("message", &consoleMessage)) {
2103     DLOG(WARNING) << "JS message parameter not found: message";
2104     return NO;
2105   }
2106   std::string origin;
2107   if (!message->GetString("origin", &origin)) {
2108     DLOG(WARNING) << "JS message parameter not found: origin";
2109     return NO;
2110   }
2112   DVLOG(0) << origin << " [" << method << "] " << consoleMessage;
2113   return YES;
2116 - (BOOL)handleDialogSuppressedMessage:(base::DictionaryValue*)message
2117                               context:(NSDictionary*)context {
2118   if ([_delegate
2119           respondsToSelector:@selector(webControllerDidSuppressDialog:)]) {
2120     [_delegate webControllerDidSuppressDialog:self];
2121   }
2122   return YES;
2125 - (BOOL)handleDialogWillShowMessage:(base::DictionaryValue*)message
2126                             context:(NSDictionary*)context {
2127   if ([_delegate respondsToSelector:@selector(webControllerWillShowDialog:)]) {
2128     [_delegate webControllerWillShowDialog:self];
2129   }
2130   return YES;
2133 - (BOOL)handleDocumentFaviconsMessage:(base::DictionaryValue*)message
2134                               context:(NSDictionary*)context {
2135   base::ListValue* favicons = nullptr;
2136   if (!message->GetList("favicons", &favicons)) {
2137     DLOG(WARNING) << "JS message parameter not found: favicons";
2138     return NO;
2139   }
2140   std::vector<web::FaviconURL> urls;
2141   for (size_t fav_idx = 0; fav_idx != favicons->GetSize(); ++fav_idx) {
2142     base::DictionaryValue* favicon = nullptr;
2143     if (!favicons->GetDictionary(fav_idx, &favicon))
2144       return NO;
2145     std::string href;
2146     std::string rel;
2147     if (!favicon->GetString("href", &href)) {
2148       DLOG(WARNING) << "JS message parameter not found: href";
2149       return NO;
2150     }
2151     if (!favicon->GetString("rel", &rel)) {
2152       DLOG(WARNING) << "JS message parameter not found: rel";
2153       return NO;
2154     }
2155     web::FaviconURL::IconType icon_type = web::FaviconURL::FAVICON;
2156     if (rel == "apple-touch-icon")
2157       icon_type = web::FaviconURL::TOUCH_ICON;
2158     else if (rel == "apple-touch-icon-precomposed")
2159       icon_type = web::FaviconURL::TOUCH_PRECOMPOSED_ICON;
2160     urls.push_back(
2161         web::FaviconURL(GURL(href), icon_type, std::vector<gfx::Size>()));
2162   }
2163   if (!urls.empty())
2164     _webStateImpl->OnFaviconUrlUpdated(urls);
2165   return YES;
2168 - (BOOL)handleDocumentSubmitMessage:(base::DictionaryValue*)message
2169                             context:(NSDictionary*)context {
2170   std::string href;
2171   if (!message->GetString("href", &href)) {
2172     DLOG(WARNING) << "JS message parameter not found: href";
2173     return NO;
2174   }
2175   const GURL targetURL(href);
2176   const GURL currentURL([self currentURL]);
2177   bool targetsFrame = false;
2178   message->GetBoolean("targetsFrame", &targetsFrame);
2179   if (!targetsFrame && web::UrlHasWebScheme(targetURL)) {
2180     // The referrer is not known yet, and will be updated later.
2181     const web::Referrer emptyReferrer;
2182     [self registerLoadRequest:targetURL
2183                      referrer:emptyReferrer
2184                    transition:ui::PAGE_TRANSITION_FORM_SUBMIT];
2185   }
2186   std::string formName;
2187   message->GetString("formName", &formName);
2188   base::scoped_nsobject<NSSet> observers([_observers copy]);
2189   // We decide the form is user-submitted if the user has interacted with
2190   // the main page (using logic from the popup blocker), or if the keyboard
2191   // is visible.
2192   BOOL submittedByUser = [context[web::kUserIsInteractingKey] boolValue] ||
2193                          [_webViewProxy keyboardAccessory];
2194   _webStateImpl->OnDocumentSubmitted(formName, submittedByUser);
2195   return YES;
2198 - (BOOL)handleExternalRequestMessage:(base::DictionaryValue*)message
2199                              context:(NSDictionary*)context {
2200   std::string href;
2201   std::string target;
2202   std::string referrerPolicy;
2203   if (!message->GetString("href", &href)) {
2204     DLOG(WARNING) << "JS message parameter not found: href";
2205     return NO;
2206   }
2207   if (!message->GetString("target", &target)) {
2208     DLOG(WARNING) << "JS message parameter not found: target";
2209     return NO;
2210   }
2211   if (!message->GetString("referrerPolicy", &referrerPolicy)) {
2212     DLOG(WARNING) << "JS message parameter not found: referrerPolicy";
2213     return NO;
2214   }
2215   // Round-trip the href through NSURL; this URL will be compared as a
2216   // string against a UIWebView-provided NSURL later, and must match exactly
2217   // for the new window to trigger, so the escaping needs to be NSURL-style.
2218   // TODO(stuartmorgan): Comparing against a URL whose exact formatting we
2219   // don't control is fundamentally fragile; try to find another
2220   // way of handling this.
2221   DCHECK(context[web::kUserIsInteractingKey]);
2222   NSString* windowName =
2223       base::SysUTF8ToNSString(href + web::kWindowNameSeparator + target);
2224   _externalRequest.reset(new web::NewWindowInfo(
2225       net::GURLWithNSURL(net::NSURLWithGURL(GURL(href))), windowName,
2226       web::ReferrerPolicyFromString(referrerPolicy),
2227       [context[web::kUserIsInteractingKey] boolValue]));
2228   return YES;
2231 - (BOOL)handleFormActivityMessage:(base::DictionaryValue*)message
2232                           context:(NSDictionary*)context {
2233   std::string formName;
2234   std::string fieldName;
2235   std::string type;
2236   std::string value;
2237   int keyCode = web::WebStateObserver::kInvalidFormKeyCode;
2238   bool inputMissing = false;
2239   if (!message->GetString("formName", &formName) ||
2240       !message->GetString("fieldName", &fieldName) ||
2241       !message->GetString("type", &type) ||
2242       !message->GetString("value", &value)) {
2243     inputMissing = true;
2244   }
2246   if (!message->GetInteger("keyCode", &keyCode) || keyCode < 0)
2247     keyCode = web::WebStateObserver::kInvalidFormKeyCode;
2248   _webStateImpl->OnFormActivityRegistered(formName, fieldName, type, value,
2249                                           keyCode, inputMissing);
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)handleWindowHistoryWillChangeStateMessage:(base::DictionaryValue*)unused
2400                                           context:(NSDictionary*)unusedContext {
2401   // This dummy handler is a workaround for crbug.com/490673. Issue was
2402   // happening when two sequential calls of window.history.pushState were
2403   // performed by the page. In that case state was changed twice before
2404   // first change was reported to embedder (and first URL change was reported
2405   // incorrectly).
2407   // Using dummy handler for window.history.willChangeState message holds
2408   // second state change until the first change is reported, because messages
2409   // are queued. This is essentially a sleep, and not the real fix of the
2410   // problem. TODO(eugenebut): refactor handleWindowHistoryDidPushStateMessage:
2411   // to avoid this "sleep".
2412   return YES;
2415 - (BOOL)handleWindowHistoryDidPushStateMessage:(base::DictionaryValue*)message
2416                                        context:(NSDictionary*)context {
2417   std::string pageURL;
2418   std::string baseURL;
2419   if (!message->GetString("pageUrl", &pageURL) ||
2420       !message->GetString("baseUrl", &baseURL)) {
2421     DLOG(WARNING) << "JS message parameter not found: pageUrl or baseUrl";
2422     return NO;
2423   }
2424   GURL pushURL = web::history_state_util::GetHistoryStateChangeUrl(
2425       [self currentURL], GURL(baseURL), pageURL);
2426   // UIWebView seems to choke on unicode characters that haven't been
2427   // escaped; escape the URL now so the expected load URL is correct.
2428   pushURL = URLEscapedForHistory(pushURL);
2429   if (!pushURL.is_valid())
2430     return YES;
2431   const NavigationManagerImpl& navigationManager =
2432       _webStateImpl->GetNavigationManagerImpl();
2433   web::NavigationItem* navItem = [self currentNavItem];
2434   // PushState happened before first navigation entry or called right after
2435   // window.open when the url is empty.
2436   if (!navItem ||
2437       (navigationManager.GetEntryCount() <= 1 && navItem->GetURL().is_empty()))
2438     return YES;
2439   if (!web::history_state_util::IsHistoryStateChangeValid(navItem->GetURL(),
2440                                                           pushURL)) {
2441     // A redirect may have occurred just prior to the pushState. Check if
2442     // the URL needs to be updated.
2443     // TODO(bdibello): Investigate how the pushState() is handled before the
2444     // redirect and after core.js injection.
2445     [self checkForUnexpectedURLChange];
2446   }
2447   if (!web::history_state_util::IsHistoryStateChangeValid(
2448           [self currentNavItem]->GetURL(), pushURL)) {
2449     // If the current session entry URL origin still doesn't match pushURL's
2450     // origin, ignore the pushState. This can happen if a new URL is loaded
2451     // just before the pushState.
2452     return YES;
2453   }
2454   std::string stateObjectJSON;
2455   if (!message->GetString("stateObject", &stateObjectJSON)) {
2456     DLOG(WARNING) << "JS message parameter not found: stateObject";
2457     return NO;
2458   }
2459   NSString* stateObject = base::SysUTF8ToNSString(stateObjectJSON);
2460   _URLOnStartLoading = pushURL;
2461   _lastRegisteredRequestURL = pushURL;
2463   // If the user interacted with the page, categorize it as a link navigation.
2464   // If not, categorize it is a client redirect as it occurred without user
2465   // input and should not be added to the history stack.
2466   // TODO(ios): Improve transition detection.
2467   ui::PageTransition transition =
2468       [context[web::kUserIsInteractingKey] boolValue]
2469           ? ui::PAGE_TRANSITION_LINK
2470           : ui::PAGE_TRANSITION_CLIENT_REDIRECT;
2471   [self pushStateWithPageURL:pushURL
2472                  stateObject:stateObject
2473                   transition:transition];
2475   NSString* replaceWebViewJS =
2476       [self javascriptToReplaceWebViewURL:pushURL stateObjectJSON:stateObject];
2477   base::WeakNSObject<CRWWebController> weakSelf(self);
2478   [self evaluateJavaScript:replaceWebViewJS
2479        stringResultHandler:^(NSString*, NSError*) {
2480          if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
2481            return;
2482          base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
2483          [strongSelf optOutScrollsToTopForSubviews];
2484          // Notify the observers.
2485          strongSelf.get()->_webStateImpl->OnHistoryStateChanged();
2486          [strongSelf didFinishNavigation];
2487        }];
2488   return YES;
2491 - (BOOL)handleWindowHistoryDidReplaceStateMessage:
2492     (base::DictionaryValue*)message
2493                                           context:(NSDictionary*)context {
2494   std::string pageURL;
2495   std::string baseURL;
2496   if (!message->GetString("pageUrl", &pageURL) ||
2497       !message->GetString("baseUrl", &baseURL)) {
2498     DLOG(WARNING) << "JS message parameter not found: pageUrl or baseUrl";
2499     return NO;
2500   }
2501   GURL replaceURL = web::history_state_util::GetHistoryStateChangeUrl(
2502       [self currentURL], GURL(baseURL), pageURL);
2503   // UIWebView seems to choke on unicode characters that haven't been
2504   // escaped; escape the URL now so the expected load URL is correct.
2505   replaceURL = URLEscapedForHistory(replaceURL);
2506   if (!replaceURL.is_valid())
2507     return YES;
2508   const NavigationManagerImpl& navigationManager =
2509       _webStateImpl->GetNavigationManagerImpl();
2510   web::NavigationItem* navItem = [self currentNavItem];
2511   // ReplaceState happened before first navigation entry or called right
2512   // after window.open when the url is empty/not valid.
2513   if (!navItem ||
2514       (navigationManager.GetEntryCount() <= 1 && navItem->GetURL().is_empty()))
2515     return YES;
2516   if (!web::history_state_util::IsHistoryStateChangeValid(navItem->GetURL(),
2517                                                           replaceURL)) {
2518     // A redirect may have occurred just prior to the replaceState. Check if
2519     // the URL needs to be updated.
2520     [self checkForUnexpectedURLChange];
2521   }
2522   if (!web::history_state_util::IsHistoryStateChangeValid(
2523           [self currentNavItem]->GetURL(), replaceURL)) {
2524     // If the current session entry URL origin still doesn't match
2525     // replaceURL's origin, ignore the replaceState. This can happen if a
2526     // new URL is loaded just before the replaceState.
2527     return YES;
2528   }
2529   std::string stateObjectJSON;
2530   if (!message->GetString("stateObject", &stateObjectJSON)) {
2531     DLOG(WARNING) << "JS message parameter not found: stateObject";
2532     return NO;
2533   }
2534   NSString* stateObject = base::SysUTF8ToNSString(stateObjectJSON);
2535   _URLOnStartLoading = replaceURL;
2536   _lastRegisteredRequestURL = replaceURL;
2537   [self replaceStateWithPageURL:replaceURL stateObject:stateObject];
2538   NSString* replaceStateJS = [self javascriptToReplaceWebViewURL:replaceURL
2539                                                  stateObjectJSON:stateObject];
2540   base::WeakNSObject<CRWWebController> weakSelf(self);
2541   [self evaluateJavaScript:replaceStateJS
2542        stringResultHandler:^(NSString*, NSError*) {
2543          if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
2544            return;
2545          base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
2546          [strongSelf didFinishNavigation];
2547        }];
2548   return YES;
2551 #pragma mark -
2553 - (BOOL)wantsKeyboardShield {
2554   if ([self.nativeController
2555           respondsToSelector:@selector(wantsKeyboardShield)]) {
2556     return [self.nativeController wantsKeyboardShield];
2557   }
2558   return YES;
2561 - (BOOL)wantsLocationBarHintText {
2562   if ([self.nativeController
2563           respondsToSelector:@selector(wantsLocationBarHintText)]) {
2564     return [self.nativeController wantsLocationBarHintText];
2565   }
2566   return YES;
2569 // TODO(stuartmorgan): This method conflates document changes and URL changes;
2570 // we should be distinguishing better, and be clear about the expected
2571 // WebDelegate and WCO callbacks in each case.
2572 - (void)webPageChanged {
2573   DCHECK(_loadPhase == web::LOAD_REQUESTED);
2575   const GURL currentURL([self currentURL]);
2576   web::Referrer referrer = [self currentReferrer];
2577   // If no referrer was known in advance, record it now. (If there was one,
2578   // keep it since it will have a more accurate URL and policy than what can
2579   // be extracted from the landing page.)
2580   web::NavigationItem* currentItem = [self currentNavItem];
2581   if (!currentItem->GetReferrer().url.is_valid()) {
2582     currentItem->SetReferrer(referrer);
2583   }
2585   // TODO(stuartmorgan): This shouldn't be called for hash state or
2586   // push/replaceState.
2587   [self resetDocumentSpecificState];
2589   [self didStartLoadingURL:currentURL updateHistory:YES];
2592 - (void)resetDocumentSpecificState {
2593   _lastUserInteraction.reset();
2594   _clickInProgress = NO;
2595   _lastSeenWindowID.reset([[_windowIDJSManager windowId] copy]);
2598 - (void)didStartLoadingURL:(const GURL&)url updateHistory:(BOOL)updateHistory {
2599   _loadPhase = web::PAGE_LOADING;
2600   _URLOnStartLoading = url;
2601   _displayStateOnStartLoading = self.pageDisplayState;
2603   _userInteractionRegistered = NO;
2604   _pageHasZoomed = NO;
2606   [[self sessionController] commitPendingEntry];
2607   _webStateImpl->GetRequestTracker()->StartPageLoad(
2608       url, [[self sessionController] currentEntry]);
2609   [_delegate webDidStartLoadingURL:url shouldUpdateHistory:updateHistory];
2612 - (BOOL)checkForUnexpectedURLChange {
2613   // Subclasses may override this method to check for and handle URL changes.
2614   return NO;
2617 - (void)wasShown {
2618   if ([self.nativeController respondsToSelector:@selector(wasShown)]) {
2619     [self.nativeController wasShown];
2620   }
2623 - (void)wasHidden {
2624   if (_isHalted)
2625     return;
2626   if ([self.nativeController respondsToSelector:@selector(wasHidden)]) {
2627     [self.nativeController wasHidden];
2628   }
2631 + (BOOL)webControllerCanShow:(const GURL&)url {
2632   return web::UrlHasWebScheme(url) ||
2633          web::GetWebClient()->IsAppSpecificURL(url) ||
2634          url.SchemeIs(url::kFileScheme) || url.SchemeIs(url::kAboutScheme);
2637 - (void)setUserInteractionRegistered:(BOOL)flag {
2638   _userInteractionRegistered = flag;
2641 - (BOOL)userInteractionRegistered {
2642   return _userInteractionRegistered;
2645 - (BOOL)useDesktopUserAgent {
2646   web::NavigationItem* item = [self currentNavItem];
2647   return item && item->IsOverridingUserAgent();
2650 - (void)cachePOSTDataForRequest:(NSURLRequest*)request
2651                  inSessionEntry:(CRWSessionEntry*)currentSessionEntry {
2652   NSUInteger maxPOSTDataSizeInBytes = 4096;
2653   NSString* cookieHeaderName = @"cookie";
2655   web::NavigationItemImpl* currentItem = currentSessionEntry.navigationItemImpl;
2656   DCHECK(currentItem);
2657   const bool shouldUpdateEntry =
2658       ui::PageTransitionCoreTypeIs(currentItem->GetTransitionType(),
2659                                    ui::PAGE_TRANSITION_FORM_SUBMIT) &&
2660       ![request HTTPBodyStream] &&  // Don't cache streams.
2661       !currentItem->HasPostData() &&
2662       currentItem->GetURL() == net::GURLWithNSURL([request URL]);
2663   const bool belowSizeCap =
2664       [[request HTTPBody] length] < maxPOSTDataSizeInBytes;
2665   DLOG_IF(WARNING, shouldUpdateEntry && !belowSizeCap)
2666       << "Data in POST request exceeds the size cap (" << maxPOSTDataSizeInBytes
2667       << " bytes), and will not be cached.";
2669   if (shouldUpdateEntry && belowSizeCap) {
2670     currentItem->SetPostData([request HTTPBody]);
2671     currentItem->ResetHttpRequestHeaders();
2672     currentItem->AddHttpRequestHeaders([request allHTTPHeaderFields]);
2673     // Don't cache the "Cookie" header.
2674     // According to NSURLRequest documentation, |-valueForHTTPHeaderField:| is
2675     // case insensitive, so it's enough to test the lower case only.
2676     if ([request valueForHTTPHeaderField:cookieHeaderName]) {
2677       // Case insensitive search in |headers|.
2678       NSSet* cookieKeys = [currentItem->GetHttpRequestHeaders()
2679           keysOfEntriesPassingTest:^(id key, id obj, BOOL* stop) {
2680             NSString* header = (NSString*)key;
2681             const BOOL found =
2682                 [header caseInsensitiveCompare:cookieHeaderName] ==
2683                 NSOrderedSame;
2684             *stop = found;
2685             return found;
2686           }];
2687       DCHECK_EQ(1u, [cookieKeys count]);
2688       currentItem->RemoveHttpRequestHeaderForKey([cookieKeys anyObject]);
2689     }
2690   }
2693 // TODO(stuartmorgan): This is mostly logic from the original UIWebView delegate
2694 // method, which provides less information than the WKWebView version. Audit
2695 // this for things that should be handled in the subclass instead.
2696 - (BOOL)shouldAllowLoadWithRequest:(NSURLRequest*)request
2697                        targetFrame:(const web::FrameInfo*)targetFrame
2698                        isLinkClick:(BOOL)isLinkClick {
2699   GURL requestURL = net::GURLWithNSURL(request.URL);
2701   // Check if the request should be delayed.
2702   if (_externalRequest && _externalRequest->url == requestURL) {
2703     // Links that can't be shown in a tab by Chrome but can be handled by
2704     // external apps (e.g. tel:, mailto:) are opened directly despite the target
2705     // attribute on the link. We don't open a new tab for them because Mobile
2706     // Safari doesn't do that (and sites are expecting us to do the same) and
2707     // also because there would be nothing shown in that new tab; it would
2708     // remain on about:blank (see crbug.com/240178)
2709     if ([CRWWebController webControllerCanShow:requestURL] ||
2710         ![_delegate openExternalURL:requestURL]) {
2711       web::NewWindowInfo windowInfo = *_externalRequest;
2712       dispatch_async(dispatch_get_main_queue(), ^{
2713         [self openPopupWithInfo:windowInfo];
2714       });
2715     }
2716     _externalRequest.reset();
2717     return NO;
2718   }
2720   BOOL shouldCheckNativeApp = [self shouldClosePageOnNativeApplicationLoad];
2722   // Check if the link navigation leads to a launch of an external app.
2723   // TODO(shreyasv): Change this such that handling/stealing of link navigations
2724   // is delegated to the WebDelegate and the logic around external app launching
2725   // is moved there as well.
2726   if (shouldCheckNativeApp || isLinkClick) {
2727     // Check If the URL is handled by a native app.
2728     if ([self urlTriggersNativeAppLaunch:requestURL
2729                                sourceURL:[self currentNavigationURL]]) {
2730       // External app has been launched successfully. Stop the current page
2731       // load operation (e.g. notifying all observers) and record the URL so
2732       // that errors reported following the 'NO' reply can be safely ignored.
2733       if ([self shouldClosePageOnNativeApplicationLoad])
2734         [_delegate webPageOrderedClose];
2735       [self abortLoad];
2736       [_openedApplicationURL addObject:request.URL];
2737       return NO;
2738     }
2739   }
2741   // The WebDelegate may instruct the CRWWebController to stop loading, and
2742   // instead instruct the next page to be loaded in an animation.
2743   GURL mainDocumentURL = net::GURLWithNSURL(request.mainDocumentURL);
2744   DCHECK(self.webView);
2745   if (![self shouldOpenURL:requestURL
2746            mainDocumentURL:mainDocumentURL
2747                linkClicked:isLinkClick]) {
2748     return NO;
2749   }
2751   // If the URL doesn't look like one we can show, try to open the link with an
2752   // external application.
2753   // TODO(droger):  Check transition type before opening an external
2754   // application? For example, only allow it for TYPED and LINK transitions.
2755   if (![CRWWebController webControllerCanShow:requestURL]) {
2756     if (![self shouldOpenExternalURLRequest:request targetFrame:targetFrame]) {
2757       return NO;
2758     }
2760     // Abort load if navigation is believed to be happening on the main frame.
2761     if ([self isPutativeMainFrameRequest:request targetFrame:targetFrame])
2762       [self abortLoad];
2764     if ([_delegate openExternalURL:requestURL]) {
2765       // Record the URL so that errors reported following the 'NO' reply can be
2766       // safely ignored.
2767       [_openedApplicationURL addObject:request.URL];
2768       if ([self shouldClosePageOnNativeApplicationLoad])
2769         [_delegate webPageOrderedClose];
2770     }
2771     return NO;
2772   }
2774   if ([[request HTTPMethod] isEqualToString:@"POST"]) {
2775     [self cachePOSTDataForRequest:request
2776                    inSessionEntry:[self currentSessionEntry]];
2777   }
2779   return YES;
2782 - (void)restoreStateAfterURLRejection {
2783   [[self sessionController] discardNonCommittedEntries];
2785   // Re-register the user agent, because UIWebView will sometimes try to read
2786   // the agent again from a saved search result page in which no other page has
2787   // yet been loaded. See crbug.com/260370.
2788   [self registerUserAgent];
2790   // Reset |_lastRegisteredRequestURL| so that it reflects the URL from before
2791   // the load was rejected. This value may be out of sync because
2792   // |_lastRegisteredRequestURL| may have already been updated before the load
2793   // was rejected.
2794   _lastRegisteredRequestURL = [self currentURL];
2795   _loadPhase = web::PAGE_LOADING;
2796   [self didFinishNavigation];
2799 - (void)handleLoadError:(NSError*)error inMainFrame:(BOOL)inMainFrame {
2800   if ([error code] == NSURLErrorUnsupportedURL)
2801     return;
2802   // In cases where a Plug-in handles the load do not take any further action.
2803   if ([error.domain isEqual:base::SysUTF8ToNSString(web::kWebKitErrorDomain)] &&
2804       (error.code == web::kWebKitErrorPlugInLoadFailed ||
2805        error.code == web::kWebKitErrorCannotShowUrl))
2806     return;
2808   // Continue processing only if the error is on the main request or is the
2809   // result of a user interaction.
2810   NSDictionary* userInfo = [error userInfo];
2811   // |userinfo| contains the request creation date as a NSDate.
2812   NSTimeInterval requestCreationDate =
2813       [[userInfo objectForKey:@"CreationDate"] timeIntervalSinceReferenceDate];
2814   bool userInteracted = false;
2815   if (requestCreationDate != 0.0 && _lastUserInteraction) {
2816     NSTimeInterval timeSinceInteraction =
2817         requestCreationDate - _lastUserInteraction->time;
2818     // The error is considered to be the result of a user interaction if any
2819     // interaction happened just before the request was made.
2820     // TODO(droger): If the user interacted with the page after the request was
2821     // made (i.e. creationTimeSinceLastInteraction < 0), then
2822     // |_lastUserInteraction| has been overridden. The current behavior is to
2823     // discard the interstitial in that case. A better decision could be made if
2824     // we had a history of all the user interactions instead of just the last
2825     // one.
2826     userInteracted =
2827         timeSinceInteraction < kMaximumDelayForUserInteractionInSeconds &&
2828         _lastUserInteraction->time > _lastTransferTimeInSeconds &&
2829         timeSinceInteraction >= 0.0;
2830   } else {
2831     // If the error does not have timing information, check if the user
2832     // interacted with the page recently.
2833     userInteracted = [self userIsInteracting];
2834   }
2835   if (!inMainFrame && !userInteracted)
2836     return;
2838   NSURL* errorURL = [NSURL
2839       URLWithString:[userInfo objectForKey:NSURLErrorFailingURLStringErrorKey]];
2840   const GURL errorGURL = net::GURLWithNSURL(errorURL);
2842   // Handles Frame Load Interrupted errors from WebView.
2843   if ([error.domain isEqual:base::SysUTF8ToNSString(web::kWebKitErrorDomain)] &&
2844       error.code == web::kWebKitErrorFrameLoadInterruptedByPolicyChange) {
2845     // See if the delegate wants to handle this case.
2846     if (errorGURL.is_valid() &&
2847         [_delegate
2848             respondsToSelector:@selector(
2849                                    controllerForUnhandledContentAtURL:)]) {
2850       id<CRWNativeContent> controller =
2851           [_delegate controllerForUnhandledContentAtURL:errorGURL];
2852       if (controller) {
2853         [self loadCompleteWithSuccess:NO];
2854         [self removeWebViewAllowingCachedReconstruction:NO];
2855         [self setNativeController:controller];
2856         [self loadNativeViewWithSuccess:YES];
2857         return;
2858       }
2859     }
2861     // Ignore errors that originate from URLs that are opened in external apps.
2862     if ([_openedApplicationURL containsObject:errorURL])
2863       return;
2864     // Certain frame errors don't have URL information for some reason; for
2865     // those cases (so far the only known case is plugin content loaded directly
2866     // in a frame) just ignore the error. See crbug.com/414295
2867     if (!errorURL) {
2868       DCHECK(!inMainFrame);
2869       return;
2870     }
2871     // The wrapper error uses the URL of the error and not the requested URL
2872     // (which can be different in case of a redirect) to match desktop Chrome
2873     // behavior.
2874     NSError* wrapperError = [NSError
2875         errorWithDomain:[error domain]
2876                    code:[error code]
2877                userInfo:@{
2878                  NSURLErrorFailingURLStringErrorKey : [errorURL absoluteString],
2879                  NSUnderlyingErrorKey : error
2880                }];
2881     [self loadCompleteWithSuccess:NO];
2882     [self loadErrorInNativeView:wrapperError];
2883     return;
2884   }
2886   if ([error code] == NSURLErrorCancelled) {
2887     [self handleCancelledError:error];
2888     // NSURLErrorCancelled errors that aren't handled by aborting the load will
2889     // automatically be retried by the web view, so early return in this case.
2890     return;
2891   }
2893   [self loadCompleteWithSuccess:NO];
2894   [self loadErrorInNativeView:error];
2897 - (void)handleCancelledError:(NSError*)cancelledError {
2898   // Subclasses must implement this method.
2899   NOTREACHED();
2902 #pragma mark -
2903 #pragma mark WebUI
2905 - (void)createWebUIForURL:(const GURL&)URL {
2906   _webStateImpl->CreateWebUI(URL);
2909 - (void)clearWebUI {
2910   _webStateImpl->ClearWebUI();
2913 #pragma mark -
2914 #pragma mark UIGestureRecognizerDelegate
2916 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2917     shouldRecognizeSimultaneouslyWithGestureRecognizer:
2918         (UIGestureRecognizer*)otherGestureRecognizer {
2919   // Allows the custom UILongPressGestureRecognizer to fire simultaneously with
2920   // other recognizers.
2921   return YES;
2924 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2925        shouldReceiveTouch:(UITouch*)touch {
2926   // Expect only _contextMenuRecognizer.
2927   DCHECK([gestureRecognizer isEqual:_contextMenuRecognizer]);
2928   if (![self supportsCustomContextMenu]) {
2929     // Fetching context menu info is not a free operation, early return if a
2930     // context menu should not be shown.
2931     return YES;
2932   }
2934   // This is custom long press gesture recognizer. By the time the gesture is
2935   // recognized the web controller needs to know if there is a link under the
2936   // touch. If there a link, the web controller will reject system's context
2937   // menu and show another one. If for some reason context menu info is not
2938   // fetched - system context menu will be shown.
2939   [self setDOMElementForLastTouch:nullptr];
2940   base::WeakNSObject<CRWWebController> weakSelf(self);
2941   [self fetchDOMElementAtPoint:[touch locationInView:self.webView]
2942              completionHandler:^(scoped_ptr<base::DictionaryValue> element) {
2943                [weakSelf setDOMElementForLastTouch:element.Pass()];
2944              }];
2945   return YES;
2948 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer {
2949   // Expect only _contextMenuRecognizer.
2950   DCHECK([gestureRecognizer isEqual:_contextMenuRecognizer]);
2951   if (!self.webView || ![self supportsCustomContextMenu]) {
2952     // Show the context menu iff currently displaying a web view.
2953     // Do nothing for native views.
2954     return NO;
2955   }
2957   UMA_HISTOGRAM_BOOLEAN("WebController.FetchContextMenuInfoAsyncSucceeded",
2958                         _DOMElementForLastTouch);
2960   return _DOMElementForLastTouch && !_DOMElementForLastTouch->empty();
2963 #pragma mark -
2964 #pragma mark CRWRequestTrackerDelegate
2966 - (BOOL)isForStaticFileRequests {
2967   return NO;
2970 - (void)updatedSSLStatus:(const web::SSLStatus&)sslStatus
2971               forPageUrl:(const GURL&)url
2972                 userInfo:(id)userInfo {
2973   // |userInfo| is a CRWSessionEntry.
2974   web::NavigationItem* item =
2975       [static_cast<CRWSessionEntry*>(userInfo) navigationItem];
2976   if (!item)
2977     return;  // This is a request update for an entry that no longer exists.
2979   // This condition happens infrequently when a page load is misinterpreted as
2980   // a resource load from a previous page. This can happen when moving quickly
2981   // back and forth through history, the notifications from the web view on the
2982   // UI thread and the one from the requests at the net layer may get out of
2983   // sync. This catches this case and prevent updating an entry with the wrong
2984   // SSL data.
2985   if (item->GetURL().GetOrigin() != url.GetOrigin())
2986     return;
2988   if (item->GetSSL().Equals(sslStatus))
2989     return;  // No need to update with the same data.
2991   item->GetSSL() = sslStatus;
2993   // Notify the UI it needs to refresh if the updated entry is the current
2994   // entry.
2995   if (userInfo == self.currentSessionEntry) {
2996     [self didUpdateSSLStatusForCurrentNavigationItem];
2997   }
3000 - (void)handleResponseHeaders:(net::HttpResponseHeaders*)headers
3001                    requestUrl:(const GURL&)requestUrl {
3002   _webStateImpl->OnHttpResponseHeadersReceived(headers, requestUrl);
3005 - (void)presentSSLError:(const net::SSLInfo&)info
3006            forSSLStatus:(const web::SSLStatus&)status
3007                   onUrl:(const GURL&)url
3008             recoverable:(BOOL)recoverable
3009                callback:(SSLErrorCallback)shouldContinue {
3010   DCHECK(_delegate);
3011   DCHECK_EQ(url, [self currentNavigationURL]);
3012   [_delegate presentSSLError:info
3013                 forSSLStatus:status
3014                  recoverable:recoverable
3015                     callback:^(BOOL proceed) {
3016                       if (proceed) {
3017                         // The interstitial will be removed during reload.
3018                         [self loadCurrentURL];
3019                       }
3020                       if (shouldContinue) {
3021                         shouldContinue(proceed);
3022                       }
3023                     }];
3024   DCHECK([self currentNavItem]);
3025   [self currentNavItem]->SetUnsafe(true);
3028 - (void)updatedProgress:(float)progress {
3029   if ([_delegate
3030           respondsToSelector:@selector(webController:didUpdateProgress:)]) {
3031     [_delegate webController:self didUpdateProgress:progress];
3032   }
3035 - (void)certificateUsed:(net::X509Certificate*)certificate
3036                 forHost:(const std::string&)host
3037                  status:(net::CertStatus)status {
3038   [[[self sessionController] sessionCertificatePolicyManager]
3039       registerAllowedCertificate:certificate
3040                          forHost:host
3041                           status:status];
3044 - (void)clearCertificates {
3045   [[[self sessionController] sessionCertificatePolicyManager]
3046       clearCertificates];
3049 #pragma mark -
3050 #pragma mark Popup handling
3052 - (void)openPopupWithInfo:(const web::NewWindowInfo&)windowInfo {
3053   const GURL url(windowInfo.url);
3054   const GURL currentURL([self currentNavigationURL]);
3055   NSString* windowName = windowInfo.window_name.get();
3056   web::Referrer referrer(currentURL, windowInfo.referrer_policy);
3057   base::WeakNSObject<CRWWebController> weakSelf(self);
3058   void (^showPopupHandler)() = ^{
3059     CRWWebController* child = [[weakSelf delegate] webPageOrderedOpen:url
3060                                                              referrer:referrer
3061                                                            windowName:windowName
3062                                                          inBackground:NO];
3063     DCHECK(!child || child.sessionController.openedByDOM);
3064   };
3066   BOOL showPopup = windowInfo.user_is_interacting ||
3067                    (![self shouldBlockPopupWithURL:url sourceURL:currentURL]);
3068   if (showPopup) {
3069     showPopupHandler();
3070   } else if ([_delegate
3071                  respondsToSelector:@selector(webController:didBlockPopup:)]) {
3072     web::BlockedPopupInfo blockedPopupInfo(url, referrer, windowName,
3073                                            showPopupHandler);
3074     [_delegate webController:self didBlockPopup:blockedPopupInfo];
3075   }
3078 #pragma mark -
3079 #pragma mark TouchTracking
3081 - (void)touched:(BOOL)touched {
3082   _clickInProgress = touched;
3083   if (touched) {
3084     _userInteractionRegistered = YES;
3085     _userInteractedWithWebController = YES;
3086     if (_isBeingDestroyed)
3087       return;
3088     const web::NavigationManagerImpl& navigationManager =
3089         self.webStateImpl->GetNavigationManagerImpl();
3090     GURL mainDocumentURL =
3091         navigationManager.GetEntryCount()
3092             ? navigationManager.GetLastCommittedItem()->GetURL()
3093             : [self currentURL];
3094     _lastUserInteraction.reset(new web::UserInteractionEvent(mainDocumentURL));
3095   }
3098 - (CRWTouchTrackingRecognizer*)touchTrackingRecognizer {
3099   if (!_touchTrackingRecognizer) {
3100     _touchTrackingRecognizer.reset(
3101         [[CRWTouchTrackingRecognizer alloc] initWithDelegate:self]);
3102   }
3103   return _touchTrackingRecognizer.get();
3106 - (BOOL)userIsInteracting {
3107   // If page transfer started after last click, user is deemed to be no longer
3108   // interacting.
3109   if (!_lastUserInteraction ||
3110       _lastTransferTimeInSeconds > _lastUserInteraction->time) {
3111     return NO;
3112   }
3113   return [self userClickedRecently];
3116 - (BOOL)userClickedRecently {
3117   if (!_lastUserInteraction)
3118     return NO;
3119   return _clickInProgress ||
3120          ((CFAbsoluteTimeGetCurrent() - _lastUserInteraction->time) <
3121           kMaximumDelayForUserInteractionInSeconds);
3124 #pragma mark Placeholder Overlay Methods
3126 - (void)addPlaceholderOverlay {
3127   if (!_overlayPreviewMode) {
3128     // Create |kSnapshotOverlayDelay| second timer to remove image with
3129     // transition.
3130     [self performSelector:@selector(removePlaceholderOverlay)
3131                withObject:nil
3132                afterDelay:kSnapshotOverlayDelay];
3133   }
3135   // Add overlay image.
3136   _placeholderOverlayView.reset([[UIImageView alloc] init]);
3137   CGRect frame = [self visibleFrame];
3138   [_placeholderOverlayView setFrame:frame];
3139   [_placeholderOverlayView
3140       setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
3141                           UIViewAutoresizingFlexibleHeight];
3142   [_placeholderOverlayView setContentMode:UIViewContentModeScaleAspectFill];
3143   [self.containerView addSubview:_placeholderOverlayView];
3145   id callback = ^(UIImage* image) {
3146     [_placeholderOverlayView setImage:image];
3147   };
3148   [_delegate webController:self retrievePlaceholderOverlayImage:callback];
3150   if (!_placeholderOverlayView.get().image) {
3151     // TODO(justincohen): This is just a blank white image. Consider fading in
3152     // the snapshot when it comes in instead.
3153     // TODO(shreyasv): This is just a blank white image. Consider adding an API
3154     // so that the delegate can return something immediately for the default
3155     // overlay image.
3156     _placeholderOverlayView.get().image = [[self class] defaultSnapshotImage];
3157   }
3160 - (void)removePlaceholderOverlay {
3161   if (!_placeholderOverlayView || _overlayPreviewMode)
3162     return;
3164   [NSObject cancelPreviousPerformRequestsWithTarget:self
3165                                            selector:@selector(removeOverlay)
3166                                              object:nil];
3167   // Remove overlay with transition.
3168   [UIView animateWithDuration:kSnapshotOverlayTransition
3169       animations:^{
3170         [_placeholderOverlayView setAlpha:0.0f];
3171       }
3172       completion:^(BOOL finished) {
3173         [_placeholderOverlayView removeFromSuperview];
3174         _placeholderOverlayView.reset();
3175       }];
3178 - (void)setOverlayPreviewMode:(BOOL)overlayPreviewMode {
3179   _overlayPreviewMode = overlayPreviewMode;
3181   // If we were showing the preview, remove it.
3182   if (!_overlayPreviewMode && _placeholderOverlayView) {
3183     [self resetContainerView];
3184     // Reset |_placeholderOverlayView| directly instead of calling
3185     // -removePlaceholderOverlay, which removes |_placeholderOverlayView| in an
3186     // animation.
3187     [_placeholderOverlayView removeFromSuperview];
3188     _placeholderOverlayView.reset();
3189     // There are cases when resetting the contentView, above, may happen after
3190     // the web view has been created. Re-add it here, rather than
3191     // relying on a subsequent call to loadCurrentURLInWebView.
3192     if (self.webView) {
3193       [[self view] addSubview:self.webView];
3194     }
3195   }
3198 - (void)internalSuppressDialogs:(BOOL)suppressFlag notify:(BOOL)notifyFlag {
3199   NSString* const kSetSuppressDialogs =
3200       [NSString stringWithFormat:@"__gCrWeb.setSuppressDialogs(%d, %d);",
3201                                  suppressFlag, notifyFlag];
3202   [self setSuppressDialogsWithHelperScript:kSetSuppressDialogs];
3205 - (void)setPageDialogOpenPolicy:(web::PageDialogOpenPolicy)policy {
3206   switch (policy) {
3207     case web::DIALOG_POLICY_ALLOW:
3208       [self setSuppressDialogs:NO notify:NO];
3209       return;
3210     case web::DIALOG_POLICY_NOTIFY_FIRST:
3211       [self setSuppressDialogs:NO notify:YES];
3212       return;
3213     case web::DIALOG_POLICY_SUPPRESS:
3214       [self setSuppressDialogs:YES notify:YES];
3215       return;
3216   }
3217   NOTREACHED();
3220 - (void)setSuppressDialogs:(BOOL)suppressFlag notify:(BOOL)notifyFlag {
3221   if (self.webView && [_earlyScriptManager hasBeenInjected]) {
3222     [self internalSuppressDialogs:suppressFlag notify:notifyFlag];
3223   } else {
3224     _setSuppressDialogsLater = suppressFlag;
3225     _setNotifyAboutDialogsLater = notifyFlag;
3226   }
3229 #pragma mark -
3230 #pragma mark Session Information
3232 - (CRWSessionController*)sessionController {
3233   return _webStateImpl
3234       ? _webStateImpl->GetNavigationManagerImpl().GetSessionController()
3235       : nil;
3238 - (CRWSessionEntry*)currentSessionEntry {
3239   return [[self sessionController] currentEntry];
3242 - (web::NavigationItem*)currentNavItem {
3243   // This goes through the legacy Session* interface rather than Navigation*
3244   // because it is itself a legacy method that should not exist, and this
3245   // avoids needing to add a GetActiveItem to NavigationManager. If/when this
3246   // method chain becomes a blocker to eliminating SessionController, the logic
3247   // can be moved here, using public NavigationManager getters. That's not
3248   // done now in order to avoid code duplication.
3249   return [[self currentSessionEntry] navigationItem];
3252 - (const GURL&)currentNavigationURL {
3253   // TODO(stuartmorgan): Fix the fact that this method doesn't have clear usage
3254   // delination that would allow changing to one of the non-deprecated URL
3255   // calls.
3256   web::NavigationItem* item = [self currentNavItem];
3257   return item ? item->GetVirtualURL() : GURL::EmptyGURL();
3260 - (ui::PageTransition)currentTransition {
3261   if ([self currentNavItem])
3262     return [self currentNavItem]->GetTransitionType();
3263   else
3264     return ui::PageTransitionFromInt(0);
3267 - (web::Referrer)currentSessionEntryReferrer {
3268   web::NavigationItem* currentItem = [self currentNavItem];
3269   return currentItem ? currentItem->GetReferrer() : web::Referrer();
3272 - (NSDictionary*)currentHTTPHeaders {
3273   DCHECK([self currentSessionEntry]);
3274   return [self currentSessionEntry].navigationItem->GetHttpRequestHeaders();
3277 #pragma mark -
3278 #pragma mark CRWWebViewScrollViewProxyObserver
3280 - (void)webViewScrollViewDidZoom:
3281         (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
3282   _pageHasZoomed = YES;
3285 #pragma mark -
3286 #pragma mark Page State
3288 - (void)recordStateInHistory {
3289   // Only record the state if:
3290   // - the current NavigationItem's URL matches the current URL, and
3291   // - the user has interacted with the page.
3292   CRWSessionEntry* current = [self currentSessionEntry];
3293   if (current && [current navigationItem]->GetURL() == [self currentURL] &&
3294       _userInteractionRegistered) {
3295     [current navigationItem]->SetPageDisplayState(self.pageDisplayState);
3296   }
3299 - (void)restoreStateFromHistory {
3300   CRWSessionEntry* current = [self currentSessionEntry];
3301   if ([current navigationItem])
3302     self.pageDisplayState = [current navigationItem]->GetPageDisplayState();
3305 - (web::PageDisplayState)pageDisplayState {
3306   web::PageDisplayState displayState;
3307   if (self.webView) {
3308     CGPoint scrollOffset = [self scrollPosition];
3309     displayState.scroll_state().set_offset_x(std::floor(scrollOffset.x));
3310     displayState.scroll_state().set_offset_y(std::floor(scrollOffset.y));
3311     UIScrollView* scrollView = self.webScrollView;
3312     displayState.zoom_state().set_minimum_zoom_scale(
3313         scrollView.minimumZoomScale);
3314     displayState.zoom_state().set_maximum_zoom_scale(
3315         scrollView.maximumZoomScale);
3316     displayState.zoom_state().set_zoom_scale(scrollView.zoomScale);
3317   } else {
3318     // TODO(kkhorimoto): Handle native views.
3319   }
3320   return displayState;
3323 - (void)setPageDisplayState:(web::PageDisplayState)displayState {
3324   if (!displayState.IsValid())
3325     return;
3326   if (self.webView) {
3327     // Page state is restored after a page load completes.  If the user has
3328     // scrolled or changed the zoom scale while the page is still loading, don't
3329     // restore any state since it will confuse the user.
3330     web::PageDisplayState currentPageDisplayState = self.pageDisplayState;
3331     if (currentPageDisplayState.scroll_state().offset_x() ==
3332             _displayStateOnStartLoading.scroll_state().offset_x() &&
3333         currentPageDisplayState.scroll_state().offset_y() ==
3334             _displayStateOnStartLoading.scroll_state().offset_y() &&
3335         !_pageHasZoomed) {
3336       [self applyPageDisplayState:displayState];
3337     }
3338   }
3341 - (void)orientationDidChange {
3342   // When rotating, the available zoom scale range may change, zoomScale's
3343   // percentage into this range should remain constant.  However, there are
3344   // two known bugs with respect to adjusting the zoomScale on rotation:
3345   // - WKWebView sometimes erroneously resets the scroll view's zoom scale to
3346   // an incorrect value ( rdar://20100815 ).
3347   // - After zooming occurs in a UIWebView that's displaying a page with a hard-
3348   // coded viewport width, the zoom will not be updated upon rotation
3349   // ( crbug.com/485055 ).
3350   if (!self.webView)
3351     return;
3352   web::NavigationItem* currentItem = self.currentNavItem;
3353   if (!currentItem)
3354     return;
3355   web::PageDisplayState displayState = currentItem->GetPageDisplayState();
3356   if (!displayState.IsValid())
3357     return;
3358   CGFloat zoomPercentage = (displayState.zoom_state().zoom_scale() -
3359                             displayState.zoom_state().minimum_zoom_scale()) /
3360                            displayState.zoom_state().GetMinMaxZoomDifference();
3361   displayState.zoom_state().set_minimum_zoom_scale(
3362       self.webScrollView.minimumZoomScale);
3363   displayState.zoom_state().set_maximum_zoom_scale(
3364       self.webScrollView.maximumZoomScale);
3365   displayState.zoom_state().set_zoom_scale(
3366       displayState.zoom_state().minimum_zoom_scale() +
3367       zoomPercentage * displayState.zoom_state().GetMinMaxZoomDifference());
3368   currentItem->SetPageDisplayState(displayState);
3369   [self applyPageDisplayState:currentItem->GetPageDisplayState()];
3372 - (void)applyPageDisplayState:(const web::PageDisplayState&)displayState {
3373   if (!displayState.IsValid())
3374     return;
3375   base::WeakNSObject<CRWWebController> weakSelf(self);
3376   web::PageDisplayState displayStateCopy = displayState;
3377   [self queryUserScalableProperty:^(BOOL isUserScalable) {
3378     base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
3379     [strongSelf applyPageDisplayState:displayStateCopy
3380                          userScalable:isUserScalable];
3381   }];
3384 - (void)applyPageDisplayState:(const web::PageDisplayState&)displayState
3385                  userScalable:(BOOL)isUserScalable {
3386   // Early return if |scrollState| doesn't match the current NavigationItem.
3387   // This can sometimes occur in tests, as navigation occurs programmatically
3388   // and |-applyPageScrollState:| is asynchronous.
3389   web::NavigationItem* currentItem = [self currentSessionEntry].navigationItem;
3390   if (currentItem && currentItem->GetPageDisplayState() != displayState)
3391     return;
3392   DCHECK(displayState.IsValid());
3393   if (isUserScalable) {
3394     [self prepareToApplyWebViewScrollZoomScale];
3395     [self applyWebViewScrollZoomScaleFromZoomState:displayState.zoom_state()];
3396     [self finishApplyingWebViewScrollZoomScale];
3397   }
3398   [self applyWebViewScrollOffsetFromScrollState:displayState.scroll_state()];
3401 - (void)prepareToApplyWebViewScrollZoomScale {
3402   id webView = self.webView;
3403   if (![webView respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
3404     return;
3405   }
3407   UIView* contentView = [webView viewForZoomingInScrollView:self.webScrollView];
3409   if ([webView
3410           respondsToSelector:@selector(scrollViewWillBeginZooming:withView:)]) {
3411     [webView scrollViewWillBeginZooming:self.webScrollView
3412                                withView:contentView];
3413   }
3416 - (void)finishApplyingWebViewScrollZoomScale {
3417   id webView = self.webView;
3418   if ([webView respondsToSelector:@selector(scrollViewDidEndZooming:
3419                                                            withView:
3420                                                             atScale:)] &&
3421       [webView respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
3422     // This correctly sets the content's frame in the scroll view to
3423     // fit the web page and upscales the content so that it isn't
3424     // blurry.
3425     UIView* contentView =
3426         [webView viewForZoomingInScrollView:self.webScrollView];
3427     [webView scrollViewDidEndZooming:self.webScrollView
3428                             withView:contentView
3429                              atScale:self.webScrollView.zoomScale];
3430   }
3433 - (void)applyWebViewScrollZoomScaleFromZoomState:
3434     (const web::PageZoomState&)zoomState {
3435   // Subclasses must implement this method.
3436   NOTREACHED();
3439 - (void)applyWebViewScrollOffsetFromScrollState:
3440     (const web::PageScrollState&)scrollState {
3441   DCHECK(scrollState.IsValid());
3442   CGPoint scrollOffset =
3443       CGPointMake(scrollState.offset_x(), scrollState.offset_y());
3444   if (_loadPhase == web::PAGE_LOADED) {
3445     // If the page is loaded, update the scroll immediately.
3446     [self.webScrollView setContentOffset:scrollOffset];
3447   } else {
3448     // If the page isn't loaded, store the action to update the scroll
3449     // when the page finishes loading.
3450     base::WeakNSObject<UIScrollView> weakScrollView(self.webScrollView);
3451     base::scoped_nsprotocol<ProceduralBlock> action([^{
3452       [weakScrollView setContentOffset:scrollOffset];
3453     } copy]);
3454     [_pendingLoadCompleteActions addObject:action];
3455   }
3458 #pragma mark -
3459 #pragma mark Web Page Features
3461 // TODO(eugenebut): move JS parsing code to a separate file.
3462 - (void)queryUserScalableProperty:(void (^)(BOOL))responseHandler {
3463   NSString* const kViewPortContentQuery =
3464       @"var viewport = document.querySelector('meta[name=\"viewport\"]');"
3465        "viewport ? viewport.content : '';";
3466   [self evaluateJavaScript:kViewPortContentQuery
3467        stringResultHandler:^(NSString* viewPortContent, NSError* error) {
3468          responseHandler(
3469              GetUserScalablePropertyFromViewPortContent(viewPortContent));
3470        }];
3473 - (void)fetchWebPageWidthWithCompletionHandler:(void (^)(CGFloat))handler {
3474   if (!self.webView) {
3475     handler(0);
3476     return;
3477   }
3479   [self evaluateJavaScript:@"__gCrWeb.getPageWidth();"
3480        stringResultHandler:^(NSString* pageWidthAsString, NSError*) {
3481          handler([pageWidthAsString floatValue]);
3482        }];
3485 - (void)fetchDOMElementAtPoint:(CGPoint)point
3486              completionHandler:
3487                  (void (^)(scoped_ptr<base::DictionaryValue>))handler {
3488   DCHECK(handler);
3489   // Convert point into web page's coordinate system (which may be scaled and/or
3490   // scrolled).
3491   CGPoint scrollOffset = self.scrollPosition;
3492   CGFloat webViewContentWidth = self.webScrollView.contentSize.width;
3493   base::WeakNSObject<CRWWebController> weakSelf(self);
3494   [self fetchWebPageWidthWithCompletionHandler:^(CGFloat pageWidth) {
3495     CGFloat scale = pageWidth / webViewContentWidth;
3496     CGPoint localPoint = CGPointMake((point.x + scrollOffset.x) * scale,
3497                                      (point.y + scrollOffset.y) * scale);
3498     NSString* const kGetElementScript =
3499         [NSString stringWithFormat:@"__gCrWeb.getElementFromPoint(%g, %g);",
3500                                    localPoint.x, localPoint.y];
3501     [weakSelf evaluateJavaScript:kGetElementScript
3502                JSONResultHandler:^(scoped_ptr<base::Value> element, NSError*) {
3503                  // Release raw element and call handler with DictionaryValue.
3504                  scoped_ptr<base::DictionaryValue> elementAsDict;
3505                  if (element) {
3506                    base::DictionaryValue* elementAsDictPtr = nullptr;
3507                    element.release()->GetAsDictionary(&elementAsDictPtr);
3508                    // |rawElement| and |elementPtr| now point to the same
3509                    // memory. |elementPtr| ownership will be transferred to
3510                    // |element| scoped_ptr.
3511                    elementAsDict.reset(elementAsDictPtr);
3512                  }
3513                  handler(elementAsDict.Pass());
3514                }];
3515   }];
3518 - (NSDictionary*)contextMenuInfoForElement:(base::DictionaryValue*)element {
3519   DCHECK(element);
3520   NSMutableDictionary* mutableInfo = [NSMutableDictionary dictionary];
3521   NSString* title = nil;
3522   std::string href;
3523   if (element->GetString("href", &href)) {
3524     mutableInfo[web::kContextLinkURLString] = base::SysUTF8ToNSString(href);
3525     GURL linkURL(href);
3526     if (linkURL.SchemeIs(url::kJavaScriptScheme)) {
3527       title = @"JavaScript";
3528     } else {
3529       DCHECK(web::GetWebClient());
3530       const std::string& acceptLangs = web::GetWebClient()->GetAcceptLangs(
3531           self.webStateImpl->GetBrowserState());
3532       base::string16 urlText =
3533           url_formatter::FormatUrl(GURL(href), acceptLangs);
3534       title = base::SysUTF16ToNSString(urlText);
3535     }
3536   }
3537   std::string src;
3538   if (element->GetString("src", &src)) {
3539     mutableInfo[web::kContextImageURLString] = base::SysUTF8ToNSString(src);
3540     if (!title)
3541       title = base::SysUTF8ToNSString(src);
3542     if ([title hasPrefix:@"data:"])
3543       title = @"";
3544   }
3545   std::string titleAttribute;
3546   if (element->GetString("title", &titleAttribute))
3547     title = base::SysUTF8ToNSString(titleAttribute);
3548   std::string referrerPolicy;
3549   element->GetString("referrerPolicy", &referrerPolicy);
3550   mutableInfo[web::kContextLinkReferrerPolicy] =
3551       @([self referrerPolicyFromString:referrerPolicy]);
3552   if (title)
3553     mutableInfo[web::kContextTitle] = title;
3554   return [[mutableInfo copy] autorelease];
3557 #pragma mark -
3558 #pragma mark Fullscreen
3560 - (CGRect)visibleFrame {
3561   CGRect frame = self.containerView.bounds;
3562   CGFloat headerHeight = [self headerHeight];
3563   frame.origin.y = headerHeight;
3564   frame.size.height -= headerHeight;
3565   return frame;
3568 - (void)optOutScrollsToTopForSubviews {
3569   NSMutableArray* stack =
3570       [NSMutableArray arrayWithArray:[self.webScrollView subviews]];
3571   while (stack.count) {
3572     UIView* current = [stack lastObject];
3573     [stack removeLastObject];
3574     [stack addObjectsFromArray:[current subviews]];
3575     if ([current isKindOfClass:[UIScrollView class]])
3576       static_cast<UIScrollView*>(current).scrollsToTop = NO;
3577   }
3580 #pragma mark -
3581 #pragma mark WebDelegate Calls
3583 - (BOOL)shouldOpenURL:(const GURL&)url
3584       mainDocumentURL:(const GURL&)mainDocumentURL
3585           linkClicked:(BOOL)linkClicked {
3586   if (![_delegate respondsToSelector:@selector(webController:
3587                                                shouldOpenURL:
3588                                              mainDocumentURL:
3589                                                  linkClicked:)]) {
3590     return YES;
3591   }
3592   return [_delegate webController:self
3593                     shouldOpenURL:url
3594                   mainDocumentURL:mainDocumentURL
3595                       linkClicked:linkClicked];
3598 - (BOOL)isPutativeMainFrameRequest:(NSURLRequest*)request
3599                        targetFrame:(const web::FrameInfo*)targetFrame {
3600   // Determine whether the request is for the main frame using frame info if
3601   // available. In the case of missing frame info, the request is considered to
3602   // have originated from the main frame if either of the following is true:
3603   //   (a) The request's URL matches the request's main document URL
3604   //   (b) The request's URL resourceSpecifier matches the request's
3605   //       mainDocumentURL specifier, as is the case upon redirect from http
3606   //       App Store links to a URL with itms-apps scheme. This appears to be is
3607   //       App Store specific behavior, specially handled by web view.
3608   // Note: These heuristics are not guaranteed to be correct, and should not be
3609   // used for any decisions with security implications.
3610   return targetFrame
3611              ? targetFrame->is_main_frame
3612              : [request.URL isEqual:request.mainDocumentURL] ||
3613                    [request.URL.resourceSpecifier
3614                        isEqual:request.mainDocumentURL.resourceSpecifier];
3617 - (BOOL)shouldOpenExternalURLRequest:(NSURLRequest*)request
3618                          targetFrame:(const web::FrameInfo*)targetFrame {
3619   ExternalURLRequestStatus requestStatus = NUM_EXTERNAL_URL_REQUEST_STATUS;
3620   if ([self isPutativeMainFrameRequest:request targetFrame:targetFrame]) {
3621     requestStatus = MAIN_FRAME_ALLOWED;
3622   } else {
3623     // If the request's main document URL differs from that at the time of the
3624     // last user interaction, then the page has changed since the user last
3625     // interacted.
3626     BOOL userInteractedWithRequestMainFrame =
3627         [self userClickedRecently] &&
3628         net::GURLWithNSURL(request.mainDocumentURL) ==
3629             _lastUserInteraction->main_document_url;
3630     // Prevent subframe requests from opening an external URL if the user has
3631     // not interacted with the request's main frame.
3632     requestStatus = userInteractedWithRequestMainFrame ? SUBFRAME_ALLOWED
3633                                                        : SUBFRAME_BLOCKED;
3634   }
3635   DCHECK_NE(requestStatus, NUM_EXTERNAL_URL_REQUEST_STATUS);
3636   UMA_HISTOGRAM_ENUMERATION("WebController.ExternalURLRequestBlocking",
3637                             requestStatus, NUM_EXTERNAL_URL_REQUEST_STATUS);
3638   if (requestStatus == SUBFRAME_BLOCKED &&
3639       web::GetWebClient()->IsExternalURLBlockingEnabled()) {
3640     return NO;
3641   }
3643   GURL requestURL = net::GURLWithNSURL(request.URL);
3644   return [_delegate respondsToSelector:@selector(webController:
3645                                            shouldOpenExternalURL:)] &&
3646          [_delegate webController:self shouldOpenExternalURL:requestURL];
3649 - (BOOL)urlTriggersNativeAppLaunch:(const GURL&)url
3650                          sourceURL:(const GURL&)sourceURL {
3651   return
3652       [_delegate respondsToSelector:@selector(urlTriggersNativeAppLaunch:
3653                                                                sourceURL:)] &&
3654       [_delegate urlTriggersNativeAppLaunch:url sourceURL:sourceURL];
3657 - (CGFloat)headerHeight {
3658   if (![_delegate respondsToSelector:@selector(headerHeightForWebController:)])
3659     return 0.0f;
3660   return [_delegate headerHeightForWebController:self];
3663 - (BOOL)shouldBlockPopupWithURL:(const GURL&)popupURL
3664                       sourceURL:(const GURL&)sourceURL {
3665   if (![_delegate respondsToSelector:@selector(webController:
3666                                          shouldBlockPopupWithURL:
3667                                                        sourceURL:)]) {
3668     return NO;
3669   }
3670   return [_delegate webController:self
3671           shouldBlockPopupWithURL:popupURL
3672                         sourceURL:sourceURL];
3675 - (void)didUpdateHistoryStateWithPageURL:(const GURL&)url {
3676   _webStateImpl->GetRequestTracker()->HistoryStateChange(url);
3677   [_delegate webDidUpdateHistoryStateWithPageURL:url];
3680 - (void)didUpdateSSLStatusForCurrentNavigationItem {
3681   if ([_delegate respondsToSelector:
3682           @selector(
3683               webControllerDidUpdateSSLStatusForCurrentNavigationItem:)]) {
3684     [_delegate webControllerDidUpdateSSLStatusForCurrentNavigationItem:self];
3685   }
3688 #pragma mark CRWWebControllerScripting Methods
3690 - (void)loadHTML:(NSString*)html {
3691   [self loadHTML:html forURL:GURL(url::kAboutBlankURL)];
3694 - (void)loadHTMLForCurrentURL:(NSString*)html {
3695   [self loadHTML:html forURL:self.currentURL];
3698 - (void)loadHTML:(NSString*)html forURL:(const GURL&)url {
3699   // Remove the transient content view.
3700   [self clearTransientContentView];
3702   DLOG_IF(WARNING, !self.webView)
3703       << "self.webView null while trying to load HTML";
3704   _loadPhase = web::LOAD_REQUESTED;
3705   [self loadWebHTMLString:html forURL:url];
3708 - (void)loadHTML:(NSString*)HTML forAppSpecificURL:(const GURL&)URL {
3709   CHECK(web::GetWebClient()->IsAppSpecificURL(URL));
3710   [self loadHTML:HTML forURL:URL];
3713 - (void)stopLoading {
3714   web::RecordAction(UserMetricsAction("Stop"));
3715   // Discard the pending and transient entried before notifying the tab model
3716   // observers of the change via |-abortLoad|.
3717   [[self sessionController] discardNonCommittedEntries];
3718   [self abortLoad];
3719   // If discarding the non-committed entries results in an app-specific URL,
3720   // reload it in its native view.
3721   if (!self.nativeController &&
3722       [self shouldLoadURLInNativeView:[self currentNavigationURL]]) {
3723     [self loadCurrentURLInNativeView];
3724   }
3727 - (void)orderClose {
3728   if (self.sessionController.openedByDOM) {
3729     [_delegate webPageOrderedClose];
3730   }
3733 #pragma mark -
3734 #pragma mark Testing-Only Methods
3736 - (void)injectWebViewContentView:(id)webViewContentView {
3737   [self removeWebViewAllowingCachedReconstruction:NO];
3739   _lastRegisteredRequestURL = _defaultURL;
3740   [self.containerView displayWebViewContentView:webViewContentView];
3743 - (void)resetInjectedWebViewContentView {
3744   [self resetWebView];
3745   [self resetContainerView];
3748 - (void)addObserver:(id<CRWWebControllerObserver>)observer {
3749   DCHECK(observer);
3750   if (!_observers) {
3751     // We don't want our observer set to block dealloc on the observers. For the
3752     // observer container, make an object compatible with NSMutableSet that does
3753     // not perform retain or release on the contained objects (weak references).
3754     CFSetCallBacks callbacks =
3755         {0, NULL, NULL, CFCopyDescription, CFEqual, CFHash};
3756     _observers.reset(base::mac::CFToNSCast(
3757         CFSetCreateMutable(kCFAllocatorDefault, 1, &callbacks)));
3758   }
3759   DCHECK(![_observers containsObject:observer]);
3760   [_observers addObject:observer];
3761   _observerBridges.push_back(
3762       new web::WebControllerObserverBridge(observer, self.webStateImpl, self));
3764   if ([observer respondsToSelector:@selector(setWebViewProxy:controller:)])
3765     [observer setWebViewProxy:_webViewProxy controller:self];
3768 - (void)removeObserver:(id<CRWWebControllerObserver>)observer {
3769   // TODO(jimblackler): make _observers use NSMapTable. crbug.com/367992
3770   DCHECK([_observers containsObject:observer]);
3771   [_observers removeObject:observer];
3772   // Remove the associated WebControllerObserverBridge.
3773   auto it = std::find_if(_observerBridges.begin(), _observerBridges.end(),
3774                          [observer](web::WebControllerObserverBridge* bridge) {
3775                            return bridge->web_controller_observer() == observer;
3776                          });
3777   DCHECK(it != _observerBridges.end());
3778   _observerBridges.erase(it);
3781 - (NSUInteger)observerCount {
3782   DCHECK_EQ(_observerBridges.size(), [_observers count]);
3783   return [_observers count];
3786 - (NSString*)windowId {
3787   return [_windowIDJSManager windowId];
3790 - (void)setWindowId:(NSString*)windowId {
3791   return [_windowIDJSManager setWindowId:windowId];
3794 - (NSString*)lastSeenWindowID {
3795   return _lastSeenWindowID;
3798 - (void)setURLOnStartLoading:(const GURL&)url {
3799   _URLOnStartLoading = url;
3802 - (const GURL&)defaultURL {
3803   return _defaultURL;
3806 - (GURL)URLOnStartLoading {
3807   return _URLOnStartLoading;
3810 - (GURL)lastRegisteredRequestURL {
3811   return _lastRegisteredRequestURL;
3814 - (void)simulateLoadRequestWithURL:(const GURL&)URL {
3815   _lastRegisteredRequestURL = URL;
3816   _loadPhase = web::LOAD_REQUESTED;
3819 - (NSString*)externalRequestWindowName {
3820   if (!_externalRequest || !_externalRequest->window_name)
3821     return @"";
3822   return _externalRequest->window_name;
3825 - (void)resetExternalRequest {
3826   _externalRequest.reset();
3829 @end