Fix breakages in https://codereview.chromium.org/1155713003/
[chromium-blink-merge.git] / ios / web / web_state / ui / crw_ui_web_view_web_controller.mm
blob0bb3deb1d38feb4f501c7f2e45dfe1cbd5140919
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"
39 namespace web {
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;
57 }  // namespace web
59 @interface CRWUIWebViewWebController () <CRWRedirectClientDelegate,
60                                          UIWebViewDelegate> {
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
68   // is YES.
69   // TODO(stuartmorgan): Change this to a struct so code using it is more
70   // readable.
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
111   // sub-resource).
112   int _loadCount;
113   int _unloadCount;
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
149 // change.
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:
183     (NSString*)name;
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
203 // processing.
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
208 // redirect.
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;
248 @end
250 namespace {
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 {
257  public:
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;
262   }
263   ~ScopedReentryGuard() {
264     DCHECK(*is_inside_javascript_context_);
265     *is_inside_javascript_context_ = NO;
266   }
268  private:
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 {
278  public:
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];
284   }
286   ~ScopedCachedCurrentUrl() {
287     if (!had_cached_current_url_)
288       [web_controller_ setURLCachingEnabled:NO];
289   }
291  private:
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;
315 }  // namespace
317 @implementation CRWUIWebViewWebController
319 - (instancetype)initWithWebState:(scoped_ptr<web::WebStateImpl>)webState {
320   self = [super initWithWebState:webState.Pass()];
321   if (self) {
322     _jsInvokeParameterQueue.reset([[CRWJSInvokeParameterQueue alloc] init]);
324     NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter];
325     [defaultCenter addObserver:self
326                       selector:@selector(moviePlayerDidEnterFullscreen:)
327                           name:
328         @"UIMoviePlayerControllerDidEnterFullscreenNotification"
329                         object:nil];
330     [defaultCenter addObserver:self
331                       selector:@selector(moviePlayerDidExitFullscreen:)
332                           name:
333         @"UIMoviePlayerControllerDidExitFullscreenNotification"
334                         object:nil];
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
345     // thread.
346     __block base::scoped_nsobject<CRWUIWebViewWebController> scopedSelf(
347         [self retain]);
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_);
355           }
356         }),
357         base::BindBlock(^{
358           scopedSelf.reset();
359         }));
360   }
361   return self;
364 - (void)dealloc {
365   [[NSNotificationCenter defaultCenter] removeObserver:self];
366   [super dealloc];
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:)
390                                withObject:script
391                             waitUntilDone:NO];
394 #pragma mark Overridden public methods
396 - (void)wasShown {
397   if (_uiWebView) {
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();
402   }
403   [super wasShown];
406 - (void)wasHidden {
407   if (self.webView) {
408     // Turn the timer off, to cut down on work being done by background tabs.
409     _continuousCheckTimer->Stop();
410   }
411   [super wasHidden];
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];
420 - (void)close {
421   [super close];
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]];
436   }
439 #pragma mark -
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
461 - (UIView*)webView {
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)
475     return;
476   _urlCachingEnabled = enabled;
477   _cachedURL.first = GURL();
480 - (BOOL)ignoreURLVerificationFailures {
481   return _spoofableRequest;
484 - (NSString*)title {
485   return [self stringByEvaluatingJavaScriptFromString:
486       @"document.title.length ? document.title : ''"];
489 #pragma mark Protected method implementations
491 - (void)ensureWebViewCreated {
492   if (!_uiWebView) {
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];
498   }
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;
509   }
510   ScopedReentryGuard reentryGuard(&_inJavaScriptContext);
511   const GURL url =
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;
526     }
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;
534   }
536   return url;
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.
552   if (!_uiWebView) {
553     return web::WEB_VIEW_DOCUMENT_TYPE_GENERIC;
554   }
555   NSString* documentType =
556       [_uiWebView stringByEvaluatingJavaScriptFromString:
557           @"'' + document"];
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());
576   }
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];
588   NSString* result =
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];
599   }
602 - (void)willLoadCurrentURLInWebView {
603 #if !defined(NDEBUG)
604   if (_uiWebView) {
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:)
609                                         withObject:nil];
610     DCHECK(userAgent);
611     const bool wrongRequestGroupID =
612         ![self.webStateImpl->GetRequestGroupID()
613             isEqualToString:web::ExtractRequestGroupIDFromUserAgent(userAgent)];
614     DLOG_IF(ERROR, wrongRequestGroupID) << "Incorrect user agent in UIWebView";
615   }
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)];
624   } else {
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)];
636       });
637       _lowerFrequencyTimer.reset(
638           new base::Timer(FROM_HERE,
639                           base::TimeDelta::FromMilliseconds(
640                               web::kContinuousCheckHighFrequencyMSMaxDuration),
641                           closure, false /* not repeating */));
642       _lowerFrequencyTimer->Reset();
643     }
644   }
647 - (BOOL)checkForUnexpectedURLChange {
648     // The check makes no sense without an active web view.
649   if (!self.webView)
650     return NO;
652   // Change to UIWebView default page is not considered a 'real' change and
653   // URL changes are not reported.
654   if ([self isDefaultPage])
655     return NO;
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) {
662     return NO;
663   }
665   // If the URL has changed, handle page load mechanics.
666   ScopedCachedCurrentUrl scopedCurrentURL(self);
667   [self webPageChanged];
668   [self checkDocumentLoaded];
669   [self titleDidChange];
671   return YES;
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 {
684   _loadCount = 0;
685   _unloadCount = 0;
688 - (void)setSuppressDialogsWithHelperScript:(NSString*)script {
689   [self stringByEvaluatingJavaScriptFromString:script];
692 - (void)checkDocumentLoaded {
693   NSString* loaded =
694       [self stringByEvaluatingJavaScriptFromString:
695            @"document.readyState === 'loaded' || "
696             "document.readyState === 'complete'"];
697   if ([loaded isEqualToString:@"true"]) {
698     [self didFinishNavigation];
699   }
702 - (NSString*)currentReferrerString {
703   return [self stringByEvaluatingJavaScriptFromString:@"document.referrer"];
706 - (void)titleDidChange {
707   if (![self.delegate respondsToSelector:
708            @selector(webController:titleDidChange:)]) {
709     return;
710   }
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
715   // this DCHECK.
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])
721     return;
723   // The title can be retrieved from the document only if the URL can be
724   // trusted.
725   web::URLVerificationTrustLevel trustLevel =
726       web::URLVerificationTrustLevel::kNone;
727   [self currentURLWithTrustLevel:&trustLevel];
728   if (trustLevel != web::URLVerificationTrustLevel::kAbsolute)
729     return;
731   NSString* title = self.title;
732   if ([title length])
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 {
742   if (!self.webView) {
743     handler(CGSizeZero);
744     return;
745   }
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) {
755     return;
756   }
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]];
779   }
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();
790     else
791       zoomScale = scrollState.zoom_scale() / scrollState.minimum_zoom_scale();
792   }
793   return zoomScale;
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.
823   if (!self.webView) {
824     _jsInvokeParameterQueue.reset([[CRWJSInvokeParameterQueue alloc] init]);
825     return;
826   }
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)
835                withObject:nil
836                afterDelay:0];
837     return;
838   }
839   DCHECK(_jsInvokeParameterQueue);
840   while (![_jsInvokeParameterQueue isEmpty]) {
841     CRWJSInvokeParameters* parameters =
842         [_jsInvokeParameterQueue popInvokeParameters];
843     if (!parameters)
844       return;
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];
854       continue;
855     }
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";
860     }
861   }
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];
876     return;
877   }
878   if (url.spec().length() > kMaxMessageQueueSize) {
879     DLOG(WARNING) << "Messages from JS ignored due to excessive length";
880     return;
881   }
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];
891   } else {
892     [_jsInvokeParameterQueue addCommandString:commandString
893                             userIsInteracting:[self userIsInteracting]
894                                     originURL:originURL
895                                   forWindowId:[super windowId]];
896     if (!_jsMessageQueueThrottled) {
897       [self performSelector:@selector(respondToJSInvoke)
898                  withObject:nil
899                  afterDelay:0];
900     }
901   }
904 - (void)setJsMessageQueueThrottled:(BOOL)throttle {
905   _jsMessageQueueThrottled = throttle;
906   if (!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);
928   int errorCode = 0;
929   std::string errorMessage;
930   scoped_ptr<base::Value> inputJSONData(base::JSONReader::ReadAndReturnError(
931       base::SysNSStringToUTF8(messageQueue), false, &errorCode, &errorMessage));
932   if (errorCode) {
933     DLOG(WARNING) << "JSON parse error: %s" << errorMessage.c_str();
934     return NO;
935   }
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";
940     return NO;
941   }
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();
950       return NO;
951     }
953     base::DictionaryValue* message = nullptr;
954     if (!messages->GetDictionary(idx, &message)) {
955       DLOG(WARNING) << "Message could not be retrieved";
956       return NO;
957     }
958     BOOL messageHandled = [self respondToMessage:message
959                                userIsInteracting:userIsInteracting
960                                        originURL:originURL];
961     if (!messageHandled)
962       return NO;
964     // If handling the message caused this page to be closed, stop processing
965     // messages.
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])
971       return YES;
972   }
973   return YES;
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:);
997   });
998   DCHECK(handlers);
999   auto iter = handlers->find(command);
1000   return iter != handlers->end()
1001              ? iter->second
1002              : [super selectorToHandleJavaScriptCommand:command];
1005 - (NSString*)windowNameFromMessage:(base::DictionaryValue*)message
1006                            context:(NSDictionary*)context {
1007   std::string target;
1008   if(!message->GetString("target", &target)) {
1009     DLOG(WARNING) << "JS message parameter not found: target";
1010     return nil;
1011   }
1012   DCHECK(&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
1018   // contain #.
1019   return base::SysUTF8ToNSString(
1020       NormalizedUrl(originURL) + web::kWindowNameSeparator + target);
1023 #pragma mark -
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];
1031   std::string href;
1032   if (!message->GetString("href", &href)) {
1033     DLOG(WARNING) << "JS message parameter not found: href";
1034     return NO;
1035   }
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];
1056     }
1057   }
1058   return YES;
1061 - (BOOL)handleDocumentLoadedMessage:(base::DictionaryValue*)message
1062                             context:(NSDictionary*)context {
1063   // Very early hashchange events can be missed, hence this extra explicit
1064   // check.
1065   [self checkForUnexpectedURLChange];
1066   [self didFinishNavigation];
1067   return YES;
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];
1076   return YES;
1079 - (BOOL)handleDocumentRetitledMessage:(base::DictionaryValue*)message
1080                               context:(NSDictionary*)context {
1081   [self titleDidChange];
1082   return YES;
1085 - (BOOL)handleWindowCloseMessage:(base::DictionaryValue*)message
1086                          context:(NSDictionary*)context {
1087   NSString* windowName = [self windowNameFromMessage:message
1088                                              context:context];
1089   if (!windowName)
1090     return NO;
1091   [[self scriptingInterfaceForWindowNamed:windowName] orderClose];
1092   return YES;
1095 - (BOOL)handleWindowDocumentWriteMessage:(base::DictionaryValue*)message
1096                                  context:(NSDictionary*)context {
1097   NSString* windowName = [self windowNameFromMessage:message
1098                                              context:context];
1099   if (!windowName)
1100     return NO;
1101   std::string HTML;
1102   if (!message->GetString("html", &HTML)) {
1103     DLOG(WARNING) << "JS message parameter not found: html";
1104     return NO;
1105   }
1106   [[self scriptingInterfaceForWindowNamed:windowName]
1107       loadHTML:base::SysUTF8ToNSString(HTML)];
1108   return YES;
1111 - (BOOL)handleWindowLocationMessage:(base::DictionaryValue*)message
1112                             context:(NSDictionary*)context {
1113   NSString* windowName = [self windowNameFromMessage:message
1114                                              context:context];
1115   if (!windowName)
1116     return NO;
1117   std::string command;
1118   if (!message->GetString("command", &command)) {
1119     DLOG(WARNING) << "JS message parameter not found: command";
1120     return NO;
1121   }
1122   std::string value;
1123   if (!message->GetString("value", &value)) {
1124     DLOG(WARNING) << "JS message parameter not found: value";
1125     return NO;
1126   }
1127   std::string escapedValue;
1128   base::EscapeJSONString(value, true, &escapedValue);
1129   NSString* HTML =
1130       [NSString stringWithFormat:@"<script>%s = %s;</script>",
1131                                  command.c_str(),
1132                                  escapedValue.c_str()];
1133   [[self scriptingInterfaceForWindowNamed:windowName] loadHTML:HTML];
1134   return YES;
1137 - (BOOL)handleWindowOpenMessage:(base::DictionaryValue*)message
1138                         context:(NSDictionary*)context {
1139   NSString* windowName = [self windowNameFromMessage:message
1140                                              context:context];
1141   if (!windowName)
1142     return NO;
1143   std::string targetURL;
1144   if (!message->GetString("url", &targetURL)) {
1145     DLOG(WARNING) << "JS message parameter not found: url";
1146     return NO;
1147   }
1148   std::string referrerPolicy;
1149   if (!message->GetString("referrerPolicy", &referrerPolicy)) {
1150     DLOG(WARNING) << "JS message parameter not found: referrerPolicy";
1151     return NO;
1152   }
1153   GURL resolvedURL = targetURL.empty() ?
1154       self.defaultURL :
1155       GURL(net::GURLWithNSURL(context[web::kOriginURLKey])).Resolve(targetURL);
1156   DCHECK(&resolvedURL);
1157   web::NewWindowInfo
1158       windowInfo(resolvedURL,
1159                  windowName,
1160                  [self referrerPolicyFromString:referrerPolicy],
1161                  [context[web::kUserIsInteractingKey] boolValue]);
1163   [self openPopupWithInfo:windowInfo];
1164   return YES;
1167 - (BOOL)handleWindowStopMessage:(base::DictionaryValue*)message
1168                         context:(NSDictionary*)context {
1169   NSString* windowName = [self windowNameFromMessage:message
1170                                              context:context];
1171   if (!windowName)
1172     return NO;
1173   [[self scriptingInterfaceForWindowNamed:windowName] stopLoading];
1174   return YES;
1177 - (BOOL)handleWindowUnloadMessage:(base::DictionaryValue*)message
1178                           context:(NSDictionary*)context {
1179   [self setPageChangeProbability:web::PAGE_CHANGE_PROBABILITY_VERY_HIGH];
1180   return YES;
1183 #pragma mark Private methods
1185 - (BOOL)isDefaultPage {
1186   if ([[self stringByEvaluatingJavaScriptFromString:@"document._defaultPage"]
1187        isEqualToString:@"true"]) {
1188     return self.currentURL == self.defaultURL;
1189   }
1190   return NO;
1193 - (BOOL)isLinkNavigation:(UIWebViewNavigationType)navigationType {
1194   switch (navigationType) {
1195     case UIWebViewNavigationTypeLinkClicked:
1196       return YES;
1197     case UIWebViewNavigationTypeOther:
1198       return [self userClickedRecently];
1199     default:
1200       return NO;
1201   }
1204 - (void)setContinuousCheckTimerInterval:(const base::TimeDelta&)interval {
1205   // The timer should never be set when there's no web view.
1206   DCHECK(_uiWebView);
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];
1216   });
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();
1225   }
1228 - (NSString*)stringByEvaluatingJavaScriptFromString:(NSString*)script {
1229   if (!_uiWebView)
1230     return nil;
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(
1243       CGRectZero,
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;
1276   if (webView) {
1277     // Do initial injection even before loading another page, since the window
1278     // object is re-used.
1279     [self injectEarlyInjectionScripts];
1280   } else {
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();
1285   }
1288 - (void)generateMissingDocumentLifecycleEvents {
1289   // The webView can be removed between this method being queued and invoked.
1290   if (!self.webView)
1291     return;
1292   if ([self webViewDocumentType] == web::WEB_VIEW_DOCUMENT_TYPE_GENERIC) {
1293     [self documentPresent];
1294     [self didFinishNavigation];
1295   }
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];
1309   }
1311   [self registerLoadRequest:currentURL referrer:referrer transition:transition];
1314 - (id<CRWWebControllerScripting>)scriptingInterfaceForWindowNamed:
1315     (NSString*)name {
1316   if (![self.delegate respondsToSelector:
1317         @selector(webController:scriptingInterfaceForWindowNamed:)]) {
1318     return nil;
1319   }
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();"];
1339 #pragma mark -
1340 #pragma mark CRWRecurringTaskDelegate
1342 // Checks for page changes are made continuously.
1343 - (void)runRecurringTask {
1344   if (!self.webView)
1345     return;
1347   [self injectEarlyInjectionScripts];
1348   [self checkForUnexpectedURLChange];
1351 #pragma mark -
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
1357   // load.
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];
1363   }
1366 #pragma mark -
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)
1377     return NO;
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"))
1384     return NO;
1386   if ([self urlSchemeIsWebInvoke:url]) {
1387     [self handleWebInvokeURL:url request:request];
1388     return NO;
1389   }
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
1404 // redirects.
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.";
1423     return;
1424   }
1425   DCHECK(!self.isBeingDestroyed);
1426   // Increment the number of pending loads. This will be balanced by either
1427   // a |-webViewDidFinishLoad:| or |-webView:didFailLoadWithError:|.
1428   ++_loadCount;
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)
1447              withObject:nil
1448              afterDelay:0];
1450   ++_unloadCount;
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
1457 // prematurely.
1458 - (void)webView:(UIWebView*)webView didFailLoadWithError:(NSError*)error {
1459   DVLOG(5) << "webViewDidFailLoadWithError "
1460            << net::FormatUrlRequestForLogging(webView.request);
1461   ++_unloadCount;
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()) {
1468     return;
1469   }
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
1480   // main request).
1481   const bool isMainFrame = (_loadCount == 1 &&
1482                             self.loadPhase != web::PAGE_LOADED);
1483   [self handleLoadError:error inMainFrame:isMainFrame];
1486 #pragma mark -
1487 #pragma mark Testing methods
1489 -(id<CRWRecurringTaskDelegate>)recurringTaskDelegate {
1490   return _recurringTaskDelegate;
1493 @end