1 // Copyright 2014 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_ui_web_view_web_controller.h"
7 #import "base/ios/weak_nsobject.h"
8 #include "base/json/json_reader.h"
9 #include "base/json/string_escape.h"
10 #include "base/mac/bind_objc_block.h"
11 #import "base/mac/scoped_nsobject.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/metrics/field_trial.h"
14 #include "base/metrics/histogram.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/sys_string_conversions.h"
17 #include "base/timer/timer.h"
18 #include "base/values.h"
19 #import "ios/net/nsurlrequest_util.h"
20 #import "ios/web/navigation/crw_session_controller.h"
21 #import "ios/web/navigation/crw_session_entry.h"
22 #include "ios/web/net/clients/crw_redirect_network_client_factory.h"
23 #import "ios/web/net/crw_url_verifying_protocol_handler.h"
24 #include "ios/web/net/request_group_util.h"
25 #include "ios/web/public/url_scheme_util.h"
26 #include "ios/web/public/web_client.h"
27 #import "ios/web/ui_web_view_util.h"
28 #include "ios/web/web_state/frame_info.h"
29 #import "ios/web/web_state/js/crw_js_invoke_parameter_queue.h"
30 #import "ios/web/web_state/ui/crw_context_menu_provider.h"
31 #import "ios/web/web_state/ui/crw_web_controller+protected.h"
32 #import "ios/web/web_state/ui/crw_web_controller.h"
33 #import "ios/web/web_state/ui/web_view_js_utils.h"
34 #import "ios/web/web_state/web_state_impl.h"
35 #import "ios/web/web_state/web_view_creation_utils.h"
36 #import "net/base/mac/url_conversions.h"
37 #include "url/url_constants.h"
41 // The following continuous check timer frequency constants are externally
42 // available for the purpose of performance tests.
43 // Frequency for the continuous checks when a reset in the page object is
44 // anticipated shortly. In milliseconds.
45 const int64 kContinuousCheckIntervalMSHigh = 100;
47 // The maximum duration that the CRWWebController can run in high-frequency
48 // check mode before being changed back to the low frequency.
49 const int64 kContinuousCheckHighFrequencyMSMaxDuration = 5000;
51 // Frequency for the continuous checks when a reset in the page object is not
52 // anticipated; checks are only made as a precaution.
53 // The URL could be out of date for this many milliseconds, so this should not
54 // be increased without careful consideration.
55 const int64 kContinuousCheckIntervalMSLow = 3000;
59 @interface CRWUIWebViewWebController () <CRWRedirectClientDelegate,
61 // The UIWebView managed by this instance.
62 base::scoped_nsobject<UIWebView> _uiWebView;
64 // Whether caching of the current URL is enabled or not.
65 BOOL _urlCachingEnabled;
67 // Temporarily cached current URL. Only valid/set while urlCachingEnabled
69 // TODO(stuartmorgan): Change this to a struct so code using it is more
71 std::pair<GURL, web::URLVerificationTrustLevel> _cachedURL;
73 // The last time a URL with absolute trust level was computed.
74 // When an untrusted URL is retrieved from the
75 // |CRWURLVerifyingProtocolHandler|, if the last trusted URL is within
76 // |kContinuousCheckIntervalMSLow|, the trustLevel is upgraded to Mixed.
77 // The reason is that it is sometimes temporarily impossible to do a
78 // AsyncXMLHttpRequest on a web page. When this happen, it is not possible
79 // to check for the validity of the current URL. Because the checker is
80 // only checking every |kContinuousCheckIntervalMSLow| anyway, waiting this
81 // amount of time before triggering an interstitial does not weaken the
82 // security of the browser.
83 base::TimeTicks _lastCorrectURLTime;
85 // Each new UIWebView starts in a state where:
86 // - window.location.href is equal to about:blank
87 // - Ajax requests seem to be impossible
88 // Because Ajax requests are used to determine is a URL is verified, this
89 // means it is impossible to do this check until the UIWebView is in a more
90 // sane state. This variable tracks whether verifying the URL is currently
91 // impossible. It starts at YES when a new UIWebView is created,
92 // and will change to NO, as soon as either window.location.href is not
93 // about:blank anymore, or an URL verification request succeeds. This means
94 // that a malicious site that is able to change the value of
95 // window.location.href and that is loaded as the first request will be able
96 // to change its URL to about:blank. As this is not an interesting URL, it is
97 // considered acceptable.
98 BOOL _spoofableRequest;
100 // Timer used to make continuous checks on the UIWebView. Timer is
101 // running only while |webView| is non-nil.
102 scoped_ptr<base::Timer> _continuousCheckTimer;
103 // Timer to lower the check frequency automatically.
104 scoped_ptr<base::Timer> _lowerFrequencyTimer;
106 // Counts of calls to |-webViewDidStartLoad:| and |-webViewDidFinishLoad|.
107 // When |_loadCount| is equal to |_unloadCount|, the page is no longer
108 // loading. Used as a fallback to determine when the page is done loading in
109 // case document.readyState isn't sufficient.
110 // When |_loadCount| is 1, the main page is loading (as opposed to a
115 // Backs the property of the same name.
116 BOOL _inJavaScriptContext;
118 // Backs the property of the same name.
119 base::scoped_nsobject<CRWJSInvokeParameterQueue> _jsInvokeParameterQueue;
121 // Blocks message queue processing (for testing).
122 BOOL _jsMessageQueueThrottled;
124 // YES if a video is playing in fullscreen.
125 BOOL _inFullscreenVideo;
127 // Backs the property of the same name.
128 id<CRWRecurringTaskDelegate>_recurringTaskDelegate;
130 // Redirect client factory.
131 base::scoped_nsobject<CRWRedirectNetworkClientFactory>
132 redirect_client_factory_;
135 // Whether or not URL caching is enabled. Between enabling and disabling
136 // caching, calls to webURLWithTrustLevel: after the first may return the same
137 // answer without re-checking the URL.
138 @property(nonatomic, setter=setURLCachingEnabled:) BOOL urlCachingEnabled;
140 // Returns whether the current page is the web views initial (default) page.
141 - (BOOL)isDefaultPage;
143 // Returns whether the given navigation is triggered by a user link click.
144 - (BOOL)isLinkNavigation:(UIWebViewNavigationType)navigationType;
146 // Starts (at the given interval) the timer that drives runRecurringTasks to see
147 // whether or not the page has changed. This is used to work around the fact
148 // that UIWebView callbacks are not sufficiently reliable to catch every page
150 - (void)setContinuousCheckTimerInterval:(const base::TimeDelta&)interval;
152 // Evaluates the supplied JavaScript and returns the result. Will return nil
153 // if it is unable to evaluate the JavaScript.
154 - (NSString*)stringByEvaluatingJavaScriptFromString:(NSString*)script;
156 // Evaluates the user-entered JavaScript in the WebView and returns the result.
157 // Will return nil if the web view is currently not available.
158 - (NSString*)stringByEvaluatingUserJavaScriptFromString:(NSString*)script;
160 // Checks if the document is loaded, and if so triggers any necessary logic.
161 - (void)checkDocumentLoaded;
163 // Returns a new autoreleased UIWebView.
164 - (UIWebView*)createWebView;
166 // Sets value to web view property.
167 - (void)setWebView:(UIWebView*)webView;
169 // Simulates the events generated by core.js during document loading process.
170 // Used for non-HTML documents (e.g. PDF) that do not support data flow from
171 // JavaScript to obj-c via iframe injection.
172 - (void)generateMissingDocumentLifecycleEvents;
174 // Makes a best-effort attempt to retroactively construct a load request for an
175 // observed-but-unexpected navigation. Should be called any time a page
176 // change is detected as having happened without the current internal state
177 // indicating it was expected.
178 - (void)generateMissingLoadRequestWithURL:(const GURL&)currentURL
179 referrer:(const web::Referrer&)referrer;
181 // Returns a child scripting CRWWebController with the given window name.
182 - (id<CRWWebControllerScripting>)scriptingInterfaceForWindowNamed:
185 // Called when UIMoviePlayerControllerDidEnterFullscreenNotification is posted.
186 - (void)moviePlayerDidEnterFullscreen:(NSNotification*)notification;
188 // Called when UIMoviePlayerControllerDidExitFullscreenNotification is posted.
189 - (void)moviePlayerDidExitFullscreen:(NSNotification*)notification;
191 // Exits fullscreen mode for any playing videos.
192 - (void)exitFullscreenVideo;
194 // Handles presentation of the web document. Checks and handles URL changes,
195 // page refreshes, and title changes.
196 - (void)documentPresent;
198 // Handles queued JS to ObjC messages.
199 // All commands are passed via JSON strings, including parameters.
200 - (void)respondToJSInvoke;
202 // Pauses (|throttle|=YES) or resumes (|throttle|=NO) crwebinvoke message
204 - (void)setJsMessageQueueThrottled:(BOOL)throttle;
206 // Removes document load commands from the queue. Otherwise they could be
207 // handled after a new page load has begun, which would cause an unwanted
209 - (void)removeDocumentLoadCommandsFromQueue;
211 // Returns YES if the given URL has a scheme associated with JS->native calls.
212 - (BOOL)urlSchemeIsWebInvoke:(const GURL&)url;
214 // Returns window name given a message and its context.
215 - (NSString*)windowNameFromMessage:(base::DictionaryValue*)message
216 context:(NSDictionary*)context;
218 // Handles 'anchor.click' message.
219 - (BOOL)handleAnchorClickMessage:(base::DictionaryValue*)message
220 context:(NSDictionary*)context;
221 // Handles 'document.loaded' message.
222 - (BOOL)handleDocumentLoadedMessage:(base::DictionaryValue*)message
223 context:(NSDictionary*)context;
224 // Handles 'document.present' message.
225 - (BOOL)handleDocumentPresentMessage:(base::DictionaryValue*)message
226 context:(NSDictionary*)context;
227 // Handles 'document.retitled' message.
228 - (BOOL)handleDocumentRetitledMessage:(base::DictionaryValue*)message
229 context:(NSDictionary*)context;
230 // Handles 'window.close' message.
231 - (BOOL)handleWindowCloseMessage:(base::DictionaryValue*)message
232 context:(NSDictionary*)context;
233 // Handles 'window.document.write' message.
234 - (BOOL)handleWindowDocumentWriteMessage:(base::DictionaryValue*)message
235 context:(NSDictionary*)context;
236 // Handles 'window.location' message.
237 - (BOOL)handleWindowLocationMessage:(base::DictionaryValue*)message
238 context:(NSDictionary*)context;
239 // Handles 'window.open' message.
240 - (BOOL)handleWindowOpenMessage:(base::DictionaryValue*)message
241 context:(NSDictionary*)context;
242 // Handles 'window.stop' message.
243 - (BOOL)handleWindowStopMessage:(base::DictionaryValue*)message
244 context:(NSDictionary*)context;
245 // Handles 'window.unload' message.
246 - (BOOL)handleWindowUnloadMessage:(base::DictionaryValue*)message
247 context:(NSDictionary*)context;
252 // Utility to help catch unwanted JavaScript re-entries. An instance should
253 // be created on the stack any time JS will be executed.
254 // It uses an instance variable (passed in as a pointer to boolean) that needs
255 // to be initialized to false.
256 class ScopedReentryGuard {
258 explicit ScopedReentryGuard(BOOL* is_inside_javascript_context)
259 : is_inside_javascript_context_(is_inside_javascript_context) {
260 DCHECK(!*is_inside_javascript_context_);
261 *is_inside_javascript_context_ = YES;
263 ~ScopedReentryGuard() {
264 DCHECK(*is_inside_javascript_context_);
265 *is_inside_javascript_context_ = NO;
269 BOOL* is_inside_javascript_context_;
270 DISALLOW_IMPLICIT_CONSTRUCTORS(ScopedReentryGuard);
273 // Class allowing to selectively enable caching of |currentURL| on a
274 // CRWUIWebViewWebController. As long as an instance of this class lives,
275 // the CRWUIWebViewWebController passed as parameter will cache the result for
276 // |currentURL| calls.
277 class ScopedCachedCurrentUrl {
279 explicit ScopedCachedCurrentUrl(CRWUIWebViewWebController* web_controller)
280 : web_controller_(web_controller),
281 had_cached_current_url_([web_controller urlCachingEnabled]) {
282 if (!had_cached_current_url_)
283 [web_controller_ setURLCachingEnabled:YES];
286 ~ScopedCachedCurrentUrl() {
287 if (!had_cached_current_url_)
288 [web_controller_ setURLCachingEnabled:NO];
292 CRWUIWebViewWebController* web_controller_;
293 bool had_cached_current_url_;
294 DISALLOW_IMPLICIT_CONSTRUCTORS(ScopedCachedCurrentUrl);
297 // Normalizes the URL for the purposes of identifying the origin page (remove
298 // any parameters, fragments, etc.) and return an absolute string of the URL.
299 std::string NormalizedUrl(const GURL& url) {
300 GURL::Replacements replacements;
301 replacements.ClearQuery();
302 replacements.ClearRef();
303 replacements.ClearUsername();
304 replacements.ClearPassword();
305 GURL page_url(url.ReplaceComponents(replacements));
307 return page_url.spec();
310 // The maximum size of JSON message passed from JavaScript to ObjC.
311 // 256kB is an arbitrary number that was chosen to be a magnitude larger than
312 // any legitimate message.
313 const size_t kMaxMessageQueueSize = 262144;
317 @implementation CRWUIWebViewWebController
319 - (instancetype)initWithWebState:(scoped_ptr<web::WebStateImpl>)webState {
320 self = [super initWithWebState:webState.Pass()];
322 _jsInvokeParameterQueue.reset([[CRWJSInvokeParameterQueue alloc] init]);
324 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
325 [defaultCenter addObserver:self
326 selector:@selector(moviePlayerDidEnterFullscreen:)
328 @"UIMoviePlayerControllerDidEnterFullscreenNotification"
330 [defaultCenter addObserver:self
331 selector:@selector(moviePlayerDidExitFullscreen:)
333 @"UIMoviePlayerControllerDidExitFullscreenNotification"
335 _recurringTaskDelegate = self;
337 // UIWebViews require a redirect network client in order to accurately
338 // detect server redirects.
339 redirect_client_factory_.reset(
340 [[CRWRedirectNetworkClientFactory alloc] initWithDelegate:self]);
341 // WeakNSObjects cannot be dereferenced outside of the main thread, and
342 // CRWWebController must be deallocated from the main thread. Keep a
343 // reference to self on the main thread and release it after successfully
344 // adding the redirect client factory to the RequestTracker on the IO
346 __block base::scoped_nsobject<CRWUIWebViewWebController> scopedSelf(
348 web::WebThread::PostTaskAndReply(
349 web::WebThread::IO, FROM_HERE, base::BindBlock(^{
350 // Only add the factory if there is a valid request tracker.
351 web::WebStateImpl* webState = [scopedSelf webStateImpl];
352 if (webState && webState->GetRequestTracker()) {
353 webState->GetRequestTracker()->AddNetworkClientFactory(
354 redirect_client_factory_);
365 [[NSNotificationCenter defaultCenter] removeObserver:self];
369 #pragma mark - CRWWebController public method implementations
371 - (void)loadCompleteWithSuccess:(BOOL)loadSuccess {
372 [super loadCompleteWithSuccess:loadSuccess];
373 [self removeDocumentLoadCommandsFromQueue];
376 - (BOOL)keyboardDisplayRequiresUserAction {
377 return [_uiWebView keyboardDisplayRequiresUserAction];
380 - (void)setKeyboardDisplayRequiresUserAction:(BOOL)requiresUserAction {
381 [_uiWebView setKeyboardDisplayRequiresUserAction:requiresUserAction];
384 - (void)evaluateUserJavaScript:(NSString*)script {
385 [self setUserInteractionRegistered:YES];
386 // A script which contains alert() call executed by UIWebView from gcd block
387 // freezes the app (crbug.com/444106), hence this uses the NSObject API.
388 [_uiWebView performSelectorOnMainThread:
389 @selector(stringByEvaluatingJavaScriptFromString:)
394 #pragma mark Overridden public methods
398 // Turn the timer back on, and do an immediate check for anything missed
399 // while the timer was off.
400 [self.recurringTaskDelegate runRecurringTask];
401 _continuousCheckTimer->Reset();
408 // Turn the timer off, to cut down on work being done by background tabs.
409 _continuousCheckTimer->Stop();
413 // The video player is not quit/dismissed when the home button is pressed and
414 // Chrome is backgrounded (crbug.com/277206).
415 if (_inFullscreenVideo)
416 [self exitFullscreenVideo];
423 // The timers must not exist at this point, otherwise this object will leak.
424 DCHECK(!_continuousCheckTimer);
425 DCHECK(!_lowerFrequencyTimer);
428 - (void)childWindowClosed:(NSString*)windowName {
429 // Get the substring of the window name after the hash.
430 NSRange range = [windowName rangeOfString:
431 base::SysUTF8ToNSString(web::kWindowNameSeparator)];
432 if (range.location != NSNotFound) {
433 NSString* target = [windowName substringFromIndex:(range.location + 1)];
434 [self stringByEvaluatingJavaScriptFromString:
435 [NSString stringWithFormat:@"__gCrWeb.windowClosed('%@');", target]];
440 #pragma mark Testing-Only Methods
442 - (void)injectWebView:(id)webView {
443 [super injectWebView:webView];
444 [self setWebView:webView];
447 #pragma mark CRWJSInjectionEvaluatorMethods
449 - (void)evaluateJavaScript:(NSString*)script
450 stringResultHandler:(web::JavaScriptCompletion)handler {
451 NSString* safeScript = [self scriptByAddingWindowIDCheckForScript:script];
452 web::EvaluateJavaScript(_uiWebView, safeScript, handler);
455 - (web::WebViewType)webViewType {
456 return web::UI_WEB_VIEW_TYPE;
459 #pragma mark - Protected property implementations
462 return _uiWebView.get();
465 - (UIScrollView*)webScrollView {
466 return [_uiWebView scrollView];
469 - (BOOL)urlCachingEnabled {
470 return _urlCachingEnabled;
473 - (void)setURLCachingEnabled:(BOOL)enabled {
474 if (enabled == _urlCachingEnabled)
476 _urlCachingEnabled = enabled;
477 _cachedURL.first = GURL();
480 - (BOOL)ignoreURLVerificationFailures {
481 return _spoofableRequest;
485 return [self stringByEvaluatingJavaScriptFromString:
486 @"document.title.length ? document.title : ''"];
489 #pragma mark Protected method implementations
491 - (void)ensureWebViewCreated {
493 [self setWebView:[self createWebView]];
494 // Notify super class about created web view. -webViewDidChange is not
495 // called from -setWebView: as the latter used in unit tests with fake
496 // web view, which cannot be added to view hierarchy.
497 [self webViewDidChange];
501 - (void)resetWebView {
502 [self setWebView:nil];
505 - (GURL)webURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel {
506 if (_cachedURL.first.is_valid()) {
507 *trustLevel = _cachedURL.second;
508 return _cachedURL.first;
510 ScopedReentryGuard reentryGuard(&_inJavaScriptContext);
512 [CRWURLVerifyingProtocolHandler currentURLForWebView:_uiWebView
513 trustLevel:trustLevel];
515 // If verification succeeded, or the URL has changed, then the UIWebView is no
516 // longer in the initial state.
517 if (*trustLevel != web::URLVerificationTrustLevel::kNone ||
518 url != GURL(url::kAboutBlankURL))
519 _spoofableRequest = NO;
521 if (*trustLevel == web::URLVerificationTrustLevel::kAbsolute) {
522 _lastCorrectURLTime = base::TimeTicks::Now();
523 if (self.urlCachingEnabled) {
524 _cachedURL.first = url;
525 _cachedURL.second = *trustLevel;
527 } else if (*trustLevel == web::URLVerificationTrustLevel::kNone &&
528 (base::TimeTicks::Now() - _lastCorrectURLTime) <
529 base::TimeDelta::FromMilliseconds(
530 web::kContinuousCheckIntervalMSLow)) {
531 // URL is not trusted, but the last time it was trusted is within
532 // kContinuousCheckIntervalMSLow.
533 *trustLevel = web::URLVerificationTrustLevel::kMixed;
539 - (void)registerUserAgent {
540 web::BuildAndRegisterUserAgentForUIWebView(
541 self.webStateImpl->GetRequestGroupID(),
542 [self useDesktopUserAgent]);
545 // The core.js cannot pass messages back to obj-c if it is injected
546 // to |WEB_VIEW_DOCUMENT| because it does not support iframe creation used
547 // by core.js to communicate back. That functionality is only supported
548 // by |WEB_VIEW_HTML_DOCUMENT|. |WEB_VIEW_DOCUMENT| is used when displaying
549 // non-HTML contents (e.g. PDF documents).
550 - (web::WebViewDocumentType)webViewDocumentType {
551 // This happens during tests.
553 return web::WEB_VIEW_DOCUMENT_TYPE_GENERIC;
555 NSString* documentType =
556 [_uiWebView stringByEvaluatingJavaScriptFromString:
558 if ([documentType isEqualToString:@"[object HTMLDocument]"])
559 return web::WEB_VIEW_DOCUMENT_TYPE_HTML;
560 else if ([documentType isEqualToString:@"[object Document]"])
561 return web::WEB_VIEW_DOCUMENT_TYPE_GENERIC;
562 return web::WEB_VIEW_DOCUMENT_TYPE_UNKNOWN;
565 - (void)loadRequest:(NSMutableURLRequest*)request {
566 DCHECK(web::GetWebClient());
567 GURL requestURL = net::GURLWithNSURL(request.URL);
568 // If the request is for WebUI, add information to let the network stack
569 // access the requestGroupID.
570 if (web::GetWebClient()->IsAppSpecificURL(requestURL)) {
571 // Sub requests of a chrome:// page will not contain the user agent.
572 // Instead use the username part of the URL to allow the network stack to
573 // associate a request to the correct tab.
574 request.URL = web::AddRequestGroupIDToURL(
575 request.URL, self.webStateImpl->GetRequestGroupID());
577 [_uiWebView loadRequest:request];
580 - (void)loadWebHTMLString:(NSString*)html forURL:(const GURL&)URL {
581 [_uiWebView loadHTMLString:html baseURL:net::NSURLWithGURL(URL)];
584 - (BOOL)scriptHasBeenInjectedForClass:(Class)jsInjectionManagerClass
585 presenceBeacon:(NSString*)beacon {
586 NSString* beaconCheckScript = [NSString stringWithFormat:
587 @"try { typeof %@; } catch (e) { 'undefined'; }", beacon];
589 [self stringByEvaluatingJavaScriptFromString:beaconCheckScript];
590 return [result isEqualToString:@"object"];
593 - (void)injectScript:(NSString*)script forClass:(Class)JSInjectionManagerClass {
594 // Skip evaluation if there's no content (e.g., if what's being injected is
595 // an umbrella manager).
596 if ([script length]) {
597 [super injectScript:script forClass:JSInjectionManagerClass];
598 [self stringByEvaluatingJavaScriptFromString:script];
602 - (void)willLoadCurrentURLInWebView {
605 // This code uses non-documented API, but is not compiled in release.
606 id documentView = [_uiWebView valueForKey:@"documentView"];
607 id webView = [documentView valueForKey:@"webView"];
608 NSString* userAgent = [webView performSelector:@selector(userAgentForURL:)
611 const bool wrongRequestGroupID =
612 ![self.webStateImpl->GetRequestGroupID()
613 isEqualToString:web::ExtractRequestGroupIDFromUserAgent(userAgent)];
614 DLOG_IF(ERROR, wrongRequestGroupID) << "Incorrect user agent in UIWebView";
616 #endif // !defined(NDEBUG)
619 - (void)setPageChangeProbability:(web::PageChangeProbability)probability {
620 if (probability == web::PAGE_CHANGE_PROBABILITY_LOW) {
621 // Reduce check interval to precautionary frequency.
622 [self setContinuousCheckTimerInterval:
623 base::TimeDelta::FromMilliseconds(web::kContinuousCheckIntervalMSLow)];
625 // Increase the timer frequency, as a window change is anticipated shortly.
626 [self setContinuousCheckTimerInterval:
627 base::TimeDelta::FromMilliseconds(web::kContinuousCheckIntervalMSHigh)];
629 if (probability != web::PAGE_CHANGE_PROBABILITY_VERY_HIGH) {
630 // The timer frequency is automatically lowered after a set duration in
631 // case the guess was wrong, to avoid wedging in high-frequency mode.
632 base::Closure closure = base::BindBlock(^{
633 [self setContinuousCheckTimerInterval:
634 base::TimeDelta::FromMilliseconds(
635 web::kContinuousCheckIntervalMSLow)];
637 _lowerFrequencyTimer.reset(
638 new base::Timer(FROM_HERE,
639 base::TimeDelta::FromMilliseconds(
640 web::kContinuousCheckHighFrequencyMSMaxDuration),
641 closure, false /* not repeating */));
642 _lowerFrequencyTimer->Reset();
647 - (BOOL)checkForUnexpectedURLChange {
648 // The check makes no sense without an active web view.
652 // Change to UIWebView default page is not considered a 'real' change and
653 // URL changes are not reported.
654 if ([self isDefaultPage])
657 // Check if currentURL is unexpected (not the incoming page).
658 // This is necessary to notice page changes if core.js injection is disabled
659 // by a malicious page.
660 if (!self.URLOnStartLoading.is_empty() &&
661 [self currentURL] == self.URLOnStartLoading) {
665 // If the URL has changed, handle page load mechanics.
666 ScopedCachedCurrentUrl scopedCurrentURL(self);
667 [self webPageChanged];
668 [self checkDocumentLoaded];
669 [self titleDidChange];
674 - (void)abortWebLoad {
675 // Current load will not complete; this should be communicated upstream to
676 // the delegate, and flagged in the WebView so further messages can be
677 // prevented (which may be confused for messages from newer pages).
678 [_uiWebView stringByEvaluatingJavaScriptFromString:
679 @"document._cancelled = true;"];
680 [_uiWebView stopLoading];
683 - (void)resetLoadState {
688 - (void)setSuppressDialogsWithHelperScript:(NSString*)script {
689 [self stringByEvaluatingJavaScriptFromString:script];
692 - (void)checkDocumentLoaded {
694 [self stringByEvaluatingJavaScriptFromString:
695 @"document.readyState === 'loaded' || "
696 "document.readyState === 'complete'"];
697 if ([loaded isEqualToString:@"true"]) {
698 [self didFinishNavigation];
702 - (NSString*)currentReferrerString {
703 return [self stringByEvaluatingJavaScriptFromString:@"document.referrer"];
706 - (void)titleDidChange {
707 if (![self.delegate respondsToSelector:
708 @selector(webController:titleDidChange:)]) {
712 // Checking the URL trust level is expensive. For performance reasons, the
713 // current URL and the trust level cache must be enabled.
714 // NOTE: Adding a ScopedCachedCurrentUrl here is not the right way to solve
716 DCHECK(self.urlCachingEnabled);
718 // Change to UIWebView default page is not considered a 'real' change and
719 // title changes are not reported.
720 if ([self isDefaultPage])
723 // The title can be retrieved from the document only if the URL can be
725 web::URLVerificationTrustLevel trustLevel =
726 web::URLVerificationTrustLevel::kNone;
727 [self currentURLWithTrustLevel:&trustLevel];
728 if (trustLevel != web::URLVerificationTrustLevel::kAbsolute)
731 NSString* title = self.title;
733 [self.delegate webController:self titleDidChange:title];
736 - (void)teminateNetworkActivity {
737 [super terminateNetworkActivity];
738 _jsInvokeParameterQueue.reset([[CRWJSInvokeParameterQueue alloc] init]);
741 - (void)fetchWebPageSizeWithCompletionHandler:(void(^)(CGSize))handler {
747 // Ensure that JavaScript has been injected.
748 [self.recurringTaskDelegate runRecurringTask];
749 [super fetchWebPageSizeWithCompletionHandler:handler];
752 - (void)documentPresent {
753 if (self.loadPhase != web::PAGE_LOADED &&
754 self.loadPhase != web::LOAD_REQUESTED) {
758 ScopedCachedCurrentUrl scopedCurrentURL(self);
760 // This is a good time to check if the URL has changed.
761 BOOL urlChanged = [self checkForUnexpectedURLChange];
763 // This is a good time to check if the page has refreshed.
764 if (!urlChanged && self.windowId != self.lastSeenWindowID)
765 [self webPageChanged];
767 // Set initial title.
768 [self titleDidChange];
771 - (void)webPageChanged {
772 if (self.loadPhase != web::LOAD_REQUESTED ||
773 self.lastRegisteredRequestURL.is_empty() ||
774 self.lastRegisteredRequestURL != [self currentURL]) {
775 // The page change was unexpected (not already messaged to
776 // webWillStartLoadingURL), so fill in the load request.
777 [self generateMissingLoadRequestWithURL:[self currentURL]
778 referrer:[self currentReferrer]];
781 [super webPageChanged];
784 - (CGFloat)absoluteZoomScaleForScrollState:
785 (const web::PageScrollState&)scrollState {
786 CGFloat zoomScale = NAN;
787 if (scrollState.IsZoomScaleValid()) {
788 if (scrollState.IsZoomScaleLegacyFormat())
789 zoomScale = scrollState.zoom_scale();
791 zoomScale = scrollState.zoom_scale() / scrollState.minimum_zoom_scale();
796 - (void)applyWebViewScrollZoomScaleFromScrollState:
797 (const web::PageScrollState&)scrollState {
798 // A UIWebView's scroll view uses zoom scales in a non-standard way. The
799 // scroll view's |zoomScale| property is always equal to 1.0, and the
800 // |minimumZoomScale| and |maximumZoomScale| properties are adjusted
801 // proportionally to reflect the relative zoom scale. Setting the |zoomScale|
802 // property here scales the page by the value set (i.e. setting zoomScale to
803 // 2.0 will update the zoom to twice its initial scale). The maximum-scale or
804 // minimum-scale meta tags of a page may have changed since the state was
805 // recorded, so clamp the zoom scale to the current range if necessary.
806 DCHECK(scrollState.IsZoomScaleValid());
807 CGFloat zoomScale = scrollState.IsZoomScaleLegacyFormat()
808 ? scrollState.zoom_scale()
809 : self.webScrollView.minimumZoomScale /
810 scrollState.minimum_zoom_scale();
811 if (zoomScale < self.webScrollView.minimumZoomScale)
812 zoomScale = self.webScrollView.minimumZoomScale;
813 if (zoomScale > self.webScrollView.maximumZoomScale)
814 zoomScale = self.webScrollView.maximumZoomScale;
815 self.webScrollView.zoomScale = zoomScale;
818 #pragma mark - JS to ObjC messaging
820 - (void)respondToJSInvoke {
821 // This call is asynchronous. If the web view has been removed, there is
822 // nothing left to do, so just discard the queued messages and return.
824 _jsInvokeParameterQueue.reset([[CRWJSInvokeParameterQueue alloc] init]);
827 // Messages are queued and processed asynchronously. However, user
828 // may initiate JavaScript at arbitrary times (e.g. through Omnibox
829 // "javascript:alert('foo')"). This delays processing of queued messages
830 // until JavaScript execution is completed.
831 // TODO(pkl): This should have a unit test or UI Automation test case.
832 // See crbug.com/228125
833 if (_inJavaScriptContext) {
834 [self performSelector:@selector(respondToJSInvoke)
839 DCHECK(_jsInvokeParameterQueue);
840 while (![_jsInvokeParameterQueue isEmpty]) {
841 CRWJSInvokeParameters* parameters =
842 [_jsInvokeParameterQueue popInvokeParameters];
845 // TODO(stuartmorgan): Some messages (e.g., window.write) should be
846 // processed even if the page has already changed by the time they are
847 // received. crbug.com/228275
848 if ([parameters windowId] != [self windowId]) {
849 // If there is a windowID mismatch, the document has been changed since
850 // messages were added to the queue. Ignore the incoming messages.
851 DLOG(WARNING) << "Messages from JS ignored due to non-matching windowID: "
852 << [[parameters windowId] UTF8String]
853 << " != " << [[self windowId] UTF8String];
856 if (![self respondToMessageQueue:[parameters commandString]
857 userIsInteracting:[parameters userIsInteracting]
858 originURL:[parameters originURL]]) {
859 DLOG(WARNING) << "Messages from JS not handled due to invalid format";
864 - (void)handleWebInvokeURL:(const GURL&)url request:(NSURLRequest*)request {
865 DCHECK([self urlSchemeIsWebInvoke:url]);
866 NSURL* nsurl = request.URL;
867 // TODO(stuartmorgan): Remove the NSURL usage here. Will require a logic
868 // change since GURL doesn't parse non-standard URLs into host and fragment
869 if (![nsurl.host isEqualToString:[self windowId]]) {
870 // If there is a windowID mismatch, we may be under attack from a
871 // malicious page, so a defense is to reset the page.
872 DLOG(WARNING) << "Messages from JS ignored due to non-matching windowID: "
873 << nsurl.host << " != " << [[self windowId] UTF8String];
874 DLOG(WARNING) << "Page reset as security precaution";
875 [self performSelector:@selector(reload) withObject:nil afterDelay:0];
878 if (url.spec().length() > kMaxMessageQueueSize) {
879 DLOG(WARNING) << "Messages from JS ignored due to excessive length";
882 NSString* commandString = [[nsurl fragment]
883 stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
885 GURL originURL(net::GURLWithNSURL(request.mainDocumentURL));
887 if (url.SchemeIs("crwebinvokeimmediate")) {
888 [self respondToMessageQueue:commandString
889 userIsInteracting:[self userIsInteracting]
890 originURL:originURL];
892 [_jsInvokeParameterQueue addCommandString:commandString
893 userIsInteracting:[self userIsInteracting]
895 forWindowId:[super windowId]];
896 if (!_jsMessageQueueThrottled) {
897 [self performSelector:@selector(respondToJSInvoke)
904 - (void)setJsMessageQueueThrottled:(BOOL)throttle {
905 _jsMessageQueueThrottled = throttle;
907 [self respondToJSInvoke];
910 - (void)removeDocumentLoadCommandsFromQueue {
911 [_jsInvokeParameterQueue removeCommandString:@"document.present"];
912 [_jsInvokeParameterQueue removeCommandString:@"document.loaded"];
915 - (BOOL)urlSchemeIsWebInvoke:(const GURL&)url {
916 return url.SchemeIs("crwebinvoke") || url.SchemeIs("crwebinvokeimmediate");
919 - (CRWJSInvokeParameterQueue*)jsInvokeParameterQueue {
920 return _jsInvokeParameterQueue;
923 - (BOOL)respondToMessageQueue:(NSString*)messageQueue
924 userIsInteracting:(BOOL)userIsInteracting
925 originURL:(const GURL&)originURL {
926 ScopedCachedCurrentUrl scopedCurrentURL(self);
929 std::string errorMessage;
930 scoped_ptr<base::Value> inputJSONData(base::JSONReader::ReadAndReturnError(
931 base::SysNSStringToUTF8(messageQueue), false, &errorCode, &errorMessage));
933 DLOG(WARNING) << "JSON parse error: %s" << errorMessage.c_str();
936 // MessageQueues pass messages as a list.
937 base::ListValue* messages = nullptr;
938 if (!inputJSONData->GetAsList(&messages)) {
939 DLOG(WARNING) << "Message queue not a list";
942 for (size_t idx = 0; idx != messages->GetSize(); ++idx) {
943 // The same-origin check has to be done for every command to mitigate the
944 // risk of command sequences where the first command would change the page
945 // and the subsequent commands would have unlimited access to it.
946 if (originURL.GetOrigin() != self.currentURL.GetOrigin()) {
947 DLOG(WARNING) << "Message source URL origin: " << originURL.GetOrigin()
948 << " does not match current URL origin: "
949 << self.currentURL.GetOrigin();
953 base::DictionaryValue* message = nullptr;
954 if (!messages->GetDictionary(idx, &message)) {
955 DLOG(WARNING) << "Message could not be retrieved";
958 BOOL messageHandled = [self respondToMessage:message
959 userIsInteracting:userIsInteracting
960 originURL:originURL];
964 // If handling the message caused this page to be closed, stop processing
966 // TODO(stuartmorgan): Ideally messages should continue to be handled until
967 // the end of the event loop (e.g., window.close(); window.open(...);
968 // should do both things). That would require knowing which messages came
969 // in the same event loop, however.
970 if ([self isBeingDestroyed])
976 - (SEL)selectorToHandleJavaScriptCommand:(const std::string&)command {
977 static std::map<std::string, SEL>* handlers = nullptr;
978 static dispatch_once_t onceToken;
979 dispatch_once(&onceToken, ^{
980 handlers = new std::map<std::string, SEL>();
981 (*handlers)["anchor.click"] = @selector(handleAnchorClickMessage:context:);
982 (*handlers)["document.loaded"] =
983 @selector(handleDocumentLoadedMessage:context:);
984 (*handlers)["document.present"] =
985 @selector(handleDocumentPresentMessage:context:);
986 (*handlers)["document.retitled"] =
987 @selector(handleDocumentRetitledMessage:context:);
988 (*handlers)["window.close"] = @selector(handleWindowCloseMessage:context:);
989 (*handlers)["window.document.write"] =
990 @selector(handleWindowDocumentWriteMessage:context:);
991 (*handlers)["window.location"] =
992 @selector(handleWindowLocationMessage:context:);
993 (*handlers)["window.open"] = @selector(handleWindowOpenMessage:context:);
994 (*handlers)["window.stop"] = @selector(handleWindowStopMessage:context:);
995 (*handlers)["window.unload"] =
996 @selector(handleWindowUnloadMessage:context:);
999 auto iter = handlers->find(command);
1000 return iter != handlers->end()
1002 : [super selectorToHandleJavaScriptCommand:command];
1005 - (NSString*)windowNameFromMessage:(base::DictionaryValue*)message
1006 context:(NSDictionary*)context {
1008 if(!message->GetString("target", &target)) {
1009 DLOG(WARNING) << "JS message parameter not found: target";
1013 DCHECK(context[web::kOriginURLKey]);
1014 const GURL& originURL = net::GURLWithNSURL(context[web::kOriginURLKey]);
1016 // Unique string made for page/target combination.
1017 // Safe to delimit unique string with # since page references won't
1019 return base::SysUTF8ToNSString(
1020 NormalizedUrl(originURL) + web::kWindowNameSeparator + target);
1024 #pragma mark JavaScript message handlers
1026 - (BOOL)handleAnchorClickMessage:(base::DictionaryValue*)message
1027 context:(NSDictionary*)context {
1028 // Reset the external click request.
1029 [self resetExternalRequest];
1032 if (!message->GetString("href", &href)) {
1033 DLOG(WARNING) << "JS message parameter not found: href";
1036 const GURL targetURL(href);
1037 const GURL currentURL([self currentURL]);
1038 if (currentURL != targetURL) {
1039 if (web::UrlHasWebScheme(targetURL)) {
1040 // The referrer is not known yet, and will be updated later.
1041 const web::Referrer emptyReferrer;
1042 [self registerLoadRequest:targetURL
1043 referrer:emptyReferrer
1044 transition:ui::PAGE_TRANSITION_LINK];
1045 [self setPageChangeProbability:web::PAGE_CHANGE_PROBABILITY_HIGH];
1046 } else if (web::GetWebClient()->IsAppSpecificURL(targetURL) &&
1047 web::GetWebClient()->IsAppSpecificURL(currentURL)) {
1048 // Allow navigations between app-specific URLs
1049 [self removeWebViewAllowingCachedReconstruction:NO];
1050 ui::PageTransition pageTransitionLink =
1051 ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK);
1052 const web::Referrer referrer(currentURL, web::ReferrerPolicyDefault);
1053 web::WebState::OpenURLParams openParams(targetURL, referrer, CURRENT_TAB,
1054 pageTransitionLink, true);
1055 [self.delegate openURLWithParams:openParams];
1061 - (BOOL)handleDocumentLoadedMessage:(base::DictionaryValue*)message
1062 context:(NSDictionary*)context {
1063 // Very early hashchange events can be missed, hence this extra explicit
1065 [self checkForUnexpectedURLChange];
1066 [self didFinishNavigation];
1070 - (BOOL)handleDocumentPresentMessage:(base::DictionaryValue*)message
1071 context:(NSDictionary*)context {
1072 NSString* documentCancelled =
1073 [self stringByEvaluatingJavaScriptFromString:@"document._cancelled"];
1074 if (![documentCancelled isEqualToString:@"true"])
1075 [self documentPresent];
1079 - (BOOL)handleDocumentRetitledMessage:(base::DictionaryValue*)message
1080 context:(NSDictionary*)context {
1081 [self titleDidChange];
1085 - (BOOL)handleWindowCloseMessage:(base::DictionaryValue*)message
1086 context:(NSDictionary*)context {
1087 NSString* windowName = [self windowNameFromMessage:message
1091 [[self scriptingInterfaceForWindowNamed:windowName] orderClose];
1095 - (BOOL)handleWindowDocumentWriteMessage:(base::DictionaryValue*)message
1096 context:(NSDictionary*)context {
1097 NSString* windowName = [self windowNameFromMessage:message
1102 if (!message->GetString("html", &HTML)) {
1103 DLOG(WARNING) << "JS message parameter not found: html";
1106 [[self scriptingInterfaceForWindowNamed:windowName]
1107 loadHTML:base::SysUTF8ToNSString(HTML)];
1111 - (BOOL)handleWindowLocationMessage:(base::DictionaryValue*)message
1112 context:(NSDictionary*)context {
1113 NSString* windowName = [self windowNameFromMessage:message
1117 std::string command;
1118 if (!message->GetString("command", &command)) {
1119 DLOG(WARNING) << "JS message parameter not found: command";
1123 if (!message->GetString("value", &value)) {
1124 DLOG(WARNING) << "JS message parameter not found: value";
1127 std::string escapedValue;
1128 base::EscapeJSONString(value, true, &escapedValue);
1130 [NSString stringWithFormat:@"<script>%s = %s;</script>",
1132 escapedValue.c_str()];
1133 [[self scriptingInterfaceForWindowNamed:windowName] loadHTML:HTML];
1137 - (BOOL)handleWindowOpenMessage:(base::DictionaryValue*)message
1138 context:(NSDictionary*)context {
1139 NSString* windowName = [self windowNameFromMessage:message
1143 std::string targetURL;
1144 if (!message->GetString("url", &targetURL)) {
1145 DLOG(WARNING) << "JS message parameter not found: url";
1148 std::string referrerPolicy;
1149 if (!message->GetString("referrerPolicy", &referrerPolicy)) {
1150 DLOG(WARNING) << "JS message parameter not found: referrerPolicy";
1153 GURL resolvedURL = targetURL.empty() ?
1155 GURL(net::GURLWithNSURL(context[web::kOriginURLKey])).Resolve(targetURL);
1156 DCHECK(&resolvedURL);
1158 windowInfo(resolvedURL,
1160 [self referrerPolicyFromString:referrerPolicy],
1161 [context[web::kUserIsInteractingKey] boolValue]);
1163 [self openPopupWithInfo:windowInfo];
1167 - (BOOL)handleWindowStopMessage:(base::DictionaryValue*)message
1168 context:(NSDictionary*)context {
1169 NSString* windowName = [self windowNameFromMessage:message
1173 [[self scriptingInterfaceForWindowNamed:windowName] stopLoading];
1177 - (BOOL)handleWindowUnloadMessage:(base::DictionaryValue*)message
1178 context:(NSDictionary*)context {
1179 [self setPageChangeProbability:web::PAGE_CHANGE_PROBABILITY_VERY_HIGH];
1183 #pragma mark Private methods
1185 - (BOOL)isDefaultPage {
1186 if ([[self stringByEvaluatingJavaScriptFromString:@"document._defaultPage"]
1187 isEqualToString:@"true"]) {
1188 return self.currentURL == self.defaultURL;
1193 - (BOOL)isLinkNavigation:(UIWebViewNavigationType)navigationType {
1194 switch (navigationType) {
1195 case UIWebViewNavigationTypeLinkClicked:
1197 case UIWebViewNavigationTypeOther:
1198 return [self userClickedRecently];
1204 - (void)setContinuousCheckTimerInterval:(const base::TimeDelta&)interval {
1205 // The timer should never be set when there's no web view.
1208 BOOL shouldStartTimer =
1209 !_continuousCheckTimer.get() || _continuousCheckTimer->IsRunning();
1210 base::Closure closure = base::BindBlock(^{
1211 // Only perform JS checks if CRWWebController is not already in JavaScript
1212 // context. This is possible when "javascript:..." is executed from
1213 // Omnibox and this block is run from the timer.
1214 if (!_inJavaScriptContext)
1215 [self.recurringTaskDelegate runRecurringTask];
1217 _continuousCheckTimer.reset(
1218 new base::Timer(FROM_HERE, interval, closure, true));
1219 if (shouldStartTimer)
1220 _continuousCheckTimer->Reset();
1221 if (_lowerFrequencyTimer &&
1222 interval == base::TimeDelta::FromMilliseconds(
1223 web::kContinuousCheckIntervalMSLow)) {
1224 _lowerFrequencyTimer.reset();
1228 - (NSString*)stringByEvaluatingJavaScriptFromString:(NSString*)script {
1232 ScopedReentryGuard reentryGuard(&_inJavaScriptContext);
1233 return [_uiWebView stringByEvaluatingJavaScriptFromString:script];
1236 - (NSString*)stringByEvaluatingUserJavaScriptFromString:(NSString*)script {
1237 [self setUserInteractionRegistered:YES];
1238 return [self stringByEvaluatingJavaScriptFromString:script];
1241 - (UIWebView*)createWebView {
1242 UIWebView* webView = web::CreateWebView(
1244 self.webStateImpl->GetRequestGroupID(),
1245 [self useDesktopUserAgent]);
1247 // Mark the document object of the default page as such, so that it is not
1248 // mistaken for a 'real' page by change detection mechanisms.
1249 [webView stringByEvaluatingJavaScriptFromString:
1250 @"document._defaultPage = true;"];
1252 [webView setScalesPageToFit:YES];
1253 // Turn off data-detectors. MobileSafari does the same thing.
1254 [webView setDataDetectorTypes:UIDataDetectorTypeNone];
1256 return [webView autorelease];
1259 - (void)setWebView:(UIWebView*)webView {
1260 DCHECK_NE(_uiWebView.get(), webView);
1261 // Per documentation, must clear the delegate before releasing the UIWebView
1262 // to avoid errant dangling pointers.
1263 [_uiWebView setDelegate:nil];
1264 _uiWebView.reset([webView retain]);
1265 [_uiWebView setDelegate:self];
1266 // Clear out the trusted URL cache.
1267 _lastCorrectURLTime = base::TimeTicks();
1268 _cachedURL.first = GURL();
1269 // Reset the spoofable state (see declaration comment).
1270 // TODO(stuartmorgan): Fix the fact that there's no guarantee that no
1271 // navigation has happened before the UIWebView is set here (ideally by
1272 // unifying the creation and setting flow).
1273 _spoofableRequest = YES;
1274 _inJavaScriptContext = NO;
1277 // Do initial injection even before loading another page, since the window
1278 // object is re-used.
1279 [self injectEarlyInjectionScripts];
1281 _continuousCheckTimer.reset();
1282 // This timer exists only to change the frequency of the main timer, so it
1283 // should not outlive the main timer.
1284 _lowerFrequencyTimer.reset();
1288 - (void)generateMissingDocumentLifecycleEvents {
1289 // The webView can be removed between this method being queued and invoked.
1292 if ([self webViewDocumentType] == web::WEB_VIEW_DOCUMENT_TYPE_GENERIC) {
1293 [self documentPresent];
1294 [self didFinishNavigation];
1298 - (void)generateMissingLoadRequestWithURL:(const GURL&)currentURL
1299 referrer:(const web::Referrer&)referrer {
1300 [self loadCancelled];
1301 // Initialize transition based on whether the request is user-initiated or
1302 // not. This is a best guess to replace lost transition type informationj.
1303 ui::PageTransition transition = self.userInteractionRegistered
1304 ? ui::PAGE_TRANSITION_LINK
1305 : ui::PAGE_TRANSITION_CLIENT_REDIRECT;
1306 // If the URL agrees with session state, use the session's transition.
1307 if (currentURL == [self currentNavigationURL]) {
1308 transition = [self currentTransition];
1311 [self registerLoadRequest:currentURL referrer:referrer transition:transition];
1314 - (id<CRWWebControllerScripting>)scriptingInterfaceForWindowNamed:
1316 if (![self.delegate respondsToSelector:
1317 @selector(webController:scriptingInterfaceForWindowNamed:)]) {
1320 return [self.delegate webController:self
1321 scriptingInterfaceForWindowNamed:name];
1324 #pragma mark FullscreenVideo
1326 - (void)moviePlayerDidEnterFullscreen:(NSNotification*)notification {
1327 _inFullscreenVideo = YES;
1330 - (void)moviePlayerDidExitFullscreen:(NSNotification*)notification {
1331 _inFullscreenVideo = NO;
1334 - (void)exitFullscreenVideo {
1335 [self stringByEvaluatingJavaScriptFromString:
1336 @"__gCrWeb.exitFullscreenVideo();"];
1340 #pragma mark CRWRecurringTaskDelegate
1342 // Checks for page changes are made continuously.
1343 - (void)runRecurringTask {
1347 [self injectEarlyInjectionScripts];
1348 [self checkForUnexpectedURLChange];
1352 #pragma mark CRWRedirectClientDelegate
1354 - (void)wasRedirectedToRequest:(NSURLRequest*)request
1355 redirectResponse:(NSURLResponse*)response {
1356 // Register the redirected load request if it originated from the main page
1358 GURL redirectedURL = net::GURLWithNSURL(response.URL);
1359 if ([self currentNavigationURL] == redirectedURL) {
1360 [self registerLoadRequest:net::GURLWithNSURL(request.URL)
1361 referrer:[self currentReferrer]
1362 transition:ui::PAGE_TRANSITION_SERVER_REDIRECT];
1367 #pragma mark UIWebViewDelegate Methods
1369 // Called when a load begins, and for subsequent subpages.
1370 - (BOOL)webView:(UIWebView*)webView
1371 shouldStartLoadWithRequest:(NSURLRequest*)request
1372 navigationType:(UIWebViewNavigationType)navigationType {
1373 DVLOG(5) << "webViewShouldStartLoadWithRequest "
1374 << net::FormatUrlRequestForLogging(request);
1376 if (self.isBeingDestroyed)
1379 GURL url = net::GURLWithNSURL(request.URL);
1381 // The crwebnull protocol is used where an element requires a URL but it
1382 // should not trigger any activity on the WebView.
1383 if (url.SchemeIs("crwebnull"))
1386 if ([self urlSchemeIsWebInvoke:url]) {
1387 [self handleWebInvokeURL:url request:request];
1391 // ##### IMPORTANT NOTE #####
1392 // Do not add new code above this line unless you're certain about what you're
1393 // doing with respect to JS re-entry.
1394 ScopedReentryGuard javaScriptReentryGuard(&_inJavaScriptContext);
1395 web::FrameInfo* targetFrame = nullptr; // No reliable way to get this info.
1396 BOOL isLinkClick = [self isLinkNavigation:navigationType];
1397 return [self shouldAllowLoadWithRequest:request
1398 targetFrame:targetFrame
1399 isLinkClick:isLinkClick];
1402 // Called at multiple points during a load, such as at the start of loading a
1403 // page, and every time an iframe loads. Not called again for server-side
1405 - (void)webViewDidStartLoad:(UIWebView *)webView {
1406 NSURLRequest* request = webView.request;
1407 DVLOG(5) << "webViewDidStartLoad "
1408 << net::FormatUrlRequestForLogging(request);
1409 // |webView:shouldStartLoad| may not be called or called with different URL
1410 // and mainDocURL for the request in certain page navigations. There
1411 // are at least 2 known page navigations where this occurs, in these cases it
1412 // is imperative the URL verification timer is started here.
1413 // The 2 known cases are:
1414 // 1) A malicious page suppressing core.js injection and calling
1415 // window.history.back() or window.history.forward()
1416 // 2) An iframe loading a URL using target=_blank.
1417 // TODO(shreyasv): crbug.com/349155. Understand further why this happens
1418 // in some case and not in others.
1419 if (webView != self.webView) {
1420 // This happens sometimes as tests are brought down.
1421 // TODO(jimblackler): work out why and fix the problem at source.
1422 LOG(WARNING) << " UIWebViewDelegate message received for inactive WebView.";
1425 DCHECK(!self.isBeingDestroyed);
1426 // Increment the number of pending loads. This will be balanced by either
1427 // a |-webViewDidFinishLoad:| or |-webView:didFailLoadWithError:|.
1429 [self.recurringTaskDelegate runRecurringTask];
1432 // Called when the page (or one of its subframes) finishes loading. This is
1433 // called multiple times during a page load, with varying frequency depending
1434 // on the action (going back, loading a page with frames, redirecting).
1435 // See http://goto/efrmm for a summary of why this is so painful.
1436 - (void)webViewDidFinishLoad:(UIWebView*)webView {
1437 DVLOG(5) << "webViewDidFinishLoad "
1438 << net::FormatUrlRequestForLogging(webView.request);
1439 DCHECK(!self.isHalted);
1440 // Occasionally this delegate is invoked as a side effect during core.js
1441 // injection. It is necessary to ensure we do not attempt to start the
1442 // injection process a second time.
1443 if (!_inJavaScriptContext)
1444 [self.recurringTaskDelegate runRecurringTask];
1446 [self performSelector:@selector(generateMissingDocumentLifecycleEvents)
1451 if ((_loadCount == _unloadCount) && (self.loadPhase != web::LOAD_REQUESTED))
1452 [self checkDocumentLoaded];
1455 // Called when there is an error loading the page. Some errors aren't actual
1456 // errors, but are caused by user actions such as stopping a page load
1458 - (void)webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error {
1459 DVLOG(5) << "webViewDidFailLoadWithError "
1460 << net::FormatUrlRequestForLogging(webView.request);
1463 // Under unknown circumstances navigation item can be null. In that case the
1464 // state of web/ will not be valid and app will crash. Early return avoid a
1465 // crash (crbug.com/411912).
1466 if (!self.webStateImpl ||
1467 !self.webStateImpl->GetNavigationManagerImpl().GetVisibleItem()) {
1471 // There's no reliable way to know if a load is for the main frame, so make a
1472 // best-effort guess.
1473 // |_loadCount| is reset to 0 before starting loading a new page, and is
1474 // incremented in each call to |-webViewDidStartLoad:|. The main request
1475 // is the first one to be loaded, and thus has a |_loadCount| of 1.
1476 // Sub-requests have a |_loadCount| > 1.
1477 // An iframe loading after the main page also has a |_loadCount| of 1, as
1478 // |_loadCount| is reset at the end of the main page load. In that case,
1479 // |loadPhase_| is web::PAGE_LOADED (as opposed to web::PAGE_LOADING for a
1481 const bool isMainFrame = (_loadCount == 1 &&
1482 self.loadPhase != web::PAGE_LOADED);
1483 [self handleLoadError:error inMainFrame:isMainFrame];
1487 #pragma mark Testing methods
1489 -(id<CRWRecurringTaskDelegate>)recurringTaskDelegate {
1490 return _recurringTaskDelegate;