Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / ios / web / web_state / ui / crw_web_controller.mm
blob974daaf2505cfaf19378d95d84c926d7e3a87a3d
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 // Cancels touch events for the given gesture recognizer.
135 void CancelTouches(UIGestureRecognizer* gesture_recognizer) {
136   if (gesture_recognizer.enabled) {
137     gesture_recognizer.enabled = NO;
138     gesture_recognizer.enabled = YES;
139   }
142 // Cancels all touch events for web view (long presses, tapping, scrolling).
143 void CancelAllTouches(UIScrollView* web_scroll_view) {
144   // Disable web view scrolling.
145   CancelTouches(web_scroll_view.panGestureRecognizer);
147   // All user gestures are handled by a subview of web view scroll view
148   // (UIWebBrowserView for UIWebView and WKContentView for WKWebView).
149   for (UIView* subview in web_scroll_view.subviews) {
150     for (UIGestureRecognizer* recognizer in subview.gestureRecognizers) {
151       CancelTouches(recognizer);
152     }
153   }
156 }  // namespace
158 @interface CRWWebController () <CRWNativeContentDelegate,
159                                 CRWWebViewScrollViewProxyObserver> {
160   base::WeakNSProtocol<id<CRWWebDelegate>> _delegate;
161   base::WeakNSProtocol<id<CRWWebUserInterfaceDelegate>> _UIDelegate;
162   base::WeakNSProtocol<id<CRWNativeContentProvider>> _nativeProvider;
163   base::WeakNSProtocol<id<CRWSwipeRecognizerProvider>> _swipeRecognizerProvider;
164   base::scoped_nsobject<CRWWebControllerContainerView> _containerView;
165   // The CRWWebViewProxy is the wrapper to give components access to the
166   // web view in a controlled and limited way.
167   base::scoped_nsobject<CRWWebViewProxyImpl> _webViewProxy;
168   // If |_contentView| contains a native view rather than a web view, this
169   // is its controller. If it's a web view, this is nil.
170   base::scoped_nsprotocol<id<CRWNativeContent>> _nativeController;
171   BOOL _isHalted;  // YES if halted. Halting happens prior to destruction.
172   BOOL _isBeingDestroyed;  // YES if in the process of closing.
173   // All CRWWebControllerObservers attached to the CRWWebController. A
174   // specially-constructed set is used that does not retain its elements.
175   base::scoped_nsobject<NSMutableSet> _observers;
176   // Each observer in |_observers| is associated with a
177   // WebControllerObserverBridge in order to listen from WebState callbacks.
178   // TODO(droger): Remove |_observerBridges| when all CRWWebControllerObservers
179   // are converted to WebStateObservers.
180   ScopedVector<web::WebControllerObserverBridge> _observerBridges;
181   // |windowId| that is saved when a page changes. Used to detect refreshes.
182   base::scoped_nsobject<NSString> _lastSeenWindowID;
183   // YES if a user interaction has been registered at any time once the page has
184   // loaded.
185   BOOL _userInteractionRegistered;
186   // Last URL change reported to webWill/DidStartLoadingURL. Used to detect page
187   // location changes (client redirects) in practice.
188   GURL _lastRegisteredRequestURL;
189   // Last URL change reported to webDidStartLoadingURL. Used to detect page
190   // location changes in practice.
191   GURL _URLOnStartLoading;
192   // Page loading phase.
193   web::LoadPhase _loadPhase;
194   // The web::PageDisplayState recorded when the page starts loading.
195   web::PageDisplayState _displayStateOnStartLoading;
196   // Whether or not the page has zoomed since the current navigation has been
197   // committed, either by user interaction or via |-restoreStateFromHistory|.
198   BOOL _pageHasZoomed;
199   // Actions to execute once the page load is complete.
200   base::scoped_nsobject<NSMutableArray> _pendingLoadCompleteActions;
201   // UIGestureRecognizers to add to the web view.
202   base::scoped_nsobject<NSMutableArray> _gestureRecognizers;
203   // Toolbars to add to the web view.
204   base::scoped_nsobject<NSMutableArray> _webViewToolbars;
205   // Flag to say if browsing is enabled.
206   BOOL _webUsageEnabled;
207   // Content view was reset due to low memory. Use the placeholder overlay on
208   // next creation.
209   BOOL _usePlaceholderOverlay;
210   // Overlay view used instead of webView.
211   base::scoped_nsobject<UIImageView> _placeholderOverlayView;
212   // The touch tracking recognizer allowing us to decide if a navigation is
213   // started by the user.
214   base::scoped_nsobject<CRWTouchTrackingRecognizer> _touchTrackingRecognizer;
215   // Long press recognizer that allows showing context menus.
216   base::scoped_nsobject<UILongPressGestureRecognizer> _contextMenuRecognizer;
217   // DOM element information for the point where the user made the last touch.
218   // Can be null if has not been calculated yet. Precalculation is necessary
219   // because retreiving DOM element relies on async API so element info can not
220   // be built on demand. May contain the following keys: "href", "src", "title",
221   // "referrerPolicy". All values are strings. Used for showing context menus.
222   scoped_ptr<base::DictionaryValue> _DOMElementForLastTouch;
223   // Whether a click is in progress.
224   BOOL _clickInProgress;
225   // Data on the recorded last user interaction.
226   scoped_ptr<web::UserInteractionEvent> _lastUserInteraction;
227   // The time of the last page transfer start, measured in seconds since Jan 1
228   // 2001.
229   CFAbsoluteTime _lastTransferTimeInSeconds;
230   // Default URL (about:blank).
231   GURL _defaultURL;
232   // Show overlay view, don't reload web page.
233   BOOL _overlayPreviewMode;
234   // If |YES|, call setSuppressDialogs when core.js is injected into the web
235   // view.
236   BOOL _setSuppressDialogsLater;
237   // If |YES|, call setSuppressDialogs when core.js is injected into the web
238   // view.
239   BOOL _setNotifyAboutDialogsLater;
240   // The URL of an expected future recreation of the |webView|. Valid
241   // only if the web view was discarded for non-user-visible reasons, such that
242   // if the next load request is for that URL, it should be treated as a
243   // reconstruction that should use cache aggressively.
244   GURL _expectedReconstructionURL;
246   scoped_ptr<web::NewWindowInfo> _externalRequest;
248   // The WebStateImpl instance associated with this CRWWebController.
249   scoped_ptr<WebStateImpl> _webStateImpl;
251   // A set of URLs opened in external applications; stored so that errors
252   // from the web view can be identified as resulting from these events.
253   base::scoped_nsobject<NSMutableSet> _openedApplicationURL;
255   // Object that manages all early script injection into the web view.
256   base::scoped_nsobject<CRWJSEarlyScriptManager> _earlyScriptManager;
258   // Script manager for setting the windowID.
259   base::scoped_nsobject<CRWJSWindowIdManager> _windowIDJSManager;
261   // The receiver of JavaScripts.
262   base::scoped_nsobject<CRWJSInjectionReceiver> _jsInjectionReceiver;
265 // The container view.  The container view should be accessed through this
266 // property rather than |self.view| from within this class, as |self.view|
267 // triggers creation while |self.containerView| will return nil if the view
268 // hasn't been instantiated.
269 @property(nonatomic, retain, readonly)
270     CRWWebControllerContainerView* containerView;
271 // The current page state of the web view. Writing to this property
272 // asynchronously applies the passed value to the current web view.
273 @property(nonatomic, readwrite) web::PageDisplayState pageDisplayState;
274 // The currently displayed native controller, if any.
275 @property(nonatomic, readwrite) id<CRWNativeContent> nativeController;
276 // Removes the container view from the hierarchy and resets the ivar.
277 - (void)resetContainerView;
278 // Resets any state that is associated with a specific document object (e.g.,
279 // page interaction tracking).
280 - (void)resetDocumentSpecificState;
281 // Returns YES if the URL looks like it is one CRWWebController can show.
282 + (BOOL)webControllerCanShow:(const GURL&)url;
283 // Clears the currently-displayed transient content view.
284 - (void)clearTransientContentView;
285 // Returns a lazily created CRWTouchTrackingRecognizer.
286 - (CRWTouchTrackingRecognizer*)touchTrackingRecognizer;
287 // Shows placeholder overlay.
288 - (void)addPlaceholderOverlay;
289 // Removes placeholder overlay.
290 - (void)removePlaceholderOverlay;
291 // Returns |YES| if |url| should be loaded in a native view.
292 - (BOOL)shouldLoadURLInNativeView:(const GURL&)url;
293 // Loads the HTML into the page at the given URL.
294 - (void)loadHTML:(NSString*)html forURL:(const GURL&)url;
295 // Loads the current nativeController in a native view. If a web view is
296 // present, removes it and swaps in the native view in its place.
297 - (void)loadNativeViewWithSuccess:(BOOL)loadSuccess;
298 // YES if the navigation to |url| should be treated as a reload.
299 - (BOOL)shouldReload:(const GURL&)destinationURL
300           transition:(ui::PageTransition)transition;
301 // Internal implementation of reload. Reloads without notifying the delegate.
302 // Most callers should use -reload instead.
303 - (void)reloadInternal;
304 // If YES, the page can be closed if the loading of the initial URL requires
305 // it (for example when an external URL is detected). After the initial URL is
306 // loaded, the page is not cancellable anymore.
307 - (BOOL)cancellable;
308 // Called after URL is finished loading and _loadPhase is set to PAGE_LOADED.
309 - (void)didFinishWithURL:(const GURL&)currentURL loadSuccess:(BOOL)loadSuccess;
310 // Informs the native controller if web usage is allowed or not.
311 - (void)setNativeControllerWebUsageEnabled:(BOOL)webUsageEnabled;
312 // Compares the two URLs being navigated between during a history navigation to
313 // determine if a # needs to be appended to endURL to trigger a hashchange
314 // event. If so, also saves the new endURL in the current CRWSessionEntry.
315 - (GURL)updateURLForHistoryNavigationFromURL:(const GURL&)startURL
316                                        toURL:(const GURL&)endURL;
317 // Evaluates the supplied JavaScript in the web view. Calls |handler| with
318 // results of the evaluation (which may be nil if the implementing object has no
319 // way to run the evaluation or the evaluation returns a nil value) or an
320 // NSError if there is an error. The |handler| can be nil.
321 - (void)evaluateJavaScript:(NSString*)script
322          JSONResultHandler:(void (^)(scoped_ptr<base::Value>, NSError*))handler;
323 // Generates the JavaScript string used to update the UIWebView's URL so that it
324 // matches the URL displayed in the omnibox and sets window.history.state to
325 // stateObject. Needed for history.pushState() and history.replaceState().
326 - (NSString*)javascriptToReplaceWebViewURL:(const GURL&)url
327                            stateObjectJSON:(NSString*)stateObject;
328 - (BOOL)isLoaded;
329 // Called by NSNotificationCenter upon orientation changes.
330 - (void)orientationDidChange;
331 // Queries the web view for the user-scalable meta tag and calls
332 // |-applyPageDisplayState:userScalable:| with the result.
333 - (void)applyPageDisplayState:(const web::PageDisplayState&)displayState;
334 // Restores state of the web view's scroll view from |scrollState|.
335 // |isUserScalable| represents the value of user-scalable meta tag.
336 - (void)applyPageDisplayState:(const web::PageDisplayState&)displayState
337                  userScalable:(BOOL)isUserScalable;
338 // Calls the zoom-preparation UIScrollViewDelegate callbacks on the web view.
339 // This is called before |-applyWebViewScrollZoomScaleFromScrollState:|.
340 - (void)prepareToApplyWebViewScrollZoomScale;
341 // Calls the zoom-completion UIScrollViewDelegate callbacks on the web view.
342 // This is called after |-applyWebViewScrollZoomScaleFromScrollState:|.
343 - (void)finishApplyingWebViewScrollZoomScale;
344 // Sets scroll offset value for webview scroll view from |scrollState|.
345 - (void)applyWebViewScrollOffsetFromScrollState:
346     (const web::PageScrollState&)scrollState;
347 // Asynchronously determines whether option |user-scalable| is on in the
348 // viewport meta of the current web page.
349 - (void)queryUserScalableProperty:(void (^)(BOOL))responseHandler;
350 // Asynchronously fetches full width of the rendered web page.
351 - (void)fetchWebPageWidthWithCompletionHandler:(void (^)(CGFloat))handler;
352 // Asynchronously fetches information about DOM element for the given point (in
353 // UIView coordinates). |handler| can not be nil. See |_DOMElementForLastTouch|
354 // for element format description.
355 - (void)fetchDOMElementAtPoint:(CGPoint)point
356              completionHandler:
357                  (void (^)(scoped_ptr<base::DictionaryValue>))handler;
358 // Extracts context menu information from the given DOM element.
359 // result keys are defined in crw_context_menu_provider.h.
360 - (NSDictionary*)contextMenuInfoForElement:(base::DictionaryValue*)element;
361 // Sets the value of |_DOMElementForLastTouch|.
362 - (void)setDOMElementForLastTouch:(scoped_ptr<base::DictionaryValue>)element;
363 // Called when the window has determined there was a long-press and context menu
364 // must be shown.
365 - (void)showContextMenu:(UIGestureRecognizer*)gestureRecognizer;
366 // YES if delegate supports showing context menu by responding to
367 // webController:runContextMenu:atPoint:inView: selector.
368 - (BOOL)supportsCustomContextMenu;
369 // Returns the referrer for the current page.
370 - (web::Referrer)currentReferrer;
371 // Presents an error to the user because the CRWWebController cannot verify the
372 // URL of the current page.
373 - (void)presentSpoofingError;
374 // Adds a new CRWSessionEntry with the given URL and state object to the history
375 // stack. A state object is a serialized generic JavaScript object that contains
376 // details of the UI's state for a given CRWSessionEntry/URL.
377 // TODO(stuartmorgan): Move the pushState/replaceState logic into
378 // NavigationManager.
379 - (void)pushStateWithPageURL:(const GURL&)pageURL
380                  stateObject:(NSString*)stateObject
381                   transition:(ui::PageTransition)transition;
382 // Assigns the given URL and state object to the current CRWSessionEntry.
383 - (void)replaceStateWithPageURL:(const GURL&)pageUrl
384                     stateObject:(NSString*)stateObject;
386 // Returns the current entry from the underlying session controller.
387 // TODO(stuartmorgan): Audit all calls to these methods; these are just wrappers
388 // around the same logic as GetActiveEntry, so should probably not be used for
389 // the same reason that GetActiveEntry is deprecated. (E.g., page operations
390 // should generally be dealing with the last commited entry, not a pending
391 // entry).
392 - (CRWSessionEntry*)currentSessionEntry;
393 - (web::NavigationItem*)currentNavItem;
394 // Returns the referrer for currentURL as a string. May return nil.
395 - (web::Referrer)currentSessionEntryReferrer;
396 // The data and HTTP headers associated to the current entry. These are nil
397 // unless the request was a POST.
398 - (NSData*)currentPOSTData;
399 - (NSDictionary*)currentHttpHeaders;
401 // Finds all the scrollviews in the view hierarchy and makes sure they do not
402 // interfere with scroll to top when tapping the statusbar.
403 - (void)optOutScrollsToTopForSubviews;
404 // Tears down the old native controller, and then replaces it with the new one.
405 - (void)setNativeController:(id<CRWNativeContent>)nativeController;
406 // Returns whether |url| should be opened.
407 - (BOOL)shouldOpenURL:(const GURL&)url
408       mainDocumentURL:(const GURL&)mainDocumentURL
409           linkClicked:(BOOL)linkClicked;
410 // Called when |url| needs to be opened in a matching native app.
411 // Returns YES if the url was succesfully opened in the native app.
412 - (BOOL)urlTriggersNativeAppLaunch:(const GURL&)url
413                          sourceURL:(const GURL&)sourceURL;
414 // Returns whether external URL request should be opened.
415 - (BOOL)shouldOpenExternalURLRequest:(NSURLRequest*)request
416                          targetFrame:(const web::FrameInfo*)targetFrame;
417 // Called when a page updates its history stack using pushState or replaceState.
418 - (void)didUpdateHistoryStateWithPageURL:(const GURL&)url;
420 // Handlers for JavaScript messages. |message| contains a JavaScript command and
421 // data relevant to the message, and |context| contains contextual information
422 // about web view state needed for some handlers.
424 // Handles 'addPluginPlaceholders' message.
425 - (BOOL)handleAddPluginPlaceholdersMessage:(base::DictionaryValue*)message
426                                    context:(NSDictionary*)context;
427 // Handles 'chrome.send' message.
428 - (BOOL)handleChromeSendMessage:(base::DictionaryValue*)message
429                         context:(NSDictionary*)context;
430 // Handles 'console' message.
431 - (BOOL)handleConsoleMessage:(base::DictionaryValue*)message
432                      context:(NSDictionary*)context;
433 // Handles 'dialog.suppressed' message.
434 - (BOOL)handleDialogSuppressedMessage:(base::DictionaryValue*)message
435                               context:(NSDictionary*)context;
436 // Handles 'dialog.willShow' message.
437 - (BOOL)handleDialogWillShowMessage:(base::DictionaryValue*)message
438                             context:(NSDictionary*)context;
439 // Handles 'document.favicons' message.
440 - (BOOL)handleDocumentFaviconsMessage:(base::DictionaryValue*)message
441                               context:(NSDictionary*)context;
442 // Handles 'document.submit' message.
443 - (BOOL)handleDocumentSubmitMessage:(base::DictionaryValue*)message
444                             context:(NSDictionary*)context;
445 // Handles 'externalRequest' message.
446 - (BOOL)handleExternalRequestMessage:(base::DictionaryValue*)message
447                              context:(NSDictionary*)context;
448 // Handles 'form.activity' message.
449 - (BOOL)handleFormActivityMessage:(base::DictionaryValue*)message
450                           context:(NSDictionary*)context;
451 // Handles 'form.requestAutocomplete' message.
452 - (BOOL)handleFormRequestAutocompleteMessage:(base::DictionaryValue*)message
453                                      context:(NSDictionary*)context;
454 // Handles 'navigator.credentials.request' message.
455 - (BOOL)handleCredentialsRequestedMessage:(base::DictionaryValue*)message
456                                   context:(NSDictionary*)context;
457 // Handles 'navigator.credentials.notifySignedIn' message.
458 - (BOOL)handleSignedInMessage:(base::DictionaryValue*)message
459                       context:(NSDictionary*)context;
460 // Handles 'navigator.credentials.notifySignedOut' message.
461 - (BOOL)handleSignedOutMessage:(base::DictionaryValue*)message
462                        context:(NSDictionary*)context;
463 // Handles 'navigator.credentials.notifyFailedSignIn' message.
464 - (BOOL)handleSignInFailedMessage:(base::DictionaryValue*)message
465                           context:(NSDictionary*)context;
466 // Handles 'resetExternalRequest' message.
467 - (BOOL)handleResetExternalRequestMessage:(base::DictionaryValue*)message
468                                   context:(NSDictionary*)context;
469 // Handles 'window.close.self' message.
470 - (BOOL)handleWindowCloseSelfMessage:(base::DictionaryValue*)message
471                              context:(NSDictionary*)context;
472 // Handles 'window.error' message.
473 - (BOOL)handleWindowErrorMessage:(base::DictionaryValue*)message
474                          context:(NSDictionary*)context;
475 // Handles 'window.hashchange' message.
476 - (BOOL)handleWindowHashChangeMessage:(base::DictionaryValue*)message
477                               context:(NSDictionary*)context;
478 // Handles 'window.history.back' message.
479 - (BOOL)handleWindowHistoryBackMessage:(base::DictionaryValue*)message
480                                context:(NSDictionary*)context;
481 // Handles 'window.history.forward' message.
482 - (BOOL)handleWindowHistoryForwardMessage:(base::DictionaryValue*)message
483                                   context:(NSDictionary*)context;
484 // Handles 'window.history.go' message.
485 - (BOOL)handleWindowHistoryGoMessage:(base::DictionaryValue*)message
486                              context:(NSDictionary*)context;
487 @end
489 namespace {
491 NSString* const kReferrerHeaderName = @"Referer";  // [sic]
493 // Full screen experimental setting.
495 // The long press detection duration must be shorter than the UIWebView's
496 // long click gesture recognizer's minimum duration. That is 0.55s.
497 // If our detection duration is shorter, our gesture recognizer will fire
498 // first, and if it fails the long click gesture (processed simultaneously)
499 // still is able to complete.
500 const NSTimeInterval kLongPressDurationSeconds = 0.55 - 0.1;
501 const CGFloat kLongPressMoveDeltaPixels = 10.0;
503 // The duration of the period following a screen touch during which the user is
504 // still considered to be interacting with the page.
505 const NSTimeInterval kMaximumDelayForUserInteractionInSeconds = 2;
507 // Define missing symbols from WebKit.
508 // See WebKitErrors.h on Mac SDK.
509 NSString* const WebKitErrorDomain = @"WebKitErrorDomain";
511 enum {
512   WebKitErrorCannotShowMIMEType = 100,
513   WebKitErrorCannotShowURL = 101,
514   WebKitErrorFrameLoadInterruptedByPolicyChange = 102,
515   // iOS-specific WebKit error that isn't documented but seen on 4.0
516   // devices.
517   WebKitErrorPlugInLoadFailed = 204,
520 // URLs that are fed into UIWebView as history push/replace get escaped,
521 // potentially changing their format. Code that attempts to determine whether a
522 // URL hasn't changed can be confused by those differences though, so method
523 // will round-trip a URL through the escaping process so that it can be adjusted
524 // pre-storing, to allow later comparisons to work as expected.
525 GURL URLEscapedForHistory(const GURL& url) {
526   // TODO(stuartmorgan): This is a very large hammer; see if limited unicode
527   // escaping would be sufficient.
528   return net::GURLWithNSURL(net::NSURLWithGURL(url));
531 // Parses a viewport tag content and returns the value of an attribute with
532 // the given |name|, or nil if the attribute is not present in the tag.
533 NSString* GetAttributeValueFromViewPortContent(NSString* attributeName,
534                                                NSString* viewPortContent) {
535   NSArray* contentItems = [viewPortContent componentsSeparatedByString:@","];
536   for (NSString* item in contentItems) {
537     NSArray* components = [item componentsSeparatedByString:@"="];
538     if ([components count] == 2) {
539       NSCharacterSet* spaceAndNewline =
540           [NSCharacterSet whitespaceAndNewlineCharacterSet];
541       NSString* currentAttributeName =
542           [components[0] stringByTrimmingCharactersInSet:spaceAndNewline];
543       if ([currentAttributeName isEqualToString:attributeName]) {
544         return [components[1] stringByTrimmingCharactersInSet:spaceAndNewline];
545       }
546     }
547   }
548   return nil;
551 // Parses a viewport tag content and returns the value of the user-scalable
552 // attribute or nil.
553 BOOL GetUserScalablePropertyFromViewPortContent(NSString* viewPortContent) {
554   NSString* value =
555       GetAttributeValueFromViewPortContent(@"user-scalable", viewPortContent);
556   if (!value) {
557     return YES;
558   }
559   return !([value isEqualToString:@"0"] ||
560            [value caseInsensitiveCompare:@"no"] == NSOrderedSame);
563 // Leave snapshot overlay up unless page loads.
564 const NSTimeInterval kSnapshotOverlayDelay = 1.5;
565 // Transition to fade snapshot overlay.
566 const NSTimeInterval kSnapshotOverlayTransition = 0.5;
568 }  // namespace
570 @implementation CRWWebController
572 @synthesize webUsageEnabled = _webUsageEnabled;
573 @synthesize usePlaceholderOverlay = _usePlaceholderOverlay;
574 @synthesize loadPhase = _loadPhase;
576 // Implemented by subclasses.
577 @dynamic keyboardDisplayRequiresUserAction;
579 + (instancetype)allocWithZone:(struct _NSZone*)zone {
580   if (self == [CRWWebController class]) {
581     // This is an abstract class which should not be instantiated directly.
582     // Callers should create concrete subclasses instead.
583     NOTREACHED();
584     return nil;
585   }
586   return [super allocWithZone:zone];
589 - (instancetype)initWithWebState:(scoped_ptr<WebStateImpl>)webState {
590   self = [super init];
591   if (self) {
592     _webStateImpl = webState.Pass();
593     DCHECK(_webStateImpl);
594     _webStateImpl->SetWebController(self);
595     _webStateImpl->InitializeRequestTracker(self);
596     // Load phase when no WebView present is 'loaded' because this represents
597     // the idle state.
598     _loadPhase = web::PAGE_LOADED;
599     // Content area is lazily instantiated.
600     _defaultURL = GURL(url::kAboutBlankURL);
601     _jsInjectionReceiver.reset(
602         [[CRWJSInjectionReceiver alloc] initWithEvaluator:self]);
603     _earlyScriptManager.reset([(CRWJSEarlyScriptManager*)[_jsInjectionReceiver
604         instanceOfClass:[CRWJSEarlyScriptManager class]] retain]);
605     _windowIDJSManager.reset([(CRWJSWindowIdManager*)[_jsInjectionReceiver
606         instanceOfClass:[CRWJSWindowIdManager class]] retain]);
607     _lastSeenWindowID.reset();
608     _webViewProxy.reset(
609         [[CRWWebViewProxyImpl alloc] initWithWebController:self]);
610     [[_webViewProxy scrollViewProxy] addObserver:self];
611     _gestureRecognizers.reset([[NSMutableArray alloc] init]);
612     _webViewToolbars.reset([[NSMutableArray alloc] init]);
613     _pendingLoadCompleteActions.reset([[NSMutableArray alloc] init]);
614     [[NSNotificationCenter defaultCenter]
615         addObserver:self
616            selector:@selector(orientationDidChange)
617                name:UIApplicationDidChangeStatusBarOrientationNotification
618              object:nil];
619   }
620   return self;
623 - (id<CRWNativeContentProvider>)nativeProvider {
624   return _nativeProvider.get();
627 - (void)setNativeProvider:(id<CRWNativeContentProvider>)nativeProvider {
628   _nativeProvider.reset(nativeProvider);
631 - (id<CRWSwipeRecognizerProvider>)swipeRecognizerProvider {
632   return _swipeRecognizerProvider.get();
635 - (void)setSwipeRecognizerProvider:
636     (id<CRWSwipeRecognizerProvider>)swipeRecognizerProvider {
637   _swipeRecognizerProvider.reset(swipeRecognizerProvider);
640 - (WebState*)webState {
641   return _webStateImpl.get();
644 - (WebStateImpl*)webStateImpl {
645   return _webStateImpl.get();
648 - (void)clearTransientContentView {
649   // Early return if there is no transient content view.
650   if (!self.containerView.transientContentView)
651     return;
653   // Remove the transient content view from the hierarchy.
654   [self.containerView clearTransientContentView];
656   // Notify the WebState so it can perform any required state cleanup.
657   if (_webStateImpl)
658     _webStateImpl->ClearTransientContentView();
661 - (void)showTransientContentView:(CRWContentView*)contentView {
662   DCHECK(contentView);
663   DCHECK(contentView.scrollView);
664   DCHECK([contentView.scrollView isDescendantOfView:contentView]);
665   [self.containerView displayTransientContent:contentView];
668 - (id<CRWWebDelegate>)delegate {
669   return _delegate.get();
672 - (void)setDelegate:(id<CRWWebDelegate>)delegate {
673   _delegate.reset(delegate);
674   if ([self.nativeController respondsToSelector:@selector(setDelegate:)]) {
675     if ([_delegate respondsToSelector:@selector(webController:titleDidChange:)])
676       [self.nativeController setDelegate:self];
677     else
678       [self.nativeController setDelegate:nil];
679   }
682 - (id<CRWWebUserInterfaceDelegate>)UIDelegate {
683   return _UIDelegate.get();
686 - (void)setUIDelegate:(id<CRWWebUserInterfaceDelegate>)UIDelegate {
687   _UIDelegate.reset(UIDelegate);
690 - (NSString*)scriptByAddingWindowIDCheckForScript:(NSString*)script {
691   NSString* kTemplate = @"if (__gCrWeb['windowId'] === '%@') { %@; }";
692   return [NSString stringWithFormat:kTemplate, [self windowId], script];
695 - (void)removeWebViewAllowingCachedReconstruction:(BOOL)allowCache {
696   if (!self.webView)
697     return;
699   if (allowCache)
700     _expectedReconstructionURL = [self currentNavigationURL];
701   else
702     _expectedReconstructionURL = GURL();
704   [self abortLoad];
705   [self.webView removeFromSuperview];
706   [self.containerView resetContent];
707   [self resetWebView];
710 - (void)dealloc {
711   DCHECK([NSThread isMainThread]);
712   DCHECK(_isBeingDestroyed);  // 'close' must have been called already.
713   DCHECK(!self.webView);
714   _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
715   [[_webViewProxy scrollViewProxy] removeObserver:self];
716   [[NSNotificationCenter defaultCenter] removeObserver:self];
717   [super dealloc];
720 - (BOOL)runUnloadListenerBeforeClosing {
721   // There's not much that can be done since there's limited access to WebKit.
722   // Always return that it's ok to close immediately.
723   return YES;
726 - (void)dismissKeyboard {
727   [self.webView endEditing:YES];
728   if ([self.nativeController respondsToSelector:@selector(dismissKeyboard)])
729     [self.nativeController dismissKeyboard];
732 - (id<CRWNativeContent>)nativeController {
733   return self.containerView.nativeController;
736 - (void)setNativeController:(id<CRWNativeContent>)nativeController {
737   // Check for pointer equality.
738   if (self.nativeController == nativeController)
739     return;
741   // Unset the delegate on the previous instance.
742   if ([self.nativeController respondsToSelector:@selector(setDelegate:)])
743     [self.nativeController setDelegate:nil];
745   [self.containerView displayNativeContent:nativeController];
746   [self setNativeControllerWebUsageEnabled:_webUsageEnabled];
749 // NativeControllerDelegate method, called to inform that title has changed.
750 - (void)nativeContent:(id)content titleDidChange:(NSString*)title {
751   // Responsiveness to delegate method was checked in setDelegate:.
752   [_delegate webController:self titleDidChange:title];
755 - (void)setNativeControllerWebUsageEnabled:(BOOL)webUsageEnabled {
756   if ([self.nativeController
757           respondsToSelector:@selector(setWebUsageEnabled:)]) {
758     [self.nativeController setWebUsageEnabled:webUsageEnabled];
759   }
762 - (void)setWebUsageEnabled:(BOOL)enabled {
763   if (_webUsageEnabled == enabled)
764     return;
765   _webUsageEnabled = enabled;
767   // WKWebView autoreleases its WKProcessPool on removal from superview.
768   // Deferring WKProcessPool deallocation may lead to issues with cookie
769   // clearing and and Browsing Data Partitioning implementation.
770   @autoreleasepool {
771     [self setNativeControllerWebUsageEnabled:_webUsageEnabled];
772     if (enabled) {
773       // Don't create the web view; let it be lazy created as needed.
774     } else {
775       [self clearTransientContentView];
776       [self removeWebViewAllowingCachedReconstruction:YES];
777       _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
778       _touchTrackingRecognizer.reset();
779       [self resetContainerView];
780     }
781   }
784 - (void)requirePageReconstruction {
785   [self removeWebViewAllowingCachedReconstruction:NO];
788 - (void)resetContainerView {
789   [self.containerView removeFromSuperview];
790   _containerView.reset();
793 - (void)handleLowMemory {
794   [self removeWebViewAllowingCachedReconstruction:YES];
795   _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
796   _touchTrackingRecognizer.reset();
797   [self resetContainerView];
798   _usePlaceholderOverlay = YES;
801 - (void)reinitializeWebViewAndReload:(BOOL)reload {
802   if (self.webView) {
803     [self removeWebViewAllowingCachedReconstruction:NO];
804     if (reload) {
805       [self loadCurrentURLInWebView];
806     } else {
807       // Clear the space for the web view to lazy load when needed.
808       _usePlaceholderOverlay = YES;
809       _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
810       _touchTrackingRecognizer.reset();
811       [self resetContainerView];
812     }
813   }
816 - (void)childWindowClosed:(NSString*)windowName {
817   // Subclasses can override this method to be informed about a closed window.
820 - (BOOL)isViewAlive {
821   return [self.containerView isViewAlive];
824 - (BOOL)contentIsHTML {
825   return [self webViewDocumentType] == web::WEB_VIEW_DOCUMENT_TYPE_HTML;
828 // Stop doing stuff, especially network stuff. Close the request tracker.
829 - (void)terminateNetworkActivity {
830   DCHECK(!_isHalted);
831   _isHalted = YES;
833   // Cancel all outstanding perform requests, and clear anything already queued
834   // (since this may be called from within the handling loop) to prevent any
835   // asynchronous JavaScript invocation handling from continuing.
836   [NSObject cancelPreviousPerformRequestsWithTarget:self];
837   _webStateImpl->CloseRequestTracker();
840 - (void)dismissModals {
841   if ([self.nativeController respondsToSelector:@selector(dismissModals)])
842     [self.nativeController dismissModals];
845 // Caller must reset the delegate before calling.
846 - (void)close {
847   self.nativeProvider = nil;
848   self.swipeRecognizerProvider = nil;
849   if ([self.nativeController respondsToSelector:@selector(close)])
850     [self.nativeController close];
852   base::scoped_nsobject<NSSet> observers([_observers copy]);
853   for (id it in observers.get()) {
854     if ([it respondsToSelector:@selector(webControllerWillClose:)])
855       [it webControllerWillClose:self];
856   }
858   if (!_isHalted) {
859     [self terminateNetworkActivity];
860   }
862   DCHECK(!_isBeingDestroyed);
863   DCHECK(!_delegate);  // Delegate should reset its association before closing.
864   // Mark the destruction sequence has started, in case someone else holds a
865   // strong reference and tries to continue using the tab.
866   _isBeingDestroyed = YES;
868   // Remove the web view now. Otherwise, delegate callbacks occur.
869   [self removeWebViewAllowingCachedReconstruction:NO];
871   // Tear down web ui (in case this is part of this tab) and web state now,
872   // since the timing of dealloc can't be guaranteed.
873   _webStateImpl.reset();
876 - (void)checkLinkPresenceUnderGesture:(UIGestureRecognizer*)gestureRecognizer
877                     completionHandler:(void (^)(BOOL))completionHandler {
878   CGPoint webViewPoint = [gestureRecognizer locationInView:self.webView];
879   base::WeakNSObject<CRWWebController> weakSelf(self);
880   [self fetchDOMElementAtPoint:webViewPoint
881              completionHandler:^(scoped_ptr<base::DictionaryValue> element) {
882                std::string link;
883                BOOL hasLink =
884                    element && element->GetString("href", &link) && link.size();
885                completionHandler(hasLink);
886              }];
889 - (void)setDOMElementForLastTouch:(scoped_ptr<base::DictionaryValue>)element {
890   _DOMElementForLastTouch = element.Pass();
893 - (void)showContextMenu:(UIGestureRecognizer*)gestureRecognizer {
894   // Calling this method if [self supportsCustomContextMenu] returned NO
895   // is a programmer error.
896   DCHECK([self supportsCustomContextMenu]);
898   // We don't want ongoing notification that the long press is held.
899   if ([gestureRecognizer state] != UIGestureRecognizerStateBegan)
900     return;
902   if (!_DOMElementForLastTouch || _DOMElementForLastTouch->empty())
903     return;
905   NSDictionary* info =
906       [self contextMenuInfoForElement:_DOMElementForLastTouch.get()];
907   CGPoint point = [gestureRecognizer locationInView:self.webView];
909   // Cancel all touches on the web view when showing custom context menu. This
910   // will suppress the system context menu and prevent further user interactions
911   // with web view (like scrolling the content and following links). This
912   // approach is similar to UIWebView and WKWebView workflow as both call
913   // -[UIApplication _cancelAllTouches] to cancel all touch events, once the
914   // long press is detected.
915   CancelAllTouches(self.webScrollView);
916   [self.UIDelegate webController:self
917                   runContextMenu:info
918                          atPoint:point
919                           inView:self.webView];
922 - (BOOL)supportsCustomContextMenu {
923   SEL runMenuSelector = @selector(webController:runContextMenu:atPoint:inView:);
924   return [self.UIDelegate respondsToSelector:runMenuSelector];
927 // TODO(shreyasv): This code is shared with SnapshotManager. Remove this and add
928 // it as part of WebDelegate delegate API such that a default image is returned
929 // immediately.
930 + (UIImage*)defaultSnapshotImage {
931   static UIImage* defaultImage = nil;
933   if (!defaultImage) {
934     CGRect frame = CGRectMake(0, 0, 2, 2);
935     UIGraphicsBeginImageContext(frame.size);
936     [[UIColor whiteColor] setFill];
937     CGContextFillRect(UIGraphicsGetCurrentContext(), frame);
939     UIImage* result = UIGraphicsGetImageFromCurrentImageContext();
940     UIGraphicsEndImageContext();
942     defaultImage =
943         [[result stretchableImageWithLeftCapWidth:1 topCapHeight:1] retain];
944   }
945   return defaultImage;
948 - (BOOL)canGoBack {
949   return _webStateImpl->GetNavigationManagerImpl().CanGoBack();
952 - (BOOL)canGoForward {
953   return _webStateImpl->GetNavigationManagerImpl().CanGoForward();
956 - (CGPoint)scrollPosition {
957   CGPoint position = CGPointMake(0.0, 0.0);
958   if (!self.webScrollView)
959     return position;
960   return self.webScrollView.contentOffset;
963 - (BOOL)atTop {
964   if (!self.webView)
965     return YES;
966   UIScrollView* scrollView = self.webScrollView;
967   return scrollView.contentOffset.y == -scrollView.contentInset.top;
970 - (void)presentSpoofingError {
971   UMA_HISTOGRAM_ENUMERATION("Web.URLVerificationFailure",
972                             [self webViewDocumentType],
973                             web::WEB_VIEW_DOCUMENT_TYPE_COUNT);
974   if (self.webView) {
975     [self removeWebViewAllowingCachedReconstruction:NO];
976     [_delegate presentSpoofingError];
977   }
980 - (GURL)currentURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel {
981   DCHECK(trustLevel) << "Verification of the trustLevel state is mandatory";
982   if (self.webView) {
983     GURL url([self webURLWithTrustLevel:trustLevel]);
984     // Web views treat all about: URLs as the same origin, which makes it
985     // possible for pages to document.write into about:<foo> pages, where <foo>
986     // can be something misleading. Report any about: URL as about:blank to
987     // prevent that. See crbug.com/326118
988     if (url.scheme() == url::kAboutScheme)
989       return GURL(url::kAboutBlankURL);
990     return url;
991   }
992   // Any non-web URL source is trusted.
993   *trustLevel = web::URLVerificationTrustLevel::kAbsolute;
994   if (self.nativeController)
995     return [self.nativeController url];
996   return [self currentNavigationURL];
999 - (GURL)currentURL {
1000   web::URLVerificationTrustLevel trustLevel =
1001       web::URLVerificationTrustLevel::kNone;
1002   const GURL url([self currentURLWithTrustLevel:&trustLevel]);
1004   // Check whether the spoofing warning needs to be displayed.
1005   if (trustLevel == web::URLVerificationTrustLevel::kNone &&
1006       ![self ignoreURLVerificationFailures]) {
1007     dispatch_async(dispatch_get_main_queue(), ^{
1008       if (!_isHalted) {
1009         DCHECK_EQ(url, [self currentNavigationURL]);
1010         [self presentSpoofingError];
1011       }
1012     });
1013   }
1015   return url;
1018 - (web::Referrer)currentReferrer {
1019   // Referrer string doesn't include the fragment, so in cases where the
1020   // previous URL is equal to the current referrer plus the fragment the
1021   // previous URL is returned as current referrer.
1022   NSString* referrerString = self.currentReferrerString;
1024   // In case of an error evaluating the JavaScript simply return empty string.
1025   if ([referrerString length] == 0)
1026     return web::Referrer();
1028   NSString* previousURLString =
1029       base::SysUTF8ToNSString([self currentNavigationURL].spec());
1030   // Check if the referrer is equal to the previous URL minus the hash symbol.
1031   // L'#' is used to convert the char '#' to a unichar.
1032   if ([previousURLString length] > [referrerString length] &&
1033       [previousURLString hasPrefix:referrerString] &&
1034       [previousURLString characterAtIndex:[referrerString length]] == L'#') {
1035     referrerString = previousURLString;
1036   }
1037   // Since referrer is being extracted from the destination page, the correct
1038   // policy from the origin has *already* been applied. Since the extracted URL
1039   // is the post-policy value, and the source policy is no longer available,
1040   // the policy is set to Always so that whatever WebKit decided to send will be
1041   // re-sent when replaying the entry.
1042   // TODO(stuartmorgan): When possible, get the real referrer and policy in
1043   // advance and use that instead. https://crbug.com/227769.
1044   return web::Referrer(GURL(base::SysNSStringToUTF8(referrerString)),
1045                        web::ReferrerPolicyAlways);
1048 - (void)pushStateWithPageURL:(const GURL&)pageURL
1049                  stateObject:(NSString*)stateObject
1050                   transition:(ui::PageTransition)transition {
1051   [[self sessionController] pushNewEntryWithURL:pageURL
1052                                     stateObject:stateObject
1053                                      transition:transition];
1054   [self didUpdateHistoryStateWithPageURL:pageURL];
1057 - (void)replaceStateWithPageURL:(const GURL&)pageUrl
1058                     stateObject:(NSString*)stateObject {
1059   [[self sessionController] updateCurrentEntryWithURL:pageUrl
1060                                           stateObject:stateObject];
1061   [self didUpdateHistoryStateWithPageURL:pageUrl];
1064 - (void)injectEarlyInjectionScripts {
1065   DCHECK(self.webView);
1066   if (![_earlyScriptManager hasBeenInjected]) {
1067     [_earlyScriptManager inject];
1068     // If this assertion fires there has been an error parsing the core.js
1069     // object.
1070     DCHECK([_earlyScriptManager hasBeenInjected]);
1071   }
1072   [self injectWindowID];
1075 - (void)injectWindowID {
1076   if (![_windowIDJSManager hasBeenInjected]) {
1077     // If the window ID wasn't present, this is a new page.
1078     [self setPageChangeProbability:web::PAGE_CHANGE_PROBABILITY_LOW];
1079     // Default values for suppressDialogs and notifyAboutDialogs are NO,
1080     // so updating them only when necessary is a good optimization.
1081     if (_setSuppressDialogsLater || _setNotifyAboutDialogsLater) {
1082       [self setSuppressDialogs:_setSuppressDialogsLater
1083                         notify:_setNotifyAboutDialogsLater];
1084       _setSuppressDialogsLater = NO;
1085       _setNotifyAboutDialogsLater = NO;
1086     }
1088     [_windowIDJSManager inject];
1089     DCHECK([_windowIDJSManager hasBeenInjected]);
1090   }
1093 // Set the specified recognizer to take priority over any recognizers in the
1094 // view that have a description containing the specified text fragment.
1095 + (void)requireGestureRecognizerToFail:(UIGestureRecognizer*)recognizer
1096                                 inView:(UIView*)view
1097                  containingDescription:(NSString*)fragment {
1098   for (UIGestureRecognizer* iRecognizer in [view gestureRecognizers]) {
1099     if (iRecognizer != recognizer) {
1100       NSString* description = [iRecognizer description];
1101       if ([description rangeOfString:fragment].location != NSNotFound) {
1102         [iRecognizer requireGestureRecognizerToFail:recognizer];
1103         // requireGestureRecognizerToFail: doesn't retain the recognizer, so it
1104         // is possible for |iRecognizer| to outlive |recognizer| and end up with
1105         // a dangling pointer. Add a retaining associative reference to ensure
1106         // that the lifetimes work out.
1107         // Note that normally using the value as the key wouldn't make any
1108         // sense, but here it's fine since nothing needs to look up the value.
1109         objc_setAssociatedObject(view, recognizer, recognizer,
1110                                  OBJC_ASSOCIATION_RETAIN_NONATOMIC);
1111       }
1112     }
1113   }
1116 - (void)webViewDidChange {
1117   CHECK(_webUsageEnabled) << "Tried to create a web view while suspended!";
1119   UIView* webView = self.webView;
1120   DCHECK(webView);
1122   [webView setTag:kWebViewTag];
1123   [webView setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
1124                                UIViewAutoresizingFlexibleHeight];
1125   [webView setBackgroundColor:[UIColor colorWithWhite:0.2 alpha:1.0]];
1127   // Create a dependency between the |webView| pan gesture and BVC side swipe
1128   // gestures. Note: This needs to be added before the longPress recognizers
1129   // below, or the longPress appears to deadlock the remaining recognizers,
1130   // thereby breaking scroll.
1131   NSSet* recognizers = [_swipeRecognizerProvider swipeRecognizers];
1132   for (UISwipeGestureRecognizer* swipeRecognizer in recognizers) {
1133     [self.webScrollView.panGestureRecognizer
1134         requireGestureRecognizerToFail:swipeRecognizer];
1135   }
1137   // On iOS 4.x, there are two gesture recognizers on the UIWebView subclasses,
1138   // that have a minimum tap threshold of 0.12s and 0.75s.
1139   //
1140   // My theory is that the shorter threshold recognizer performs the link
1141   // highlight (grey highlight around links when it is tapped and held) while
1142   // the longer threshold one pops up the context menu.
1143   //
1144   // To override the context menu, this recognizer needs to react faster than
1145   // the 0.75s one. The below gesture recognizer is initialized with a
1146   // detection duration a little lower than that (see
1147   // kLongPressDurationSeconds). It also points the delegate to this class that
1148   // allows simultaneously operate along with the other recognizers.
1149   _contextMenuRecognizer.reset([[UILongPressGestureRecognizer alloc]
1150       initWithTarget:self
1151               action:@selector(showContextMenu:)]);
1152   [_contextMenuRecognizer setMinimumPressDuration:kLongPressDurationSeconds];
1153   [_contextMenuRecognizer setAllowableMovement:kLongPressMoveDeltaPixels];
1154   [_contextMenuRecognizer setDelegate:self];
1155   [webView addGestureRecognizer:_contextMenuRecognizer];
1156   // Certain system gesture handlers are known to conflict with our context
1157   // menu handler, causing extra events to fire when the context menu is active.
1159   // A number of solutions have been investigated. The lowest-risk solution
1160   // appears to be to recurse through the web controller's recognizers, looking
1161   // for fingerprints of the recognizers known to cause problems, which are then
1162   // de-prioritized (below our own long click handler).
1163   // Hunting for description fragments of system recognizers is undeniably
1164   // brittle for future versions of iOS. If it does break the context menu
1165   // events may leak (regressing b/5310177), but the app will otherwise work.
1166   [CRWWebController
1167       requireGestureRecognizerToFail:_contextMenuRecognizer
1168                               inView:webView
1169                containingDescription:@"action=_highlightLongPressRecognized:"];
1171   // Add all additional gesture recognizers to the web view.
1172   for (UIGestureRecognizer* recognizer in _gestureRecognizers.get()) {
1173     [webView addGestureRecognizer:recognizer];
1174   }
1176   _URLOnStartLoading = _defaultURL;
1178   // Add the web toolbars.
1179   [self.containerView addToolbars:_webViewToolbars];
1181   base::scoped_nsobject<CRWWebViewContentView> webViewContentView(
1182       [[CRWWebViewContentView alloc] initWithWebView:self.webView
1183                                           scrollView:self.webScrollView]);
1184   [self.containerView displayWebViewContentView:webViewContentView];
1187 - (CRWWebController*)createChildWebControllerWithReferrerURL:
1188     (const GURL&)referrerURL {
1189   web::Referrer referrer(referrerURL, web::ReferrerPolicyDefault);
1190   CRWWebController* result =
1191       [self.delegate webPageOrderedOpenBlankWithReferrer:referrer
1192                                             inBackground:NO];
1193   DCHECK(!result || result.sessionController.openedByDOM);
1194   return result;
1197 - (BOOL)canUseViewForGeneratingOverlayPlaceholderView {
1198   return self.containerView != nil;
1201 - (UIView*)view {
1202   // Kick off the process of lazily creating the view and starting the load if
1203   // necessary; this creates _containerView if it doesn't exist.
1204   [self triggerPendingLoad];
1205   DCHECK(self.containerView);
1206   return self.containerView;
1209 - (CRWWebControllerContainerView*)containerView {
1210   return _containerView.get();
1213 - (id<CRWWebViewProxy>)webViewProxy {
1214   return _webViewProxy.get();
1217 - (UIView*)viewForPrinting {
1218   // TODO(ios): crbug.com/227944. Printing is not supported for native
1219   // controllers.
1220   return self.webView;
1223 - (void)loadRequest:(NSMutableURLRequest*)request {
1224   // Subclasses must implement this method.
1225   NOTREACHED();
1228 - (void)registerLoadRequest:(const GURL&)requestURL
1229                    referrer:(const web::Referrer&)referrer
1230                  transition:(ui::PageTransition)transition {
1231   // Transfer time is registered so that further transitions within the time
1232   // envelope are not also registered as links.
1233   _lastTransferTimeInSeconds = CFAbsoluteTimeGetCurrent();
1234   // Before changing phases, the delegate should be informed that any existing
1235   // request is being cancelled before completion.
1236   [self loadCancelled];
1237   DCHECK(_loadPhase == web::PAGE_LOADED);
1239   _loadPhase = web::LOAD_REQUESTED;
1240   [self resetLoadState];
1241   _lastRegisteredRequestURL = requestURL;
1243   if (!(transition & ui::PAGE_TRANSITION_IS_REDIRECT_MASK)) {
1244     // Record state of outgoing page.
1245     [self recordStateInHistory];
1246   }
1248   // If the web view had been discarded, and this request is to load that
1249   // URL again, then it's a rebuild and should use the cache.
1250   BOOL preferCache = _expectedReconstructionURL.is_valid() &&
1251                      _expectedReconstructionURL == requestURL;
1253   [_delegate webWillAddPendingURL:requestURL transition:transition];
1254   // Add or update pending url.
1255   if (_webStateImpl->GetNavigationManagerImpl().GetPendingItem()) {
1256     // Update the existing pending entry.
1257     // Typically on PAGE_TRANSITION_CLIENT_REDIRECT.
1258     [[self sessionController] updatePendingEntry:requestURL];
1259   } else {
1260     // A new session history entry needs to be created.
1261     [[self sessionController] addPendingEntry:requestURL
1262                                      referrer:referrer
1263                                    transition:transition
1264                             rendererInitiated:YES];
1265   }
1266   // Update the cache mode for all the network requests issued by this web view.
1267   // The mode is reset to CACHE_NORMAL after each page load.
1268   if (_webStateImpl->GetCacheMode() != net::RequestTracker::CACHE_NORMAL) {
1269     _webStateImpl->GetRequestTracker()->SetCacheModeFromUIThread(
1270         _webStateImpl->GetCacheMode());
1271   } else if (preferCache) {
1272     _webStateImpl->GetRequestTracker()->SetCacheModeFromUIThread(
1273         net::RequestTracker::CACHE_HISTORY);
1274   }
1275   _webStateImpl->SetIsLoading(true);
1276   [_delegate webDidAddPendingURL];
1277   _webStateImpl->OnProvisionalNavigationStarted(requestURL);
1280 - (NSString*)javascriptToReplaceWebViewURL:(const GURL&)url
1281                            stateObjectJSON:(NSString*)stateObject {
1282   std::string outURL;
1283   base::EscapeJSONString(url.spec(), true, &outURL);
1284   return
1285       [NSString stringWithFormat:@"__gCrWeb.replaceWebViewURL(%@, %@);",
1286                                  base::SysUTF8ToNSString(outURL), stateObject];
1289 - (void)finishPushStateNavigationToURL:(const GURL&)url
1290                        withStateObject:(NSString*)stateObject {
1291   // TODO(stuartmorgan): Make CRWSessionController manage this internally (or
1292   // remove it; it's not clear this matches other platforms' behavior).
1293   _webStateImpl->GetNavigationManagerImpl().OnNavigationItemCommitted();
1295   NSString* replaceWebViewUrlJS =
1296       [self javascriptToReplaceWebViewURL:url stateObjectJSON:stateObject];
1297   std::string outState;
1298   base::EscapeJSONString(base::SysNSStringToUTF8(stateObject), true, &outState);
1299   NSString* popstateJS =
1300       [NSString stringWithFormat:@"__gCrWeb.dispatchPopstateEvent(%@);",
1301                                  base::SysUTF8ToNSString(outState)];
1302   NSString* combinedJS =
1303       [NSString stringWithFormat:@"%@%@", replaceWebViewUrlJS, popstateJS];
1304   GURL urlCopy(url);
1305   base::WeakNSObject<CRWWebController> weakSelf(self);
1306   [self evaluateJavaScript:combinedJS
1307        stringResultHandler:^(NSString*, NSError*) {
1308          if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
1309            return;
1310          base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
1311          strongSelf.get()->_URLOnStartLoading = urlCopy;
1312          strongSelf.get()->_lastRegisteredRequestURL = urlCopy;
1313        }];
1316 // Load the current URL in a web view, first ensuring the web view is visible.
1317 // If a native controller is present, remove it and swap a new web view in
1318 // its place.
1319 - (void)loadCurrentURLInWebView {
1320   [self willLoadCurrentURLInWebView];
1322   // Re-register the user agent, because UIWebView sometimes loses it.
1323   // See crbug.com/228397.
1324   [self registerUserAgent];
1326   // Clear the set of URLs opened in external applications.
1327   _openedApplicationURL.reset([[NSMutableSet alloc] init]);
1329   // Load the url. The UIWebView delegate callbacks take care of updating the
1330   // session history and UI.
1331   const GURL targetURL([self currentNavigationURL]);
1332   if (!targetURL.is_valid())
1333     return;
1335   // JavaScript should never be evaluated here. User-entered JS should be
1336   // evaluated via stringByEvaluatingUserJavaScriptFromString.
1337   DCHECK(!targetURL.SchemeIs(url::kJavaScriptScheme));
1338   [self ensureWebViewCreated];
1340   DCHECK(self.webView && !self.nativeController);
1341   NSMutableURLRequest* request =
1342       [NSMutableURLRequest requestWithURL:net::NSURLWithGURL(targetURL)];
1343   const web::Referrer referrer([self currentSessionEntryReferrer]);
1344   if (referrer.url.is_valid()) {
1345     std::string referrerValue =
1346         web::ReferrerHeaderValueForNavigation(targetURL, referrer);
1347     if (!referrerValue.empty()) {
1348       [request setValue:base::SysUTF8ToNSString(referrerValue)
1349           forHTTPHeaderField:kReferrerHeaderName];
1350     }
1351   }
1353   // If there are headers in the current session entry add them to |request|.
1354   // Headers that would overwrite fields already present in |request| are
1355   // skipped.
1356   NSDictionary* headers = [self currentHttpHeaders];
1357   for (NSString* headerName in headers) {
1358     if (![request valueForHTTPHeaderField:headerName]) {
1359       [request setValue:[headers objectForKey:headerName]
1360           forHTTPHeaderField:headerName];
1361     }
1362   }
1364   NSData* postData = [self currentPOSTData];
1365   if (postData) {
1366     web::NavigationItemImpl* currentItem =
1367         [self currentSessionEntry].navigationItemImpl;
1368     if ([postData length] > 0 &&
1369         !(currentItem && currentItem->ShouldSkipResubmitDataConfirmation())) {
1370       id cancelBlock = ^{
1371         [self registerLoadRequest:[self currentNavigationURL]
1372                          referrer:[self currentSessionEntryReferrer]
1373                        transition:[self currentTransition]];
1374         [self loadRequest:request];
1375       };
1376       id continueBlock = ^{
1377         [request setHTTPMethod:@"POST"];
1378         [request setHTTPBody:[self currentPOSTData]];
1379         [request setAllHTTPHeaderFields:[self currentHttpHeaders]];
1380         [self registerLoadRequest:[self currentNavigationURL]
1381                          referrer:[self currentSessionEntryReferrer]
1382                        transition:[self currentTransition]];
1383         [self loadRequest:request];
1384       };
1385       [_delegate webController:self
1386           onFormResubmissionForRequest:request
1387                          continueBlock:continueBlock
1388                            cancelBlock:cancelBlock];
1389       return;
1390     } else {
1391       // The user does not need to confirm if POST data is empty.
1392       [request setHTTPMethod:@"POST"];
1393       [request setHTTPBody:postData];
1394       [request setAllHTTPHeaderFields:[self currentHttpHeaders]];
1395     }
1396   }
1398   // registerLoadRequest will be called when load is about to begin.
1399   // The phase at that point is guaranteed to be web::LOAD_REQUESTED.
1400   // However the delegate is not immediately called.
1401   [self registerLoadRequest:targetURL
1402                    referrer:referrer
1403                  transition:[self currentTransition]];
1404   [self loadRequest:request];
1407 - (void)loadNativeViewWithSuccess:(BOOL)loadSuccess {
1408   const GURL currentURL([self currentURL]);
1409   [self didStartLoadingURL:currentURL updateHistory:loadSuccess];
1410   _loadPhase = web::PAGE_LOADED;
1412   // Perform post-load-finished updates.
1413   [self didFinishWithURL:currentURL loadSuccess:loadSuccess];
1415   // Inform the embedder the title changed.
1416   if ([_delegate respondsToSelector:@selector(webController:titleDidChange:)]) {
1417     NSString* title = [self.nativeController title];
1418     // If a title is present, notify the delegate.
1419     if (title)
1420       [_delegate webController:self titleDidChange:title];
1421     // If the controller handles title change notification, route those to the
1422     // delegate.
1423     if ([self.nativeController respondsToSelector:@selector(setDelegate:)]) {
1424       [self.nativeController setDelegate:self];
1425     }
1426   }
1429 - (void)loadErrorInNativeView:(NSError*)error {
1430   [self removeWebViewAllowingCachedReconstruction:NO];
1432   const GURL currentUrl = [self currentNavigationURL];
1433   BOOL isPost = [self currentPOSTData] != nil;
1435   error = web::NetErrorFromError(error);
1436   [self setNativeController:[_nativeProvider controllerForURL:currentUrl
1437                                                     withError:error
1438                                                        isPost:isPost]];
1439   [self loadNativeViewWithSuccess:NO];
1442 // Load the current URL in a native controller, retrieved from the native
1443 // provider. Call |loadNativeViewWithSuccess:YES| to load the native controller.
1444 - (void)loadCurrentURLInNativeView {
1445   // Free the web view.
1446   [self removeWebViewAllowingCachedReconstruction:NO];
1448   const GURL targetURL = [self currentNavigationURL];
1449   const web::Referrer referrer;
1450   // Unlike the WebView case, always create a new controller and view.
1451   // TODO(pinkerton): What to do if this does return nil?
1452   [self setNativeController:[_nativeProvider controllerForURL:targetURL]];
1453   [self registerLoadRequest:targetURL
1454                    referrer:referrer
1455                  transition:[self currentTransition]];
1456   [self loadNativeViewWithSuccess:YES];
1459 - (void)loadWithParams:(const web::WebLoadParams&)originalParams {
1460   // Make a copy of |params|, as some of the delegate methods may modify it.
1461   web::WebLoadParams params(originalParams);
1463   // Initiating a navigation from the UI, record the current page state before
1464   // the new page loads. Don't record for back/forward, as the current entry
1465   // has already been moved to the next entry in the history. Do, however,
1466   // record it for general reload.
1467   // TODO(jimblackler): consider a single unified call to record state whenever
1468   // the page is about to be changed. This cannot currently be done after
1469   // addPendingEntry is called.
1471   [_delegate webWillInitiateLoadWithParams:params];
1473   GURL navUrl = params.url;
1474   ui::PageTransition transition = params.transition_type;
1476   BOOL initialNavigation = NO;
1477   BOOL forwardBack =
1478       PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD) &&
1479       (transition & ui::PAGE_TRANSITION_FORWARD_BACK);
1480   if (forwardBack) {
1481     // Setting these for back/forward is not supported.
1482     DCHECK(!params.extra_headers);
1483     DCHECK(!params.post_data);
1484   } else {
1485     // TODO(stuartmorgan): Why doesn't recordStateInHistory get called for
1486     // forward/back transitions?
1487     [self recordStateInHistory];
1489     CRWSessionController* history =
1490         _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1491     if (!self.currentSessionEntry)
1492       initialNavigation = YES;
1493     [history addPendingEntry:navUrl
1494                     referrer:params.referrer
1495                   transition:transition
1496            rendererInitiated:params.is_renderer_initiated];
1497     web::NavigationItemImpl* addedItem =
1498         [self currentSessionEntry].navigationItemImpl;
1499     DCHECK(addedItem);
1500     if (params.extra_headers)
1501       addedItem->AddHttpRequestHeaders(params.extra_headers);
1502     if (params.post_data) {
1503       DCHECK([addedItem->GetHttpRequestHeaders() objectForKey:@"Content-Type"])
1504           << "Post data should have an associated content type";
1505       addedItem->SetPostData(params.post_data);
1506       addedItem->SetShouldSkipResubmitDataConfirmation(true);
1507     }
1508   }
1510   [_delegate webDidUpdateSessionForLoadWithParams:params
1511                              wasInitialNavigation:initialNavigation];
1513   // If a non-default cache mode is passed in, it takes precedence over
1514   // |reload|.
1515   const BOOL reload = [self shouldReload:navUrl transition:transition];
1516   if (params.cache_mode != net::RequestTracker::CACHE_NORMAL) {
1517     _webStateImpl->SetCacheMode(params.cache_mode);
1518   } else if (reload) {
1519     _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_RELOAD);
1520   }
1522   [self loadCurrentURL];
1524   // Change the cache mode back to CACHE_NORMAL after a reload.
1525   _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1528 - (void)loadCurrentURL {
1529   // If the content view doesn't exist, the tab has either been evicted, or
1530   // never displayed. Bail, and let the URL be loaded when the tab is shown.
1531   if (!self.containerView)
1532     return;
1534   // Reset current WebUI if one exists.
1535   [self clearWebUI];
1537   // Precaution, so that the outgoing URL is registered, to reduce the risk of
1538   // it being seen as a fresh URL later by the same method (and new page change
1539   // erroneously reported).
1540   [self checkForUnexpectedURLChange];
1542   // Abort any outstanding page load. This ensures the delegate gets informed
1543   // about the outgoing page, and further messages from the page are suppressed.
1544   if (_loadPhase != web::PAGE_LOADED)
1545     [self abortLoad];
1547   DCHECK(!_isHalted);
1548   // Remove the transient content view.
1549   [self clearTransientContentView];
1551   const GURL currentURL = [self currentNavigationURL];
1552   // If it's a chrome URL, but not a native one, create the WebUI instance.
1553   if (web::GetWebClient()->IsAppSpecificURL(currentURL) &&
1554       ![_nativeProvider hasControllerForURL:currentURL]) {
1555     [self createWebUIForURL:currentURL];
1556   }
1558   // Loading a new url, must check here if it's a native chrome URL and
1559   // replace the appropriate view if so, or transition back to a web view from
1560   // a native view.
1561   if ([self shouldLoadURLInNativeView:currentURL]) {
1562     [self loadCurrentURLInNativeView];
1563   } else {
1564     [self loadCurrentURLInWebView];
1565   }
1567   // Once a URL has been loaded, any cached-based reconstruction state has
1568   // either been handled or obsoleted.
1569   _expectedReconstructionURL = GURL();
1572 - (BOOL)shouldLoadURLInNativeView:(const GURL&)url {
1573   // App-specific URLs that don't require WebUI are loaded in native views.
1574   return web::GetWebClient()->IsAppSpecificURL(url) &&
1575          !_webStateImpl->HasWebUI();
1578 - (void)triggerPendingLoad {
1579   if (!self.containerView) {
1580     DCHECK(!_isBeingDestroyed);
1581     // Create the top-level parent view, which will contain the content (whether
1582     // native or web). Note, this needs to be created with a non-zero size
1583     // to allow for (native) subviews with autosize constraints to be correctly
1584     // processed.
1585     _containerView.reset([[CRWWebControllerContainerView alloc]
1586         initWithContentViewProxy:_webViewProxy]);
1587     self.containerView.frame = [[UIScreen mainScreen] bounds];
1588     [self.containerView addGestureRecognizer:[self touchTrackingRecognizer]];
1589     [self.containerView setAccessibilityIdentifier:web::kContainerViewID];
1590     // Is |currentUrl| a web scheme or native chrome scheme.
1591     BOOL isChromeScheme =
1592         web::GetWebClient()->IsAppSpecificURL([self currentNavigationURL]);
1594     // Don't immediately load the web page if in overlay mode. Always load if
1595     // native.
1596     if (isChromeScheme || !_overlayPreviewMode) {
1597       // TODO(jimblackler): end the practice of calling |loadCurrentURL| when it
1598       // is possible there is no current URL. If the call performs necessary
1599       // initialization, break that out.
1600       [self loadCurrentURL];
1601     }
1603     // Display overlay view until current url has finished loading or delay and
1604     // then transition away.
1605     if ((_overlayPreviewMode || _usePlaceholderOverlay) && !isChromeScheme)
1606       [self addPlaceholderOverlay];
1608     // Don't reset the overlay flag if in preview mode.
1609     if (!_overlayPreviewMode)
1610       _usePlaceholderOverlay = NO;
1611   }
1614 - (BOOL)shouldReload:(const GURL&)destinationURL
1615           transition:(ui::PageTransition)transition {
1616   // Do a reload if the user hits enter in the address bar or re-types a URL.
1617   CRWSessionController* sessionController =
1618       _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1619   web::NavigationItem* item =
1620       _webStateImpl->GetNavigationManagerImpl().GetVisibleItem();
1621   return (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) && item &&
1622          (destinationURL == item->GetURL() ||
1623           destinationURL == [sessionController currentEntry].originalUrl);
1626 // Reload either the web view or the native content depending on which is
1627 // displayed.
1628 - (void)reloadInternal {
1629   web::RecordAction(UserMetricsAction("Reload"));
1630   if (self.webView) {
1631     // Just as we don't use the WebView native back and forward navigation
1632     // (preferring to load the URLs manually) we don't use the native reload.
1633     // This ensures state processing and delegate calls are consistent.
1634     [self loadCurrentURL];
1635   } else {
1636     [self.nativeController reload];
1637   }
1640 - (void)reload {
1641   [_delegate webWillReload];
1643   _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_RELOAD);
1644   [self reloadInternal];
1645   _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1648 - (void)loadCancelled {
1649   // Current load will not complete; this should be communicated upstream to the
1650   // delegate.
1651   switch (_loadPhase) {
1652     case web::LOAD_REQUESTED:
1653       // Load phase after abort is always PAGE_LOADED.
1654       _loadPhase = web::PAGE_LOADED;
1655       if (!_isHalted) {
1656         _webStateImpl->SetIsLoading(false);
1657       }
1658       [_delegate webCancelStartLoadingRequest];
1659       break;
1660     case web::PAGE_LOADING:
1661       // The previous load never fully completed before this page change. The
1662       // loadPhase is changed to PAGE_LOADED to indicate the cycle is complete,
1663       // and the delegate is called.
1664       _loadPhase = web::PAGE_LOADED;
1665       if (!_isHalted) {
1666         // RequestTracker expects StartPageLoad to be followed by
1667         // FinishPageLoad, passing the exact same URL.
1668         self.webStateImpl->GetRequestTracker()->FinishPageLoad(
1669             _URLOnStartLoading, false);
1670       }
1671       [_delegate webLoadCancelled:_URLOnStartLoading];
1672       break;
1673     case web::PAGE_LOADED:
1674       break;
1675   }
1678 - (void)abortLoad {
1679   [self abortWebLoad];
1680   [self loadCancelled];
1683 - (void)prepareForGoBack {
1684   // Make sure any transitions that may have occurred have been seen and acted
1685   // on by the CRWWebController, so the history stack and state of the
1686   // CRWWebController is 100% up to date before the stack navigation starts.
1687   if (self.webView) {
1688     [self injectEarlyInjectionScripts];
1689     [self checkForUnexpectedURLChange];
1690   }
1691   // Discard any outstanding pending entries before adjusting the navigation
1692   // index.
1693   CRWSessionController* sessionController =
1694       _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1695   [sessionController discardNonCommittedEntries];
1697   bool wasShowingInterstitial = _webStateImpl->IsShowingWebInterstitial();
1699   // Call into the delegate before |recordStateInHistory|.
1700   // TODO(rohitrao): Can this be reordered after |recordStateInHistory|?
1701   [_delegate webDidPrepareForGoBack];
1703   // Before changing the current session history entry, record the tab state.
1704   if (!wasShowingInterstitial) {
1705     [self recordStateInHistory];
1706   }
1709 - (void)goBack {
1710   [self goDelta:-1];
1713 - (void)goForward {
1714   [self goDelta:1];
1717 - (void)goDelta:(int)delta {
1718   if (delta == 0) {
1719     [self reload];
1720     return;
1721   }
1723   // Abort if there is nothing next in the history.
1724   // Note that it is NOT checked that the history depth is at least |delta|.
1725   if ((delta < 0 && ![self canGoBack]) || (delta > 0 && ![self canGoForward])) {
1726     return;
1727   }
1729   if (delta < 0) {
1730     [self prepareForGoBack];
1731   } else {
1732     // Before changing the current session history entry, record the tab state.
1733     [self recordStateInHistory];
1734   }
1736   [_delegate webWillGoDelta:delta];
1738   CRWSessionController* sessionController =
1739       _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1740   CRWSessionEntry* fromEntry = [sessionController currentEntry];
1741   [sessionController goDelta:delta];
1742   if (fromEntry) {
1743     _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_HISTORY);
1744     [self finishHistoryNavigationFromEntry:fromEntry];
1745     _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1746   }
1749 - (BOOL)isLoaded {
1750   return _loadPhase == web::PAGE_LOADED;
1753 - (void)loadCompleteWithSuccess:(BOOL)loadSuccess {
1754   [self removePlaceholderOverlay];
1755   // The webView may have been torn down (or replaced by a native view). Be
1756   // safe and do nothing if that's happened.
1757   if (_loadPhase != web::PAGE_LOADING)
1758     return;
1760   DCHECK(self.webView);
1762   const GURL currentURL([self currentURL]);
1764   [self resetLoadState];
1765   _loadPhase = web::PAGE_LOADED;
1767   [self optOutScrollsToTopForSubviews];
1769   // Ensure the URL is as expected (and already reported to the delegate).
1770   DCHECK(currentURL == _lastRegisteredRequestURL)
1771       << std::endl
1772       << "currentURL = [" << currentURL << "]" << std::endl
1773       << "_lastRegisteredRequestURL = [" << _lastRegisteredRequestURL << "]";
1775   // Perform post-load-finished updates.
1776   [self didFinishWithURL:currentURL loadSuccess:loadSuccess];
1778   // Execute the pending LoadCompleteActions.
1779   for (ProceduralBlock action in _pendingLoadCompleteActions.get()) {
1780     action();
1781   }
1782   [_pendingLoadCompleteActions removeAllObjects];
1785 - (void)didFinishWithURL:(const GURL&)currentURL loadSuccess:(BOOL)loadSuccess {
1786   DCHECK(_loadPhase == web::PAGE_LOADED);
1787   _webStateImpl->GetRequestTracker()->FinishPageLoad(currentURL, loadSuccess);
1788   // Reset the navigation type to the default value.
1789   // Note: it is possible that the web view has already started loading the
1790   // next page when this is called. In that case the cache mode can leak to
1791   // (some of) the requests of the next page. It's expected to be an edge case,
1792   // but if it becomes a problem it should be possible to notice it afterwards
1793   // and react to it (by warning the user or reloading the page for example).
1794   _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1795   _webStateImpl->GetRequestTracker()->SetCacheModeFromUIThread(
1796       _webStateImpl->GetCacheMode());
1798   [self restoreStateFromHistory];
1799   _webStateImpl->OnPageLoaded(currentURL, loadSuccess);
1800   _webStateImpl->SetIsLoading(false);
1801   // Inform the embedder the load completed.
1802   [_delegate webDidFinishWithURL:currentURL loadSuccess:loadSuccess];
1805 - (void)finishHistoryNavigationFromEntry:(CRWSessionEntry*)fromEntry {
1806   [_delegate webWillFinishHistoryNavigationFromEntry:fromEntry];
1808   // Check if toEntry was created by a JavaScript window.history.pushState()
1809   // call from fromEntry. If it was, don't load the URL. Instead update
1810   // UIWebView's URL and dispatch a popstate event.
1811   if ([_webStateImpl->GetNavigationManagerImpl().GetSessionController()
1812           isPushStateNavigationBetweenEntry:fromEntry
1813                                    andEntry:self.currentSessionEntry]) {
1814     NSString* state = [self currentSessionEntry]
1815                           .navigationItemImpl->GetSerializedStateObject();
1816     [self finishPushStateNavigationToURL:[self currentNavigationURL]
1817                          withStateObject:state];
1818   } else {
1819     GURL activeURL = [self currentNavigationURL];
1820     GURL fromURL = fromEntry.navigationItem->GetURL();
1821     GURL endURL =
1822         [self updateURLForHistoryNavigationFromURL:fromURL toURL:activeURL];
1823     web::NavigationItem* currentItem =
1824         _webStateImpl->GetNavigationManagerImpl().GetVisibleItem();
1825     ui::PageTransition transition = ui::PageTransitionFromInt(
1826         ui::PAGE_TRANSITION_RELOAD | ui::PAGE_TRANSITION_FORWARD_BACK);
1828     web::WebLoadParams params(endURL);
1829     if (currentItem) {
1830       params.referrer = currentItem->GetReferrer();
1831     }
1832     params.transition_type = transition;
1833     [self loadWithParams:params];
1834   }
1837 - (GURL)updateURLForHistoryNavigationFromURL:(const GURL&)startURL
1838                                        toURL:(const GURL&)endURL {
1839   // Check the state of the fragments on both URLs (aka, is there a '#' in the
1840   // url or not).
1841   if (!startURL.has_ref() || endURL.has_ref()) {
1842     return endURL;
1843   }
1845   // startURL contains a fragment and endURL doesn't. Remove the fragment from
1846   // startURL and compare the resulting string to endURL. If they are equal, add
1847   // # to endURL to cause a hashchange event.
1848   GURL hashless = web::GURLByRemovingRefFromGURL(startURL);
1850   if (hashless != endURL)
1851     return endURL;
1853   url::StringPieceReplacements<std::string> emptyRef;
1854   emptyRef.SetRefStr("");
1855   GURL newEndURL = endURL.ReplaceComponents(emptyRef);
1856   web::NavigationItem* item =
1857       _webStateImpl->GetNavigationManagerImpl().GetVisibleItem();
1858   if (item)
1859     item->SetURL(newEndURL);
1860   return newEndURL;
1863 - (void)evaluateJavaScript:(NSString*)script
1864          JSONResultHandler:
1865              (void (^)(scoped_ptr<base::Value>, NSError*))handler {
1866   [self evaluateJavaScript:script
1867        stringResultHandler:^(NSString* stringResult, NSError* error) {
1868          DCHECK(stringResult || error);
1869          if (handler) {
1870            scoped_ptr<base::Value> result(
1871                base::JSONReader::Read(base::SysNSStringToUTF8(stringResult)));
1872            handler(result.Pass(), error);
1873          }
1874        }];
1877 - (void)addGestureRecognizerToWebView:(UIGestureRecognizer*)recognizer {
1878   if ([_gestureRecognizers containsObject:recognizer])
1879     return;
1881   [self.webView addGestureRecognizer:recognizer];
1882   [_gestureRecognizers addObject:recognizer];
1885 - (void)removeGestureRecognizerFromWebView:(UIGestureRecognizer*)recognizer {
1886   if (![_gestureRecognizers containsObject:recognizer])
1887     return;
1889   [self.webView removeGestureRecognizer:recognizer];
1890   [_gestureRecognizers removeObject:recognizer];
1893 - (void)addToolbarViewToWebView:(UIView*)toolbarView {
1894   DCHECK(toolbarView);
1895   if ([_webViewToolbars containsObject:toolbarView])
1896     return;
1897   [_webViewToolbars addObject:toolbarView];
1898   if (self.webView)
1899     [self.containerView addToolbar:toolbarView];
1902 - (void)removeToolbarViewFromWebView:(UIView*)toolbarView {
1903   if (![_webViewToolbars containsObject:toolbarView])
1904     return;
1905   [_webViewToolbars removeObject:toolbarView];
1906   if (self.webView)
1907     [self.containerView removeToolbar:toolbarView];
1910 - (CRWJSInjectionReceiver*)jsInjectionReceiver {
1911   return _jsInjectionReceiver;
1914 - (BOOL)cancellable {
1915   return self.sessionController.openedByDOM &&
1916          !self.sessionController.lastCommittedEntry;
1919 - (BOOL)isBeingDestroyed {
1920   return _isBeingDestroyed;
1923 - (BOOL)isHalted {
1924   return _isHalted;
1927 - (web::ReferrerPolicy)referrerPolicyFromString:(const std::string&)policy {
1928   // TODO(stuartmorgan): Remove this temporary bridge to the helper function
1929   // once the referrer handling moves into the subclasses.
1930   return web::ReferrerPolicyFromString(policy);
1933 #pragma mark -
1934 #pragma mark CRWJSInjectionEvaluator Methods
1936 - (void)evaluateJavaScript:(NSString*)script
1937        stringResultHandler:(web::JavaScriptCompletion)handler {
1938   // Subclasses must implement this method.
1939   NOTREACHED();
1942 - (BOOL)scriptHasBeenInjectedForClass:(Class)jsInjectionManagerClass
1943                        presenceBeacon:(NSString*)beacon {
1944   // Subclasses must implement this method.
1945   NOTREACHED();
1946   return NO;
1949 - (void)injectScript:(NSString*)script forClass:(Class)JSInjectionManagerClass {
1950   // Make sure that CRWJSEarlyScriptManager has been injected.
1951   BOOL ealyScriptInjected =
1952       [self scriptHasBeenInjectedForClass:[CRWJSEarlyScriptManager class]
1953                            presenceBeacon:[_earlyScriptManager presenceBeacon]];
1954   if (!ealyScriptInjected &&
1955       JSInjectionManagerClass != [CRWJSEarlyScriptManager class]) {
1956     [_earlyScriptManager inject];
1957   }
1960 - (web::WebViewType)webViewType {
1961   // Subclasses must implement this method.
1962   NOTREACHED();
1963   return web::UI_WEB_VIEW_TYPE;
1966 #pragma mark -
1968 - (void)evaluateUserJavaScript:(NSString*)script {
1969   // Subclasses must implement this method.
1970   NOTREACHED();
1973 - (void)didFinishNavigation {
1974   // This can be called at multiple times after the document has loaded. Do
1975   // nothing if the document has already loaded.
1976   if (_loadPhase == web::PAGE_LOADED)
1977     return;
1978   [self loadCompleteWithSuccess:YES];
1981 - (BOOL)respondToMessage:(base::DictionaryValue*)message
1982        userIsInteracting:(BOOL)userIsInteracting
1983                originURL:(const GURL&)originURL {
1984   std::string command;
1985   if (!message->GetString("command", &command)) {
1986     DLOG(WARNING) << "JS message parameter not found: command";
1987     return NO;
1988   }
1990   SEL handler = [self selectorToHandleJavaScriptCommand:command];
1991   if (!handler) {
1992     if (!self.webStateImpl->OnScriptCommandReceived(
1993             command, *message, originURL, userIsInteracting)) {
1994       // Message was either unexpected or not correctly handled.
1995       // Page is reset as a precaution.
1996       DLOG(WARNING) << "Unexpected message received: " << command;
1997       return NO;
1998     }
1999     return YES;
2000   }
2002   typedef BOOL (*HandlerType)(id, SEL, base::DictionaryValue*, NSDictionary*);
2003   HandlerType handlerImplementation =
2004       reinterpret_cast<HandlerType>([self methodForSelector:handler]);
2005   DCHECK(handlerImplementation);
2006   NSMutableDictionary* context =
2007       [NSMutableDictionary dictionaryWithObject:@(userIsInteracting)
2008                                          forKey:web::kUserIsInteractingKey];
2009   NSURL* originNSURL = net::NSURLWithGURL(originURL);
2010   if (originNSURL)
2011     context[web::kOriginURLKey] = originNSURL;
2012   return handlerImplementation(self, handler, message, context);
2015 - (SEL)selectorToHandleJavaScriptCommand:(const std::string&)command {
2016   static std::map<std::string, SEL>* handlers = nullptr;
2017   static dispatch_once_t onceToken;
2018   dispatch_once(&onceToken, ^{
2019     handlers = new std::map<std::string, SEL>();
2020     (*handlers)["addPluginPlaceholders"] =
2021         @selector(handleAddPluginPlaceholdersMessage:context:);
2022     (*handlers)["chrome.send"] = @selector(handleChromeSendMessage:context:);
2023     (*handlers)["console"] = @selector(handleConsoleMessage:context:);
2024     (*handlers)["dialog.suppressed"] =
2025         @selector(handleDialogSuppressedMessage:context:);
2026     (*handlers)["dialog.willShow"] =
2027         @selector(handleDialogWillShowMessage:context:);
2028     (*handlers)["document.favicons"] =
2029         @selector(handleDocumentFaviconsMessage:context:);
2030     (*handlers)["document.retitled"] =
2031         @selector(handleDocumentRetitledMessage:context:);
2032     (*handlers)["document.submit"] =
2033         @selector(handleDocumentSubmitMessage:context:);
2034     (*handlers)["externalRequest"] =
2035         @selector(handleExternalRequestMessage:context:);
2036     (*handlers)["form.activity"] =
2037         @selector(handleFormActivityMessage:context:);
2038     (*handlers)["form.requestAutocomplete"] =
2039         @selector(handleFormRequestAutocompleteMessage:context:);
2040     (*handlers)["navigator.credentials.request"] =
2041         @selector(handleCredentialsRequestedMessage:context:);
2042     (*handlers)["navigator.credentials.notifySignedIn"] =
2043         @selector(handleSignedInMessage:context:);
2044     (*handlers)["navigator.credentials.notifySignedOut"] =
2045         @selector(handleSignedOutMessage:context:);
2046     (*handlers)["navigator.credentials.notifyFailedSignIn"] =
2047         @selector(handleSignInFailedMessage:context:);
2048     (*handlers)["resetExternalRequest"] =
2049         @selector(handleResetExternalRequestMessage:context:);
2050     (*handlers)["window.close.self"] =
2051         @selector(handleWindowCloseSelfMessage:context:);
2052     (*handlers)["window.error"] = @selector(handleWindowErrorMessage:context:);
2053     (*handlers)["window.hashchange"] =
2054         @selector(handleWindowHashChangeMessage:context:);
2055     (*handlers)["window.history.back"] =
2056         @selector(handleWindowHistoryBackMessage:context:);
2057     (*handlers)["window.history.willChangeState"] =
2058         @selector(handleWindowHistoryWillChangeStateMessage:context:);
2059     (*handlers)["window.history.didPushState"] =
2060         @selector(handleWindowHistoryDidPushStateMessage:context:);
2061     (*handlers)["window.history.didReplaceState"] =
2062         @selector(handleWindowHistoryDidReplaceStateMessage:context:);
2063     (*handlers)["window.history.forward"] =
2064         @selector(handleWindowHistoryForwardMessage:context:);
2065     (*handlers)["window.history.go"] =
2066         @selector(handleWindowHistoryGoMessage:context:);
2067   });
2068   DCHECK(handlers);
2069   auto iter = handlers->find(command);
2070   return iter != handlers->end() ? iter->second : nullptr;
2073 #pragma mark -
2074 #pragma mark JavaScript message handlers
2076 - (BOOL)handleAddPluginPlaceholdersMessage:(base::DictionaryValue*)message
2077                                    context:(NSDictionary*)context {
2078   // Inject the script that adds the plugin placeholders.
2079   [[_jsInjectionReceiver
2080       instanceOfClass:[CRWJSPluginPlaceholderManager class]] inject];
2081   return YES;
2084 - (BOOL)handleChromeSendMessage:(base::DictionaryValue*)message
2085                         context:(NSDictionary*)context {
2086   if (_webStateImpl->HasWebUI()) {
2087     const GURL currentURL([self currentURL]);
2088     if (web::GetWebClient()->IsAppSpecificURL(currentURL)) {
2089       std::string messageContent;
2090       base::ListValue* arguments = nullptr;
2091       if (!message->GetString("message", &messageContent)) {
2092         DLOG(WARNING) << "JS message parameter not found: message";
2093         return NO;
2094       }
2095       if (!message->GetList("arguments", &arguments)) {
2096         DLOG(WARNING) << "JS message parameter not found: arguments";
2097         return NO;
2098       }
2099       _webStateImpl->OnScriptCommandReceived(
2100           messageContent, *message, currentURL,
2101           context[web::kUserIsInteractingKey]);
2102       _webStateImpl->ProcessWebUIMessage(currentURL, messageContent,
2103                                          *arguments);
2104       return YES;
2105     }
2106   }
2108   DLOG(WARNING)
2109       << "chrome.send message not handled because WebUI was not found.";
2110   return NO;
2113 - (BOOL)handleConsoleMessage:(base::DictionaryValue*)message
2114                      context:(NSDictionary*)context {
2115   // Do not log if JS logging is off.
2116   if (![[NSUserDefaults standardUserDefaults] boolForKey:web::kLogJavaScript]) {
2117     return YES;
2118   }
2120   std::string method;
2121   if (!message->GetString("method", &method)) {
2122     DLOG(WARNING) << "JS message parameter not found: method";
2123     return NO;
2124   }
2125   std::string consoleMessage;
2126   if (!message->GetString("message", &consoleMessage)) {
2127     DLOG(WARNING) << "JS message parameter not found: message";
2128     return NO;
2129   }
2130   std::string origin;
2131   if (!message->GetString("origin", &origin)) {
2132     DLOG(WARNING) << "JS message parameter not found: origin";
2133     return NO;
2134   }
2136   DVLOG(0) << origin << " [" << method << "] " << consoleMessage;
2137   return YES;
2140 - (BOOL)handleDialogSuppressedMessage:(base::DictionaryValue*)message
2141                               context:(NSDictionary*)context {
2142   if ([_delegate
2143           respondsToSelector:@selector(webControllerDidSuppressDialog:)]) {
2144     [_delegate webControllerDidSuppressDialog:self];
2145   }
2146   return YES;
2149 - (BOOL)handleDialogWillShowMessage:(base::DictionaryValue*)message
2150                             context:(NSDictionary*)context {
2151   if ([_delegate respondsToSelector:@selector(webControllerWillShowDialog:)]) {
2152     [_delegate webControllerWillShowDialog:self];
2153   }
2154   return YES;
2157 - (BOOL)handleDocumentFaviconsMessage:(base::DictionaryValue*)message
2158                               context:(NSDictionary*)context {
2159   base::ListValue* favicons = nullptr;
2160   if (!message->GetList("favicons", &favicons)) {
2161     DLOG(WARNING) << "JS message parameter not found: favicons";
2162     return NO;
2163   }
2164   std::vector<web::FaviconURL> urls;
2165   for (size_t fav_idx = 0; fav_idx != favicons->GetSize(); ++fav_idx) {
2166     base::DictionaryValue* favicon = nullptr;
2167     if (!favicons->GetDictionary(fav_idx, &favicon))
2168       return NO;
2169     std::string href;
2170     std::string rel;
2171     if (!favicon->GetString("href", &href)) {
2172       DLOG(WARNING) << "JS message parameter not found: href";
2173       return NO;
2174     }
2175     if (!favicon->GetString("rel", &rel)) {
2176       DLOG(WARNING) << "JS message parameter not found: rel";
2177       return NO;
2178     }
2179     web::FaviconURL::IconType icon_type = web::FaviconURL::FAVICON;
2180     if (rel == "apple-touch-icon")
2181       icon_type = web::FaviconURL::TOUCH_ICON;
2182     else if (rel == "apple-touch-icon-precomposed")
2183       icon_type = web::FaviconURL::TOUCH_PRECOMPOSED_ICON;
2184     urls.push_back(
2185         web::FaviconURL(GURL(href), icon_type, std::vector<gfx::Size>()));
2186   }
2187   if (!urls.empty())
2188     _webStateImpl->OnFaviconUrlUpdated(urls);
2189   return YES;
2192 - (BOOL)handleDocumentSubmitMessage:(base::DictionaryValue*)message
2193                             context:(NSDictionary*)context {
2194   std::string href;
2195   if (!message->GetString("href", &href)) {
2196     DLOG(WARNING) << "JS message parameter not found: href";
2197     return NO;
2198   }
2199   const GURL targetURL(href);
2200   const GURL currentURL([self currentURL]);
2201   bool targetsFrame = false;
2202   message->GetBoolean("targetsFrame", &targetsFrame);
2203   if (!targetsFrame && web::UrlHasWebScheme(targetURL)) {
2204     // The referrer is not known yet, and will be updated later.
2205     const web::Referrer emptyReferrer;
2206     [self registerLoadRequest:targetURL
2207                      referrer:emptyReferrer
2208                    transition:ui::PAGE_TRANSITION_FORM_SUBMIT];
2209   }
2210   std::string formName;
2211   message->GetString("formName", &formName);
2212   base::scoped_nsobject<NSSet> observers([_observers copy]);
2213   // We decide the form is user-submitted if the user has interacted with
2214   // the main page (using logic from the popup blocker), or if the keyboard
2215   // is visible.
2216   BOOL submittedByUser = [context[web::kUserIsInteractingKey] boolValue] ||
2217                          [_webViewProxy getKeyboardAccessory];
2218   _webStateImpl->OnDocumentSubmitted(formName, submittedByUser);
2219   return YES;
2222 - (BOOL)handleExternalRequestMessage:(base::DictionaryValue*)message
2223                              context:(NSDictionary*)context {
2224   std::string href;
2225   std::string target;
2226   std::string referrerPolicy;
2227   if (!message->GetString("href", &href)) {
2228     DLOG(WARNING) << "JS message parameter not found: href";
2229     return NO;
2230   }
2231   if (!message->GetString("target", &target)) {
2232     DLOG(WARNING) << "JS message parameter not found: target";
2233     return NO;
2234   }
2235   if (!message->GetString("referrerPolicy", &referrerPolicy)) {
2236     DLOG(WARNING) << "JS message parameter not found: referrerPolicy";
2237     return NO;
2238   }
2239   // Round-trip the href through NSURL; this URL will be compared as a
2240   // string against a UIWebView-provided NSURL later, and must match exactly
2241   // for the new window to trigger, so the escaping needs to be NSURL-style.
2242   // TODO(stuartmorgan): Comparing against a URL whose exact formatting we
2243   // don't control is fundamentally fragile; try to find another
2244   // way of handling this.
2245   DCHECK(context[web::kUserIsInteractingKey]);
2246   NSString* windowName =
2247       base::SysUTF8ToNSString(href + web::kWindowNameSeparator + target);
2248   _externalRequest.reset(new web::NewWindowInfo(
2249       net::GURLWithNSURL(net::NSURLWithGURL(GURL(href))), windowName,
2250       web::ReferrerPolicyFromString(referrerPolicy),
2251       [context[web::kUserIsInteractingKey] boolValue]));
2252   return YES;
2255 - (BOOL)handleFormActivityMessage:(base::DictionaryValue*)message
2256                           context:(NSDictionary*)context {
2257   std::string formName;
2258   std::string fieldName;
2259   std::string type;
2260   std::string value;
2261   int keyCode = web::WebStateObserver::kInvalidFormKeyCode;
2262   bool inputMissing = false;
2263   if (!message->GetString("formName", &formName) ||
2264       !message->GetString("fieldName", &fieldName) ||
2265       !message->GetString("type", &type) ||
2266       !message->GetString("value", &value)) {
2267     inputMissing = true;
2268   }
2270   if (!message->GetInteger("keyCode", &keyCode) || keyCode < 0)
2271     keyCode = web::WebStateObserver::kInvalidFormKeyCode;
2272   _webStateImpl->OnFormActivityRegistered(formName, fieldName, type, value,
2273                                           keyCode, inputMissing);
2274   return YES;
2277 - (BOOL)handleFormRequestAutocompleteMessage:(base::DictionaryValue*)message
2278                                      context:(NSDictionary*)context {
2279   std::string formName;
2280   if (!message->GetString("formName", &formName)) {
2281     DLOG(WARNING) << "JS message parameter not found: formName";
2282     return NO;
2283   }
2284   DCHECK(context[web::kUserIsInteractingKey]);
2285   _webStateImpl->OnAutocompleteRequested(
2286       net::GURLWithNSURL(context[web::kOriginURLKey]), formName,
2287       [context[web::kUserIsInteractingKey] boolValue]);
2288   return YES;
2291 - (BOOL)handleCredentialsRequestedMessage:(base::DictionaryValue*)message
2292                                   context:(NSDictionary*)context {
2293   int request_id = -1;
2294   if (!message->GetInteger("requestId", &request_id)) {
2295     DLOG(WARNING) << "JS message parameter not found: requestId";
2296     return NO;
2297   }
2298   bool suppress_ui = false;
2299   if (!message->GetBoolean("suppressUI", &suppress_ui)) {
2300     DLOG(WARNING) << "JS message parameter not found: suppressUI";
2301     return NO;
2302   }
2303   base::ListValue* federations_value = nullptr;
2304   if (!message->GetList("federations", &federations_value)) {
2305     DLOG(WARNING) << "JS message parameter not found: federations";
2306     return NO;
2307   }
2308   std::vector<std::string> federations;
2309   for (auto federation_value : *federations_value) {
2310     std::string federation;
2311     if (!federation_value->GetAsString(&federation)) {
2312       DLOG(WARNING) << "JS message parameter 'federations' contains wrong type";
2313       return NO;
2314     }
2315     federations.push_back(federation);
2316   }
2317   DCHECK(context[web::kUserIsInteractingKey]);
2318   _webStateImpl->OnCredentialsRequested(
2319       request_id, net::GURLWithNSURL(context[web::kOriginURLKey]), suppress_ui,
2320       federations, [context[web::kUserIsInteractingKey] boolValue]);
2321   return YES;
2324 - (BOOL)handleSignedInMessage:(base::DictionaryValue*)message
2325                       context:(NSDictionary*)context {
2326   int request_id = -1;
2327   if (!message->GetInteger("requestId", &request_id)) {
2328     DLOG(WARNING) << "JS message parameter not found: requestId";
2329     return NO;
2330   }
2331   base::DictionaryValue* credential_data = nullptr;
2332   web::Credential credential;
2333   if (message->GetDictionary("credential", &credential_data)) {
2334     if (!web::DictionaryValueToCredential(*credential_data, &credential)) {
2335       DLOG(WARNING) << "JS message parameter 'credential' is invalid";
2336       return NO;
2337     }
2338     _webStateImpl->OnSignedIn(request_id,
2339                               net::GURLWithNSURL(context[web::kOriginURLKey]),
2340                               credential);
2341   } else {
2342     _webStateImpl->OnSignedIn(request_id,
2343                               net::GURLWithNSURL(context[web::kOriginURLKey]));
2344   }
2345   return YES;
2348 - (BOOL)handleSignedOutMessage:(base::DictionaryValue*)message
2349                        context:(NSDictionary*)context {
2350   int request_id = -1;
2351   if (!message->GetInteger("requestId", &request_id)) {
2352     DLOG(WARNING) << "JS message parameter not found: requestId";
2353     return NO;
2354   }
2355   _webStateImpl->OnSignedOut(request_id,
2356                              net::GURLWithNSURL(context[web::kOriginURLKey]));
2357   return YES;
2360 - (BOOL)handleSignInFailedMessage:(base::DictionaryValue*)message
2361                           context:(NSDictionary*)context {
2362   int request_id = -1;
2363   if (!message->GetInteger("requestId", &request_id)) {
2364     DLOG(WARNING) << "JS message parameter not found: requestId";
2365     return NO;
2366   }
2367   base::DictionaryValue* credential_data = nullptr;
2368   web::Credential credential;
2369   if (message->GetDictionary("credential", &credential_data)) {
2370     if (!web::DictionaryValueToCredential(*credential_data, &credential)) {
2371       DLOG(WARNING) << "JS message parameter 'credential' is invalid";
2372       return NO;
2373     }
2374     _webStateImpl->OnSignInFailed(
2375         request_id, net::GURLWithNSURL(context[web::kOriginURLKey]),
2376         credential);
2377   } else {
2378     _webStateImpl->OnSignInFailed(
2379         request_id, net::GURLWithNSURL(context[web::kOriginURLKey]));
2380   }
2381   return YES;
2384 - (BOOL)handleResetExternalRequestMessage:(base::DictionaryValue*)message
2385                                   context:(NSDictionary*)context {
2386   _externalRequest.reset();
2387   return YES;
2390 - (BOOL)handleWindowCloseSelfMessage:(base::DictionaryValue*)message
2391                              context:(NSDictionary*)context {
2392   [self orderClose];
2393   return YES;
2396 - (BOOL)handleWindowErrorMessage:(base::DictionaryValue*)message
2397                          context:(NSDictionary*)context {
2398   std::string errorMessage;
2399   if (!message->GetString("message", &errorMessage)) {
2400     DLOG(WARNING) << "JS message parameter not found: message";
2401     return NO;
2402   }
2403   DLOG(ERROR) << "JavaScript error: " << errorMessage
2404               << " URL:" << [self currentURL].spec();
2405   return YES;
2408 - (BOOL)handleWindowHashChangeMessage:(base::DictionaryValue*)message
2409                               context:(NSDictionary*)context {
2410   [self checkForUnexpectedURLChange];
2412   // Notify the observers.
2413   _webStateImpl->OnUrlHashChanged();
2414   return YES;
2417 - (BOOL)handleWindowHistoryBackMessage:(base::DictionaryValue*)message
2418                                context:(NSDictionary*)context {
2419   [self goBack];
2420   return YES;
2423 - (BOOL)handleWindowHistoryForwardMessage:(base::DictionaryValue*)message
2424                                   context:(NSDictionary*)context {
2425   [self goForward];
2426   return YES;
2429 - (BOOL)handleWindowHistoryGoMessage:(base::DictionaryValue*)message
2430                              context:(NSDictionary*)context {
2431   int delta;
2432   message->GetInteger("value", &delta);
2433   [self goDelta:delta];
2434   return YES;
2437 - (BOOL)handleWindowHistoryWillChangeStateMessage:(base::DictionaryValue*)unused
2438                                           context:(NSDictionary*)unusedContext {
2439   // This dummy handler is a workaround for crbug.com/490673. Issue was
2440   // happening when two sequential calls of window.history.pushState were
2441   // performed by the page. In that case state was changed twice before
2442   // first change was reported to embedder (and first URL change was reported
2443   // incorrectly).
2445   // Using dummy handler for window.history.willChangeState message holds
2446   // second state change until the first change is reported, because messages
2447   // are queued. This is essentially a sleep, and not the real fix of the
2448   // problem. TODO(eugenebut): refactor handleWindowHistoryDidPushStateMessage:
2449   // to avoid this "sleep".
2450   return YES;
2453 - (BOOL)handleWindowHistoryDidPushStateMessage:(base::DictionaryValue*)message
2454                                        context:(NSDictionary*)context {
2455   std::string pageURL;
2456   std::string baseURL;
2457   if (!message->GetString("pageUrl", &pageURL) ||
2458       !message->GetString("baseUrl", &baseURL)) {
2459     DLOG(WARNING) << "JS message parameter not found: pageUrl or baseUrl";
2460     return NO;
2461   }
2462   GURL pushURL = web::history_state_util::GetHistoryStateChangeUrl(
2463       [self currentURL], GURL(baseURL), pageURL);
2464   // UIWebView seems to choke on unicode characters that haven't been
2465   // escaped; escape the URL now so the expected load URL is correct.
2466   pushURL = URLEscapedForHistory(pushURL);
2467   if (!pushURL.is_valid())
2468     return YES;
2469   const NavigationManagerImpl& navigationManager =
2470       _webStateImpl->GetNavigationManagerImpl();
2471   web::NavigationItem* navItem = [self currentNavItem];
2472   // PushState happened before first navigation entry or called right after
2473   // window.open when the url is empty.
2474   if (!navItem ||
2475       (navigationManager.GetEntryCount() <= 1 && navItem->GetURL().is_empty()))
2476     return YES;
2477   if (!web::history_state_util::IsHistoryStateChangeValid(navItem->GetURL(),
2478                                                           pushURL)) {
2479     // A redirect may have occurred just prior to the pushState. Check if
2480     // the URL needs to be updated.
2481     // TODO(bdibello): Investigate how the pushState() is handled before the
2482     // redirect and after core.js injection.
2483     [self checkForUnexpectedURLChange];
2484   }
2485   if (!web::history_state_util::IsHistoryStateChangeValid(
2486           [self currentNavItem]->GetURL(), pushURL)) {
2487     // If the current session entry URL origin still doesn't match pushURL's
2488     // origin, ignore the pushState. This can happen if a new URL is loaded
2489     // just before the pushState.
2490     return YES;
2491   }
2492   std::string stateObjectJSON;
2493   if (!message->GetString("stateObject", &stateObjectJSON)) {
2494     DLOG(WARNING) << "JS message parameter not found: stateObject";
2495     return NO;
2496   }
2497   NSString* stateObject = base::SysUTF8ToNSString(stateObjectJSON);
2498   _URLOnStartLoading = pushURL;
2499   _lastRegisteredRequestURL = pushURL;
2501   // If the user interacted with the page, categorize it as a link navigation.
2502   // If not, categorize it is a client redirect as it occurred without user
2503   // input and should not be added to the history stack.
2504   // TODO(ios): Improve transition detection.
2505   ui::PageTransition transition =
2506       [context[web::kUserIsInteractingKey] boolValue]
2507           ? ui::PAGE_TRANSITION_LINK
2508           : ui::PAGE_TRANSITION_CLIENT_REDIRECT;
2509   [self pushStateWithPageURL:pushURL
2510                  stateObject:stateObject
2511                   transition:transition];
2513   NSString* replaceWebViewJS =
2514       [self javascriptToReplaceWebViewURL:pushURL stateObjectJSON:stateObject];
2515   base::WeakNSObject<CRWWebController> weakSelf(self);
2516   [self evaluateJavaScript:replaceWebViewJS
2517        stringResultHandler:^(NSString*, NSError*) {
2518          if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
2519            return;
2520          base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
2521          [strongSelf optOutScrollsToTopForSubviews];
2522          // Notify the observers.
2523          strongSelf.get()->_webStateImpl->OnHistoryStateChanged();
2524          [strongSelf didFinishNavigation];
2525        }];
2526   return YES;
2529 - (BOOL)handleWindowHistoryDidReplaceStateMessage:
2530     (base::DictionaryValue*)message
2531                                           context:(NSDictionary*)context {
2532   std::string pageURL;
2533   std::string baseURL;
2534   if (!message->GetString("pageUrl", &pageURL) ||
2535       !message->GetString("baseUrl", &baseURL)) {
2536     DLOG(WARNING) << "JS message parameter not found: pageUrl or baseUrl";
2537     return NO;
2538   }
2539   GURL replaceURL = web::history_state_util::GetHistoryStateChangeUrl(
2540       [self currentURL], GURL(baseURL), pageURL);
2541   // UIWebView seems to choke on unicode characters that haven't been
2542   // escaped; escape the URL now so the expected load URL is correct.
2543   replaceURL = URLEscapedForHistory(replaceURL);
2544   if (!replaceURL.is_valid())
2545     return YES;
2546   const NavigationManagerImpl& navigationManager =
2547       _webStateImpl->GetNavigationManagerImpl();
2548   web::NavigationItem* navItem = [self currentNavItem];
2549   // ReplaceState happened before first navigation entry or called right
2550   // after window.open when the url is empty/not valid.
2551   if (!navItem ||
2552       (navigationManager.GetEntryCount() <= 1 && navItem->GetURL().is_empty()))
2553     return YES;
2554   if (!web::history_state_util::IsHistoryStateChangeValid(navItem->GetURL(),
2555                                                           replaceURL)) {
2556     // A redirect may have occurred just prior to the replaceState. Check if
2557     // the URL needs to be updated.
2558     [self checkForUnexpectedURLChange];
2559   }
2560   if (!web::history_state_util::IsHistoryStateChangeValid(
2561           [self currentNavItem]->GetURL(), replaceURL)) {
2562     // If the current session entry URL origin still doesn't match
2563     // replaceURL's origin, ignore the replaceState. This can happen if a
2564     // new URL is loaded just before the replaceState.
2565     return YES;
2566   }
2567   std::string stateObjectJSON;
2568   if (!message->GetString("stateObject", &stateObjectJSON)) {
2569     DLOG(WARNING) << "JS message parameter not found: stateObject";
2570     return NO;
2571   }
2572   NSString* stateObject = base::SysUTF8ToNSString(stateObjectJSON);
2573   _URLOnStartLoading = replaceURL;
2574   _lastRegisteredRequestURL = replaceURL;
2575   [self replaceStateWithPageURL:replaceURL stateObject:stateObject];
2576   NSString* replaceStateJS = [self javascriptToReplaceWebViewURL:replaceURL
2577                                                  stateObjectJSON:stateObject];
2578   base::WeakNSObject<CRWWebController> weakSelf(self);
2579   [self evaluateJavaScript:replaceStateJS
2580        stringResultHandler:^(NSString*, NSError*) {
2581          if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
2582            return;
2583          base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
2584          [strongSelf didFinishNavigation];
2585        }];
2586   return YES;
2589 #pragma mark -
2591 - (BOOL)wantsKeyboardShield {
2592   if ([self.nativeController
2593           respondsToSelector:@selector(wantsKeyboardShield)]) {
2594     return [self.nativeController wantsKeyboardShield];
2595   }
2596   return YES;
2599 - (BOOL)wantsLocationBarHintText {
2600   if ([self.nativeController
2601           respondsToSelector:@selector(wantsLocationBarHintText)]) {
2602     return [self.nativeController wantsLocationBarHintText];
2603   }
2604   return YES;
2607 // TODO(stuartmorgan): This method conflates document changes and URL changes;
2608 // we should be distinguishing better, and be clear about the expected
2609 // WebDelegate and WCO callbacks in each case.
2610 - (void)webPageChanged {
2611   DCHECK(_loadPhase == web::LOAD_REQUESTED);
2613   const GURL currentURL([self currentURL]);
2614   web::Referrer referrer = [self currentReferrer];
2615   // If no referrer was known in advance, record it now. (If there was one,
2616   // keep it since it will have a more accurate URL and policy than what can
2617   // be extracted from the landing page.)
2618   web::NavigationItem* currentItem = [self currentNavItem];
2619   if (!currentItem->GetReferrer().url.is_valid()) {
2620     currentItem->SetReferrer(referrer);
2621   }
2623   // TODO(stuartmorgan): This shouldn't be called for hash state or
2624   // push/replaceState.
2625   [self resetDocumentSpecificState];
2627   [self didStartLoadingURL:currentURL updateHistory:YES];
2630 - (void)resetDocumentSpecificState {
2631   _lastUserInteraction.reset();
2632   _clickInProgress = NO;
2633   _lastSeenWindowID.reset([[_windowIDJSManager windowId] copy]);
2636 - (void)didStartLoadingURL:(const GURL&)url updateHistory:(BOOL)updateHistory {
2637   _loadPhase = web::PAGE_LOADING;
2638   _URLOnStartLoading = url;
2639   _displayStateOnStartLoading = self.pageDisplayState;
2641   _userInteractionRegistered = NO;
2642   _pageHasZoomed = NO;
2644   [[self sessionController] commitPendingEntry];
2645   _webStateImpl->GetRequestTracker()->StartPageLoad(
2646       url, [[self sessionController] currentEntry]);
2647   [_delegate webDidStartLoadingURL:url shouldUpdateHistory:updateHistory];
2650 - (BOOL)checkForUnexpectedURLChange {
2651   // Subclasses may override this method to check for and handle URL changes.
2652   return NO;
2655 - (void)wasShown {
2656   if ([self.nativeController respondsToSelector:@selector(wasShown)]) {
2657     [self.nativeController wasShown];
2658   }
2661 - (void)wasHidden {
2662   if (_isHalted)
2663     return;
2664   if ([self.nativeController respondsToSelector:@selector(wasHidden)]) {
2665     [self.nativeController wasHidden];
2666   }
2669 + (BOOL)webControllerCanShow:(const GURL&)url {
2670   return web::UrlHasWebScheme(url) ||
2671          web::GetWebClient()->IsAppSpecificURL(url) ||
2672          url.SchemeIs(url::kFileScheme) || url.SchemeIs(url::kAboutScheme);
2675 - (void)setUserInteractionRegistered:(BOOL)flag {
2676   _userInteractionRegistered = flag;
2679 - (BOOL)userInteractionRegistered {
2680   return _userInteractionRegistered;
2683 - (BOOL)useDesktopUserAgent {
2684   web::NavigationItem* item = [self currentNavItem];
2685   return item && item->IsOverridingUserAgent();
2688 - (void)cachePOSTDataForRequest:(NSURLRequest*)request
2689                  inSessionEntry:(CRWSessionEntry*)currentSessionEntry {
2690   NSUInteger maxPOSTDataSizeInBytes = 4096;
2691   NSString* cookieHeaderName = @"cookie";
2693   web::NavigationItemImpl* currentItem = currentSessionEntry.navigationItemImpl;
2694   DCHECK(currentItem);
2695   const bool shouldUpdateEntry =
2696       ui::PageTransitionCoreTypeIs(currentItem->GetTransitionType(),
2697                                    ui::PAGE_TRANSITION_FORM_SUBMIT) &&
2698       ![request HTTPBodyStream] &&  // Don't cache streams.
2699       !currentItem->HasPostData() &&
2700       currentItem->GetURL() == net::GURLWithNSURL([request URL]);
2701   const bool belowSizeCap =
2702       [[request HTTPBody] length] < maxPOSTDataSizeInBytes;
2703   DLOG_IF(WARNING, shouldUpdateEntry && !belowSizeCap)
2704       << "Data in POST request exceeds the size cap (" << maxPOSTDataSizeInBytes
2705       << " bytes), and will not be cached.";
2707   if (shouldUpdateEntry && belowSizeCap) {
2708     currentItem->SetPostData([request HTTPBody]);
2709     currentItem->ResetHttpRequestHeaders();
2710     currentItem->AddHttpRequestHeaders([request allHTTPHeaderFields]);
2711     // Don't cache the "Cookie" header.
2712     // According to NSURLRequest documentation, |-valueForHTTPHeaderField:| is
2713     // case insensitive, so it's enough to test the lower case only.
2714     if ([request valueForHTTPHeaderField:cookieHeaderName]) {
2715       // Case insensitive search in |headers|.
2716       NSSet* cookieKeys = [currentItem->GetHttpRequestHeaders()
2717           keysOfEntriesPassingTest:^(id key, id obj, BOOL* stop) {
2718             NSString* header = (NSString*)key;
2719             const BOOL found =
2720                 [header caseInsensitiveCompare:cookieHeaderName] ==
2721                 NSOrderedSame;
2722             *stop = found;
2723             return found;
2724           }];
2725       DCHECK_EQ(1u, [cookieKeys count]);
2726       currentItem->RemoveHttpRequestHeaderForKey([cookieKeys anyObject]);
2727     }
2728   }
2731 // TODO(stuartmorgan): This is mostly logic from the original UIWebView delegate
2732 // method, which provides less information than the WKWebView version. Audit
2733 // this for things that should be handled in the subclass instead.
2734 - (BOOL)shouldAllowLoadWithRequest:(NSURLRequest*)request
2735                        targetFrame:(const web::FrameInfo*)targetFrame
2736                        isLinkClick:(BOOL)isLinkClick {
2737   GURL requestURL = net::GURLWithNSURL(request.URL);
2739   // Check if the request should be delayed.
2740   if (_externalRequest && _externalRequest->url == requestURL) {
2741     // Links that can't be shown in a tab by Chrome but can be handled by
2742     // external apps (e.g. tel:, mailto:) are opened directly despite the target
2743     // attribute on the link. We don't open a new tab for them because Mobile
2744     // Safari doesn't do that (and sites are expecting us to do the same) and
2745     // also because there would be nothing shown in that new tab; it would
2746     // remain on about:blank (see crbug.com/240178)
2747     if ([CRWWebController webControllerCanShow:requestURL] ||
2748         ![_delegate openExternalURL:requestURL]) {
2749       web::NewWindowInfo windowInfo = *_externalRequest;
2750       dispatch_async(dispatch_get_main_queue(), ^{
2751         [self openPopupWithInfo:windowInfo];
2752       });
2753     }
2754     _externalRequest.reset();
2755     return NO;
2756   }
2758   BOOL shouldCheckNativeApp = [self cancellable];
2760   // Check if the link navigation leads to a launch of an external app.
2761   // TODO(shreyasv): Change this such that handling/stealing of link navigations
2762   // is delegated to the WebDelegate and the logic around external app launching
2763   // is moved there as well.
2764   if (shouldCheckNativeApp || isLinkClick) {
2765     // Check If the URL is handled by a native app.
2766     if ([self urlTriggersNativeAppLaunch:requestURL
2767                                sourceURL:[self currentNavigationURL]]) {
2768       // External app has been launched successfully. Stop the current page
2769       // load operation (e.g. notifying all observers) and record the URL so
2770       // that errors reported following the 'NO' reply can be safely ignored.
2771       if ([self cancellable])
2772         [_delegate webPageOrderedClose];
2773       [self abortLoad];
2774       [_openedApplicationURL addObject:request.URL];
2775       return NO;
2776     }
2777   }
2779   // The WebDelegate may instruct the CRWWebController to stop loading, and
2780   // instead instruct the next page to be loaded in an animation.
2781   GURL mainDocumentURL = net::GURLWithNSURL(request.mainDocumentURL);
2782   DCHECK(self.webView);
2783   if (![self shouldOpenURL:requestURL
2784            mainDocumentURL:mainDocumentURL
2785                linkClicked:isLinkClick]) {
2786     return NO;
2787   }
2789   // If the URL doesn't look like one we can show, try to open the link with an
2790   // external application.
2791   // TODO(droger):  Check transition type before opening an external
2792   // application? For example, only allow it for TYPED and LINK transitions.
2793   if (![CRWWebController webControllerCanShow:requestURL]) {
2794     if (![self shouldOpenExternalURLRequest:request targetFrame:targetFrame]) {
2795       return NO;
2796     }
2798     // Abort load if navigation is hapenning on the main frame. If |targetFrame|
2799     // is unknown use heuristic to guess the target frame by comparing
2800     // documentURL and navigation URL. This heuristic may have false positives.
2801     bool shouldAbortLoad = targetFrame ? targetFrame->is_main_frame
2802                                        : requestURL == mainDocumentURL;
2803     if (shouldAbortLoad)
2804       [self abortLoad];
2806     if ([_delegate openExternalURL:requestURL]) {
2807       // Record the URL so that errors reported following the 'NO' reply can be
2808       // safely ignored.
2809       [_openedApplicationURL addObject:request.URL];
2810       if ([self cancellable])
2811         [_delegate webPageOrderedClose];
2812     }
2813     return NO;
2814   }
2816   if ([[request HTTPMethod] isEqualToString:@"POST"]) {
2817     [self cachePOSTDataForRequest:request
2818                    inSessionEntry:[self currentSessionEntry]];
2819   }
2821   return YES;
2824 - (void)restoreStateAfterURLRejection {
2825   [[self sessionController] discardNonCommittedEntries];
2827   // Re-register the user agent, because UIWebView will sometimes try to read
2828   // the agent again from a saved search result page in which no other page has
2829   // yet been loaded. See crbug.com/260370.
2830   [self registerUserAgent];
2832   // Reset |_lastRegisteredRequestURL| so that it reflects the URL from before
2833   // the load was rejected. This value may be out of sync because
2834   // |_lastRegisteredRequestURL| may have already been updated before the load
2835   // was rejected.
2836   _lastRegisteredRequestURL = [self currentURL];
2837   _loadPhase = web::PAGE_LOADING;
2838   [self didFinishNavigation];
2841 - (void)handleLoadError:(NSError*)error inMainFrame:(BOOL)inMainFrame {
2842   if ([error code] == NSURLErrorUnsupportedURL)
2843     return;
2844   // In cases where a Plug-in handles the load do not take any further action.
2845   if ([[error domain] isEqual:WebKitErrorDomain] &&
2846       ([error code] == WebKitErrorPlugInLoadFailed ||
2847        [error code] == WebKitErrorCannotShowURL))
2848     return;
2850   // Continue processing only if the error is on the main request or is the
2851   // result of a user interaction.
2852   NSDictionary* userInfo = [error userInfo];
2853   // |userinfo| contains the request creation date as a NSDate.
2854   NSTimeInterval requestCreationDate =
2855       [[userInfo objectForKey:@"CreationDate"] timeIntervalSinceReferenceDate];
2856   bool userInteracted = false;
2857   if (requestCreationDate != 0.0 && _lastUserInteraction) {
2858     NSTimeInterval timeSinceInteraction =
2859         requestCreationDate - _lastUserInteraction->time;
2860     // The error is considered to be the result of a user interaction if any
2861     // interaction happened just before the request was made.
2862     // TODO(droger): If the user interacted with the page after the request was
2863     // made (i.e. creationTimeSinceLastInteraction < 0), then
2864     // |_lastUserInteraction| has been overridden. The current behavior is to
2865     // discard the interstitial in that case. A better decision could be made if
2866     // we had a history of all the user interactions instead of just the last
2867     // one.
2868     userInteracted =
2869         timeSinceInteraction < kMaximumDelayForUserInteractionInSeconds &&
2870         _lastUserInteraction->time > _lastTransferTimeInSeconds &&
2871         timeSinceInteraction >= 0.0;
2872   } else {
2873     // If the error does not have timing information, check if the user
2874     // interacted with the page recently.
2875     userInteracted = [self userIsInteracting];
2876   }
2877   if (!inMainFrame && !userInteracted)
2878     return;
2880   NSURL* errorURL = [NSURL
2881       URLWithString:[userInfo objectForKey:NSURLErrorFailingURLStringErrorKey]];
2882   const GURL errorGURL = net::GURLWithNSURL(errorURL);
2884   // Handles Frame Load Interrupted errors from WebView.
2885   if ([[error domain] isEqualToString:WebKitErrorDomain] &&
2886       [error code] == WebKitErrorFrameLoadInterruptedByPolicyChange) {
2887     // See if the delegate wants to handle this case.
2888     if (errorGURL.is_valid() &&
2889         [_delegate
2890             respondsToSelector:@selector(
2891                                    controllerForUnhandledContentAtURL:)]) {
2892       id<CRWNativeContent> controller =
2893           [_delegate controllerForUnhandledContentAtURL:errorGURL];
2894       if (controller) {
2895         [self loadCompleteWithSuccess:NO];
2896         [self removeWebViewAllowingCachedReconstruction:NO];
2897         [self setNativeController:controller];
2898         [self loadNativeViewWithSuccess:YES];
2899         return;
2900       }
2901     }
2903     // Ignore errors that originate from URLs that are opened in external apps.
2904     if ([_openedApplicationURL containsObject:errorURL])
2905       return;
2906     // Certain frame errors don't have URL information for some reason; for
2907     // those cases (so far the only known case is plugin content loaded directly
2908     // in a frame) just ignore the error. See crbug.com/414295
2909     if (!errorURL) {
2910       DCHECK(!inMainFrame);
2911       return;
2912     }
2913     // The wrapper error uses the URL of the error and not the requested URL
2914     // (which can be different in case of a redirect) to match desktop Chrome
2915     // behavior.
2916     NSError* wrapperError = [NSError
2917         errorWithDomain:[error domain]
2918                    code:[error code]
2919                userInfo:@{
2920                  NSURLErrorFailingURLStringErrorKey : [errorURL absoluteString],
2921                  NSUnderlyingErrorKey : error
2922                }];
2923     [self loadCompleteWithSuccess:NO];
2924     [self loadErrorInNativeView:wrapperError];
2925     return;
2926   }
2928   if ([error code] == NSURLErrorCancelled) {
2929     [self handleCancelledError:error];
2930     // NSURLErrorCancelled errors that aren't handled by aborting the load will
2931     // automatically be retried by the web view, so early return in this case.
2932     return;
2933   }
2935   [self loadCompleteWithSuccess:NO];
2936   [self loadErrorInNativeView:error];
2939 - (void)handleCancelledError:(NSError*)cancelledError {
2940   // Subclasses must implement this method.
2941   NOTREACHED();
2944 #pragma mark -
2945 #pragma mark WebUI
2947 - (void)createWebUIForURL:(const GURL&)URL {
2948   _webStateImpl->CreateWebUI(URL);
2951 - (void)clearWebUI {
2952   _webStateImpl->ClearWebUI();
2955 #pragma mark -
2956 #pragma mark UIGestureRecognizerDelegate
2958 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2959     shouldRecognizeSimultaneouslyWithGestureRecognizer:
2960         (UIGestureRecognizer*)otherGestureRecognizer {
2961   // Allows the custom UILongPressGestureRecognizer to fire simultaneously with
2962   // other recognizers.
2963   return YES;
2966 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2967        shouldReceiveTouch:(UITouch*)touch {
2968   // Expect only _contextMenuRecognizer.
2969   DCHECK([gestureRecognizer isEqual:_contextMenuRecognizer]);
2970   if (![self supportsCustomContextMenu]) {
2971     // Fetching context menu info is not a free operation, early return if a
2972     // context menu should not be shown.
2973     return YES;
2974   }
2976   // This is custom long press gesture recognizer. By the time the gesture is
2977   // recognized the web controller needs to know if there is a link under the
2978   // touch. If there a link, the web controller will reject system's context
2979   // menu and show another one. If for some reason context menu info is not
2980   // fetched - system context menu will be shown.
2981   [self setDOMElementForLastTouch:nullptr];
2982   base::WeakNSObject<CRWWebController> weakSelf(self);
2983   [self fetchDOMElementAtPoint:[touch locationInView:self.webView]
2984              completionHandler:^(scoped_ptr<base::DictionaryValue> element) {
2985                [weakSelf setDOMElementForLastTouch:element.Pass()];
2986              }];
2987   return YES;
2990 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer {
2991   // Expect only _contextMenuRecognizer.
2992   DCHECK([gestureRecognizer isEqual:_contextMenuRecognizer]);
2993   if (!self.webView || ![self supportsCustomContextMenu]) {
2994     // Show the context menu iff currently displaying a web view.
2995     // Do nothing for native views.
2996     return NO;
2997   }
2999   UMA_HISTOGRAM_BOOLEAN("WebController.FetchContextMenuInfoAsyncSucceeded",
3000                         _DOMElementForLastTouch);
3002   return _DOMElementForLastTouch && !_DOMElementForLastTouch->empty();
3005 #pragma mark -
3006 #pragma mark CRWRequestTrackerDelegate
3008 - (BOOL)isForStaticFileRequests {
3009   return NO;
3012 - (void)updatedSSLStatus:(const web::SSLStatus&)sslStatus
3013               forPageUrl:(const GURL&)url
3014                 userInfo:(id)userInfo {
3015   // |userInfo| is a CRWSessionEntry.
3016   web::NavigationItem* item =
3017       [static_cast<CRWSessionEntry*>(userInfo) navigationItem];
3018   if (!item)
3019     return;  // This is a request update for an entry that no longer exists.
3021   // This condition happens infrequently when a page load is misinterpreted as
3022   // a resource load from a previous page. This can happen when moving quickly
3023   // back and forth through history, the notifications from the web view on the
3024   // UI thread and the one from the requests at the net layer may get out of
3025   // sync. This catches this case and prevent updating an entry with the wrong
3026   // SSL data.
3027   if (item->GetURL().GetOrigin() != url.GetOrigin())
3028     return;
3030   if (item->GetSSL().Equals(sslStatus))
3031     return;  // No need to update with the same data.
3033   item->GetSSL() = sslStatus;
3035   // Notify the UI it needs to refresh if the updated entry is the current
3036   // entry.
3037   if (userInfo == self.currentSessionEntry) {
3038     [self didUpdateSSLStatusForCurrentNavigationItem];
3039   }
3042 - (void)handleResponseHeaders:(net::HttpResponseHeaders*)headers
3043                    requestUrl:(const GURL&)requestUrl {
3044   _webStateImpl->OnHttpResponseHeadersReceived(headers, requestUrl);
3047 - (void)presentSSLError:(const net::SSLInfo&)info
3048            forSSLStatus:(const web::SSLStatus&)status
3049                   onUrl:(const GURL&)url
3050             recoverable:(BOOL)recoverable
3051                callback:(SSLErrorCallback)shouldContinue {
3052   DCHECK(_delegate);
3053   DCHECK_EQ(url, [self currentNavigationURL]);
3054   [_delegate presentSSLError:info
3055                 forSSLStatus:status
3056                  recoverable:recoverable
3057                     callback:^(BOOL proceed) {
3058                       if (proceed) {
3059                         // The interstitial will be removed during reload.
3060                         [self loadCurrentURL];
3061                       }
3062                       if (shouldContinue) {
3063                         shouldContinue(proceed);
3064                       }
3065                     }];
3068 - (void)updatedProgress:(float)progress {
3069   if ([_delegate
3070           respondsToSelector:@selector(webController:didUpdateProgress:)]) {
3071     [_delegate webController:self didUpdateProgress:progress];
3072   }
3075 - (void)certificateUsed:(net::X509Certificate*)certificate
3076                 forHost:(const std::string&)host
3077                  status:(net::CertStatus)status {
3078   [[[self sessionController] sessionCertificatePolicyManager]
3079       registerAllowedCertificate:certificate
3080                          forHost:host
3081                           status:status];
3084 - (void)clearCertificates {
3085   [[[self sessionController] sessionCertificatePolicyManager]
3086       clearCertificates];
3089 #pragma mark -
3090 #pragma mark Popup handling
3092 - (void)openPopupWithInfo:(const web::NewWindowInfo&)windowInfo {
3093   const GURL url(windowInfo.url);
3094   const GURL currentURL([self currentNavigationURL]);
3095   NSString* windowName = windowInfo.window_name.get();
3096   web::Referrer referrer(currentURL, windowInfo.referrer_policy);
3097   base::WeakNSObject<CRWWebController> weakSelf(self);
3098   void (^showPopupHandler)() = ^{
3099     CRWWebController* child = [[weakSelf delegate] webPageOrderedOpen:url
3100                                                              referrer:referrer
3101                                                            windowName:windowName
3102                                                          inBackground:NO];
3103     DCHECK(!child || child.sessionController.openedByDOM);
3104   };
3106   BOOL showPopup = windowInfo.user_is_interacting ||
3107                    (![self shouldBlockPopupWithURL:url sourceURL:currentURL]);
3108   if (showPopup) {
3109     showPopupHandler();
3110   } else if ([_delegate
3111                  respondsToSelector:@selector(webController:didBlockPopup:)]) {
3112     web::BlockedPopupInfo blockedPopupInfo(url, referrer, windowName,
3113                                            showPopupHandler);
3114     [_delegate webController:self didBlockPopup:blockedPopupInfo];
3115   }
3118 #pragma mark -
3119 #pragma mark TouchTracking
3121 - (void)touched:(BOOL)touched {
3122   _clickInProgress = touched;
3123   if (touched) {
3124     _userInteractionRegistered = 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)shouldOpenExternalURLRequest:(NSURLRequest*)request
3642                          targetFrame:(const web::FrameInfo*)targetFrame {
3643   // If targetFrame information is not provided, the request originated from the
3644   // main frame if (a) the request's URL matches the request's main document URL
3645   // or (b) if the current pending entry matches the request's main document
3646   // URL, as this is a redirect from an in-progress main frame load.
3647   BOOL isMainFrame = targetFrame
3648                          ? targetFrame->is_main_frame
3649                          : [request.URL isEqual:request.mainDocumentURL];
3650   if (!targetFrame && !isMainFrame) {
3651     web::NavigationItem* pendingItem =
3652         [self webStateImpl]->GetNavigationManager()->GetPendingItem();
3653     if (pendingItem) {
3654       isMainFrame =
3655           pendingItem->GetURL() == net::GURLWithNSURL(request.mainDocumentURL);
3656     }
3657   }
3659   // If the request's main document URL differs from that at the time of the
3660   // last user interaction, then the page has changed since the user last
3661   // interacted.
3662   BOOL userHasInteractedWithCurrentPage =
3663       _lastUserInteraction &&
3664       net::GURLWithNSURL(request.mainDocumentURL) ==
3665           _lastUserInteraction->main_document_url;
3667   // Prevent subframe requests from opening an external URL if the user has not
3668   // interacted with the page.
3669   if (!isMainFrame && !userHasInteractedWithCurrentPage)
3670     return NO;
3672   GURL requestURL = net::GURLWithNSURL(request.URL);
3673   return [_delegate respondsToSelector:@selector(webController:
3674                                            shouldOpenExternalURL:)] &&
3675          [_delegate webController:self shouldOpenExternalURL:requestURL];
3678 - (BOOL)urlTriggersNativeAppLaunch:(const GURL&)url
3679                          sourceURL:(const GURL&)sourceURL {
3680   return
3681       [_delegate respondsToSelector:@selector(urlTriggersNativeAppLaunch:
3682                                                                sourceURL:)] &&
3683       [_delegate urlTriggersNativeAppLaunch:url sourceURL:sourceURL];
3686 - (CGFloat)headerHeight {
3687   if (![_delegate respondsToSelector:@selector(headerHeightForWebController:)])
3688     return 0.0f;
3689   return [_delegate headerHeightForWebController:self];
3692 - (BOOL)shouldBlockPopupWithURL:(const GURL&)popupURL
3693                       sourceURL:(const GURL&)sourceURL {
3694   if (![_delegate respondsToSelector:@selector(webController:
3695                                          shouldBlockPopupWithURL:
3696                                                        sourceURL:)]) {
3697     return NO;
3698   }
3699   return [_delegate webController:self
3700           shouldBlockPopupWithURL:popupURL
3701                         sourceURL:sourceURL];
3704 - (void)didUpdateHistoryStateWithPageURL:(const GURL&)url {
3705   _webStateImpl->GetRequestTracker()->HistoryStateChange(url);
3706   [_delegate webDidUpdateHistoryStateWithPageURL:url];
3709 - (void)didUpdateSSLStatusForCurrentNavigationItem {
3710   if ([_delegate respondsToSelector:
3711           @selector(
3712               webControllerDidUpdateSSLStatusForCurrentNavigationItem:)]) {
3713     [_delegate webControllerDidUpdateSSLStatusForCurrentNavigationItem:self];
3714   }
3717 #pragma mark CRWWebControllerScripting Methods
3719 - (void)loadHTML:(NSString*)html {
3720   [self loadHTML:html forURL:GURL(url::kAboutBlankURL)];
3723 - (void)loadHTMLForCurrentURL:(NSString*)html {
3724   [self loadHTML:html forURL:self.currentURL];
3727 - (void)loadHTML:(NSString*)html forURL:(const GURL&)url {
3728   // Remove the transient content view.
3729   [self clearTransientContentView];
3731   DLOG_IF(WARNING, !self.webView)
3732       << "self.webView null while trying to load HTML";
3733   _loadPhase = web::LOAD_REQUESTED;
3734   [self loadWebHTMLString:html forURL:url];
3737 - (void)loadHTML:(NSString*)HTML forAppSpecificURL:(const GURL&)URL {
3738   CHECK(web::GetWebClient()->IsAppSpecificURL(URL));
3739   [self loadHTML:HTML forURL:URL];
3742 - (void)stopLoading {
3743   web::RecordAction(UserMetricsAction("Stop"));
3744   // Discard the pending and transient entried before notifying the tab model
3745   // observers of the change via |-abortLoad|.
3746   [[self sessionController] discardNonCommittedEntries];
3747   [self abortLoad];
3748   // If discarding the non-committed entries results in an app-specific URL,
3749   // reload it in its native view.
3750   if (!self.nativeController &&
3751       [self shouldLoadURLInNativeView:[self currentNavigationURL]]) {
3752     [self loadCurrentURLInNativeView];
3753   }
3756 - (void)orderClose {
3757   if (self.sessionController.openedByDOM) {
3758     [_delegate webPageOrderedClose];
3759   }
3762 #pragma mark -
3763 #pragma mark Testing-Only Methods
3765 - (void)injectWebViewContentView:(id)webViewContentView {
3766   [self removeWebViewAllowingCachedReconstruction:NO];
3768   _lastRegisteredRequestURL = _defaultURL;
3769   [self.containerView displayWebViewContentView:webViewContentView];
3772 - (void)resetInjectedWebViewContentView {
3773   [self resetWebView];
3774   [self resetContainerView];
3777 - (void)addObserver:(id<CRWWebControllerObserver>)observer {
3778   DCHECK(observer);
3779   if (!_observers) {
3780     // We don't want our observer set to block dealloc on the observers. For the
3781     // observer container, make an object compatible with NSMutableSet that does
3782     // not perform retain or release on the contained objects (weak references).
3783     CFSetCallBacks callbacks =
3784         {0, NULL, NULL, CFCopyDescription, CFEqual, CFHash};
3785     _observers.reset(base::mac::CFToNSCast(
3786         CFSetCreateMutable(kCFAllocatorDefault, 1, &callbacks)));
3787   }
3788   DCHECK(![_observers containsObject:observer]);
3789   [_observers addObject:observer];
3790   _observerBridges.push_back(
3791       new web::WebControllerObserverBridge(observer, self.webStateImpl, self));
3793   if ([observer respondsToSelector:@selector(setWebViewProxy:controller:)])
3794     [observer setWebViewProxy:_webViewProxy controller:self];
3797 - (void)removeObserver:(id<CRWWebControllerObserver>)observer {
3798   // TODO(jimblackler): make _observers use NSMapTable. crbug.com/367992
3799   DCHECK([_observers containsObject:observer]);
3800   [_observers removeObject:observer];
3801   // Remove the associated WebControllerObserverBridge.
3802   auto it = std::find_if(_observerBridges.begin(), _observerBridges.end(),
3803                          [observer](web::WebControllerObserverBridge* bridge) {
3804                            return bridge->web_controller_observer() == observer;
3805                          });
3806   DCHECK(it != _observerBridges.end());
3807   _observerBridges.erase(it);
3810 - (NSUInteger)observerCount {
3811   DCHECK_EQ(_observerBridges.size(), [_observers count]);
3812   return [_observers count];
3815 - (NSString*)windowId {
3816   return [_windowIDJSManager windowId];
3819 - (void)setWindowId:(NSString*)windowId {
3820   return [_windowIDJSManager setWindowId:windowId];
3823 - (NSString*)lastSeenWindowID {
3824   return _lastSeenWindowID;
3827 - (void)setURLOnStartLoading:(const GURL&)url {
3828   _URLOnStartLoading = url;
3831 - (const GURL&)defaultURL {
3832   return _defaultURL;
3835 - (GURL)URLOnStartLoading {
3836   return _URLOnStartLoading;
3839 - (GURL)lastRegisteredRequestURL {
3840   return _lastRegisteredRequestURL;
3843 - (void)simulateLoadRequestWithURL:(const GURL&)URL {
3844   _lastRegisteredRequestURL = URL;
3845   _loadPhase = web::LOAD_REQUESTED;
3848 - (NSString*)externalRequestWindowName {
3849   if (!_externalRequest || !_externalRequest->window_name)
3850     return @"";
3851   return _externalRequest->window_name;
3854 - (void)resetExternalRequest {
3855   _externalRequest.reset();
3858 @end