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