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 #include "base/ios/weak_nsobject.h"
12 #include "base/json/json_reader.h"
13 #include "base/json/json_writer.h"
14 #include "base/json/string_escape.h"
15 #include "base/logging.h"
16 #include "base/mac/bundle_locations.h"
17 #include "base/mac/foundation_util.h"
18 #include "base/mac/objc_property_releaser.h"
19 #include "base/mac/scoped_cftyperef.h"
20 #include "base/mac/scoped_nsobject.h"
21 #include "base/memory/scoped_ptr.h"
22 #include "base/metrics/histogram.h"
23 #include "base/metrics/user_metrics_action.h"
24 #include "base/prefs/pref_service.h"
25 #include "base/strings/string_util.h"
26 #include "base/strings/sys_string_conversions.h"
27 #include "base/strings/utf_string_conversions.h"
28 #include "base/time/time.h"
29 #include "base/values.h"
30 #import "ios/net/nsurlrequest_util.h"
31 #include "ios/public/provider/web/web_ui_ios.h"
32 #import "ios/web/history_state_util.h"
33 #include "ios/web/interstitials/web_interstitial_impl.h"
34 #import "ios/web/navigation/crw_session_certificate_policy_manager.h"
35 #import "ios/web/navigation/crw_session_controller.h"
36 #import "ios/web/navigation/crw_session_entry.h"
37 #import "ios/web/navigation/navigation_item_impl.h"
38 #import "ios/web/navigation/navigation_manager_impl.h"
39 #import "ios/web/navigation/web_load_params.h"
40 #include "ios/web/net/request_group_util.h"
41 #include "ios/web/public/browser_state.h"
42 #include "ios/web/public/favicon_url.h"
43 #include "ios/web/public/navigation_item.h"
44 #include "ios/web/public/referrer.h"
45 #include "ios/web/public/referrer_util.h"
46 #include "ios/web/public/ssl_status.h"
47 #include "ios/web/public/url_scheme_util.h"
48 #include "ios/web/public/url_util.h"
49 #include "ios/web/public/user_metrics.h"
50 #include "ios/web/public/web_client.h"
51 #include "ios/web/public/web_state/credential.h"
52 #import "ios/web/public/web_state/crw_native_content.h"
53 #import "ios/web/public/web_state/crw_native_content_provider.h"
54 #import "ios/web/public/web_state/crw_web_controller_observer.h"
55 #import "ios/web/public/web_state/crw_web_view_scroll_view_proxy.h"
56 #import "ios/web/public/web_state/js/crw_js_injection_manager.h"
57 #import "ios/web/public/web_state/js/crw_js_injection_receiver.h"
58 #include "ios/web/public/web_state/url_verification_constants.h"
59 #include "ios/web/public/web_state/web_state.h"
60 #include "ios/web/web_state/blocked_popup_info.h"
61 #import "ios/web/web_state/crw_web_view_proxy_impl.h"
62 #import "ios/web/web_state/error_translation_util.h"
63 #include "ios/web/web_state/frame_info.h"
64 #import "ios/web/web_state/js/credential_util.h"
65 #import "ios/web/web_state/js/crw_js_early_script_manager.h"
66 #import "ios/web/web_state/js/crw_js_plugin_placeholder_manager.h"
67 #import "ios/web/web_state/js/crw_js_window_id_manager.h"
68 #import "ios/web/web_state/ui/crw_context_menu_provider.h"
69 #import "ios/web/web_state/ui/crw_swipe_recognizer_provider.h"
70 #import "ios/web/web_state/ui/crw_ui_web_view_web_controller.h"
71 #import "ios/web/web_state/ui/crw_web_controller+protected.h"
72 #import "ios/web/web_state/ui/crw_web_controller_container_view.h"
73 #import "ios/web/web_state/ui/crw_wk_web_view_web_controller.h"
74 #import "ios/web/web_state/web_controller_observer_bridge.h"
75 #include "ios/web/web_state/web_state_facade_delegate.h"
76 #import "ios/web/web_state/web_state_impl.h"
77 #import "net/base/mac/url_conversions.h"
78 #include "net/base/net_errors.h"
79 #include "net/base/net_util.h"
80 #import "ui/base/ios/cru_context_menu_holder.h"
81 #include "ui/base/page_transition_types.h"
83 #include "url/url_constants.h"
85 using base::UserMetricsAction;
86 using web::NavigationManagerImpl;
88 using web::WebStateImpl;
92 NSString* const kContainerViewID = @"Container View";
93 const char* kWindowNameSeparator = "#";
94 NSString* const kUserIsInteractingKey = @"userIsInteracting";
95 NSString* const kOriginURLKey = @"originURL";
96 NSString* const kLogJavaScript = @"LogJavascript";
98 NewWindowInfo::NewWindowInfo(GURL target_url,
99 NSString* target_window_name,
100 web::ReferrerPolicy target_referrer_policy,
101 bool target_user_is_interacting)
103 window_name([target_window_name copy]),
104 referrer_policy(target_referrer_policy),
105 user_is_interacting(target_user_is_interacting) {
108 NewWindowInfo::~NewWindowInfo() {
115 // A tag for the web view, so that tests can identify it. This is used instead
116 // of exposing a getter (and deliberately not exposed in the header) to make it
117 // *very* clear that this is a hack which should only be used as a last resort.
118 const NSUInteger kWebViewTag = 0x3eb71e3;
120 // Cancels touch events for the given gesture recognizer.
121 void CancelTouches(UIGestureRecognizer* gesture_recognizer) {
122 if (gesture_recognizer.enabled) {
123 gesture_recognizer.enabled = NO;
124 gesture_recognizer.enabled = YES;
128 // Cancels all touch events for web view (long presses, tapping, scrolling).
129 void CancelAllTouches(UIScrollView* web_scroll_view) {
130 // Disable web view scrolling.
131 CancelTouches(web_scroll_view.panGestureRecognizer);
133 // All user gestures are handled by a subview of web view scroll view
134 // (UIWebBrowserView for UIWebView and WKContentView for WKWebView).
135 for (UIView* subview in web_scroll_view.subviews) {
136 for (UIGestureRecognizer* recognizer in subview.gestureRecognizers) {
137 CancelTouches(recognizer);
144 @interface CRWWebController () <CRWNativeContentDelegate> {
145 base::WeakNSProtocol<id<CRWWebDelegate>> _delegate;
146 base::WeakNSProtocol<id<CRWWebUserInterfaceDelegate>> _UIDelegate;
147 base::WeakNSProtocol<id<CRWNativeContentProvider>> _nativeProvider;
148 base::WeakNSProtocol<id<CRWSwipeRecognizerProvider>> _swipeRecognizerProvider;
149 base::scoped_nsobject<CRWWebControllerContainerView> _containerView;
150 // The CRWWebViewProxy is the wrapper to give components access to the
151 // web view in a controlled and limited way.
152 base::scoped_nsobject<CRWWebViewProxyImpl> _webViewProxy;
153 // If |_contentView| contains a native view rather than a web view, this
154 // is its controller. If it's a web view, this is nil.
155 base::scoped_nsprotocol<id<CRWNativeContent>> _nativeController;
156 BOOL _isHalted; // YES if halted. Halting happens prior to destruction.
157 BOOL _isBeingDestroyed; // YES if in the process of closing.
158 // All CRWWebControllerObservers attached to the CRWWebController. A
159 // specially-constructed set is used that does not retain its elements.
160 base::scoped_nsobject<NSMutableSet> _observers;
161 // Each observer in |_observers| is associated with a
162 // WebControllerObserverBridge in order to listen from WebState callbacks.
163 // TODO(droger): Remove |_observerBridges| when all CRWWebControllerObservers
164 // are converted to WebStateObservers.
165 ScopedVector<web::WebControllerObserverBridge> _observerBridges;
166 // |windowId| that is saved when a page changes. Used to detect refreshes.
167 base::scoped_nsobject<NSString> _lastSeenWindowID;
168 // YES if a user interaction has been registered at any time once the page has
170 BOOL _userInteractionRegistered;
171 // Last URL change reported to webWill/DidStartLoadingURL. Used to detect page
172 // location changes (client redirects) in practice.
173 GURL _lastRegisteredRequestURL;
174 // Last URL change reported to webDidStartLoadingURL. Used to detect page
175 // location changes in practice.
176 GURL _URLOnStartLoading;
177 // Page loading phase.
178 web::LoadPhase _loadPhase;
179 // The web::PageScrollState recorded when the page starts loading.
180 web::PageScrollState _scrollStateOnStartLoading;
181 // Actions to execute once the page load is complete.
182 base::scoped_nsobject<NSMutableArray> _pendingLoadCompleteActions;
183 // UIGestureRecognizers to add to the web view.
184 base::scoped_nsobject<NSMutableArray> _gestureRecognizers;
185 // Toolbars to add to the web view.
186 base::scoped_nsobject<NSMutableArray> _webViewToolbars;
187 // Flag to say if browsing is enabled.
188 BOOL _webUsageEnabled;
189 // Content view was reset due to low memory. Use the placeholder overlay on
191 BOOL _usePlaceholderOverlay;
192 // Overlay view used instead of webView.
193 base::scoped_nsobject<UIImageView> _placeholderOverlayView;
194 // The touch tracking recognizer allowing us to decide if a navigation is
195 // started by the user.
196 base::scoped_nsobject<CRWTouchTrackingRecognizer> _touchTrackingRecognizer;
197 // Long press recognizer that allows showing context menus.
198 base::scoped_nsobject<UILongPressGestureRecognizer> _contextMenuRecognizer;
199 // DOM element information for the point where the user made the last touch.
200 // Can be null if has not been calculated yet. Precalculation is necessary
201 // because retreiving DOM element relies on async API so element info can not
202 // be built on demand. May contain the following keys: "href", "src", "title",
203 // "referrerPolicy". All values are strings. Used for showing context menus.
204 scoped_ptr<base::DictionaryValue> _DOMElementForLastTouch;
205 // Whether a click is in progress.
206 BOOL _clickInProgress;
207 // The time of the last click, measured in seconds since Jan 1 2001.
208 CFAbsoluteTime _lastClickTimeInSeconds;
209 // The time of the last page transfer start, measured in seconds since Jan 1
211 CFAbsoluteTime _lastTransferTimeInSeconds;
212 // Default URL (about:blank).
214 // Show overlay view, don't reload web page.
215 BOOL _overlayPreviewMode;
216 // If |YES|, call setSuppressDialogs when core.js is injected into the web
218 BOOL _setSuppressDialogsLater;
219 // If |YES|, call setSuppressDialogs when core.js is injected into the web
221 BOOL _setNotifyAboutDialogsLater;
222 // The URL of an expected future recreation of the |webView|. Valid
223 // only if the web view was discarded for non-user-visible reasons, such that
224 // if the next load request is for that URL, it should be treated as a
225 // reconstruction that should use cache aggressively.
226 GURL _expectedReconstructionURL;
228 scoped_ptr<web::NewWindowInfo> _externalRequest;
230 // The WebStateImpl instance associated with this CRWWebController.
231 scoped_ptr<WebStateImpl> _webStateImpl;
233 // A set of URLs opened in external applications; stored so that errors
234 // from the web view can be identified as resulting from these events.
235 base::scoped_nsobject<NSMutableSet> _openedApplicationURL;
237 // Object that manages all early script injection into the web view.
238 base::scoped_nsobject<CRWJSEarlyScriptManager> _earlyScriptManager;
240 // Script manager for setting the windowID.
241 base::scoped_nsobject<CRWJSWindowIdManager> _windowIDJSManager;
243 // The receiver of JavaScripts.
244 base::scoped_nsobject<CRWJSInjectionReceiver> _jsInjectionReceiver;
247 // The current page state of the web view. Writing to this property
248 // asynchronously applies the passed value to the current web view.
249 @property(nonatomic, readwrite) web::PageScrollState pageScrollState;
250 // Resets any state that is associated with a specific document object (e.g.,
251 // page interaction tracking).
252 - (void)resetDocumentSpecificState;
253 // Returns YES if the URL looks like it is one CRWWebController can show.
254 + (BOOL)webControllerCanShow:(const GURL&)url;
255 // Clear any interstitials being displayed.
256 - (void)clearInterstitials;
257 // Returns a lazily created CRWTouchTrackingRecognizer.
258 - (CRWTouchTrackingRecognizer*)touchTrackingRecognizer;
259 // Shows placeholder overlay.
260 - (void)addPlaceholderOverlay;
261 // Removes placeholder overlay.
262 - (void)removePlaceholderOverlay;
263 // Returns |YES| if |url| should be loaded in a native view.
264 - (BOOL)shouldLoadURLInNativeView:(const GURL&)url;
265 // Loads the HTML into the page at the given URL.
266 - (void)loadHTML:(NSString*)html forURL:(const GURL&)url;
267 // Loads the current nativeController in a native view. If a web view is
268 // present, removes it and swaps in the native view in its place.
269 - (void)loadNativeViewWithSuccess:(BOOL)loadSuccess;
270 // YES if the navigation to |url| should be treated as a reload.
271 - (BOOL)shouldReload:(const GURL&)destinationURL
272 transition:(ui::PageTransition)transition;
273 // Internal implementation of reload. Reloads without notifying the delegate.
274 // Most callers should use -reload instead.
275 - (void)reloadInternal;
276 // If YES, the page can be closed if the loading of the initial URL requires
277 // it (for example when an external URL is detected). After the initial URL is
278 // loaded, the page is not cancellable anymore.
280 // Called after URL is finished loading and _loadPhase is set to PAGE_LOADED.
281 - (void)didFinishWithURL:(const GURL&)currentURL loadSuccess:(BOOL)loadSuccess;
282 // Informs the native controller if web usage is allowed or not.
283 - (void)setNativeControllerWebUsageEnabled:(BOOL)webUsageEnabled;
284 // Compares the two URLs being navigated between during a history navigation to
285 // determine if a # needs to be appended to endURL to trigger a hashchange
286 // event. If so, also saves the new endURL in the current CRWSessionEntry.
287 - (GURL)updateURLForHistoryNavigationFromURL:(const GURL&)startURL
288 toURL:(const GURL&)endURL;
289 // Evaluates the supplied JavaScript in the web view. Calls |handler| with
290 // results of the evaluation (which may be nil if the implementing object has no
291 // way to run the evaluation or the evaluation returns a nil value) or an
292 // NSError if there is an error. The |handler| can be nil.
293 - (void)evaluateJavaScript:(NSString*)script
294 JSONResultHandler:(void (^)(scoped_ptr<base::Value>, NSError*))handler;
295 // Generates the JavaScript string used to update the UIWebView's URL so that it
296 // matches the URL displayed in the omnibox and sets window.history.state to
297 // stateObject. Needed for history.pushState() and history.replaceState().
298 - (NSString*)javascriptToReplaceWebViewURL:(const GURL&)url
299 stateObjectJSON:(NSString*)stateObject;
301 // Restores state of the web view's scroll view from |scrollState|.
302 // |isUserScalable| represents the value of user-scalable meta tag.
303 - (void)applyPageScrollState:(const web::PageScrollState&)scrollState
304 userScalable:(BOOL)isUserScalable;
305 // Calls the zoom-preparation UIScrollViewDelegate callbacks on the web view.
306 // This is called before |-applyWebViewScrollZoomScaleFromScrollState:|.
307 - (void)prepareToApplyWebViewScrollZoomScale;
308 // Calls the zoom-completion UIScrollViewDelegate callbacks on the web view.
309 // This is called after |-applyWebViewScrollZoomScaleFromScrollState:|.
310 - (void)finishApplyingWebViewScrollZoomScale;
311 // Sets scroll offset value for webview scroll view from |scrollState|.
312 - (void)applyWebViewScrollOffsetFromScrollState:
313 (const web::PageScrollState&)scrollState;
314 // Asynchronously determines whether option |user-scalable| is on in the
315 // viewport meta of the current web page.
316 - (void)queryUserScalableProperty:(void (^)(BOOL))responseHandler;
317 // Asynchronously fetches full width of the rendered web page.
318 - (void)fetchWebPageWidthWithCompletionHandler:(void (^)(CGFloat))handler;
319 // Asynchronously fetches information about DOM element for the given point (in
320 // UIView coordinates). |handler| can not be nil. See |_DOMElementForLastTouch|
321 // for element format description.
322 - (void)fetchDOMElementAtPoint:(CGPoint)point
324 (void (^)(scoped_ptr<base::DictionaryValue>))handler;
325 // Extracts context menu information from the given DOM element.
326 // result keys are defined in crw_context_menu_provider.h.
327 - (NSDictionary*)contextMenuInfoForElement:(base::DictionaryValue*)element;
328 // Sets the value of |_DOMElementForLastTouch|.
329 - (void)setDOMElementForLastTouch:(scoped_ptr<base::DictionaryValue>)element;
330 // Called when the window has determined there was a long-press and context menu
332 - (void)showContextMenu:(UIGestureRecognizer*)gestureRecognizer;
333 // YES if delegate supports showing context menu by responding to
334 // webController:runContextMenu:atPoint:inView: selector.
335 - (BOOL)supportsCustomContextMenu;
336 // Returns the referrer for the current page.
337 - (web::Referrer)currentReferrer;
338 // Presents an error to the user because the CRWWebController cannot verify the
339 // URL of the current page.
340 - (void)presentSpoofingError;
341 // Adds a new CRWSessionEntry with the given URL and state object to the history
342 // stack. A state object is a serialized generic JavaScript object that contains
343 // details of the UI's state for a given CRWSessionEntry/URL.
344 // TODO(stuartmorgan): Move the pushState/replaceState logic into
345 // NavigationManager.
346 - (void)pushStateWithPageURL:(const GURL&)pageUrl
347 stateObject:(NSString*)stateObject;
348 // Assigns the given URL and state object to the current CRWSessionEntry.
349 - (void)replaceStateWithPageURL:(const GURL&)pageUrl
350 stateObject:(NSString*)stateObject;
352 // Returns the current entry from the underlying session controller.
353 // TODO(stuartmorgan): Audit all calls to these methods; these are just wrappers
354 // around the same logic as GetActiveEntry, so should probably not be used for
355 // the same reason that GetActiveEntry is deprecated. (E.g., page operations
356 // should generally be dealing with the last commited entry, not a pending
358 - (CRWSessionEntry*)currentSessionEntry;
359 - (web::NavigationItem*)currentNavItem;
360 // Returns the referrer for currentURL as a string. May return nil.
361 - (web::Referrer)currentSessionEntryReferrer;
362 // The data and HTTP headers associated to the current entry. These are nil
363 // unless the request was a POST.
364 - (NSData*)currentPOSTData;
365 - (NSDictionary*)currentHttpHeaders;
367 // Finds all the scrollviews in the view hierarchy and makes sure they do not
368 // interfere with scroll to top when tapping the statusbar.
369 - (void)optOutScrollsToTopForSubviews;
370 // Tears down the old native controller, and then replaces it with the new one.
371 - (void)setNativeController:(id<CRWNativeContent>)nativeController;
372 // Returns whether |url| should be opened.
373 - (BOOL)shouldOpenURL:(const GURL&)url
374 mainDocumentURL:(const GURL&)mainDocumentURL
375 linkClicked:(BOOL)linkClicked;
376 // Called when |url| needs to be opened in a matching native app.
377 // Returns YES if the url was succesfully opened in the native app.
378 - (BOOL)urlTriggersNativeAppLaunch:(const GURL&)url
379 sourceURL:(const GURL&)sourceURL;
380 // Returns whether external |url| should be opened.
381 - (BOOL)shouldOpenExternalURL:(const GURL&)url;
382 // Called when a page updates its history stack using pushState or replaceState.
383 - (void)didUpdateHistoryStateWithPageURL:(const GURL&)url;
385 // Handlers for JavaScript messages. |message| contains a JavaScript command and
386 // data relevant to the message, and |context| contains contextual information
387 // about web view state needed for some handlers.
389 // Handles 'addPluginPlaceholders' message.
390 - (BOOL)handleAddPluginPlaceholdersMessage:(base::DictionaryValue*)message
391 context:(NSDictionary*)context;
392 // Handles 'chrome.send' message.
393 - (BOOL)handleChromeSendMessage:(base::DictionaryValue*)message
394 context:(NSDictionary*)context;
395 // Handles 'console' message.
396 - (BOOL)handleConsoleMessage:(base::DictionaryValue*)message
397 context:(NSDictionary*)context;
398 // Handles 'dialog.suppressed' message.
399 - (BOOL)handleDialogSuppressedMessage:(base::DictionaryValue*)message
400 context:(NSDictionary*)context;
401 // Handles 'dialog.willShow' message.
402 - (BOOL)handleDialogWillShowMessage:(base::DictionaryValue*)message
403 context:(NSDictionary*)context;
404 // Handles 'document.favicons' message.
405 - (BOOL)handleDocumentFaviconsMessage:(base::DictionaryValue*)message
406 context:(NSDictionary*)context;
407 // Handles 'document.submit' message.
408 - (BOOL)handleDocumentSubmitMessage:(base::DictionaryValue*)message
409 context:(NSDictionary*)context;
410 // Handles 'externalRequest' message.
411 - (BOOL)handleExternalRequestMessage:(base::DictionaryValue*)message
412 context:(NSDictionary*)context;
413 // Handles 'form.activity' message.
414 - (BOOL)handleFormActivityMessage:(base::DictionaryValue*)message
415 context:(NSDictionary*)context;
416 // Handles 'form.requestAutocomplete' message.
417 - (BOOL)handleFormRequestAutocompleteMessage:(base::DictionaryValue*)message
418 context:(NSDictionary*)context;
419 // Handles 'navigator.credentials.request' message.
420 - (BOOL)handleCredentialsRequestedMessage:(base::DictionaryValue*)message
421 context:(NSDictionary*)context;
422 // Handles 'navigator.credentials.notifySignedIn' message.
423 - (BOOL)handleSignedInMessage:(base::DictionaryValue*)message
424 context:(NSDictionary*)context;
425 // Handles 'navigator.credentials.notifySignedOut' message.
426 - (BOOL)handleSignedOutMessage:(base::DictionaryValue*)message
427 context:(NSDictionary*)context;
428 // Handles 'navigator.credentials.notifyFailedSignIn' message.
429 - (BOOL)handleSignInFailedMessage:(base::DictionaryValue*)message
430 context:(NSDictionary*)context;
431 // Handles 'resetExternalRequest' message.
432 - (BOOL)handleResetExternalRequestMessage:(base::DictionaryValue*)message
433 context:(NSDictionary*)context;
434 // Handles 'window.close.self' message.
435 - (BOOL)handleWindowCloseSelfMessage:(base::DictionaryValue*)message
436 context:(NSDictionary*)context;
437 // Handles 'window.error' message.
438 - (BOOL)handleWindowErrorMessage:(base::DictionaryValue*)message
439 context:(NSDictionary*)context;
440 // Handles 'window.hashchange' message.
441 - (BOOL)handleWindowHashChangeMessage:(base::DictionaryValue*)message
442 context:(NSDictionary*)context;
443 // Handles 'window.history.back' message.
444 - (BOOL)handleWindowHistoryBackMessage:(base::DictionaryValue*)message
445 context:(NSDictionary*)context;
446 // Handles 'window.history.forward' message.
447 - (BOOL)handleWindowHistoryForwardMessage:(base::DictionaryValue*)message
448 context:(NSDictionary*)context;
449 // Handles 'window.history.go' message.
450 - (BOOL)handleWindowHistoryGoMessage:(base::DictionaryValue*)message
451 context:(NSDictionary*)context;
456 NSString* const kReferrerHeaderName = @"Referer"; // [sic]
458 // Full screen experimental setting.
460 // The long press detection duration must be shorter than the UIWebView's
461 // long click gesture recognizer's minimum duration. That is 0.55s.
462 // If our detection duration is shorter, our gesture recognizer will fire
463 // first, and if it fails the long click gesture (processed simultaneously)
464 // still is able to complete.
465 const NSTimeInterval kLongPressDurationSeconds = 0.55 - 0.1;
466 const CGFloat kLongPressMoveDeltaPixels = 10.0;
468 // The duration of the period following a screen touch during which the user is
469 // still considered to be interacting with the page.
470 const NSTimeInterval kMaximumDelayForUserInteractionInSeconds = 2;
472 // Define missing symbols from WebKit.
473 // See WebKitErrors.h on Mac SDK.
474 NSString* const WebKitErrorDomain = @"WebKitErrorDomain";
477 WebKitErrorCannotShowMIMEType = 100,
478 WebKitErrorCannotShowURL = 101,
479 WebKitErrorFrameLoadInterruptedByPolicyChange = 102,
480 // iOS-specific WebKit error that isn't documented but seen on 4.0
482 WebKitErrorPlugInLoadFailed = 204,
485 // Tag for the interstitial view so we can find it and dismiss it later.
487 kInterstitialViewTag = 1000,
490 // URLs that are fed into UIWebView as history push/replace get escaped,
491 // potentially changing their format. Code that attempts to determine whether a
492 // URL hasn't changed can be confused by those differences though, so method
493 // will round-trip a URL through the escaping process so that it can be adjusted
494 // pre-storing, to allow later comparisons to work as expected.
495 GURL URLEscapedForHistory(const GURL& url) {
496 // TODO(stuartmorgan): This is a very large hammer; see if limited unicode
497 // escaping would be sufficient.
498 return net::GURLWithNSURL(net::NSURLWithGURL(url));
501 // Parses a viewport tag content and returns the value of an attribute with
502 // the given |name|, or nil if the attribute is not present in the tag.
503 NSString* GetAttributeValueFromViewPortContent(NSString* attributeName,
504 NSString* viewPortContent) {
505 NSArray* contentItems = [viewPortContent componentsSeparatedByString:@","];
506 for (NSString* item in contentItems) {
507 NSArray* components = [item componentsSeparatedByString:@"="];
508 if ([components count] == 2) {
509 NSCharacterSet* spaceAndNewline =
510 [NSCharacterSet whitespaceAndNewlineCharacterSet];
511 NSString* currentAttributeName =
512 [components[0] stringByTrimmingCharactersInSet:spaceAndNewline];
513 if ([currentAttributeName isEqualToString:attributeName]) {
514 return [components[1] stringByTrimmingCharactersInSet:spaceAndNewline];
521 // Parses a viewport tag content and returns the value of the user-scalable
523 BOOL GetUserScalablePropertyFromViewPortContent(NSString* viewPortContent) {
525 GetAttributeValueFromViewPortContent(@"user-scalable", viewPortContent);
529 return !([value isEqualToString:@"0"] ||
530 [value caseInsensitiveCompare:@"no"] == NSOrderedSame);
533 // Leave snapshot overlay up unless page loads.
534 const NSTimeInterval kSnapshotOverlayDelay = 1.5;
535 // Transition to fade snapshot overlay.
536 const NSTimeInterval kSnapshotOverlayTransition = 0.5;
540 @implementation CRWWebController
542 @synthesize webUsageEnabled = _webUsageEnabled;
543 @synthesize usePlaceholderOverlay = _usePlaceholderOverlay;
544 @synthesize loadPhase = _loadPhase;
546 // Implemented by subclasses.
547 @dynamic keyboardDisplayRequiresUserAction;
549 + (instancetype)allocWithZone:(struct _NSZone*)zone {
550 if (self == [CRWWebController class]) {
551 // This is an abstract class which should not be instantiated directly.
552 // Callers should create concrete subclasses instead.
556 return [super allocWithZone:zone];
559 - (instancetype)initWithWebState:(scoped_ptr<WebStateImpl>)webState {
562 _webStateImpl = webState.Pass();
563 DCHECK(_webStateImpl);
564 _webStateImpl->SetWebController(self);
565 _webStateImpl->InitializeRequestTracker(self);
566 // Load phase when no WebView present is 'loaded' because this represents
568 _loadPhase = web::PAGE_LOADED;
569 // Content area is lazily instantiated.
570 _defaultURL = GURL(url::kAboutBlankURL);
571 _jsInjectionReceiver.reset(
572 [[CRWJSInjectionReceiver alloc] initWithEvaluator:self]);
573 _earlyScriptManager.reset([(CRWJSEarlyScriptManager*)[_jsInjectionReceiver
574 instanceOfClass:[CRWJSEarlyScriptManager class]] retain]);
575 _windowIDJSManager.reset([(CRWJSWindowIdManager*)[_jsInjectionReceiver
576 instanceOfClass:[CRWJSWindowIdManager class]] retain]);
577 _lastSeenWindowID.reset();
579 [[CRWWebViewProxyImpl alloc] initWithWebController:self]);
580 _gestureRecognizers.reset([[NSMutableArray alloc] init]);
581 _webViewToolbars.reset([[NSMutableArray alloc] init]);
582 _pendingLoadCompleteActions.reset([[NSMutableArray alloc] init]);
587 - (id<CRWNativeContentProvider>)nativeProvider {
588 return _nativeProvider.get();
591 - (void)setNativeProvider:(id<CRWNativeContentProvider>)nativeProvider {
592 _nativeProvider.reset(nativeProvider);
595 - (id<CRWSwipeRecognizerProvider>)swipeRecognizerProvider {
596 return _swipeRecognizerProvider.get();
599 - (void)setSwipeRecognizerProvider:
600 (id<CRWSwipeRecognizerProvider>)swipeRecognizerProvider {
601 _swipeRecognizerProvider.reset(swipeRecognizerProvider);
604 - (WebState*)webState {
605 return _webStateImpl.get();
608 - (WebStateImpl*)webStateImpl {
609 return _webStateImpl.get();
612 // WebStateImpl will delete the interstitial page object, which will in turn
613 // remove its view from |_contentView|.
614 - (void)clearInterstitials {
615 [_webViewProxy setWebView:self.webView scrollView:self.webScrollView];
617 _webStateImpl->ClearWebInterstitialForNavigation();
620 // Attaches |interstitialView| to |_contentView|. Note that this class never
621 // explicitly removes the interstitial from |_contentView|;
622 // web::WebStateImpl::DismissWebInterstitial() takes care of that.
623 - (void)displayInterstitialView:(UIView*)interstitialView
624 withScrollView:(UIScrollView*)scrollView {
625 DCHECK(interstitialView);
627 [_webViewProxy setWebView:interstitialView scrollView:scrollView];
628 interstitialView.tag = kInterstitialViewTag;
629 [_containerView addSubview:interstitialView];
632 - (id<CRWWebDelegate>)delegate {
633 return _delegate.get();
636 - (void)setDelegate:(id<CRWWebDelegate>)delegate {
637 _delegate.reset(delegate);
638 if ([_nativeController respondsToSelector:@selector(setDelegate:)]) {
639 if ([_delegate respondsToSelector:@selector(webController:titleDidChange:)])
640 [_nativeController setDelegate:self];
642 [_nativeController setDelegate:nil];
646 - (id<CRWWebUserInterfaceDelegate>)UIDelegate {
647 return _UIDelegate.get();
650 - (void)setUIDelegate:(id<CRWWebUserInterfaceDelegate>)UIDelegate {
651 _UIDelegate.reset(UIDelegate);
654 - (NSString*)scriptByAddingWindowIDCheckForScript:(NSString*)script {
655 NSString* kTemplate = @"if (__gCrWeb['windowId'] === '%@') { %@; }";
656 return [NSString stringWithFormat:kTemplate, [self windowId], script];
659 - (void)removeWebViewAllowingCachedReconstruction:(BOOL)allowCache {
664 _expectedReconstructionURL = [self currentNavigationURL];
666 _expectedReconstructionURL = GURL();
669 [self.webView removeFromSuperview];
670 [_webViewProxy setWebView:nil scrollView:nil];
672 // Remove the web toolbars.
673 [_containerView removeAllToolbars];
677 DCHECK([NSThread isMainThread]);
678 DCHECK(_isBeingDestroyed); // 'close' must have been called already.
679 DCHECK(!self.webView);
680 _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
684 - (BOOL)runUnloadListenerBeforeClosing {
685 // There's not much that can be done since there's limited access to WebKit.
686 // Always return that it's ok to close immediately.
690 - (void)dismissKeyboard {
691 [self.webView endEditing:YES];
692 if ([_nativeController respondsToSelector:@selector(dismissKeyboard)])
693 [_nativeController dismissKeyboard];
696 - (id<CRWNativeContent>)nativeController {
697 return _nativeController.get();
700 - (void)setNativeController:(id<CRWNativeContent>)nativeController {
701 // Check for pointer equality.
702 if (_nativeController.get() == nativeController)
705 // Unset the delegate on the previous instance.
706 if ([_nativeController respondsToSelector:@selector(setDelegate:)])
707 [_nativeController setDelegate:nil];
709 _nativeController.reset([nativeController retain]);
710 [self setNativeControllerWebUsageEnabled:_webUsageEnabled];
713 // NativeControllerDelegate method, called to inform that title has changed.
714 - (void)nativeContent:(id)content titleDidChange:(NSString*)title {
715 // Responsiveness to delegate method was checked in setDelegate:.
716 [_delegate webController:self titleDidChange:title];
719 - (void)setNativeControllerWebUsageEnabled:(BOOL)webUsageEnabled {
720 if ([_nativeController respondsToSelector:@selector(setWebUsageEnabled:)]) {
721 [_nativeController setWebUsageEnabled:webUsageEnabled];
725 - (void)setWebUsageEnabled:(BOOL)enabled {
726 if (_webUsageEnabled == enabled)
728 _webUsageEnabled = enabled;
730 // WKWebView autoreleases its WKProcessPool on removal from superview.
731 // Deferring WKProcessPool deallocation may lead to issues with cookie
732 // clearing and and Browsing Data Partitioning implementation.
734 [self setNativeControllerWebUsageEnabled:_webUsageEnabled];
736 // Don't create the web view; let it be lazy created as needed.
738 [self clearInterstitials];
739 [self removeWebViewAllowingCachedReconstruction:YES];
740 _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
741 _touchTrackingRecognizer.reset();
742 _containerView.reset();
747 - (void)requirePageReconstruction {
748 [self removeWebViewAllowingCachedReconstruction:NO];
751 - (void)handleLowMemory {
752 [self removeWebViewAllowingCachedReconstruction:YES];
753 [self setNativeController:nil];
754 _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
755 _touchTrackingRecognizer.reset();
756 _containerView.reset();
757 _usePlaceholderOverlay = YES;
760 - (void)reinitializeWebViewAndReload:(BOOL)reload {
762 [self removeWebViewAllowingCachedReconstruction:NO];
764 [self loadCurrentURLInWebView];
766 // Clear the space for the web view to lazy load when needed.
767 _usePlaceholderOverlay = YES;
768 _touchTrackingRecognizer.get().touchTrackingDelegate = nil;
769 _touchTrackingRecognizer.reset();
770 _containerView.reset();
775 - (void)childWindowClosed:(NSString*)windowName {
776 // Subclasses can override this method to be informed about a closed window.
779 - (BOOL)isViewAlive {
780 return self.webView || [_nativeController isViewAlive];
783 - (BOOL)contentIsHTML {
784 return [self webViewDocumentType] == web::WEB_VIEW_DOCUMENT_TYPE_HTML;
787 // Stop doing stuff, especially network stuff. Close the request tracker.
788 - (void)terminateNetworkActivity {
792 // Cancel all outstanding perform requests, and clear anything already queued
793 // (since this may be called from within the handling loop) to prevent any
794 // asynchronous JavaScript invocation handling from continuing.
795 [NSObject cancelPreviousPerformRequestsWithTarget:self];
796 _webStateImpl->CloseRequestTracker();
799 - (void)dismissModals {
800 if ([_nativeController respondsToSelector:@selector(dismissModals)])
801 [_nativeController dismissModals];
804 // Caller must reset the delegate before calling.
806 self.nativeProvider = nil;
807 self.swipeRecognizerProvider = nil;
808 if ([_nativeController respondsToSelector:@selector(close)])
809 [_nativeController close];
811 base::scoped_nsobject<NSSet> observers([_observers copy]);
812 for (id it in observers.get()) {
813 if ([it respondsToSelector:@selector(webControllerWillClose:)])
814 [it webControllerWillClose:self];
818 [self terminateNetworkActivity];
821 DCHECK(!_isBeingDestroyed);
822 DCHECK(!_delegate); // Delegate should reset its association before closing.
823 // Mark the destruction sequence has started, in case someone else holds a
824 // strong reference and tries to continue using the tab.
825 _isBeingDestroyed = YES;
827 // Remove the web view now. Otherwise, delegate callbacks occur.
828 [self removeWebViewAllowingCachedReconstruction:NO];
830 // Tear down web ui (in case this is part of this tab) and web state now,
831 // since the timing of dealloc can't be guaranteed.
832 _webStateImpl.reset();
835 - (void)checkLinkPresenceUnderGesture:(UIGestureRecognizer*)gestureRecognizer
836 completionHandler:(void (^)(BOOL))completionHandler {
837 CGPoint webViewPoint = [gestureRecognizer locationInView:self.webView];
838 base::WeakNSObject<CRWWebController> weakSelf(self);
839 [self fetchDOMElementAtPoint:webViewPoint
840 completionHandler:^(scoped_ptr<base::DictionaryValue> element) {
843 element && element->GetString("href", &link) && link.size();
844 completionHandler(hasLink);
848 - (void)setDOMElementForLastTouch:(scoped_ptr<base::DictionaryValue>)element {
849 _DOMElementForLastTouch = element.Pass();
852 - (void)showContextMenu:(UIGestureRecognizer*)gestureRecognizer {
853 // Calling this method if [self supportsCustomContextMenu] returned NO
854 // is a programmer error.
855 DCHECK([self supportsCustomContextMenu]);
857 // We don't want ongoing notification that the long press is held.
858 if ([gestureRecognizer state] != UIGestureRecognizerStateBegan)
861 if (!_DOMElementForLastTouch || _DOMElementForLastTouch->empty())
865 [self contextMenuInfoForElement:_DOMElementForLastTouch.get()];
866 CGPoint point = [gestureRecognizer locationInView:self.webView];
868 // Cancel all touches on the web view when showing custom context menu. This
869 // will suppress the system context menu and prevent further user interactions
870 // with web view (like scrolling the content and following links). This
871 // approach is similar to UIWebView and WKWebView workflow as both call
872 // -[UIApplication _cancelAllTouches] to cancel all touch events, once the
873 // long press is detected.
874 CancelAllTouches(self.webScrollView);
875 [self.UIDelegate webController:self
878 inView:self.webView];
881 - (BOOL)supportsCustomContextMenu {
882 SEL runMenuSelector = @selector(webController:runContextMenu:atPoint:inView:);
883 return [self.UIDelegate respondsToSelector:runMenuSelector];
886 // TODO(shreyasv): This code is shared with SnapshotManager. Remove this and add
887 // it as part of WebDelegate delegate API such that a default image is returned
889 + (UIImage*)defaultSnapshotImage {
890 static UIImage* defaultImage = nil;
893 CGRect frame = CGRectMake(0, 0, 2, 2);
894 UIGraphicsBeginImageContext(frame.size);
895 [[UIColor whiteColor] setFill];
896 CGContextFillRect(UIGraphicsGetCurrentContext(), frame);
898 UIImage* result = UIGraphicsGetImageFromCurrentImageContext();
899 UIGraphicsEndImageContext();
902 [[result stretchableImageWithLeftCapWidth:1 topCapHeight:1] retain];
908 return _webStateImpl->GetNavigationManagerImpl().CanGoBack();
911 - (BOOL)canGoForward {
912 return _webStateImpl->GetNavigationManagerImpl().CanGoForward();
915 - (CGPoint)scrollPosition {
916 CGPoint position = CGPointMake(0.0, 0.0);
917 if (!self.webScrollView)
919 return self.webScrollView.contentOffset;
925 UIScrollView* scrollView = self.webScrollView;
926 return scrollView.contentOffset.y == -scrollView.contentInset.top;
929 - (void)presentSpoofingError {
930 UMA_HISTOGRAM_ENUMERATION("Web.URLVerificationFailure",
931 [self webViewDocumentType],
932 web::WEB_VIEW_DOCUMENT_TYPE_COUNT);
934 [self removeWebViewAllowingCachedReconstruction:NO];
935 [_delegate presentSpoofingError];
939 - (GURL)currentURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel {
940 DCHECK(trustLevel) << "Verification of the trustLevel state is mandatory";
942 GURL url([self webURLWithTrustLevel:trustLevel]);
943 // Web views treat all about: URLs as the same origin, which makes it
944 // possible for pages to document.write into about:<foo> pages, where <foo>
945 // can be something misleading. Report any about: URL as about:blank to
946 // prevent that. See crbug.com/326118
947 if (url.scheme() == url::kAboutScheme)
948 return GURL(url::kAboutBlankURL);
951 // Any non-web URL source is trusted.
952 *trustLevel = web::URLVerificationTrustLevel::kAbsolute;
953 if (_nativeController)
954 return [_nativeController url];
955 return [self currentNavigationURL];
959 web::URLVerificationTrustLevel trustLevel =
960 web::URLVerificationTrustLevel::kNone;
961 const GURL url([self currentURLWithTrustLevel:&trustLevel]);
963 // Check whether the spoofing warning needs to be displayed.
964 if (trustLevel == web::URLVerificationTrustLevel::kNone &&
965 ![self ignoreURLVerificationFailures]) {
966 dispatch_async(dispatch_get_main_queue(), ^{
968 DCHECK_EQ(url, [self currentNavigationURL]);
969 [self presentSpoofingError];
977 - (web::Referrer)currentReferrer {
978 // Referrer string doesn't include the fragment, so in cases where the
979 // previous URL is equal to the current referrer plus the fragment the
980 // previous URL is returned as current referrer.
981 NSString* referrerString = self.currentReferrerString;
983 // In case of an error evaluating the JavaScript simply return empty string.
984 if ([referrerString length] == 0)
985 return web::Referrer();
987 NSString* previousURLString =
988 base::SysUTF8ToNSString([self currentNavigationURL].spec());
989 // Check if the referrer is equal to the previous URL minus the hash symbol.
990 // L'#' is used to convert the char '#' to a unichar.
991 if ([previousURLString length] > [referrerString length] &&
992 [previousURLString hasPrefix:referrerString] &&
993 [previousURLString characterAtIndex:[referrerString length]] == L'#') {
994 referrerString = previousURLString;
996 // Since referrer is being extracted from the destination page, the correct
997 // policy from the origin has *already* been applied. Since the extracted URL
998 // is the post-policy value, and the source policy is no longer available,
999 // the policy is set to Always so that whatever WebKit decided to send will be
1000 // re-sent when replaying the entry.
1001 // TODO(stuartmorgan): When possible, get the real referrer and policy in
1002 // advance and use that instead. https://crbug.com/227769.
1003 return web::Referrer(GURL(base::SysNSStringToUTF8(referrerString)),
1004 web::ReferrerPolicyAlways);
1007 - (void)pushStateWithPageURL:(const GURL&)pageUrl
1008 stateObject:(NSString*)stateObject {
1009 [[self sessionController] pushNewEntryWithURL:pageUrl
1010 stateObject:stateObject];
1011 [self didUpdateHistoryStateWithPageURL:pageUrl];
1014 - (void)replaceStateWithPageURL:(const GURL&)pageUrl
1015 stateObject:(NSString*)stateObject {
1016 [[self sessionController] updateCurrentEntryWithURL:pageUrl
1017 stateObject:stateObject];
1018 [self didUpdateHistoryStateWithPageURL:pageUrl];
1021 - (void)injectEarlyInjectionScripts {
1022 DCHECK(self.webView);
1023 if (![_earlyScriptManager hasBeenInjected]) {
1024 [_earlyScriptManager inject];
1025 // If this assertion fires there has been an error parsing the core.js
1027 DCHECK([_earlyScriptManager hasBeenInjected]);
1029 [self injectWindowID];
1032 - (void)injectWindowID {
1033 if (![_windowIDJSManager hasBeenInjected]) {
1034 // If the window ID wasn't present, this is a new page.
1035 [self setPageChangeProbability:web::PAGE_CHANGE_PROBABILITY_LOW];
1036 // Default values for suppressDialogs and notifyAboutDialogs are NO,
1037 // so updating them only when necessary is a good optimization.
1038 if (_setSuppressDialogsLater || _setNotifyAboutDialogsLater) {
1039 [self setSuppressDialogs:_setSuppressDialogsLater
1040 notify:_setNotifyAboutDialogsLater];
1041 _setSuppressDialogsLater = NO;
1042 _setNotifyAboutDialogsLater = NO;
1045 [_windowIDJSManager inject];
1046 DCHECK([_windowIDJSManager hasBeenInjected]);
1050 // Set the specified recognizer to take priority over any recognizers in the
1051 // view that have a description containing the specified text fragment.
1052 + (void)requireGestureRecognizerToFail:(UIGestureRecognizer*)recognizer
1053 inView:(UIView*)view
1054 containingDescription:(NSString*)fragment {
1055 for (UIGestureRecognizer* iRecognizer in [view gestureRecognizers]) {
1056 if (iRecognizer != recognizer) {
1057 NSString* description = [iRecognizer description];
1058 if ([description rangeOfString:fragment].location != NSNotFound) {
1059 [iRecognizer requireGestureRecognizerToFail:recognizer];
1060 // requireGestureRecognizerToFail: doesn't retain the recognizer, so it
1061 // is possible for |iRecognizer| to outlive |recognizer| and end up with
1062 // a dangling pointer. Add a retaining associative reference to ensure
1063 // that the lifetimes work out.
1064 // Note that normally using the value as the key wouldn't make any
1065 // sense, but here it's fine since nothing needs to look up the value.
1066 objc_setAssociatedObject(view, recognizer, recognizer,
1067 OBJC_ASSOCIATION_RETAIN_NONATOMIC);
1073 - (void)webViewDidChange {
1074 CHECK(_webUsageEnabled) << "Tried to create a web view while suspended!";
1076 UIView* webView = self.webView;
1079 [webView setTag:kWebViewTag];
1080 [webView setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
1081 UIViewAutoresizingFlexibleHeight];
1082 [webView setBackgroundColor:[UIColor colorWithWhite:0.2 alpha:1.0]];
1084 // Create a dependency between the |webView| pan gesture and BVC side swipe
1085 // gestures. Note: This needs to be added before the longPress recognizers
1086 // below, or the longPress appears to deadlock the remaining recognizers,
1087 // thereby breaking scroll.
1088 NSSet* recognizers = [_swipeRecognizerProvider swipeRecognizers];
1089 for (UISwipeGestureRecognizer* swipeRecognizer in recognizers) {
1090 [self.webScrollView.panGestureRecognizer
1091 requireGestureRecognizerToFail:swipeRecognizer];
1094 // On iOS 4.x, there are two gesture recognizers on the UIWebView subclasses,
1095 // that have a minimum tap threshold of 0.12s and 0.75s.
1097 // My theory is that the shorter threshold recognizer performs the link
1098 // highlight (grey highlight around links when it is tapped and held) while
1099 // the longer threshold one pops up the context menu.
1101 // To override the context menu, this recognizer needs to react faster than
1102 // the 0.75s one. The below gesture recognizer is initialized with a
1103 // detection duration a little lower than that (see
1104 // kLongPressDurationSeconds). It also points the delegate to this class that
1105 // allows simultaneously operate along with the other recognizers.
1106 _contextMenuRecognizer.reset([[UILongPressGestureRecognizer alloc]
1108 action:@selector(showContextMenu:)]);
1109 [_contextMenuRecognizer setMinimumPressDuration:kLongPressDurationSeconds];
1110 [_contextMenuRecognizer setAllowableMovement:kLongPressMoveDeltaPixels];
1111 [_contextMenuRecognizer setDelegate:self];
1112 [webView addGestureRecognizer:_contextMenuRecognizer];
1113 // Certain system gesture handlers are known to conflict with our context
1114 // menu handler, causing extra events to fire when the context menu is active.
1116 // A number of solutions have been investigated. The lowest-risk solution
1117 // appears to be to recurse through the web controller's recognizers, looking
1118 // for fingerprints of the recognizers known to cause problems, which are then
1119 // de-prioritized (below our own long click handler).
1120 // Hunting for description fragments of system recognizers is undeniably
1121 // brittle for future versions of iOS. If it does break the context menu
1122 // events may leak (regressing b/5310177), but the app will otherwise work.
1124 requireGestureRecognizerToFail:_contextMenuRecognizer
1126 containingDescription:@"action=_highlightLongPressRecognized:"];
1128 // Add all additional gesture recognizers to the web view.
1129 for (UIGestureRecognizer* recognizer in _gestureRecognizers.get()) {
1130 [webView addGestureRecognizer:recognizer];
1133 webView.frame = [_containerView bounds];
1135 _URLOnStartLoading = _defaultURL;
1137 // Do final view setup.
1138 CGPoint initialOffset = CGPointMake(0, 0 - [self headerHeight]);
1139 [self.webScrollView setContentOffset:initialOffset];
1140 [_containerView addToolbars:_webViewToolbars];
1142 [_webViewProxy setWebView:self.webView scrollView:self.webScrollView];
1144 [_containerView addSubview:webView];
1147 - (CRWWebController*)createChildWebControllerWithReferrerURL:
1148 (const GURL&)referrerURL {
1149 web::Referrer referrer(referrerURL, web::ReferrerPolicyDefault);
1150 CRWWebController* result =
1151 [self.delegate webPageOrderedOpenBlankWithReferrer:referrer
1153 DCHECK(!result || result.sessionController.openedByDOM);
1157 - (BOOL)canUseViewForGeneratingOverlayPlaceholderView {
1158 return _containerView != nil;
1162 // Kick off the process of lazily creating the view and starting the load if
1163 // necessary; this creates _contentView if it doesn't exist.
1164 [self triggerPendingLoad];
1165 DCHECK(_containerView);
1166 return _containerView.get();
1169 - (id<CRWWebViewProxy>)webViewProxy {
1170 return _webViewProxy.get();
1173 - (UIView*)viewForPrinting {
1174 // TODO(ios): crbug.com/227944. Printing is not supported for native
1176 return self.webView;
1179 - (void)loadRequest:(NSMutableURLRequest*)request {
1180 // Subclasses must implement this method.
1184 - (void)registerLoadRequest:(const GURL&)requestURL
1185 referrer:(const web::Referrer&)referrer
1186 transition:(ui::PageTransition)transition {
1187 // Transfer time is registered so that further transitions within the time
1188 // envelope are not also registered as links.
1189 _lastTransferTimeInSeconds = CFAbsoluteTimeGetCurrent();
1190 // Before changing phases, the delegate should be informed that any existing
1191 // request is being cancelled before completion.
1192 [self loadCancelled];
1193 DCHECK(_loadPhase == web::PAGE_LOADED);
1195 _loadPhase = web::LOAD_REQUESTED;
1196 [self resetLoadState];
1197 _lastRegisteredRequestURL = requestURL;
1199 if (!(transition & ui::PAGE_TRANSITION_IS_REDIRECT_MASK)) {
1200 // Record state of outgoing page.
1201 [self recordStateInHistory];
1204 // If the web view had been discarded, and this request is to load that
1205 // URL again, then it's a rebuild and should use the cache.
1206 BOOL preferCache = _expectedReconstructionURL.is_valid() &&
1207 _expectedReconstructionURL == requestURL;
1209 [_delegate webWillAddPendingURL:requestURL transition:transition];
1210 // Add or update pending url.
1211 if (_webStateImpl->GetNavigationManagerImpl().GetPendingItem()) {
1212 // Update the existing pending entry.
1213 // Typically on PAGE_TRANSITION_CLIENT_REDIRECT.
1214 [[self sessionController] updatePendingEntry:requestURL];
1216 // A new session history entry needs to be created.
1217 [[self sessionController] addPendingEntry:requestURL
1219 transition:transition
1220 rendererInitiated:YES];
1222 // Update the cache mode for all the network requests issued by this web view.
1223 // The mode is reset to CACHE_NORMAL after each page load.
1224 if (_webStateImpl->GetCacheMode() != net::RequestTracker::CACHE_NORMAL) {
1225 _webStateImpl->GetRequestTracker()->SetCacheModeFromUIThread(
1226 _webStateImpl->GetCacheMode());
1227 } else if (preferCache) {
1228 _webStateImpl->GetRequestTracker()->SetCacheModeFromUIThread(
1229 net::RequestTracker::CACHE_HISTORY);
1231 _webStateImpl->SetIsLoading(true);
1232 [_delegate webDidAddPendingURL];
1233 _webStateImpl->OnProvisionalNavigationStarted(requestURL);
1236 - (NSString*)javascriptToReplaceWebViewURL:(const GURL&)url
1237 stateObjectJSON:(NSString*)stateObject {
1239 base::EscapeJSONString(url.spec(), true, &outURL);
1241 [NSString stringWithFormat:@"__gCrWeb.replaceWebViewURL(%@, %@);",
1242 base::SysUTF8ToNSString(outURL), stateObject];
1245 - (void)finishPushStateNavigationToURL:(const GURL&)url
1246 withStateObject:(NSString*)stateObject {
1247 // TODO(stuartmorgan): Make CRWSessionController manage this internally (or
1248 // remove it; it's not clear this matches other platforms' behavior).
1249 _webStateImpl->GetNavigationManagerImpl().OnNavigationItemCommitted();
1251 NSString* replaceWebViewUrlJS =
1252 [self javascriptToReplaceWebViewURL:url stateObjectJSON:stateObject];
1253 std::string outState;
1254 base::EscapeJSONString(base::SysNSStringToUTF8(stateObject), true, &outState);
1255 NSString* popstateJS =
1256 [NSString stringWithFormat:@"__gCrWeb.dispatchPopstateEvent(%@);",
1257 base::SysUTF8ToNSString(outState)];
1258 NSString* combinedJS =
1259 [NSString stringWithFormat:@"%@%@", replaceWebViewUrlJS, popstateJS];
1261 base::WeakNSObject<CRWWebController> weakSelf(self);
1262 [self evaluateJavaScript:combinedJS
1263 stringResultHandler:^(NSString*, NSError*) {
1264 if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
1266 base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
1267 strongSelf.get()->_URLOnStartLoading = urlCopy;
1268 strongSelf.get()->_lastRegisteredRequestURL = urlCopy;
1272 // Load the current URL in a web view, first ensuring the web view is visible.
1273 // If a native controller is present, remove it and swap a new web view in
1275 - (void)loadCurrentURLInWebView {
1276 [self willLoadCurrentURLInWebView];
1278 // Re-register the user agent, because UIWebView sometimes loses it.
1279 // See crbug.com/228397.
1280 [self registerUserAgent];
1282 // Freeing the native controller removes its view from the view hierarchy.
1283 [self setNativeController:nil];
1285 // Clear the set of URLs opened in external applications.
1286 _openedApplicationURL.reset([[NSMutableSet alloc] init]);
1288 // Load the url. The UIWebView delegate callbacks take care of updating the
1289 // session history and UI.
1290 const GURL targetURL([self currentNavigationURL]);
1291 if (!targetURL.is_valid())
1294 // JavaScript should never be evaluated here. User-entered JS should be
1295 // evaluated via stringByEvaluatingUserJavaScriptFromString.
1296 DCHECK(!targetURL.SchemeIs(url::kJavaScriptScheme));
1297 [self ensureWebViewCreated];
1299 DCHECK(self.webView && !_nativeController);
1300 NSMutableURLRequest* request =
1301 [NSMutableURLRequest requestWithURL:net::NSURLWithGURL(targetURL)];
1302 const web::Referrer referrer([self currentSessionEntryReferrer]);
1303 if (referrer.url.is_valid()) {
1304 std::string referrerValue =
1305 web::ReferrerHeaderValueForNavigation(targetURL, referrer);
1306 if (!referrerValue.empty()) {
1307 [request setValue:base::SysUTF8ToNSString(referrerValue)
1308 forHTTPHeaderField:kReferrerHeaderName];
1312 // If there are headers in the current session entry add them to |request|.
1313 // Headers that would overwrite fields already present in |request| are
1315 NSDictionary* headers = [self currentHttpHeaders];
1316 for (NSString* headerName in headers) {
1317 if (![request valueForHTTPHeaderField:headerName]) {
1318 [request setValue:[headers objectForKey:headerName]
1319 forHTTPHeaderField:headerName];
1323 NSData* postData = [self currentPOSTData];
1325 web::NavigationItemImpl* currentItem =
1326 [self currentSessionEntry].navigationItemImpl;
1327 if ([postData length] > 0 &&
1328 !(currentItem && currentItem->ShouldSkipResubmitDataConfirmation())) {
1330 [self registerLoadRequest:[self currentNavigationURL]
1331 referrer:[self currentSessionEntryReferrer]
1332 transition:[self currentTransition]];
1333 [self loadRequest:request];
1335 id continueBlock = ^{
1336 [request setHTTPMethod:@"POST"];
1337 [request setHTTPBody:[self currentPOSTData]];
1338 [request setAllHTTPHeaderFields:[self currentHttpHeaders]];
1339 [self registerLoadRequest:[self currentNavigationURL]
1340 referrer:[self currentSessionEntryReferrer]
1341 transition:[self currentTransition]];
1342 [self loadRequest:request];
1344 [_delegate webController:self
1345 onFormResubmissionForRequest:request
1346 continueBlock:continueBlock
1347 cancelBlock:cancelBlock];
1350 // The user does not need to confirm if POST data is empty.
1351 [request setHTTPMethod:@"POST"];
1352 [request setHTTPBody:postData];
1353 [request setAllHTTPHeaderFields:[self currentHttpHeaders]];
1357 // registerLoadRequest will be called when load is about to begin.
1358 // The phase at that point is guaranteed to be web::LOAD_REQUESTED.
1359 // However the delegate is not immediately called.
1360 [self registerLoadRequest:targetURL
1362 transition:[self currentTransition]];
1363 [self loadRequest:request];
1366 - (void)loadNativeViewWithSuccess:(BOOL)loadSuccess {
1367 [_nativeController view].frame = [self visibleFrame];
1368 [_containerView addSubview:[_nativeController view]];
1369 [[_nativeController view] setNeedsUpdateConstraints];
1370 const GURL currentURL([self currentURL]);
1371 [self didStartLoadingURL:currentURL updateHistory:loadSuccess];
1372 _loadPhase = web::PAGE_LOADED;
1374 // Perform post-load-finished updates.
1375 [self didFinishWithURL:currentURL loadSuccess:loadSuccess];
1377 // Inform the embedder the title changed.
1378 if ([_delegate respondsToSelector:@selector(webController:titleDidChange:)]) {
1379 NSString* title = [_nativeController title];
1380 // If a title is present, notify the delegate.
1382 [_delegate webController:self titleDidChange:title];
1383 // If the controller handles title change notification, route those to the
1385 if ([_nativeController respondsToSelector:@selector(setDelegate:)]) {
1386 [_nativeController setDelegate:self];
1391 - (void)loadErrorInNativeView:(NSError*)error {
1392 [self removeWebViewAllowingCachedReconstruction:NO];
1394 const GURL currentUrl = [self currentNavigationURL];
1395 BOOL isPost = [self currentPOSTData] != nil;
1397 [self setNativeController:[_nativeProvider controllerForURL:currentUrl
1400 [self loadNativeViewWithSuccess:NO];
1403 // Load the current URL in a native controller, retrieved from the native
1404 // provider. Call |loadNativeViewWithSuccess:YES| to load the native controller.
1405 - (void)loadCurrentURLInNativeView {
1406 // Free the web view.
1407 [self removeWebViewAllowingCachedReconstruction:NO];
1409 const GURL targetURL = [self currentNavigationURL];
1410 const web::Referrer referrer;
1411 // Unlike the WebView case, always create a new controller and view.
1412 // TODO(pinkerton): What to do if this does return nil?
1413 [self setNativeController:[_nativeProvider controllerForURL:targetURL]];
1414 [self registerLoadRequest:targetURL
1416 transition:[self currentTransition]];
1417 [self loadNativeViewWithSuccess:YES];
1420 - (void)loadWithParams:(const web::WebLoadParams&)originalParams {
1421 // Make a copy of |params|, as some of the delegate methods may modify it.
1422 web::WebLoadParams params(originalParams);
1424 // Initiating a navigation from the UI, record the current page state before
1425 // the new page loads. Don't record for back/forward, as the current entry
1426 // has already been moved to the next entry in the history. Do, however,
1427 // record it for general reload.
1428 // TODO(jimblackler): consider a single unified call to record state whenever
1429 // the page is about to be changed. This cannot currently be done after
1430 // addPendingEntry is called.
1432 [_delegate webWillInitiateLoadWithParams:params];
1434 GURL navUrl = params.url;
1435 ui::PageTransition transition = params.transition_type;
1437 BOOL initialNavigation = NO;
1439 PageTransitionCoreTypeIs(transition, ui::PAGE_TRANSITION_RELOAD) &&
1440 (transition & ui::PAGE_TRANSITION_FORWARD_BACK);
1442 // Setting these for back/forward is not supported.
1443 DCHECK(!params.extra_headers);
1444 DCHECK(!params.post_data);
1446 // TODO(stuartmorgan): Why doesn't recordStateInHistory get called for
1447 // forward/back transitions?
1448 [self recordStateInHistory];
1450 CRWSessionController* history =
1451 _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1452 if (!self.currentSessionEntry)
1453 initialNavigation = YES;
1454 [history addPendingEntry:navUrl
1455 referrer:params.referrer
1456 transition:transition
1457 rendererInitiated:params.is_renderer_initiated];
1458 web::NavigationItemImpl* addedItem =
1459 [self currentSessionEntry].navigationItemImpl;
1461 if (params.extra_headers)
1462 addedItem->AddHttpRequestHeaders(params.extra_headers);
1463 if (params.post_data) {
1464 DCHECK([addedItem->GetHttpRequestHeaders() objectForKey:@"Content-Type"])
1465 << "Post data should have an associated content type";
1466 addedItem->SetPostData(params.post_data);
1467 addedItem->SetShouldSkipResubmitDataConfirmation(true);
1471 [_delegate webDidUpdateSessionForLoadWithParams:params
1472 wasInitialNavigation:initialNavigation];
1474 // If a non-default cache mode is passed in, it takes precedence over
1476 const BOOL reload = [self shouldReload:navUrl transition:transition];
1477 if (params.cache_mode != net::RequestTracker::CACHE_NORMAL) {
1478 _webStateImpl->SetCacheMode(params.cache_mode);
1479 } else if (reload) {
1480 _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_RELOAD);
1483 [self loadCurrentURL];
1485 // Change the cache mode back to CACHE_NORMAL after a reload.
1486 _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1489 - (void)loadCurrentURL {
1490 // If the content view doesn't exist, the tab has either been evicted, or
1491 // never displayed. Bail, and let the URL be loaded when the tab is shown.
1492 if (!_containerView)
1495 // Reset current WebUI if one exists.
1498 // Precaution, so that the outgoing URL is registered, to reduce the risk of
1499 // it being seen as a fresh URL later by the same method (and new page change
1500 // erroneously reported).
1501 [self checkForUnexpectedURLChange];
1503 // Abort any outstanding page load. This ensures the delegate gets informed
1504 // about the outgoing page, and further messages from the page are suppressed.
1505 if (_loadPhase != web::PAGE_LOADED)
1509 // Remove the interstitial before doing anything else.
1510 [self clearInterstitials];
1512 const GURL currentURL = [self currentNavigationURL];
1513 // If it's a chrome URL, but not a native one, create the WebUI instance.
1514 if (web::GetWebClient()->IsAppSpecificURL(currentURL) &&
1515 ![_nativeProvider hasControllerForURL:currentURL]) {
1516 [self createWebUIForURL:currentURL];
1519 // Loading a new url, must check here if it's a native chrome URL and
1520 // replace the appropriate view if so, or transition back to a web view from
1522 if ([self shouldLoadURLInNativeView:currentURL]) {
1523 [self loadCurrentURLInNativeView];
1525 [self loadCurrentURLInWebView];
1528 // Once a URL has been loaded, any cached-based reconstruction state has
1529 // either been handled or obsoleted.
1530 _expectedReconstructionURL = GURL();
1533 - (BOOL)shouldLoadURLInNativeView:(const GURL&)url {
1534 // App-specific URLs that don't require WebUI are loaded in native views.
1535 return web::GetWebClient()->IsAppSpecificURL(url) &&
1536 !_webStateImpl->HasWebUI();
1539 - (void)triggerPendingLoad {
1540 if (!_containerView) {
1541 DCHECK(!_isBeingDestroyed);
1542 // Create the top-level parent view, which will contain the content (whether
1543 // native or web). Note, this needs to be created with a non-zero size
1544 // to allow for (native) subviews with autosize constraints to be correctly
1546 _containerView.reset([[CRWWebControllerContainerView alloc]
1547 initWithFrame:[[UIScreen mainScreen] bounds]]);
1548 [_containerView addGestureRecognizer:[self touchTrackingRecognizer]];
1549 [_containerView setAccessibilityIdentifier:web::kContainerViewID];
1550 // Is |currentUrl| a web scheme or native chrome scheme.
1551 BOOL isChromeScheme =
1552 web::GetWebClient()->IsAppSpecificURL([self currentNavigationURL]);
1554 // Don't immediately load the web page if in overlay mode. Always load if
1556 if (isChromeScheme || !_overlayPreviewMode) {
1557 // TODO(jimblackler): end the practice of calling |loadCurrentURL| when it
1558 // is possible there is no current URL. If the call performs necessary
1559 // initialization, break that out.
1560 [self loadCurrentURL];
1563 // Display overlay view until current url has finished loading or delay and
1564 // then transition away.
1565 if ((_overlayPreviewMode || _usePlaceholderOverlay) && !isChromeScheme)
1566 [self addPlaceholderOverlay];
1568 // Don't reset the overlay flag if in preview mode.
1569 if (!_overlayPreviewMode)
1570 _usePlaceholderOverlay = NO;
1574 - (BOOL)shouldReload:(const GURL&)destinationURL
1575 transition:(ui::PageTransition)transition {
1576 // Do a reload if the user hits enter in the address bar or re-types a URL.
1577 CRWSessionController* sessionController =
1578 _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1579 web::NavigationItem* item =
1580 _webStateImpl->GetNavigationManagerImpl().GetVisibleItem();
1581 return (transition & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) && item &&
1582 (destinationURL == item->GetURL() ||
1583 destinationURL == [sessionController currentEntry].originalUrl);
1586 // Reload either the web view or the native content depending on which is
1588 - (void)reloadInternal {
1589 web::RecordAction(UserMetricsAction("Reload"));
1591 // Just as we don't use the WebView native back and forward navigation
1592 // (preferring to load the URLs manually) we don't use the native reload.
1593 // This ensures state processing and delegate calls are consistent.
1594 [self loadCurrentURL];
1596 [_nativeController reload];
1601 [_delegate webWillReload];
1603 _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_RELOAD);
1604 [self reloadInternal];
1605 _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1608 - (void)loadCancelled {
1609 // Current load will not complete; this should be communicated upstream to the
1611 switch (_loadPhase) {
1612 case web::LOAD_REQUESTED:
1613 // Load phase after abort is always PAGE_LOADED.
1614 _loadPhase = web::PAGE_LOADED;
1616 _webStateImpl->SetIsLoading(false);
1618 [_delegate webCancelStartLoadingRequest];
1620 case web::PAGE_LOADING:
1621 // The previous load never fully completed before this page change. The
1622 // loadPhase is changed to PAGE_LOADED to indicate the cycle is complete,
1623 // and the delegate is called.
1624 _loadPhase = web::PAGE_LOADED;
1626 // RequestTracker expects StartPageLoad to be followed by
1627 // FinishPageLoad, passing the exact same URL.
1628 self.webStateImpl->GetRequestTracker()->FinishPageLoad(
1629 _URLOnStartLoading, false);
1631 [_delegate webLoadCancelled:_URLOnStartLoading];
1633 case web::PAGE_LOADED:
1639 [self abortWebLoad];
1640 [self loadCancelled];
1643 - (void)prepareForGoBack {
1644 // Make sure any transitions that may have occurred have been seen and acted
1645 // on by the CRWWebController, so the history stack and state of the
1646 // CRWWebController is 100% up to date before the stack navigation starts.
1648 [self injectEarlyInjectionScripts];
1649 [self checkForUnexpectedURLChange];
1651 // Discard any outstanding pending entries before adjusting the navigation
1653 CRWSessionController* sessionController =
1654 _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1655 [sessionController discardNonCommittedEntries];
1657 bool wasShowingInterstitial = _webStateImpl->IsShowingWebInterstitial();
1659 // Call into the delegate before |recordStateInHistory|.
1660 // TODO(rohitrao): Can this be reordered after |recordStateInHistory|?
1661 [_delegate webDidPrepareForGoBack];
1663 // Before changing the current session history entry, record the tab state.
1664 if (!wasShowingInterstitial) {
1665 [self recordStateInHistory];
1677 - (void)goDelta:(int)delta {
1683 // Abort if there is nothing next in the history.
1684 // Note that it is NOT checked that the history depth is at least |delta|.
1685 if ((delta < 0 && ![self canGoBack]) || (delta > 0 && ![self canGoForward])) {
1690 [self prepareForGoBack];
1692 // Before changing the current session history entry, record the tab state.
1693 [self recordStateInHistory];
1696 [_delegate webWillGoDelta:delta];
1698 CRWSessionController* sessionController =
1699 _webStateImpl->GetNavigationManagerImpl().GetSessionController();
1700 CRWSessionEntry* fromEntry = [sessionController currentEntry];
1701 [sessionController goDelta:delta];
1703 _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_HISTORY);
1704 [self finishHistoryNavigationFromEntry:fromEntry];
1705 _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1710 return _loadPhase == web::PAGE_LOADED;
1713 - (void)loadCompleteWithSuccess:(BOOL)loadSuccess {
1714 [self removePlaceholderOverlay];
1715 // The webView may have been torn down (or replaced by a native view). Be
1716 // safe and do nothing if that's happened.
1717 if (_loadPhase != web::PAGE_LOADING)
1720 DCHECK(self.webView);
1722 const GURL currentURL([self currentURL]);
1724 [self resetLoadState];
1725 _loadPhase = web::PAGE_LOADED;
1727 [self optOutScrollsToTopForSubviews];
1729 // Ensure the URL is as expected (and already reported to the delegate).
1730 DCHECK(currentURL == _lastRegisteredRequestURL)
1732 << "currentURL = [" << currentURL << "]" << std::endl
1733 << "_lastRegisteredRequestURL = [" << _lastRegisteredRequestURL << "]";
1735 // Perform post-load-finished updates.
1736 [self didFinishWithURL:currentURL loadSuccess:loadSuccess];
1738 // Execute the pending LoadCompleteActions.
1739 for (ProceduralBlock action in _pendingLoadCompleteActions.get()) {
1742 [_pendingLoadCompleteActions removeAllObjects];
1745 - (void)didFinishWithURL:(const GURL&)currentURL loadSuccess:(BOOL)loadSuccess {
1746 DCHECK(_loadPhase == web::PAGE_LOADED);
1747 _webStateImpl->GetRequestTracker()->FinishPageLoad(currentURL, loadSuccess);
1748 // Reset the navigation type to the default value.
1749 // Note: it is possible that the web view has already started loading the
1750 // next page when this is called. In that case the cache mode can leak to
1751 // (some of) the requests of the next page. It's expected to be an edge case,
1752 // but if it becomes a problem it should be possible to notice it afterwards
1753 // and react to it (by warning the user or reloading the page for example).
1754 _webStateImpl->SetCacheMode(net::RequestTracker::CACHE_NORMAL);
1755 _webStateImpl->GetRequestTracker()->SetCacheModeFromUIThread(
1756 _webStateImpl->GetCacheMode());
1758 [self restoreStateFromHistory];
1759 _webStateImpl->OnPageLoaded(currentURL, loadSuccess);
1760 _webStateImpl->SetIsLoading(false);
1761 // Inform the embedder the load completed.
1762 [_delegate webDidFinishWithURL:currentURL loadSuccess:loadSuccess];
1765 - (void)finishHistoryNavigationFromEntry:(CRWSessionEntry*)fromEntry {
1766 [_delegate webWillFinishHistoryNavigationFromEntry:fromEntry];
1768 // Check if toEntry was created by a JavaScript window.history.pushState()
1769 // call from fromEntry. If it was, don't load the URL. Instead update
1770 // UIWebView's URL and dispatch a popstate event.
1771 if ([_webStateImpl->GetNavigationManagerImpl().GetSessionController()
1772 isPushStateNavigationBetweenEntry:fromEntry
1773 andEntry:self.currentSessionEntry]) {
1774 NSString* state = [self currentSessionEntry]
1775 .navigationItemImpl->GetSerializedStateObject();
1776 [self finishPushStateNavigationToURL:[self currentNavigationURL]
1777 withStateObject:state];
1779 GURL activeURL = [self currentNavigationURL];
1780 GURL fromURL = fromEntry.navigationItem->GetURL();
1782 [self updateURLForHistoryNavigationFromURL:fromURL toURL:activeURL];
1783 web::NavigationItem* currentItem =
1784 _webStateImpl->GetNavigationManagerImpl().GetVisibleItem();
1785 ui::PageTransition transition = ui::PageTransitionFromInt(
1786 ui::PAGE_TRANSITION_RELOAD | ui::PAGE_TRANSITION_FORWARD_BACK);
1788 web::WebLoadParams params(endURL);
1790 params.referrer = currentItem->GetReferrer();
1792 params.transition_type = transition;
1793 [self loadWithParams:params];
1797 - (GURL)updateURLForHistoryNavigationFromURL:(const GURL&)startURL
1798 toURL:(const GURL&)endURL {
1799 // Check the state of the fragments on both URLs (aka, is there a '#' in the
1801 if (!startURL.has_ref() || endURL.has_ref()) {
1805 // startURL contains a fragment and endURL doesn't. Remove the fragment from
1806 // startURL and compare the resulting string to endURL. If they are equal, add
1807 // # to endURL to cause a hashchange event.
1808 GURL hashless = web::GURLByRemovingRefFromGURL(startURL);
1810 if (hashless != endURL)
1813 url::StringPieceReplacements<std::string> emptyRef;
1814 emptyRef.SetRefStr("");
1815 GURL newEndURL = endURL.ReplaceComponents(emptyRef);
1816 web::NavigationItem* item =
1817 _webStateImpl->GetNavigationManagerImpl().GetVisibleItem();
1819 item->SetURL(newEndURL);
1823 - (void)evaluateJavaScript:(NSString*)script
1825 (void (^)(scoped_ptr<base::Value>, NSError*))handler {
1826 [self evaluateJavaScript:script
1827 stringResultHandler:^(NSString* stringResult, NSError* error) {
1829 scoped_ptr<base::Value> result(
1830 base::JSONReader::Read(base::SysNSStringToUTF8(stringResult)));
1831 DCHECK(result || error);
1832 handler(result.Pass(), error);
1837 - (void)addGestureRecognizerToWebView:(UIGestureRecognizer*)recognizer {
1838 if ([_gestureRecognizers containsObject:recognizer])
1841 [self.webView addGestureRecognizer:recognizer];
1842 [_gestureRecognizers addObject:recognizer];
1845 - (void)removeGestureRecognizerFromWebView:(UIGestureRecognizer*)recognizer {
1846 if (![_gestureRecognizers containsObject:recognizer])
1849 [self.webView removeGestureRecognizer:recognizer];
1850 [_gestureRecognizers removeObject:recognizer];
1853 - (void)addToolbarViewToWebView:(UIView*)toolbarView {
1854 DCHECK(toolbarView);
1855 if ([_webViewToolbars containsObject:toolbarView])
1857 [_webViewToolbars addObject:toolbarView];
1859 [_containerView addToolbar:toolbarView];
1862 - (void)removeToolbarViewFromWebView:(UIView*)toolbarView {
1863 if (![_webViewToolbars containsObject:toolbarView])
1865 [_webViewToolbars removeObject:toolbarView];
1867 [_containerView removeToolbar:toolbarView];
1870 - (CRWJSInjectionReceiver*)jsInjectionReceiver {
1871 return _jsInjectionReceiver;
1874 - (BOOL)cancellable {
1875 return self.sessionController.openedByDOM &&
1876 !self.sessionController.lastCommittedEntry;
1879 - (BOOL)isBeingDestroyed {
1880 return _isBeingDestroyed;
1887 - (web::ReferrerPolicy)referrerPolicyFromString:(const std::string&)policy {
1888 // TODO(stuartmorgan): Remove this temporary bridge to the helper function
1889 // once the referrer handling moves into the subclasses.
1890 return web::ReferrerPolicyFromString(policy);
1894 #pragma mark CRWJSInjectionEvaluator Methods
1896 - (void)evaluateJavaScript:(NSString*)script
1897 stringResultHandler:(web::JavaScriptCompletion)handler {
1898 // Subclasses must implement this method.
1902 - (BOOL)scriptHasBeenInjectedForClass:(Class)jsInjectionManagerClass
1903 presenceBeacon:(NSString*)beacon {
1904 // Subclasses must implement this method.
1909 - (void)injectScript:(NSString*)script forClass:(Class)JSInjectionManagerClass {
1910 // Make sure that CRWJSEarlyScriptManager has been injected.
1911 BOOL ealyScriptInjected =
1912 [self scriptHasBeenInjectedForClass:[CRWJSEarlyScriptManager class]
1913 presenceBeacon:[_earlyScriptManager presenceBeacon]];
1914 if (!ealyScriptInjected &&
1915 JSInjectionManagerClass != [CRWJSEarlyScriptManager class]) {
1916 [_earlyScriptManager inject];
1920 - (web::WebViewType)webViewType {
1921 // Subclasses must implement this method.
1923 return web::UI_WEB_VIEW_TYPE;
1928 - (void)evaluateUserJavaScript:(NSString*)script {
1929 // Subclasses must implement this method.
1933 - (void)didFinishNavigation {
1934 // This can be called at multiple times after the document has loaded. Do
1935 // nothing if the document has already loaded.
1936 if (_loadPhase == web::PAGE_LOADED)
1938 [self loadCompleteWithSuccess:YES];
1941 - (BOOL)respondToMessage:(base::DictionaryValue*)message
1942 userIsInteracting:(BOOL)userIsInteracting
1943 originURL:(const GURL&)originURL {
1944 std::string command;
1945 if (!message->GetString("command", &command)) {
1946 DLOG(WARNING) << "JS message parameter not found: command";
1950 SEL handler = [self selectorToHandleJavaScriptCommand:command];
1952 if (!self.webStateImpl->OnScriptCommandReceived(
1953 command, *message, originURL, userIsInteracting)) {
1954 // Message was either unexpected or not correctly handled.
1955 // Page is reset as a precaution.
1956 DLOG(WARNING) << "Unexpected message received: " << command;
1962 typedef BOOL (*HandlerType)(id, SEL, base::DictionaryValue*, NSDictionary*);
1963 HandlerType handlerImplementation =
1964 reinterpret_cast<HandlerType>([self methodForSelector:handler]);
1965 DCHECK(handlerImplementation);
1966 NSMutableDictionary* context =
1967 [NSMutableDictionary dictionaryWithObject:@(userIsInteracting)
1968 forKey:web::kUserIsInteractingKey];
1969 NSURL* originNSURL = net::NSURLWithGURL(originURL);
1971 context[web::kOriginURLKey] = originNSURL;
1972 return handlerImplementation(self, handler, message, context);
1975 - (SEL)selectorToHandleJavaScriptCommand:(const std::string&)command {
1976 static std::map<std::string, SEL>* handlers = nullptr;
1977 static dispatch_once_t onceToken;
1978 dispatch_once(&onceToken, ^{
1979 handlers = new std::map<std::string, SEL>();
1980 (*handlers)["addPluginPlaceholders"] =
1981 @selector(handleAddPluginPlaceholdersMessage:context:);
1982 (*handlers)["chrome.send"] = @selector(handleChromeSendMessage:context:);
1983 (*handlers)["console"] = @selector(handleConsoleMessage:context:);
1984 (*handlers)["dialog.suppressed"] =
1985 @selector(handleDialogSuppressedMessage:context:);
1986 (*handlers)["dialog.willShow"] =
1987 @selector(handleDialogWillShowMessage:context:);
1988 (*handlers)["document.favicons"] =
1989 @selector(handleDocumentFaviconsMessage:context:);
1990 (*handlers)["document.retitled"] =
1991 @selector(handleDocumentRetitledMessage:context:);
1992 (*handlers)["document.submit"] =
1993 @selector(handleDocumentSubmitMessage:context:);
1994 (*handlers)["externalRequest"] =
1995 @selector(handleExternalRequestMessage:context:);
1996 (*handlers)["form.activity"] =
1997 @selector(handleFormActivityMessage:context:);
1998 (*handlers)["form.requestAutocomplete"] =
1999 @selector(handleFormRequestAutocompleteMessage:context:);
2000 (*handlers)["navigator.credentials.request"] =
2001 @selector(handleCredentialsRequestedMessage:context:);
2002 (*handlers)["navigator.credentials.notifySignedIn"] =
2003 @selector(handleSignedInMessage:context:);
2004 (*handlers)["navigator.credentials.notifySignedOut"] =
2005 @selector(handleSignedOutMessage:context:);
2006 (*handlers)["navigator.credentials.notifyFailedSignIn"] =
2007 @selector(handleSignInFailedMessage:context:);
2008 (*handlers)["resetExternalRequest"] =
2009 @selector(handleResetExternalRequestMessage:context:);
2010 (*handlers)["window.close.self"] =
2011 @selector(handleWindowCloseSelfMessage:context:);
2012 (*handlers)["window.error"] = @selector(handleWindowErrorMessage:context:);
2013 (*handlers)["window.hashchange"] =
2014 @selector(handleWindowHashChangeMessage:context:);
2015 (*handlers)["window.history.back"] =
2016 @selector(handleWindowHistoryBackMessage:context:);
2017 (*handlers)["window.history.didPushState"] =
2018 @selector(handleWindowHistoryDidPushStateMessage:context:);
2019 (*handlers)["window.history.didReplaceState"] =
2020 @selector(handleWindowHistoryDidReplaceStateMessage:context:);
2021 (*handlers)["window.history.forward"] =
2022 @selector(handleWindowHistoryForwardMessage:context:);
2023 (*handlers)["window.history.go"] =
2024 @selector(handleWindowHistoryGoMessage:context:);
2027 auto iter = handlers->find(command);
2028 return iter != handlers->end() ? iter->second : nullptr;
2032 #pragma mark JavaScript message handlers
2034 - (BOOL)handleAddPluginPlaceholdersMessage:(base::DictionaryValue*)message
2035 context:(NSDictionary*)context {
2036 // Inject the script that adds the plugin placeholders.
2037 [[_jsInjectionReceiver
2038 instanceOfClass:[CRWJSPluginPlaceholderManager class]] inject];
2042 - (BOOL)handleChromeSendMessage:(base::DictionaryValue*)message
2043 context:(NSDictionary*)context {
2044 if (_webStateImpl->HasWebUI()) {
2045 const GURL currentURL([self currentURL]);
2046 if (web::GetWebClient()->IsAppSpecificURL(currentURL)) {
2047 std::string messageContent;
2048 base::ListValue* arguments = nullptr;
2049 if (!message->GetString("message", &messageContent)) {
2050 DLOG(WARNING) << "JS message parameter not found: message";
2053 if (!message->GetList("arguments", &arguments)) {
2054 DLOG(WARNING) << "JS message parameter not found: arguments";
2057 _webStateImpl->OnScriptCommandReceived(
2058 messageContent, *message, currentURL,
2059 context[web::kUserIsInteractingKey]);
2060 _webStateImpl->ProcessWebUIMessage(currentURL, messageContent,
2067 << "chrome.send message not handled because WebUI was not found.";
2071 - (BOOL)handleConsoleMessage:(base::DictionaryValue*)message
2072 context:(NSDictionary*)context {
2073 // Do not log if JS logging is off.
2074 if (![[NSUserDefaults standardUserDefaults] boolForKey:web::kLogJavaScript]) {
2079 if (!message->GetString("method", &method)) {
2080 DLOG(WARNING) << "JS message parameter not found: method";
2083 std::string consoleMessage;
2084 if (!message->GetString("message", &consoleMessage)) {
2085 DLOG(WARNING) << "JS message parameter not found: message";
2089 if (!message->GetString("origin", &origin)) {
2090 DLOG(WARNING) << "JS message parameter not found: origin";
2094 DVLOG(0) << origin << " [" << method << "] " << consoleMessage;
2098 - (BOOL)handleDialogSuppressedMessage:(base::DictionaryValue*)message
2099 context:(NSDictionary*)context {
2101 respondsToSelector:@selector(webControllerDidSuppressDialog:)]) {
2102 [_delegate webControllerDidSuppressDialog:self];
2107 - (BOOL)handleDialogWillShowMessage:(base::DictionaryValue*)message
2108 context:(NSDictionary*)context {
2109 if ([_delegate respondsToSelector:@selector(webControllerWillShowDialog:)]) {
2110 [_delegate webControllerWillShowDialog:self];
2115 - (BOOL)handleDocumentFaviconsMessage:(base::DictionaryValue*)message
2116 context:(NSDictionary*)context {
2117 base::ListValue* favicons = nullptr;
2118 if (!message->GetList("favicons", &favicons)) {
2119 DLOG(WARNING) << "JS message parameter not found: favicons";
2122 std::vector<web::FaviconURL> urls;
2123 for (size_t fav_idx = 0; fav_idx != favicons->GetSize(); ++fav_idx) {
2124 base::DictionaryValue* favicon = nullptr;
2125 if (!favicons->GetDictionary(fav_idx, &favicon))
2129 if (!favicon->GetString("href", &href)) {
2130 DLOG(WARNING) << "JS message parameter not found: href";
2133 if (!favicon->GetString("rel", &rel)) {
2134 DLOG(WARNING) << "JS message parameter not found: rel";
2137 web::FaviconURL::IconType icon_type = web::FaviconURL::FAVICON;
2138 if (rel == "apple-touch-icon")
2139 icon_type = web::FaviconURL::TOUCH_ICON;
2140 else if (rel == "apple-touch-icon-precomposed")
2141 icon_type = web::FaviconURL::TOUCH_PRECOMPOSED_ICON;
2143 web::FaviconURL(GURL(href), icon_type, std::vector<gfx::Size>()));
2146 _webStateImpl->OnFaviconUrlUpdated(urls);
2150 - (BOOL)handleDocumentSubmitMessage:(base::DictionaryValue*)message
2151 context:(NSDictionary*)context {
2153 if (!message->GetString("href", &href)) {
2154 DLOG(WARNING) << "JS message parameter not found: href";
2157 const GURL targetURL(href);
2158 const GURL currentURL([self currentURL]);
2159 bool targetsFrame = false;
2160 message->GetBoolean("targetsFrame", &targetsFrame);
2161 if (!targetsFrame && web::UrlHasWebScheme(targetURL)) {
2162 // The referrer is not known yet, and will be updated later.
2163 const web::Referrer emptyReferrer;
2164 [self registerLoadRequest:targetURL
2165 referrer:emptyReferrer
2166 transition:ui::PAGE_TRANSITION_FORM_SUBMIT];
2168 std::string formName;
2169 message->GetString("formName", &formName);
2170 base::scoped_nsobject<NSSet> observers([_observers copy]);
2171 // We decide the form is user-submitted if the user has interacted with
2172 // the main page (using logic from the popup blocker), or if the keyboard
2174 BOOL submittedByUser = [context[web::kUserIsInteractingKey] boolValue] ||
2175 [_webViewProxy getKeyboardAccessory];
2176 _webStateImpl->OnDocumentSubmitted(formName, submittedByUser);
2180 - (BOOL)handleExternalRequestMessage:(base::DictionaryValue*)message
2181 context:(NSDictionary*)context {
2184 std::string referrerPolicy;
2185 if (!message->GetString("href", &href)) {
2186 DLOG(WARNING) << "JS message parameter not found: href";
2189 if (!message->GetString("target", &target)) {
2190 DLOG(WARNING) << "JS message parameter not found: target";
2193 if (!message->GetString("referrerPolicy", &referrerPolicy)) {
2194 DLOG(WARNING) << "JS message parameter not found: referrerPolicy";
2197 // Round-trip the href through NSURL; this URL will be compared as a
2198 // string against a UIWebView-provided NSURL later, and must match exactly
2199 // for the new window to trigger, so the escaping needs to be NSURL-style.
2200 // TODO(stuartmorgan): Comparing against a URL whose exact formatting we
2201 // don't control is fundamentally fragile; try to find another
2202 // way of handling this.
2203 DCHECK(context[web::kUserIsInteractingKey]);
2204 NSString* windowName =
2205 base::SysUTF8ToNSString(href + web::kWindowNameSeparator + target);
2206 _externalRequest.reset(new web::NewWindowInfo(
2207 net::GURLWithNSURL(net::NSURLWithGURL(GURL(href))), windowName,
2208 web::ReferrerPolicyFromString(referrerPolicy),
2209 [context[web::kUserIsInteractingKey] boolValue]));
2213 - (BOOL)handleFormActivityMessage:(base::DictionaryValue*)message
2214 context:(NSDictionary*)context {
2215 std::string formName;
2216 std::string fieldName;
2219 int keyCode = web::WebStateObserver::kInvalidFormKeyCode;
2220 bool inputMissing = false;
2221 if (!message->GetString("formName", &formName) ||
2222 !message->GetString("fieldName", &fieldName) ||
2223 !message->GetString("type", &type) ||
2224 !message->GetString("value", &value)) {
2225 inputMissing = true;
2228 if (!message->GetInteger("keyCode", &keyCode) || keyCode < 0)
2229 keyCode = web::WebStateObserver::kInvalidFormKeyCode;
2230 _webStateImpl->OnFormActivityRegistered(formName, fieldName, type, value,
2231 keyCode, inputMissing);
2235 - (BOOL)handleFormRequestAutocompleteMessage:(base::DictionaryValue*)message
2236 context:(NSDictionary*)context {
2237 std::string formName;
2238 if (!message->GetString("formName", &formName)) {
2239 DLOG(WARNING) << "JS message parameter not found: formName";
2242 DCHECK(context[web::kUserIsInteractingKey]);
2243 _webStateImpl->OnAutocompleteRequested(
2244 net::GURLWithNSURL(context[web::kOriginURLKey]), formName,
2245 [context[web::kUserIsInteractingKey] boolValue]);
2249 - (BOOL)handleCredentialsRequestedMessage:(base::DictionaryValue*)message
2250 context:(NSDictionary*)context {
2251 int request_id = -1;
2252 if (!message->GetInteger("requestId", &request_id)) {
2253 DLOG(WARNING) << "JS message parameter not found: requestId";
2256 bool suppress_ui = false;
2257 if (!message->GetBoolean("suppressUI", &suppress_ui)) {
2258 DLOG(WARNING) << "JS message parameter not found: suppressUI";
2261 base::ListValue* federations_value = nullptr;
2262 if (!message->GetList("federations", &federations_value)) {
2263 DLOG(WARNING) << "JS message parameter not found: federations";
2266 std::vector<std::string> federations;
2267 for (auto federation_value : *federations_value) {
2268 std::string federation;
2269 if (!federation_value->GetAsString(&federation)) {
2270 DLOG(WARNING) << "JS message parameter 'federations' contains wrong type";
2273 federations.push_back(federation);
2275 DCHECK(context[web::kUserIsInteractingKey]);
2276 _webStateImpl->OnCredentialsRequested(
2277 request_id, net::GURLWithNSURL(context[web::kOriginURLKey]), suppress_ui,
2278 federations, [context[web::kUserIsInteractingKey] boolValue]);
2282 - (BOOL)handleSignedInMessage:(base::DictionaryValue*)message
2283 context:(NSDictionary*)context {
2284 int request_id = -1;
2285 if (!message->GetInteger("requestId", &request_id)) {
2286 DLOG(WARNING) << "JS message parameter not found: requestId";
2289 base::DictionaryValue* credential_data = nullptr;
2290 web::Credential credential;
2291 if (message->GetDictionary("credential", &credential_data)) {
2292 if (!web::DictionaryValueToCredential(*credential_data, &credential)) {
2293 DLOG(WARNING) << "JS message parameter 'credential' is invalid";
2296 _webStateImpl->OnSignedIn(request_id,
2297 net::GURLWithNSURL(context[web::kOriginURLKey]),
2300 _webStateImpl->OnSignedIn(request_id,
2301 net::GURLWithNSURL(context[web::kOriginURLKey]));
2306 - (BOOL)handleSignedOutMessage:(base::DictionaryValue*)message
2307 context:(NSDictionary*)context {
2308 int request_id = -1;
2309 if (!message->GetInteger("requestId", &request_id)) {
2310 DLOG(WARNING) << "JS message parameter not found: requestId";
2313 _webStateImpl->OnSignedOut(request_id,
2314 net::GURLWithNSURL(context[web::kOriginURLKey]));
2318 - (BOOL)handleSignInFailedMessage:(base::DictionaryValue*)message
2319 context:(NSDictionary*)context {
2320 int request_id = -1;
2321 if (!message->GetInteger("requestId", &request_id)) {
2322 DLOG(WARNING) << "JS message parameter not found: requestId";
2325 base::DictionaryValue* credential_data = nullptr;
2326 web::Credential credential;
2327 if (message->GetDictionary("credential", &credential_data)) {
2328 if (!web::DictionaryValueToCredential(*credential_data, &credential)) {
2329 DLOG(WARNING) << "JS message parameter 'credential' is invalid";
2332 _webStateImpl->OnSignInFailed(
2333 request_id, net::GURLWithNSURL(context[web::kOriginURLKey]),
2336 _webStateImpl->OnSignInFailed(
2337 request_id, net::GURLWithNSURL(context[web::kOriginURLKey]));
2342 - (BOOL)handleResetExternalRequestMessage:(base::DictionaryValue*)message
2343 context:(NSDictionary*)context {
2344 _externalRequest.reset();
2348 - (BOOL)handleWindowCloseSelfMessage:(base::DictionaryValue*)message
2349 context:(NSDictionary*)context {
2354 - (BOOL)handleWindowErrorMessage:(base::DictionaryValue*)message
2355 context:(NSDictionary*)context {
2356 std::string errorMessage;
2357 if (!message->GetString("message", &errorMessage)) {
2358 DLOG(WARNING) << "JS message parameter not found: message";
2361 DLOG(ERROR) << "JavaScript error: " << errorMessage
2362 << " URL:" << [self currentURL].spec();
2366 - (BOOL)handleWindowHashChangeMessage:(base::DictionaryValue*)message
2367 context:(NSDictionary*)context {
2368 [self checkForUnexpectedURLChange];
2370 // Notify the observers.
2371 _webStateImpl->OnUrlHashChanged();
2375 - (BOOL)handleWindowHistoryBackMessage:(base::DictionaryValue*)message
2376 context:(NSDictionary*)context {
2381 - (BOOL)handleWindowHistoryForwardMessage:(base::DictionaryValue*)message
2382 context:(NSDictionary*)context {
2387 - (BOOL)handleWindowHistoryGoMessage:(base::DictionaryValue*)message
2388 context:(NSDictionary*)context {
2390 message->GetInteger("value", &delta);
2391 [self goDelta:delta];
2395 - (BOOL)handleWindowHistoryDidPushStateMessage:(base::DictionaryValue*)message
2396 context:(NSDictionary*)context {
2397 std::string pageURL;
2398 std::string baseURL;
2399 if (!message->GetString("pageUrl", &pageURL) ||
2400 !message->GetString("baseUrl", &baseURL)) {
2401 DLOG(WARNING) << "JS message parameter not found: pageUrl or baseUrl";
2404 GURL pushURL = web::history_state_util::GetHistoryStateChangeUrl(
2405 [self currentURL], GURL(baseURL), pageURL);
2406 // UIWebView seems to choke on unicode characters that haven't been
2407 // escaped; escape the URL now so the expected load URL is correct.
2408 pushURL = URLEscapedForHistory(pushURL);
2409 if (!pushURL.is_valid())
2411 const NavigationManagerImpl& navigationManager =
2412 _webStateImpl->GetNavigationManagerImpl();
2413 web::NavigationItem* navItem = [self currentNavItem];
2414 // PushState happened before first navigation entry or called right after
2415 // window.open when the url is empty.
2417 (navigationManager.GetEntryCount() <= 1 && navItem->GetURL().is_empty()))
2419 if (!web::history_state_util::IsHistoryStateChangeValid(navItem->GetURL(),
2421 // A redirect may have occurred just prior to the pushState. Check if
2422 // the URL needs to be updated.
2423 // TODO(bdibello): Investigate how the pushState() is handled before the
2424 // redirect and after core.js injection.
2425 [self checkForUnexpectedURLChange];
2427 if (!web::history_state_util::IsHistoryStateChangeValid(
2428 [self currentNavItem]->GetURL(), pushURL)) {
2429 // If the current session entry URL origin still doesn't match pushURL's
2430 // origin, ignore the pushState. This can happen if a new URL is loaded
2431 // just before the pushState.
2434 std::string stateObjectJSON;
2435 if (!message->GetString("stateObject", &stateObjectJSON)) {
2436 DLOG(WARNING) << "JS message parameter not found: stateObject";
2439 NSString* stateObject = base::SysUTF8ToNSString(stateObjectJSON);
2440 _URLOnStartLoading = pushURL;
2441 _lastRegisteredRequestURL = pushURL;
2442 [self pushStateWithPageURL:pushURL stateObject:stateObject];
2444 NSString* replaceWebViewJS =
2445 [self javascriptToReplaceWebViewURL:pushURL stateObjectJSON:stateObject];
2446 base::WeakNSObject<CRWWebController> weakSelf(self);
2447 [self evaluateJavaScript:replaceWebViewJS
2448 stringResultHandler:^(NSString*, NSError*) {
2449 if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
2451 base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
2452 [strongSelf optOutScrollsToTopForSubviews];
2453 // Notify the observers.
2454 strongSelf.get()->_webStateImpl->OnHistoryStateChanged();
2455 [strongSelf didFinishNavigation];
2460 - (BOOL)handleWindowHistoryDidReplaceStateMessage:
2461 (base::DictionaryValue*)message
2462 context:(NSDictionary*)context {
2463 std::string pageURL;
2464 std::string baseURL;
2465 if (!message->GetString("pageUrl", &pageURL) ||
2466 !message->GetString("baseUrl", &baseURL)) {
2467 DLOG(WARNING) << "JS message parameter not found: pageUrl or baseUrl";
2470 GURL replaceURL = web::history_state_util::GetHistoryStateChangeUrl(
2471 [self currentURL], GURL(baseURL), pageURL);
2472 // UIWebView seems to choke on unicode characters that haven't been
2473 // escaped; escape the URL now so the expected load URL is correct.
2474 replaceURL = URLEscapedForHistory(replaceURL);
2475 if (!replaceURL.is_valid())
2477 const NavigationManagerImpl& navigationManager =
2478 _webStateImpl->GetNavigationManagerImpl();
2479 web::NavigationItem* navItem = [self currentNavItem];
2480 // ReplaceState happened before first navigation entry or called right
2481 // after window.open when the url is empty/not valid.
2483 (navigationManager.GetEntryCount() <= 1 && navItem->GetURL().is_empty()))
2485 if (!web::history_state_util::IsHistoryStateChangeValid(navItem->GetURL(),
2487 // A redirect may have occurred just prior to the replaceState. Check if
2488 // the URL needs to be updated.
2489 [self checkForUnexpectedURLChange];
2491 if (!web::history_state_util::IsHistoryStateChangeValid(
2492 [self currentNavItem]->GetURL(), replaceURL)) {
2493 // If the current session entry URL origin still doesn't match
2494 // replaceURL's origin, ignore the replaceState. This can happen if a
2495 // new URL is loaded just before the replaceState.
2498 std::string stateObjectJSON;
2499 if (!message->GetString("stateObject", &stateObjectJSON)) {
2500 DLOG(WARNING) << "JS message parameter not found: stateObject";
2503 NSString* stateObject = base::SysUTF8ToNSString(stateObjectJSON);
2504 _URLOnStartLoading = replaceURL;
2505 _lastRegisteredRequestURL = replaceURL;
2506 [self replaceStateWithPageURL:replaceURL stateObject:stateObject];
2507 NSString* replaceStateJS = [self javascriptToReplaceWebViewURL:replaceURL
2508 stateObjectJSON:stateObject];
2509 base::WeakNSObject<CRWWebController> weakSelf(self);
2510 [self evaluateJavaScript:replaceStateJS
2511 stringResultHandler:^(NSString*, NSError*) {
2512 if (!weakSelf || weakSelf.get()->_isBeingDestroyed)
2514 base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
2515 [strongSelf didFinishNavigation];
2522 - (BOOL)wantsKeyboardShield {
2523 if (_nativeController &&
2524 [_nativeController respondsToSelector:@selector(wantsKeyboardShield)]) {
2525 return [_nativeController wantsKeyboardShield];
2530 - (BOOL)wantsLocationBarHintText {
2531 if (_nativeController &&
2533 respondsToSelector:@selector(wantsLocationBarHintText)]) {
2534 return [_nativeController wantsLocationBarHintText];
2539 // TODO(stuartmorgan): This method conflates document changes and URL changes;
2540 // we should be distinguishing better, and be clear about the expected
2541 // WebDelegate and WCO callbacks in each case.
2542 - (void)webPageChanged {
2543 DCHECK(_loadPhase == web::LOAD_REQUESTED);
2545 const GURL currentURL([self currentURL]);
2546 web::Referrer referrer = [self currentReferrer];
2547 // If no referrer was known in advance, record it now. (If there was one,
2548 // keep it since it will have a more accurate URL and policy than what can
2549 // be extracted from the landing page.)
2550 web::NavigationItem* currentItem = [self currentNavItem];
2551 if (!currentItem->GetReferrer().url.is_valid()) {
2552 currentItem->SetReferrer(referrer);
2555 // TODO(stuartmorgan): This shouldn't be called for hash state or
2556 // push/replaceState.
2557 [self resetDocumentSpecificState];
2559 [self didStartLoadingURL:currentURL updateHistory:YES];
2562 - (void)resetDocumentSpecificState {
2563 _lastClickTimeInSeconds = -DBL_MAX;
2564 _clickInProgress = NO;
2566 _lastSeenWindowID.reset([[_windowIDJSManager windowId] copy]);
2569 - (void)didStartLoadingURL:(const GURL&)url updateHistory:(BOOL)updateHistory {
2570 _loadPhase = web::PAGE_LOADING;
2571 _URLOnStartLoading = url;
2572 _scrollStateOnStartLoading = self.pageScrollState;
2574 _userInteractionRegistered = NO;
2576 [[self sessionController] commitPendingEntry];
2577 _webStateImpl->GetRequestTracker()->StartPageLoad(
2578 url, [[self sessionController] currentEntry]);
2579 [_delegate webDidStartLoadingURL:url shouldUpdateHistory:updateHistory];
2582 - (BOOL)checkForUnexpectedURLChange {
2583 // Subclasses may override this method to check for and handle URL changes.
2588 if (_nativeController &&
2589 [_nativeController respondsToSelector:@selector(wasShown)]) {
2590 [_nativeController wasShown];
2597 if (_nativeController &&
2598 [_nativeController respondsToSelector:@selector(wasHidden)]) {
2599 [_nativeController wasHidden];
2603 + (BOOL)webControllerCanShow:(const GURL&)url {
2604 return web::UrlHasWebScheme(url) ||
2605 web::GetWebClient()->IsAppSpecificURL(url) ||
2606 url.SchemeIs(url::kFileScheme) || url.SchemeIs(url::kAboutScheme);
2609 - (void)setUserInteractionRegistered:(BOOL)flag {
2610 _userInteractionRegistered = flag;
2613 - (BOOL)userInteractionRegistered {
2614 return _userInteractionRegistered;
2617 - (BOOL)useDesktopUserAgent {
2618 web::NavigationItem* item = [self currentNavItem];
2619 return item && item->IsOverridingUserAgent();
2622 - (void)cachePOSTDataForRequest:(NSURLRequest*)request
2623 inSessionEntry:(CRWSessionEntry*)currentSessionEntry {
2624 NSUInteger maxPOSTDataSizeInBytes = 4096;
2625 NSString* cookieHeaderName = @"cookie";
2627 web::NavigationItemImpl* currentItem = currentSessionEntry.navigationItemImpl;
2628 DCHECK(currentItem);
2629 const bool shouldUpdateEntry =
2630 ui::PageTransitionCoreTypeIs(currentItem->GetTransitionType(),
2631 ui::PAGE_TRANSITION_FORM_SUBMIT) &&
2632 ![request HTTPBodyStream] && // Don't cache streams.
2633 !currentItem->HasPostData() &&
2634 currentItem->GetURL() == net::GURLWithNSURL([request URL]);
2635 const bool belowSizeCap =
2636 [[request HTTPBody] length] < maxPOSTDataSizeInBytes;
2637 DLOG_IF(WARNING, shouldUpdateEntry && !belowSizeCap)
2638 << "Data in POST request exceeds the size cap (" << maxPOSTDataSizeInBytes
2639 << " bytes), and will not be cached.";
2641 if (shouldUpdateEntry && belowSizeCap) {
2642 currentItem->SetPostData([request HTTPBody]);
2643 currentItem->ResetHttpRequestHeaders();
2644 currentItem->AddHttpRequestHeaders([request allHTTPHeaderFields]);
2645 // Don't cache the "Cookie" header.
2646 // According to NSURLRequest documentation, |-valueForHTTPHeaderField:| is
2647 // case insensitive, so it's enough to test the lower case only.
2648 if ([request valueForHTTPHeaderField:cookieHeaderName]) {
2649 // Case insensitive search in |headers|.
2650 NSSet* cookieKeys = [currentItem->GetHttpRequestHeaders()
2651 keysOfEntriesPassingTest:^(id key, id obj, BOOL* stop) {
2652 NSString* header = (NSString*)key;
2654 [header caseInsensitiveCompare:cookieHeaderName] ==
2659 DCHECK_EQ(1u, [cookieKeys count]);
2660 currentItem->RemoveHttpRequestHeaderForKey([cookieKeys anyObject]);
2665 // TODO(stuartmorgan): This is mostly logic from the original UIWebView delegate
2666 // method, which provides less information than the WKWebView version. Audit
2667 // this for things that should be handled in the subclass instead.
2668 - (BOOL)shouldAllowLoadWithRequest:(NSURLRequest*)request
2669 targetFrame:(const web::FrameInfo*)targetFrame
2670 isLinkClick:(BOOL)isLinkClick {
2671 GURL requestURL = net::GURLWithNSURL(request.URL);
2673 // Check if the request should be delayed.
2674 if (_externalRequest && _externalRequest->url == requestURL) {
2675 // Links that can't be shown in a tab by Chrome but can be handled by
2676 // external apps (e.g. tel:, mailto:) are opened directly despite the target
2677 // attribute on the link. We don't open a new tab for them because Mobile
2678 // Safari doesn't do that (and sites are expecting us to do the same) and
2679 // also because there would be nothing shown in that new tab; it would
2680 // remain on about:blank (see crbug.com/240178)
2681 if ([CRWWebController webControllerCanShow:requestURL] ||
2682 ![_delegate openExternalURL:requestURL]) {
2683 web::NewWindowInfo windowInfo = *_externalRequest;
2684 dispatch_async(dispatch_get_main_queue(), ^{
2685 [self openPopupWithInfo:windowInfo];
2688 _externalRequest.reset();
2692 BOOL shouldCheckNativeApp = [self cancellable];
2694 // Check if the link navigation leads to a launch of an external app.
2695 // TODO(shreyasv): Change this such that handling/stealing of link navigations
2696 // is delegated to the WebDelegate and the logic around external app launching
2697 // is moved there as well.
2698 if (shouldCheckNativeApp || isLinkClick) {
2699 // Check If the URL is handled by a native app.
2700 if ([self urlTriggersNativeAppLaunch:requestURL
2701 sourceURL:[self currentNavigationURL]]) {
2702 // External app has been launched successfully. Stop the current page
2703 // load operation (e.g. notifying all observers) and record the URL so
2704 // that errors reported following the 'NO' reply can be safely ignored.
2705 if ([self cancellable])
2706 [_delegate webPageOrderedClose];
2708 [_openedApplicationURL addObject:request.URL];
2713 // The WebDelegate may instruct the CRWWebController to stop loading, and
2714 // instead instruct the next page to be loaded in an animation.
2715 GURL mainDocumentURL = net::GURLWithNSURL(request.mainDocumentURL);
2716 DCHECK(self.webView);
2717 if (![self shouldOpenURL:requestURL
2718 mainDocumentURL:mainDocumentURL
2719 linkClicked:isLinkClick]) {
2723 // If the URL doesn't look like one we can show, try to open the link with an
2724 // external application.
2725 // TODO(droger): Check transition type before opening an external
2726 // application? For example, only allow it for TYPED and LINK transitions.
2727 if (![CRWWebController webControllerCanShow:requestURL]) {
2728 if (![self shouldOpenExternalURL:requestURL]) {
2732 // Abort load if navigation is hapenning on the main frame. If |targetFrame|
2733 // is unknown use heuristic to guess the target frame by comparing
2734 // documentURL and navigation URL. This heuristic may have false positives.
2735 bool shouldAbortLoad = targetFrame ? targetFrame->is_main_frame
2736 : requestURL == mainDocumentURL;
2737 if (shouldAbortLoad)
2740 if ([_delegate openExternalURL:requestURL]) {
2741 // Record the URL so that errors reported following the 'NO' reply can be
2743 [_openedApplicationURL addObject:request.URL];
2749 if ([[request HTTPMethod] isEqualToString:@"POST"]) {
2750 [self cachePOSTDataForRequest:request
2751 inSessionEntry:[self currentSessionEntry]];
2757 - (void)restoreStateAfterURLRejection {
2758 [[self sessionController] discardNonCommittedEntries];
2760 // Re-register the user agent, because UIWebView will sometimes try to read
2761 // the agent again from a saved search result page in which no other page has
2762 // yet been loaded. See crbug.com/260370.
2763 [self registerUserAgent];
2765 // Reset |_lastRegisteredRequestURL| so that it reflects the URL from before
2766 // the load was rejected. This value may be out of sync because
2767 // |_lastRegisteredRequestURL| may have already been updated before the load
2769 _lastRegisteredRequestURL = [self currentURL];
2770 _loadPhase = web::PAGE_LOADING;
2771 [self didFinishNavigation];
2774 - (void)handleLoadError:(NSError*)error inMainFrame:(BOOL)inMainFrame {
2775 // Attempt to translate iOS errors into their corresponding net errors.
2776 error = web::NetErrorFromError(error);
2778 if ([error code] == NSURLErrorUnsupportedURL)
2780 // In cases where a Plug-in handles the load do not take any further action.
2781 if ([[error domain] isEqual:WebKitErrorDomain] &&
2782 ([error code] == WebKitErrorPlugInLoadFailed ||
2783 [error code] == WebKitErrorCannotShowURL))
2786 // Continue processing only if the error is on the main request or is the
2787 // result of a user interaction.
2788 NSDictionary* userInfo = [error userInfo];
2789 // |userinfo| contains the request creation date as a NSDate.
2790 NSTimeInterval requestCreationDate =
2791 [[userInfo objectForKey:@"CreationDate"] timeIntervalSinceReferenceDate];
2792 bool userInteracted = false;
2793 if (requestCreationDate != 0.0) {
2794 NSTimeInterval timeSinceInteraction =
2795 requestCreationDate - _lastClickTimeInSeconds;
2796 // The error is considered to be the result of a user interaction if any
2797 // interaction happened just before the request was made.
2798 // TODO(droger): If the user interacted with the page after the request was
2799 // made (i.e. creationTimeSinceLastInteraction < 0), then
2800 // |_lastClickTimeInSeconds| has been overridden. The current behavior is to
2801 // discard the interstitial in that case. A better decision could be made if
2802 // we had a history of all the user interactions instead of just the last
2805 timeSinceInteraction < kMaximumDelayForUserInteractionInSeconds &&
2806 _lastClickTimeInSeconds > _lastTransferTimeInSeconds &&
2807 timeSinceInteraction >= 0.0;
2809 // If the error does not have timing information, check if the user
2810 // interacted with the page recently.
2811 userInteracted = [self userIsInteracting];
2813 if (!inMainFrame && !userInteracted)
2816 NSURL* errorURL = [NSURL
2817 URLWithString:[userInfo objectForKey:NSURLErrorFailingURLStringErrorKey]];
2818 const GURL errorGURL = net::GURLWithNSURL(errorURL);
2820 // Handles Frame Load Interrupted errors from WebView.
2821 if ([[error domain] isEqualToString:WebKitErrorDomain] &&
2822 [error code] == WebKitErrorFrameLoadInterruptedByPolicyChange) {
2823 // See if the delegate wants to handle this case.
2824 if (errorGURL.is_valid() &&
2826 respondsToSelector:@selector(
2827 controllerForUnhandledContentAtURL:)]) {
2828 id<CRWNativeContent> controller =
2829 [_delegate controllerForUnhandledContentAtURL:errorGURL];
2831 [self loadCompleteWithSuccess:NO];
2832 [self removeWebViewAllowingCachedReconstruction:NO];
2833 [self setNativeController:controller];
2834 [self loadNativeViewWithSuccess:YES];
2839 // Otherwise, handle the error normally.
2840 if ([_openedApplicationURL containsObject:errorURL])
2842 // Certain frame errors don't have URL information for some reason; for
2843 // those cases (so far the only known case is plugin content loaded directly
2844 // in a frame) just ignore the error. See crbug.com/414295
2846 DCHECK(!inMainFrame);
2849 // The wrapper error uses the URL of the error and not the requested URL
2850 // (which can be different in case of a redirect) to match desktop Chrome
2852 NSError* wrapperError = [NSError
2853 errorWithDomain:[error domain]
2856 NSURLErrorFailingURLStringErrorKey : [errorURL absoluteString],
2857 NSUnderlyingErrorKey : error
2859 [self loadCompleteWithSuccess:NO];
2860 [self loadErrorInNativeView:wrapperError];
2864 // Ignore cancelled errors.
2865 if ([error code] == NSURLErrorCancelled) {
2866 NSError* underlyingError = [userInfo objectForKey:NSUnderlyingErrorKey];
2867 if (underlyingError) {
2868 DCHECK([underlyingError isKindOfClass:[NSError class]]);
2870 // The Error contains an NSUnderlyingErrorKey so it's being generated
2871 // in the Chrome network stack. Aborting the load in this case.
2874 switch ([underlyingError code]) {
2875 case net::ERR_ABORTED:
2876 // |NSURLErrorCancelled| errors with underlying net error code
2877 // |net::ERR_ABORTED| are used by the Chrome network stack to
2878 // indicate that the current load should be aborted and the pending
2879 // entry should be discarded.
2880 [[self sessionController] discardNonCommittedEntries];
2882 case net::ERR_BLOCKED_BY_CLIENT:
2883 // |NSURLErrorCancelled| errors with underlying net error code
2884 // |net::ERR_BLOCKED_BY_CLIENT| are used by the Chrome network stack
2885 // to indicate that the current load should be aborted and the pending
2886 // entry should be kept.
2895 [self loadCompleteWithSuccess:NO];
2896 [self loadErrorInNativeView:error];
2902 - (void)createWebUIForURL:(const GURL&)URL {
2903 _webStateImpl->CreateWebUI(URL);
2906 - (void)clearWebUI {
2907 _webStateImpl->ClearWebUI();
2911 #pragma mark UIGestureRecognizerDelegate
2913 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2914 shouldRecognizeSimultaneouslyWithGestureRecognizer:
2915 (UIGestureRecognizer*)otherGestureRecognizer {
2916 // Allows the custom UILongPressGestureRecognizer to fire simultaneously with
2917 // other recognizers.
2921 - (BOOL)gestureRecognizer:(UIGestureRecognizer*)gestureRecognizer
2922 shouldReceiveTouch:(UITouch*)touch {
2923 // Expect only _contextMenuRecognizer.
2924 DCHECK([gestureRecognizer isEqual:_contextMenuRecognizer]);
2925 if (![self supportsCustomContextMenu]) {
2926 // Fetching context menu info is not a free operation, early return if a
2927 // context menu should not be shown.
2931 // This is custom long press gesture recognizer. By the time the gesture is
2932 // recognized the web controller needs to know if there is a link under the
2933 // touch. If there a link, the web controller will reject system's context
2934 // menu and show another one. If for some reason context menu info is not
2935 // fetched - system context menu will be shown.
2936 [self setDOMElementForLastTouch:nullptr];
2937 base::WeakNSObject<CRWWebController> weakSelf(self);
2938 [self fetchDOMElementAtPoint:[touch locationInView:self.webView]
2939 completionHandler:^(scoped_ptr<base::DictionaryValue> element) {
2940 [weakSelf setDOMElementForLastTouch:element.Pass()];
2945 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer*)gestureRecognizer {
2946 // Expect only _contextMenuRecognizer.
2947 DCHECK([gestureRecognizer isEqual:_contextMenuRecognizer]);
2948 if (!self.webView || ![self supportsCustomContextMenu]) {
2949 // Show the context menu iff currently displaying a web view.
2950 // Do nothing for native views.
2954 UMA_HISTOGRAM_BOOLEAN("WebController.FetchContextMenuInfoAsyncSucceeded",
2955 _DOMElementForLastTouch);
2957 return _DOMElementForLastTouch && !_DOMElementForLastTouch->empty();
2961 #pragma mark CRWRequestTrackerDelegate
2963 - (BOOL)isForStaticFileRequests {
2967 - (void)updatedSSLStatus:(const web::SSLStatus&)sslStatus
2968 forPageUrl:(const GURL&)url
2969 userInfo:(id)userInfo {
2970 // |userInfo| is a CRWSessionEntry.
2971 web::NavigationItem* item =
2972 [static_cast<CRWSessionEntry*>(userInfo) navigationItem];
2974 return; // This is a request update for an entry that no longer exists.
2976 // This condition happens infrequently when a page load is misinterpreted as
2977 // a resource load from a previous page. This can happen when moving quickly
2978 // back and forth through history, the notifications from the web view on the
2979 // UI thread and the one from the requests at the net layer may get out of
2980 // sync. This catches this case and prevent updating an entry with the wrong
2982 if (item->GetURL().GetOrigin() != url.GetOrigin())
2985 if (item->GetSSL().Equals(sslStatus))
2986 return; // No need to update with the same data.
2988 item->GetSSL() = sslStatus;
2990 // Notify the UI it needs to refresh if the updated entry is the current
2992 if (userInfo == self.currentSessionEntry) {
2993 [self didUpdateSSLStatusForCurrentNavigationItem];
2997 - (void)handleResponseHeaders:(net::HttpResponseHeaders*)headers
2998 requestUrl:(const GURL&)requestUrl {
2999 _webStateImpl->OnHttpResponseHeadersReceived(headers, requestUrl);
3002 - (void)presentSSLError:(const net::SSLInfo&)info
3003 forSSLStatus:(const web::SSLStatus&)status
3004 onUrl:(const GURL&)url
3005 recoverable:(BOOL)recoverable
3006 callback:(SSLErrorCallback)shouldContinue {
3008 DCHECK_EQ(url, [self currentNavigationURL]);
3009 [_delegate presentSSLError:info
3011 recoverable:recoverable
3012 callback:shouldContinue];
3015 - (void)updatedProgress:(float)progress {
3017 respondsToSelector:@selector(webController:didUpdateProgress:)]) {
3018 [_delegate webController:self didUpdateProgress:progress];
3022 - (void)certificateUsed:(net::X509Certificate*)certificate
3023 forHost:(const std::string&)host
3024 status:(net::CertStatus)status {
3025 [[[self sessionController] sessionCertificatePolicyManager]
3026 registerAllowedCertificate:certificate
3031 - (void)clearCertificates {
3032 [[[self sessionController] sessionCertificatePolicyManager]
3037 #pragma mark Popup handling
3039 - (void)openPopupWithInfo:(const web::NewWindowInfo&)windowInfo {
3040 const GURL url(windowInfo.url);
3041 const GURL currentURL([self currentNavigationURL]);
3042 NSString* windowName = windowInfo.window_name.get();
3043 web::Referrer referrer(currentURL, windowInfo.referrer_policy);
3044 base::WeakNSObject<CRWWebController> weakSelf(self);
3045 void (^showPopupHandler)() = ^{
3046 CRWWebController* child = [[weakSelf delegate] webPageOrderedOpen:url
3048 windowName:windowName
3050 DCHECK(!child || child.sessionController.openedByDOM);
3053 BOOL showPopup = windowInfo.user_is_interacting ||
3054 (![self shouldBlockPopupWithURL:url sourceURL:currentURL]);
3057 } else if ([_delegate
3058 respondsToSelector:@selector(webController:didBlockPopup:)]) {
3059 web::BlockedPopupInfo blockedPopupInfo(url, referrer, windowName,
3061 [_delegate webController:self didBlockPopup:blockedPopupInfo];
3066 #pragma mark TouchTracking
3068 - (void)touched:(BOOL)touched {
3069 _clickInProgress = touched;
3071 _userInteractionRegistered = YES;
3072 _lastClickTimeInSeconds = CFAbsoluteTimeGetCurrent();
3076 - (CRWTouchTrackingRecognizer*)touchTrackingRecognizer {
3077 if (!_touchTrackingRecognizer) {
3078 _touchTrackingRecognizer.reset(
3079 [[CRWTouchTrackingRecognizer alloc] initWithDelegate:self]);
3081 return _touchTrackingRecognizer.get();
3084 - (BOOL)userIsInteracting {
3085 // If page transfer started after last click, user is deemed to be no longer
3087 if (_lastTransferTimeInSeconds > _lastClickTimeInSeconds)
3089 return [self userClickedRecently];
3092 - (BOOL)userClickedRecently {
3093 return _clickInProgress ||
3094 ((CFAbsoluteTimeGetCurrent() - _lastClickTimeInSeconds) <
3095 kMaximumDelayForUserInteractionInSeconds);
3098 #pragma mark Placeholder Overlay Methods
3100 - (void)addPlaceholderOverlay {
3101 if (!_overlayPreviewMode) {
3102 // Create |kSnapshotOverlayDelay| second timer to remove image with
3104 [self performSelector:@selector(removePlaceholderOverlay)
3106 afterDelay:kSnapshotOverlayDelay];
3109 // Add overlay image.
3110 _placeholderOverlayView.reset([[UIImageView alloc] init]);
3111 CGRect frame = [self visibleFrame];
3112 [_placeholderOverlayView setFrame:frame];
3113 [_placeholderOverlayView
3114 setAutoresizingMask:UIViewAutoresizingFlexibleWidth |
3115 UIViewAutoresizingFlexibleHeight];
3116 [_placeholderOverlayView setContentMode:UIViewContentModeScaleAspectFill];
3117 [_containerView addSubview:_placeholderOverlayView];
3119 id callback = ^(UIImage* image) {
3120 [_placeholderOverlayView setImage:image];
3122 [_delegate webController:self retrievePlaceholderOverlayImage:callback];
3124 if (!_placeholderOverlayView.get().image) {
3125 // TODO(justincohen): This is just a blank white image. Consider fading in
3126 // the snapshot when it comes in instead.
3127 // TODO(shreyasv): This is just a blank white image. Consider adding an API
3128 // so that the delegate can return something immediately for the default
3130 _placeholderOverlayView.get().image = [[self class] defaultSnapshotImage];
3134 - (void)removePlaceholderOverlay {
3135 if (!_placeholderOverlayView || _overlayPreviewMode)
3138 [NSObject cancelPreviousPerformRequestsWithTarget:self
3139 selector:@selector(removeOverlay)
3141 // Remove overlay with transition.
3142 [UIView animateWithDuration:kSnapshotOverlayTransition
3144 [_placeholderOverlayView setAlpha:0.0f];
3146 completion:^(BOOL finished) {
3147 [_placeholderOverlayView removeFromSuperview];
3148 _placeholderOverlayView.reset();
3152 - (void)setOverlayPreviewMode:(BOOL)overlayPreviewMode {
3153 _overlayPreviewMode = overlayPreviewMode;
3155 // If we were showing the preview, remove it.
3156 if (!_overlayPreviewMode && _placeholderOverlayView) {
3157 _containerView.reset();
3158 // Reset |_placeholderOverlayView| directly instead of calling
3159 // -removePlaceholderOverlay, which removes |_placeholderOverlayView| in an
3161 [_placeholderOverlayView removeFromSuperview];
3162 _placeholderOverlayView.reset();
3163 // There are cases when resetting the contentView, above, may happen after
3164 // the web view has been created. Re-add it here, rather than
3165 // relying on a subsequent call to loadCurrentURLInWebView.
3167 [[self view] addSubview:self.webView];
3172 - (void)internalSuppressDialogs:(BOOL)suppressFlag notify:(BOOL)notifyFlag {
3173 NSString* const kSetSuppressDialogs =
3174 [NSString stringWithFormat:@"__gCrWeb.setSuppressDialogs(%d, %d);",
3175 suppressFlag, notifyFlag];
3176 [self setSuppressDialogsWithHelperScript:kSetSuppressDialogs];
3179 - (void)setPageDialogOpenPolicy:(web::PageDialogOpenPolicy)policy {
3181 case web::DIALOG_POLICY_ALLOW:
3182 [self setSuppressDialogs:NO notify:NO];
3184 case web::DIALOG_POLICY_NOTIFY_FIRST:
3185 [self setSuppressDialogs:NO notify:YES];
3187 case web::DIALOG_POLICY_SUPPRESS:
3188 [self setSuppressDialogs:YES notify:YES];
3194 - (void)setSuppressDialogs:(BOOL)suppressFlag notify:(BOOL)notifyFlag {
3195 if (self.webView && [_earlyScriptManager hasBeenInjected]) {
3196 [self internalSuppressDialogs:suppressFlag notify:notifyFlag];
3198 _setSuppressDialogsLater = suppressFlag;
3199 _setNotifyAboutDialogsLater = notifyFlag;
3204 #pragma mark Session Information
3206 - (CRWSessionController*)sessionController {
3207 DCHECK(_webStateImpl);
3208 return _webStateImpl->GetNavigationManagerImpl().GetSessionController();
3211 - (CRWSessionEntry*)currentSessionEntry {
3212 return [[self sessionController] currentEntry];
3215 - (web::NavigationItem*)currentNavItem {
3216 // This goes through the legacy Session* interface rather than Navigation*
3217 // because it is itself a legacy method that should not exist, and this
3218 // avoids needing to add a GetActiveItem to NavigationManager. If/when this
3219 // method chain becomes a blocker to eliminating SessionController, the logic
3220 // can be moved here, using public NavigationManager getters. That's not
3221 // done now in order to avoid code duplication.
3222 return [[self currentSessionEntry] navigationItem];
3225 - (const GURL&)currentNavigationURL {
3226 // TODO(stuartmorgan): Fix the fact that this method doesn't have clear usage
3227 // delination that would allow changing to one of the non-deprecated URL
3229 web::NavigationItem* item = [self currentNavItem];
3230 return item ? item->GetVirtualURL() : GURL::EmptyGURL();
3233 - (ui::PageTransition)currentTransition {
3234 if ([self currentNavItem])
3235 return [self currentNavItem]->GetTransitionType();
3237 return ui::PageTransitionFromInt(0);
3240 - (web::Referrer)currentSessionEntryReferrer {
3241 web::NavigationItem* currentItem = [self currentNavItem];
3242 return currentItem ? currentItem->GetReferrer() : web::Referrer();
3245 - (NSData*)currentPOSTData {
3246 DCHECK([self currentSessionEntry]);
3247 return [self currentSessionEntry].navigationItemImpl->GetPostData();
3250 - (NSDictionary*)currentHttpHeaders {
3251 DCHECK([self currentSessionEntry]);
3252 return [self currentSessionEntry].navigationItem->GetHttpRequestHeaders();
3256 #pragma mark Page State
3258 - (void)recordStateInHistory {
3259 // Check that the url in the web view matches the url in the history entry.
3260 CRWSessionEntry* current = [self currentSessionEntry];
3261 if (current && [current navigationItem]->GetURL() == [self currentURL])
3262 [current navigationItem]->SetPageScrollState(self.pageScrollState);
3265 - (void)restoreStateFromHistory {
3266 CRWSessionEntry* current = [self currentSessionEntry];
3267 if ([current navigationItem])
3268 self.pageScrollState = [current navigationItem]->GetPageScrollState();
3271 - (web::PageScrollState)pageScrollState {
3272 web::PageScrollState scrollState;
3274 CGPoint scrollOffset = [self scrollPosition];
3275 scrollState.set_scroll_offset_x(std::floor(scrollOffset.x));
3276 scrollState.set_scroll_offset_y(std::floor(scrollOffset.y));
3277 UIScrollView* scrollView = self.webScrollView;
3278 scrollState.set_minimum_zoom_scale(scrollView.minimumZoomScale);
3279 scrollState.set_maximum_zoom_scale(scrollView.maximumZoomScale);
3280 scrollState.set_zoom_scale(scrollView.zoomScale);
3282 // TODO(kkhorimoto): Handle native views.
3287 - (void)setPageScrollState:(web::PageScrollState)pageScrollState {
3288 if (!pageScrollState.IsValid())
3291 // Page state is restored after a page load completes. If the user has
3292 // scrolled or changed the zoom scale while the page is still loading, don't
3293 // restore any state since it will confuse the user. Since the web view's
3294 // zoom scale range may have changed during rendering, check the absolute
3295 // zoom scale rather than doing a simple equality comparison.
3296 web::PageScrollState currentScrollState = self.pageScrollState;
3297 if (currentScrollState.scroll_offset_x() ==
3298 _scrollStateOnStartLoading.scroll_offset_x() &&
3299 currentScrollState.scroll_offset_y() ==
3300 _scrollStateOnStartLoading.scroll_offset_y() &&
3301 [self absoluteZoomScaleForScrollState:currentScrollState] ==
3302 [self absoluteZoomScaleForScrollState:_scrollStateOnStartLoading]) {
3303 base::WeakNSObject<CRWWebController> weakSelf(self);
3304 [self queryUserScalableProperty:^(BOOL isUserScalable) {
3305 base::scoped_nsobject<CRWWebController> strongSelf([weakSelf retain]);
3306 [strongSelf applyPageScrollState:pageScrollState
3307 userScalable:isUserScalable];
3313 - (void)applyPageScrollState:(const web::PageScrollState&)scrollState
3314 userScalable:(BOOL)isUserScalable {
3315 DCHECK(scrollState.IsValid());
3316 if (isUserScalable) {
3317 [self prepareToApplyWebViewScrollZoomScale];
3318 [self applyWebViewScrollZoomScaleFromScrollState:scrollState];
3319 [self finishApplyingWebViewScrollZoomScale];
3321 [self applyWebViewScrollOffsetFromScrollState:scrollState];
3324 - (void)prepareToApplyWebViewScrollZoomScale {
3325 id webView = self.webView;
3326 if (![webView respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
3330 UIView* contentView = [webView viewForZoomingInScrollView:self.webScrollView];
3333 respondsToSelector:@selector(scrollViewWillBeginZooming:withView:)]) {
3334 [webView scrollViewWillBeginZooming:self.webScrollView
3335 withView:contentView];
3339 - (void)finishApplyingWebViewScrollZoomScale {
3340 id webView = self.webView;
3341 if ([webView respondsToSelector:@selector(scrollViewDidEndZooming:
3344 [webView respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
3345 // This correctly sets the content's frame in the scroll view to
3346 // fit the web page and upscales the content so that it isn't
3348 UIView* contentView =
3349 [webView viewForZoomingInScrollView:self.webScrollView];
3350 [webView scrollViewDidEndZooming:self.webScrollView
3351 withView:contentView
3352 atScale:self.webScrollView.zoomScale];
3356 - (void)applyWebViewScrollZoomScaleFromScrollState:
3357 (const web::PageScrollState&)scrollState {
3358 // Subclasses must implement this method.
3362 - (void)applyWebViewScrollOffsetFromScrollState:
3363 (const web::PageScrollState&)scrollState {
3364 DCHECK(scrollState.IsValid());
3365 CGPoint scrollOffset =
3366 CGPointMake(scrollState.scroll_offset_x(), scrollState.scroll_offset_y());
3367 if (_loadPhase == web::PAGE_LOADED) {
3368 // If the page is loaded, update the scroll immediately.
3369 [self.webScrollView setContentOffset:scrollOffset];
3371 // If the page isn't loaded, store the action to update the scroll
3372 // when the page finishes loading.
3373 base::WeakNSObject<UIScrollView> weakScrollView(self.webScrollView);
3374 base::scoped_nsprotocol<ProceduralBlock> action([^{
3375 [weakScrollView setContentOffset:scrollOffset];
3377 [_pendingLoadCompleteActions addObject:action];
3382 #pragma mark Web Page Features
3384 // TODO(eugenebut): move JS parsing code to a separate file.
3385 - (void)queryUserScalableProperty:(void (^)(BOOL))responseHandler {
3386 NSString* const kViewPortContentQuery =
3387 @"var viewport = document.querySelector('meta[name=\"viewport\"]');"
3388 "viewport ? viewport.content : '';";
3389 [self evaluateJavaScript:kViewPortContentQuery
3390 stringResultHandler:^(NSString* viewPortContent, NSError* error) {
3392 GetUserScalablePropertyFromViewPortContent(viewPortContent));
3396 - (void)fetchWebPageWidthWithCompletionHandler:(void (^)(CGFloat))handler {
3397 if (!self.webView) {
3402 [self evaluateJavaScript:@"__gCrWeb.getPageWidth();"
3403 stringResultHandler:^(NSString* pageWidthAsString, NSError*) {
3404 handler([pageWidthAsString floatValue]);
3408 - (void)fetchDOMElementAtPoint:(CGPoint)point
3410 (void (^)(scoped_ptr<base::DictionaryValue>))handler {
3412 // Convert point into web page's coordinate system (which may be scaled and/or
3414 CGPoint scrollOffset = self.scrollPosition;
3415 CGFloat webViewContentWidth = self.webScrollView.contentSize.width;
3416 base::WeakNSObject<CRWWebController> weakSelf(self);
3417 [self fetchWebPageWidthWithCompletionHandler:^(CGFloat pageWidth) {
3418 CGFloat scale = pageWidth / webViewContentWidth;
3419 CGPoint localPoint = CGPointMake((point.x + scrollOffset.x) * scale,
3420 (point.y + scrollOffset.y) * scale);
3421 NSString* const kGetElementScript =
3422 [NSString stringWithFormat:@"__gCrWeb.getElementFromPoint(%g, %g);",
3423 localPoint.x, localPoint.y];
3424 [weakSelf evaluateJavaScript:kGetElementScript
3425 JSONResultHandler:^(scoped_ptr<base::Value> element, NSError*) {
3426 // Release raw element and call handler with DictionaryValue.
3427 scoped_ptr<base::DictionaryValue> elementAsDict;
3429 base::DictionaryValue* elementAsDictPtr = nullptr;
3430 element.release()->GetAsDictionary(&elementAsDictPtr);
3431 // |rawElement| and |elementPtr| now point to the same
3432 // memory. |elementPtr| ownership will be transferred to
3433 // |element| scoped_ptr.
3434 elementAsDict.reset(elementAsDictPtr);
3436 handler(elementAsDict.Pass());
3441 - (NSDictionary*)contextMenuInfoForElement:(base::DictionaryValue*)element {
3443 NSMutableDictionary* mutableInfo = [NSMutableDictionary dictionary];
3444 NSString* title = nil;
3446 if (element->GetString("href", &href)) {
3447 mutableInfo[web::kContextLinkURLString] = base::SysUTF8ToNSString(href);
3449 if (linkURL.SchemeIs(url::kJavaScriptScheme)) {
3450 title = @"JavaScript";
3452 DCHECK(web::GetWebClient());
3453 const std::string& acceptLangs = web::GetWebClient()->GetAcceptLangs(
3454 self.webStateImpl->GetBrowserState());
3455 base::string16 urlText = net::FormatUrl(GURL(href), acceptLangs);
3456 title = base::SysUTF16ToNSString(urlText);
3460 if (element->GetString("src", &src)) {
3461 mutableInfo[web::kContextImageURLString] = base::SysUTF8ToNSString(src);
3463 title = base::SysUTF8ToNSString(src);
3464 if ([title hasPrefix:@"data:"])
3467 std::string titleAttribute;
3468 if (element->GetString("title", &titleAttribute))
3469 title = base::SysUTF8ToNSString(titleAttribute);
3470 std::string referrerPolicy;
3471 element->GetString("referrerPolicy", &referrerPolicy);
3472 mutableInfo[web::kContextLinkReferrerPolicy] =
3473 @([self referrerPolicyFromString:referrerPolicy]);
3475 mutableInfo[web::kContextTitle] = title;
3476 return [[mutableInfo copy] autorelease];
3480 #pragma mark Fullscreen
3482 - (CGRect)visibleFrame {
3483 CGRect frame = [_containerView bounds];
3484 CGFloat headerHeight = [self headerHeight];
3485 frame.origin.y = headerHeight;
3486 frame.size.height -= headerHeight;
3490 - (void)optOutScrollsToTopForSubviews {
3491 NSMutableArray* stack =
3492 [NSMutableArray arrayWithArray:[self.webScrollView subviews]];
3493 while (stack.count) {
3494 UIView* current = [stack lastObject];
3495 [stack removeLastObject];
3496 [stack addObjectsFromArray:[current subviews]];
3497 if ([current isKindOfClass:[UIScrollView class]])
3498 static_cast<UIScrollView*>(current).scrollsToTop = NO;
3503 #pragma mark WebDelegate Calls
3505 - (BOOL)shouldOpenURL:(const GURL&)url
3506 mainDocumentURL:(const GURL&)mainDocumentURL
3507 linkClicked:(BOOL)linkClicked {
3508 if (![_delegate respondsToSelector:@selector(webController:
3514 return [_delegate webController:self
3516 mainDocumentURL:mainDocumentURL
3517 linkClicked:linkClicked];
3520 - (BOOL)shouldOpenExternalURL:(const GURL&)url {
3521 return [_delegate respondsToSelector:@selector(webController:
3522 shouldOpenExternalURL:)] &&
3523 [_delegate webController:self shouldOpenExternalURL:url];
3526 - (BOOL)urlTriggersNativeAppLaunch:(const GURL&)url
3527 sourceURL:(const GURL&)sourceURL {
3529 [_delegate respondsToSelector:@selector(urlTriggersNativeAppLaunch:
3531 [_delegate urlTriggersNativeAppLaunch:url sourceURL:sourceURL];
3534 - (CGFloat)headerHeight {
3535 if (![_delegate respondsToSelector:@selector(headerHeightForWebController:)])
3537 return [_delegate headerHeightForWebController:self];
3540 - (BOOL)shouldBlockPopupWithURL:(const GURL&)popupURL
3541 sourceURL:(const GURL&)sourceURL {
3542 if (![_delegate respondsToSelector:@selector(webController:
3543 shouldBlockPopupWithURL:
3547 return [_delegate webController:self
3548 shouldBlockPopupWithURL:popupURL
3549 sourceURL:sourceURL];
3552 - (void)didUpdateHistoryStateWithPageURL:(const GURL&)url {
3553 _webStateImpl->GetRequestTracker()->HistoryStateChange(url);
3554 [_delegate webDidUpdateHistoryStateWithPageURL:url];
3557 - (void)didUpdateSSLStatusForCurrentNavigationItem {
3558 if ([_delegate respondsToSelector:
3560 webControllerDidUpdateSSLStatusForCurrentNavigationItem:)]) {
3561 [_delegate webControllerDidUpdateSSLStatusForCurrentNavigationItem:self];
3565 #pragma mark CRWWebControllerScripting Methods
3567 - (void)loadHTML:(NSString*)html {
3568 [self loadHTML:html forURL:GURL(url::kAboutBlankURL)];
3571 - (void)loadHTMLForCurrentURL:(NSString*)html {
3572 [self loadHTML:html forURL:self.currentURL];
3575 - (void)loadHTML:(NSString*)html forURL:(const GURL&)url {
3576 // Remove the interstitial before doing anything else.
3577 [self clearInterstitials];
3579 DLOG_IF(WARNING, !self.webView)
3580 << "self.webView null while trying to load HTML";
3581 _loadPhase = web::LOAD_REQUESTED;
3582 [self loadWebHTMLString:html forURL:url];
3585 - (void)loadHTML:(NSString*)HTML forAppSpecificURL:(const GURL&)URL {
3586 CHECK(web::GetWebClient()->IsAppSpecificURL(URL));
3587 [self loadHTML:HTML forURL:URL];
3590 - (void)stopLoading {
3591 web::RecordAction(UserMetricsAction("Stop"));
3592 // Discard the pending and transient entried before notifying the tab model
3593 // observers of the change via |-abortLoad|.
3594 [[self sessionController] discardNonCommittedEntries];
3596 // If discarding the non-committed entries results in an app-specific URL,
3597 // reload it in its native view.
3598 if (!_nativeController &&
3599 [self shouldLoadURLInNativeView:[self currentNavigationURL]]) {
3600 [self loadCurrentURLInNativeView];
3604 - (void)orderClose {
3605 if (self.sessionController.openedByDOM) {
3606 [_delegate webPageOrderedClose];
3611 #pragma mark Testing-Only Methods
3613 - (void)injectWebView:(id)webView {
3614 [self removeWebViewAllowingCachedReconstruction:NO];
3616 _lastRegisteredRequestURL = _defaultURL;
3617 CHECK([webView respondsToSelector:@selector(scrollView)]);
3618 [_webViewProxy setWebView:webView
3619 scrollView:[static_cast<id>(webView) scrollView]];
3622 - (void)resetInjectedWebView {
3623 [self resetWebView];
3626 - (void)addObserver:(id<CRWWebControllerObserver>)observer {
3629 // We don't want our observer set to block dealloc on the observers. For the
3630 // observer container, make an object compatible with NSMutableSet that does
3631 // not perform retain or release on the contained objects (weak references).
3632 CFSetCallBacks callbacks =
3633 {0, NULL, NULL, CFCopyDescription, CFEqual, CFHash};
3634 _observers.reset(base::mac::CFToNSCast(
3635 CFSetCreateMutable(kCFAllocatorDefault, 1, &callbacks)));
3637 DCHECK(![_observers containsObject:observer]);
3638 [_observers addObject:observer];
3639 _observerBridges.push_back(
3640 new web::WebControllerObserverBridge(observer, self.webStateImpl, self));
3642 if ([observer respondsToSelector:@selector(setWebViewProxy:controller:)])
3643 [observer setWebViewProxy:_webViewProxy controller:self];
3646 - (void)removeObserver:(id<CRWWebControllerObserver>)observer {
3647 // TODO(jimblackler): make _observers use NSMapTable. crbug.com/367992
3648 DCHECK([_observers containsObject:observer]);
3649 [_observers removeObject:observer];
3650 // Remove the associated WebControllerObserverBridge.
3651 auto it = std::find_if(_observerBridges.begin(), _observerBridges.end(),
3652 [observer](web::WebControllerObserverBridge* bridge) {
3653 return bridge->web_controller_observer() == observer;
3655 DCHECK(it != _observerBridges.end());
3656 _observerBridges.erase(it);
3659 - (NSUInteger)observerCount {
3660 DCHECK_EQ(_observerBridges.size(), [_observers count]);
3661 return [_observers count];
3664 - (NSString*)windowId {
3665 return [_windowIDJSManager windowId];
3668 - (void)setWindowId:(NSString*)windowId {
3669 return [_windowIDJSManager setWindowId:windowId];
3672 - (NSString*)lastSeenWindowID {
3673 return _lastSeenWindowID;
3676 - (void)setURLOnStartLoading:(const GURL&)url {
3677 _URLOnStartLoading = url;
3680 - (const GURL&)defaultURL {
3684 - (GURL)URLOnStartLoading {
3685 return _URLOnStartLoading;
3688 - (GURL)lastRegisteredRequestURL {
3689 return _lastRegisteredRequestURL;
3692 - (void)simulateLoadRequestWithURL:(const GURL&)URL {
3693 _lastRegisteredRequestURL = URL;
3694 _loadPhase = web::LOAD_REQUESTED;
3697 - (NSString*)externalRequestWindowName {
3698 if (!_externalRequest || !_externalRequest->window_name)
3700 return _externalRequest->window_name;
3703 - (void)resetExternalRequest {
3704 _externalRequest.reset();