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>
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"
87 #include "url/url_constants.h"
89 using base::UserMetricsAction;
90 using web::NavigationManagerImpl;
92 using web::WebStateImpl;
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)
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.
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,
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;
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);
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
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|.
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
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
241 CFAbsoluteTime _lastTransferTimeInSeconds;
242 // Default URL (about:blank).
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
248 BOOL _setSuppressDialogsLater;
249 // If |YES|, call setSuppressDialogs when core.js is injected into the web
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;
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
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
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
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;
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];
531 // Parses a viewport tag content and returns the value of the user-scalable
533 BOOL GetUserScalablePropertyFromViewPortContent(NSString* viewPortContent) {
535 GetAttributeValueFromViewPortContent(@"user-scalable", viewPortContent);
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;
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.
566 return [super allocWithZone:zone];
569 - (instancetype)initWithWebState:(scoped_ptr<WebStateImpl>)webState {
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
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();
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]
596 selector:@selector(orientationDidChange)
597 name:UIApplicationDidChangeStatusBarOrientationNotification
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)
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.
638 _webStateImpl->ClearTransientContentView();
641 - (void)showTransientContentView:(CRWContentView*)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];
658 [self.nativeController setDelegate:nil];
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 {
680 _expectedReconstructionURL = [self currentNavigationURL];
682 _expectedReconstructionURL = GURL();
685 [self.webView removeFromSuperview];
686 [self.containerView resetContent];
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];
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.
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)
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];
742 - (void)setWebUsageEnabled:(BOOL)enabled {
743 if (_webUsageEnabled == enabled)
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.
751 [self setNativeControllerWebUsageEnabled:_webUsageEnabled];
753 // Don't create the web view; let it be lazy created as needed.
755 [self clearTransientContentView];
756 [self removeWebViewAllowingCachedReconstruction:YES];
757 _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
758 _touchTrackingRecognizer.reset();
759 [self resetContainerView];
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 {
783 [self removeWebViewAllowingCachedReconstruction:NO];
785 [self loadCurrentURLInWebView];
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];
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 {
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.
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];
839 [self terminateNetworkActivity];
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) {
864 element && element->GetString("href", &link) && link.size();
865 completionHandler(hasLink);
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)
882 if (!_DOMElementForLastTouch || _DOMElementForLastTouch->empty())
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
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
910 + (UIImage*)defaultSnapshotImage {
911 static UIImage* defaultImage = nil;
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();
923 [[result stretchableImageWithLeftCapWidth:1 topCapHeight:1] retain];
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)
940 return self.webScrollView.contentOffset;
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);
955 [self removeWebViewAllowingCachedReconstruction:NO];
956 [_delegate presentSpoofingError];
960 - (GURL)currentURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel {
961 DCHECK(trustLevel) << "Verification of the trustLevel state is mandatory";
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);
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];
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(), ^{
989 DCHECK_EQ(url, [self currentNavigationURL]);
990 [self presentSpoofingError];
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;
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
1050 DCHECK([_earlyScriptManager hasBeenInjected]);
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;
1068 [_windowIDJSManager inject];
1069 DCHECK([_windowIDJSManager hasBeenInjected]);
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);
1096 - (void)webViewDidChange {
1097 CHECK(_webUsageEnabled) << "Tried to create a web view while suspended!";
1099 UIView* webView = self.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];
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.
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.
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]
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.
1147 requireGestureRecognizerToFail:_contextMenuRecognizer
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];
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
1173 DCHECK(!result || result.sessionController.openedByDOM);
1177 - (BOOL)canUseViewForGeneratingOverlayPlaceholderView {
1178 return self.containerView != nil;
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
1200 return self.webView;
1203 - (void)loadRequest:(NSMutableURLRequest*)request {
1204 // Subclasses must implement this method.
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];
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];
1240 // A new session history entry needs to be created.
1241 [[self sessionController] addPendingEntry:requestURL
1243 transition:transition
1244 rendererInitiated:YES];
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);
1255 _webStateImpl->SetIsLoading(true);
1256 [_delegate webDidAddPendingURL];
1257 _webStateImpl->OnProvisionalNavigationStarted(requestURL);
1260 - (NSString*)javascriptToReplaceWebViewURL:(const GURL&)url
1261 stateObjectJSON:(NSString*)stateObject {
1263 base::EscapeJSONString(url.spec(), true, &outURL);
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];
1285 base::WeakNSObject<CRWWebController> weakSelf(self);
1286 [self evaluateJavaScript:combinedJS
1287 stringResultHandler:^(NSString*, NSError*) {
1288 if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
1290 base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
1291 strongSelf.get()->_URLOnStartLoading = urlCopy;
1292 strongSelf.get()->_lastRegisteredRequestURL = urlCopy;
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())
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.
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];
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
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];
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.
1368 [_delegate webController:self titleDidChange:title];
1369 // If the controller handles title change notification, route those to the
1371 if ([self.nativeController respondsToSelector:@selector(setDelegate:)]) {
1372 [self.nativeController setDelegate:self];
1377 - (void)loadErrorInNativeView:(NSError*)error {
1378 [self removeWebViewAllowingCachedReconstruction:NO];
1380 const GURL currentUrl = [self currentNavigationURL];
1382 // TODO(bhnascar): Fix POST data check for WKWebView here and elsewhere.
1383 // See crbug.com/517911.
1384 DCHECK([self currentSessionEntry]);
1385 NSData* currentPOSTData =
1386 [self currentSessionEntry].navigationItemImpl->GetPostData();
1387 BOOL isPost = currentPOSTData != nil;
1389 error = web::NetErrorFromError(error);
1390 [self setNativeController:[_nativeProvider controllerForURL:currentUrl
1393 [self loadNativeViewWithSuccess:NO];
1396 // Load the current URL in a native controller, retrieved from the native
1397 // provider. Call |loadNativeViewWithSuccess:YES| to load the native controller.
1398 - (void)loadCurrentURLInNativeView {
1399 // Free the web view.
1400 [self removeWebViewAllowingCachedReconstruction:NO];
1402 const GURL targetURL = [self currentNavigationURL];
1403 const web::Referrer referrer;
1404 // Unlike the WebView case, always create a new controller and view.
1405 // TODO(pinkerton): What to do if this does return nil?
1406 [self setNativeController:[_nativeProvider controllerForURL:targetURL]];
1407 [self registerLoadRequest:targetURL
1409 transition:[self currentTransition]];
1410 [self loadNativeViewWithSuccess:YES];
1413 - (void)loadWithParams:(const web::WebLoadParams&)originalParams {
1414 // Make a copy of |params|, as some of the delegate methods may modify it.
1415 web::WebLoadParams params(originalParams);
1417 // Initiating a navigation from the UI, record the current page state before
1418 // the new page loads. Don't record for back/forward, as the current entry
1419 // has already been moved to the next entry in the history. Do, however,
1420 // record it for general reload.
1421 // TODO(jimblackler): consider a single unified call to record state whenever
1422 // the page is about to be changed. This cannot currently be done after
1423 // addPendingEntry is called.
1425 [_delegate webWillInitiateLoadWithParams:params];
1427 GURL navUrl = params.url;
1428 ui::PageTransition transition = params.transition_type;
1430 BOOL initialNavigation = NO;
1432 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD) &&
1433 (transition & ui::PAGE_TRANSITION_FORWARD_BACK);
1435 // Setting these for back/forward is not supported.
1436 DCHECK(!params.extra_headers);
1437 DCHECK(!params.post_data);
1439 // Clear transient view before making any changes to history and navigation
1440 // manager. TODO(stuartmorgan): Drive Transient Item clearing from
1441 // navigation system, rather than from WebController.
1442 [self clearTransientContentView];
1444 // TODO(stuartmorgan): Why doesn't recordStateInHistory get called for
1445 // forward/back transitions?
1446 [self recordStateInHistory];
1448 CRWSessionController* history =
1449 _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1450 if (!self.currentSessionEntry)
1451 initialNavigation = YES;
1452 [history addPendingEntry:navUrl
1453 referrer:params.referrer
1454 transition:transition
1455 rendererInitiated:params.is_renderer_initiated];
1456 web::NavigationItemImpl* addedItem =
1457 [self currentSessionEntry].navigationItemImpl;
1459 if (params.extra_headers)
1460 addedItem->AddHttpRequestHeaders(params.extra_headers);
1461 if (params.post_data) {
1462 DCHECK([addedItem->GetHttpRequestHeaders() objectForKey:@"Content-Type"])
1463 << "Post data should have an associated content type";
1464 addedItem->SetPostData(params.post_data);
1465 addedItem->SetShouldSkipResubmitDataConfirmation(true);
1469 [_delegate webDidUpdateSessionForLoadWithParams:params
1470 wasInitialNavigation:initialNavigation];
1472 // If a non-default cache mode is passed in, it takes precedence over
1474 const BOOL reload = [self shouldReload:navUrl transition:transition];
1475 if (params.cache_mode != net::RequestTracker::CACHE_NORMAL) {
1476 _webStateImpl->SetCacheMode(params.cache_mode);
1477 } else if (reload) {
1478 _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_RELOAD);
1481 [self loadCurrentURL];
1483 // Change the cache mode back to CACHE_NORMAL after a reload.
1484 _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1487 - (void)loadCurrentURL {
1488 // If the content view doesn't exist, the tab has either been evicted, or
1489 // never displayed. Bail, and let the URL be loaded when the tab is shown.
1490 if (!self.containerView)
1493 // Reset current WebUI if one exists.
1496 // Precaution, so that the outgoing URL is registered, to reduce the risk of
1497 // it being seen as a fresh URL later by the same method (and new page change
1498 // erroneously reported).
1499 [self checkForUnexpectedURLChange];
1501 // Abort any outstanding page load. This ensures the delegate gets informed
1502 // about the outgoing page, and further messages from the page are suppressed.
1503 if (_loadPhase != web::PAGE_LOADED)
1507 // Remove the transient content view.
1508 [self clearTransientContentView];
1510 const GURL currentURL = [self currentNavigationURL];
1511 // If it's a chrome URL, but not a native one, create the WebUI instance.
1512 if (web::GetWebClient()->IsAppSpecificURL(currentURL) &&
1513 ![_nativeProvider hasControllerForURL:currentURL]) {
1514 [self createWebUIForURL:currentURL];
1517 // Loading a new url, must check here if it's a native chrome URL and
1518 // replace the appropriate view if so, or transition back to a web view from
1520 if ([self shouldLoadURLInNativeView:currentURL]) {
1521 [self loadCurrentURLInNativeView];
1523 [self loadCurrentURLInWebView];
1526 // Once a URL has been loaded, any cached-based reconstruction state has
1527 // either been handled or obsoleted.
1528 _expectedReconstructionURL = GURL();
1531 - (BOOL)shouldLoadURLInNativeView:(const GURL&)url {
1532 // App-specific URLs that don't require WebUI are loaded in native views.
1533 return web::GetWebClient()->IsAppSpecificURL(url) &&
1534 !_webStateImpl->HasWebUI();
1537 - (void)triggerPendingLoad {
1538 if (!self.containerView) {
1539 DCHECK(!_isBeingDestroyed);
1540 // Create the top-level parent view, which will contain the content (whether
1541 // native or web). Note, this needs to be created with a non-zero size
1542 // to allow for (native) subviews with autosize constraints to be correctly
1544 _containerView.reset([[CRWWebControllerContainerView alloc]
1545 initWithContentViewProxy:_webViewProxy]);
1546 self.containerView.frame = [[UIScreen mainScreen] bounds];
1547 [self.containerView addGestureRecognizer:[self touchTrackingRecognizer]];
1548 [self.containerView setAccessibilityIdentifier:web::kContainerViewID];
1549 // Is |currentUrl| a web scheme or native chrome scheme.
1550 BOOL isChromeScheme =
1551 web::GetWebClient()->IsAppSpecificURL([self currentNavigationURL]);
1553 // Don't immediately load the web page if in overlay mode. Always load if
1555 if (isChromeScheme || !_overlayPreviewMode) {
1556 // TODO(jimblackler): end the practice of calling |loadCurrentURL| when it
1557 // is possible there is no current URL. If the call performs necessary
1558 // initialization, break that out.
1559 [self loadCurrentURL];
1562 // Display overlay view until current url has finished loading or delay and
1563 // then transition away.
1564 if ((_overlayPreviewMode || _usePlaceholderOverlay) && !isChromeScheme)
1565 [self addPlaceholderOverlay];
1567 // Don't reset the overlay flag if in preview mode.
1568 if (!_overlayPreviewMode)
1569 _usePlaceholderOverlay = NO;
1573 - (BOOL)shouldReload:(const GURL&)destinationURL
1574 transition:(ui::PageTransition)transition {
1575 // Do a reload if the user hits enter in the address bar or re-types a URL.
1576 CRWSessionController* sessionController =
1577 _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1578 web::NavigationItem* item =
1579 _webStateImpl->GetNavigationManagerImpl().GetVisibleItem();
1580 return (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) && item &&
1581 (destinationURL == item->GetURL() ||
1582 destinationURL == [sessionController currentEntry].originalUrl);
1585 // Reload either the web view or the native content depending on which is
1587 - (void)reloadInternal {
1588 // Clear last user interaction.
1589 // TODO(jyquinn): Move to after the load commits, in the subclass
1590 // implementation. This will be inaccurate if the reload fails or is
1592 _lastUserInteraction.reset();
1593 web::RecordAction(UserMetricsAction("Reload"));
1595 // Just as we don't use the WebView native back and forward navigation
1596 // (preferring to load the URLs manually) we don't use the native reload.
1597 // This ensures state processing and delegate calls are consistent.
1598 [self loadCurrentURL];
1600 [self.nativeController reload];
1605 [_delegate webWillReload];
1607 _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_RELOAD);
1608 [self reloadInternal];
1609 _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1612 - (void)loadCancelled {
1613 // Current load will not complete; this should be communicated upstream to the
1615 switch (_loadPhase) {
1616 case web::LOAD_REQUESTED:
1617 // Load phase after abort is always PAGE_LOADED.
1618 _loadPhase = web::PAGE_LOADED;
1620 _webStateImpl->SetIsLoading(false);
1622 [_delegate webCancelStartLoadingRequest];
1624 case web::PAGE_LOADING:
1625 // The previous load never fully completed before this page change. The
1626 // loadPhase is changed to PAGE_LOADED to indicate the cycle is complete,
1627 // and the delegate is called.
1628 _loadPhase = web::PAGE_LOADED;
1630 // RequestTracker expects StartPageLoad to be followed by
1631 // FinishPageLoad, passing the exact same URL.
1632 self.webStateImpl->GetRequestTracker()->FinishPageLoad(
1633 _URLOnStartLoading, false);
1635 [_delegate webLoadCancelled:_URLOnStartLoading];
1637 case web::PAGE_LOADED:
1643 [self abortWebLoad];
1644 [self loadCancelled];
1647 - (void)prepareForGoBack {
1648 // Make sure any transitions that may have occurred have been seen and acted
1649 // on by the CRWWebController, so the history stack and state of the
1650 // CRWWebController is 100% up to date before the stack navigation starts.
1652 [self injectEarlyInjectionScripts];
1653 [self checkForUnexpectedURLChange];
1655 // Discard any outstanding pending entries before adjusting the navigation
1657 CRWSessionController* sessionController =
1658 _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1659 [sessionController discardNonCommittedEntries];
1661 bool wasShowingInterstitial = _webStateImpl->IsShowingWebInterstitial();
1663 // Call into the delegate before |recordStateInHistory|.
1664 // TODO(rohitrao): Can this be reordered after |recordStateInHistory|?
1665 [_delegate webDidPrepareForGoBack];
1667 // Before changing the current session history entry, record the tab state.
1668 if (!wasShowingInterstitial) {
1669 [self recordStateInHistory];
1681 - (void)goDelta:(int)delta {
1687 // Abort if there is nothing next in the history.
1688 // Note that it is NOT checked that the history depth is at least |delta|.
1689 if ((delta < 0 && ![self canGoBack]) || (delta > 0 && ![self canGoForward])) {
1694 [self prepareForGoBack];
1696 // Before changing the current session history entry, record the tab state.
1697 [self recordStateInHistory];
1700 [_delegate webWillGoDelta:delta];
1702 CRWSessionController* sessionController =
1703 _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1704 CRWSessionEntry* fromEntry = [sessionController currentEntry];
1705 [sessionController goDelta:delta];
1707 _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_HISTORY);
1708 [self finishHistoryNavigationFromEntry:fromEntry];
1709 _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1714 return _loadPhase == web::PAGE_LOADED;
1717 - (void)loadCompleteWithSuccess:(BOOL)loadSuccess {
1718 [self removePlaceholderOverlay];
1719 // The webView may have been torn down (or replaced by a native view). Be
1720 // safe and do nothing if that's happened.
1721 if (_loadPhase != web::PAGE_LOADING)
1724 DCHECK(self.webView);
1726 const GURL currentURL([self currentURL]);
1728 [self resetLoadState];
1729 _loadPhase = web::PAGE_LOADED;
1731 [self optOutScrollsToTopForSubviews];
1733 // Ensure the URL is as expected (and already reported to the delegate).
1734 DCHECK(currentURL == _lastRegisteredRequestURL)
1736 << "currentURL = [" << currentURL << "]" << std::endl
1737 << "_lastRegisteredRequestURL = [" << _lastRegisteredRequestURL << "]";
1739 // Perform post-load-finished updates.
1740 [self didFinishWithURL:currentURL loadSuccess:loadSuccess];
1742 // Execute the pending LoadCompleteActions.
1743 for (ProceduralBlock action in _pendingLoadCompleteActions.get()) {
1746 [_pendingLoadCompleteActions removeAllObjects];
1749 - (void)didFinishWithURL:(const GURL&)currentURL loadSuccess:(BOOL)loadSuccess {
1750 DCHECK(_loadPhase == web::PAGE_LOADED);
1751 _webStateImpl->GetRequestTracker()->FinishPageLoad(currentURL, loadSuccess);
1752 // Reset the navigation type to the default value.
1753 // Note: it is possible that the web view has already started loading the
1754 // next page when this is called. In that case the cache mode can leak to
1755 // (some of) the requests of the next page. It's expected to be an edge case,
1756 // but if it becomes a problem it should be possible to notice it afterwards
1757 // and react to it (by warning the user or reloading the page for example).
1758 _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1759 _webStateImpl->GetRequestTracker()->SetCacheModeFromUIThread(
1760 _webStateImpl->GetCacheMode());
1762 [self restoreStateFromHistory];
1763 _webStateImpl->OnPageLoaded(currentURL, loadSuccess);
1764 _webStateImpl->SetIsLoading(false);
1765 // Inform the embedder the load completed.
1766 [_delegate webDidFinishWithURL:currentURL loadSuccess:loadSuccess];
1769 - (void)finishHistoryNavigationFromEntry:(CRWSessionEntry*)fromEntry {
1770 [_delegate webWillFinishHistoryNavigationFromEntry:fromEntry];
1772 // Check if toEntry was created by a JavaScript window.history.pushState()
1773 // call from fromEntry. If it was, don't load the URL. Instead update
1774 // UIWebView's URL and dispatch a popstate event.
1775 if ([_webStateImpl->GetNavigationManagerImpl().GetSessionController()
1776 isPushStateNavigationBetweenEntry:fromEntry
1777 andEntry:self.currentSessionEntry]) {
1778 NSString* state = [self currentSessionEntry]
1779 .navigationItemImpl->GetSerializedStateObject();
1780 [self finishPushStateNavigationToURL:[self currentNavigationURL]
1781 withStateObject:state];
1783 web::NavigationItem* currentItem =
1784 _webStateImpl->GetNavigationManagerImpl().GetVisibleItem();
1785 GURL endURL = [self URLForHistoryNavigationFromItem:fromEntry.navigationItem
1786 toItem:currentItem];
1787 ui::PageTransition transition = ui::PageTransitionFromInt(
1788 ui::PAGE_TRANSITION_RELOAD | ui::PAGE_TRANSITION_FORWARD_BACK);
1790 web::WebLoadParams params(endURL);
1792 params.referrer = currentItem->GetReferrer();
1794 params.transition_type = transition;
1795 [self loadWithParams:params];
1799 - (GURL)URLForHistoryNavigationFromItem:(web::NavigationItem*)fromItem
1800 toItem:(web::NavigationItem*)toItem {
1801 const GURL& startURL = fromItem->GetURL();
1802 const GURL& endURL = toItem->GetURL();
1804 // Check the state of the fragments on both URLs (aka, is there a '#' in the
1806 if (!startURL.has_ref() || endURL.has_ref()) {
1810 // startURL contains a fragment and endURL doesn't. Remove the fragment from
1811 // startURL and compare the resulting string to endURL. If they are equal, add
1812 // # to endURL to cause a hashchange event.
1813 GURL hashless = web::GURLByRemovingRefFromGURL(startURL);
1815 if (hashless != endURL)
1818 url::StringPieceReplacements<std::string> emptyRef;
1819 emptyRef.SetRefStr("");
1820 GURL newEndURL = endURL.ReplaceComponents(emptyRef);
1821 toItem->SetURL(newEndURL);
1825 - (void)evaluateJavaScript:(NSString*)script
1827 (void (^)(scoped_ptr<base::Value>, NSError*))handler {
1828 [self evaluateJavaScript:script
1829 stringResultHandler:^(NSString* stringResult, NSError* error) {
1830 DCHECK(stringResult || error);
1832 scoped_ptr<base::Value> result(
1833 base::JSONReader::Read(base::SysNSStringToUTF8(stringResult)));
1834 handler(result.Pass(), error);
1839 - (void)addGestureRecognizerToWebView:(UIGestureRecognizer*)recognizer {
1840 if ([_gestureRecognizers containsObject:recognizer])
1843 [self.webView addGestureRecognizer:recognizer];
1844 [_gestureRecognizers addObject:recognizer];
1847 - (void)removeGestureRecognizerFromWebView:(UIGestureRecognizer*)recognizer {
1848 if (![_gestureRecognizers containsObject:recognizer])
1851 [self.webView removeGestureRecognizer:recognizer];
1852 [_gestureRecognizers removeObject:recognizer];
1855 - (void)addToolbarViewToWebView:(UIView*)toolbarView {
1856 DCHECK(toolbarView);
1857 if ([_webViewToolbars containsObject:toolbarView])
1859 [_webViewToolbars addObject:toolbarView];
1861 [self.containerView addToolbar:toolbarView];
1864 - (void)removeToolbarViewFromWebView:(UIView*)toolbarView {
1865 if (![_webViewToolbars containsObject:toolbarView])
1867 [_webViewToolbars removeObject:toolbarView];
1869 [self.containerView removeToolbar:toolbarView];
1872 - (CRWJSInjectionReceiver*)jsInjectionReceiver {
1873 return _jsInjectionReceiver;
1876 - (BOOL)shouldClosePageOnNativeApplicationLoad {
1877 // The page should be closed if it was initiated by the DOM and there has been
1878 // no user interaction with the page since the web view was created.
1879 return self.sessionController.openedByDOM &&
1880 !_userInteractedWithWebController;
1883 - (BOOL)isBeingDestroyed {
1884 return _isBeingDestroyed;
1891 - (web::ReferrerPolicy)referrerPolicyFromString:(const std::string&)policy {
1892 // TODO(stuartmorgan): Remove this temporary bridge to the helper function
1893 // once the referrer handling moves into the subclasses.
1894 return web::ReferrerPolicyFromString(policy);
1898 #pragma mark CRWJSInjectionEvaluator Methods
1900 - (void)evaluateJavaScript:(NSString*)script
1901 stringResultHandler:(web::JavaScriptCompletion)handler {
1902 // Subclasses must implement this method.
1906 - (BOOL)scriptHasBeenInjectedForClass:(Class)jsInjectionManagerClass
1907 presenceBeacon:(NSString*)beacon {
1908 // Subclasses must implement this method.
1913 - (void)injectScript:(NSString*)script forClass:(Class)JSInjectionManagerClass {
1914 // Make sure that CRWJSEarlyScriptManager has been injected.
1915 BOOL ealyScriptInjected =
1916 [self scriptHasBeenInjectedForClass:[CRWJSEarlyScriptManager class]
1917 presenceBeacon:[_earlyScriptManager presenceBeacon]];
1918 if (!ealyScriptInjected &&
1919 JSInjectionManagerClass != [CRWJSEarlyScriptManager class]) {
1920 [_earlyScriptManager inject];
1924 - (web::WebViewType)webViewType {
1925 // Subclasses must implement this method.
1927 return web::UI_WEB_VIEW_TYPE;
1932 - (void)evaluateUserJavaScript:(NSString*)script {
1933 // Subclasses must implement this method.
1937 - (void)didFinishNavigation {
1938 // This can be called at multiple times after the document has loaded. Do
1939 // nothing if the document has already loaded.
1940 if (_loadPhase == web::PAGE_LOADED)
1942 [self loadCompleteWithSuccess:YES];
1945 - (BOOL)respondToMessage:(base::DictionaryValue*)message
1946 userIsInteracting:(BOOL)userIsInteracting
1947 originURL:(const GURL&)originURL {
1948 std::string command;
1949 if (!message->GetString("command", &command)) {
1950 DLOG(WARNING) << "JS message parameter not found: command";
1954 SEL handler = [self selectorToHandleJavaScriptCommand:command];
1956 if (!self.webStateImpl->OnScriptCommandReceived(
1957 command, *message, originURL, userIsInteracting)) {
1958 // Message was either unexpected or not correctly handled.
1959 // Page is reset as a precaution.
1960 DLOG(WARNING) << "Unexpected message received: " << command;
1966 typedef BOOL (*HandlerType)(id, SEL, base::DictionaryValue*, NSDictionary*);
1967 HandlerType handlerImplementation =
1968 reinterpret_cast<HandlerType>([self methodForSelector:handler]);
1969 DCHECK(handlerImplementation);
1970 NSMutableDictionary* context =
1971 [NSMutableDictionary dictionaryWithObject:@(userIsInteracting)
1972 forKey:web::kUserIsInteractingKey];
1973 NSURL* originNSURL = net::NSURLWithGURL(originURL);
1975 context[web::kOriginURLKey] = originNSURL;
1976 return handlerImplementation(self, handler, message, context);
1979 - (SEL)selectorToHandleJavaScriptCommand:(const std::string&)command {
1980 static std::map<std::string, SEL>* handlers = nullptr;
1981 static dispatch_once_t onceToken;
1982 dispatch_once(&onceToken, ^{
1983 handlers = new std::map<std::string, SEL>();
1984 (*handlers)["addPluginPlaceholders"] =
1985 @selector(handleAddPluginPlaceholdersMessage:context:);
1986 (*handlers)["chrome.send"] = @selector(handleChromeSendMessage:context:);
1987 (*handlers)["console"] = @selector(handleConsoleMessage:context:);
1988 (*handlers)["dialog.suppressed"] =
1989 @selector(handleDialogSuppressedMessage:context:);
1990 (*handlers)["dialog.willShow"] =
1991 @selector(handleDialogWillShowMessage:context:);
1992 (*handlers)["document.favicons"] =
1993 @selector(handleDocumentFaviconsMessage:context:);
1994 (*handlers)["document.retitled"] =
1995 @selector(handleDocumentRetitledMessage:context:);
1996 (*handlers)["document.submit"] =
1997 @selector(handleDocumentSubmitMessage:context:);
1998 (*handlers)["externalRequest"] =
1999 @selector(handleExternalRequestMessage:context:);
2000 (*handlers)["form.activity"] =
2001 @selector(handleFormActivityMessage:context:);
2002 (*handlers)["navigator.credentials.request"] =
2003 @selector(handleCredentialsRequestedMessage:context:);
2004 (*handlers)["navigator.credentials.notifySignedIn"] =
2005 @selector(handleSignedInMessage:context:);
2006 (*handlers)["navigator.credentials.notifySignedOut"] =
2007 @selector(handleSignedOutMessage:context:);
2008 (*handlers)["navigator.credentials.notifyFailedSignIn"] =
2009 @selector(handleSignInFailedMessage:context:);
2010 (*handlers)["resetExternalRequest"] =
2011 @selector(handleResetExternalRequestMessage:context:);
2012 (*handlers)["window.close.self"] =
2013 @selector(handleWindowCloseSelfMessage:context:);
2014 (*handlers)["window.error"] = @selector(handleWindowErrorMessage:context:);
2015 (*handlers)["window.hashchange"] =
2016 @selector(handleWindowHashChangeMessage:context:);
2017 (*handlers)["window.history.back"] =
2018 @selector(handleWindowHistoryBackMessage:context:);
2019 (*handlers)["window.history.willChangeState"] =
2020 @selector(handleWindowHistoryWillChangeStateMessage:context:);
2021 (*handlers)["window.history.didPushState"] =
2022 @selector(handleWindowHistoryDidPushStateMessage:context:);
2023 (*handlers)["window.history.didReplaceState"] =
2024 @selector(handleWindowHistoryDidReplaceStateMessage:context:);
2025 (*handlers)["window.history.forward"] =
2026 @selector(handleWindowHistoryForwardMessage:context:);
2027 (*handlers)["window.history.go"] =
2028 @selector(handleWindowHistoryGoMessage:context:);
2031 auto iter = handlers->find(command);
2032 return iter != handlers->end() ? iter->second : nullptr;
2036 #pragma mark JavaScript message handlers
2038 - (BOOL)handleAddPluginPlaceholdersMessage:(base::DictionaryValue*)message
2039 context:(NSDictionary*)context {
2040 // Inject the script that adds the plugin placeholders.
2041 [[_jsInjectionReceiver
2042 instanceOfClass:[CRWJSPluginPlaceholderManager class]] inject];
2046 - (BOOL)handleChromeSendMessage:(base::DictionaryValue*)message
2047 context:(NSDictionary*)context {
2048 if (_webStateImpl->HasWebUI()) {
2049 const GURL currentURL([self currentURL]);
2050 if (web::GetWebClient()->IsAppSpecificURL(currentURL)) {
2051 std::string messageContent;
2052 base::ListValue* arguments = nullptr;
2053 if (!message->GetString("message", &messageContent)) {
2054 DLOG(WARNING) << "JS message parameter not found: message";
2057 if (!message->GetList("arguments", &arguments)) {
2058 DLOG(WARNING) << "JS message parameter not found: arguments";
2061 _webStateImpl->OnScriptCommandReceived(
2062 messageContent, *message, currentURL,
2063 context[web::kUserIsInteractingKey]);
2064 _webStateImpl->ProcessWebUIMessage(currentURL, messageContent,
2071 << "chrome.send message not handled because WebUI was not found.";
2075 - (BOOL)handleConsoleMessage:(base::DictionaryValue*)message
2076 context:(NSDictionary*)context {
2077 // Do not log if JS logging is off.
2078 if (![[NSUserDefaults standardUserDefaults] boolForKey:web::kLogJavaScript]) {
2083 if (!message->GetString("method", &method)) {
2084 DLOG(WARNING) << "JS message parameter not found: method";
2087 std::string consoleMessage;
2088 if (!message->GetString("message", &consoleMessage)) {
2089 DLOG(WARNING) << "JS message parameter not found: message";
2093 if (!message->GetString("origin", &origin)) {
2094 DLOG(WARNING) << "JS message parameter not found: origin";
2098 DVLOG(0) << origin << " [" << method << "] " << consoleMessage;
2102 - (BOOL)handleDialogSuppressedMessage:(base::DictionaryValue*)message
2103 context:(NSDictionary*)context {
2105 respondsToSelector:@selector(webControllerDidSuppressDialog:)]) {
2106 [_delegate webControllerDidSuppressDialog:self];
2111 - (BOOL)handleDialogWillShowMessage:(base::DictionaryValue*)message
2112 context:(NSDictionary*)context {
2113 if ([_delegate respondsToSelector:@selector(webControllerWillShowDialog:)]) {
2114 [_delegate webControllerWillShowDialog:self];
2119 - (BOOL)handleDocumentFaviconsMessage:(base::DictionaryValue*)message
2120 context:(NSDictionary*)context {
2121 base::ListValue* favicons = nullptr;
2122 if (!message->GetList("favicons", &favicons)) {
2123 DLOG(WARNING) << "JS message parameter not found: favicons";
2126 std::vector<web::FaviconURL> urls;
2127 for (size_t fav_idx = 0; fav_idx != favicons->GetSize(); ++fav_idx) {
2128 base::DictionaryValue* favicon = nullptr;
2129 if (!favicons->GetDictionary(fav_idx, &favicon))
2133 if (!favicon->GetString("href", &href)) {
2134 DLOG(WARNING) << "JS message parameter not found: href";
2137 if (!favicon->GetString("rel", &rel)) {
2138 DLOG(WARNING) << "JS message parameter not found: rel";
2141 web::FaviconURL::IconType icon_type = web::FaviconURL::FAVICON;
2142 if (rel == "apple-touch-icon")
2143 icon_type = web::FaviconURL::TOUCH_ICON;
2144 else if (rel == "apple-touch-icon-precomposed")
2145 icon_type = web::FaviconURL::TOUCH_PRECOMPOSED_ICON;
2147 web::FaviconURL(GURL(href), icon_type, std::vector<gfx::Size>()));
2150 _webStateImpl->OnFaviconUrlUpdated(urls);
2154 - (BOOL)handleDocumentSubmitMessage:(base::DictionaryValue*)message
2155 context:(NSDictionary*)context {
2157 if (!message->GetString("href", &href)) {
2158 DLOG(WARNING) << "JS message parameter not found: href";
2161 const GURL targetURL(href);
2162 const GURL currentURL([self currentURL]);
2163 bool targetsFrame = false;
2164 message->GetBoolean("targetsFrame", &targetsFrame);
2165 if (!targetsFrame && web::UrlHasWebScheme(targetURL)) {
2166 // The referrer is not known yet, and will be updated later.
2167 const web::Referrer emptyReferrer;
2168 [self registerLoadRequest:targetURL
2169 referrer:emptyReferrer
2170 transition:ui::PAGE_TRANSITION_FORM_SUBMIT];
2172 std::string formName;
2173 message->GetString("formName", &formName);
2174 base::scoped_nsobject<NSSet> observers([_observers copy]);
2175 // We decide the form is user-submitted if the user has interacted with
2176 // the main page (using logic from the popup blocker), or if the keyboard
2178 BOOL submittedByUser = [context[web::kUserIsInteractingKey] boolValue] ||
2179 [_webViewProxy getKeyboardAccessory];
2180 _webStateImpl->OnDocumentSubmitted(formName, submittedByUser);
2184 - (BOOL)handleExternalRequestMessage:(base::DictionaryValue*)message
2185 context:(NSDictionary*)context {
2188 std::string referrerPolicy;
2189 if (!message->GetString("href", &href)) {
2190 DLOG(WARNING) << "JS message parameter not found: href";
2193 if (!message->GetString("target", &target)) {
2194 DLOG(WARNING) << "JS message parameter not found: target";
2197 if (!message->GetString("referrerPolicy", &referrerPolicy)) {
2198 DLOG(WARNING) << "JS message parameter not found: referrerPolicy";
2201 // Round-trip the href through NSURL; this URL will be compared as a
2202 // string against a UIWebView-provided NSURL later, and must match exactly
2203 // for the new window to trigger, so the escaping needs to be NSURL-style.
2204 // TODO(stuartmorgan): Comparing against a URL whose exact formatting we
2205 // don't control is fundamentally fragile; try to find another
2206 // way of handling this.
2207 DCHECK(context[web::kUserIsInteractingKey]);
2208 NSString* windowName =
2209 base::SysUTF8ToNSString(href + web::kWindowNameSeparator + target);
2210 _externalRequest.reset(new web::NewWindowInfo(
2211 net::GURLWithNSURL(net::NSURLWithGURL(GURL(href))), windowName,
2212 web::ReferrerPolicyFromString(referrerPolicy),
2213 [context[web::kUserIsInteractingKey] boolValue]));
2217 - (BOOL)handleFormActivityMessage:(base::DictionaryValue*)message
2218 context:(NSDictionary*)context {
2219 std::string formName;
2220 std::string fieldName;
2223 int keyCode = web::WebStateObserver::kInvalidFormKeyCode;
2224 bool inputMissing = false;
2225 if (!message->GetString("formName", &formName) ||
2226 !message->GetString("fieldName", &fieldName) ||
2227 !message->GetString("type", &type) ||
2228 !message->GetString("value", &value)) {
2229 inputMissing = true;
2232 if (!message->GetInteger("keyCode", &keyCode) || keyCode < 0)
2233 keyCode = web::WebStateObserver::kInvalidFormKeyCode;
2234 _webStateImpl->OnFormActivityRegistered(formName, fieldName, type, value,
2235 keyCode, inputMissing);
2239 - (BOOL)handleCredentialsRequestedMessage:(base::DictionaryValue*)message
2240 context:(NSDictionary*)context {
2241 int request_id = -1;
2242 if (!message->GetInteger("requestId", &request_id)) {
2243 DLOG(WARNING) << "JS message parameter not found: requestId";
2246 bool suppress_ui = false;
2247 if (!message->GetBoolean("suppressUI", &suppress_ui)) {
2248 DLOG(WARNING) << "JS message parameter not found: suppressUI";
2251 base::ListValue* federations_value = nullptr;
2252 if (!message->GetList("federations", &federations_value)) {
2253 DLOG(WARNING) << "JS message parameter not found: federations";
2256 std::vector<std::string> federations;
2257 for (auto federation_value : *federations_value) {
2258 std::string federation;
2259 if (!federation_value->GetAsString(&federation)) {
2260 DLOG(WARNING) << "JS message parameter 'federations' contains wrong type";
2263 federations.push_back(federation);
2265 DCHECK(context[web::kUserIsInteractingKey]);
2266 _webStateImpl->OnCredentialsRequested(
2267 request_id, net::GURLWithNSURL(context[web::kOriginURLKey]), suppress_ui,
2268 federations, [context[web::kUserIsInteractingKey] boolValue]);
2272 - (BOOL)handleSignedInMessage:(base::DictionaryValue*)message
2273 context:(NSDictionary*)context {
2274 int request_id = -1;
2275 if (!message->GetInteger("requestId", &request_id)) {
2276 DLOG(WARNING) << "JS message parameter not found: requestId";
2279 base::DictionaryValue* credential_data = nullptr;
2280 web::Credential credential;
2281 if (message->GetDictionary("credential", &credential_data)) {
2282 if (!web::DictionaryValueToCredential(*credential_data, &credential)) {
2283 DLOG(WARNING) << "JS message parameter 'credential' is invalid";
2286 _webStateImpl->OnSignedIn(request_id,
2287 net::GURLWithNSURL(context[web::kOriginURLKey]),
2290 _webStateImpl->OnSignedIn(request_id,
2291 net::GURLWithNSURL(context[web::kOriginURLKey]));
2296 - (BOOL)handleSignedOutMessage:(base::DictionaryValue*)message
2297 context:(NSDictionary*)context {
2298 int request_id = -1;
2299 if (!message->GetInteger("requestId", &request_id)) {
2300 DLOG(WARNING) << "JS message parameter not found: requestId";
2303 _webStateImpl->OnSignedOut(request_id,
2304 net::GURLWithNSURL(context[web::kOriginURLKey]));
2308 - (BOOL)handleSignInFailedMessage:(base::DictionaryValue*)message
2309 context:(NSDictionary*)context {
2310 int request_id = -1;
2311 if (!message->GetInteger("requestId", &request_id)) {
2312 DLOG(WARNING) << "JS message parameter not found: requestId";
2315 base::DictionaryValue* credential_data = nullptr;
2316 web::Credential credential;
2317 if (message->GetDictionary("credential", &credential_data)) {
2318 if (!web::DictionaryValueToCredential(*credential_data, &credential)) {
2319 DLOG(WARNING) << "JS message parameter 'credential' is invalid";
2322 _webStateImpl->OnSignInFailed(
2323 request_id, net::GURLWithNSURL(context[web::kOriginURLKey]),
2326 _webStateImpl->OnSignInFailed(
2327 request_id, net::GURLWithNSURL(context[web::kOriginURLKey]));
2332 - (BOOL)handleResetExternalRequestMessage:(base::DictionaryValue*)message
2333 context:(NSDictionary*)context {
2334 _externalRequest.reset();
2338 - (BOOL)handleWindowCloseSelfMessage:(base::DictionaryValue*)message
2339 context:(NSDictionary*)context {
2344 - (BOOL)handleWindowErrorMessage:(base::DictionaryValue*)message
2345 context:(NSDictionary*)context {
2346 std::string errorMessage;
2347 if (!message->GetString("message", &errorMessage)) {
2348 DLOG(WARNING) << "JS message parameter not found: message";
2351 DLOG(ERROR) << "JavaScript error: " << errorMessage
2352 << " URL:" << [self currentURL].spec();
2356 - (BOOL)handleWindowHashChangeMessage:(base::DictionaryValue*)message
2357 context:(NSDictionary*)context {
2358 [self checkForUnexpectedURLChange];
2360 // Notify the observers.
2361 _webStateImpl->OnUrlHashChanged();
2365 - (BOOL)handleWindowHistoryBackMessage:(base::DictionaryValue*)message
2366 context:(NSDictionary*)context {
2371 - (BOOL)handleWindowHistoryForwardMessage:(base::DictionaryValue*)message
2372 context:(NSDictionary*)context {
2377 - (BOOL)handleWindowHistoryGoMessage:(base::DictionaryValue*)message
2378 context:(NSDictionary*)context {
2380 message->GetInteger("value", &delta);
2381 [self goDelta:delta];
2385 - (BOOL)handleWindowHistoryWillChangeStateMessage:(base::DictionaryValue*)unused
2386 context:(NSDictionary*)unusedContext {
2387 // This dummy handler is a workaround for crbug.com/490673. Issue was
2388 // happening when two sequential calls of window.history.pushState were
2389 // performed by the page. In that case state was changed twice before
2390 // first change was reported to embedder (and first URL change was reported
2393 // Using dummy handler for window.history.willChangeState message holds
2394 // second state change until the first change is reported, because messages
2395 // are queued. This is essentially a sleep, and not the real fix of the
2396 // problem. TODO(eugenebut): refactor handleWindowHistoryDidPushStateMessage:
2397 // to avoid this "sleep".
2401 - (BOOL)handleWindowHistoryDidPushStateMessage:(base::DictionaryValue*)message
2402 context:(NSDictionary*)context {
2403 std::string pageURL;
2404 std::string baseURL;
2405 if (!message->GetString("pageUrl", &pageURL) ||
2406 !message->GetString("baseUrl", &baseURL)) {
2407 DLOG(WARNING) << "JS message parameter not found: pageUrl or baseUrl";
2410 GURL pushURL = web::history_state_util::GetHistoryStateChangeUrl(
2411 [self currentURL], GURL(baseURL), pageURL);
2412 // UIWebView seems to choke on unicode characters that haven't been
2413 // escaped; escape the URL now so the expected load URL is correct.
2414 pushURL = URLEscapedForHistory(pushURL);
2415 if (!pushURL.is_valid())
2417 const NavigationManagerImpl& navigationManager =
2418 _webStateImpl->GetNavigationManagerImpl();
2419 web::NavigationItem* navItem = [self currentNavItem];
2420 // PushState happened before first navigation entry or called right after
2421 // window.open when the url is empty.
2423 (navigationManager.GetEntryCount() <= 1 && navItem->GetURL().is_empty()))
2425 if (!web::history_state_util::IsHistoryStateChangeValid(navItem->GetURL(),
2427 // A redirect may have occurred just prior to the pushState. Check if
2428 // the URL needs to be updated.
2429 // TODO(bdibello): Investigate how the pushState() is handled before the
2430 // redirect and after core.js injection.
2431 [self checkForUnexpectedURLChange];
2433 if (!web::history_state_util::IsHistoryStateChangeValid(
2434 [self currentNavItem]->GetURL(), pushURL)) {
2435 // If the current session entry URL origin still doesn't match pushURL's
2436 // origin, ignore the pushState. This can happen if a new URL is loaded
2437 // just before the pushState.
2440 std::string stateObjectJSON;
2441 if (!message->GetString("stateObject", &stateObjectJSON)) {
2442 DLOG(WARNING) << "JS message parameter not found: stateObject";
2445 NSString* stateObject = base::SysUTF8ToNSString(stateObjectJSON);
2446 _URLOnStartLoading = pushURL;
2447 _lastRegisteredRequestURL = pushURL;
2449 // If the user interacted with the page, categorize it as a link navigation.
2450 // If not, categorize it is a client redirect as it occurred without user
2451 // input and should not be added to the history stack.
2452 // TODO(ios): Improve transition detection.
2453 ui::PageTransition transition =
2454 [context[web::kUserIsInteractingKey] boolValue]
2455 ? ui::PAGE_TRANSITION_LINK
2456 : ui::PAGE_TRANSITION_CLIENT_REDIRECT;
2457 [self pushStateWithPageURL:pushURL
2458 stateObject:stateObject
2459 transition:transition];
2461 NSString* replaceWebViewJS =
2462 [self javascriptToReplaceWebViewURL:pushURL stateObjectJSON:stateObject];
2463 base::WeakNSObject<CRWWebController> weakSelf(self);
2464 [self evaluateJavaScript:replaceWebViewJS
2465 stringResultHandler:^(NSString*, NSError*) {
2466 if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
2468 base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
2469 [strongSelf optOutScrollsToTopForSubviews];
2470 // Notify the observers.
2471 strongSelf.get()->_webStateImpl->OnHistoryStateChanged();
2472 [strongSelf didFinishNavigation];
2477 - (BOOL)handleWindowHistoryDidReplaceStateMessage:
2478 (base::DictionaryValue*)message
2479 context:(NSDictionary*)context {
2480 std::string pageURL;
2481 std::string baseURL;
2482 if (!message->GetString("pageUrl", &pageURL) ||
2483 !message->GetString("baseUrl", &baseURL)) {
2484 DLOG(WARNING) << "JS message parameter not found: pageUrl or baseUrl";
2487 GURL replaceURL = web::history_state_util::GetHistoryStateChangeUrl(
2488 [self currentURL], GURL(baseURL), pageURL);
2489 // UIWebView seems to choke on unicode characters that haven't been
2490 // escaped; escape the URL now so the expected load URL is correct.
2491 replaceURL = URLEscapedForHistory(replaceURL);
2492 if (!replaceURL.is_valid())
2494 const NavigationManagerImpl& navigationManager =
2495 _webStateImpl->GetNavigationManagerImpl();
2496 web::NavigationItem* navItem = [self currentNavItem];
2497 // ReplaceState happened before first navigation entry or called right
2498 // after window.open when the url is empty/not valid.
2500 (navigationManager.GetEntryCount() <= 1 && navItem->GetURL().is_empty()))
2502 if (!web::history_state_util::IsHistoryStateChangeValid(navItem->GetURL(),
2504 // A redirect may have occurred just prior to the replaceState. Check if
2505 // the URL needs to be updated.
2506 [self checkForUnexpectedURLChange];
2508 if (!web::history_state_util::IsHistoryStateChangeValid(
2509 [self currentNavItem]->GetURL(), replaceURL)) {
2510 // If the current session entry URL origin still doesn't match
2511 // replaceURL's origin, ignore the replaceState. This can happen if a
2512 // new URL is loaded just before the replaceState.
2515 std::string stateObjectJSON;
2516 if (!message->GetString("stateObject", &stateObjectJSON)) {
2517 DLOG(WARNING) << "JS message parameter not found: stateObject";
2520 NSString* stateObject = base::SysUTF8ToNSString(stateObjectJSON);
2521 _URLOnStartLoading = replaceURL;
2522 _lastRegisteredRequestURL = replaceURL;
2523 [self replaceStateWithPageURL:replaceURL stateObject:stateObject];
2524 NSString* replaceStateJS = [self javascriptToReplaceWebViewURL:replaceURL
2525 stateObjectJSON:stateObject];
2526 base::WeakNSObject<CRWWebController> weakSelf(self);
2527 [self evaluateJavaScript:replaceStateJS
2528 stringResultHandler:^(NSString*, NSError*) {
2529 if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
2531 base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
2532 [strongSelf didFinishNavigation];
2539 - (BOOL)wantsKeyboardShield {
2540 if ([self.nativeController
2541 respondsToSelector:@selector(wantsKeyboardShield)]) {
2542 return [self.nativeController wantsKeyboardShield];
2547 - (BOOL)wantsLocationBarHintText {
2548 if ([self.nativeController
2549 respondsToSelector:@selector(wantsLocationBarHintText)]) {
2550 return [self.nativeController wantsLocationBarHintText];
2555 // TODO(stuartmorgan): This method conflates document changes and URL changes;
2556 // we should be distinguishing better, and be clear about the expected
2557 // WebDelegate and WCO callbacks in each case.
2558 - (void)webPageChanged {
2559 DCHECK(_loadPhase == web::LOAD_REQUESTED);
2561 const GURL currentURL([self currentURL]);
2562 web::Referrer referrer = [self currentReferrer];
2563 // If no referrer was known in advance, record it now. (If there was one,
2564 // keep it since it will have a more accurate URL and policy than what can
2565 // be extracted from the landing page.)
2566 web::NavigationItem* currentItem = [self currentNavItem];
2567 if (!currentItem->GetReferrer().url.is_valid()) {
2568 currentItem->SetReferrer(referrer);
2571 // TODO(stuartmorgan): This shouldn't be called for hash state or
2572 // push/replaceState.
2573 [self resetDocumentSpecificState];
2575 [self didStartLoadingURL:currentURL updateHistory:YES];
2578 - (void)resetDocumentSpecificState {
2579 _lastUserInteraction.reset();
2580 _clickInProgress = NO;
2581 _lastSeenWindowID.reset([[_windowIDJSManager windowId] copy]);
2584 - (void)didStartLoadingURL:(const GURL&)url updateHistory:(BOOL)updateHistory {
2585 _loadPhase = web::PAGE_LOADING;
2586 _URLOnStartLoading = url;
2587 _displayStateOnStartLoading = self.pageDisplayState;
2589 _userInteractionRegistered = NO;
2590 _pageHasZoomed = NO;
2592 [[self sessionController] commitPendingEntry];
2593 _webStateImpl->GetRequestTracker()->StartPageLoad(
2594 url, [[self sessionController] currentEntry]);
2595 [_delegate webDidStartLoadingURL:url shouldUpdateHistory:updateHistory];
2598 - (BOOL)checkForUnexpectedURLChange {
2599 // Subclasses may override this method to check for and handle URL changes.
2604 if ([self.nativeController respondsToSelector:@selector(wasShown)]) {
2605 [self.nativeController wasShown];
2612 if ([self.nativeController respondsToSelector:@selector(wasHidden)]) {
2613 [self.nativeController wasHidden];
2617 + (BOOL)webControllerCanShow:(const GURL&)url {
2618 return web::UrlHasWebScheme(url) ||
2619 web::GetWebClient()->IsAppSpecificURL(url) ||
2620 url.SchemeIs(url::kFileScheme) || url.SchemeIs(url::kAboutScheme);
2623 - (void)setUserInteractionRegistered:(BOOL)flag {
2624 _userInteractionRegistered = flag;
2627 - (BOOL)userInteractionRegistered {
2628 return _userInteractionRegistered;
2631 - (BOOL)useDesktopUserAgent {
2632 web::NavigationItem* item = [self currentNavItem];
2633 return item && item->IsOverridingUserAgent();
2636 - (void)cachePOSTDataForRequest:(NSURLRequest*)request
2637 inSessionEntry:(CRWSessionEntry*)currentSessionEntry {
2638 NSUInteger maxPOSTDataSizeInBytes = 4096;
2639 NSString* cookieHeaderName = @"cookie";
2641 web::NavigationItemImpl* currentItem = currentSessionEntry.navigationItemImpl;
2642 DCHECK(currentItem);
2643 const bool shouldUpdateEntry =
2644 ui::PageTransitionCoreTypeIs(currentItem->GetTransitionType(),
2645 ui::PAGE_TRANSITION_FORM_SUBMIT) &&
2646 ![request HTTPBodyStream] && // Don't cache streams.
2647 !currentItem->HasPostData() &&
2648 currentItem->GetURL() == net::GURLWithNSURL([request URL]);
2649 const bool belowSizeCap =
2650 [[request HTTPBody] length] < maxPOSTDataSizeInBytes;
2651 DLOG_IF(WARNING, shouldUpdateEntry && !belowSizeCap)
2652 << "Data in POST request exceeds the size cap (" << maxPOSTDataSizeInBytes
2653 << " bytes), and will not be cached.";
2655 if (shouldUpdateEntry && belowSizeCap) {
2656 currentItem->SetPostData([request HTTPBody]);
2657 currentItem->ResetHttpRequestHeaders();
2658 currentItem->AddHttpRequestHeaders([request allHTTPHeaderFields]);
2659 // Don't cache the "Cookie" header.
2660 // According to NSURLRequest documentation, |-valueForHTTPHeaderField:| is
2661 // case insensitive, so it's enough to test the lower case only.
2662 if ([request valueForHTTPHeaderField:cookieHeaderName]) {
2663 // Case insensitive search in |headers|.
2664 NSSet* cookieKeys = [currentItem->GetHttpRequestHeaders()
2665 keysOfEntriesPassingTest:^(id key, id obj, BOOL* stop) {
2666 NSString* header = (NSString*)key;
2668 [header caseInsensitiveCompare:cookieHeaderName] ==
2673 DCHECK_EQ(1u, [cookieKeys count]);
2674 currentItem->RemoveHttpRequestHeaderForKey([cookieKeys anyObject]);
2679 // TODO(stuartmorgan): This is mostly logic from the original UIWebView delegate
2680 // method, which provides less information than the WKWebView version. Audit
2681 // this for things that should be handled in the subclass instead.
2682 - (BOOL)shouldAllowLoadWithRequest:(NSURLRequest*)request
2683 targetFrame:(const web::FrameInfo*)targetFrame
2684 isLinkClick:(BOOL)isLinkClick {
2685 GURL requestURL = net::GURLWithNSURL(request.URL);
2687 // Check if the request should be delayed.
2688 if (_externalRequest && _externalRequest->url == requestURL) {
2689 // Links that can't be shown in a tab by Chrome but can be handled by
2690 // external apps (e.g. tel:, mailto:) are opened directly despite the target
2691 // attribute on the link. We don't open a new tab for them because Mobile
2692 // Safari doesn't do that (and sites are expecting us to do the same) and
2693 // also because there would be nothing shown in that new tab; it would
2694 // remain on about:blank (see crbug.com/240178)
2695 if ([CRWWebController webControllerCanShow:requestURL] ||
2696 ![_delegate openExternalURL:requestURL]) {
2697 web::NewWindowInfo windowInfo = *_externalRequest;
2698 dispatch_async(dispatch_get_main_queue(), ^{
2699 [self openPopupWithInfo:windowInfo];
2702 _externalRequest.reset();
2706 BOOL shouldCheckNativeApp = [self shouldClosePageOnNativeApplicationLoad];
2708 // Check if the link navigation leads to a launch of an external app.
2709 // TODO(shreyasv): Change this such that handling/stealing of link navigations
2710 // is delegated to the WebDelegate and the logic around external app launching
2711 // is moved there as well.
2712 if (shouldCheckNativeApp || isLinkClick) {
2713 // Check If the URL is handled by a native app.
2714 if ([self urlTriggersNativeAppLaunch:requestURL
2715 sourceURL:[self currentNavigationURL]]) {
2716 // External app has been launched successfully. Stop the current page
2717 // load operation (e.g. notifying all observers) and record the URL so
2718 // that errors reported following the 'NO' reply can be safely ignored.
2719 if ([self shouldClosePageOnNativeApplicationLoad])
2720 [_delegate webPageOrderedClose];
2722 [_openedApplicationURL addObject:request.URL];
2727 // The WebDelegate may instruct the CRWWebController to stop loading, and
2728 // instead instruct the next page to be loaded in an animation.
2729 GURL mainDocumentURL = net::GURLWithNSURL(request.mainDocumentURL);
2730 DCHECK(self.webView);
2731 if (![self shouldOpenURL:requestURL
2732 mainDocumentURL:mainDocumentURL
2733 linkClicked:isLinkClick]) {
2737 // If the URL doesn't look like one we can show, try to open the link with an
2738 // external application.
2739 // TODO(droger): Check transition type before opening an external
2740 // application? For example, only allow it for TYPED and LINK transitions.
2741 if (![CRWWebController webControllerCanShow:requestURL]) {
2742 if (![self shouldOpenExternalURLRequest:request targetFrame:targetFrame]) {
2746 // Abort load if navigation is believed to be happening on the main frame.
2747 if ([self isPutativeMainFrameRequest:request targetFrame:targetFrame])
2750 if ([_delegate openExternalURL:requestURL]) {
2751 // Record the URL so that errors reported following the 'NO' reply can be
2753 [_openedApplicationURL addObject:request.URL];
2754 if ([self shouldClosePageOnNativeApplicationLoad])
2755 [_delegate webPageOrderedClose];
2760 if ([[request HTTPMethod] isEqualToString:@"POST"]) {
2761 [self cachePOSTDataForRequest:request
2762 inSessionEntry:[self currentSessionEntry]];
2768 - (void)restoreStateAfterURLRejection {
2769 [[self sessionController] discardNonCommittedEntries];
2771 // Re-register the user agent, because UIWebView will sometimes try to read
2772 // the agent again from a saved search result page in which no other page has
2773 // yet been loaded. See crbug.com/260370.
2774 [self registerUserAgent];
2776 // Reset |_lastRegisteredRequestURL| so that it reflects the URL from before
2777 // the load was rejected. This value may be out of sync because
2778 // |_lastRegisteredRequestURL| may have already been updated before the load
2780 _lastRegisteredRequestURL = [self currentURL];
2781 _loadPhase = web::PAGE_LOADING;
2782 [self didFinishNavigation];
2785 - (void)handleLoadError:(NSError*)error inMainFrame:(BOOL)inMainFrame {
2786 if ([error code] == NSURLErrorUnsupportedURL)
2788 // In cases where a Plug-in handles the load do not take any further action.
2789 if ([error.domain isEqual:base::SysUTF8ToNSString(web::kWebKitErrorDomain)] &&
2790 (error.code == web::kWebKitErrorPlugInLoadFailed ||
2791 error.code == web::kWebKitErrorCannotShowUrl))
2794 // Continue processing only if the error is on the main request or is the
2795 // result of a user interaction.
2796 NSDictionary* userInfo = [error userInfo];
2797 // |userinfo| contains the request creation date as a NSDate.
2798 NSTimeInterval requestCreationDate =
2799 [[userInfo objectForKey:@"CreationDate"] timeIntervalSinceReferenceDate];
2800 bool userInteracted = false;
2801 if (requestCreationDate != 0.0 && _lastUserInteraction) {
2802 NSTimeInterval timeSinceInteraction =
2803 requestCreationDate - _lastUserInteraction->time;
2804 // The error is considered to be the result of a user interaction if any
2805 // interaction happened just before the request was made.
2806 // TODO(droger): If the user interacted with the page after the request was
2807 // made (i.e. creationTimeSinceLastInteraction < 0), then
2808 // |_lastUserInteraction| has been overridden. The current behavior is to
2809 // discard the interstitial in that case. A better decision could be made if
2810 // we had a history of all the user interactions instead of just the last
2813 timeSinceInteraction < kMaximumDelayForUserInteractionInSeconds &&
2814 _lastUserInteraction->time > _lastTransferTimeInSeconds &&
2815 timeSinceInteraction >= 0.0;
2817 // If the error does not have timing information, check if the user
2818 // interacted with the page recently.
2819 userInteracted = [self userIsInteracting];
2821 if (!inMainFrame && !userInteracted)
2824 NSURL* errorURL = [NSURL
2825 URLWithString:[userInfo objectForKey:NSURLErrorFailingURLStringErrorKey]];
2826 const GURL errorGURL = net::GURLWithNSURL(errorURL);
2828 // Handles Frame Load Interrupted errors from WebView.
2829 if ([error.domain isEqual:base::SysUTF8ToNSString(web::kWebKitErrorDomain)] &&
2830 error.code == web::kWebKitErrorFrameLoadInterruptedByPolicyChange) {
2831 // See if the delegate wants to handle this case.
2832 if (errorGURL.is_valid() &&
2834 respondsToSelector:@selector(
2835 controllerForUnhandledContentAtURL:)]) {
2836 id<CRWNativeContent> controller =
2837 [_delegate controllerForUnhandledContentAtURL:errorGURL];
2839 [self loadCompleteWithSuccess:NO];
2840 [self removeWebViewAllowingCachedReconstruction:NO];
2841 [self setNativeController:controller];
2842 [self loadNativeViewWithSuccess:YES];
2847 // Ignore errors that originate from URLs that are opened in external apps.
2848 if ([_openedApplicationURL containsObject:errorURL])
2850 // Certain frame errors don't have URL information for some reason; for
2851 // those cases (so far the only known case is plugin content loaded directly
2852 // in a frame) just ignore the error. See crbug.com/414295
2854 DCHECK(!inMainFrame);
2857 // The wrapper error uses the URL of the error and not the requested URL
2858 // (which can be different in case of a redirect) to match desktop Chrome
2860 NSError* wrapperError = [NSError
2861 errorWithDomain:[error domain]
2864 NSURLErrorFailingURLStringErrorKey : [errorURL absoluteString],
2865 NSUnderlyingErrorKey : error
2867 [self loadCompleteWithSuccess:NO];
2868 [self loadErrorInNativeView:wrapperError];
2872 if ([error code] == NSURLErrorCancelled) {
2873 [self handleCancelledError:error];
2874 // NSURLErrorCancelled errors that aren't handled by aborting the load will
2875 // automatically be retried by the web view, so early return in this case.
2879 [self loadCompleteWithSuccess:NO];
2880 [self loadErrorInNativeView:error];
2883 - (void)handleCancelledError:(NSError*)cancelledError {
2884 // Subclasses must implement this method.
2891 - (void)createWebUIForURL:(const GURL&)URL {
2892 _webStateImpl->CreateWebUI(URL);
2895 - (void)clearWebUI {
2896 _webStateImpl->ClearWebUI();
2900 #pragma mark UIGestureRecognizerDelegate
2902 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2903 shouldRecognizeSimultaneouslyWithGestureRecognizer:
2904 (UIGestureRecognizer*)otherGestureRecognizer {
2905 // Allows the custom UILongPressGestureRecognizer to fire simultaneously with
2906 // other recognizers.
2910 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2911 shouldReceiveTouch:(UITouch*)touch {
2912 // Expect only _contextMenuRecognizer.
2913 DCHECK([gestureRecognizer isEqual:_contextMenuRecognizer]);
2914 if (![self supportsCustomContextMenu]) {
2915 // Fetching context menu info is not a free operation, early return if a
2916 // context menu should not be shown.
2920 // This is custom long press gesture recognizer. By the time the gesture is
2921 // recognized the web controller needs to know if there is a link under the
2922 // touch. If there a link, the web controller will reject system's context
2923 // menu and show another one. If for some reason context menu info is not
2924 // fetched - system context menu will be shown.
2925 [self setDOMElementForLastTouch:nullptr];
2926 base::WeakNSObject<CRWWebController> weakSelf(self);
2927 [self fetchDOMElementAtPoint:[touch locationInView:self.webView]
2928 completionHandler:^(scoped_ptr<base::DictionaryValue> element) {
2929 [weakSelf setDOMElementForLastTouch:element.Pass()];
2934 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer {
2935 // Expect only _contextMenuRecognizer.
2936 DCHECK([gestureRecognizer isEqual:_contextMenuRecognizer]);
2937 if (!self.webView || ![self supportsCustomContextMenu]) {
2938 // Show the context menu iff currently displaying a web view.
2939 // Do nothing for native views.
2943 UMA_HISTOGRAM_BOOLEAN("WebController.FetchContextMenuInfoAsyncSucceeded",
2944 _DOMElementForLastTouch);
2946 return _DOMElementForLastTouch && !_DOMElementForLastTouch->empty();
2950 #pragma mark CRWRequestTrackerDelegate
2952 - (BOOL)isForStaticFileRequests {
2956 - (void)updatedSSLStatus:(const web::SSLStatus&)sslStatus
2957 forPageUrl:(const GURL&)url
2958 userInfo:(id)userInfo {
2959 // |userInfo| is a CRWSessionEntry.
2960 web::NavigationItem* item =
2961 [static_cast<CRWSessionEntry*>(userInfo) navigationItem];
2963 return; // This is a request update for an entry that no longer exists.
2965 // This condition happens infrequently when a page load is misinterpreted as
2966 // a resource load from a previous page. This can happen when moving quickly
2967 // back and forth through history, the notifications from the web view on the
2968 // UI thread and the one from the requests at the net layer may get out of
2969 // sync. This catches this case and prevent updating an entry with the wrong
2971 if (item->GetURL().GetOrigin() != url.GetOrigin())
2974 if (item->GetSSL().Equals(sslStatus))
2975 return; // No need to update with the same data.
2977 item->GetSSL() = sslStatus;
2979 // Notify the UI it needs to refresh if the updated entry is the current
2981 if (userInfo == self.currentSessionEntry) {
2982 [self didUpdateSSLStatusForCurrentNavigationItem];
2986 - (void)handleResponseHeaders:(net::HttpResponseHeaders*)headers
2987 requestUrl:(const GURL&)requestUrl {
2988 _webStateImpl->OnHttpResponseHeadersReceived(headers, requestUrl);
2991 - (void)presentSSLError:(const net::SSLInfo&)info
2992 forSSLStatus:(const web::SSLStatus&)status
2993 onUrl:(const GURL&)url
2994 recoverable:(BOOL)recoverable
2995 callback:(SSLErrorCallback)shouldContinue {
2997 DCHECK_EQ(url, [self currentNavigationURL]);
2998 [_delegate presentSSLError:info
3000 recoverable:recoverable
3001 callback:^(BOOL proceed) {
3003 // The interstitial will be removed during reload.
3004 [self loadCurrentURL];
3006 if (shouldContinue) {
3007 shouldContinue(proceed);
3010 DCHECK([self currentNavItem]);
3011 [self currentNavItem]->SetUnsafe(true);
3014 - (void)updatedProgress:(float)progress {
3016 respondsToSelector:@selector(webController:didUpdateProgress:)]) {
3017 [_delegate webController:self didUpdateProgress:progress];
3021 - (void)certificateUsed:(net::X509Certificate*)certificate
3022 forHost:(const std::string&)host
3023 status:(net::CertStatus)status {
3024 [[[self sessionController] sessionCertificatePolicyManager]
3025 registerAllowedCertificate:certificate
3030 - (void)clearCertificates {
3031 [[[self sessionController] sessionCertificatePolicyManager]
3036 #pragma mark Popup handling
3038 - (void)openPopupWithInfo:(const web::NewWindowInfo&)windowInfo {
3039 const GURL url(windowInfo.url);
3040 const GURL currentURL([self currentNavigationURL]);
3041 NSString* windowName = windowInfo.window_name.get();
3042 web::Referrer referrer(currentURL, windowInfo.referrer_policy);
3043 base::WeakNSObject<CRWWebController> weakSelf(self);
3044 void (^showPopupHandler)() = ^{
3045 CRWWebController* child = [[weakSelf delegate] webPageOrderedOpen:url
3047 windowName:windowName
3049 DCHECK(!child || child.sessionController.openedByDOM);
3052 BOOL showPopup = windowInfo.user_is_interacting ||
3053 (![self shouldBlockPopupWithURL:url sourceURL:currentURL]);
3056 } else if ([_delegate
3057 respondsToSelector:@selector(webController:didBlockPopup:)]) {
3058 web::BlockedPopupInfo blockedPopupInfo(url, referrer, windowName,
3060 [_delegate webController:self didBlockPopup:blockedPopupInfo];
3065 #pragma mark TouchTracking
3067 - (void)touched:(BOOL)touched {
3068 _clickInProgress = touched;
3070 _userInteractionRegistered = YES;
3071 _userInteractedWithWebController = YES;
3072 if (_isBeingDestroyed)
3074 const web::NavigationManagerImpl& navigationManager =
3075 self.webStateImpl->GetNavigationManagerImpl();
3076 GURL mainDocumentURL =
3077 navigationManager.GetEntryCount()
3078 ? navigationManager.GetLastCommittedItem()->GetURL()
3079 : [self currentURL];
3080 _lastUserInteraction.reset(new web::UserInteractionEvent(mainDocumentURL));
3084 - (CRWTouchTrackingRecognizer*)touchTrackingRecognizer {
3085 if (!_touchTrackingRecognizer) {
3086 _touchTrackingRecognizer.reset(
3087 [[CRWTouchTrackingRecognizer alloc] initWithDelegate:self]);
3089 return _touchTrackingRecognizer.get();
3092 - (BOOL)userIsInteracting {
3093 // If page transfer started after last click, user is deemed to be no longer
3095 if (!_lastUserInteraction ||
3096 _lastTransferTimeInSeconds > _lastUserInteraction->time) {
3099 return [self userClickedRecently];
3102 - (BOOL)userClickedRecently {
3103 if (!_lastUserInteraction)
3105 return _clickInProgress ||
3106 ((CFAbsoluteTimeGetCurrent() - _lastUserInteraction->time) <
3107 kMaximumDelayForUserInteractionInSeconds);
3110 #pragma mark Placeholder Overlay Methods
3112 - (void)addPlaceholderOverlay {
3113 if (!_overlayPreviewMode) {
3114 // Create |kSnapshotOverlayDelay| second timer to remove image with
3116 [self performSelector:@selector(removePlaceholderOverlay)
3118 afterDelay:kSnapshotOverlayDelay];
3121 // Add overlay image.
3122 _placeholderOverlayView.reset([[UIImageView alloc] init]);
3123 CGRect frame = [self visibleFrame];
3124 [_placeholderOverlayView setFrame:frame];
3125 [_placeholderOverlayView
3126 setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
3127 UIViewAutoresizingFlexibleHeight];
3128 [_placeholderOverlayView setContentMode:UIViewContentModeScaleAspectFill];
3129 [self.containerView addSubview:_placeholderOverlayView];
3131 id callback = ^(UIImage* image) {
3132 [_placeholderOverlayView setImage:image];
3134 [_delegate webController:self retrievePlaceholderOverlayImage:callback];
3136 if (!_placeholderOverlayView.get().image) {
3137 // TODO(justincohen): This is just a blank white image. Consider fading in
3138 // the snapshot when it comes in instead.
3139 // TODO(shreyasv): This is just a blank white image. Consider adding an API
3140 // so that the delegate can return something immediately for the default
3142 _placeholderOverlayView.get().image = [[self class] defaultSnapshotImage];
3146 - (void)removePlaceholderOverlay {
3147 if (!_placeholderOverlayView || _overlayPreviewMode)
3150 [NSObject cancelPreviousPerformRequestsWithTarget:self
3151 selector:@selector(removeOverlay)
3153 // Remove overlay with transition.
3154 [UIView animateWithDuration:kSnapshotOverlayTransition
3156 [_placeholderOverlayView setAlpha:0.0f];
3158 completion:^(BOOL finished) {
3159 [_placeholderOverlayView removeFromSuperview];
3160 _placeholderOverlayView.reset();
3164 - (void)setOverlayPreviewMode:(BOOL)overlayPreviewMode {
3165 _overlayPreviewMode = overlayPreviewMode;
3167 // If we were showing the preview, remove it.
3168 if (!_overlayPreviewMode && _placeholderOverlayView) {
3169 [self resetContainerView];
3170 // Reset |_placeholderOverlayView| directly instead of calling
3171 // -removePlaceholderOverlay, which removes |_placeholderOverlayView| in an
3173 [_placeholderOverlayView removeFromSuperview];
3174 _placeholderOverlayView.reset();
3175 // There are cases when resetting the contentView, above, may happen after
3176 // the web view has been created. Re-add it here, rather than
3177 // relying on a subsequent call to loadCurrentURLInWebView.
3179 [[self view] addSubview:self.webView];
3184 - (void)internalSuppressDialogs:(BOOL)suppressFlag notify:(BOOL)notifyFlag {
3185 NSString* const kSetSuppressDialogs =
3186 [NSString stringWithFormat:@"__gCrWeb.setSuppressDialogs(%d, %d);",
3187 suppressFlag, notifyFlag];
3188 [self setSuppressDialogsWithHelperScript:kSetSuppressDialogs];
3191 - (void)setPageDialogOpenPolicy:(web::PageDialogOpenPolicy)policy {
3193 case web::DIALOG_POLICY_ALLOW:
3194 [self setSuppressDialogs:NO notify:NO];
3196 case web::DIALOG_POLICY_NOTIFY_FIRST:
3197 [self setSuppressDialogs:NO notify:YES];
3199 case web::DIALOG_POLICY_SUPPRESS:
3200 [self setSuppressDialogs:YES notify:YES];
3206 - (void)setSuppressDialogs:(BOOL)suppressFlag notify:(BOOL)notifyFlag {
3207 if (self.webView && [_earlyScriptManager hasBeenInjected]) {
3208 [self internalSuppressDialogs:suppressFlag notify:notifyFlag];
3210 _setSuppressDialogsLater = suppressFlag;
3211 _setNotifyAboutDialogsLater = notifyFlag;
3216 #pragma mark Session Information
3218 - (CRWSessionController*)sessionController {
3219 return _webStateImpl
3220 ? _webStateImpl->GetNavigationManagerImpl().GetSessionController()
3224 - (CRWSessionEntry*)currentSessionEntry {
3225 return [[self sessionController] currentEntry];
3228 - (web::NavigationItem*)currentNavItem {
3229 // This goes through the legacy Session* interface rather than Navigation*
3230 // because it is itself a legacy method that should not exist, and this
3231 // avoids needing to add a GetActiveItem to NavigationManager. If/when this
3232 // method chain becomes a blocker to eliminating SessionController, the logic
3233 // can be moved here, using public NavigationManager getters. That's not
3234 // done now in order to avoid code duplication.
3235 return [[self currentSessionEntry] navigationItem];
3238 - (const GURL&)currentNavigationURL {
3239 // TODO(stuartmorgan): Fix the fact that this method doesn't have clear usage
3240 // delination that would allow changing to one of the non-deprecated URL
3242 web::NavigationItem* item = [self currentNavItem];
3243 return item ? item->GetVirtualURL() : GURL::EmptyGURL();
3246 - (ui::PageTransition)currentTransition {
3247 if ([self currentNavItem])
3248 return [self currentNavItem]->GetTransitionType();
3250 return ui::PageTransitionFromInt(0);
3253 - (web::Referrer)currentSessionEntryReferrer {
3254 web::NavigationItem* currentItem = [self currentNavItem];
3255 return currentItem ? currentItem->GetReferrer() : web::Referrer();
3258 - (NSDictionary*)currentHTTPHeaders {
3259 DCHECK([self currentSessionEntry]);
3260 return [self currentSessionEntry].navigationItem->GetHttpRequestHeaders();
3264 #pragma mark CRWWebViewScrollViewProxyObserver
3266 - (void)webViewScrollViewDidZoom:
3267 (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
3268 _pageHasZoomed = YES;
3272 #pragma mark Page State
3274 - (void)recordStateInHistory {
3275 // Only record the state if:
3276 // - the current NavigationItem's URL matches the current URL, and
3277 // - the user has interacted with the page.
3278 CRWSessionEntry* current = [self currentSessionEntry];
3279 if (current && [current navigationItem]->GetURL() == [self currentURL] &&
3280 _userInteractionRegistered) {
3281 [current navigationItem]->SetPageDisplayState(self.pageDisplayState);
3285 - (void)restoreStateFromHistory {
3286 CRWSessionEntry* current = [self currentSessionEntry];
3287 if ([current navigationItem])
3288 self.pageDisplayState = [current navigationItem]->GetPageDisplayState();
3291 - (web::PageDisplayState)pageDisplayState {
3292 web::PageDisplayState displayState;
3294 CGPoint scrollOffset = [self scrollPosition];
3295 displayState.scroll_state().set_offset_x(std::floor(scrollOffset.x));
3296 displayState.scroll_state().set_offset_y(std::floor(scrollOffset.y));
3297 UIScrollView* scrollView = self.webScrollView;
3298 displayState.zoom_state().set_minimum_zoom_scale(
3299 scrollView.minimumZoomScale);
3300 displayState.zoom_state().set_maximum_zoom_scale(
3301 scrollView.maximumZoomScale);
3302 displayState.zoom_state().set_zoom_scale(scrollView.zoomScale);
3304 // TODO(kkhorimoto): Handle native views.
3306 return displayState;
3309 - (void)setPageDisplayState:(web::PageDisplayState)displayState {
3310 if (!displayState.IsValid())
3313 // Page state is restored after a page load completes. If the user has
3314 // scrolled or changed the zoom scale while the page is still loading, don't
3315 // restore any state since it will confuse the user.
3316 web::PageDisplayState currentPageDisplayState = self.pageDisplayState;
3317 if (currentPageDisplayState.scroll_state().offset_x() ==
3318 _displayStateOnStartLoading.scroll_state().offset_x() &&
3319 currentPageDisplayState.scroll_state().offset_y() ==
3320 _displayStateOnStartLoading.scroll_state().offset_y() &&
3322 [self applyPageDisplayState:displayState];
3327 - (void)orientationDidChange {
3328 // When rotating, the available zoom scale range may change, zoomScale's
3329 // percentage into this range should remain constant. However, there are
3330 // two known bugs with respect to adjusting the zoomScale on rotation:
3331 // - WKWebView sometimes erroneously resets the scroll view's zoom scale to
3332 // an incorrect value ( rdar://20100815 ).
3333 // - After zooming occurs in a UIWebView that's displaying a page with a hard-
3334 // coded viewport width, the zoom will not be updated upon rotation
3335 // ( crbug.com/485055 ).
3338 web::NavigationItem* currentItem = self.currentNavItem;
3341 web::PageDisplayState displayState = currentItem->GetPageDisplayState();
3342 if (!displayState.IsValid())
3344 CGFloat zoomPercentage = (displayState.zoom_state().zoom_scale() -
3345 displayState.zoom_state().minimum_zoom_scale()) /
3346 displayState.zoom_state().GetMinMaxZoomDifference();
3347 displayState.zoom_state().set_minimum_zoom_scale(
3348 self.webScrollView.minimumZoomScale);
3349 displayState.zoom_state().set_maximum_zoom_scale(
3350 self.webScrollView.maximumZoomScale);
3351 displayState.zoom_state().set_zoom_scale(
3352 displayState.zoom_state().minimum_zoom_scale() +
3353 zoomPercentage * displayState.zoom_state().GetMinMaxZoomDifference());
3354 currentItem->SetPageDisplayState(displayState);
3355 [self applyPageDisplayState:currentItem->GetPageDisplayState()];
3358 - (void)applyPageDisplayState:(const web::PageDisplayState&)displayState {
3359 if (!displayState.IsValid())
3361 base::WeakNSObject<CRWWebController> weakSelf(self);
3362 web::PageDisplayState displayStateCopy = displayState;
3363 [self queryUserScalableProperty:^(BOOL isUserScalable) {
3364 base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
3365 [strongSelf applyPageDisplayState:displayStateCopy
3366 userScalable:isUserScalable];
3370 - (void)applyPageDisplayState:(const web::PageDisplayState&)displayState
3371 userScalable:(BOOL)isUserScalable {
3372 // Early return if |scrollState| doesn't match the current NavigationItem.
3373 // This can sometimes occur in tests, as navigation occurs programmatically
3374 // and |-applyPageScrollState:| is asynchronous.
3375 web::NavigationItem* currentItem = [self currentSessionEntry].navigationItem;
3376 if (currentItem && currentItem->GetPageDisplayState() != displayState)
3378 DCHECK(displayState.IsValid());
3379 if (isUserScalable) {
3380 [self prepareToApplyWebViewScrollZoomScale];
3381 [self applyWebViewScrollZoomScaleFromZoomState:displayState.zoom_state()];
3382 [self finishApplyingWebViewScrollZoomScale];
3384 [self applyWebViewScrollOffsetFromScrollState:displayState.scroll_state()];
3387 - (void)prepareToApplyWebViewScrollZoomScale {
3388 id webView = self.webView;
3389 if (![webView respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
3393 UIView* contentView = [webView viewForZoomingInScrollView:self.webScrollView];
3396 respondsToSelector:@selector(scrollViewWillBeginZooming:withView:)]) {
3397 [webView scrollViewWillBeginZooming:self.webScrollView
3398 withView:contentView];
3402 - (void)finishApplyingWebViewScrollZoomScale {
3403 id webView = self.webView;
3404 if ([webView respondsToSelector:@selector(scrollViewDidEndZooming:
3407 [webView respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
3408 // This correctly sets the content's frame in the scroll view to
3409 // fit the web page and upscales the content so that it isn't
3411 UIView* contentView =
3412 [webView viewForZoomingInScrollView:self.webScrollView];
3413 [webView scrollViewDidEndZooming:self.webScrollView
3414 withView:contentView
3415 atScale:self.webScrollView.zoomScale];
3419 - (void)applyWebViewScrollZoomScaleFromZoomState:
3420 (const web::PageZoomState&)zoomState {
3421 // Subclasses must implement this method.
3425 - (void)applyWebViewScrollOffsetFromScrollState:
3426 (const web::PageScrollState&)scrollState {
3427 DCHECK(scrollState.IsValid());
3428 CGPoint scrollOffset =
3429 CGPointMake(scrollState.offset_x(), scrollState.offset_y());
3430 if (_loadPhase == web::PAGE_LOADED) {
3431 // If the page is loaded, update the scroll immediately.
3432 [self.webScrollView setContentOffset:scrollOffset];
3434 // If the page isn't loaded, store the action to update the scroll
3435 // when the page finishes loading.
3436 base::WeakNSObject<UIScrollView> weakScrollView(self.webScrollView);
3437 base::scoped_nsprotocol<ProceduralBlock> action([^{
3438 [weakScrollView setContentOffset:scrollOffset];
3440 [_pendingLoadCompleteActions addObject:action];
3445 #pragma mark Web Page Features
3447 // TODO(eugenebut): move JS parsing code to a separate file.
3448 - (void)queryUserScalableProperty:(void (^)(BOOL))responseHandler {
3449 NSString* const kViewPortContentQuery =
3450 @"var viewport = document.querySelector('meta[name=\"viewport\"]');"
3451 "viewport ? viewport.content : '';";
3452 [self evaluateJavaScript:kViewPortContentQuery
3453 stringResultHandler:^(NSString* viewPortContent, NSError* error) {
3455 GetUserScalablePropertyFromViewPortContent(viewPortContent));
3459 - (void)fetchWebPageWidthWithCompletionHandler:(void (^)(CGFloat))handler {
3460 if (!self.webView) {
3465 [self evaluateJavaScript:@"__gCrWeb.getPageWidth();"
3466 stringResultHandler:^(NSString* pageWidthAsString, NSError*) {
3467 handler([pageWidthAsString floatValue]);
3471 - (void)fetchDOMElementAtPoint:(CGPoint)point
3473 (void (^)(scoped_ptr<base::DictionaryValue>))handler {
3475 // Convert point into web page's coordinate system (which may be scaled and/or
3477 CGPoint scrollOffset = self.scrollPosition;
3478 CGFloat webViewContentWidth = self.webScrollView.contentSize.width;
3479 base::WeakNSObject<CRWWebController> weakSelf(self);
3480 [self fetchWebPageWidthWithCompletionHandler:^(CGFloat pageWidth) {
3481 CGFloat scale = pageWidth / webViewContentWidth;
3482 CGPoint localPoint = CGPointMake((point.x + scrollOffset.x) * scale,
3483 (point.y + scrollOffset.y) * scale);
3484 NSString* const kGetElementScript =
3485 [NSString stringWithFormat:@"__gCrWeb.getElementFromPoint(%g, %g);",
3486 localPoint.x, localPoint.y];
3487 [weakSelf evaluateJavaScript:kGetElementScript
3488 JSONResultHandler:^(scoped_ptr<base::Value> element, NSError*) {
3489 // Release raw element and call handler with DictionaryValue.
3490 scoped_ptr<base::DictionaryValue> elementAsDict;
3492 base::DictionaryValue* elementAsDictPtr = nullptr;
3493 element.release()->GetAsDictionary(&elementAsDictPtr);
3494 // |rawElement| and |elementPtr| now point to the same
3495 // memory. |elementPtr| ownership will be transferred to
3496 // |element| scoped_ptr.
3497 elementAsDict.reset(elementAsDictPtr);
3499 handler(elementAsDict.Pass());
3504 - (NSDictionary*)contextMenuInfoForElement:(base::DictionaryValue*)element {
3506 NSMutableDictionary* mutableInfo = [NSMutableDictionary dictionary];
3507 NSString* title = nil;
3509 if (element->GetString("href", &href)) {
3510 mutableInfo[web::kContextLinkURLString] = base::SysUTF8ToNSString(href);
3512 if (linkURL.SchemeIs(url::kJavaScriptScheme)) {
3513 title = @"JavaScript";
3515 DCHECK(web::GetWebClient());
3516 const std::string& acceptLangs = web::GetWebClient()->GetAcceptLangs(
3517 self.webStateImpl->GetBrowserState());
3518 base::string16 urlText =
3519 url_formatter::FormatUrl(GURL(href), acceptLangs);
3520 title = base::SysUTF16ToNSString(urlText);
3524 if (element->GetString("src", &src)) {
3525 mutableInfo[web::kContextImageURLString] = base::SysUTF8ToNSString(src);
3527 title = base::SysUTF8ToNSString(src);
3528 if ([title hasPrefix:@"data:"])
3531 std::string titleAttribute;
3532 if (element->GetString("title", &titleAttribute))
3533 title = base::SysUTF8ToNSString(titleAttribute);
3534 std::string referrerPolicy;
3535 element->GetString("referrerPolicy", &referrerPolicy);
3536 mutableInfo[web::kContextLinkReferrerPolicy] =
3537 @([self referrerPolicyFromString:referrerPolicy]);
3539 mutableInfo[web::kContextTitle] = title;
3540 return [[mutableInfo copy] autorelease];
3544 #pragma mark Fullscreen
3546 - (CGRect)visibleFrame {
3547 CGRect frame = self.containerView.bounds;
3548 CGFloat headerHeight = [self headerHeight];
3549 frame.origin.y = headerHeight;
3550 frame.size.height -= headerHeight;
3554 - (void)optOutScrollsToTopForSubviews {
3555 NSMutableArray* stack =
3556 [NSMutableArray arrayWithArray:[self.webScrollView subviews]];
3557 while (stack.count) {
3558 UIView* current = [stack lastObject];
3559 [stack removeLastObject];
3560 [stack addObjectsFromArray:[current subviews]];
3561 if ([current isKindOfClass:[UIScrollView class]])
3562 static_cast<UIScrollView*>(current).scrollsToTop = NO;
3567 #pragma mark WebDelegate Calls
3569 - (BOOL)shouldOpenURL:(const GURL&)url
3570 mainDocumentURL:(const GURL&)mainDocumentURL
3571 linkClicked:(BOOL)linkClicked {
3572 if (![_delegate respondsToSelector:@selector(webController:
3578 return [_delegate webController:self
3580 mainDocumentURL:mainDocumentURL
3581 linkClicked:linkClicked];
3584 - (BOOL)isPutativeMainFrameRequest:(NSURLRequest*)request
3585 targetFrame:(const web::FrameInfo*)targetFrame {
3586 // Determine whether the request is for the main frame using frame info if
3587 // available. In the case of missing frame info, the request is considered to
3588 // have originated from the main frame if either of the following is true:
3589 // (a) The request's URL matches the request's main document URL
3590 // (b) The request's URL resourceSpecifier matches the request's
3591 // mainDocumentURL specifier, as is the case upon redirect from http
3592 // App Store links to a URL with itms-apps scheme. This appears to be is
3593 // App Store specific behavior, specially handled by web view.
3594 // Note: These heuristics are not guaranteed to be correct, and should not be
3595 // used for any decisions with security implications.
3597 ? targetFrame->is_main_frame
3598 : [request.URL isEqual:request.mainDocumentURL] ||
3599 [request.URL.resourceSpecifier
3600 isEqual:request.mainDocumentURL.resourceSpecifier];
3603 - (BOOL)shouldOpenExternalURLRequest:(NSURLRequest*)request
3604 targetFrame:(const web::FrameInfo*)targetFrame {
3605 ExternalURLRequestStatus requestStatus = NUM_EXTERNAL_URL_REQUEST_STATUS;
3606 if ([self isPutativeMainFrameRequest:request targetFrame:targetFrame]) {
3607 requestStatus = MAIN_FRAME_ALLOWED;
3609 // If the request's main document URL differs from that at the time of the
3610 // last user interaction, then the page has changed since the user last
3612 BOOL userInteractedWithRequestMainFrame =
3613 [self userClickedRecently] &&
3614 net::GURLWithNSURL(request.mainDocumentURL) ==
3615 _lastUserInteraction->main_document_url;
3616 // Prevent subframe requests from opening an external URL if the user has
3617 // not interacted with the request's main frame.
3618 requestStatus = userInteractedWithRequestMainFrame ? SUBFRAME_ALLOWED
3621 DCHECK_NE(requestStatus, NUM_EXTERNAL_URL_REQUEST_STATUS);
3622 UMA_HISTOGRAM_ENUMERATION("WebController.ExternalURLRequestBlocking",
3623 requestStatus, NUM_EXTERNAL_URL_REQUEST_STATUS);
3624 if (requestStatus == SUBFRAME_BLOCKED &&
3625 web::GetWebClient()->IsExternalURLBlockingEnabled()) {
3629 GURL requestURL = net::GURLWithNSURL(request.URL);
3630 return [_delegate respondsToSelector:@selector(webController:
3631 shouldOpenExternalURL:)] &&
3632 [_delegate webController:self shouldOpenExternalURL:requestURL];
3635 - (BOOL)urlTriggersNativeAppLaunch:(const GURL&)url
3636 sourceURL:(const GURL&)sourceURL {
3638 [_delegate respondsToSelector:@selector(urlTriggersNativeAppLaunch:
3640 [_delegate urlTriggersNativeAppLaunch:url sourceURL:sourceURL];
3643 - (CGFloat)headerHeight {
3644 if (![_delegate respondsToSelector:@selector(headerHeightForWebController:)])
3646 return [_delegate headerHeightForWebController:self];
3649 - (BOOL)shouldBlockPopupWithURL:(const GURL&)popupURL
3650 sourceURL:(const GURL&)sourceURL {
3651 if (![_delegate respondsToSelector:@selector(webController:
3652 shouldBlockPopupWithURL:
3656 return [_delegate webController:self
3657 shouldBlockPopupWithURL:popupURL
3658 sourceURL:sourceURL];
3661 - (void)didUpdateHistoryStateWithPageURL:(const GURL&)url {
3662 _webStateImpl->GetRequestTracker()->HistoryStateChange(url);
3663 [_delegate webDidUpdateHistoryStateWithPageURL:url];
3666 - (void)didUpdateSSLStatusForCurrentNavigationItem {
3667 if ([_delegate respondsToSelector:
3669 webControllerDidUpdateSSLStatusForCurrentNavigationItem:)]) {
3670 [_delegate webControllerDidUpdateSSLStatusForCurrentNavigationItem:self];
3674 #pragma mark CRWWebControllerScripting Methods
3676 - (void)loadHTML:(NSString*)html {
3677 [self loadHTML:html forURL:GURL(url::kAboutBlankURL)];
3680 - (void)loadHTMLForCurrentURL:(NSString*)html {
3681 [self loadHTML:html forURL:self.currentURL];
3684 - (void)loadHTML:(NSString*)html forURL:(const GURL&)url {
3685 // Remove the transient content view.
3686 [self clearTransientContentView];
3688 DLOG_IF(WARNING, !self.webView)
3689 << "self.webView null while trying to load HTML";
3690 _loadPhase = web::LOAD_REQUESTED;
3691 [self loadWebHTMLString:html forURL:url];
3694 - (void)loadHTML:(NSString*)HTML forAppSpecificURL:(const GURL&)URL {
3695 CHECK(web::GetWebClient()->IsAppSpecificURL(URL));
3696 [self loadHTML:HTML forURL:URL];
3699 - (void)stopLoading {
3700 web::RecordAction(UserMetricsAction("Stop"));
3701 // Discard the pending and transient entried before notifying the tab model
3702 // observers of the change via |-abortLoad|.
3703 [[self sessionController] discardNonCommittedEntries];
3705 // If discarding the non-committed entries results in an app-specific URL,
3706 // reload it in its native view.
3707 if (!self.nativeController &&
3708 [self shouldLoadURLInNativeView:[self currentNavigationURL]]) {
3709 [self loadCurrentURLInNativeView];
3713 - (void)orderClose {
3714 if (self.sessionController.openedByDOM) {
3715 [_delegate webPageOrderedClose];
3720 #pragma mark Testing-Only Methods
3722 - (void)injectWebViewContentView:(id)webViewContentView {
3723 [self removeWebViewAllowingCachedReconstruction:NO];
3725 _lastRegisteredRequestURL = _defaultURL;
3726 [self.containerView displayWebViewContentView:webViewContentView];
3729 - (void)resetInjectedWebViewContentView {
3730 [self resetWebView];
3731 [self resetContainerView];
3734 - (void)addObserver:(id<CRWWebControllerObserver>)observer {
3737 // We don't want our observer set to block dealloc on the observers. For the
3738 // observer container, make an object compatible with NSMutableSet that does
3739 // not perform retain or release on the contained objects (weak references).
3740 CFSetCallBacks callbacks =
3741 {0, NULL, NULL, CFCopyDescription, CFEqual, CFHash};
3742 _observers.reset(base::mac::CFToNSCast(
3743 CFSetCreateMutable(kCFAllocatorDefault, 1, &callbacks)));
3745 DCHECK(![_observers containsObject:observer]);
3746 [_observers addObject:observer];
3747 _observerBridges.push_back(
3748 new web::WebControllerObserverBridge(observer, self.webStateImpl, self));
3750 if ([observer respondsToSelector:@selector(setWebViewProxy:controller:)])
3751 [observer setWebViewProxy:_webViewProxy controller:self];
3754 - (void)removeObserver:(id<CRWWebControllerObserver>)observer {
3755 // TODO(jimblackler): make _observers use NSMapTable. crbug.com/367992
3756 DCHECK([_observers containsObject:observer]);
3757 [_observers removeObject:observer];
3758 // Remove the associated WebControllerObserverBridge.
3759 auto it = std::find_if(_observerBridges.begin(), _observerBridges.end(),
3760 [observer](web::WebControllerObserverBridge* bridge) {
3761 return bridge->web_controller_observer() == observer;
3763 DCHECK(it != _observerBridges.end());
3764 _observerBridges.erase(it);
3767 - (NSUInteger)observerCount {
3768 DCHECK_EQ(_observerBridges.size(), [_observers count]);
3769 return [_observers count];
3772 - (NSString*)windowId {
3773 return [_windowIDJSManager windowId];
3776 - (void)setWindowId:(NSString*)windowId {
3777 return [_windowIDJSManager setWindowId:windowId];
3780 - (NSString*)lastSeenWindowID {
3781 return _lastSeenWindowID;
3784 - (void)setURLOnStartLoading:(const GURL&)url {
3785 _URLOnStartLoading = url;
3788 - (const GURL&)defaultURL {
3792 - (GURL)URLOnStartLoading {
3793 return _URLOnStartLoading;
3796 - (GURL)lastRegisteredRequestURL {
3797 return _lastRegisteredRequestURL;
3800 - (void)simulateLoadRequestWithURL:(const GURL&)URL {
3801 _lastRegisteredRequestURL = URL;
3802 _loadPhase = web::LOAD_REQUESTED;
3805 - (NSString*)externalRequestWindowName {
3806 if (!_externalRequest || !_externalRequest->window_name)
3808 return _externalRequest->window_name;
3811 - (void)resetExternalRequest {
3812 _externalRequest.reset();