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 #import "ios/net/nsurlrequest_util.h"
32 #include "ios/public/provider/web/web_ui_ios.h"
33 #import "ios/web/history_state_util.h"
34 #include "ios/web/interstitials/web_interstitial_impl.h"
35 #import "ios/web/navigation/crw_session_certificate_policy_manager.h"
36 #import "ios/web/navigation/crw_session_controller.h"
37 #import "ios/web/navigation/crw_session_entry.h"
38 #import "ios/web/navigation/navigation_item_impl.h"
39 #import "ios/web/navigation/navigation_manager_impl.h"
40 #import "ios/web/navigation/web_load_params.h"
41 #include "ios/web/net/request_group_util.h"
42 #include "ios/web/public/browser_state.h"
43 #include "ios/web/public/favicon_url.h"
44 #include "ios/web/public/navigation_item.h"
45 #include "ios/web/public/referrer.h"
46 #include "ios/web/public/referrer_util.h"
47 #include "ios/web/public/ssl_status.h"
48 #include "ios/web/public/url_scheme_util.h"
49 #include "ios/web/public/url_util.h"
50 #include "ios/web/public/user_metrics.h"
51 #include "ios/web/public/web_client.h"
52 #include "ios/web/public/web_state/credential.h"
53 #import "ios/web/public/web_state/crw_web_controller_observer.h"
54 #import "ios/web/public/web_state/crw_web_view_scroll_view_proxy.h"
55 #import "ios/web/public/web_state/js/crw_js_injection_manager.h"
56 #import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
57 #import "ios/web/public/web_state/ui/crw_content_view.h"
58 #import "ios/web/public/web_state/ui/crw_native_content.h"
59 #import "ios/web/public/web_state/ui/crw_native_content_provider.h"
60 #import "ios/web/public/web_state/ui/crw_web_view_content_view.h"
61 #include "ios/web/public/web_state/url_verification_constants.h"
62 #include "ios/web/public/web_state/web_state.h"
63 #include "ios/web/web_state/blocked_popup_info.h"
64 #import "ios/web/web_state/crw_web_view_proxy_impl.h"
65 #import "ios/web/web_state/error_translation_util.h"
66 #include "ios/web/web_state/frame_info.h"
67 #import "ios/web/web_state/js/credential_util.h"
68 #import "ios/web/web_state/js/crw_js_early_script_manager.h"
69 #import "ios/web/web_state/js/crw_js_plugin_placeholder_manager.h"
70 #import "ios/web/web_state/js/crw_js_window_id_manager.h"
71 #import "ios/web/web_state/ui/crw_context_menu_provider.h"
72 #import "ios/web/web_state/ui/crw_swipe_recognizer_provider.h"
73 #import "ios/web/web_state/ui/crw_ui_web_view_web_controller.h"
74 #import "ios/web/web_state/ui/crw_web_controller+protected.h"
75 #import "ios/web/web_state/ui/crw_web_controller_container_view.h"
76 #import "ios/web/web_state/ui/crw_wk_web_view_web_controller.h"
77 #import "ios/web/web_state/web_controller_observer_bridge.h"
78 #include "ios/web/web_state/web_state_facade_delegate.h"
79 #import "ios/web/web_state/web_state_impl.h"
80 #import "net/base/mac/url_conversions.h"
81 #include "net/base/net_errors.h"
82 #include "net/base/net_util.h"
83 #import "ui/base/ios/cru_context_menu_holder.h"
84 #include "ui/base/page_transition_types.h"
86 #include "url/url_constants.h"
88 using base::UserMetricsAction;
89 using web::NavigationManagerImpl;
91 using web::WebStateImpl;
95 NSString* const kContainerViewID = @"Container View";
96 const char* kWindowNameSeparator = "#";
97 NSString* const kUserIsInteractingKey = @"userIsInteracting";
98 NSString* const kOriginURLKey = @"originURL";
99 NSString* const kLogJavaScript = @"LogJavascript";
101 NewWindowInfo::NewWindowInfo(GURL target_url,
102 NSString* target_window_name,
103 web::ReferrerPolicy target_referrer_policy,
104 bool target_user_is_interacting)
106 window_name([target_window_name copy]),
107 referrer_policy(target_referrer_policy),
108 user_is_interacting(target_user_is_interacting) {
111 NewWindowInfo::~NewWindowInfo() {
114 // Struct to capture data about a user interaction. Records the time of the
115 // interaction and the main document URL at that time.
116 struct UserInteractionEvent {
117 UserInteractionEvent(GURL url)
118 : main_document_url(url), time(CFAbsoluteTimeGetCurrent()) {}
119 // Main document URL at the time the interaction occurred.
120 GURL main_document_url;
121 // Time that the interaction occured, measured in seconds since Jan 1 2001.
129 // A tag for the web view, so that tests can identify it. This is used instead
130 // of exposing a getter (and deliberately not exposed in the header) to make it
131 // *very* clear that this is a hack which should only be used as a last resort.
132 const NSUInteger kWebViewTag = 0x3eb71e3;
134 // Cancels touch events for the given gesture recognizer.
135 void CancelTouches(UIGestureRecognizer* gesture_recognizer) {
136 if (gesture_recognizer.enabled) {
137 gesture_recognizer.enabled = NO;
138 gesture_recognizer.enabled = YES;
142 // Cancels all touch events for web view (long presses, tapping, scrolling).
143 void CancelAllTouches(UIScrollView* web_scroll_view) {
144 // Disable web view scrolling.
145 CancelTouches(web_scroll_view.panGestureRecognizer);
147 // All user gestures are handled by a subview of web view scroll view
148 // (UIWebBrowserView for UIWebView and WKContentView for WKWebView).
149 for (UIView* subview in web_scroll_view.subviews) {
150 for (UIGestureRecognizer* recognizer in subview.gestureRecognizers) {
151 CancelTouches(recognizer);
158 @interface CRWWebController () <CRWNativeContentDelegate,
159 CRWWebViewScrollViewProxyObserver> {
160 base::WeakNSProtocol<id<CRWWebDelegate>> _delegate;
161 base::WeakNSProtocol<id<CRWWebUserInterfaceDelegate>> _UIDelegate;
162 base::WeakNSProtocol<id<CRWNativeContentProvider>> _nativeProvider;
163 base::WeakNSProtocol<id<CRWSwipeRecognizerProvider>> _swipeRecognizerProvider;
164 base::scoped_nsobject<CRWWebControllerContainerView> _containerView;
165 // The CRWWebViewProxy is the wrapper to give components access to the
166 // web view in a controlled and limited way.
167 base::scoped_nsobject<CRWWebViewProxyImpl> _webViewProxy;
168 // If |_contentView| contains a native view rather than a web view, this
169 // is its controller. If it's a web view, this is nil.
170 base::scoped_nsprotocol<id<CRWNativeContent>> _nativeController;
171 BOOL _isHalted; // YES if halted. Halting happens prior to destruction.
172 BOOL _isBeingDestroyed; // YES if in the process of closing.
173 // All CRWWebControllerObservers attached to the CRWWebController. A
174 // specially-constructed set is used that does not retain its elements.
175 base::scoped_nsobject<NSMutableSet> _observers;
176 // Each observer in |_observers| is associated with a
177 // WebControllerObserverBridge in order to listen from WebState callbacks.
178 // TODO(droger): Remove |_observerBridges| when all CRWWebControllerObservers
179 // are converted to WebStateObservers.
180 ScopedVector<web::WebControllerObserverBridge> _observerBridges;
181 // |windowId| that is saved when a page changes. Used to detect refreshes.
182 base::scoped_nsobject<NSString> _lastSeenWindowID;
183 // YES if a user interaction has been registered at any time once the page has
185 BOOL _userInteractionRegistered;
186 // Last URL change reported to webWill/DidStartLoadingURL. Used to detect page
187 // location changes (client redirects) in practice.
188 GURL _lastRegisteredRequestURL;
189 // Last URL change reported to webDidStartLoadingURL. Used to detect page
190 // location changes in practice.
191 GURL _URLOnStartLoading;
192 // Page loading phase.
193 web::LoadPhase _loadPhase;
194 // The web::PageDisplayState recorded when the page starts loading.
195 web::PageDisplayState _displayStateOnStartLoading;
196 // Whether or not the page has zoomed since the current navigation has been
197 // committed, either by user interaction or via |-restoreStateFromHistory|.
199 // Actions to execute once the page load is complete.
200 base::scoped_nsobject<NSMutableArray> _pendingLoadCompleteActions;
201 // UIGestureRecognizers to add to the web view.
202 base::scoped_nsobject<NSMutableArray> _gestureRecognizers;
203 // Toolbars to add to the web view.
204 base::scoped_nsobject<NSMutableArray> _webViewToolbars;
205 // Flag to say if browsing is enabled.
206 BOOL _webUsageEnabled;
207 // Content view was reset due to low memory. Use the placeholder overlay on
209 BOOL _usePlaceholderOverlay;
210 // Overlay view used instead of webView.
211 base::scoped_nsobject<UIImageView> _placeholderOverlayView;
212 // The touch tracking recognizer allowing us to decide if a navigation is
213 // started by the user.
214 base::scoped_nsobject<CRWTouchTrackingRecognizer> _touchTrackingRecognizer;
215 // Long press recognizer that allows showing context menus.
216 base::scoped_nsobject<UILongPressGestureRecognizer> _contextMenuRecognizer;
217 // DOM element information for the point where the user made the last touch.
218 // Can be null if has not been calculated yet. Precalculation is necessary
219 // because retreiving DOM element relies on async API so element info can not
220 // be built on demand. May contain the following keys: "href", "src", "title",
221 // "referrerPolicy". All values are strings. Used for showing context menus.
222 scoped_ptr<base::DictionaryValue> _DOMElementForLastTouch;
223 // Whether a click is in progress.
224 BOOL _clickInProgress;
225 // Data on the recorded last user interaction.
226 scoped_ptr<web::UserInteractionEvent> _lastUserInteraction;
227 // The time of the last page transfer start, measured in seconds since Jan 1
229 CFAbsoluteTime _lastTransferTimeInSeconds;
230 // Default URL (about:blank).
232 // Show overlay view, don't reload web page.
233 BOOL _overlayPreviewMode;
234 // If |YES|, call setSuppressDialogs when core.js is injected into the web
236 BOOL _setSuppressDialogsLater;
237 // If |YES|, call setSuppressDialogs when core.js is injected into the web
239 BOOL _setNotifyAboutDialogsLater;
240 // The URL of an expected future recreation of the |webView|. Valid
241 // only if the web view was discarded for non-user-visible reasons, such that
242 // if the next load request is for that URL, it should be treated as a
243 // reconstruction that should use cache aggressively.
244 GURL _expectedReconstructionURL;
246 scoped_ptr<web::NewWindowInfo> _externalRequest;
248 // The WebStateImpl instance associated with this CRWWebController.
249 scoped_ptr<WebStateImpl> _webStateImpl;
251 // A set of URLs opened in external applications; stored so that errors
252 // from the web view can be identified as resulting from these events.
253 base::scoped_nsobject<NSMutableSet> _openedApplicationURL;
255 // Object that manages all early script injection into the web view.
256 base::scoped_nsobject<CRWJSEarlyScriptManager> _earlyScriptManager;
258 // Script manager for setting the windowID.
259 base::scoped_nsobject<CRWJSWindowIdManager> _windowIDJSManager;
261 // The receiver of JavaScripts.
262 base::scoped_nsobject<CRWJSInjectionReceiver> _jsInjectionReceiver;
265 // The container view. The container view should be accessed through this
266 // property rather than |self.view| from within this class, as |self.view|
267 // triggers creation while |self.containerView| will return nil if the view
268 // hasn't been instantiated.
269 @property(nonatomic, retain, readonly)
270 CRWWebControllerContainerView* containerView;
271 // The current page state of the web view. Writing to this property
272 // asynchronously applies the passed value to the current web view.
273 @property(nonatomic, readwrite) web::PageDisplayState pageDisplayState;
274 // The currently displayed native controller, if any.
275 @property(nonatomic, readwrite) id<CRWNativeContent> nativeController;
276 // Removes the container view from the hierarchy and resets the ivar.
277 - (void)resetContainerView;
278 // Resets any state that is associated with a specific document object (e.g.,
279 // page interaction tracking).
280 - (void)resetDocumentSpecificState;
281 // Returns YES if the URL looks like it is one CRWWebController can show.
282 + (BOOL)webControllerCanShow:(const GURL&)url;
283 // Clears the currently-displayed transient content view.
284 - (void)clearTransientContentView;
285 // Returns a lazily created CRWTouchTrackingRecognizer.
286 - (CRWTouchTrackingRecognizer*)touchTrackingRecognizer;
287 // Shows placeholder overlay.
288 - (void)addPlaceholderOverlay;
289 // Removes placeholder overlay.
290 - (void)removePlaceholderOverlay;
291 // Returns |YES| if |url| should be loaded in a native view.
292 - (BOOL)shouldLoadURLInNativeView:(const GURL&)url;
293 // Loads the HTML into the page at the given URL.
294 - (void)loadHTML:(NSString*)html forURL:(const GURL&)url;
295 // Loads the current nativeController in a native view. If a web view is
296 // present, removes it and swaps in the native view in its place.
297 - (void)loadNativeViewWithSuccess:(BOOL)loadSuccess;
298 // YES if the navigation to |url| should be treated as a reload.
299 - (BOOL)shouldReload:(const GURL&)destinationURL
300 transition:(ui::PageTransition)transition;
301 // Internal implementation of reload. Reloads without notifying the delegate.
302 // Most callers should use -reload instead.
303 - (void)reloadInternal;
304 // If YES, the page can be closed if the loading of the initial URL requires
305 // it (for example when an external URL is detected). After the initial URL is
306 // loaded, the page is not cancellable anymore.
308 // Called after URL is finished loading and _loadPhase is set to PAGE_LOADED.
309 - (void)didFinishWithURL:(const GURL&)currentURL loadSuccess:(BOOL)loadSuccess;
310 // Informs the native controller if web usage is allowed or not.
311 - (void)setNativeControllerWebUsageEnabled:(BOOL)webUsageEnabled;
312 // Compares the two URLs being navigated between during a history navigation to
313 // determine if a # needs to be appended to endURL to trigger a hashchange
314 // event. If so, also saves the new endURL in the current CRWSessionEntry.
315 - (GURL)updateURLForHistoryNavigationFromURL:(const GURL&)startURL
316 toURL:(const GURL&)endURL;
317 // Evaluates the supplied JavaScript in the web view. Calls |handler| with
318 // results of the evaluation (which may be nil if the implementing object has no
319 // way to run the evaluation or the evaluation returns a nil value) or an
320 // NSError if there is an error. The |handler| can be nil.
321 - (void)evaluateJavaScript:(NSString*)script
322 JSONResultHandler:(void (^)(scoped_ptr<base::Value>, NSError*))handler;
323 // Generates the JavaScript string used to update the UIWebView's URL so that it
324 // matches the URL displayed in the omnibox and sets window.history.state to
325 // stateObject. Needed for history.pushState() and history.replaceState().
326 - (NSString*)javascriptToReplaceWebViewURL:(const GURL&)url
327 stateObjectJSON:(NSString*)stateObject;
329 // Called by NSNotificationCenter upon orientation changes.
330 - (void)orientationDidChange;
331 // Queries the web view for the user-scalable meta tag and calls
332 // |-applyPageDisplayState:userScalable:| with the result.
333 - (void)applyPageDisplayState:(const web::PageDisplayState&)displayState;
334 // Restores state of the web view's scroll view from |scrollState|.
335 // |isUserScalable| represents the value of user-scalable meta tag.
336 - (void)applyPageDisplayState:(const web::PageDisplayState&)displayState
337 userScalable:(BOOL)isUserScalable;
338 // Calls the zoom-preparation UIScrollViewDelegate callbacks on the web view.
339 // This is called before |-applyWebViewScrollZoomScaleFromScrollState:|.
340 - (void)prepareToApplyWebViewScrollZoomScale;
341 // Calls the zoom-completion UIScrollViewDelegate callbacks on the web view.
342 // This is called after |-applyWebViewScrollZoomScaleFromScrollState:|.
343 - (void)finishApplyingWebViewScrollZoomScale;
344 // Sets scroll offset value for webview scroll view from |scrollState|.
345 - (void)applyWebViewScrollOffsetFromScrollState:
346 (const web::PageScrollState&)scrollState;
347 // Asynchronously determines whether option |user-scalable| is on in the
348 // viewport meta of the current web page.
349 - (void)queryUserScalableProperty:(void (^)(BOOL))responseHandler;
350 // Asynchronously fetches full width of the rendered web page.
351 - (void)fetchWebPageWidthWithCompletionHandler:(void (^)(CGFloat))handler;
352 // Asynchronously fetches information about DOM element for the given point (in
353 // UIView coordinates). |handler| can not be nil. See |_DOMElementForLastTouch|
354 // for element format description.
355 - (void)fetchDOMElementAtPoint:(CGPoint)point
357 (void (^)(scoped_ptr<base::DictionaryValue>))handler;
358 // Extracts context menu information from the given DOM element.
359 // result keys are defined in crw_context_menu_provider.h.
360 - (NSDictionary*)contextMenuInfoForElement:(base::DictionaryValue*)element;
361 // Sets the value of |_DOMElementForLastTouch|.
362 - (void)setDOMElementForLastTouch:(scoped_ptr<base::DictionaryValue>)element;
363 // Called when the window has determined there was a long-press and context menu
365 - (void)showContextMenu:(UIGestureRecognizer*)gestureRecognizer;
366 // YES if delegate supports showing context menu by responding to
367 // webController:runContextMenu:atPoint:inView: selector.
368 - (BOOL)supportsCustomContextMenu;
369 // Returns the referrer for the current page.
370 - (web::Referrer)currentReferrer;
371 // Presents an error to the user because the CRWWebController cannot verify the
372 // URL of the current page.
373 - (void)presentSpoofingError;
374 // Adds a new CRWSessionEntry with the given URL and state object to the history
375 // stack. A state object is a serialized generic JavaScript object that contains
376 // details of the UI's state for a given CRWSessionEntry/URL.
377 // TODO(stuartmorgan): Move the pushState/replaceState logic into
378 // NavigationManager.
379 - (void)pushStateWithPageURL:(const GURL&)pageURL
380 stateObject:(NSString*)stateObject
381 transition:(ui::PageTransition)transition;
382 // Assigns the given URL and state object to the current CRWSessionEntry.
383 - (void)replaceStateWithPageURL:(const GURL&)pageUrl
384 stateObject:(NSString*)stateObject;
386 // Returns the current entry from the underlying session controller.
387 // TODO(stuartmorgan): Audit all calls to these methods; these are just wrappers
388 // around the same logic as GetActiveEntry, so should probably not be used for
389 // the same reason that GetActiveEntry is deprecated. (E.g., page operations
390 // should generally be dealing with the last commited entry, not a pending
392 - (CRWSessionEntry*)currentSessionEntry;
393 - (web::NavigationItem*)currentNavItem;
394 // Returns the referrer for currentURL as a string. May return nil.
395 - (web::Referrer)currentSessionEntryReferrer;
396 // The data and HTTP headers associated to the current entry. These are nil
397 // unless the request was a POST.
398 - (NSData*)currentPOSTData;
399 - (NSDictionary*)currentHttpHeaders;
401 // Finds all the scrollviews in the view hierarchy and makes sure they do not
402 // interfere with scroll to top when tapping the statusbar.
403 - (void)optOutScrollsToTopForSubviews;
404 // Tears down the old native controller, and then replaces it with the new one.
405 - (void)setNativeController:(id<CRWNativeContent>)nativeController;
406 // Returns whether |url| should be opened.
407 - (BOOL)shouldOpenURL:(const GURL&)url
408 mainDocumentURL:(const GURL&)mainDocumentURL
409 linkClicked:(BOOL)linkClicked;
410 // Called when |url| needs to be opened in a matching native app.
411 // Returns YES if the url was succesfully opened in the native app.
412 - (BOOL)urlTriggersNativeAppLaunch:(const GURL&)url
413 sourceURL:(const GURL&)sourceURL;
414 // Returns whether external URL request should be opened.
415 - (BOOL)shouldOpenExternalURLRequest:(NSURLRequest*)request
416 targetFrame:(const web::FrameInfo*)targetFrame;
417 // Called when a page updates its history stack using pushState or replaceState.
418 - (void)didUpdateHistoryStateWithPageURL:(const GURL&)url;
420 // Handlers for JavaScript messages. |message| contains a JavaScript command and
421 // data relevant to the message, and |context| contains contextual information
422 // about web view state needed for some handlers.
424 // Handles 'addPluginPlaceholders' message.
425 - (BOOL)handleAddPluginPlaceholdersMessage:(base::DictionaryValue*)message
426 context:(NSDictionary*)context;
427 // Handles 'chrome.send' message.
428 - (BOOL)handleChromeSendMessage:(base::DictionaryValue*)message
429 context:(NSDictionary*)context;
430 // Handles 'console' message.
431 - (BOOL)handleConsoleMessage:(base::DictionaryValue*)message
432 context:(NSDictionary*)context;
433 // Handles 'dialog.suppressed' message.
434 - (BOOL)handleDialogSuppressedMessage:(base::DictionaryValue*)message
435 context:(NSDictionary*)context;
436 // Handles 'dialog.willShow' message.
437 - (BOOL)handleDialogWillShowMessage:(base::DictionaryValue*)message
438 context:(NSDictionary*)context;
439 // Handles 'document.favicons' message.
440 - (BOOL)handleDocumentFaviconsMessage:(base::DictionaryValue*)message
441 context:(NSDictionary*)context;
442 // Handles 'document.submit' message.
443 - (BOOL)handleDocumentSubmitMessage:(base::DictionaryValue*)message
444 context:(NSDictionary*)context;
445 // Handles 'externalRequest' message.
446 - (BOOL)handleExternalRequestMessage:(base::DictionaryValue*)message
447 context:(NSDictionary*)context;
448 // Handles 'form.activity' message.
449 - (BOOL)handleFormActivityMessage:(base::DictionaryValue*)message
450 context:(NSDictionary*)context;
451 // Handles 'form.requestAutocomplete' message.
452 - (BOOL)handleFormRequestAutocompleteMessage:(base::DictionaryValue*)message
453 context:(NSDictionary*)context;
454 // Handles 'navigator.credentials.request' message.
455 - (BOOL)handleCredentialsRequestedMessage:(base::DictionaryValue*)message
456 context:(NSDictionary*)context;
457 // Handles 'navigator.credentials.notifySignedIn' message.
458 - (BOOL)handleSignedInMessage:(base::DictionaryValue*)message
459 context:(NSDictionary*)context;
460 // Handles 'navigator.credentials.notifySignedOut' message.
461 - (BOOL)handleSignedOutMessage:(base::DictionaryValue*)message
462 context:(NSDictionary*)context;
463 // Handles 'navigator.credentials.notifyFailedSignIn' message.
464 - (BOOL)handleSignInFailedMessage:(base::DictionaryValue*)message
465 context:(NSDictionary*)context;
466 // Handles 'resetExternalRequest' message.
467 - (BOOL)handleResetExternalRequestMessage:(base::DictionaryValue*)message
468 context:(NSDictionary*)context;
469 // Handles 'window.close.self' message.
470 - (BOOL)handleWindowCloseSelfMessage:(base::DictionaryValue*)message
471 context:(NSDictionary*)context;
472 // Handles 'window.error' message.
473 - (BOOL)handleWindowErrorMessage:(base::DictionaryValue*)message
474 context:(NSDictionary*)context;
475 // Handles 'window.hashchange' message.
476 - (BOOL)handleWindowHashChangeMessage:(base::DictionaryValue*)message
477 context:(NSDictionary*)context;
478 // Handles 'window.history.back' message.
479 - (BOOL)handleWindowHistoryBackMessage:(base::DictionaryValue*)message
480 context:(NSDictionary*)context;
481 // Handles 'window.history.forward' message.
482 - (BOOL)handleWindowHistoryForwardMessage:(base::DictionaryValue*)message
483 context:(NSDictionary*)context;
484 // Handles 'window.history.go' message.
485 - (BOOL)handleWindowHistoryGoMessage:(base::DictionaryValue*)message
486 context:(NSDictionary*)context;
491 NSString* const kReferrerHeaderName = @"Referer"; // [sic]
493 // Full screen experimental setting.
495 // The long press detection duration must be shorter than the UIWebView's
496 // long click gesture recognizer's minimum duration. That is 0.55s.
497 // If our detection duration is shorter, our gesture recognizer will fire
498 // first, and if it fails the long click gesture (processed simultaneously)
499 // still is able to complete.
500 const NSTimeInterval kLongPressDurationSeconds = 0.55 - 0.1;
501 const CGFloat kLongPressMoveDeltaPixels = 10.0;
503 // The duration of the period following a screen touch during which the user is
504 // still considered to be interacting with the page.
505 const NSTimeInterval kMaximumDelayForUserInteractionInSeconds = 2;
507 // Define missing symbols from WebKit.
508 // See WebKitErrors.h on Mac SDK.
509 NSString* const WebKitErrorDomain = @"WebKitErrorDomain";
512 WebKitErrorCannotShowMIMEType = 100,
513 WebKitErrorCannotShowURL = 101,
514 WebKitErrorFrameLoadInterruptedByPolicyChange = 102,
515 // iOS-specific WebKit error that isn't documented but seen on 4.0
517 WebKitErrorPlugInLoadFailed = 204,
520 // URLs that are fed into UIWebView as history push/replace get escaped,
521 // potentially changing their format. Code that attempts to determine whether a
522 // URL hasn't changed can be confused by those differences though, so method
523 // will round-trip a URL through the escaping process so that it can be adjusted
524 // pre-storing, to allow later comparisons to work as expected.
525 GURL URLEscapedForHistory(const GURL& url) {
526 // TODO(stuartmorgan): This is a very large hammer; see if limited unicode
527 // escaping would be sufficient.
528 return net::GURLWithNSURL(net::NSURLWithGURL(url));
531 // Parses a viewport tag content and returns the value of an attribute with
532 // the given |name|, or nil if the attribute is not present in the tag.
533 NSString* GetAttributeValueFromViewPortContent(NSString* attributeName,
534 NSString* viewPortContent) {
535 NSArray* contentItems = [viewPortContent componentsSeparatedByString:@","];
536 for (NSString* item in contentItems) {
537 NSArray* components = [item componentsSeparatedByString:@"="];
538 if ([components count] == 2) {
539 NSCharacterSet* spaceAndNewline =
540 [NSCharacterSet whitespaceAndNewlineCharacterSet];
541 NSString* currentAttributeName =
542 [components[0] stringByTrimmingCharactersInSet:spaceAndNewline];
543 if ([currentAttributeName isEqualToString:attributeName]) {
544 return [components[1] stringByTrimmingCharactersInSet:spaceAndNewline];
551 // Parses a viewport tag content and returns the value of the user-scalable
553 BOOL GetUserScalablePropertyFromViewPortContent(NSString* viewPortContent) {
555 GetAttributeValueFromViewPortContent(@"user-scalable", viewPortContent);
559 return !([value isEqualToString:@"0"] ||
560 [value caseInsensitiveCompare:@"no"] == NSOrderedSame);
563 // Leave snapshot overlay up unless page loads.
564 const NSTimeInterval kSnapshotOverlayDelay = 1.5;
565 // Transition to fade snapshot overlay.
566 const NSTimeInterval kSnapshotOverlayTransition = 0.5;
570 @implementation CRWWebController
572 @synthesize webUsageEnabled = _webUsageEnabled;
573 @synthesize usePlaceholderOverlay = _usePlaceholderOverlay;
574 @synthesize loadPhase = _loadPhase;
576 // Implemented by subclasses.
577 @dynamic keyboardDisplayRequiresUserAction;
579 + (instancetype)allocWithZone:(struct _NSZone*)zone {
580 if (self == [CRWWebController class]) {
581 // This is an abstract class which should not be instantiated directly.
582 // Callers should create concrete subclasses instead.
586 return [super allocWithZone:zone];
589 - (instancetype)initWithWebState:(scoped_ptr<WebStateImpl>)webState {
592 _webStateImpl = webState.Pass();
593 DCHECK(_webStateImpl);
594 _webStateImpl->SetWebController(self);
595 _webStateImpl->InitializeRequestTracker(self);
596 // Load phase when no WebView present is 'loaded' because this represents
598 _loadPhase = web::PAGE_LOADED;
599 // Content area is lazily instantiated.
600 _defaultURL = GURL(url::kAboutBlankURL);
601 _jsInjectionReceiver.reset(
602 [[CRWJSInjectionReceiver alloc] initWithEvaluator:self]);
603 _earlyScriptManager.reset([(CRWJSEarlyScriptManager*)[_jsInjectionReceiver
604 instanceOfClass:[CRWJSEarlyScriptManager class]] retain]);
605 _windowIDJSManager.reset([(CRWJSWindowIdManager*)[_jsInjectionReceiver
606 instanceOfClass:[CRWJSWindowIdManager class]] retain]);
607 _lastSeenWindowID.reset();
609 [[CRWWebViewProxyImpl alloc] initWithWebController:self]);
610 [[_webViewProxy scrollViewProxy] addObserver:self];
611 _gestureRecognizers.reset([[NSMutableArray alloc] init]);
612 _webViewToolbars.reset([[NSMutableArray alloc] init]);
613 _pendingLoadCompleteActions.reset([[NSMutableArray alloc] init]);
614 [[NSNotificationCenter defaultCenter]
616 selector:@selector(orientationDidChange)
617 name:UIApplicationDidChangeStatusBarOrientationNotification
623 - (id<CRWNativeContentProvider>)nativeProvider {
624 return _nativeProvider.get();
627 - (void)setNativeProvider:(id<CRWNativeContentProvider>)nativeProvider {
628 _nativeProvider.reset(nativeProvider);
631 - (id<CRWSwipeRecognizerProvider>)swipeRecognizerProvider {
632 return _swipeRecognizerProvider.get();
635 - (void)setSwipeRecognizerProvider:
636 (id<CRWSwipeRecognizerProvider>)swipeRecognizerProvider {
637 _swipeRecognizerProvider.reset(swipeRecognizerProvider);
640 - (WebState*)webState {
641 return _webStateImpl.get();
644 - (WebStateImpl*)webStateImpl {
645 return _webStateImpl.get();
648 - (void)clearTransientContentView {
649 // Early return if there is no transient content view.
650 if (!self.containerView.transientContentView)
653 // Remove the transient content view from the hierarchy.
654 [self.containerView clearTransientContentView];
656 // Notify the WebState so it can perform any required state cleanup.
658 _webStateImpl->ClearTransientContentView();
661 - (void)showTransientContentView:(CRWContentView*)contentView {
663 DCHECK(contentView.scrollView);
664 DCHECK([contentView.scrollView isDescendantOfView:contentView]);
665 [self.containerView displayTransientContent:contentView];
668 - (id<CRWWebDelegate>)delegate {
669 return _delegate.get();
672 - (void)setDelegate:(id<CRWWebDelegate>)delegate {
673 _delegate.reset(delegate);
674 if ([self.nativeController respondsToSelector:@selector(setDelegate:)]) {
675 if ([_delegate respondsToSelector:@selector(webController:titleDidChange:)])
676 [self.nativeController setDelegate:self];
678 [self.nativeController setDelegate:nil];
682 - (id<CRWWebUserInterfaceDelegate>)UIDelegate {
683 return _UIDelegate.get();
686 - (void)setUIDelegate:(id<CRWWebUserInterfaceDelegate>)UIDelegate {
687 _UIDelegate.reset(UIDelegate);
690 - (NSString*)scriptByAddingWindowIDCheckForScript:(NSString*)script {
691 NSString* kTemplate = @"if (__gCrWeb['windowId'] === '%@') { %@; }";
692 return [NSString stringWithFormat:kTemplate, [self windowId], script];
695 - (void)removeWebViewAllowingCachedReconstruction:(BOOL)allowCache {
700 _expectedReconstructionURL = [self currentNavigationURL];
702 _expectedReconstructionURL = GURL();
705 [self.webView removeFromSuperview];
706 [self.containerView resetContent];
711 DCHECK([NSThread isMainThread]);
712 DCHECK(_isBeingDestroyed); // 'close' must have been called already.
713 DCHECK(!self.webView);
714 _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
715 [[_webViewProxy scrollViewProxy] removeObserver:self];
716 [[NSNotificationCenter defaultCenter] removeObserver:self];
720 - (BOOL)runUnloadListenerBeforeClosing {
721 // There's not much that can be done since there's limited access to WebKit.
722 // Always return that it's ok to close immediately.
726 - (void)dismissKeyboard {
727 [self.webView endEditing:YES];
728 if ([self.nativeController respondsToSelector:@selector(dismissKeyboard)])
729 [self.nativeController dismissKeyboard];
732 - (id<CRWNativeContent>)nativeController {
733 return self.containerView.nativeController;
736 - (void)setNativeController:(id<CRWNativeContent>)nativeController {
737 // Check for pointer equality.
738 if (self.nativeController == nativeController)
741 // Unset the delegate on the previous instance.
742 if ([self.nativeController respondsToSelector:@selector(setDelegate:)])
743 [self.nativeController setDelegate:nil];
745 [self.containerView displayNativeContent:nativeController];
746 [self setNativeControllerWebUsageEnabled:_webUsageEnabled];
749 // NativeControllerDelegate method, called to inform that title has changed.
750 - (void)nativeContent:(id)content titleDidChange:(NSString*)title {
751 // Responsiveness to delegate method was checked in setDelegate:.
752 [_delegate webController:self titleDidChange:title];
755 - (void)setNativeControllerWebUsageEnabled:(BOOL)webUsageEnabled {
756 if ([self.nativeController
757 respondsToSelector:@selector(setWebUsageEnabled:)]) {
758 [self.nativeController setWebUsageEnabled:webUsageEnabled];
762 - (void)setWebUsageEnabled:(BOOL)enabled {
763 if (_webUsageEnabled == enabled)
765 _webUsageEnabled = enabled;
767 // WKWebView autoreleases its WKProcessPool on removal from superview.
768 // Deferring WKProcessPool deallocation may lead to issues with cookie
769 // clearing and and Browsing Data Partitioning implementation.
771 [self setNativeControllerWebUsageEnabled:_webUsageEnabled];
773 // Don't create the web view; let it be lazy created as needed.
775 [self clearTransientContentView];
776 [self removeWebViewAllowingCachedReconstruction:YES];
777 _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
778 _touchTrackingRecognizer.reset();
779 [self resetContainerView];
784 - (void)requirePageReconstruction {
785 [self removeWebViewAllowingCachedReconstruction:NO];
788 - (void)resetContainerView {
789 [self.containerView removeFromSuperview];
790 _containerView.reset();
793 - (void)handleLowMemory {
794 [self removeWebViewAllowingCachedReconstruction:YES];
795 _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
796 _touchTrackingRecognizer.reset();
797 [self resetContainerView];
798 _usePlaceholderOverlay = YES;
801 - (void)reinitializeWebViewAndReload:(BOOL)reload {
803 [self removeWebViewAllowingCachedReconstruction:NO];
805 [self loadCurrentURLInWebView];
807 // Clear the space for the web view to lazy load when needed.
808 _usePlaceholderOverlay = YES;
809 _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
810 _touchTrackingRecognizer.reset();
811 [self resetContainerView];
816 - (void)childWindowClosed:(NSString*)windowName {
817 // Subclasses can override this method to be informed about a closed window.
820 - (BOOL)isViewAlive {
821 return [self.containerView isViewAlive];
824 - (BOOL)contentIsHTML {
825 return [self webViewDocumentType] == web::WEB_VIEW_DOCUMENT_TYPE_HTML;
828 // Stop doing stuff, especially network stuff. Close the request tracker.
829 - (void)terminateNetworkActivity {
833 // Cancel all outstanding perform requests, and clear anything already queued
834 // (since this may be called from within the handling loop) to prevent any
835 // asynchronous JavaScript invocation handling from continuing.
836 [NSObject cancelPreviousPerformRequestsWithTarget:self];
837 _webStateImpl->CloseRequestTracker();
840 - (void)dismissModals {
841 if ([self.nativeController respondsToSelector:@selector(dismissModals)])
842 [self.nativeController dismissModals];
845 // Caller must reset the delegate before calling.
847 self.nativeProvider = nil;
848 self.swipeRecognizerProvider = nil;
849 if ([self.nativeController respondsToSelector:@selector(close)])
850 [self.nativeController close];
852 base::scoped_nsobject<NSSet> observers([_observers copy]);
853 for (id it in observers.get()) {
854 if ([it respondsToSelector:@selector(webControllerWillClose:)])
855 [it webControllerWillClose:self];
859 [self terminateNetworkActivity];
862 DCHECK(!_isBeingDestroyed);
863 DCHECK(!_delegate); // Delegate should reset its association before closing.
864 // Mark the destruction sequence has started, in case someone else holds a
865 // strong reference and tries to continue using the tab.
866 _isBeingDestroyed = YES;
868 // Remove the web view now. Otherwise, delegate callbacks occur.
869 [self removeWebViewAllowingCachedReconstruction:NO];
871 // Tear down web ui (in case this is part of this tab) and web state now,
872 // since the timing of dealloc can't be guaranteed.
873 _webStateImpl.reset();
876 - (void)checkLinkPresenceUnderGesture:(UIGestureRecognizer*)gestureRecognizer
877 completionHandler:(void (^)(BOOL))completionHandler {
878 CGPoint webViewPoint = [gestureRecognizer locationInView:self.webView];
879 base::WeakNSObject<CRWWebController> weakSelf(self);
880 [self fetchDOMElementAtPoint:webViewPoint
881 completionHandler:^(scoped_ptr<base::DictionaryValue> element) {
884 element && element->GetString("href", &link) && link.size();
885 completionHandler(hasLink);
889 - (void)setDOMElementForLastTouch:(scoped_ptr<base::DictionaryValue>)element {
890 _DOMElementForLastTouch = element.Pass();
893 - (void)showContextMenu:(UIGestureRecognizer*)gestureRecognizer {
894 // Calling this method if [self supportsCustomContextMenu] returned NO
895 // is a programmer error.
896 DCHECK([self supportsCustomContextMenu]);
898 // We don't want ongoing notification that the long press is held.
899 if ([gestureRecognizer state] != UIGestureRecognizerStateBegan)
902 if (!_DOMElementForLastTouch || _DOMElementForLastTouch->empty())
906 [self contextMenuInfoForElement:_DOMElementForLastTouch.get()];
907 CGPoint point = [gestureRecognizer locationInView:self.webView];
909 // Cancel all touches on the web view when showing custom context menu. This
910 // will suppress the system context menu and prevent further user interactions
911 // with web view (like scrolling the content and following links). This
912 // approach is similar to UIWebView and WKWebView workflow as both call
913 // -[UIApplication _cancelAllTouches] to cancel all touch events, once the
914 // long press is detected.
915 CancelAllTouches(self.webScrollView);
916 [self.UIDelegate webController:self
919 inView:self.webView];
922 - (BOOL)supportsCustomContextMenu {
923 SEL runMenuSelector = @selector(webController:runContextMenu:atPoint:inView:);
924 return [self.UIDelegate respondsToSelector:runMenuSelector];
927 // TODO(shreyasv): This code is shared with SnapshotManager. Remove this and add
928 // it as part of WebDelegate delegate API such that a default image is returned
930 + (UIImage*)defaultSnapshotImage {
931 static UIImage* defaultImage = nil;
934 CGRect frame = CGRectMake(0, 0, 2, 2);
935 UIGraphicsBeginImageContext(frame.size);
936 [[UIColor whiteColor] setFill];
937 CGContextFillRect(UIGraphicsGetCurrentContext(), frame);
939 UIImage* result = UIGraphicsGetImageFromCurrentImageContext();
940 UIGraphicsEndImageContext();
943 [[result stretchableImageWithLeftCapWidth:1 topCapHeight:1] retain];
949 return _webStateImpl->GetNavigationManagerImpl().CanGoBack();
952 - (BOOL)canGoForward {
953 return _webStateImpl->GetNavigationManagerImpl().CanGoForward();
956 - (CGPoint)scrollPosition {
957 CGPoint position = CGPointMake(0.0, 0.0);
958 if (!self.webScrollView)
960 return self.webScrollView.contentOffset;
966 UIScrollView* scrollView = self.webScrollView;
967 return scrollView.contentOffset.y == -scrollView.contentInset.top;
970 - (void)presentSpoofingError {
971 UMA_HISTOGRAM_ENUMERATION("Web.URLVerificationFailure",
972 [self webViewDocumentType],
973 web::WEB_VIEW_DOCUMENT_TYPE_COUNT);
975 [self removeWebViewAllowingCachedReconstruction:NO];
976 [_delegate presentSpoofingError];
980 - (GURL)currentURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel {
981 DCHECK(trustLevel) << "Verification of the trustLevel state is mandatory";
983 GURL url([self webURLWithTrustLevel:trustLevel]);
984 // Web views treat all about: URLs as the same origin, which makes it
985 // possible for pages to document.write into about:<foo> pages, where <foo>
986 // can be something misleading. Report any about: URL as about:blank to
987 // prevent that. See crbug.com/326118
988 if (url.scheme() == url::kAboutScheme)
989 return GURL(url::kAboutBlankURL);
992 // Any non-web URL source is trusted.
993 *trustLevel = web::URLVerificationTrustLevel::kAbsolute;
994 if (self.nativeController)
995 return [self.nativeController url];
996 return [self currentNavigationURL];
1000 web::URLVerificationTrustLevel trustLevel =
1001 web::URLVerificationTrustLevel::kNone;
1002 const GURL url([self currentURLWithTrustLevel:&trustLevel]);
1004 // Check whether the spoofing warning needs to be displayed.
1005 if (trustLevel == web::URLVerificationTrustLevel::kNone &&
1006 ![self ignoreURLVerificationFailures]) {
1007 dispatch_async(dispatch_get_main_queue(), ^{
1009 DCHECK_EQ(url, [self currentNavigationURL]);
1010 [self presentSpoofingError];
1018 - (web::Referrer)currentReferrer {
1019 // Referrer string doesn't include the fragment, so in cases where the
1020 // previous URL is equal to the current referrer plus the fragment the
1021 // previous URL is returned as current referrer.
1022 NSString* referrerString = self.currentReferrerString;
1024 // In case of an error evaluating the JavaScript simply return empty string.
1025 if ([referrerString length] == 0)
1026 return web::Referrer();
1028 NSString* previousURLString =
1029 base::SysUTF8ToNSString([self currentNavigationURL].spec());
1030 // Check if the referrer is equal to the previous URL minus the hash symbol.
1031 // L'#' is used to convert the char '#' to a unichar.
1032 if ([previousURLString length] > [referrerString length] &&
1033 [previousURLString hasPrefix:referrerString] &&
1034 [previousURLString characterAtIndex:[referrerString length]] == L'#') {
1035 referrerString = previousURLString;
1037 // Since referrer is being extracted from the destination page, the correct
1038 // policy from the origin has *already* been applied. Since the extracted URL
1039 // is the post-policy value, and the source policy is no longer available,
1040 // the policy is set to Always so that whatever WebKit decided to send will be
1041 // re-sent when replaying the entry.
1042 // TODO(stuartmorgan): When possible, get the real referrer and policy in
1043 // advance and use that instead. https://crbug.com/227769.
1044 return web::Referrer(GURL(base::SysNSStringToUTF8(referrerString)),
1045 web::ReferrerPolicyAlways);
1048 - (void)pushStateWithPageURL:(const GURL&)pageURL
1049 stateObject:(NSString*)stateObject
1050 transition:(ui::PageTransition)transition {
1051 [[self sessionController] pushNewEntryWithURL:pageURL
1052 stateObject:stateObject
1053 transition:transition];
1054 [self didUpdateHistoryStateWithPageURL:pageURL];
1057 - (void)replaceStateWithPageURL:(const GURL&)pageUrl
1058 stateObject:(NSString*)stateObject {
1059 [[self sessionController] updateCurrentEntryWithURL:pageUrl
1060 stateObject:stateObject];
1061 [self didUpdateHistoryStateWithPageURL:pageUrl];
1064 - (void)injectEarlyInjectionScripts {
1065 DCHECK(self.webView);
1066 if (![_earlyScriptManager hasBeenInjected]) {
1067 [_earlyScriptManager inject];
1068 // If this assertion fires there has been an error parsing the core.js
1070 DCHECK([_earlyScriptManager hasBeenInjected]);
1072 [self injectWindowID];
1075 - (void)injectWindowID {
1076 if (![_windowIDJSManager hasBeenInjected]) {
1077 // If the window ID wasn't present, this is a new page.
1078 [self setPageChangeProbability:web::PAGE_CHANGE_PROBABILITY_LOW];
1079 // Default values for suppressDialogs and notifyAboutDialogs are NO,
1080 // so updating them only when necessary is a good optimization.
1081 if (_setSuppressDialogsLater || _setNotifyAboutDialogsLater) {
1082 [self setSuppressDialogs:_setSuppressDialogsLater
1083 notify:_setNotifyAboutDialogsLater];
1084 _setSuppressDialogsLater = NO;
1085 _setNotifyAboutDialogsLater = NO;
1088 [_windowIDJSManager inject];
1089 DCHECK([_windowIDJSManager hasBeenInjected]);
1093 // Set the specified recognizer to take priority over any recognizers in the
1094 // view that have a description containing the specified text fragment.
1095 + (void)requireGestureRecognizerToFail:(UIGestureRecognizer*)recognizer
1096 inView:(UIView*)view
1097 containingDescription:(NSString*)fragment {
1098 for (UIGestureRecognizer* iRecognizer in [view gestureRecognizers]) {
1099 if (iRecognizer != recognizer) {
1100 NSString* description = [iRecognizer description];
1101 if ([description rangeOfString:fragment].location != NSNotFound) {
1102 [iRecognizer requireGestureRecognizerToFail:recognizer];
1103 // requireGestureRecognizerToFail: doesn't retain the recognizer, so it
1104 // is possible for |iRecognizer| to outlive |recognizer| and end up with
1105 // a dangling pointer. Add a retaining associative reference to ensure
1106 // that the lifetimes work out.
1107 // Note that normally using the value as the key wouldn't make any
1108 // sense, but here it's fine since nothing needs to look up the value.
1109 objc_setAssociatedObject(view, recognizer, recognizer,
1110 OBJC_ASSOCIATION_RETAIN_NONATOMIC);
1116 - (void)webViewDidChange {
1117 CHECK(_webUsageEnabled) << "Tried to create a web view while suspended!";
1119 UIView* webView = self.webView;
1122 [webView setTag:kWebViewTag];
1123 [webView setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
1124 UIViewAutoresizingFlexibleHeight];
1125 [webView setBackgroundColor:[UIColor colorWithWhite:0.2 alpha:1.0]];
1127 // Create a dependency between the |webView| pan gesture and BVC side swipe
1128 // gestures. Note: This needs to be added before the longPress recognizers
1129 // below, or the longPress appears to deadlock the remaining recognizers,
1130 // thereby breaking scroll.
1131 NSSet* recognizers = [_swipeRecognizerProvider swipeRecognizers];
1132 for (UISwipeGestureRecognizer* swipeRecognizer in recognizers) {
1133 [self.webScrollView.panGestureRecognizer
1134 requireGestureRecognizerToFail:swipeRecognizer];
1137 // On iOS 4.x, there are two gesture recognizers on the UIWebView subclasses,
1138 // that have a minimum tap threshold of 0.12s and 0.75s.
1140 // My theory is that the shorter threshold recognizer performs the link
1141 // highlight (grey highlight around links when it is tapped and held) while
1142 // the longer threshold one pops up the context menu.
1144 // To override the context menu, this recognizer needs to react faster than
1145 // the 0.75s one. The below gesture recognizer is initialized with a
1146 // detection duration a little lower than that (see
1147 // kLongPressDurationSeconds). It also points the delegate to this class that
1148 // allows simultaneously operate along with the other recognizers.
1149 _contextMenuRecognizer.reset([[UILongPressGestureRecognizer alloc]
1151 action:@selector(showContextMenu:)]);
1152 [_contextMenuRecognizer setMinimumPressDuration:kLongPressDurationSeconds];
1153 [_contextMenuRecognizer setAllowableMovement:kLongPressMoveDeltaPixels];
1154 [_contextMenuRecognizer setDelegate:self];
1155 [webView addGestureRecognizer:_contextMenuRecognizer];
1156 // Certain system gesture handlers are known to conflict with our context
1157 // menu handler, causing extra events to fire when the context menu is active.
1159 // A number of solutions have been investigated. The lowest-risk solution
1160 // appears to be to recurse through the web controller's recognizers, looking
1161 // for fingerprints of the recognizers known to cause problems, which are then
1162 // de-prioritized (below our own long click handler).
1163 // Hunting for description fragments of system recognizers is undeniably
1164 // brittle for future versions of iOS. If it does break the context menu
1165 // events may leak (regressing b/5310177), but the app will otherwise work.
1167 requireGestureRecognizerToFail:_contextMenuRecognizer
1169 containingDescription:@"action=_highlightLongPressRecognized:"];
1171 // Add all additional gesture recognizers to the web view.
1172 for (UIGestureRecognizer* recognizer in _gestureRecognizers.get()) {
1173 [webView addGestureRecognizer:recognizer];
1176 _URLOnStartLoading = _defaultURL;
1178 // Add the web toolbars.
1179 [self.containerView addToolbars:_webViewToolbars];
1181 base::scoped_nsobject<CRWWebViewContentView> webViewContentView(
1182 [[CRWWebViewContentView alloc] initWithWebView:self.webView
1183 scrollView:self.webScrollView]);
1184 [self.containerView displayWebViewContentView:webViewContentView];
1187 - (CRWWebController*)createChildWebControllerWithReferrerURL:
1188 (const GURL&)referrerURL {
1189 web::Referrer referrer(referrerURL, web::ReferrerPolicyDefault);
1190 CRWWebController* result =
1191 [self.delegate webPageOrderedOpenBlankWithReferrer:referrer
1193 DCHECK(!result || result.sessionController.openedByDOM);
1197 - (BOOL)canUseViewForGeneratingOverlayPlaceholderView {
1198 return self.containerView != nil;
1202 // Kick off the process of lazily creating the view and starting the load if
1203 // necessary; this creates _containerView if it doesn't exist.
1204 [self triggerPendingLoad];
1205 DCHECK(self.containerView);
1206 return self.containerView;
1209 - (CRWWebControllerContainerView*)containerView {
1210 return _containerView.get();
1213 - (id<CRWWebViewProxy>)webViewProxy {
1214 return _webViewProxy.get();
1217 - (UIView*)viewForPrinting {
1218 // TODO(ios): crbug.com/227944. Printing is not supported for native
1220 return self.webView;
1223 - (void)loadRequest:(NSMutableURLRequest*)request {
1224 // Subclasses must implement this method.
1228 - (void)registerLoadRequest:(const GURL&)requestURL
1229 referrer:(const web::Referrer&)referrer
1230 transition:(ui::PageTransition)transition {
1231 // Transfer time is registered so that further transitions within the time
1232 // envelope are not also registered as links.
1233 _lastTransferTimeInSeconds = CFAbsoluteTimeGetCurrent();
1234 // Before changing phases, the delegate should be informed that any existing
1235 // request is being cancelled before completion.
1236 [self loadCancelled];
1237 DCHECK(_loadPhase == web::PAGE_LOADED);
1239 _loadPhase = web::LOAD_REQUESTED;
1240 [self resetLoadState];
1241 _lastRegisteredRequestURL = requestURL;
1243 if (!(transition & ui::PAGE_TRANSITION_IS_REDIRECT_MASK)) {
1244 // Record state of outgoing page.
1245 [self recordStateInHistory];
1248 // If the web view had been discarded, and this request is to load that
1249 // URL again, then it's a rebuild and should use the cache.
1250 BOOL preferCache = _expectedReconstructionURL.is_valid() &&
1251 _expectedReconstructionURL == requestURL;
1253 [_delegate webWillAddPendingURL:requestURL transition:transition];
1254 // Add or update pending url.
1255 if (_webStateImpl->GetNavigationManagerImpl().GetPendingItem()) {
1256 // Update the existing pending entry.
1257 // Typically on PAGE_TRANSITION_CLIENT_REDIRECT.
1258 [[self sessionController] updatePendingEntry:requestURL];
1260 // A new session history entry needs to be created.
1261 [[self sessionController] addPendingEntry:requestURL
1263 transition:transition
1264 rendererInitiated:YES];
1266 // Update the cache mode for all the network requests issued by this web view.
1267 // The mode is reset to CACHE_NORMAL after each page load.
1268 if (_webStateImpl->GetCacheMode() != net::RequestTracker::CACHE_NORMAL) {
1269 _webStateImpl->GetRequestTracker()->SetCacheModeFromUIThread(
1270 _webStateImpl->GetCacheMode());
1271 } else if (preferCache) {
1272 _webStateImpl->GetRequestTracker()->SetCacheModeFromUIThread(
1273 net::RequestTracker::CACHE_HISTORY);
1275 _webStateImpl->SetIsLoading(true);
1276 [_delegate webDidAddPendingURL];
1277 _webStateImpl->OnProvisionalNavigationStarted(requestURL);
1280 - (NSString*)javascriptToReplaceWebViewURL:(const GURL&)url
1281 stateObjectJSON:(NSString*)stateObject {
1283 base::EscapeJSONString(url.spec(), true, &outURL);
1285 [NSString stringWithFormat:@"__gCrWeb.replaceWebViewURL(%@, %@);",
1286 base::SysUTF8ToNSString(outURL), stateObject];
1289 - (void)finishPushStateNavigationToURL:(const GURL&)url
1290 withStateObject:(NSString*)stateObject {
1291 // TODO(stuartmorgan): Make CRWSessionController manage this internally (or
1292 // remove it; it's not clear this matches other platforms' behavior).
1293 _webStateImpl->GetNavigationManagerImpl().OnNavigationItemCommitted();
1295 NSString* replaceWebViewUrlJS =
1296 [self javascriptToReplaceWebViewURL:url stateObjectJSON:stateObject];
1297 std::string outState;
1298 base::EscapeJSONString(base::SysNSStringToUTF8(stateObject), true, &outState);
1299 NSString* popstateJS =
1300 [NSString stringWithFormat:@"__gCrWeb.dispatchPopstateEvent(%@);",
1301 base::SysUTF8ToNSString(outState)];
1302 NSString* combinedJS =
1303 [NSString stringWithFormat:@"%@%@", replaceWebViewUrlJS, popstateJS];
1305 base::WeakNSObject<CRWWebController> weakSelf(self);
1306 [self evaluateJavaScript:combinedJS
1307 stringResultHandler:^(NSString*, NSError*) {
1308 if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
1310 base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
1311 strongSelf.get()->_URLOnStartLoading = urlCopy;
1312 strongSelf.get()->_lastRegisteredRequestURL = urlCopy;
1316 // Load the current URL in a web view, first ensuring the web view is visible.
1317 // If a native controller is present, remove it and swap a new web view in
1319 - (void)loadCurrentURLInWebView {
1320 [self willLoadCurrentURLInWebView];
1322 // Re-register the user agent, because UIWebView sometimes loses it.
1323 // See crbug.com/228397.
1324 [self registerUserAgent];
1326 // Clear the set of URLs opened in external applications.
1327 _openedApplicationURL.reset([[NSMutableSet alloc] init]);
1329 // Load the url. The UIWebView delegate callbacks take care of updating the
1330 // session history and UI.
1331 const GURL targetURL([self currentNavigationURL]);
1332 if (!targetURL.is_valid())
1335 // JavaScript should never be evaluated here. User-entered JS should be
1336 // evaluated via stringByEvaluatingUserJavaScriptFromString.
1337 DCHECK(!targetURL.SchemeIs(url::kJavaScriptScheme));
1338 [self ensureWebViewCreated];
1340 DCHECK(self.webView && !self.nativeController);
1341 NSMutableURLRequest* request =
1342 [NSMutableURLRequest requestWithURL:net::NSURLWithGURL(targetURL)];
1343 const web::Referrer referrer([self currentSessionEntryReferrer]);
1344 if (referrer.url.is_valid()) {
1345 std::string referrerValue =
1346 web::ReferrerHeaderValueForNavigation(targetURL, referrer);
1347 if (!referrerValue.empty()) {
1348 [request setValue:base::SysUTF8ToNSString(referrerValue)
1349 forHTTPHeaderField:kReferrerHeaderName];
1353 // If there are headers in the current session entry add them to |request|.
1354 // Headers that would overwrite fields already present in |request| are
1356 NSDictionary* headers = [self currentHttpHeaders];
1357 for (NSString* headerName in headers) {
1358 if (![request valueForHTTPHeaderField:headerName]) {
1359 [request setValue:[headers objectForKey:headerName]
1360 forHTTPHeaderField:headerName];
1364 NSData* postData = [self currentPOSTData];
1366 web::NavigationItemImpl* currentItem =
1367 [self currentSessionEntry].navigationItemImpl;
1368 if ([postData length] > 0 &&
1369 !(currentItem && currentItem->ShouldSkipResubmitDataConfirmation())) {
1371 [self registerLoadRequest:[self currentNavigationURL]
1372 referrer:[self currentSessionEntryReferrer]
1373 transition:[self currentTransition]];
1374 [self loadRequest:request];
1376 id continueBlock = ^{
1377 [request setHTTPMethod:@"POST"];
1378 [request setHTTPBody:[self currentPOSTData]];
1379 [request setAllHTTPHeaderFields:[self currentHttpHeaders]];
1380 [self registerLoadRequest:[self currentNavigationURL]
1381 referrer:[self currentSessionEntryReferrer]
1382 transition:[self currentTransition]];
1383 [self loadRequest:request];
1385 [_delegate webController:self
1386 onFormResubmissionForRequest:request
1387 continueBlock:continueBlock
1388 cancelBlock:cancelBlock];
1391 // The user does not need to confirm if POST data is empty.
1392 [request setHTTPMethod:@"POST"];
1393 [request setHTTPBody:postData];
1394 [request setAllHTTPHeaderFields:[self currentHttpHeaders]];
1398 // registerLoadRequest will be called when load is about to begin.
1399 // The phase at that point is guaranteed to be web::LOAD_REQUESTED.
1400 // However the delegate is not immediately called.
1401 [self registerLoadRequest:targetURL
1403 transition:[self currentTransition]];
1404 [self loadRequest:request];
1407 - (void)loadNativeViewWithSuccess:(BOOL)loadSuccess {
1408 const GURL currentURL([self currentURL]);
1409 [self didStartLoadingURL:currentURL updateHistory:loadSuccess];
1410 _loadPhase = web::PAGE_LOADED;
1412 // Perform post-load-finished updates.
1413 [self didFinishWithURL:currentURL loadSuccess:loadSuccess];
1415 // Inform the embedder the title changed.
1416 if ([_delegate respondsToSelector:@selector(webController:titleDidChange:)]) {
1417 NSString* title = [self.nativeController title];
1418 // If a title is present, notify the delegate.
1420 [_delegate webController:self titleDidChange:title];
1421 // If the controller handles title change notification, route those to the
1423 if ([self.nativeController respondsToSelector:@selector(setDelegate:)]) {
1424 [self.nativeController setDelegate:self];
1429 - (void)loadErrorInNativeView:(NSError*)error {
1430 [self removeWebViewAllowingCachedReconstruction:NO];
1432 const GURL currentUrl = [self currentNavigationURL];
1433 BOOL isPost = [self currentPOSTData] != nil;
1435 error = web::NetErrorFromError(error);
1436 [self setNativeController:[_nativeProvider controllerForURL:currentUrl
1439 [self loadNativeViewWithSuccess:NO];
1442 // Load the current URL in a native controller, retrieved from the native
1443 // provider. Call |loadNativeViewWithSuccess:YES| to load the native controller.
1444 - (void)loadCurrentURLInNativeView {
1445 // Free the web view.
1446 [self removeWebViewAllowingCachedReconstruction:NO];
1448 const GURL targetURL = [self currentNavigationURL];
1449 const web::Referrer referrer;
1450 // Unlike the WebView case, always create a new controller and view.
1451 // TODO(pinkerton): What to do if this does return nil?
1452 [self setNativeController:[_nativeProvider controllerForURL:targetURL]];
1453 [self registerLoadRequest:targetURL
1455 transition:[self currentTransition]];
1456 [self loadNativeViewWithSuccess:YES];
1459 - (void)loadWithParams:(const web::WebLoadParams&)originalParams {
1460 // Make a copy of |params|, as some of the delegate methods may modify it.
1461 web::WebLoadParams params(originalParams);
1463 // Initiating a navigation from the UI, record the current page state before
1464 // the new page loads. Don't record for back/forward, as the current entry
1465 // has already been moved to the next entry in the history. Do, however,
1466 // record it for general reload.
1467 // TODO(jimblackler): consider a single unified call to record state whenever
1468 // the page is about to be changed. This cannot currently be done after
1469 // addPendingEntry is called.
1471 [_delegate webWillInitiateLoadWithParams:params];
1473 GURL navUrl = params.url;
1474 ui::PageTransition transition = params.transition_type;
1476 BOOL initialNavigation = NO;
1478 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD) &&
1479 (transition & ui::PAGE_TRANSITION_FORWARD_BACK);
1481 // Setting these for back/forward is not supported.
1482 DCHECK(!params.extra_headers);
1483 DCHECK(!params.post_data);
1485 // TODO(stuartmorgan): Why doesn't recordStateInHistory get called for
1486 // forward/back transitions?
1487 [self recordStateInHistory];
1489 CRWSessionController* history =
1490 _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1491 if (!self.currentSessionEntry)
1492 initialNavigation = YES;
1493 [history addPendingEntry:navUrl
1494 referrer:params.referrer
1495 transition:transition
1496 rendererInitiated:params.is_renderer_initiated];
1497 web::NavigationItemImpl* addedItem =
1498 [self currentSessionEntry].navigationItemImpl;
1500 if (params.extra_headers)
1501 addedItem->AddHttpRequestHeaders(params.extra_headers);
1502 if (params.post_data) {
1503 DCHECK([addedItem->GetHttpRequestHeaders() objectForKey:@"Content-Type"])
1504 << "Post data should have an associated content type";
1505 addedItem->SetPostData(params.post_data);
1506 addedItem->SetShouldSkipResubmitDataConfirmation(true);
1510 [_delegate webDidUpdateSessionForLoadWithParams:params
1511 wasInitialNavigation:initialNavigation];
1513 // If a non-default cache mode is passed in, it takes precedence over
1515 const BOOL reload = [self shouldReload:navUrl transition:transition];
1516 if (params.cache_mode != net::RequestTracker::CACHE_NORMAL) {
1517 _webStateImpl->SetCacheMode(params.cache_mode);
1518 } else if (reload) {
1519 _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_RELOAD);
1522 [self loadCurrentURL];
1524 // Change the cache mode back to CACHE_NORMAL after a reload.
1525 _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1528 - (void)loadCurrentURL {
1529 // If the content view doesn't exist, the tab has either been evicted, or
1530 // never displayed. Bail, and let the URL be loaded when the tab is shown.
1531 if (!self.containerView)
1534 // Reset current WebUI if one exists.
1537 // Precaution, so that the outgoing URL is registered, to reduce the risk of
1538 // it being seen as a fresh URL later by the same method (and new page change
1539 // erroneously reported).
1540 [self checkForUnexpectedURLChange];
1542 // Abort any outstanding page load. This ensures the delegate gets informed
1543 // about the outgoing page, and further messages from the page are suppressed.
1544 if (_loadPhase != web::PAGE_LOADED)
1548 // Remove the transient content view.
1549 [self clearTransientContentView];
1551 const GURL currentURL = [self currentNavigationURL];
1552 // If it's a chrome URL, but not a native one, create the WebUI instance.
1553 if (web::GetWebClient()->IsAppSpecificURL(currentURL) &&
1554 ![_nativeProvider hasControllerForURL:currentURL]) {
1555 [self createWebUIForURL:currentURL];
1558 // Loading a new url, must check here if it's a native chrome URL and
1559 // replace the appropriate view if so, or transition back to a web view from
1561 if ([self shouldLoadURLInNativeView:currentURL]) {
1562 [self loadCurrentURLInNativeView];
1564 [self loadCurrentURLInWebView];
1567 // Once a URL has been loaded, any cached-based reconstruction state has
1568 // either been handled or obsoleted.
1569 _expectedReconstructionURL = GURL();
1572 - (BOOL)shouldLoadURLInNativeView:(const GURL&)url {
1573 // App-specific URLs that don't require WebUI are loaded in native views.
1574 return web::GetWebClient()->IsAppSpecificURL(url) &&
1575 !_webStateImpl->HasWebUI();
1578 - (void)triggerPendingLoad {
1579 if (!self.containerView) {
1580 DCHECK(!_isBeingDestroyed);
1581 // Create the top-level parent view, which will contain the content (whether
1582 // native or web). Note, this needs to be created with a non-zero size
1583 // to allow for (native) subviews with autosize constraints to be correctly
1585 _containerView.reset([[CRWWebControllerContainerView alloc]
1586 initWithContentViewProxy:_webViewProxy]);
1587 self.containerView.frame = [[UIScreen mainScreen] bounds];
1588 [self.containerView addGestureRecognizer:[self touchTrackingRecognizer]];
1589 [self.containerView setAccessibilityIdentifier:web::kContainerViewID];
1590 // Is |currentUrl| a web scheme or native chrome scheme.
1591 BOOL isChromeScheme =
1592 web::GetWebClient()->IsAppSpecificURL([self currentNavigationURL]);
1594 // Don't immediately load the web page if in overlay mode. Always load if
1596 if (isChromeScheme || !_overlayPreviewMode) {
1597 // TODO(jimblackler): end the practice of calling |loadCurrentURL| when it
1598 // is possible there is no current URL. If the call performs necessary
1599 // initialization, break that out.
1600 [self loadCurrentURL];
1603 // Display overlay view until current url has finished loading or delay and
1604 // then transition away.
1605 if ((_overlayPreviewMode || _usePlaceholderOverlay) && !isChromeScheme)
1606 [self addPlaceholderOverlay];
1608 // Don't reset the overlay flag if in preview mode.
1609 if (!_overlayPreviewMode)
1610 _usePlaceholderOverlay = NO;
1614 - (BOOL)shouldReload:(const GURL&)destinationURL
1615 transition:(ui::PageTransition)transition {
1616 // Do a reload if the user hits enter in the address bar or re-types a URL.
1617 CRWSessionController* sessionController =
1618 _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1619 web::NavigationItem* item =
1620 _webStateImpl->GetNavigationManagerImpl().GetVisibleItem();
1621 return (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) && item &&
1622 (destinationURL == item->GetURL() ||
1623 destinationURL == [sessionController currentEntry].originalUrl);
1626 // Reload either the web view or the native content depending on which is
1628 - (void)reloadInternal {
1629 web::RecordAction(UserMetricsAction("Reload"));
1631 // Just as we don't use the WebView native back and forward navigation
1632 // (preferring to load the URLs manually) we don't use the native reload.
1633 // This ensures state processing and delegate calls are consistent.
1634 [self loadCurrentURL];
1636 [self.nativeController reload];
1641 [_delegate webWillReload];
1643 _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_RELOAD);
1644 [self reloadInternal];
1645 _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1648 - (void)loadCancelled {
1649 // Current load will not complete; this should be communicated upstream to the
1651 switch (_loadPhase) {
1652 case web::LOAD_REQUESTED:
1653 // Load phase after abort is always PAGE_LOADED.
1654 _loadPhase = web::PAGE_LOADED;
1656 _webStateImpl->SetIsLoading(false);
1658 [_delegate webCancelStartLoadingRequest];
1660 case web::PAGE_LOADING:
1661 // The previous load never fully completed before this page change. The
1662 // loadPhase is changed to PAGE_LOADED to indicate the cycle is complete,
1663 // and the delegate is called.
1664 _loadPhase = web::PAGE_LOADED;
1666 // RequestTracker expects StartPageLoad to be followed by
1667 // FinishPageLoad, passing the exact same URL.
1668 self.webStateImpl->GetRequestTracker()->FinishPageLoad(
1669 _URLOnStartLoading, false);
1671 [_delegate webLoadCancelled:_URLOnStartLoading];
1673 case web::PAGE_LOADED:
1679 [self abortWebLoad];
1680 [self loadCancelled];
1683 - (void)prepareForGoBack {
1684 // Make sure any transitions that may have occurred have been seen and acted
1685 // on by the CRWWebController, so the history stack and state of the
1686 // CRWWebController is 100% up to date before the stack navigation starts.
1688 [self injectEarlyInjectionScripts];
1689 [self checkForUnexpectedURLChange];
1691 // Discard any outstanding pending entries before adjusting the navigation
1693 CRWSessionController* sessionController =
1694 _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1695 [sessionController discardNonCommittedEntries];
1697 bool wasShowingInterstitial = _webStateImpl->IsShowingWebInterstitial();
1699 // Call into the delegate before |recordStateInHistory|.
1700 // TODO(rohitrao): Can this be reordered after |recordStateInHistory|?
1701 [_delegate webDidPrepareForGoBack];
1703 // Before changing the current session history entry, record the tab state.
1704 if (!wasShowingInterstitial) {
1705 [self recordStateInHistory];
1717 - (void)goDelta:(int)delta {
1723 // Abort if there is nothing next in the history.
1724 // Note that it is NOT checked that the history depth is at least |delta|.
1725 if ((delta < 0 && ![self canGoBack]) || (delta > 0 && ![self canGoForward])) {
1730 [self prepareForGoBack];
1732 // Before changing the current session history entry, record the tab state.
1733 [self recordStateInHistory];
1736 [_delegate webWillGoDelta:delta];
1738 CRWSessionController* sessionController =
1739 _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1740 CRWSessionEntry* fromEntry = [sessionController currentEntry];
1741 [sessionController goDelta:delta];
1743 _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_HISTORY);
1744 [self finishHistoryNavigationFromEntry:fromEntry];
1745 _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1750 return _loadPhase == web::PAGE_LOADED;
1753 - (void)loadCompleteWithSuccess:(BOOL)loadSuccess {
1754 [self removePlaceholderOverlay];
1755 // The webView may have been torn down (or replaced by a native view). Be
1756 // safe and do nothing if that's happened.
1757 if (_loadPhase != web::PAGE_LOADING)
1760 DCHECK(self.webView);
1762 const GURL currentURL([self currentURL]);
1764 [self resetLoadState];
1765 _loadPhase = web::PAGE_LOADED;
1767 [self optOutScrollsToTopForSubviews];
1769 // Ensure the URL is as expected (and already reported to the delegate).
1770 DCHECK(currentURL == _lastRegisteredRequestURL)
1772 << "currentURL = [" << currentURL << "]" << std::endl
1773 << "_lastRegisteredRequestURL = [" << _lastRegisteredRequestURL << "]";
1775 // Perform post-load-finished updates.
1776 [self didFinishWithURL:currentURL loadSuccess:loadSuccess];
1778 // Execute the pending LoadCompleteActions.
1779 for (ProceduralBlock action in _pendingLoadCompleteActions.get()) {
1782 [_pendingLoadCompleteActions removeAllObjects];
1785 - (void)didFinishWithURL:(const GURL&)currentURL loadSuccess:(BOOL)loadSuccess {
1786 DCHECK(_loadPhase == web::PAGE_LOADED);
1787 _webStateImpl->GetRequestTracker()->FinishPageLoad(currentURL, loadSuccess);
1788 // Reset the navigation type to the default value.
1789 // Note: it is possible that the web view has already started loading the
1790 // next page when this is called. In that case the cache mode can leak to
1791 // (some of) the requests of the next page. It's expected to be an edge case,
1792 // but if it becomes a problem it should be possible to notice it afterwards
1793 // and react to it (by warning the user or reloading the page for example).
1794 _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1795 _webStateImpl->GetRequestTracker()->SetCacheModeFromUIThread(
1796 _webStateImpl->GetCacheMode());
1798 [self restoreStateFromHistory];
1799 _webStateImpl->OnPageLoaded(currentURL, loadSuccess);
1800 _webStateImpl->SetIsLoading(false);
1801 // Inform the embedder the load completed.
1802 [_delegate webDidFinishWithURL:currentURL loadSuccess:loadSuccess];
1805 - (void)finishHistoryNavigationFromEntry:(CRWSessionEntry*)fromEntry {
1806 [_delegate webWillFinishHistoryNavigationFromEntry:fromEntry];
1808 // Check if toEntry was created by a JavaScript window.history.pushState()
1809 // call from fromEntry. If it was, don't load the URL. Instead update
1810 // UIWebView's URL and dispatch a popstate event.
1811 if ([_webStateImpl->GetNavigationManagerImpl().GetSessionController()
1812 isPushStateNavigationBetweenEntry:fromEntry
1813 andEntry:self.currentSessionEntry]) {
1814 NSString* state = [self currentSessionEntry]
1815 .navigationItemImpl->GetSerializedStateObject();
1816 [self finishPushStateNavigationToURL:[self currentNavigationURL]
1817 withStateObject:state];
1819 GURL activeURL = [self currentNavigationURL];
1820 GURL fromURL = fromEntry.navigationItem->GetURL();
1822 [self updateURLForHistoryNavigationFromURL:fromURL toURL:activeURL];
1823 web::NavigationItem* currentItem =
1824 _webStateImpl->GetNavigationManagerImpl().GetVisibleItem();
1825 ui::PageTransition transition = ui::PageTransitionFromInt(
1826 ui::PAGE_TRANSITION_RELOAD | ui::PAGE_TRANSITION_FORWARD_BACK);
1828 web::WebLoadParams params(endURL);
1830 params.referrer = currentItem->GetReferrer();
1832 params.transition_type = transition;
1833 [self loadWithParams:params];
1837 - (GURL)updateURLForHistoryNavigationFromURL:(const GURL&)startURL
1838 toURL:(const GURL&)endURL {
1839 // Check the state of the fragments on both URLs (aka, is there a '#' in the
1841 if (!startURL.has_ref() || endURL.has_ref()) {
1845 // startURL contains a fragment and endURL doesn't. Remove the fragment from
1846 // startURL and compare the resulting string to endURL. If they are equal, add
1847 // # to endURL to cause a hashchange event.
1848 GURL hashless = web::GURLByRemovingRefFromGURL(startURL);
1850 if (hashless != endURL)
1853 url::StringPieceReplacements<std::string> emptyRef;
1854 emptyRef.SetRefStr("");
1855 GURL newEndURL = endURL.ReplaceComponents(emptyRef);
1856 web::NavigationItem* item =
1857 _webStateImpl->GetNavigationManagerImpl().GetVisibleItem();
1859 item->SetURL(newEndURL);
1863 - (void)evaluateJavaScript:(NSString*)script
1865 (void (^)(scoped_ptr<base::Value>, NSError*))handler {
1866 [self evaluateJavaScript:script
1867 stringResultHandler:^(NSString* stringResult, NSError* error) {
1868 DCHECK(stringResult || error);
1870 scoped_ptr<base::Value> result(
1871 base::JSONReader::Read(base::SysNSStringToUTF8(stringResult)));
1872 handler(result.Pass(), error);
1877 - (void)addGestureRecognizerToWebView:(UIGestureRecognizer*)recognizer {
1878 if ([_gestureRecognizers containsObject:recognizer])
1881 [self.webView addGestureRecognizer:recognizer];
1882 [_gestureRecognizers addObject:recognizer];
1885 - (void)removeGestureRecognizerFromWebView:(UIGestureRecognizer*)recognizer {
1886 if (![_gestureRecognizers containsObject:recognizer])
1889 [self.webView removeGestureRecognizer:recognizer];
1890 [_gestureRecognizers removeObject:recognizer];
1893 - (void)addToolbarViewToWebView:(UIView*)toolbarView {
1894 DCHECK(toolbarView);
1895 if ([_webViewToolbars containsObject:toolbarView])
1897 [_webViewToolbars addObject:toolbarView];
1899 [self.containerView addToolbar:toolbarView];
1902 - (void)removeToolbarViewFromWebView:(UIView*)toolbarView {
1903 if (![_webViewToolbars containsObject:toolbarView])
1905 [_webViewToolbars removeObject:toolbarView];
1907 [self.containerView removeToolbar:toolbarView];
1910 - (CRWJSInjectionReceiver*)jsInjectionReceiver {
1911 return _jsInjectionReceiver;
1914 - (BOOL)cancellable {
1915 return self.sessionController.openedByDOM &&
1916 !self.sessionController.lastCommittedEntry;
1919 - (BOOL)isBeingDestroyed {
1920 return _isBeingDestroyed;
1927 - (web::ReferrerPolicy)referrerPolicyFromString:(const std::string&)policy {
1928 // TODO(stuartmorgan): Remove this temporary bridge to the helper function
1929 // once the referrer handling moves into the subclasses.
1930 return web::ReferrerPolicyFromString(policy);
1934 #pragma mark CRWJSInjectionEvaluator Methods
1936 - (void)evaluateJavaScript:(NSString*)script
1937 stringResultHandler:(web::JavaScriptCompletion)handler {
1938 // Subclasses must implement this method.
1942 - (BOOL)scriptHasBeenInjectedForClass:(Class)jsInjectionManagerClass
1943 presenceBeacon:(NSString*)beacon {
1944 // Subclasses must implement this method.
1949 - (void)injectScript:(NSString*)script forClass:(Class)JSInjectionManagerClass {
1950 // Make sure that CRWJSEarlyScriptManager has been injected.
1951 BOOL ealyScriptInjected =
1952 [self scriptHasBeenInjectedForClass:[CRWJSEarlyScriptManager class]
1953 presenceBeacon:[_earlyScriptManager presenceBeacon]];
1954 if (!ealyScriptInjected &&
1955 JSInjectionManagerClass != [CRWJSEarlyScriptManager class]) {
1956 [_earlyScriptManager inject];
1960 - (web::WebViewType)webViewType {
1961 // Subclasses must implement this method.
1963 return web::UI_WEB_VIEW_TYPE;
1968 - (void)evaluateUserJavaScript:(NSString*)script {
1969 // Subclasses must implement this method.
1973 - (void)didFinishNavigation {
1974 // This can be called at multiple times after the document has loaded. Do
1975 // nothing if the document has already loaded.
1976 if (_loadPhase == web::PAGE_LOADED)
1978 [self loadCompleteWithSuccess:YES];
1981 - (BOOL)respondToMessage:(base::DictionaryValue*)message
1982 userIsInteracting:(BOOL)userIsInteracting
1983 originURL:(const GURL&)originURL {
1984 std::string command;
1985 if (!message->GetString("command", &command)) {
1986 DLOG(WARNING) << "JS message parameter not found: command";
1990 SEL handler = [self selectorToHandleJavaScriptCommand:command];
1992 if (!self.webStateImpl->OnScriptCommandReceived(
1993 command, *message, originURL, userIsInteracting)) {
1994 // Message was either unexpected or not correctly handled.
1995 // Page is reset as a precaution.
1996 DLOG(WARNING) << "Unexpected message received: " << command;
2002 typedef BOOL (*HandlerType)(id, SEL, base::DictionaryValue*, NSDictionary*);
2003 HandlerType handlerImplementation =
2004 reinterpret_cast<HandlerType>([self methodForSelector:handler]);
2005 DCHECK(handlerImplementation);
2006 NSMutableDictionary* context =
2007 [NSMutableDictionary dictionaryWithObject:@(userIsInteracting)
2008 forKey:web::kUserIsInteractingKey];
2009 NSURL* originNSURL = net::NSURLWithGURL(originURL);
2011 context[web::kOriginURLKey] = originNSURL;
2012 return handlerImplementation(self, handler, message, context);
2015 - (SEL)selectorToHandleJavaScriptCommand:(const std::string&)command {
2016 static std::map<std::string, SEL>* handlers = nullptr;
2017 static dispatch_once_t onceToken;
2018 dispatch_once(&onceToken, ^{
2019 handlers = new std::map<std::string, SEL>();
2020 (*handlers)["addPluginPlaceholders"] =
2021 @selector(handleAddPluginPlaceholdersMessage:context:);
2022 (*handlers)["chrome.send"] = @selector(handleChromeSendMessage:context:);
2023 (*handlers)["console"] = @selector(handleConsoleMessage:context:);
2024 (*handlers)["dialog.suppressed"] =
2025 @selector(handleDialogSuppressedMessage:context:);
2026 (*handlers)["dialog.willShow"] =
2027 @selector(handleDialogWillShowMessage:context:);
2028 (*handlers)["document.favicons"] =
2029 @selector(handleDocumentFaviconsMessage:context:);
2030 (*handlers)["document.retitled"] =
2031 @selector(handleDocumentRetitledMessage:context:);
2032 (*handlers)["document.submit"] =
2033 @selector(handleDocumentSubmitMessage:context:);
2034 (*handlers)["externalRequest"] =
2035 @selector(handleExternalRequestMessage:context:);
2036 (*handlers)["form.activity"] =
2037 @selector(handleFormActivityMessage:context:);
2038 (*handlers)["form.requestAutocomplete"] =
2039 @selector(handleFormRequestAutocompleteMessage:context:);
2040 (*handlers)["navigator.credentials.request"] =
2041 @selector(handleCredentialsRequestedMessage:context:);
2042 (*handlers)["navigator.credentials.notifySignedIn"] =
2043 @selector(handleSignedInMessage:context:);
2044 (*handlers)["navigator.credentials.notifySignedOut"] =
2045 @selector(handleSignedOutMessage:context:);
2046 (*handlers)["navigator.credentials.notifyFailedSignIn"] =
2047 @selector(handleSignInFailedMessage:context:);
2048 (*handlers)["resetExternalRequest"] =
2049 @selector(handleResetExternalRequestMessage:context:);
2050 (*handlers)["window.close.self"] =
2051 @selector(handleWindowCloseSelfMessage:context:);
2052 (*handlers)["window.error"] = @selector(handleWindowErrorMessage:context:);
2053 (*handlers)["window.hashchange"] =
2054 @selector(handleWindowHashChangeMessage:context:);
2055 (*handlers)["window.history.back"] =
2056 @selector(handleWindowHistoryBackMessage:context:);
2057 (*handlers)["window.history.willChangeState"] =
2058 @selector(handleWindowHistoryWillChangeStateMessage:context:);
2059 (*handlers)["window.history.didPushState"] =
2060 @selector(handleWindowHistoryDidPushStateMessage:context:);
2061 (*handlers)["window.history.didReplaceState"] =
2062 @selector(handleWindowHistoryDidReplaceStateMessage:context:);
2063 (*handlers)["window.history.forward"] =
2064 @selector(handleWindowHistoryForwardMessage:context:);
2065 (*handlers)["window.history.go"] =
2066 @selector(handleWindowHistoryGoMessage:context:);
2069 auto iter = handlers->find(command);
2070 return iter != handlers->end() ? iter->second : nullptr;
2074 #pragma mark JavaScript message handlers
2076 - (BOOL)handleAddPluginPlaceholdersMessage:(base::DictionaryValue*)message
2077 context:(NSDictionary*)context {
2078 // Inject the script that adds the plugin placeholders.
2079 [[_jsInjectionReceiver
2080 instanceOfClass:[CRWJSPluginPlaceholderManager class]] inject];
2084 - (BOOL)handleChromeSendMessage:(base::DictionaryValue*)message
2085 context:(NSDictionary*)context {
2086 if (_webStateImpl->HasWebUI()) {
2087 const GURL currentURL([self currentURL]);
2088 if (web::GetWebClient()->IsAppSpecificURL(currentURL)) {
2089 std::string messageContent;
2090 base::ListValue* arguments = nullptr;
2091 if (!message->GetString("message", &messageContent)) {
2092 DLOG(WARNING) << "JS message parameter not found: message";
2095 if (!message->GetList("arguments", &arguments)) {
2096 DLOG(WARNING) << "JS message parameter not found: arguments";
2099 _webStateImpl->OnScriptCommandReceived(
2100 messageContent, *message, currentURL,
2101 context[web::kUserIsInteractingKey]);
2102 _webStateImpl->ProcessWebUIMessage(currentURL, messageContent,
2109 << "chrome.send message not handled because WebUI was not found.";
2113 - (BOOL)handleConsoleMessage:(base::DictionaryValue*)message
2114 context:(NSDictionary*)context {
2115 // Do not log if JS logging is off.
2116 if (![[NSUserDefaults standardUserDefaults] boolForKey:web::kLogJavaScript]) {
2121 if (!message->GetString("method", &method)) {
2122 DLOG(WARNING) << "JS message parameter not found: method";
2125 std::string consoleMessage;
2126 if (!message->GetString("message", &consoleMessage)) {
2127 DLOG(WARNING) << "JS message parameter not found: message";
2131 if (!message->GetString("origin", &origin)) {
2132 DLOG(WARNING) << "JS message parameter not found: origin";
2136 DVLOG(0) << origin << " [" << method << "] " << consoleMessage;
2140 - (BOOL)handleDialogSuppressedMessage:(base::DictionaryValue*)message
2141 context:(NSDictionary*)context {
2143 respondsToSelector:@selector(webControllerDidSuppressDialog:)]) {
2144 [_delegate webControllerDidSuppressDialog:self];
2149 - (BOOL)handleDialogWillShowMessage:(base::DictionaryValue*)message
2150 context:(NSDictionary*)context {
2151 if ([_delegate respondsToSelector:@selector(webControllerWillShowDialog:)]) {
2152 [_delegate webControllerWillShowDialog:self];
2157 - (BOOL)handleDocumentFaviconsMessage:(base::DictionaryValue*)message
2158 context:(NSDictionary*)context {
2159 base::ListValue* favicons = nullptr;
2160 if (!message->GetList("favicons", &favicons)) {
2161 DLOG(WARNING) << "JS message parameter not found: favicons";
2164 std::vector<web::FaviconURL> urls;
2165 for (size_t fav_idx = 0; fav_idx != favicons->GetSize(); ++fav_idx) {
2166 base::DictionaryValue* favicon = nullptr;
2167 if (!favicons->GetDictionary(fav_idx, &favicon))
2171 if (!favicon->GetString("href", &href)) {
2172 DLOG(WARNING) << "JS message parameter not found: href";
2175 if (!favicon->GetString("rel", &rel)) {
2176 DLOG(WARNING) << "JS message parameter not found: rel";
2179 web::FaviconURL::IconType icon_type = web::FaviconURL::FAVICON;
2180 if (rel == "apple-touch-icon")
2181 icon_type = web::FaviconURL::TOUCH_ICON;
2182 else if (rel == "apple-touch-icon-precomposed")
2183 icon_type = web::FaviconURL::TOUCH_PRECOMPOSED_ICON;
2185 web::FaviconURL(GURL(href), icon_type, std::vector<gfx::Size>()));
2188 _webStateImpl->OnFaviconUrlUpdated(urls);
2192 - (BOOL)handleDocumentSubmitMessage:(base::DictionaryValue*)message
2193 context:(NSDictionary*)context {
2195 if (!message->GetString("href", &href)) {
2196 DLOG(WARNING) << "JS message parameter not found: href";
2199 const GURL targetURL(href);
2200 const GURL currentURL([self currentURL]);
2201 bool targetsFrame = false;
2202 message->GetBoolean("targetsFrame", &targetsFrame);
2203 if (!targetsFrame && web::UrlHasWebScheme(targetURL)) {
2204 // The referrer is not known yet, and will be updated later.
2205 const web::Referrer emptyReferrer;
2206 [self registerLoadRequest:targetURL
2207 referrer:emptyReferrer
2208 transition:ui::PAGE_TRANSITION_FORM_SUBMIT];
2210 std::string formName;
2211 message->GetString("formName", &formName);
2212 base::scoped_nsobject<NSSet> observers([_observers copy]);
2213 // We decide the form is user-submitted if the user has interacted with
2214 // the main page (using logic from the popup blocker), or if the keyboard
2216 BOOL submittedByUser = [context[web::kUserIsInteractingKey] boolValue] ||
2217 [_webViewProxy getKeyboardAccessory];
2218 _webStateImpl->OnDocumentSubmitted(formName, submittedByUser);
2222 - (BOOL)handleExternalRequestMessage:(base::DictionaryValue*)message
2223 context:(NSDictionary*)context {
2226 std::string referrerPolicy;
2227 if (!message->GetString("href", &href)) {
2228 DLOG(WARNING) << "JS message parameter not found: href";
2231 if (!message->GetString("target", &target)) {
2232 DLOG(WARNING) << "JS message parameter not found: target";
2235 if (!message->GetString("referrerPolicy", &referrerPolicy)) {
2236 DLOG(WARNING) << "JS message parameter not found: referrerPolicy";
2239 // Round-trip the href through NSURL; this URL will be compared as a
2240 // string against a UIWebView-provided NSURL later, and must match exactly
2241 // for the new window to trigger, so the escaping needs to be NSURL-style.
2242 // TODO(stuartmorgan): Comparing against a URL whose exact formatting we
2243 // don't control is fundamentally fragile; try to find another
2244 // way of handling this.
2245 DCHECK(context[web::kUserIsInteractingKey]);
2246 NSString* windowName =
2247 base::SysUTF8ToNSString(href + web::kWindowNameSeparator + target);
2248 _externalRequest.reset(new web::NewWindowInfo(
2249 net::GURLWithNSURL(net::NSURLWithGURL(GURL(href))), windowName,
2250 web::ReferrerPolicyFromString(referrerPolicy),
2251 [context[web::kUserIsInteractingKey] boolValue]));
2255 - (BOOL)handleFormActivityMessage:(base::DictionaryValue*)message
2256 context:(NSDictionary*)context {
2257 std::string formName;
2258 std::string fieldName;
2261 int keyCode = web::WebStateObserver::kInvalidFormKeyCode;
2262 bool inputMissing = false;
2263 if (!message->GetString("formName", &formName) ||
2264 !message->GetString("fieldName", &fieldName) ||
2265 !message->GetString("type", &type) ||
2266 !message->GetString("value", &value)) {
2267 inputMissing = true;
2270 if (!message->GetInteger("keyCode", &keyCode) || keyCode < 0)
2271 keyCode = web::WebStateObserver::kInvalidFormKeyCode;
2272 _webStateImpl->OnFormActivityRegistered(formName, fieldName, type, value,
2273 keyCode, inputMissing);
2277 - (BOOL)handleFormRequestAutocompleteMessage:(base::DictionaryValue*)message
2278 context:(NSDictionary*)context {
2279 std::string formName;
2280 if (!message->GetString("formName", &formName)) {
2281 DLOG(WARNING) << "JS message parameter not found: formName";
2284 DCHECK(context[web::kUserIsInteractingKey]);
2285 _webStateImpl->OnAutocompleteRequested(
2286 net::GURLWithNSURL(context[web::kOriginURLKey]), formName,
2287 [context[web::kUserIsInteractingKey] boolValue]);
2291 - (BOOL)handleCredentialsRequestedMessage:(base::DictionaryValue*)message
2292 context:(NSDictionary*)context {
2293 int request_id = -1;
2294 if (!message->GetInteger("requestId", &request_id)) {
2295 DLOG(WARNING) << "JS message parameter not found: requestId";
2298 bool suppress_ui = false;
2299 if (!message->GetBoolean("suppressUI", &suppress_ui)) {
2300 DLOG(WARNING) << "JS message parameter not found: suppressUI";
2303 base::ListValue* federations_value = nullptr;
2304 if (!message->GetList("federations", &federations_value)) {
2305 DLOG(WARNING) << "JS message parameter not found: federations";
2308 std::vector<std::string> federations;
2309 for (auto federation_value : *federations_value) {
2310 std::string federation;
2311 if (!federation_value->GetAsString(&federation)) {
2312 DLOG(WARNING) << "JS message parameter 'federations' contains wrong type";
2315 federations.push_back(federation);
2317 DCHECK(context[web::kUserIsInteractingKey]);
2318 _webStateImpl->OnCredentialsRequested(
2319 request_id, net::GURLWithNSURL(context[web::kOriginURLKey]), suppress_ui,
2320 federations, [context[web::kUserIsInteractingKey] boolValue]);
2324 - (BOOL)handleSignedInMessage:(base::DictionaryValue*)message
2325 context:(NSDictionary*)context {
2326 int request_id = -1;
2327 if (!message->GetInteger("requestId", &request_id)) {
2328 DLOG(WARNING) << "JS message parameter not found: requestId";
2331 base::DictionaryValue* credential_data = nullptr;
2332 web::Credential credential;
2333 if (message->GetDictionary("credential", &credential_data)) {
2334 if (!web::DictionaryValueToCredential(*credential_data, &credential)) {
2335 DLOG(WARNING) << "JS message parameter 'credential' is invalid";
2338 _webStateImpl->OnSignedIn(request_id,
2339 net::GURLWithNSURL(context[web::kOriginURLKey]),
2342 _webStateImpl->OnSignedIn(request_id,
2343 net::GURLWithNSURL(context[web::kOriginURLKey]));
2348 - (BOOL)handleSignedOutMessage:(base::DictionaryValue*)message
2349 context:(NSDictionary*)context {
2350 int request_id = -1;
2351 if (!message->GetInteger("requestId", &request_id)) {
2352 DLOG(WARNING) << "JS message parameter not found: requestId";
2355 _webStateImpl->OnSignedOut(request_id,
2356 net::GURLWithNSURL(context[web::kOriginURLKey]));
2360 - (BOOL)handleSignInFailedMessage:(base::DictionaryValue*)message
2361 context:(NSDictionary*)context {
2362 int request_id = -1;
2363 if (!message->GetInteger("requestId", &request_id)) {
2364 DLOG(WARNING) << "JS message parameter not found: requestId";
2367 base::DictionaryValue* credential_data = nullptr;
2368 web::Credential credential;
2369 if (message->GetDictionary("credential", &credential_data)) {
2370 if (!web::DictionaryValueToCredential(*credential_data, &credential)) {
2371 DLOG(WARNING) << "JS message parameter 'credential' is invalid";
2374 _webStateImpl->OnSignInFailed(
2375 request_id, net::GURLWithNSURL(context[web::kOriginURLKey]),
2378 _webStateImpl->OnSignInFailed(
2379 request_id, net::GURLWithNSURL(context[web::kOriginURLKey]));
2384 - (BOOL)handleResetExternalRequestMessage:(base::DictionaryValue*)message
2385 context:(NSDictionary*)context {
2386 _externalRequest.reset();
2390 - (BOOL)handleWindowCloseSelfMessage:(base::DictionaryValue*)message
2391 context:(NSDictionary*)context {
2396 - (BOOL)handleWindowErrorMessage:(base::DictionaryValue*)message
2397 context:(NSDictionary*)context {
2398 std::string errorMessage;
2399 if (!message->GetString("message", &errorMessage)) {
2400 DLOG(WARNING) << "JS message parameter not found: message";
2403 DLOG(ERROR) << "JavaScript error: " << errorMessage
2404 << " URL:" << [self currentURL].spec();
2408 - (BOOL)handleWindowHashChangeMessage:(base::DictionaryValue*)message
2409 context:(NSDictionary*)context {
2410 [self checkForUnexpectedURLChange];
2412 // Notify the observers.
2413 _webStateImpl->OnUrlHashChanged();
2417 - (BOOL)handleWindowHistoryBackMessage:(base::DictionaryValue*)message
2418 context:(NSDictionary*)context {
2423 - (BOOL)handleWindowHistoryForwardMessage:(base::DictionaryValue*)message
2424 context:(NSDictionary*)context {
2429 - (BOOL)handleWindowHistoryGoMessage:(base::DictionaryValue*)message
2430 context:(NSDictionary*)context {
2432 message->GetInteger("value", &delta);
2433 [self goDelta:delta];
2437 - (BOOL)handleWindowHistoryWillChangeStateMessage:(base::DictionaryValue*)unused
2438 context:(NSDictionary*)unusedContext {
2439 // This dummy handler is a workaround for crbug.com/490673. Issue was
2440 // happening when two sequential calls of window.history.pushState were
2441 // performed by the page. In that case state was changed twice before
2442 // first change was reported to embedder (and first URL change was reported
2445 // Using dummy handler for window.history.willChangeState message holds
2446 // second state change until the first change is reported, because messages
2447 // are queued. This is essentially a sleep, and not the real fix of the
2448 // problem. TODO(eugenebut): refactor handleWindowHistoryDidPushStateMessage:
2449 // to avoid this "sleep".
2453 - (BOOL)handleWindowHistoryDidPushStateMessage:(base::DictionaryValue*)message
2454 context:(NSDictionary*)context {
2455 std::string pageURL;
2456 std::string baseURL;
2457 if (!message->GetString("pageUrl", &pageURL) ||
2458 !message->GetString("baseUrl", &baseURL)) {
2459 DLOG(WARNING) << "JS message parameter not found: pageUrl or baseUrl";
2462 GURL pushURL = web::history_state_util::GetHistoryStateChangeUrl(
2463 [self currentURL], GURL(baseURL), pageURL);
2464 // UIWebView seems to choke on unicode characters that haven't been
2465 // escaped; escape the URL now so the expected load URL is correct.
2466 pushURL = URLEscapedForHistory(pushURL);
2467 if (!pushURL.is_valid())
2469 const NavigationManagerImpl& navigationManager =
2470 _webStateImpl->GetNavigationManagerImpl();
2471 web::NavigationItem* navItem = [self currentNavItem];
2472 // PushState happened before first navigation entry or called right after
2473 // window.open when the url is empty.
2475 (navigationManager.GetEntryCount() <= 1 && navItem->GetURL().is_empty()))
2477 if (!web::history_state_util::IsHistoryStateChangeValid(navItem->GetURL(),
2479 // A redirect may have occurred just prior to the pushState. Check if
2480 // the URL needs to be updated.
2481 // TODO(bdibello): Investigate how the pushState() is handled before the
2482 // redirect and after core.js injection.
2483 [self checkForUnexpectedURLChange];
2485 if (!web::history_state_util::IsHistoryStateChangeValid(
2486 [self currentNavItem]->GetURL(), pushURL)) {
2487 // If the current session entry URL origin still doesn't match pushURL's
2488 // origin, ignore the pushState. This can happen if a new URL is loaded
2489 // just before the pushState.
2492 std::string stateObjectJSON;
2493 if (!message->GetString("stateObject", &stateObjectJSON)) {
2494 DLOG(WARNING) << "JS message parameter not found: stateObject";
2497 NSString* stateObject = base::SysUTF8ToNSString(stateObjectJSON);
2498 _URLOnStartLoading = pushURL;
2499 _lastRegisteredRequestURL = pushURL;
2501 // If the user interacted with the page, categorize it as a link navigation.
2502 // If not, categorize it is a client redirect as it occurred without user
2503 // input and should not be added to the history stack.
2504 // TODO(ios): Improve transition detection.
2505 ui::PageTransition transition =
2506 [context[web::kUserIsInteractingKey] boolValue]
2507 ? ui::PAGE_TRANSITION_LINK
2508 : ui::PAGE_TRANSITION_CLIENT_REDIRECT;
2509 [self pushStateWithPageURL:pushURL
2510 stateObject:stateObject
2511 transition:transition];
2513 NSString* replaceWebViewJS =
2514 [self javascriptToReplaceWebViewURL:pushURL stateObjectJSON:stateObject];
2515 base::WeakNSObject<CRWWebController> weakSelf(self);
2516 [self evaluateJavaScript:replaceWebViewJS
2517 stringResultHandler:^(NSString*, NSError*) {
2518 if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
2520 base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
2521 [strongSelf optOutScrollsToTopForSubviews];
2522 // Notify the observers.
2523 strongSelf.get()->_webStateImpl->OnHistoryStateChanged();
2524 [strongSelf didFinishNavigation];
2529 - (BOOL)handleWindowHistoryDidReplaceStateMessage:
2530 (base::DictionaryValue*)message
2531 context:(NSDictionary*)context {
2532 std::string pageURL;
2533 std::string baseURL;
2534 if (!message->GetString("pageUrl", &pageURL) ||
2535 !message->GetString("baseUrl", &baseURL)) {
2536 DLOG(WARNING) << "JS message parameter not found: pageUrl or baseUrl";
2539 GURL replaceURL = web::history_state_util::GetHistoryStateChangeUrl(
2540 [self currentURL], GURL(baseURL), pageURL);
2541 // UIWebView seems to choke on unicode characters that haven't been
2542 // escaped; escape the URL now so the expected load URL is correct.
2543 replaceURL = URLEscapedForHistory(replaceURL);
2544 if (!replaceURL.is_valid())
2546 const NavigationManagerImpl& navigationManager =
2547 _webStateImpl->GetNavigationManagerImpl();
2548 web::NavigationItem* navItem = [self currentNavItem];
2549 // ReplaceState happened before first navigation entry or called right
2550 // after window.open when the url is empty/not valid.
2552 (navigationManager.GetEntryCount() <= 1 && navItem->GetURL().is_empty()))
2554 if (!web::history_state_util::IsHistoryStateChangeValid(navItem->GetURL(),
2556 // A redirect may have occurred just prior to the replaceState. Check if
2557 // the URL needs to be updated.
2558 [self checkForUnexpectedURLChange];
2560 if (!web::history_state_util::IsHistoryStateChangeValid(
2561 [self currentNavItem]->GetURL(), replaceURL)) {
2562 // If the current session entry URL origin still doesn't match
2563 // replaceURL's origin, ignore the replaceState. This can happen if a
2564 // new URL is loaded just before the replaceState.
2567 std::string stateObjectJSON;
2568 if (!message->GetString("stateObject", &stateObjectJSON)) {
2569 DLOG(WARNING) << "JS message parameter not found: stateObject";
2572 NSString* stateObject = base::SysUTF8ToNSString(stateObjectJSON);
2573 _URLOnStartLoading = replaceURL;
2574 _lastRegisteredRequestURL = replaceURL;
2575 [self replaceStateWithPageURL:replaceURL stateObject:stateObject];
2576 NSString* replaceStateJS = [self javascriptToReplaceWebViewURL:replaceURL
2577 stateObjectJSON:stateObject];
2578 base::WeakNSObject<CRWWebController> weakSelf(self);
2579 [self evaluateJavaScript:replaceStateJS
2580 stringResultHandler:^(NSString*, NSError*) {
2581 if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
2583 base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
2584 [strongSelf didFinishNavigation];
2591 - (BOOL)wantsKeyboardShield {
2592 if ([self.nativeController
2593 respondsToSelector:@selector(wantsKeyboardShield)]) {
2594 return [self.nativeController wantsKeyboardShield];
2599 - (BOOL)wantsLocationBarHintText {
2600 if ([self.nativeController
2601 respondsToSelector:@selector(wantsLocationBarHintText)]) {
2602 return [self.nativeController wantsLocationBarHintText];
2607 // TODO(stuartmorgan): This method conflates document changes and URL changes;
2608 // we should be distinguishing better, and be clear about the expected
2609 // WebDelegate and WCO callbacks in each case.
2610 - (void)webPageChanged {
2611 DCHECK(_loadPhase == web::LOAD_REQUESTED);
2613 const GURL currentURL([self currentURL]);
2614 web::Referrer referrer = [self currentReferrer];
2615 // If no referrer was known in advance, record it now. (If there was one,
2616 // keep it since it will have a more accurate URL and policy than what can
2617 // be extracted from the landing page.)
2618 web::NavigationItem* currentItem = [self currentNavItem];
2619 if (!currentItem->GetReferrer().url.is_valid()) {
2620 currentItem->SetReferrer(referrer);
2623 // TODO(stuartmorgan): This shouldn't be called for hash state or
2624 // push/replaceState.
2625 [self resetDocumentSpecificState];
2627 [self didStartLoadingURL:currentURL updateHistory:YES];
2630 - (void)resetDocumentSpecificState {
2631 _lastUserInteraction.reset();
2632 _clickInProgress = NO;
2633 _lastSeenWindowID.reset([[_windowIDJSManager windowId] copy]);
2636 - (void)didStartLoadingURL:(const GURL&)url updateHistory:(BOOL)updateHistory {
2637 _loadPhase = web::PAGE_LOADING;
2638 _URLOnStartLoading = url;
2639 _displayStateOnStartLoading = self.pageDisplayState;
2641 _userInteractionRegistered = NO;
2642 _pageHasZoomed = NO;
2644 [[self sessionController] commitPendingEntry];
2645 _webStateImpl->GetRequestTracker()->StartPageLoad(
2646 url, [[self sessionController] currentEntry]);
2647 [_delegate webDidStartLoadingURL:url shouldUpdateHistory:updateHistory];
2650 - (BOOL)checkForUnexpectedURLChange {
2651 // Subclasses may override this method to check for and handle URL changes.
2656 if ([self.nativeController respondsToSelector:@selector(wasShown)]) {
2657 [self.nativeController wasShown];
2664 if ([self.nativeController respondsToSelector:@selector(wasHidden)]) {
2665 [self.nativeController wasHidden];
2669 + (BOOL)webControllerCanShow:(const GURL&)url {
2670 return web::UrlHasWebScheme(url) ||
2671 web::GetWebClient()->IsAppSpecificURL(url) ||
2672 url.SchemeIs(url::kFileScheme) || url.SchemeIs(url::kAboutScheme);
2675 - (void)setUserInteractionRegistered:(BOOL)flag {
2676 _userInteractionRegistered = flag;
2679 - (BOOL)userInteractionRegistered {
2680 return _userInteractionRegistered;
2683 - (BOOL)useDesktopUserAgent {
2684 web::NavigationItem* item = [self currentNavItem];
2685 return item && item->IsOverridingUserAgent();
2688 - (void)cachePOSTDataForRequest:(NSURLRequest*)request
2689 inSessionEntry:(CRWSessionEntry*)currentSessionEntry {
2690 NSUInteger maxPOSTDataSizeInBytes = 4096;
2691 NSString* cookieHeaderName = @"cookie";
2693 web::NavigationItemImpl* currentItem = currentSessionEntry.navigationItemImpl;
2694 DCHECK(currentItem);
2695 const bool shouldUpdateEntry =
2696 ui::PageTransitionCoreTypeIs(currentItem->GetTransitionType(),
2697 ui::PAGE_TRANSITION_FORM_SUBMIT) &&
2698 ![request HTTPBodyStream] && // Don't cache streams.
2699 !currentItem->HasPostData() &&
2700 currentItem->GetURL() == net::GURLWithNSURL([request URL]);
2701 const bool belowSizeCap =
2702 [[request HTTPBody] length] < maxPOSTDataSizeInBytes;
2703 DLOG_IF(WARNING, shouldUpdateEntry && !belowSizeCap)
2704 << "Data in POST request exceeds the size cap (" << maxPOSTDataSizeInBytes
2705 << " bytes), and will not be cached.";
2707 if (shouldUpdateEntry && belowSizeCap) {
2708 currentItem->SetPostData([request HTTPBody]);
2709 currentItem->ResetHttpRequestHeaders();
2710 currentItem->AddHttpRequestHeaders([request allHTTPHeaderFields]);
2711 // Don't cache the "Cookie" header.
2712 // According to NSURLRequest documentation, |-valueForHTTPHeaderField:| is
2713 // case insensitive, so it's enough to test the lower case only.
2714 if ([request valueForHTTPHeaderField:cookieHeaderName]) {
2715 // Case insensitive search in |headers|.
2716 NSSet* cookieKeys = [currentItem->GetHttpRequestHeaders()
2717 keysOfEntriesPassingTest:^(id key, id obj, BOOL* stop) {
2718 NSString* header = (NSString*)key;
2720 [header caseInsensitiveCompare:cookieHeaderName] ==
2725 DCHECK_EQ(1u, [cookieKeys count]);
2726 currentItem->RemoveHttpRequestHeaderForKey([cookieKeys anyObject]);
2731 // TODO(stuartmorgan): This is mostly logic from the original UIWebView delegate
2732 // method, which provides less information than the WKWebView version. Audit
2733 // this for things that should be handled in the subclass instead.
2734 - (BOOL)shouldAllowLoadWithRequest:(NSURLRequest*)request
2735 targetFrame:(const web::FrameInfo*)targetFrame
2736 isLinkClick:(BOOL)isLinkClick {
2737 GURL requestURL = net::GURLWithNSURL(request.URL);
2739 // Check if the request should be delayed.
2740 if (_externalRequest && _externalRequest->url == requestURL) {
2741 // Links that can't be shown in a tab by Chrome but can be handled by
2742 // external apps (e.g. tel:, mailto:) are opened directly despite the target
2743 // attribute on the link. We don't open a new tab for them because Mobile
2744 // Safari doesn't do that (and sites are expecting us to do the same) and
2745 // also because there would be nothing shown in that new tab; it would
2746 // remain on about:blank (see crbug.com/240178)
2747 if ([CRWWebController webControllerCanShow:requestURL] ||
2748 ![_delegate openExternalURL:requestURL]) {
2749 web::NewWindowInfo windowInfo = *_externalRequest;
2750 dispatch_async(dispatch_get_main_queue(), ^{
2751 [self openPopupWithInfo:windowInfo];
2754 _externalRequest.reset();
2758 BOOL shouldCheckNativeApp = [self cancellable];
2760 // Check if the link navigation leads to a launch of an external app.
2761 // TODO(shreyasv): Change this such that handling/stealing of link navigations
2762 // is delegated to the WebDelegate and the logic around external app launching
2763 // is moved there as well.
2764 if (shouldCheckNativeApp || isLinkClick) {
2765 // Check If the URL is handled by a native app.
2766 if ([self urlTriggersNativeAppLaunch:requestURL
2767 sourceURL:[self currentNavigationURL]]) {
2768 // External app has been launched successfully. Stop the current page
2769 // load operation (e.g. notifying all observers) and record the URL so
2770 // that errors reported following the 'NO' reply can be safely ignored.
2771 if ([self cancellable])
2772 [_delegate webPageOrderedClose];
2774 [_openedApplicationURL addObject:request.URL];
2779 // The WebDelegate may instruct the CRWWebController to stop loading, and
2780 // instead instruct the next page to be loaded in an animation.
2781 GURL mainDocumentURL = net::GURLWithNSURL(request.mainDocumentURL);
2782 DCHECK(self.webView);
2783 if (![self shouldOpenURL:requestURL
2784 mainDocumentURL:mainDocumentURL
2785 linkClicked:isLinkClick]) {
2789 // If the URL doesn't look like one we can show, try to open the link with an
2790 // external application.
2791 // TODO(droger): Check transition type before opening an external
2792 // application? For example, only allow it for TYPED and LINK transitions.
2793 if (![CRWWebController webControllerCanShow:requestURL]) {
2794 if (![self shouldOpenExternalURLRequest:request targetFrame:targetFrame]) {
2798 // Abort load if navigation is hapenning on the main frame. If |targetFrame|
2799 // is unknown use heuristic to guess the target frame by comparing
2800 // documentURL and navigation URL. This heuristic may have false positives.
2801 bool shouldAbortLoad = targetFrame ? targetFrame->is_main_frame
2802 : requestURL == mainDocumentURL;
2803 if (shouldAbortLoad)
2806 if ([_delegate openExternalURL:requestURL]) {
2807 // Record the URL so that errors reported following the 'NO' reply can be
2809 [_openedApplicationURL addObject:request.URL];
2810 if ([self cancellable])
2811 [_delegate webPageOrderedClose];
2816 if ([[request HTTPMethod] isEqualToString:@"POST"]) {
2817 [self cachePOSTDataForRequest:request
2818 inSessionEntry:[self currentSessionEntry]];
2824 - (void)restoreStateAfterURLRejection {
2825 [[self sessionController] discardNonCommittedEntries];
2827 // Re-register the user agent, because UIWebView will sometimes try to read
2828 // the agent again from a saved search result page in which no other page has
2829 // yet been loaded. See crbug.com/260370.
2830 [self registerUserAgent];
2832 // Reset |_lastRegisteredRequestURL| so that it reflects the URL from before
2833 // the load was rejected. This value may be out of sync because
2834 // |_lastRegisteredRequestURL| may have already been updated before the load
2836 _lastRegisteredRequestURL = [self currentURL];
2837 _loadPhase = web::PAGE_LOADING;
2838 [self didFinishNavigation];
2841 - (void)handleLoadError:(NSError*)error inMainFrame:(BOOL)inMainFrame {
2842 if ([error code] == NSURLErrorUnsupportedURL)
2844 // In cases where a Plug-in handles the load do not take any further action.
2845 if ([[error domain] isEqual:WebKitErrorDomain] &&
2846 ([error code] == WebKitErrorPlugInLoadFailed ||
2847 [error code] == WebKitErrorCannotShowURL))
2850 // Continue processing only if the error is on the main request or is the
2851 // result of a user interaction.
2852 NSDictionary* userInfo = [error userInfo];
2853 // |userinfo| contains the request creation date as a NSDate.
2854 NSTimeInterval requestCreationDate =
2855 [[userInfo objectForKey:@"CreationDate"] timeIntervalSinceReferenceDate];
2856 bool userInteracted = false;
2857 if (requestCreationDate != 0.0 && _lastUserInteraction) {
2858 NSTimeInterval timeSinceInteraction =
2859 requestCreationDate - _lastUserInteraction->time;
2860 // The error is considered to be the result of a user interaction if any
2861 // interaction happened just before the request was made.
2862 // TODO(droger): If the user interacted with the page after the request was
2863 // made (i.e. creationTimeSinceLastInteraction < 0), then
2864 // |_lastUserInteraction| has been overridden. The current behavior is to
2865 // discard the interstitial in that case. A better decision could be made if
2866 // we had a history of all the user interactions instead of just the last
2869 timeSinceInteraction < kMaximumDelayForUserInteractionInSeconds &&
2870 _lastUserInteraction->time > _lastTransferTimeInSeconds &&
2871 timeSinceInteraction >= 0.0;
2873 // If the error does not have timing information, check if the user
2874 // interacted with the page recently.
2875 userInteracted = [self userIsInteracting];
2877 if (!inMainFrame && !userInteracted)
2880 NSURL* errorURL = [NSURL
2881 URLWithString:[userInfo objectForKey:NSURLErrorFailingURLStringErrorKey]];
2882 const GURL errorGURL = net::GURLWithNSURL(errorURL);
2884 // Handles Frame Load Interrupted errors from WebView.
2885 if ([[error domain] isEqualToString:WebKitErrorDomain] &&
2886 [error code] == WebKitErrorFrameLoadInterruptedByPolicyChange) {
2887 // See if the delegate wants to handle this case.
2888 if (errorGURL.is_valid() &&
2890 respondsToSelector:@selector(
2891 controllerForUnhandledContentAtURL:)]) {
2892 id<CRWNativeContent> controller =
2893 [_delegate controllerForUnhandledContentAtURL:errorGURL];
2895 [self loadCompleteWithSuccess:NO];
2896 [self removeWebViewAllowingCachedReconstruction:NO];
2897 [self setNativeController:controller];
2898 [self loadNativeViewWithSuccess:YES];
2903 // Ignore errors that originate from URLs that are opened in external apps.
2904 if ([_openedApplicationURL containsObject:errorURL])
2906 // Certain frame errors don't have URL information for some reason; for
2907 // those cases (so far the only known case is plugin content loaded directly
2908 // in a frame) just ignore the error. See crbug.com/414295
2910 DCHECK(!inMainFrame);
2913 // The wrapper error uses the URL of the error and not the requested URL
2914 // (which can be different in case of a redirect) to match desktop Chrome
2916 NSError* wrapperError = [NSError
2917 errorWithDomain:[error domain]
2920 NSURLErrorFailingURLStringErrorKey : [errorURL absoluteString],
2921 NSUnderlyingErrorKey : error
2923 [self loadCompleteWithSuccess:NO];
2924 [self loadErrorInNativeView:wrapperError];
2928 if ([error code] == NSURLErrorCancelled) {
2929 [self handleCancelledError:error];
2930 // NSURLErrorCancelled errors that aren't handled by aborting the load will
2931 // automatically be retried by the web view, so early return in this case.
2935 [self loadCompleteWithSuccess:NO];
2936 [self loadErrorInNativeView:error];
2939 - (void)handleCancelledError:(NSError*)cancelledError {
2940 // Subclasses must implement this method.
2947 - (void)createWebUIForURL:(const GURL&)URL {
2948 _webStateImpl->CreateWebUI(URL);
2951 - (void)clearWebUI {
2952 _webStateImpl->ClearWebUI();
2956 #pragma mark UIGestureRecognizerDelegate
2958 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2959 shouldRecognizeSimultaneouslyWithGestureRecognizer:
2960 (UIGestureRecognizer*)otherGestureRecognizer {
2961 // Allows the custom UILongPressGestureRecognizer to fire simultaneously with
2962 // other recognizers.
2966 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2967 shouldReceiveTouch:(UITouch*)touch {
2968 // Expect only _contextMenuRecognizer.
2969 DCHECK([gestureRecognizer isEqual:_contextMenuRecognizer]);
2970 if (![self supportsCustomContextMenu]) {
2971 // Fetching context menu info is not a free operation, early return if a
2972 // context menu should not be shown.
2976 // This is custom long press gesture recognizer. By the time the gesture is
2977 // recognized the web controller needs to know if there is a link under the
2978 // touch. If there a link, the web controller will reject system's context
2979 // menu and show another one. If for some reason context menu info is not
2980 // fetched - system context menu will be shown.
2981 [self setDOMElementForLastTouch:nullptr];
2982 base::WeakNSObject<CRWWebController> weakSelf(self);
2983 [self fetchDOMElementAtPoint:[touch locationInView:self.webView]
2984 completionHandler:^(scoped_ptr<base::DictionaryValue> element) {
2985 [weakSelf setDOMElementForLastTouch:element.Pass()];
2990 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer {
2991 // Expect only _contextMenuRecognizer.
2992 DCHECK([gestureRecognizer isEqual:_contextMenuRecognizer]);
2993 if (!self.webView || ![self supportsCustomContextMenu]) {
2994 // Show the context menu iff currently displaying a web view.
2995 // Do nothing for native views.
2999 UMA_HISTOGRAM_BOOLEAN("WebController.FetchContextMenuInfoAsyncSucceeded",
3000 _DOMElementForLastTouch);
3002 return _DOMElementForLastTouch && !_DOMElementForLastTouch->empty();
3006 #pragma mark CRWRequestTrackerDelegate
3008 - (BOOL)isForStaticFileRequests {
3012 - (void)updatedSSLStatus:(const web::SSLStatus&)sslStatus
3013 forPageUrl:(const GURL&)url
3014 userInfo:(id)userInfo {
3015 // |userInfo| is a CRWSessionEntry.
3016 web::NavigationItem* item =
3017 [static_cast<CRWSessionEntry*>(userInfo) navigationItem];
3019 return; // This is a request update for an entry that no longer exists.
3021 // This condition happens infrequently when a page load is misinterpreted as
3022 // a resource load from a previous page. This can happen when moving quickly
3023 // back and forth through history, the notifications from the web view on the
3024 // UI thread and the one from the requests at the net layer may get out of
3025 // sync. This catches this case and prevent updating an entry with the wrong
3027 if (item->GetURL().GetOrigin() != url.GetOrigin())
3030 if (item->GetSSL().Equals(sslStatus))
3031 return; // No need to update with the same data.
3033 item->GetSSL() = sslStatus;
3035 // Notify the UI it needs to refresh if the updated entry is the current
3037 if (userInfo == self.currentSessionEntry) {
3038 [self didUpdateSSLStatusForCurrentNavigationItem];
3042 - (void)handleResponseHeaders:(net::HttpResponseHeaders*)headers
3043 requestUrl:(const GURL&)requestUrl {
3044 _webStateImpl->OnHttpResponseHeadersReceived(headers, requestUrl);
3047 - (void)presentSSLError:(const net::SSLInfo&)info
3048 forSSLStatus:(const web::SSLStatus&)status
3049 onUrl:(const GURL&)url
3050 recoverable:(BOOL)recoverable
3051 callback:(SSLErrorCallback)shouldContinue {
3053 DCHECK_EQ(url, [self currentNavigationURL]);
3054 [_delegate presentSSLError:info
3056 recoverable:recoverable
3057 callback:^(BOOL proceed) {
3059 // The interstitial will be removed during reload.
3060 [self loadCurrentURL];
3062 if (shouldContinue) {
3063 shouldContinue(proceed);
3068 - (void)updatedProgress:(float)progress {
3070 respondsToSelector:@selector(webController:didUpdateProgress:)]) {
3071 [_delegate webController:self didUpdateProgress:progress];
3075 - (void)certificateUsed:(net::X509Certificate*)certificate
3076 forHost:(const std::string&)host
3077 status:(net::CertStatus)status {
3078 [[[self sessionController] sessionCertificatePolicyManager]
3079 registerAllowedCertificate:certificate
3084 - (void)clearCertificates {
3085 [[[self sessionController] sessionCertificatePolicyManager]
3090 #pragma mark Popup handling
3092 - (void)openPopupWithInfo:(const web::NewWindowInfo&)windowInfo {
3093 const GURL url(windowInfo.url);
3094 const GURL currentURL([self currentNavigationURL]);
3095 NSString* windowName = windowInfo.window_name.get();
3096 web::Referrer referrer(currentURL, windowInfo.referrer_policy);
3097 base::WeakNSObject<CRWWebController> weakSelf(self);
3098 void (^showPopupHandler)() = ^{
3099 CRWWebController* child = [[weakSelf delegate] webPageOrderedOpen:url
3101 windowName:windowName
3103 DCHECK(!child || child.sessionController.openedByDOM);
3106 BOOL showPopup = windowInfo.user_is_interacting ||
3107 (![self shouldBlockPopupWithURL:url sourceURL:currentURL]);
3110 } else if ([_delegate
3111 respondsToSelector:@selector(webController:didBlockPopup:)]) {
3112 web::BlockedPopupInfo blockedPopupInfo(url, referrer, windowName,
3114 [_delegate webController:self didBlockPopup:blockedPopupInfo];
3119 #pragma mark TouchTracking
3121 - (void)touched:(BOOL)touched {
3122 _clickInProgress = touched;
3124 _userInteractionRegistered = YES;
3125 if (_isBeingDestroyed)
3127 const web::NavigationManagerImpl& navigationManager =
3128 self.webStateImpl->GetNavigationManagerImpl();
3129 GURL mainDocumentURL =
3130 navigationManager.GetEntryCount()
3131 ? navigationManager.GetLastCommittedItem()->GetURL()
3132 : [self currentURL];
3133 _lastUserInteraction.reset(new web::UserInteractionEvent(mainDocumentURL));
3137 - (CRWTouchTrackingRecognizer*)touchTrackingRecognizer {
3138 if (!_touchTrackingRecognizer) {
3139 _touchTrackingRecognizer.reset(
3140 [[CRWTouchTrackingRecognizer alloc] initWithDelegate:self]);
3142 return _touchTrackingRecognizer.get();
3145 - (BOOL)userIsInteracting {
3146 // If page transfer started after last click, user is deemed to be no longer
3148 if (!_lastUserInteraction ||
3149 _lastTransferTimeInSeconds > _lastUserInteraction->time) {
3152 return [self userClickedRecently];
3155 - (BOOL)userClickedRecently {
3156 if (!_lastUserInteraction)
3158 return _clickInProgress ||
3159 ((CFAbsoluteTimeGetCurrent() - _lastUserInteraction->time) <
3160 kMaximumDelayForUserInteractionInSeconds);
3163 #pragma mark Placeholder Overlay Methods
3165 - (void)addPlaceholderOverlay {
3166 if (!_overlayPreviewMode) {
3167 // Create |kSnapshotOverlayDelay| second timer to remove image with
3169 [self performSelector:@selector(removePlaceholderOverlay)
3171 afterDelay:kSnapshotOverlayDelay];
3174 // Add overlay image.
3175 _placeholderOverlayView.reset([[UIImageView alloc] init]);
3176 CGRect frame = [self visibleFrame];
3177 [_placeholderOverlayView setFrame:frame];
3178 [_placeholderOverlayView
3179 setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
3180 UIViewAutoresizingFlexibleHeight];
3181 [_placeholderOverlayView setContentMode:UIViewContentModeScaleAspectFill];
3182 [self.containerView addSubview:_placeholderOverlayView];
3184 id callback = ^(UIImage* image) {
3185 [_placeholderOverlayView setImage:image];
3187 [_delegate webController:self retrievePlaceholderOverlayImage:callback];
3189 if (!_placeholderOverlayView.get().image) {
3190 // TODO(justincohen): This is just a blank white image. Consider fading in
3191 // the snapshot when it comes in instead.
3192 // TODO(shreyasv): This is just a blank white image. Consider adding an API
3193 // so that the delegate can return something immediately for the default
3195 _placeholderOverlayView.get().image = [[self class] defaultSnapshotImage];
3199 - (void)removePlaceholderOverlay {
3200 if (!_placeholderOverlayView || _overlayPreviewMode)
3203 [NSObject cancelPreviousPerformRequestsWithTarget:self
3204 selector:@selector(removeOverlay)
3206 // Remove overlay with transition.
3207 [UIView animateWithDuration:kSnapshotOverlayTransition
3209 [_placeholderOverlayView setAlpha:0.0f];
3211 completion:^(BOOL finished) {
3212 [_placeholderOverlayView removeFromSuperview];
3213 _placeholderOverlayView.reset();
3217 - (void)setOverlayPreviewMode:(BOOL)overlayPreviewMode {
3218 _overlayPreviewMode = overlayPreviewMode;
3220 // If we were showing the preview, remove it.
3221 if (!_overlayPreviewMode && _placeholderOverlayView) {
3222 [self resetContainerView];
3223 // Reset |_placeholderOverlayView| directly instead of calling
3224 // -removePlaceholderOverlay, which removes |_placeholderOverlayView| in an
3226 [_placeholderOverlayView removeFromSuperview];
3227 _placeholderOverlayView.reset();
3228 // There are cases when resetting the contentView, above, may happen after
3229 // the web view has been created. Re-add it here, rather than
3230 // relying on a subsequent call to loadCurrentURLInWebView.
3232 [[self view] addSubview:self.webView];
3237 - (void)internalSuppressDialogs:(BOOL)suppressFlag notify:(BOOL)notifyFlag {
3238 NSString* const kSetSuppressDialogs =
3239 [NSString stringWithFormat:@"__gCrWeb.setSuppressDialogs(%d, %d);",
3240 suppressFlag, notifyFlag];
3241 [self setSuppressDialogsWithHelperScript:kSetSuppressDialogs];
3244 - (void)setPageDialogOpenPolicy:(web::PageDialogOpenPolicy)policy {
3246 case web::DIALOG_POLICY_ALLOW:
3247 [self setSuppressDialogs:NO notify:NO];
3249 case web::DIALOG_POLICY_NOTIFY_FIRST:
3250 [self setSuppressDialogs:NO notify:YES];
3252 case web::DIALOG_POLICY_SUPPRESS:
3253 [self setSuppressDialogs:YES notify:YES];
3259 - (void)setSuppressDialogs:(BOOL)suppressFlag notify:(BOOL)notifyFlag {
3260 if (self.webView && [_earlyScriptManager hasBeenInjected]) {
3261 [self internalSuppressDialogs:suppressFlag notify:notifyFlag];
3263 _setSuppressDialogsLater = suppressFlag;
3264 _setNotifyAboutDialogsLater = notifyFlag;
3269 #pragma mark Session Information
3271 - (CRWSessionController*)sessionController {
3272 return _webStateImpl
3273 ? _webStateImpl->GetNavigationManagerImpl().GetSessionController()
3277 - (CRWSessionEntry*)currentSessionEntry {
3278 return [[self sessionController] currentEntry];
3281 - (web::NavigationItem*)currentNavItem {
3282 // This goes through the legacy Session* interface rather than Navigation*
3283 // because it is itself a legacy method that should not exist, and this
3284 // avoids needing to add a GetActiveItem to NavigationManager. If/when this
3285 // method chain becomes a blocker to eliminating SessionController, the logic
3286 // can be moved here, using public NavigationManager getters. That's not
3287 // done now in order to avoid code duplication.
3288 return [[self currentSessionEntry] navigationItem];
3291 - (const GURL&)currentNavigationURL {
3292 // TODO(stuartmorgan): Fix the fact that this method doesn't have clear usage
3293 // delination that would allow changing to one of the non-deprecated URL
3295 web::NavigationItem* item = [self currentNavItem];
3296 return item ? item->GetVirtualURL() : GURL::EmptyGURL();
3299 - (ui::PageTransition)currentTransition {
3300 if ([self currentNavItem])
3301 return [self currentNavItem]->GetTransitionType();
3303 return ui::PageTransitionFromInt(0);
3306 - (web::Referrer)currentSessionEntryReferrer {
3307 web::NavigationItem* currentItem = [self currentNavItem];
3308 return currentItem ? currentItem->GetReferrer() : web::Referrer();
3311 - (NSData*)currentPOSTData {
3312 DCHECK([self currentSessionEntry]);
3313 return [self currentSessionEntry].navigationItemImpl->GetPostData();
3316 - (NSDictionary*)currentHttpHeaders {
3317 DCHECK([self currentSessionEntry]);
3318 return [self currentSessionEntry].navigationItem->GetHttpRequestHeaders();
3322 #pragma mark CRWWebViewScrollViewProxyObserver
3324 - (void)webViewScrollViewDidZoom:
3325 (CRWWebViewScrollViewProxy*)webViewScrollViewProxy {
3326 _pageHasZoomed = YES;
3330 #pragma mark Page State
3332 - (void)recordStateInHistory {
3333 // Only record the state if:
3334 // - the current NavigationItem's URL matches the current URL, and
3335 // - the user has interacted with the page.
3336 CRWSessionEntry* current = [self currentSessionEntry];
3337 if (current && [current navigationItem]->GetURL() == [self currentURL] &&
3338 _userInteractionRegistered) {
3339 [current navigationItem]->SetPageDisplayState(self.pageDisplayState);
3343 - (void)restoreStateFromHistory {
3344 CRWSessionEntry* current = [self currentSessionEntry];
3345 if ([current navigationItem])
3346 self.pageDisplayState = [current navigationItem]->GetPageDisplayState();
3349 - (web::PageDisplayState)pageDisplayState {
3350 web::PageDisplayState displayState;
3352 CGPoint scrollOffset = [self scrollPosition];
3353 displayState.scroll_state().set_offset_x(std::floor(scrollOffset.x));
3354 displayState.scroll_state().set_offset_y(std::floor(scrollOffset.y));
3355 UIScrollView* scrollView = self.webScrollView;
3356 displayState.zoom_state().set_minimum_zoom_scale(
3357 scrollView.minimumZoomScale);
3358 displayState.zoom_state().set_maximum_zoom_scale(
3359 scrollView.maximumZoomScale);
3360 displayState.zoom_state().set_zoom_scale(scrollView.zoomScale);
3362 // TODO(kkhorimoto): Handle native views.
3364 return displayState;
3367 - (void)setPageDisplayState:(web::PageDisplayState)displayState {
3368 if (!displayState.IsValid())
3371 // Page state is restored after a page load completes. If the user has
3372 // scrolled or changed the zoom scale while the page is still loading, don't
3373 // restore any state since it will confuse the user.
3374 web::PageDisplayState currentPageDisplayState = self.pageDisplayState;
3375 if (currentPageDisplayState.scroll_state().offset_x() ==
3376 _displayStateOnStartLoading.scroll_state().offset_x() &&
3377 currentPageDisplayState.scroll_state().offset_y() ==
3378 _displayStateOnStartLoading.scroll_state().offset_y() &&
3380 [self applyPageDisplayState:displayState];
3385 - (void)orientationDidChange {
3386 // When rotating, the available zoom scale range may change, zoomScale's
3387 // percentage into this range should remain constant. However, there are
3388 // two known bugs with respect to adjusting the zoomScale on rotation:
3389 // - WKWebView sometimes erroneously resets the scroll view's zoom scale to
3390 // an incorrect value ( rdar://20100815 ).
3391 // - After zooming occurs in a UIWebView that's displaying a page with a hard-
3392 // coded viewport width, the zoom will not be updated upon rotation
3393 // ( crbug.com/485055 ).
3396 web::NavigationItem* currentItem = self.currentNavItem;
3399 web::PageDisplayState displayState = currentItem->GetPageDisplayState();
3400 if (!displayState.IsValid())
3402 CGFloat zoomPercentage = (displayState.zoom_state().zoom_scale() -
3403 displayState.zoom_state().minimum_zoom_scale()) /
3404 displayState.zoom_state().GetMinMaxZoomDifference();
3405 displayState.zoom_state().set_minimum_zoom_scale(
3406 self.webScrollView.minimumZoomScale);
3407 displayState.zoom_state().set_maximum_zoom_scale(
3408 self.webScrollView.maximumZoomScale);
3409 displayState.zoom_state().set_zoom_scale(
3410 displayState.zoom_state().minimum_zoom_scale() +
3411 zoomPercentage * displayState.zoom_state().GetMinMaxZoomDifference());
3412 currentItem->SetPageDisplayState(displayState);
3413 [self applyPageDisplayState:currentItem->GetPageDisplayState()];
3416 - (void)applyPageDisplayState:(const web::PageDisplayState&)displayState {
3417 if (!displayState.IsValid())
3419 base::WeakNSObject<CRWWebController> weakSelf(self);
3420 web::PageDisplayState displayStateCopy = displayState;
3421 [self queryUserScalableProperty:^(BOOL isUserScalable) {
3422 base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
3423 [strongSelf applyPageDisplayState:displayStateCopy
3424 userScalable:isUserScalable];
3428 - (void)applyPageDisplayState:(const web::PageDisplayState&)displayState
3429 userScalable:(BOOL)isUserScalable {
3430 // Early return if |scrollState| doesn't match the current NavigationItem.
3431 // This can sometimes occur in tests, as navigation occurs programmatically
3432 // and |-applyPageScrollState:| is asynchronous.
3433 web::NavigationItem* currentItem = [self currentSessionEntry].navigationItem;
3434 if (currentItem && currentItem->GetPageDisplayState() != displayState)
3436 DCHECK(displayState.IsValid());
3437 if (isUserScalable) {
3438 [self prepareToApplyWebViewScrollZoomScale];
3439 [self applyWebViewScrollZoomScaleFromZoomState:displayState.zoom_state()];
3440 [self finishApplyingWebViewScrollZoomScale];
3442 [self applyWebViewScrollOffsetFromScrollState:displayState.scroll_state()];
3445 - (void)prepareToApplyWebViewScrollZoomScale {
3446 id webView = self.webView;
3447 if (![webView respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
3451 UIView* contentView = [webView viewForZoomingInScrollView:self.webScrollView];
3454 respondsToSelector:@selector(scrollViewWillBeginZooming:withView:)]) {
3455 [webView scrollViewWillBeginZooming:self.webScrollView
3456 withView:contentView];
3460 - (void)finishApplyingWebViewScrollZoomScale {
3461 id webView = self.webView;
3462 if ([webView respondsToSelector:@selector(scrollViewDidEndZooming:
3465 [webView respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
3466 // This correctly sets the content's frame in the scroll view to
3467 // fit the web page and upscales the content so that it isn't
3469 UIView* contentView =
3470 [webView viewForZoomingInScrollView:self.webScrollView];
3471 [webView scrollViewDidEndZooming:self.webScrollView
3472 withView:contentView
3473 atScale:self.webScrollView.zoomScale];
3477 - (void)applyWebViewScrollZoomScaleFromZoomState:
3478 (const web::PageZoomState&)zoomState {
3479 // Subclasses must implement this method.
3483 - (void)applyWebViewScrollOffsetFromScrollState:
3484 (const web::PageScrollState&)scrollState {
3485 DCHECK(scrollState.IsValid());
3486 CGPoint scrollOffset =
3487 CGPointMake(scrollState.offset_x(), scrollState.offset_y());
3488 if (_loadPhase == web::PAGE_LOADED) {
3489 // If the page is loaded, update the scroll immediately.
3490 [self.webScrollView setContentOffset:scrollOffset];
3492 // If the page isn't loaded, store the action to update the scroll
3493 // when the page finishes loading.
3494 base::WeakNSObject<UIScrollView> weakScrollView(self.webScrollView);
3495 base::scoped_nsprotocol<ProceduralBlock> action([^{
3496 [weakScrollView setContentOffset:scrollOffset];
3498 [_pendingLoadCompleteActions addObject:action];
3503 #pragma mark Web Page Features
3505 // TODO(eugenebut): move JS parsing code to a separate file.
3506 - (void)queryUserScalableProperty:(void (^)(BOOL))responseHandler {
3507 NSString* const kViewPortContentQuery =
3508 @"var viewport = document.querySelector('meta[name=\"viewport\"]');"
3509 "viewport ? viewport.content : '';";
3510 [self evaluateJavaScript:kViewPortContentQuery
3511 stringResultHandler:^(NSString* viewPortContent, NSError* error) {
3513 GetUserScalablePropertyFromViewPortContent(viewPortContent));
3517 - (void)fetchWebPageWidthWithCompletionHandler:(void (^)(CGFloat))handler {
3518 if (!self.webView) {
3523 [self evaluateJavaScript:@"__gCrWeb.getPageWidth();"
3524 stringResultHandler:^(NSString* pageWidthAsString, NSError*) {
3525 handler([pageWidthAsString floatValue]);
3529 - (void)fetchDOMElementAtPoint:(CGPoint)point
3531 (void (^)(scoped_ptr<base::DictionaryValue>))handler {
3533 // Convert point into web page's coordinate system (which may be scaled and/or
3535 CGPoint scrollOffset = self.scrollPosition;
3536 CGFloat webViewContentWidth = self.webScrollView.contentSize.width;
3537 base::WeakNSObject<CRWWebController> weakSelf(self);
3538 [self fetchWebPageWidthWithCompletionHandler:^(CGFloat pageWidth) {
3539 CGFloat scale = pageWidth / webViewContentWidth;
3540 CGPoint localPoint = CGPointMake((point.x + scrollOffset.x) * scale,
3541 (point.y + scrollOffset.y) * scale);
3542 NSString* const kGetElementScript =
3543 [NSString stringWithFormat:@"__gCrWeb.getElementFromPoint(%g, %g);",
3544 localPoint.x, localPoint.y];
3545 [weakSelf evaluateJavaScript:kGetElementScript
3546 JSONResultHandler:^(scoped_ptr<base::Value> element, NSError*) {
3547 // Release raw element and call handler with DictionaryValue.
3548 scoped_ptr<base::DictionaryValue> elementAsDict;
3550 base::DictionaryValue* elementAsDictPtr = nullptr;
3551 element.release()->GetAsDictionary(&elementAsDictPtr);
3552 // |rawElement| and |elementPtr| now point to the same
3553 // memory. |elementPtr| ownership will be transferred to
3554 // |element| scoped_ptr.
3555 elementAsDict.reset(elementAsDictPtr);
3557 handler(elementAsDict.Pass());
3562 - (NSDictionary*)contextMenuInfoForElement:(base::DictionaryValue*)element {
3564 NSMutableDictionary* mutableInfo = [NSMutableDictionary dictionary];
3565 NSString* title = nil;
3567 if (element->GetString("href", &href)) {
3568 mutableInfo[web::kContextLinkURLString] = base::SysUTF8ToNSString(href);
3570 if (linkURL.SchemeIs(url::kJavaScriptScheme)) {
3571 title = @"JavaScript";
3573 DCHECK(web::GetWebClient());
3574 const std::string& acceptLangs = web::GetWebClient()->GetAcceptLangs(
3575 self.webStateImpl->GetBrowserState());
3576 base::string16 urlText = net::FormatUrl(GURL(href), acceptLangs);
3577 title = base::SysUTF16ToNSString(urlText);
3581 if (element->GetString("src", &src)) {
3582 mutableInfo[web::kContextImageURLString] = base::SysUTF8ToNSString(src);
3584 title = base::SysUTF8ToNSString(src);
3585 if ([title hasPrefix:@"data:"])
3588 std::string titleAttribute;
3589 if (element->GetString("title", &titleAttribute))
3590 title = base::SysUTF8ToNSString(titleAttribute);
3591 std::string referrerPolicy;
3592 element->GetString("referrerPolicy", &referrerPolicy);
3593 mutableInfo[web::kContextLinkReferrerPolicy] =
3594 @([self referrerPolicyFromString:referrerPolicy]);
3596 mutableInfo[web::kContextTitle] = title;
3597 return [[mutableInfo copy] autorelease];
3601 #pragma mark Fullscreen
3603 - (CGRect)visibleFrame {
3604 CGRect frame = self.containerView.bounds;
3605 CGFloat headerHeight = [self headerHeight];
3606 frame.origin.y = headerHeight;
3607 frame.size.height -= headerHeight;
3611 - (void)optOutScrollsToTopForSubviews {
3612 NSMutableArray* stack =
3613 [NSMutableArray arrayWithArray:[self.webScrollView subviews]];
3614 while (stack.count) {
3615 UIView* current = [stack lastObject];
3616 [stack removeLastObject];
3617 [stack addObjectsFromArray:[current subviews]];
3618 if ([current isKindOfClass:[UIScrollView class]])
3619 static_cast<UIScrollView*>(current).scrollsToTop = NO;
3624 #pragma mark WebDelegate Calls
3626 - (BOOL)shouldOpenURL:(const GURL&)url
3627 mainDocumentURL:(const GURL&)mainDocumentURL
3628 linkClicked:(BOOL)linkClicked {
3629 if (![_delegate respondsToSelector:@selector(webController:
3635 return [_delegate webController:self
3637 mainDocumentURL:mainDocumentURL
3638 linkClicked:linkClicked];
3641 - (BOOL)shouldOpenExternalURLRequest:(NSURLRequest*)request
3642 targetFrame:(const web::FrameInfo*)targetFrame {
3643 // If targetFrame information is not provided, the request originated from the
3644 // main frame if (a) the request's URL matches the request's main document URL
3645 // or (b) if the current pending entry matches the request's main document
3646 // URL, as this is a redirect from an in-progress main frame load.
3647 BOOL isMainFrame = targetFrame
3648 ? targetFrame->is_main_frame
3649 : [request.URL isEqual:request.mainDocumentURL];
3650 if (!targetFrame && !isMainFrame) {
3651 web::NavigationItem* pendingItem =
3652 [self webStateImpl]->GetNavigationManager()->GetPendingItem();
3655 pendingItem->GetURL() == net::GURLWithNSURL(request.mainDocumentURL);
3659 // If the request's main document URL differs from that at the time of the
3660 // last user interaction, then the page has changed since the user last
3662 BOOL userHasInteractedWithCurrentPage =
3663 _lastUserInteraction &&
3664 net::GURLWithNSURL(request.mainDocumentURL) ==
3665 _lastUserInteraction->main_document_url;
3667 // Prevent subframe requests from opening an external URL if the user has not
3668 // interacted with the page.
3669 if (!isMainFrame && !userHasInteractedWithCurrentPage)
3672 GURL requestURL = net::GURLWithNSURL(request.URL);
3673 return [_delegate respondsToSelector:@selector(webController:
3674 shouldOpenExternalURL:)] &&
3675 [_delegate webController:self shouldOpenExternalURL:requestURL];
3678 - (BOOL)urlTriggersNativeAppLaunch:(const GURL&)url
3679 sourceURL:(const GURL&)sourceURL {
3681 [_delegate respondsToSelector:@selector(urlTriggersNativeAppLaunch:
3683 [_delegate urlTriggersNativeAppLaunch:url sourceURL:sourceURL];
3686 - (CGFloat)headerHeight {
3687 if (![_delegate respondsToSelector:@selector(headerHeightForWebController:)])
3689 return [_delegate headerHeightForWebController:self];
3692 - (BOOL)shouldBlockPopupWithURL:(const GURL&)popupURL
3693 sourceURL:(const GURL&)sourceURL {
3694 if (![_delegate respondsToSelector:@selector(webController:
3695 shouldBlockPopupWithURL:
3699 return [_delegate webController:self
3700 shouldBlockPopupWithURL:popupURL
3701 sourceURL:sourceURL];
3704 - (void)didUpdateHistoryStateWithPageURL:(const GURL&)url {
3705 _webStateImpl->GetRequestTracker()->HistoryStateChange(url);
3706 [_delegate webDidUpdateHistoryStateWithPageURL:url];
3709 - (void)didUpdateSSLStatusForCurrentNavigationItem {
3710 if ([_delegate respondsToSelector:
3712 webControllerDidUpdateSSLStatusForCurrentNavigationItem:)]) {
3713 [_delegate webControllerDidUpdateSSLStatusForCurrentNavigationItem:self];
3717 #pragma mark CRWWebControllerScripting Methods
3719 - (void)loadHTML:(NSString*)html {
3720 [self loadHTML:html forURL:GURL(url::kAboutBlankURL)];
3723 - (void)loadHTMLForCurrentURL:(NSString*)html {
3724 [self loadHTML:html forURL:self.currentURL];
3727 - (void)loadHTML:(NSString*)html forURL:(const GURL&)url {
3728 // Remove the transient content view.
3729 [self clearTransientContentView];
3731 DLOG_IF(WARNING, !self.webView)
3732 << "self.webView null while trying to load HTML";
3733 _loadPhase = web::LOAD_REQUESTED;
3734 [self loadWebHTMLString:html forURL:url];
3737 - (void)loadHTML:(NSString*)HTML forAppSpecificURL:(const GURL&)URL {
3738 CHECK(web::GetWebClient()->IsAppSpecificURL(URL));
3739 [self loadHTML:HTML forURL:URL];
3742 - (void)stopLoading {
3743 web::RecordAction(UserMetricsAction("Stop"));
3744 // Discard the pending and transient entried before notifying the tab model
3745 // observers of the change via |-abortLoad|.
3746 [[self sessionController] discardNonCommittedEntries];
3748 // If discarding the non-committed entries results in an app-specific URL,
3749 // reload it in its native view.
3750 if (!self.nativeController &&
3751 [self shouldLoadURLInNativeView:[self currentNavigationURL]]) {
3752 [self loadCurrentURLInNativeView];
3756 - (void)orderClose {
3757 if (self.sessionController.openedByDOM) {
3758 [_delegate webPageOrderedClose];
3763 #pragma mark Testing-Only Methods
3765 - (void)injectWebViewContentView:(id)webViewContentView {
3766 [self removeWebViewAllowingCachedReconstruction:NO];
3768 _lastRegisteredRequestURL = _defaultURL;
3769 [self.containerView displayWebViewContentView:webViewContentView];
3772 - (void)resetInjectedWebViewContentView {
3773 [self resetWebView];
3774 [self resetContainerView];
3777 - (void)addObserver:(id<CRWWebControllerObserver>)observer {
3780 // We don't want our observer set to block dealloc on the observers. For the
3781 // observer container, make an object compatible with NSMutableSet that does
3782 // not perform retain or release on the contained objects (weak references).
3783 CFSetCallBacks callbacks =
3784 {0, NULL, NULL, CFCopyDescription, CFEqual, CFHash};
3785 _observers.reset(base::mac::CFToNSCast(
3786 CFSetCreateMutable(kCFAllocatorDefault, 1, &callbacks)));
3788 DCHECK(![_observers containsObject:observer]);
3789 [_observers addObject:observer];
3790 _observerBridges.push_back(
3791 new web::WebControllerObserverBridge(observer, self.webStateImpl, self));
3793 if ([observer respondsToSelector:@selector(setWebViewProxy:controller:)])
3794 [observer setWebViewProxy:_webViewProxy controller:self];
3797 - (void)removeObserver:(id<CRWWebControllerObserver>)observer {
3798 // TODO(jimblackler): make _observers use NSMapTable. crbug.com/367992
3799 DCHECK([_observers containsObject:observer]);
3800 [_observers removeObject:observer];
3801 // Remove the associated WebControllerObserverBridge.
3802 auto it = std::find_if(_observerBridges.begin(), _observerBridges.end(),
3803 [observer](web::WebControllerObserverBridge* bridge) {
3804 return bridge->web_controller_observer() == observer;
3806 DCHECK(it != _observerBridges.end());
3807 _observerBridges.erase(it);
3810 - (NSUInteger)observerCount {
3811 DCHECK_EQ(_observerBridges.size(), [_observers count]);
3812 return [_observers count];
3815 - (NSString*)windowId {
3816 return [_windowIDJSManager windowId];
3819 - (void)setWindowId:(NSString*)windowId {
3820 return [_windowIDJSManager setWindowId:windowId];
3823 - (NSString*)lastSeenWindowID {
3824 return _lastSeenWindowID;
3827 - (void)setURLOnStartLoading:(const GURL&)url {
3828 _URLOnStartLoading = url;
3831 - (const GURL&)defaultURL {
3835 - (GURL)URLOnStartLoading {
3836 return _URLOnStartLoading;
3839 - (GURL)lastRegisteredRequestURL {
3840 return _lastRegisteredRequestURL;
3843 - (void)simulateLoadRequestWithURL:(const GURL&)URL {
3844 _lastRegisteredRequestURL = URL;
3845 _loadPhase = web::LOAD_REQUESTED;
3848 - (NSString*)externalRequestWindowName {
3849 if (!_externalRequest || !_externalRequest->window_name)
3851 return _externalRequest->window_name;
3854 - (void)resetExternalRequest {
3855 _externalRequest.reset();