blob: d5a66adbac5837d6e5d383170c6b227a552487ce [file] [log] [blame]
sdefresned9217bc2016-12-19 13:58:321// Copyright 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#import <UIKit/UIKit.h>
6
Rohit Rao44f204302017-08-10 14:49:547#include "ios/chrome/browser/prerender/preload_controller.h"
sdefresned9217bc2016-12-19 13:58:328
9#include "base/ios/device_util.h"
10#include "base/logging.h"
11#include "base/metrics/field_trial.h"
asvitkinef1899e32017-01-27 16:30:2912#include "base/metrics/histogram_macros.h"
sdefresned9217bc2016-12-19 13:58:3213#include "base/strings/sys_string_conversions.h"
Moe Ahmadi7431d0b2018-04-13 17:32:2014#import "components/prefs/ios/pref_observer_bridge.h"
sdefresned9217bc2016-12-19 13:58:3215#include "components/prefs/pref_service.h"
edchincd32fdf2017-10-25 12:45:4516#import "components/signin/ios/browser/account_consistency_service.h"
mrefaat3ad5e412018-08-02 20:11:3417#import "ios/chrome/browser/app_launcher/app_launcher_tab_helper.h"
sdefresned9217bc2016-12-19 13:58:3218#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
mrefaat4aec47b2019-03-29 18:44:2119#include "ios/chrome/browser/crash_report/crash_report_helper.h"
Sylvain Defresnef5d2d952017-11-14 11:15:3120#import "ios/chrome/browser/geolocation/omnibox_geolocation_controller.h"
Sylvain Defresnef5b8a8e2017-10-24 02:54:4421#import "ios/chrome/browser/history/history_tab_helper.h"
mrefaat3ad5e412018-08-02 20:11:3422#import "ios/chrome/browser/itunes_urls/itunes_urls_handler_tab_helper.h"
sdefresned9217bc2016-12-19 13:58:3223#include "ios/chrome/browser/pref_names.h"
Rohit Rao44f204302017-08-10 14:49:5424#include "ios/chrome/browser/prerender/preload_controller_delegate.h"
edchincd32fdf2017-10-25 12:45:4525#import "ios/chrome/browser/signin/account_consistency_service_factory.h"
sdefresne2c600c52017-04-04 16:49:5926#import "ios/chrome/browser/tabs/tab_helper_util.h"
Mark Cogan7ed77ac2019-05-28 19:01:0827#import "ios/web/public/deprecated/crw_native_content.h"
28#import "ios/web/public/deprecated/crw_native_content_holder.h"
Mohammad Refaate92aff52019-06-26 17:32:2929#import "ios/web/public/deprecated/crw_web_controller_util.h"
Eugene Butde5ddcdc2019-07-23 00:23:4230#import "ios/web/public/navigation/navigation_item.h"
31#import "ios/web/public/navigation/navigation_manager.h"
32#import "ios/web/public/navigation/web_state_policy_decider_bridge.h"
Yi Sua98c08fb2019-06-13 09:03:5633#include "ios/web/public/thread/web_thread.h"
Kurt Horimoto271707392019-09-21 00:28:0834#import "ios/web/public/ui/java_script_dialog_presenter.h"
Eugene Butd3564aaea2019-08-13 15:50:4535#import "ios/web/public/web_state.h"
Eugene But45bb9962019-09-16 17:34:2136#import "ios/web/public/web_state_observer_bridge.h"
sdefresned9217bc2016-12-19 13:58:3237#import "ios/web/web_state/ui/crw_web_controller.h"
38#import "net/base/mac/url_conversions.h"
sdefresned9217bc2016-12-19 13:58:3239#include "ui/base/page_transition_types.h"
40
stkhapugine757b63a2017-04-10 12:31:2741#if !defined(__has_feature) || !__has_feature(objc_arc)
42#error "This file requires ARC support."
43#endif
44
mrefaat3ad5e412018-08-02 20:11:3445using web::WebStatePolicyDecider;
46
Kurt Horimoto271707392019-09-21 00:28:0847@interface PreloadController () <CRWWebStateObserver,
48 CRWWebStatePolicyDecider,
49 ManageAccountsDelegate,
50 PrefObserverDelegate> {
sdefresne2c600c52017-04-04 16:49:5951 ios::ChromeBrowserState* browserState_; // Weak.
52
53 // The WebState used for prerendering.
54 std::unique_ptr<web::WebState> webState_;
55
Sylvain Defresnef5d2d952017-11-14 11:15:3156 // The WebStateDelegateBridge used to register self as a CRWWebStateDelegate
57 // with the pre-rendered WebState.
58 std::unique_ptr<web::WebStateDelegateBridge> webStateDelegate_;
59
Sylvain Defresne949367262018-03-02 22:33:1660 // The WebStateObserverBridge used to register self as a WebStateObserver
61 // with the pre-rendered WebState.
62 std::unique_ptr<web::WebStateObserverBridge> webStateObserver_;
63
sdefresne2c600c52017-04-04 16:49:5964 // The URL that is prerendered in |webState_|. This can be different from
65 // the value returned by WebState last committed navigation item, for example
66 // in cases where there was a redirect.
67 //
68 // When choosing whether or not to use a prerendered Tab,
69 // BrowserViewController compares the URL being loaded by the omnibox with the
70 // URL of the prerendered Tab. Comparing against the Tab's currently URL
71 // could return false negatives in cases of redirect, hence the need to store
72 // the originally prerendered URL.
73 GURL prerenderedURL_;
74
75 // The URL that is scheduled to be prerendered, its associated transition and
76 // referrer. |scheduledTransition_| and |scheduledReferrer_| are not valid
77 // when |scheduledURL_| is empty.
78 GURL scheduledURL_;
79 ui::PageTransition scheduledTransition_;
80 web::Referrer scheduledReferrer_;
81
sdefresne2c600c52017-04-04 16:49:5982 // Bridge to listen to pref changes.
83 std::unique_ptr<PrefObserverBridge> observerBridge_;
84 // Registrar for pref changes notifications.
85 PrefChangeRegistrar prefChangeRegistrar_;
86 // Observer for the WWAN setting. Contains a valid object only if the
87 // instant setting is set to wifi-only.
88 std::unique_ptr<ConnectionTypeObserverBridge> connectionTypeObserverBridge_;
89
90 // Whether or not the preference is enabled.
91 BOOL enabled_;
92 // Whether or not prerendering is only when on wifi.
93 BOOL wifiOnly_;
94 // Whether or not the current connection is using WWAN.
95 BOOL usingWWAN_;
96
97 // Number of successful prerenders (i.e. the user viewed the prerendered page)
98 // during the lifetime of this controller.
99 int successfulPrerendersPerSessionCount_;
mrefaat3ad5e412018-08-02 20:11:34100
Justin Cohen118fee6c2018-12-06 19:36:59101 // Tracks the last time of the last attempt to load a |prerenderedURL_|. Used
102 // for UMA reporting of load durations.
103 base::TimeTicks startTime_;
104
mrefaat3ad5e412018-08-02 20:11:34105 // Bridge to provide navigation policies for |webState_|.
106 std::unique_ptr<web::WebStatePolicyDeciderBridge> policyDeciderBridge_;
Kurt Horimoto271707392019-09-21 00:28:08107
108 // The dialog presenter.
109 std::unique_ptr<web::JavaScriptDialogPresenter> dialog_presenter_;
sdefresne2c600c52017-04-04 16:49:59110}
sdefresned9217bc2016-12-19 13:58:32111
Kurt Horimoto271707392019-09-21 00:28:08112// Returns YES if prerendering is enabled.
113- (BOOL)isPrerenderingEnabled;
114
115// Returns YES if the |url| is valid for prerendering.
116- (BOOL)shouldPreloadURL:(const GURL&)url;
117
118// Called to start any scheduled prerendering requests.
119- (void)startPrerender;
120
121// Destroys the preview Tab and resets |prerenderURL_| to the empty URL.
122- (void)destroyPreviewContents;
123
124// Schedules the current prerender to be cancelled during the next run of the
125// event loop.
126- (void)schedulePrerenderCancel;
127
128// Removes any scheduled prerender requests and resets |scheduledURL| to the
129// empty URL.
130- (void)removeScheduledPrerenderRequests;
131
132// Records metric on a successful prerender.
133- (void)recordReleaseMetrics;
134
135@end
136
137namespace {
138
139// PrerenderFinalStatus values are used in the "Prerender.FinalStatus" histogram
140// and new values needs to be kept in sync with histogram.xml.
141enum PrerenderFinalStatus {
142 PRERENDER_FINAL_STATUS_USED = 0,
143 PRERENDER_FINAL_STATUS_MEMORY_LIMIT_EXCEEDED = 12,
144 PRERENDER_FINAL_STATUS_CANCELLED = 32,
145 PRERENDER_FINAL_STATUS_MAX = 52,
146};
147
148// Delay before starting to prerender a URL.
149const NSTimeInterval kPrerenderDelay = 0.5;
150
151// The finch experiment to turn off prerendering as a field trial.
152const char kTabEvictionFieldTrialName[] = "TabEviction";
153// The associated group.
154const char kPrerenderTabEvictionTrialGroup[] = "NoPrerendering";
155// The name of the histogram for recording final status (e.g. used/cancelled)
156// of prerender requests.
157const char kPrerenderFinalStatusHistogramName[] = "Prerender.FinalStatus";
158// The name of the histogram for recording the number of successful prerenders.
159const char kPrerendersPerSessionCountHistogramName[] =
160 "Prerender.PrerendersPerSessionCount";
161// The name of the histogram for recording time until a successful prerender.
162const char kPrerenderStartToReleaseContentsTime[] =
163 "Prerender.PrerenderStartToReleaseContentsTime";
164
165// Is this install selected for this particular experiment.
166bool IsPrerenderTabEvictionExperimentalGroup() {
167 base::FieldTrial* trial =
168 base::FieldTrialList::Find(kTabEvictionFieldTrialName);
169 return trial && trial->group_name() == kPrerenderTabEvictionTrialGroup;
170}
171
172// A no-op JavaScriptDialogPresenter that cancels prerendering when the
173// prerendered page attempts to show dialogs.
174class PreloadJavaScriptDialogPresenter : public web::JavaScriptDialogPresenter {
175 public:
176 explicit PreloadJavaScriptDialogPresenter(PreloadController* controller)
177 : controller_(controller) {
178 DCHECK(controller_);
179 }
180
181 // web::JavaScriptDialogPresenter:
182 void RunJavaScriptDialog(web::WebState* web_state,
183 const GURL& origin_url,
184 web::JavaScriptDialogType dialog_type,
185 NSString* message_text,
186 NSString* default_prompt_text,
187 web::DialogClosedCallback callback) override {
188 std::move(callback).Run(NO, nil);
189 [controller_ schedulePrerenderCancel];
190 }
191
192 void CancelDialogs(web::WebState* web_state) override {}
193
194 private:
195 __weak PreloadController* controller_ = nil;
196};
197} // namespace
198
199@implementation PreloadController
sdefresned9217bc2016-12-19 13:58:32200
Kurt Horimoto38bfb152019-09-25 21:05:41201@synthesize prerenderedURL = prerenderedURL_;
202
sdefresned9217bc2016-12-19 13:58:32203- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState {
204 DCHECK(browserState);
205 DCHECK_CURRENTLY_ON(web::WebThread::UI);
206 if ((self = [super init])) {
207 browserState_ = browserState;
208 enabled_ =
209 browserState_->GetPrefs()->GetBoolean(prefs::kNetworkPredictionEnabled);
210 wifiOnly_ = browserState_->GetPrefs()->GetBoolean(
211 prefs::kNetworkPredictionWifiOnly);
212 usingWWAN_ = net::NetworkChangeNotifier::IsConnectionCellular(
213 net::NetworkChangeNotifier::GetConnectionType());
Sylvain Defresne949367262018-03-02 22:33:16214 webStateDelegate_ = std::make_unique<web::WebStateDelegateBridge>(self);
215 webStateObserver_ = std::make_unique<web::WebStateObserverBridge>(self);
216 observerBridge_ = std::make_unique<PrefObserverBridge>(self);
sdefresned9217bc2016-12-19 13:58:32217 prefChangeRegistrar_.Init(browserState_->GetPrefs());
218 observerBridge_->ObserveChangesForPreference(
219 prefs::kNetworkPredictionEnabled, &prefChangeRegistrar_);
220 observerBridge_->ObserveChangesForPreference(
221 prefs::kNetworkPredictionWifiOnly, &prefChangeRegistrar_);
Kurt Horimoto271707392019-09-21 00:28:08222 dialog_presenter_ =
223 std::make_unique<PreloadJavaScriptDialogPresenter>(self);
sdefresned9217bc2016-12-19 13:58:32224 if (enabled_ && wifiOnly_) {
Sylvain Defresne949367262018-03-02 22:33:16225 connectionTypeObserverBridge_ =
226 std::make_unique<ConnectionTypeObserverBridge>(self);
sdefresned9217bc2016-12-19 13:58:32227 }
228
229 [[NSNotificationCenter defaultCenter]
230 addObserver:self
231 selector:@selector(didReceiveMemoryWarning)
232 name:UIApplicationDidReceiveMemoryWarningNotification
233 object:nil];
234 }
235 return self;
236}
237
michaeldobc2f42e2017-01-12 19:04:47238- (void)browserStateDestroyed {
239 [self cancelPrerender];
240 connectionTypeObserverBridge_.reset();
241}
242
sdefresned9217bc2016-12-19 13:58:32243- (void)dealloc {
Steven Holte95922222018-09-14 20:06:23244 UMA_HISTOGRAM_COUNTS_1M(kPrerendersPerSessionCountHistogramName,
245 successfulPrerendersPerSessionCount_);
sdefresned9217bc2016-12-19 13:58:32246 [self cancelPrerender];
sdefresned9217bc2016-12-19 13:58:32247}
248
249- (void)prerenderURL:(const GURL&)url
250 referrer:(const web::Referrer&)referrer
251 transition:(ui::PageTransition)transition
252 immediately:(BOOL)immediately {
Rohit Rao44f204302017-08-10 14:49:54253 // TODO(crbug.com/754050): If shouldPrerenderURL returns false, should we
254 // cancel any scheduled prerender requests?
sdefresned9217bc2016-12-19 13:58:32255 if (![self isPrerenderingEnabled] || ![self shouldPreloadURL:url])
256 return;
257
258 // Ignore this request if there is already a scheduled request for the same
259 // URL; or, if there is no scheduled request, but the currently prerendered
260 // page matches this URL.
261 if (url == scheduledURL_ ||
262 (scheduledURL_.is_empty() && url == prerenderedURL_)) {
263 return;
264 }
265
266 [self removeScheduledPrerenderRequests];
267 scheduledURL_ = url;
268 scheduledTransition_ = transition;
269 scheduledReferrer_ = referrer;
270
271 NSTimeInterval delay = immediately ? 0.0 : kPrerenderDelay;
272 [self performSelector:@selector(startPrerender)
273 withObject:nil
274 afterDelay:delay];
275}
276
sdefresned9217bc2016-12-19 13:58:32277- (void)cancelPrerender {
278 [self cancelPrerenderForReason:PRERENDER_FINAL_STATUS_CANCELLED];
279}
280
281- (void)cancelPrerenderForReason:(PrerenderFinalStatus)reason {
282 [self removeScheduledPrerenderRequests];
283 [self destroyPreviewContentsForReason:reason];
284}
285
Sylvain Defresnef5d2d952017-11-14 11:15:31286- (BOOL)isWebStatePrerendered:(web::WebState*)webState {
287 return webState && webState_.get() == webState;
288}
289
sdefresne2c600c52017-04-04 16:49:59290- (std::unique_ptr<web::WebState>)releasePrerenderContents {
sdefresned9217bc2016-12-19 13:58:32291 successfulPrerendersPerSessionCount_++;
Justin Cohen118fee6c2018-12-06 19:36:59292 [self recordReleaseMetrics];
sdefresned9217bc2016-12-19 13:58:32293 [self removeScheduledPrerenderRequests];
294 prerenderedURL_ = GURL();
Justin Cohen118fee6c2018-12-06 19:36:59295 startTime_ = base::TimeTicks();
Sylvain Defresnef5d2d952017-11-14 11:15:31296 if (!webState_)
297 return nullptr;
Sylvain Defresnef5b8a8e2017-10-24 02:54:44298
Sylvain Defresnef5d2d952017-11-14 11:15:31299 // Move the pre-rendered WebState to a local variable so that it will no
300 // longer be considered as pre-rendering (otherwise tab helpers may early
301 // exist when invoked).
302 std::unique_ptr<web::WebState> webState = std::move(webState_);
303 DCHECK(![self isWebStatePrerendered:webState.get()]);
edchincd32fdf2017-10-25 12:45:45304
Mohammad Refaate92aff52019-06-26 17:32:29305 web_deprecated::SetNativeProvider(webState.get(), nil);
Sylvain Defresne949367262018-03-02 22:33:16306 webState->RemoveObserver(webStateObserver_.get());
mrefaat4aec47b2019-03-29 18:44:21307 breakpad::StopMonitoringURLsForWebState(webState.get());
Sylvain Defresnef5d2d952017-11-14 11:15:31308 webState->SetDelegate(nullptr);
mrefaat3ad5e412018-08-02 20:11:34309 policyDeciderBridge_.reset();
Gauthier Ambard5144bfbd2017-11-15 10:07:52310 HistoryTabHelper::FromWebState(webState.get())
Sylvain Defresnef5d2d952017-11-14 11:15:31311 ->SetDelayHistoryServiceNotification(false);
312
313 if (AccountConsistencyService* accountConsistencyService =
314 ios::AccountConsistencyServiceFactory::GetForBrowserState(
315 browserState_)) {
Gauthier Ambard5144bfbd2017-11-15 10:07:52316 accountConsistencyService->RemoveWebStateHandler(webState.get());
Sylvain Defresnef5d2d952017-11-14 11:15:31317 }
318
mrefaata4bdb95b2018-12-04 18:36:10319 if (!webState->IsLoading()) {
mrefaatc977e372019-02-04 22:19:18320 [[OmniboxGeolocationController sharedInstance]
321 finishPageLoadForWebState:webState.get()
322 loadSuccess:YES];
sdefresne2c600c52017-04-04 16:49:59323 }
324
Sylvain Defresnef5d2d952017-11-14 11:15:31325 return webState;
sdefresned9217bc2016-12-19 13:58:32326}
327
328- (void)connectionTypeChanged:(net::NetworkChangeNotifier::ConnectionType)type {
329 DCHECK_CURRENTLY_ON(web::WebThread::UI);
330 usingWWAN_ = net::NetworkChangeNotifier::IsConnectionCellular(type);
331 if (wifiOnly_ && usingWWAN_)
332 [self cancelPrerender];
333}
334
335- (void)onPreferenceChanged:(const std::string&)preferenceName {
336 if (preferenceName == prefs::kNetworkPredictionEnabled ||
337 preferenceName == prefs::kNetworkPredictionWifiOnly) {
338 DCHECK_CURRENTLY_ON(web::WebThread::UI);
339 // The logic is simpler if both preferences changes are handled equally.
340 enabled_ =
341 browserState_->GetPrefs()->GetBoolean(prefs::kNetworkPredictionEnabled);
342 wifiOnly_ = browserState_->GetPrefs()->GetBoolean(
343 prefs::kNetworkPredictionWifiOnly);
344
345 if (wifiOnly_ && enabled_) {
346 if (!connectionTypeObserverBridge_.get()) {
347 usingWWAN_ = net::NetworkChangeNotifier::IsConnectionCellular(
348 net::NetworkChangeNotifier::GetConnectionType());
349 connectionTypeObserverBridge_.reset(
350 new ConnectionTypeObserverBridge(self));
351 }
352 if (usingWWAN_) {
353 [self cancelPrerender];
354 }
355 } else if (enabled_) {
356 connectionTypeObserverBridge_.reset();
357 } else {
358 [self cancelPrerender];
359 connectionTypeObserverBridge_.reset();
360 }
361 }
362}
363
364- (void)didReceiveMemoryWarning {
365 [self cancelPrerenderForReason:PRERENDER_FINAL_STATUS_MEMORY_LIMIT_EXCEEDED];
366}
367
368#pragma mark -
369#pragma mark CRWNativeContentProvider implementation
370
sdefresned9217bc2016-12-19 13:58:32371- (BOOL)hasControllerForURL:(const GURL&)url {
sdefresne2c600c52017-04-04 16:49:59372 if (!webState_)
373 return NO;
rohitraoeeb5293b2017-06-15 14:40:02374
Kurt Horimoto271707392019-09-21 00:28:08375 return [self.delegate preloadHasNativeControllerForURL:url];
sdefresned9217bc2016-12-19 13:58:32376}
377
378// Override the CRWNativeContentProvider methods to cancel any prerenders that
379// require native content.
olivierrobind43eecb2017-01-27 20:35:26380- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
381 webState:(web::WebState*)webState {
sdefresned9217bc2016-12-19 13:58:32382 [self schedulePrerenderCancel];
383 return nil;
384}
385
Kurt Horimotoa36505e2018-12-22 04:03:10386- (UIEdgeInsets)nativeContentInsetForWebState:(web::WebState*)webState {
387 // |-controllerForURL:webState:| short-circuits the native controller
388 // presentation flow, so the insets are never used.
389 return UIEdgeInsetsZero;
Eugene But00fcaa82018-04-13 20:03:57390}
391
sdefresned9217bc2016-12-19 13:58:32392#pragma mark -
393#pragma mark Private Methods
394
395- (BOOL)isPrerenderingEnabled {
396 DCHECK_CURRENTLY_ON(web::WebThread::UI);
397 return !IsPrerenderTabEvictionExperimentalGroup() && enabled_ &&
398 !ios::device_util::IsSingleCoreDevice() &&
399 ios::device_util::RamIsAtLeast512Mb() && (!wifiOnly_ || !usingWWAN_);
400}
401
sdefresned9217bc2016-12-19 13:58:32402- (BOOL)shouldPreloadURL:(const GURL&)url {
403 return url.SchemeIs(url::kHttpScheme) || url.SchemeIs(url::kHttpsScheme);
404}
405
406- (void)startPrerender {
407 // Destroy any existing prerenders before starting a new one.
408 [self destroyPreviewContents];
409 prerenderedURL_ = scheduledURL_;
410 scheduledURL_ = GURL();
411
412 DCHECK(prerenderedURL_.is_valid());
413 if (!prerenderedURL_.is_valid()) {
414 [self destroyPreviewContents];
415 return;
416 }
417
sdefresne2c600c52017-04-04 16:49:59418 web::WebState::CreateParams createParams(browserState_);
419 webState_ = web::WebState::Create(createParams);
mrefaat3ad5e412018-08-02 20:11:34420 // Add the preload controller as a policyDecider before other tab helpers, so
421 // that it can block the navigation if needed before other policy deciders
422 // execute thier side effects (eg. AppLauncherTabHelper launching app).
423 policyDeciderBridge_ =
424 std::make_unique<web::WebStatePolicyDeciderBridge>(webState_.get(), self);
Sylvain Defresne17b8aa42017-12-21 16:17:17425 AttachTabHelpers(webState_.get(), /*for_prerender=*/true);
sdefresned9217bc2016-12-19 13:58:32426
Mohammad Refaate92aff52019-06-26 17:32:29427 web_deprecated::SetNativeProvider(webState_.get(), nil);
Sylvain Defresne949367262018-03-02 22:33:16428
Sylvain Defresnef5d2d952017-11-14 11:15:31429 webState_->SetDelegate(webStateDelegate_.get());
Sylvain Defresne949367262018-03-02 22:33:16430 webState_->AddObserver(webStateObserver_.get());
mrefaat4aec47b2019-03-29 18:44:21431 breakpad::MonitorURLsForWebState(webState_.get());
Sylvain Defresnef5d2d952017-11-14 11:15:31432 webState_->SetWebUsageEnabled(true);
Sylvain Defresne949367262018-03-02 22:33:16433
edchincd32fdf2017-10-25 12:45:45434 if (AccountConsistencyService* accountConsistencyService =
435 ios::AccountConsistencyServiceFactory::GetForBrowserState(
436 browserState_)) {
437 accountConsistencyService->SetWebStateHandler(webState_.get(), self);
438 }
sdefresne2c600c52017-04-04 16:49:59439
Sylvain Defresnef5b8a8e2017-10-24 02:54:44440 HistoryTabHelper::FromWebState(webState_.get())
441 ->SetDelayHistoryServiceNotification(true);
442
sdefresne2c600c52017-04-04 16:49:59443 web::NavigationManager::WebLoadParams loadParams(prerenderedURL_);
444 loadParams.referrer = scheduledReferrer_;
445 loadParams.transition_type = scheduledTransition_;
Kurt Horimoto271707392019-09-21 00:28:08446 if ([self.delegate preloadShouldUseDesktopUserAgent]) {
sdefresne2c600c52017-04-04 16:49:59447 loadParams.user_agent_override_option =
448 web::NavigationManager::UserAgentOverrideOption::DESKTOP;
449 }
Justin Cohen43fcdec2019-03-27 11:58:45450 webState_->SetKeepRenderProcessAlive(true);
sdefresne7d699dd2017-04-05 13:05:23451 webState_->GetNavigationManager()->LoadURLWithParams(loadParams);
sdefresned9217bc2016-12-19 13:58:32452
Sylvain Defresne83c32cb2018-03-01 11:03:35453 // LoadIfNecessary is needed because the view is not created (but needed) when
454 // loading the page. TODO(crbug.com/705819): Remove this call.
455 webState_->GetNavigationManager()->LoadIfNecessary();
Justin Cohen118fee6c2018-12-06 19:36:59456
457 startTime_ = base::TimeTicks::Now();
sdefresned9217bc2016-12-19 13:58:32458}
459
sdefresned9217bc2016-12-19 13:58:32460- (void)destroyPreviewContents {
461 [self destroyPreviewContentsForReason:PRERENDER_FINAL_STATUS_CANCELLED];
462}
463
464- (void)destroyPreviewContentsForReason:(PrerenderFinalStatus)reason {
sdefresne2c600c52017-04-04 16:49:59465 if (!webState_)
sdefresned9217bc2016-12-19 13:58:32466 return;
467
468 UMA_HISTOGRAM_ENUMERATION(kPrerenderFinalStatusHistogramName, reason,
469 PRERENDER_FINAL_STATUS_MAX);
sdefresne2c600c52017-04-04 16:49:59470
Mohammad Refaate92aff52019-06-26 17:32:29471 web_deprecated::SetNativeProvider(webState_.get(), nil);
Sylvain Defresne949367262018-03-02 22:33:16472 webState_->RemoveObserver(webStateObserver_.get());
mrefaat4aec47b2019-03-29 18:44:21473 breakpad::StopMonitoringURLsForWebState(webState_.get());
Sylvain Defresnef5d2d952017-11-14 11:15:31474 webState_->SetDelegate(nullptr);
sdefresne2c600c52017-04-04 16:49:59475 webState_.reset();
476
sdefresned9217bc2016-12-19 13:58:32477 prerenderedURL_ = GURL();
Justin Cohen118fee6c2018-12-06 19:36:59478 startTime_ = base::TimeTicks();
sdefresned9217bc2016-12-19 13:58:32479}
480
481- (void)schedulePrerenderCancel {
Rohit Rao44f204302017-08-10 14:49:54482 // TODO(crbug.com/228550): Instead of cancelling the prerender, should we mark
483 // it as failed instead? That way, subsequent prerender requests for the same
484 // URL will not kick off new prerenders.
sdefresned9217bc2016-12-19 13:58:32485 [self removeScheduledPrerenderRequests];
486 [self performSelector:@selector(cancelPrerender) withObject:nil afterDelay:0];
487}
488
489- (void)removeScheduledPrerenderRequests {
490 [NSObject cancelPreviousPerformRequestsWithTarget:self];
491 scheduledURL_ = GURL();
492}
493
Sylvain Defresnef5d2d952017-11-14 11:15:31494#pragma mark - CRWWebStateDelegate
495
Eugene But6c41f5e2018-11-09 00:39:32496- (web::WebState*)webState:(web::WebState*)webState
497 createNewWebStateForURL:(const GURL&)URL
498 openerURL:(const GURL&)openerURL
499 initiatedByUser:(BOOL)initiatedByUser {
500 DCHECK([self isWebStatePrerendered:webState]);
501 [self schedulePrerenderCancel];
502 return nil;
503}
504
505- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
506 (web::WebState*)webState {
507 DCHECK([self isWebStatePrerendered:webState]);
Kurt Horimoto271707392019-09-21 00:28:08508 return dialog_presenter_.get();
Eugene But6c41f5e2018-11-09 00:39:32509}
510
Sylvain Defresnef5d2d952017-11-14 11:15:31511- (void)webState:(web::WebState*)webState
512 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
513 proposedCredential:(NSURLCredential*)proposedCredential
514 completionHandler:(void (^)(NSString* username,
515 NSString* password))handler {
516 DCHECK([self isWebStatePrerendered:webState]);
517 [self schedulePrerenderCancel];
518 if (handler) {
519 handler(nil, nil);
520 }
521}
522
Justin Cohen118fee6c2018-12-06 19:36:59523- (void)recordReleaseMetrics {
524 UMA_HISTOGRAM_ENUMERATION(kPrerenderFinalStatusHistogramName,
525 PRERENDER_FINAL_STATUS_USED,
526 PRERENDER_FINAL_STATUS_MAX);
527
528 DCHECK_NE(base::TimeTicks(), startTime_);
529 UMA_HISTOGRAM_TIMES(kPrerenderStartToReleaseContentsTime,
530 base::TimeTicks::Now() - startTime_);
531}
532
Sylvain Defresne949367262018-03-02 22:33:16533#pragma mark - CRWWebStateObserver
sdefresned9217bc2016-12-19 13:58:32534
Sylvain Defresne949367262018-03-02 22:33:16535- (void)webState:(web::WebState*)webState
Sylvain Defresne949367262018-03-02 22:33:16536 didLoadPageWithSuccess:(BOOL)loadSuccess {
537 DCHECK_EQ(webState, webState_.get());
538 // Cancel prerendering if response is "application/octet-stream". It can be a
539 // video file which should not be played from preload tab. See issue at
540 // http://crbug.com/436813 for more details.
541 const std::string& mimeType = webState->GetContentsMimeType();
542 if (mimeType == "application/octet-stream")
543 [self schedulePrerenderCancel];
544}
545
edchincd32fdf2017-10-25 12:45:45546#pragma mark - ManageAccountsDelegate
547
548- (void)onManageAccounts {
Sylvain Defresne949367262018-03-02 22:33:16549 [self schedulePrerenderCancel];
edchincd32fdf2017-10-25 12:45:45550}
551
552- (void)onAddAccount {
Sylvain Defresne949367262018-03-02 22:33:16553 [self schedulePrerenderCancel];
edchincd32fdf2017-10-25 12:45:45554}
555
556- (void)onGoIncognito:(const GURL&)url {
Sylvain Defresne949367262018-03-02 22:33:16557 [self schedulePrerenderCancel];
edchincd32fdf2017-10-25 12:45:45558}
559
mrefaat3ad5e412018-08-02 20:11:34560#pragma mark - CRWWebStatePolicyDecider
561
562- (BOOL)shouldAllowRequest:(NSURLRequest*)request
563 requestInfo:(const WebStatePolicyDecider::RequestInfo&)info {
564 GURL requestURL = net::GURLWithNSURL(request.URL);
565 // Don't allow preloading for requests that are handled by opening another
566 // application or by presenting a native UI.
567 if (AppLauncherTabHelper::IsAppUrl(requestURL) ||
568 ITunesUrlsHandlerTabHelper::CanHandleUrl(requestURL)) {
569 [self schedulePrerenderCancel];
570 return NO;
571 }
572 return YES;
573}
sdefresned9217bc2016-12-19 13:58:32574@end