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