Add ICU message format support
[chromium-blink-merge.git] / ios / web / web_state / ui / crw_ui_web_view_web_controller.mm
blob43e73fccce6aafe527b878990298bc211f87ba10
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/ns_error_util.h"
8 #import "base/ios/weak_nsobject.h"
9 #include "base/json/json_reader.h"
10 #include "base/json/string_escape.h"
11 #include "base/mac/bind_objc_block.h"
12 #import "base/mac/scoped_nsobject.h"
13 #include "base/memory/scoped_ptr.h"
14 #include "base/metrics/field_trial.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/public/web_state/ui/crw_web_view_content_view.h"
28 #import "ios/web/ui_web_view_util.h"
29 #include "ios/web/web_state/frame_info.h"
30 #import "ios/web/web_state/js/crw_js_invoke_parameter_queue.h"
31 #import "ios/web/web_state/ui/crw_context_menu_provider.h"
32 #import "ios/web/web_state/ui/crw_web_controller+protected.h"
33 #import "ios/web/web_state/ui/crw_web_controller.h"
34 #import "ios/web/web_state/ui/web_view_js_utils.h"
35 #import "ios/web/web_state/web_state_impl.h"
36 #import "ios/web/web_state/web_view_internal_creation_util.h"
37 #import "net/base/mac/url_conversions.h"
38 #include "net/base/net_errors.h"
39 #include "url/url_constants.h"
41 namespace web {
43 // The following continuous check timer frequency constants are externally
44 // available for the purpose of performance tests.
45 // Frequency for the continuous checks when a reset in the page object is
46 // anticipated shortly. In milliseconds.
47 const int64 kContinuousCheckIntervalMSHigh = 100;
49 // The maximum duration that the CRWWebController can run in high-frequency
50 // check mode before being changed back to the low frequency.
51 const int64 kContinuousCheckHighFrequencyMSMaxDuration = 5000;
53 // Frequency for the continuous checks when a reset in the page object is not
54 // anticipated; checks are only made as a precaution.
55 // The URL could be out of date for this many milliseconds, so this should not
56 // be increased without careful consideration.
57 const int64 kContinuousCheckIntervalMSLow = 3000;
59 }  // namespace web
61 @interface CRWUIWebViewWebController () <CRWRedirectClientDelegate,
62                                          UIWebViewDelegate> {
63   // The UIWebView managed by this instance.
64   base::scoped_nsobject<UIWebView> _uiWebView;
66   // Whether caching of the current URL is enabled or not.
67   BOOL _urlCachingEnabled;
69   // Temporarily cached current URL. Only valid/set while urlCachingEnabled
70   // is YES.
71   // TODO(stuartmorgan): Change this to a struct so code using it is more
72   // readable.
73   std::pair<GURL, web::URLVerificationTrustLevel> _cachedURL;
75   // The last time a URL with absolute trust level was computed.
76   // When an untrusted URL is retrieved from the
77   // |CRWURLVerifyingProtocolHandler|, if the last trusted URL is within
78   // |kContinuousCheckIntervalMSLow|, the trustLevel is upgraded to Mixed.
79   // The reason is that it is sometimes temporarily impossible to do a
80   // AsyncXMLHttpRequest on a web page. When this happen, it is not possible
81   // to check for the validity of the current URL. Because the checker is
82   // only checking every |kContinuousCheckIntervalMSLow| anyway, waiting this
83   // amount of time before triggering an interstitial does not weaken the
84   // security of the browser.
85   base::TimeTicks _lastCorrectURLTime;
87   // Each new UIWebView starts in a state where:
88   // - window.location.href is equal to about:blank
89   // - Ajax requests seem to be impossible
90   // Because Ajax requests are used to determine is a URL is verified, this
91   // means it is impossible to do this check until the UIWebView is in a more
92   // sane state. This variable tracks whether verifying the URL is currently
93   // impossible. It starts at YES when a new UIWebView is created,
94   // and will change to NO, as soon as either window.location.href is not
95   // about:blank anymore, or an URL verification request succeeds. This means
96   // that a malicious site that is able to change the value of
97   // window.location.href and that is loaded as the first request will be able
98   // to change its URL to about:blank. As this is not an interesting URL, it is
99   // considered acceptable.
100   BOOL _spoofableRequest;
102   // Timer used to make continuous checks on the UIWebView.  Timer is
103   // running only while |webView| is non-nil.
104   scoped_ptr<base::Timer> _continuousCheckTimer;
105   // Timer to lower the check frequency automatically.
106   scoped_ptr<base::Timer> _lowerFrequencyTimer;
108   // Counts of calls to |-webViewDidStartLoad:| and |-webViewDidFinishLoad|.
109   // When |_loadCount| is equal to |_unloadCount|, the page is no longer
110   // loading. Used as a fallback to determine when the page is done loading in
111   // case document.readyState isn't sufficient.
112   // When |_loadCount| is 1, the main page is loading (as opposed to a
113   // sub-resource).
114   int _loadCount;
115   int _unloadCount;
117   // Backs the property of the same name.
118   BOOL _inJavaScriptContext;
120   // Backs the property of the same name.
121   base::scoped_nsobject<CRWJSInvokeParameterQueue> _jsInvokeParameterQueue;
123   // Blocks message queue processing (for testing).
124   BOOL _jsMessageQueueThrottled;
126   // YES if a video is playing in fullscreen.
127   BOOL _inFullscreenVideo;
129   // Backs the property of the same name.
130   id<CRWRecurringTaskDelegate>_recurringTaskDelegate;
132   // Redirect client factory.
133   base::scoped_nsobject<CRWRedirectNetworkClientFactory>
134       redirect_client_factory_;
137 // Whether or not URL caching is enabled. Between enabling and disabling
138 // caching, calls to webURLWithTrustLevel: after the first may return the same
139 // answer without re-checking the URL.
140 @property(nonatomic, setter=setURLCachingEnabled:) BOOL urlCachingEnabled;
142 // Returns whether the current page is the web views initial (default) page.
143 - (BOOL)isDefaultPage;
145 // Returns whether the given navigation is triggered by a user link click.
146 - (BOOL)isLinkNavigation:(UIWebViewNavigationType)navigationType;
148 // Starts (at the given interval) the timer that drives runRecurringTasks to see
149 // whether or not the page has changed. This is used to work around the fact
150 // that UIWebView callbacks are not sufficiently reliable to catch every page
151 // change.
152 - (void)setContinuousCheckTimerInterval:(const base::TimeDelta&)interval;
154 // Evaluates the supplied JavaScript and returns the result. Will return nil
155 // if it is unable to evaluate the JavaScript.
156 - (NSString*)stringByEvaluatingJavaScriptFromString:(NSString*)script;
158 // Evaluates the user-entered JavaScript in the WebView and returns the result.
159 // Will return nil if the web view is currently not available.
160 - (NSString*)stringByEvaluatingUserJavaScriptFromString:(NSString*)script;
162 // Checks if the document is loaded, and if so triggers any necessary logic.
163 - (void)checkDocumentLoaded;
165 // Returns a new autoreleased UIWebView.
166 - (UIWebView*)createWebView;
168 // Sets value to web view property.
169 - (void)setWebView:(UIWebView*)webView;
171 // Simulates the events generated by core.js during document loading process.
172 // Used for non-HTML documents (e.g. PDF) that do not support data flow from
173 // JavaScript to obj-c via iframe injection.
174 - (void)generateMissingDocumentLifecycleEvents;
176 // Makes a best-effort attempt to retroactively construct a load request for an
177 // observed-but-unexpected navigation. Should be called any time a page
178 // change is detected as having happened without the current internal state
179 // indicating it was expected.
180 - (void)generateMissingLoadRequestWithURL:(const GURL&)currentURL
181                                  referrer:(const web::Referrer&)referrer;
183 // Returns a child scripting CRWWebController with the given window name.
184 - (id<CRWWebControllerScripting>)scriptingInterfaceForWindowNamed:
185     (NSString*)name;
187 // Called when UIMoviePlayerControllerDidEnterFullscreenNotification is posted.
188 - (void)moviePlayerDidEnterFullscreen:(NSNotification*)notification;
190 // Called when UIMoviePlayerControllerDidExitFullscreenNotification is posted.
191 - (void)moviePlayerDidExitFullscreen:(NSNotification*)notification;
193 // Exits fullscreen mode for any playing videos.
194 - (void)exitFullscreenVideo;
196 // Handles presentation of the web document. Checks and handles URL changes,
197 // page refreshes, and title changes.
198 - (void)documentPresent;
200 // Handles queued JS to ObjC messages.
201 // All commands are passed via JSON strings, including parameters.
202 - (void)respondToJSInvoke;
204 // Pauses (|throttle|=YES) or resumes (|throttle|=NO) crwebinvoke message
205 // processing.
206 - (void)setJsMessageQueueThrottled:(BOOL)throttle;
208 // Removes document load commands from the queue. Otherwise they could be
209 // handled after a new page load has begun, which would cause an unwanted
210 // redirect.
211 - (void)removeDocumentLoadCommandsFromQueue;
213 // Returns YES if the given URL has a scheme associated with JS->native calls.
214 - (BOOL)urlSchemeIsWebInvoke:(const GURL&)url;
216 // Returns window name given a message and its context.
217 - (NSString*)windowNameFromMessage:(base::DictionaryValue*)message
218                            context:(NSDictionary*)context;
220 // Handles 'anchor.click' message.
221 - (BOOL)handleAnchorClickMessage:(base::DictionaryValue*)message
222                          context:(NSDictionary*)context;
223 // Handles 'document.loaded' message.
224 - (BOOL)handleDocumentLoadedMessage:(base::DictionaryValue*)message
225                             context:(NSDictionary*)context;
226 // Handles 'document.present' message.
227 - (BOOL)handleDocumentPresentMessage:(base::DictionaryValue*)message
228                              context:(NSDictionary*)context;
229 // Handles 'document.retitled' message.
230 - (BOOL)handleDocumentRetitledMessage:(base::DictionaryValue*)message
231                               context:(NSDictionary*)context;
232 // Handles 'window.close' message.
233 - (BOOL)handleWindowCloseMessage:(base::DictionaryValue*)message
234                          context:(NSDictionary*)context;
235 // Handles 'window.document.write' message.
236 - (BOOL)handleWindowDocumentWriteMessage:(base::DictionaryValue*)message
237                                  context:(NSDictionary*)context;
238 // Handles 'window.location' message.
239 - (BOOL)handleWindowLocationMessage:(base::DictionaryValue*)message
240                             context:(NSDictionary*)context;
241 // Handles 'window.open' message.
242 - (BOOL)handleWindowOpenMessage:(base::DictionaryValue*)message
243                         context:(NSDictionary*)context;
244 // Handles 'window.stop' message.
245 - (BOOL)handleWindowStopMessage:(base::DictionaryValue*)message
246                         context:(NSDictionary*)context;
247 // Handles 'window.unload' message.
248 - (BOOL)handleWindowUnloadMessage:(base::DictionaryValue*)message
249                           context:(NSDictionary*)context;
250 @end
252 namespace {
254 // Utility to help catch unwanted JavaScript re-entries. An instance should
255 // be created on the stack any time JS will be executed.
256 // It uses an instance variable (passed in as a pointer to boolean) that needs
257 // to be initialized to false.
258 class ScopedReentryGuard {
259  public:
260   explicit ScopedReentryGuard(BOOL* is_inside_javascript_context)
261       : is_inside_javascript_context_(is_inside_javascript_context) {
262     DCHECK(!*is_inside_javascript_context_);
263     *is_inside_javascript_context_ = YES;
264   }
265   ~ScopedReentryGuard() {
266     DCHECK(*is_inside_javascript_context_);
267     *is_inside_javascript_context_ = NO;
268   }
270  private:
271   BOOL* is_inside_javascript_context_;
272   DISALLOW_IMPLICIT_CONSTRUCTORS(ScopedReentryGuard);
275 // Class allowing to selectively enable caching of |currentURL| on a
276 // CRWUIWebViewWebController. As long as an instance of this class lives,
277 // the CRWUIWebViewWebController passed as parameter will cache the result for
278 // |currentURL| calls.
279 class ScopedCachedCurrentUrl {
280  public:
281   explicit ScopedCachedCurrentUrl(CRWUIWebViewWebController* web_controller)
282       : web_controller_(web_controller),
283         had_cached_current_url_([web_controller urlCachingEnabled]) {
284     if (!had_cached_current_url_)
285       [web_controller_ setURLCachingEnabled:YES];
286   }
288   ~ScopedCachedCurrentUrl() {
289     if (!had_cached_current_url_)
290       [web_controller_ setURLCachingEnabled:NO];
291   }
293  private:
294   CRWUIWebViewWebController* web_controller_;
295   bool had_cached_current_url_;
296   DISALLOW_IMPLICIT_CONSTRUCTORS(ScopedCachedCurrentUrl);
299 // Normalizes the URL for the purposes of identifying the origin page (remove
300 // any parameters, fragments, etc.) and return an absolute string of the URL.
301 std::string NormalizedUrl(const GURL& url) {
302   GURL::Replacements replacements;
303   replacements.ClearQuery();
304   replacements.ClearRef();
305   replacements.ClearUsername();
306   replacements.ClearPassword();
307   GURL page_url(url.ReplaceComponents(replacements));
309   return page_url.spec();
312 // The maximum size of JSON message passed from JavaScript to ObjC.
313 // 256kB is an arbitrary number that was chosen to be a magnitude larger than
314 // any legitimate message.
315 const size_t kMaxMessageQueueSize = 262144;
317 }  // namespace
319 @implementation CRWUIWebViewWebController
321 - (instancetype)initWithWebState:(scoped_ptr<web::WebStateImpl>)webState {
322   self = [super initWithWebState:webState.Pass()];
323   if (self) {
324     _jsInvokeParameterQueue.reset([[CRWJSInvokeParameterQueue alloc] init]);
326     NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
327     [defaultCenter addObserver:self
328                       selector:@selector(moviePlayerDidEnterFullscreen:)
329                           name:
330         @"UIMoviePlayerControllerDidEnterFullscreenNotification"
331                         object:nil];
332     [defaultCenter addObserver:self
333                       selector:@selector(moviePlayerDidExitFullscreen:)
334                           name:
335         @"UIMoviePlayerControllerDidExitFullscreenNotification"
336                         object:nil];
337     _recurringTaskDelegate = self;
339     // UIWebViews require a redirect network client in order to accurately
340     // detect server redirects.
341     redirect_client_factory_.reset(
342         [[CRWRedirectNetworkClientFactory alloc] initWithDelegate:self]);
343     // WeakNSObjects cannot be dereferenced outside of the main thread, and
344     // CRWWebController must be deallocated from the main thread.  Keep a
345     // reference to self on the main thread and release it after successfully
346     // adding the redirect client factory to the RequestTracker on the IO
347     // thread.
348     __block base::scoped_nsobject<CRWUIWebViewWebController> scopedSelf(
349         [self retain]);
350     web::WebThread::PostTaskAndReply(
351         web::WebThread::IO, FROM_HERE, base::BindBlock(^{
352           // Only add the factory if there is a valid request tracker.
353           web::WebStateImpl* webState = [scopedSelf webStateImpl];
354           if (webState && webState->GetRequestTracker()) {
355             webState->GetRequestTracker()->AddNetworkClientFactory(
356                 redirect_client_factory_);
357           }
358         }),
359         base::BindBlock(^{
360           scopedSelf.reset();
361         }));
362   }
363   return self;
366 - (void)dealloc {
367   [[NSNotificationCenter defaultCenter] removeObserver:self];
368   [super dealloc];
371 #pragma mark - CRWWebController public method implementations
373 - (void)loadCompleteWithSuccess:(BOOL)loadSuccess {
374   [super loadCompleteWithSuccess:loadSuccess];
375   [self removeDocumentLoadCommandsFromQueue];
378 - (BOOL)keyboardDisplayRequiresUserAction {
379   return [_uiWebView keyboardDisplayRequiresUserAction];
382 - (void)setKeyboardDisplayRequiresUserAction:(BOOL)requiresUserAction {
383   [_uiWebView setKeyboardDisplayRequiresUserAction:requiresUserAction];
386 - (void)evaluateUserJavaScript:(NSString*)script {
387   [self setUserInteractionRegistered:YES];
388   // A script which contains alert() call executed by UIWebView from gcd block
389   // freezes the app (crbug.com/444106), hence this uses the NSObject API.
390   [_uiWebView performSelectorOnMainThread:
391       @selector(stringByEvaluatingJavaScriptFromString:)
392                                withObject:script
393                             waitUntilDone:NO];
396 #pragma mark Overridden public methods
398 - (void)wasShown {
399   if (_uiWebView) {
400     // Turn the timer back on, and do an immediate check for anything missed
401     // while the timer was off.
402     [self.recurringTaskDelegate runRecurringTask];
403     _continuousCheckTimer->Reset();
404   }
405   [super wasShown];
408 - (void)wasHidden {
409   if (self.webView) {
410     // Turn the timer off, to cut down on work being done by background tabs.
411     _continuousCheckTimer->Stop();
412   }
413   [super wasHidden];
415   // The video player is not quit/dismissed when the home button is pressed and
416   // Chrome is backgrounded (crbug.com/277206).
417   if (_inFullscreenVideo)
418     [self exitFullscreenVideo];
422 - (void)close {
423   [super close];
425   // The timers must not exist at this point, otherwise this object will leak.
426   DCHECK(!_continuousCheckTimer);
427   DCHECK(!_lowerFrequencyTimer);
430 - (void)childWindowClosed:(NSString*)windowName {
431   // Get the substring of the window name after the hash.
432   NSRange range = [windowName rangeOfString:
433       base::SysUTF8ToNSString(web::kWindowNameSeparator)];
434   if (range.location != NSNotFound) {
435     NSString* target = [windowName substringFromIndex:(range.location + 1)];
436     [self stringByEvaluatingJavaScriptFromString:
437         [NSString stringWithFormat:@"__gCrWeb.windowClosed('%@');", target]];
438   }
441 #pragma mark -
442 #pragma mark Testing-Only Methods
444 - (void)injectWebViewContentView:(CRWWebViewContentView*)webViewContentView {
445   [super injectWebViewContentView:webViewContentView];
446   [self setWebView:static_cast<UIWebView*>(webViewContentView.webView)];
449 #pragma mark CRWJSInjectionEvaluatorMethods
451 - (void)evaluateJavaScript:(NSString*)script
452        stringResultHandler:(web::JavaScriptCompletion)handler {
453   NSString* safeScript = [self scriptByAddingWindowIDCheckForScript:script];
454   web::EvaluateJavaScript(_uiWebView, safeScript, handler);
457 - (web::WebViewType)webViewType {
458   return web::UI_WEB_VIEW_TYPE;
461 #pragma mark - Protected property implementations
463 - (UIView*)webView {
464   return _uiWebView.get();
467 - (UIScrollView*)webScrollView {
468   return [_uiWebView scrollView];
471 - (BOOL)urlCachingEnabled {
472   return _urlCachingEnabled;
475 - (void)setURLCachingEnabled:(BOOL)enabled {
476   if (enabled == _urlCachingEnabled)
477     return;
478   _urlCachingEnabled = enabled;
479   _cachedURL.first = GURL();
482 - (BOOL)ignoreURLVerificationFailures {
483   return _spoofableRequest;
486 - (NSString*)title {
487   return [self stringByEvaluatingJavaScriptFromString:
488       @"document.title.length ? document.title : ''"];
491 #pragma mark Protected method implementations
493 - (void)ensureWebViewCreated {
494   if (!_uiWebView) {
495     [self setWebView:[self createWebView]];
496     // Notify super class about created web view. -webViewDidChange is not
497     // called from -setWebView: as the latter used in unit tests with fake
498     // web view, which cannot be added to view hierarchy.
499     [self webViewDidChange];
500   }
503 - (void)resetWebView {
504   [self setWebView:nil];
507 - (GURL)webURLWithTrustLevel:(web::URLVerificationTrustLevel*)trustLevel {
508   if (_cachedURL.first.is_valid()) {
509     *trustLevel = _cachedURL.second;
510     return _cachedURL.first;
511   }
512   ScopedReentryGuard reentryGuard(&_inJavaScriptContext);
513   const GURL url =
514       [CRWURLVerifyingProtocolHandler currentURLForWebView:_uiWebView
515                                                 trustLevel:trustLevel];
517   // If verification succeeded, or the URL has changed, then the UIWebView is no
518   // longer in the initial state.
519   if (*trustLevel != web::URLVerificationTrustLevel::kNone ||
520       url != GURL(url::kAboutBlankURL))
521     _spoofableRequest = NO;
523   if (*trustLevel == web::URLVerificationTrustLevel::kAbsolute) {
524     _lastCorrectURLTime = base::TimeTicks::Now();
525     if (self.urlCachingEnabled) {
526       _cachedURL.first = url;
527       _cachedURL.second = *trustLevel;
528     }
529   } else if (*trustLevel == web::URLVerificationTrustLevel::kNone &&
530              (base::TimeTicks::Now() - _lastCorrectURLTime) <
531                  base::TimeDelta::FromMilliseconds(
532                      web::kContinuousCheckIntervalMSLow)) {
533     // URL is not trusted, but the last time it was trusted is within
534     // kContinuousCheckIntervalMSLow.
535     *trustLevel = web::URLVerificationTrustLevel::kMixed;
536   }
538   return url;
541 - (void)registerUserAgent {
542   web::BuildAndRegisterUserAgentForUIWebView(
543       self.webStateImpl->GetRequestGroupID(),
544       [self useDesktopUserAgent]);
547 // The core.js cannot pass messages back to obj-c  if it is injected
548 // to |WEB_VIEW_DOCUMENT| because it does not support iframe creation used
549 // by core.js to communicate back. That functionality is only supported
550 // by |WEB_VIEW_HTML_DOCUMENT|. |WEB_VIEW_DOCUMENT| is used when displaying
551 // non-HTML contents (e.g. PDF documents).
552 - (web::WebViewDocumentType)webViewDocumentType {
553   // This happens during tests.
554   if (!_uiWebView) {
555     return web::WEB_VIEW_DOCUMENT_TYPE_GENERIC;
556   }
557   NSString* documentType =
558       [_uiWebView stringByEvaluatingJavaScriptFromString:
559           @"'' + document"];
560   if ([documentType isEqualToString:@"[object HTMLDocument]"])
561     return web::WEB_VIEW_DOCUMENT_TYPE_HTML;
562   else if ([documentType isEqualToString:@"[object Document]"])
563     return web::WEB_VIEW_DOCUMENT_TYPE_GENERIC;
564   return web::WEB_VIEW_DOCUMENT_TYPE_UNKNOWN;
567 - (void)loadRequest:(NSMutableURLRequest*)request {
568   DCHECK(web::GetWebClient());
569   GURL requestURL = net::GURLWithNSURL(request.URL);
570   // If the request is for WebUI, add information to let the network stack
571   // access the requestGroupID.
572   if (web::GetWebClient()->IsAppSpecificURL(requestURL)) {
573     // Sub requests of a chrome:// page will not contain the user agent.
574     // Instead use the username part of the URL to allow the network stack to
575     // associate a request to the correct tab.
576     request.URL = web::AddRequestGroupIDToURL(
577         request.URL, self.webStateImpl->GetRequestGroupID());
578   }
579   [_uiWebView loadRequest:request];
582 - (void)loadWebHTMLString:(NSString*)html forURL:(const GURL&)URL {
583   [_uiWebView loadHTMLString:html baseURL:net::NSURLWithGURL(URL)];
586 - (BOOL)scriptHasBeenInjectedForClass:(Class)jsInjectionManagerClass
587                        presenceBeacon:(NSString*)beacon {
588   NSString* beaconCheckScript = [NSString stringWithFormat:
589       @"try { typeof %@; } catch (e) { 'undefined'; }", beacon];
590   NSString* result =
591       [self stringByEvaluatingJavaScriptFromString:beaconCheckScript];
592   return [result isEqualToString:@"object"];
595 - (void)injectScript:(NSString*)script forClass:(Class)JSInjectionManagerClass {
596   // Skip evaluation if there's no content (e.g., if what's being injected is
597   // an umbrella manager).
598   if ([script length]) {
599     [super injectScript:script forClass:JSInjectionManagerClass];
600     [self stringByEvaluatingJavaScriptFromString:script];
601   }
604 - (void)willLoadCurrentURLInWebView {
605 #if !defined(NDEBUG)
606   if (_uiWebView) {
607     // This code uses non-documented API, but is not compiled in release.
608     id documentView = [_uiWebView valueForKey:@"documentView"];
609     id webView = [documentView valueForKey:@"webView"];
610     NSString* userAgent = [webView performSelector:@selector(userAgentForURL:)
611                                         withObject:nil];
612     DCHECK(userAgent);
613     const bool wrongRequestGroupID =
614         ![self.webStateImpl->GetRequestGroupID()
615             isEqualToString:web::ExtractRequestGroupIDFromUserAgent(userAgent)];
616     DLOG_IF(ERROR, wrongRequestGroupID) << "Incorrect user agent in UIWebView";
617   }
618 #endif  // !defined(NDEBUG)
621 - (void)setPageChangeProbability:(web::PageChangeProbability)probability {
622   if (probability == web::PAGE_CHANGE_PROBABILITY_LOW) {
623     // Reduce check interval to precautionary frequency.
624     [self setContinuousCheckTimerInterval:
625         base::TimeDelta::FromMilliseconds(web::kContinuousCheckIntervalMSLow)];
626   } else {
627     // Increase the timer frequency, as a window change is anticipated shortly.
628     [self setContinuousCheckTimerInterval:
629         base::TimeDelta::FromMilliseconds(web::kContinuousCheckIntervalMSHigh)];
631     if (probability != web::PAGE_CHANGE_PROBABILITY_VERY_HIGH) {
632       // The timer frequency is automatically lowered after a set duration in
633       // case the guess was wrong, to avoid wedging in high-frequency mode.
634       base::Closure closure = base::BindBlock(^{
635           [self setContinuousCheckTimerInterval:
636               base::TimeDelta::FromMilliseconds(
637                   web::kContinuousCheckIntervalMSLow)];
638       });
639       _lowerFrequencyTimer.reset(
640           new base::Timer(FROM_HERE,
641                           base::TimeDelta::FromMilliseconds(
642                               web::kContinuousCheckHighFrequencyMSMaxDuration),
643                           closure, false /* not repeating */));
644       _lowerFrequencyTimer->Reset();
645     }
646   }
649 - (BOOL)checkForUnexpectedURLChange {
650     // The check makes no sense without an active web view.
651   if (!self.webView)
652     return NO;
654   // Change to UIWebView default page is not considered a 'real' change and
655   // URL changes are not reported.
656   if ([self isDefaultPage])
657     return NO;
659   // Check if currentURL is unexpected (not the incoming page).
660   // This is necessary to notice page changes if core.js injection is disabled
661   // by a malicious page.
662   if (!self.URLOnStartLoading.is_empty() &&
663       [self currentURL] == self.URLOnStartLoading) {
664     return NO;
665   }
667   // If the URL has changed, handle page load mechanics.
668   ScopedCachedCurrentUrl scopedCurrentURL(self);
669   [self webPageChanged];
670   [self checkDocumentLoaded];
671   [self titleDidChange];
673   return YES;
676 - (void)abortWebLoad {
677   // Current load will not complete; this should be communicated upstream to
678   // the delegate, and flagged in the WebView so further messages can be
679   // prevented (which may be confused for messages from newer pages).
680   [_uiWebView stringByEvaluatingJavaScriptFromString:
681       @"document._cancelled = true;"];
682   [_uiWebView stopLoading];
685 - (void)resetLoadState {
686   _loadCount = 0;
687   _unloadCount = 0;
690 - (void)setSuppressDialogsWithHelperScript:(NSString*)script {
691   [self stringByEvaluatingJavaScriptFromString:script];
694 - (void)checkDocumentLoaded {
695   NSString* loaded =
696       [self stringByEvaluatingJavaScriptFromString:
697            @"document.readyState === 'loaded' || "
698             "document.readyState === 'complete'"];
699   if ([loaded isEqualToString:@"true"]) {
700     [self didFinishNavigation];
701   }
704 - (NSString*)currentReferrerString {
705   return [self stringByEvaluatingJavaScriptFromString:@"document.referrer"];
708 - (void)titleDidChange {
709   if (![self.delegate respondsToSelector:
710            @selector(webController:titleDidChange:)]) {
711     return;
712   }
714   // Checking the URL trust level is expensive. For performance reasons, the
715   // current URL and the trust level cache must be enabled.
716   // NOTE: Adding a ScopedCachedCurrentUrl here is not the right way to solve
717   // this DCHECK.
718   DCHECK(self.urlCachingEnabled);
720   // Change to UIWebView default page is not considered a 'real' change and
721   // title changes are not reported.
722   if ([self isDefaultPage])
723     return;
725   // The title can be retrieved from the document only if the URL can be
726   // trusted.
727   web::URLVerificationTrustLevel trustLevel =
728       web::URLVerificationTrustLevel::kNone;
729   [self currentURLWithTrustLevel:&trustLevel];
730   if (trustLevel != web::URLVerificationTrustLevel::kAbsolute)
731     return;
733   NSString* title = self.title;
734   if ([title length])
735     [self.delegate webController:self titleDidChange:title];
738 - (void)teminateNetworkActivity {
739   [super terminateNetworkActivity];
740   _jsInvokeParameterQueue.reset([[CRWJSInvokeParameterQueue alloc] init]);
743 - (void)fetchWebPageSizeWithCompletionHandler:(void(^)(CGSize))handler {
744   if (!self.webView) {
745     handler(CGSizeZero);
746     return;
747   }
749   // Ensure that JavaScript has been injected.
750   [self.recurringTaskDelegate runRecurringTask];
751   [super fetchWebPageSizeWithCompletionHandler:handler];
754 - (void)documentPresent {
755   if (self.loadPhase != web::PAGE_LOADED &&
756       self.loadPhase != web::LOAD_REQUESTED) {
757     return;
758   }
760   ScopedCachedCurrentUrl scopedCurrentURL(self);
762   // This is a good time to check if the URL has changed.
763   BOOL urlChanged = [self checkForUnexpectedURLChange];
765   // This is a good time to check if the page has refreshed.
766   if (!urlChanged && self.windowId != self.lastSeenWindowID)
767     [self webPageChanged];
769   // Set initial title.
770   [self titleDidChange];
773 - (void)webPageChanged {
774   if (self.loadPhase != web::LOAD_REQUESTED ||
775       self.lastRegisteredRequestURL.is_empty() ||
776       self.lastRegisteredRequestURL != [self currentURL]) {
777     // The page change was unexpected (not already messaged to
778     // webWillStartLoadingURL), so fill in the load request.
779     [self generateMissingLoadRequestWithURL:[self currentURL]
780                                    referrer:[self currentReferrer]];
781   }
783   [super webPageChanged];
786 - (void)applyWebViewScrollZoomScaleFromZoomState:
787     (const web::PageZoomState&)zoomState {
788   // A UIWebView's scroll view uses zoom scales in a non-standard way.  The
789   // scroll view's |zoomScale| property is always equal to 1.0, and the
790   // |minimumZoomScale| and |maximumZoomScale| properties are adjusted
791   // proportionally to reflect the relative zoom scale.  Setting the |zoomScale|
792   // property here scales the page by the value set (i.e. setting zoomScale to
793   // 2.0 will update the zoom to twice its initial scale). The maximum-scale or
794   // minimum-scale meta tags of a page may have changed since the state was
795   // recorded, so clamp the zoom scale to the current range if necessary.
796   DCHECK(zoomState.IsValid());
797   CGFloat zoomScale = zoomState.IsLegacyFormat()
798                           ? zoomState.zoom_scale()
799                           : self.webScrollView.minimumZoomScale /
800                                 zoomState.minimum_zoom_scale();
801   if (zoomScale < self.webScrollView.minimumZoomScale)
802     zoomScale = self.webScrollView.minimumZoomScale;
803   if (zoomScale > self.webScrollView.maximumZoomScale)
804     zoomScale = self.webScrollView.maximumZoomScale;
805   self.webScrollView.zoomScale = zoomScale;
808 - (void)handleCancelledError:(NSError*)error {
809   // NSURLErrorCancelled errors generated by the Chrome net stack should be
810   // aborted.  If the error was generated by the UIWebView, it will not have
811   // an underlying net error and will be automatically retried by the web view.
812   DCHECK_EQ(error.code, NSURLErrorCancelled);
813   NSError* underlyingError = base::ios::GetFinalUnderlyingErrorFromError(error);
814   NSString* netDomain = base::SysUTF8ToNSString(net::kErrorDomain);
815   BOOL shouldAbortLoadForCancelledError =
816       [underlyingError.domain isEqualToString:netDomain];
817   if (!shouldAbortLoadForCancelledError)
818     return;
820   // NSURLCancelled errors with underlying errors are generated from the
821   // Chrome network stack.  Abort the load in this case.
822   [self abortLoad];
824   switch (underlyingError.code) {
825     case net::ERR_ABORTED:
826       // |NSURLErrorCancelled| errors with underlying net error code
827       // |net::ERR_ABORTED| are used by the Chrome network stack to
828       // indicate that the current load should be aborted and the pending
829       // entry should be discarded.
830       [[self sessionController] discardNonCommittedEntries];
831       break;
832     case net::ERR_BLOCKED_BY_CLIENT:
833       // |NSURLErrorCancelled| errors with underlying net error code
834       // |net::ERR_BLOCKED_BY_CLIENT| are used by the Chrome network stack
835       // to indicate that the current load should be aborted and the pending
836       // entry should be kept.
837       break;
838     default:
839       NOTREACHED();
840   }
843 #pragma mark - JS to ObjC messaging
845 - (void)respondToJSInvoke {
846   // This call is asynchronous. If the web view has been removed, there is
847   // nothing left to do, so just discard the queued messages and return.
848   if (!self.webView) {
849     _jsInvokeParameterQueue.reset([[CRWJSInvokeParameterQueue alloc] init]);
850     return;
851   }
852   // Messages are queued and processed asynchronously. However, user
853   // may initiate JavaScript at arbitrary times (e.g. through Omnibox
854   // "javascript:alert('foo')"). This delays processing of queued messages
855   // until JavaScript execution is completed.
856   // TODO(pkl): This should have a unit test or UI Automation test case.
857   // See crbug.com/228125
858   if (_inJavaScriptContext) {
859     [self performSelector:@selector(respondToJSInvoke)
860                withObject:nil
861                afterDelay:0];
862     return;
863   }
864   DCHECK(_jsInvokeParameterQueue);
865   while (![_jsInvokeParameterQueue isEmpty]) {
866     CRWJSInvokeParameters* parameters =
867         [_jsInvokeParameterQueue popInvokeParameters];
868     if (!parameters)
869       return;
870     // TODO(stuartmorgan): Some messages (e.g., window.write) should be
871     // processed even if the page has already changed by the time they are
872     // received. crbug.com/228275
873     if ([parameters windowId] != [self windowId]) {
874       // If there is a windowID mismatch, the document has been changed since
875       // messages were added to the queue. Ignore the incoming messages.
876       DLOG(WARNING) << "Messages from JS ignored due to non-matching windowID: "
877                     << [[parameters windowId] UTF8String]
878                     << " != " << [[self windowId] UTF8String];
879       continue;
880     }
881     if (![self respondToMessageQueue:[parameters commandString]
882                    userIsInteracting:[parameters userIsInteracting]
883                            originURL:[parameters originURL]]) {
884       DLOG(WARNING) << "Messages from JS not handled due to invalid format";
885     }
886   }
889 - (void)handleWebInvokeURL:(const GURL&)url request:(NSURLRequest*)request {
890   DCHECK([self urlSchemeIsWebInvoke:url]);
891   NSURL* nsurl = request.URL;
892   // TODO(stuartmorgan): Remove the NSURL usage here. Will require a logic
893   // change since GURL doesn't parse non-standard URLs into host and fragment
894   if (![nsurl.host isEqualToString:[self windowId]]) {
895     // If there is a windowID mismatch, we may be under attack from a
896     // malicious page, so a defense is to reset the page.
897     DLOG(WARNING) << "Messages from JS ignored due to non-matching windowID: "
898                   << nsurl.host << " != " << [[self windowId] UTF8String];
899     DLOG(WARNING) << "Page reset as security precaution";
900     [self performSelector:@selector(reload) withObject:nil afterDelay:0];
901     return;
902   }
903   if (url.spec().length() > kMaxMessageQueueSize) {
904     DLOG(WARNING) << "Messages from JS ignored due to excessive length";
905     return;
906   }
907   NSString* commandString = [[nsurl fragment]
908       stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
910   GURL originURL(net::GURLWithNSURL(request.mainDocumentURL));
912   if (url.SchemeIs("crwebinvokeimmediate")) {
913     [self respondToMessageQueue:commandString
914               userIsInteracting:[self userIsInteracting]
915                       originURL:originURL];
916   } else {
917     [_jsInvokeParameterQueue addCommandString:commandString
918                             userIsInteracting:[self userIsInteracting]
919                                     originURL:originURL
920                                   forWindowId:[super windowId]];
921     if (!_jsMessageQueueThrottled) {
922       [self performSelector:@selector(respondToJSInvoke)
923                  withObject:nil
924                  afterDelay:0];
925     }
926   }
929 - (void)setJsMessageQueueThrottled:(BOOL)throttle {
930   _jsMessageQueueThrottled = throttle;
931   if (!throttle)
932     [self respondToJSInvoke];
935 - (void)removeDocumentLoadCommandsFromQueue {
936   [_jsInvokeParameterQueue removeCommandString:@"document.present"];
937   [_jsInvokeParameterQueue removeCommandString:@"document.loaded"];
940 - (BOOL)urlSchemeIsWebInvoke:(const GURL&)url {
941   return url.SchemeIs("crwebinvoke") || url.SchemeIs("crwebinvokeimmediate");
944 - (CRWJSInvokeParameterQueue*)jsInvokeParameterQueue {
945   return _jsInvokeParameterQueue;
948 - (BOOL)respondToMessageQueue:(NSString*)messageQueue
949             userIsInteracting:(BOOL)userIsInteracting
950                     originURL:(const GURL&)originURL {
951   ScopedCachedCurrentUrl scopedCurrentURL(self);
953   int errorCode = 0;
954   std::string errorMessage;
955   scoped_ptr<base::Value> inputJSONData(base::JSONReader::ReadAndReturnError(
956       base::SysNSStringToUTF8(messageQueue), false, &errorCode, &errorMessage));
957   if (errorCode) {
958     DLOG(WARNING) << "JSON parse error: %s" << errorMessage.c_str();
959     return NO;
960   }
961   // MessageQueues pass messages as a list.
962   base::ListValue* messages = nullptr;
963   if (!inputJSONData->GetAsList(&messages)) {
964     DLOG(WARNING) << "Message queue not a list";
965     return NO;
966   }
967   for (size_t idx = 0; idx != messages->GetSize(); ++idx) {
968     // The same-origin check has to be done for every command to mitigate the
969     // risk of command sequences where the first command would change the page
970     // and the subsequent commands would have unlimited access to it.
971     if (originURL.GetOrigin() != self.currentURL.GetOrigin()) {
972       DLOG(WARNING) << "Message source URL origin: " << originURL.GetOrigin()
973                     << " does not match current URL origin: "
974                     << self.currentURL.GetOrigin();
975       return NO;
976     }
978     base::DictionaryValue* message = nullptr;
979     if (!messages->GetDictionary(idx, &message)) {
980       DLOG(WARNING) << "Message could not be retrieved";
981       return NO;
982     }
983     BOOL messageHandled = [self respondToMessage:message
984                                userIsInteracting:userIsInteracting
985                                        originURL:originURL];
986     if (!messageHandled)
987       return NO;
989     // If handling the message caused this page to be closed, stop processing
990     // messages.
991     // TODO(stuartmorgan): Ideally messages should continue to be handled until
992     // the end of the event loop (e.g., window.close(); window.open(...);
993     // should do both things). That would require knowing which messages came
994     // in the same event loop, however.
995     if ([self isBeingDestroyed])
996       return YES;
997   }
998   return YES;
1001 - (SEL)selectorToHandleJavaScriptCommand:(const std::string&)command {
1002   static std::map<std::string, SEL>* handlers = nullptr;
1003   static dispatch_once_t onceToken;
1004   dispatch_once(&onceToken, ^{
1005     handlers = new std::map<std::string, SEL>();
1006     (*handlers)["anchor.click"] = @selector(handleAnchorClickMessage:context:);
1007     (*handlers)["document.loaded"] =
1008         @selector(handleDocumentLoadedMessage:context:);
1009     (*handlers)["document.present"] =
1010         @selector(handleDocumentPresentMessage:context:);
1011     (*handlers)["document.retitled"] =
1012         @selector(handleDocumentRetitledMessage:context:);
1013     (*handlers)["window.close"] = @selector(handleWindowCloseMessage:context:);
1014     (*handlers)["window.document.write"] =
1015         @selector(handleWindowDocumentWriteMessage:context:);
1016     (*handlers)["window.location"] =
1017         @selector(handleWindowLocationMessage:context:);
1018     (*handlers)["window.open"] = @selector(handleWindowOpenMessage:context:);
1019     (*handlers)["window.stop"] = @selector(handleWindowStopMessage:context:);
1020     (*handlers)["window.unload"] =
1021         @selector(handleWindowUnloadMessage:context:);
1022   });
1023   DCHECK(handlers);
1024   auto iter = handlers->find(command);
1025   return iter != handlers->end()
1026              ? iter->second
1027              : [super selectorToHandleJavaScriptCommand:command];
1030 - (NSString*)windowNameFromMessage:(base::DictionaryValue*)message
1031                            context:(NSDictionary*)context {
1032   std::string target;
1033   if(!message->GetString("target", &target)) {
1034     DLOG(WARNING) << "JS message parameter not found: target";
1035     return nil;
1036   }
1037   DCHECK(&target);
1038   DCHECK(context[web::kOriginURLKey]);
1039   const GURL& originURL = net::GURLWithNSURL(context[web::kOriginURLKey]);
1041   // Unique string made for page/target combination.
1042   // Safe to delimit unique string with # since page references won't
1043   // contain #.
1044   return base::SysUTF8ToNSString(
1045       NormalizedUrl(originURL) + web::kWindowNameSeparator + target);
1048 #pragma mark -
1049 #pragma mark JavaScript message handlers
1051 - (BOOL)handleAnchorClickMessage:(base::DictionaryValue*)message
1052                          context:(NSDictionary*)context {
1053   // Reset the external click request.
1054   [self resetExternalRequest];
1056   std::string href;
1057   if (!message->GetString("href", &href)) {
1058     DLOG(WARNING) << "JS message parameter not found: href";
1059     return NO;
1060   }
1061   const GURL targetURL(href);
1062   const GURL currentURL([self currentURL]);
1063   if (currentURL != targetURL) {
1064     if (web::UrlHasWebScheme(targetURL)) {
1065       // The referrer is not known yet, and will be updated later.
1066       const web::Referrer emptyReferrer;
1067       [self registerLoadRequest:targetURL
1068                        referrer:emptyReferrer
1069                      transition:ui::PAGE_TRANSITION_LINK];
1070       [self setPageChangeProbability:web::PAGE_CHANGE_PROBABILITY_HIGH];
1071     } else if (web::GetWebClient()->IsAppSpecificURL(targetURL) &&
1072                web::GetWebClient()->IsAppSpecificURL(currentURL)) {
1073       // Allow navigations between app-specific URLs
1074       [self removeWebViewAllowingCachedReconstruction:NO];
1075       ui::PageTransition pageTransitionLink =
1076           ui::PageTransitionFromInt(ui::PAGE_TRANSITION_LINK);
1077       const web::Referrer referrer(currentURL, web::ReferrerPolicyDefault);
1078       web::WebState::OpenURLParams openParams(targetURL, referrer, CURRENT_TAB,
1079                                               pageTransitionLink, true);
1080       [self.delegate openURLWithParams:openParams];
1081     }
1082   }
1083   return YES;
1086 - (BOOL)handleDocumentLoadedMessage:(base::DictionaryValue*)message
1087                             context:(NSDictionary*)context {
1088   // Very early hashchange events can be missed, hence this extra explicit
1089   // check.
1090   [self checkForUnexpectedURLChange];
1091   [self didFinishNavigation];
1092   return YES;
1095 - (BOOL)handleDocumentPresentMessage:(base::DictionaryValue*)message
1096                              context:(NSDictionary*)context {
1097   NSString* documentCancelled =
1098       [self stringByEvaluatingJavaScriptFromString:@"document._cancelled"];
1099   if (![documentCancelled isEqualToString:@"true"])
1100     [self documentPresent];
1101   return YES;
1104 - (BOOL)handleDocumentRetitledMessage:(base::DictionaryValue*)message
1105                               context:(NSDictionary*)context {
1106   [self titleDidChange];
1107   return YES;
1110 - (BOOL)handleWindowCloseMessage:(base::DictionaryValue*)message
1111                          context:(NSDictionary*)context {
1112   NSString* windowName = [self windowNameFromMessage:message
1113                                              context:context];
1114   if (!windowName)
1115     return NO;
1116   [[self scriptingInterfaceForWindowNamed:windowName] orderClose];
1117   return YES;
1120 - (BOOL)handleWindowDocumentWriteMessage:(base::DictionaryValue*)message
1121                                  context:(NSDictionary*)context {
1122   NSString* windowName = [self windowNameFromMessage:message
1123                                              context:context];
1124   if (!windowName)
1125     return NO;
1126   std::string HTML;
1127   if (!message->GetString("html", &HTML)) {
1128     DLOG(WARNING) << "JS message parameter not found: html";
1129     return NO;
1130   }
1131   [[self scriptingInterfaceForWindowNamed:windowName]
1132       loadHTML:base::SysUTF8ToNSString(HTML)];
1133   return YES;
1136 - (BOOL)handleWindowLocationMessage:(base::DictionaryValue*)message
1137                             context:(NSDictionary*)context {
1138   NSString* windowName = [self windowNameFromMessage:message
1139                                              context:context];
1140   if (!windowName)
1141     return NO;
1142   std::string command;
1143   if (!message->GetString("command", &command)) {
1144     DLOG(WARNING) << "JS message parameter not found: command";
1145     return NO;
1146   }
1147   std::string value;
1148   if (!message->GetString("value", &value)) {
1149     DLOG(WARNING) << "JS message parameter not found: value";
1150     return NO;
1151   }
1152   std::string escapedValue;
1153   base::EscapeJSONString(value, true, &escapedValue);
1154   NSString* HTML =
1155       [NSString stringWithFormat:@"<script>%s = %s;</script>",
1156                                  command.c_str(),
1157                                  escapedValue.c_str()];
1158   [[self scriptingInterfaceForWindowNamed:windowName] loadHTML:HTML];
1159   return YES;
1162 - (BOOL)handleWindowOpenMessage:(base::DictionaryValue*)message
1163                         context:(NSDictionary*)context {
1164   NSString* windowName = [self windowNameFromMessage:message
1165                                              context:context];
1166   if (!windowName)
1167     return NO;
1168   std::string targetURL;
1169   if (!message->GetString("url", &targetURL)) {
1170     DLOG(WARNING) << "JS message parameter not found: url";
1171     return NO;
1172   }
1173   std::string referrerPolicy;
1174   if (!message->GetString("referrerPolicy", &referrerPolicy)) {
1175     DLOG(WARNING) << "JS message parameter not found: referrerPolicy";
1176     return NO;
1177   }
1178   GURL resolvedURL = targetURL.empty() ?
1179       self.defaultURL :
1180       GURL(net::GURLWithNSURL(context[web::kOriginURLKey])).Resolve(targetURL);
1181   DCHECK(&resolvedURL);
1182   web::NewWindowInfo
1183       windowInfo(resolvedURL,
1184                  windowName,
1185                  [self referrerPolicyFromString:referrerPolicy],
1186                  [context[web::kUserIsInteractingKey] boolValue]);
1188   [self openPopupWithInfo:windowInfo];
1189   return YES;
1192 - (BOOL)handleWindowStopMessage:(base::DictionaryValue*)message
1193                         context:(NSDictionary*)context {
1194   NSString* windowName = [self windowNameFromMessage:message
1195                                              context:context];
1196   if (!windowName)
1197     return NO;
1198   [[self scriptingInterfaceForWindowNamed:windowName] stopLoading];
1199   return YES;
1202 - (BOOL)handleWindowUnloadMessage:(base::DictionaryValue*)message
1203                           context:(NSDictionary*)context {
1204   [self setPageChangeProbability:web::PAGE_CHANGE_PROBABILITY_VERY_HIGH];
1205   return YES;
1208 #pragma mark Private methods
1210 - (BOOL)isDefaultPage {
1211   if ([[self stringByEvaluatingJavaScriptFromString:@"document._defaultPage"]
1212        isEqualToString:@"true"]) {
1213     return self.currentURL == self.defaultURL;
1214   }
1215   return NO;
1218 - (BOOL)isLinkNavigation:(UIWebViewNavigationType)navigationType {
1219   switch (navigationType) {
1220     case UIWebViewNavigationTypeLinkClicked:
1221       return YES;
1222     case UIWebViewNavigationTypeOther:
1223       return [self userClickedRecently];
1224     default:
1225       return NO;
1226   }
1229 - (void)setContinuousCheckTimerInterval:(const base::TimeDelta&)interval {
1230   // The timer should never be set when there's no web view.
1231   DCHECK(_uiWebView);
1233   BOOL shouldStartTimer =
1234       !_continuousCheckTimer.get() || _continuousCheckTimer->IsRunning();
1235   base::Closure closure = base::BindBlock(^{
1236       // Only perform JS checks if CRWWebController is not already in JavaScript
1237       // context. This is possible when "javascript:..." is executed from
1238       // Omnibox and this block is run from the timer.
1239       if (!_inJavaScriptContext)
1240         [self.recurringTaskDelegate runRecurringTask];
1241   });
1242   _continuousCheckTimer.reset(
1243       new base::Timer(FROM_HERE, interval, closure, true));
1244   if (shouldStartTimer)
1245     _continuousCheckTimer->Reset();
1246   if (_lowerFrequencyTimer &&
1247       interval == base::TimeDelta::FromMilliseconds(
1248           web::kContinuousCheckIntervalMSLow)) {
1249     _lowerFrequencyTimer.reset();
1250   }
1253 - (NSString*)stringByEvaluatingJavaScriptFromString:(NSString*)script {
1254   if (!_uiWebView)
1255     return nil;
1257   ScopedReentryGuard reentryGuard(&_inJavaScriptContext);
1258   return [_uiWebView stringByEvaluatingJavaScriptFromString:script];
1261 - (NSString*)stringByEvaluatingUserJavaScriptFromString:(NSString*)script {
1262   [self setUserInteractionRegistered:YES];
1263   return [self stringByEvaluatingJavaScriptFromString:script];
1266 - (UIWebView*)createWebView {
1267   UIWebView* webView = web::CreateWebView(
1268       CGRectZero,
1269       self.webStateImpl->GetRequestGroupID(),
1270       [self useDesktopUserAgent]);
1272   // Mark the document object of the default page as such, so that it is not
1273   // mistaken for a 'real' page by change detection mechanisms.
1274   [webView stringByEvaluatingJavaScriptFromString:
1275       @"document._defaultPage = true;"];
1277   [webView setScalesPageToFit:YES];
1278   // Turn off data-detectors. MobileSafari does the same thing.
1279   [webView setDataDetectorTypes:UIDataDetectorTypeNone];
1281   return [webView autorelease];
1284 - (void)setWebView:(UIWebView*)webView {
1285   DCHECK_NE(_uiWebView.get(), webView);
1286   // Per documentation, must clear the delegate before releasing the UIWebView
1287   // to avoid errant dangling pointers.
1288   [_uiWebView setDelegate:nil];
1289   _uiWebView.reset([webView retain]);
1290   [_uiWebView setDelegate:self];
1291   // Clear out the trusted URL cache.
1292   _lastCorrectURLTime = base::TimeTicks();
1293   _cachedURL.first = GURL();
1294   // Reset the spoofable state (see declaration comment).
1295   // TODO(stuartmorgan): Fix the fact that there's no guarantee that no
1296   // navigation has happened before the UIWebView is set here (ideally by
1297   // unifying the creation and setting flow).
1298   _spoofableRequest = YES;
1300   if (webView) {
1301     _inJavaScriptContext = NO;
1302     // Do initial injection even before loading another page, since the window
1303     // object is re-used.
1304     [self injectEarlyInjectionScripts];
1305   } else {
1306     _continuousCheckTimer.reset();
1307     // This timer exists only to change the frequency of the main timer, so it
1308     // should not outlive the main timer.
1309     _lowerFrequencyTimer.reset();
1310   }
1313 - (void)generateMissingDocumentLifecycleEvents {
1314   // The webView can be removed between this method being queued and invoked.
1315   if (!self.webView)
1316     return;
1317   if ([self webViewDocumentType] == web::WEB_VIEW_DOCUMENT_TYPE_GENERIC) {
1318     [self documentPresent];
1319     [self didFinishNavigation];
1320   }
1323 - (void)generateMissingLoadRequestWithURL:(const GURL&)currentURL
1324                                  referrer:(const web::Referrer&)referrer {
1325   [self loadCancelled];
1326   // Initialize transition based on whether the request is user-initiated or
1327   // not. This is a best guess to replace lost transition type informationj.
1328   ui::PageTransition transition = self.userInteractionRegistered
1329                                       ? ui::PAGE_TRANSITION_LINK
1330                                       : ui::PAGE_TRANSITION_CLIENT_REDIRECT;
1331   // If the URL agrees with session state, use the session's transition.
1332   if (currentURL == [self currentNavigationURL]) {
1333     transition = [self currentTransition];
1334   }
1336   [self registerLoadRequest:currentURL referrer:referrer transition:transition];
1339 - (id<CRWWebControllerScripting>)scriptingInterfaceForWindowNamed:
1340     (NSString*)name {
1341   if (![self.delegate respondsToSelector:
1342         @selector(webController:scriptingInterfaceForWindowNamed:)]) {
1343     return nil;
1344   }
1345   return [self.delegate webController:self
1346      scriptingInterfaceForWindowNamed:name];
1349 #pragma mark FullscreenVideo
1351 - (void)moviePlayerDidEnterFullscreen:(NSNotification*)notification {
1352   _inFullscreenVideo = YES;
1355 - (void)moviePlayerDidExitFullscreen:(NSNotification*)notification {
1356   _inFullscreenVideo = NO;
1359 - (void)exitFullscreenVideo {
1360   [self stringByEvaluatingJavaScriptFromString:
1361       @"__gCrWeb.exitFullscreenVideo();"];
1364 #pragma mark -
1365 #pragma mark CRWRecurringTaskDelegate
1367 // Checks for page changes are made continuously.
1368 - (void)runRecurringTask {
1369   if (!self.webView)
1370     return;
1372   [self injectEarlyInjectionScripts];
1373   [self checkForUnexpectedURLChange];
1376 #pragma mark -
1377 #pragma mark CRWRedirectClientDelegate
1379 - (void)wasRedirectedToRequest:(NSURLRequest*)request
1380               redirectResponse:(NSURLResponse*)response {
1381   // This callback can be received after -close is called; ignore it.
1382   if (self.isBeingDestroyed)
1383     return;
1385   // Register the redirected load request if it originated from the main page
1386   // load.
1387   GURL redirectedURL = net::GURLWithNSURL(response.URL);
1388   if ([self currentNavigationURL] == redirectedURL) {
1389     [self registerLoadRequest:net::GURLWithNSURL(request.URL)
1390                      referrer:[self currentReferrer]
1391                    transition:ui::PAGE_TRANSITION_SERVER_REDIRECT];
1392   }
1395 #pragma mark -
1396 #pragma mark UIWebViewDelegate Methods
1398 // Called when a load begins, and for subsequent subpages.
1399 - (BOOL)webView:(UIWebView*)webView
1400     shouldStartLoadWithRequest:(NSURLRequest*)request
1401                 navigationType:(UIWebViewNavigationType)navigationType {
1402   DVLOG(5) << "webViewShouldStartLoadWithRequest "
1403            << net::FormatUrlRequestForLogging(request);
1405   if (self.isBeingDestroyed)
1406     return NO;
1408   GURL url = net::GURLWithNSURL(request.URL);
1410   // The crwebnull protocol is used where an element requires a URL but it
1411   // should not trigger any activity on the WebView.
1412   if (url.SchemeIs("crwebnull"))
1413     return NO;
1415   if ([self urlSchemeIsWebInvoke:url]) {
1416     [self handleWebInvokeURL:url request:request];
1417     return NO;
1418   }
1420   // ##### IMPORTANT NOTE #####
1421   // Do not add new code above this line unless you're certain about what you're
1422   // doing with respect to JS re-entry.
1423   ScopedReentryGuard javaScriptReentryGuard(&_inJavaScriptContext);
1424   web::FrameInfo* targetFrame = nullptr;  // No reliable way to get this info.
1425   BOOL isLinkClick = [self isLinkNavigation:navigationType];
1426   return [self shouldAllowLoadWithRequest:request
1427                               targetFrame:targetFrame
1428                               isLinkClick:isLinkClick];
1431 // Called at multiple points during a load, such as at the start of loading a
1432 // page, and every time an iframe loads. Not called again for server-side
1433 // redirects.
1434 - (void)webViewDidStartLoad:(UIWebView *)webView {
1435   NSURLRequest* request = webView.request;
1436   DVLOG(5) << "webViewDidStartLoad "
1437            << net::FormatUrlRequestForLogging(request);
1438   // |webView:shouldStartLoad| may not be called or called with different URL
1439   // and mainDocURL for the request in certain page navigations. There
1440   // are at least 2 known page navigations where this occurs, in these cases it
1441   // is imperative the URL verification timer is started here.
1442   // The 2 known cases are:
1443   // 1) A malicious page suppressing core.js injection and calling
1444   //    window.history.back() or window.history.forward()
1445   // 2) An iframe loading a URL using target=_blank.
1446   // TODO(shreyasv): crbug.com/349155. Understand further why this happens
1447   // in some case and not in others.
1448   if (webView != self.webView) {
1449     // This happens sometimes as tests are brought down.
1450     // TODO(jimblackler): work out why and fix the problem at source.
1451     LOG(WARNING) << " UIWebViewDelegate message received for inactive WebView.";
1452     return;
1453   }
1454   DCHECK(!self.isBeingDestroyed);
1455   // Increment the number of pending loads. This will be balanced by either
1456   // a |-webViewDidFinishLoad:| or |-webView:didFailLoadWithError:|.
1457   ++_loadCount;
1458   [self.recurringTaskDelegate runRecurringTask];
1461 // Called when the page (or one of its subframes) finishes loading. This is
1462 // called multiple times during a page load, with varying frequency depending
1463 // on the action (going back, loading a page with frames, redirecting).
1464 // See http://goto/efrmm for a summary of why this is so painful.
1465 - (void)webViewDidFinishLoad:(UIWebView*)webView {
1466   DVLOG(5) << "webViewDidFinishLoad "
1467            << net::FormatUrlRequestForLogging(webView.request);
1468   DCHECK(!self.isHalted);
1469   // Occasionally this delegate is invoked as a side effect during core.js
1470   // injection. It is necessary to ensure we do not attempt to start the
1471   // injection process a second time.
1472   if (!_inJavaScriptContext)
1473     [self.recurringTaskDelegate runRecurringTask];
1475   [self performSelector:@selector(generateMissingDocumentLifecycleEvents)
1476              withObject:nil
1477              afterDelay:0];
1479   ++_unloadCount;
1480   if ((_loadCount == _unloadCount) && (self.loadPhase != web::LOAD_REQUESTED))
1481     [self checkDocumentLoaded];
1484 // Called when there is an error loading the page. Some errors aren't actual
1485 // errors, but are caused by user actions such as stopping a page load
1486 // prematurely.
1487 - (void)webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error {
1488   DVLOG(5) << "webViewDidFailLoadWithError "
1489            << net::FormatUrlRequestForLogging(webView.request);
1490   ++_unloadCount;
1492   // Under unknown circumstances navigation item can be null. In that case the
1493   // state of web/ will not be valid and app will crash. Early return avoid a
1494   // crash (crbug.com/411912).
1495   if (!self.webStateImpl ||
1496       !self.webStateImpl->GetNavigationManagerImpl().GetVisibleItem()) {
1497     return;
1498   }
1500   // There's no reliable way to know if a load is for the main frame, so make a
1501   // best-effort guess.
1502   // |_loadCount| is reset to 0 before starting loading a new page, and is
1503   // incremented in each call to |-webViewDidStartLoad:|. The main request
1504   // is the first one to be loaded, and thus has a |_loadCount| of 1.
1505   // Sub-requests have a |_loadCount| > 1.
1506   // An iframe loading after the main page also has a |_loadCount| of 1, as
1507   // |_loadCount| is reset at the end of the main page load. In that case,
1508   // |loadPhase_| is web::PAGE_LOADED (as opposed to web::PAGE_LOADING for a
1509   // main request).
1510   const bool isMainFrame = (_loadCount == 1 &&
1511                             self.loadPhase != web::PAGE_LOADED);
1512   [self handleLoadError:error inMainFrame:isMainFrame];
1515 #pragma mark -
1516 #pragma mark Testing methods
1518 -(id<CRWRecurringTaskDelegate>)recurringTaskDelegate {
1519   return _recurringTaskDelegate;
1522 @end