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