blob: a79762a107f0a4452088ff97c9a782c13c26fd79 [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 Horimotoeb0081e2019-09-26 18:53:3547// Protocol used to cancel a scheduled preload request.
48@protocol PreloadCancelling <NSObject>
Kurt Horimoto271707392019-09-21 00:28:0849
50// Schedules the current prerender to be cancelled during the next run of the
51// event loop.
52- (void)schedulePrerenderCancel;
53
Kurt Horimoto271707392019-09-21 00:28:0854@end
55
56namespace {
57
58// PrerenderFinalStatus values are used in the "Prerender.FinalStatus" histogram
59// and new values needs to be kept in sync with histogram.xml.
60enum PrerenderFinalStatus {
61 PRERENDER_FINAL_STATUS_USED = 0,
62 PRERENDER_FINAL_STATUS_MEMORY_LIMIT_EXCEEDED = 12,
63 PRERENDER_FINAL_STATUS_CANCELLED = 32,
64 PRERENDER_FINAL_STATUS_MAX = 52,
65};
66
67// Delay before starting to prerender a URL.
68const NSTimeInterval kPrerenderDelay = 0.5;
69
70// The finch experiment to turn off prerendering as a field trial.
71const char kTabEvictionFieldTrialName[] = "TabEviction";
72// The associated group.
73const char kPrerenderTabEvictionTrialGroup[] = "NoPrerendering";
74// The name of the histogram for recording final status (e.g. used/cancelled)
75// of prerender requests.
76const char kPrerenderFinalStatusHistogramName[] = "Prerender.FinalStatus";
77// The name of the histogram for recording the number of successful prerenders.
78const char kPrerendersPerSessionCountHistogramName[] =
79 "Prerender.PrerendersPerSessionCount";
80// The name of the histogram for recording time until a successful prerender.
81const char kPrerenderStartToReleaseContentsTime[] =
82 "Prerender.PrerenderStartToReleaseContentsTime";
83
84// Is this install selected for this particular experiment.
85bool IsPrerenderTabEvictionExperimentalGroup() {
86 base::FieldTrial* trial =
87 base::FieldTrialList::Find(kTabEvictionFieldTrialName);
88 return trial && trial->group_name() == kPrerenderTabEvictionTrialGroup;
89}
90
Kurt Horimotoeb0081e2019-09-26 18:53:3591// Returns whether |url| can be prerendered.
92bool CanPrerenderURL(const GURL& url) {
93 // Prerendering is only enabled for http and https URLs.
94 return url.is_valid() &&
95 (url.SchemeIs(url::kHttpScheme) || url.SchemeIs(url::kHttpsScheme));
96}
97
98// Object used to schedule prerenders.
99class PrerenderRequest {
100 public:
101 PrerenderRequest() {}
102 PrerenderRequest(const GURL& url,
103 ui::PageTransition transition,
104 const web::Referrer& referrer)
105 : url_(url), transition_(transition), referrer_(referrer) {}
106
107 const GURL& url() const { return url_; }
108 ui::PageTransition transition() const { return transition_; }
109 const web::Referrer referrer() const { return referrer_; }
110
111 private:
112 const GURL url_;
113 const ui::PageTransition transition_ = ui::PAGE_TRANSITION_LINK;
114 const web::Referrer referrer_;
115};
116
Kurt Horimoto271707392019-09-21 00:28:08117// A no-op JavaScriptDialogPresenter that cancels prerendering when the
118// prerendered page attempts to show dialogs.
119class PreloadJavaScriptDialogPresenter : public web::JavaScriptDialogPresenter {
120 public:
Kurt Horimotoeb0081e2019-09-26 18:53:35121 explicit PreloadJavaScriptDialogPresenter(
122 id<PreloadCancelling> cancel_handler)
123 : cancel_handler_(cancel_handler) {
124 DCHECK(cancel_handler_);
Kurt Horimoto271707392019-09-21 00:28:08125 }
126
127 // web::JavaScriptDialogPresenter:
128 void RunJavaScriptDialog(web::WebState* web_state,
129 const GURL& origin_url,
130 web::JavaScriptDialogType dialog_type,
131 NSString* message_text,
132 NSString* default_prompt_text,
133 web::DialogClosedCallback callback) override {
134 std::move(callback).Run(NO, nil);
Kurt Horimotoeb0081e2019-09-26 18:53:35135 [cancel_handler_ schedulePrerenderCancel];
Kurt Horimoto271707392019-09-21 00:28:08136 }
137
138 void CancelDialogs(web::WebState* web_state) override {}
139
140 private:
Kurt Horimotoeb0081e2019-09-26 18:53:35141 __weak id<PreloadCancelling> cancel_handler_ = nil;
Kurt Horimoto271707392019-09-21 00:28:08142};
143} // namespace
144
Kurt Horimotoeb0081e2019-09-26 18:53:35145@interface PreloadController () <CRConnectionTypeObserverBridge,
146 CRWWebStateDelegate,
147 CRWWebStateObserver,
148 CRWWebStatePolicyDecider,
149 ManageAccountsDelegate,
150 PrefObserverDelegate,
151 PreloadCancelling> {
152 std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
153 std::unique_ptr<web::WebStateObserverBridge> _webStateObserver;
154 std::unique_ptr<PrefObserverBridge> _observerBridge;
155 std::unique_ptr<ConnectionTypeObserverBridge> _connectionTypeObserver;
156 std::unique_ptr<web::WebStatePolicyDeciderBridge> _policyDeciderBridge;
sdefresned9217bc2016-12-19 13:58:32157
Kurt Horimotoeb0081e2019-09-26 18:53:35158 // The WebState used for prerendering.
159 std::unique_ptr<web::WebState> _webState;
160
161 // The scheduled request.
162 std::unique_ptr<PrerenderRequest> _scheduledRequest;
163
164 // Registrar for pref changes notifications.
165 PrefChangeRegistrar _prefChangeRegistrar;
166
167 // The dialog presenter.
168 std::unique_ptr<web::JavaScriptDialogPresenter> _dialogPresenter;
169}
170
171// The ChromeBrowserState passed on initialization.
172@property(nonatomic) ios::ChromeBrowserState* browserState;
173
174// Redefine property as readwrite. The URL that is prerendered in |_webState|.
175// This can be different from the value returned by WebState last committed
176// navigation item, for example in cases where there was a redirect.
177//
178// When choosing whether or not to use a prerendered Tab,
179// BrowserViewController compares the URL being loaded by the omnibox with the
180// URL of the prerendered Tab. Comparing against the Tab's currently URL
181// could return false negatives in cases of redirect, hence the need to store
182// the originally prerendered URL.
183@property(nonatomic, readwrite, assign) GURL prerenderedURL;
184
185// The URL in the currently scheduled prerender request, or an empty one if
186// there is no prerender scheduled.
187@property(nonatomic, readonly) const GURL& scheduledURL;
188
189// Whether or not the preference is enabled.
190@property(nonatomic, getter=isPreferenceEnabled) BOOL preferenceEnabled;
191
192// Whether or not prerendering is only when on wifi.
193@property(nonatomic, getter=isWifiOnly) BOOL wifiOnly;
194
195// Whether or not the current connection is using WWAN.
196@property(nonatomic, getter=isUsingWWAN) BOOL usingWWAN;
197
198// Number of successful prerenders (i.e. the user viewed the prerendered page)
199// during the lifetime of this controller.
200@property(nonatomic) NSUInteger successfulPrerendersPerSessionCount;
201
202// Tracks the time of the last attempt to load a prerender URL. Used for UMA
203// reporting of load durations.
204@property(nonatomic) base::TimeTicks startTime;
205
206// Called to start any scheduled prerendering requests.
207- (void)startPrerender;
208
209// Destroys the preview Tab and resets |prerenderURL_| to the empty URL.
210- (void)destroyPreviewContents;
211
212// Removes any scheduled prerender requests and resets |scheduledURL| to the
213// empty URL.
214- (void)removeScheduledPrerenderRequests;
215
216// Records metric on a successful prerender.
217- (void)recordReleaseMetrics;
218
219@end
220
221@implementation PreloadController
Kurt Horimoto38bfb152019-09-25 21:05:41222
sdefresned9217bc2016-12-19 13:58:32223- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState {
224 DCHECK(browserState);
225 DCHECK_CURRENTLY_ON(web::WebThread::UI);
226 if ((self = [super init])) {
Kurt Horimotoeb0081e2019-09-26 18:53:35227 _browserState = browserState;
228 _preferenceEnabled =
229 _browserState->GetPrefs()->GetBoolean(prefs::kNetworkPredictionEnabled);
230 _wifiOnly = _browserState->GetPrefs()->GetBoolean(
sdefresned9217bc2016-12-19 13:58:32231 prefs::kNetworkPredictionWifiOnly);
Kurt Horimotoeb0081e2019-09-26 18:53:35232 _usingWWAN = net::NetworkChangeNotifier::IsConnectionCellular(
sdefresned9217bc2016-12-19 13:58:32233 net::NetworkChangeNotifier::GetConnectionType());
Kurt Horimotoeb0081e2019-09-26 18:53:35234 _webStateDelegate = std::make_unique<web::WebStateDelegateBridge>(self);
235 _webStateObserver = std::make_unique<web::WebStateObserverBridge>(self);
236 _observerBridge = std::make_unique<PrefObserverBridge>(self);
237 _prefChangeRegistrar.Init(_browserState->GetPrefs());
238 _observerBridge->ObserveChangesForPreference(
239 prefs::kNetworkPredictionEnabled, &_prefChangeRegistrar);
240 _observerBridge->ObserveChangesForPreference(
241 prefs::kNetworkPredictionWifiOnly, &_prefChangeRegistrar);
242 _dialogPresenter = std::make_unique<PreloadJavaScriptDialogPresenter>(self);
243 if (_preferenceEnabled && _wifiOnly) {
244 _connectionTypeObserver =
Sylvain Defresne949367262018-03-02 22:33:16245 std::make_unique<ConnectionTypeObserverBridge>(self);
sdefresned9217bc2016-12-19 13:58:32246 }
247
248 [[NSNotificationCenter defaultCenter]
249 addObserver:self
250 selector:@selector(didReceiveMemoryWarning)
251 name:UIApplicationDidReceiveMemoryWarningNotification
252 object:nil];
253 }
254 return self;
255}
256
sdefresned9217bc2016-12-19 13:58:32257- (void)dealloc {
Steven Holte95922222018-09-14 20:06:23258 UMA_HISTOGRAM_COUNTS_1M(kPrerendersPerSessionCountHistogramName,
Kurt Horimotoeb0081e2019-09-26 18:53:35259 self.successfulPrerendersPerSessionCount);
sdefresned9217bc2016-12-19 13:58:32260 [self cancelPrerender];
sdefresned9217bc2016-12-19 13:58:32261}
262
Kurt Horimotoeb0081e2019-09-26 18:53:35263#pragma mark - Accessors
264
265- (const GURL&)scheduledURL {
266 return _scheduledRequest ? _scheduledRequest->url() : GURL::EmptyGURL();
267}
268
269- (BOOL)isEnabled {
270 DCHECK_CURRENTLY_ON(web::WebThread::UI);
271 return !IsPrerenderTabEvictionExperimentalGroup() && self.preferenceEnabled &&
272 !ios::device_util::IsSingleCoreDevice() &&
273 ios::device_util::RamIsAtLeast512Mb() &&
274 (!self.wifiOnly || !self.usingWWAN);
275}
276
277#pragma mark - Public
278
279- (void)browserStateDestroyed {
280 [self cancelPrerender];
281 _connectionTypeObserver.reset();
282}
283
sdefresned9217bc2016-12-19 13:58:32284- (void)prerenderURL:(const GURL&)url
285 referrer:(const web::Referrer&)referrer
286 transition:(ui::PageTransition)transition
287 immediately:(BOOL)immediately {
Kurt Horimotoeb0081e2019-09-26 18:53:35288 // TODO(crbug.com/754050): If CanPrerenderURL() returns false, should we
Rohit Rao44f204302017-08-10 14:49:54289 // cancel any scheduled prerender requests?
Kurt Horimotoeb0081e2019-09-26 18:53:35290 if (!self.enabled || !CanPrerenderURL(url))
sdefresned9217bc2016-12-19 13:58:32291 return;
292
293 // Ignore this request if there is already a scheduled request for the same
294 // URL; or, if there is no scheduled request, but the currently prerendered
295 // page matches this URL.
Kurt Horimotoeb0081e2019-09-26 18:53:35296 if (url == self.scheduledURL ||
297 (self.scheduledURL.is_empty() && url == self.prerenderedURL)) {
sdefresned9217bc2016-12-19 13:58:32298 return;
299 }
300
301 [self removeScheduledPrerenderRequests];
Kurt Horimotoeb0081e2019-09-26 18:53:35302 _scheduledRequest =
303 std::make_unique<PrerenderRequest>(url, transition, referrer);
sdefresned9217bc2016-12-19 13:58:32304
305 NSTimeInterval delay = immediately ? 0.0 : kPrerenderDelay;
306 [self performSelector:@selector(startPrerender)
307 withObject:nil
308 afterDelay:delay];
309}
310
sdefresned9217bc2016-12-19 13:58:32311- (void)cancelPrerender {
312 [self cancelPrerenderForReason:PRERENDER_FINAL_STATUS_CANCELLED];
313}
314
315- (void)cancelPrerenderForReason:(PrerenderFinalStatus)reason {
316 [self removeScheduledPrerenderRequests];
317 [self destroyPreviewContentsForReason:reason];
318}
319
Sylvain Defresnef5d2d952017-11-14 11:15:31320- (BOOL)isWebStatePrerendered:(web::WebState*)webState {
Kurt Horimotoeb0081e2019-09-26 18:53:35321 return webState && _webState.get() == webState;
Sylvain Defresnef5d2d952017-11-14 11:15:31322}
323
sdefresne2c600c52017-04-04 16:49:59324- (std::unique_ptr<web::WebState>)releasePrerenderContents {
Kurt Horimotoeb0081e2019-09-26 18:53:35325 if (!_webState)
326 return nullptr;
327
328 self.successfulPrerendersPerSessionCount++;
Justin Cohen118fee6c2018-12-06 19:36:59329 [self recordReleaseMetrics];
sdefresned9217bc2016-12-19 13:58:32330 [self removeScheduledPrerenderRequests];
Kurt Horimotoeb0081e2019-09-26 18:53:35331 self.prerenderedURL = GURL();
332 self.startTime = base::TimeTicks();
Sylvain Defresnef5b8a8e2017-10-24 02:54:44333
Sylvain Defresnef5d2d952017-11-14 11:15:31334 // Move the pre-rendered WebState to a local variable so that it will no
335 // longer be considered as pre-rendering (otherwise tab helpers may early
336 // exist when invoked).
Kurt Horimotoeb0081e2019-09-26 18:53:35337 std::unique_ptr<web::WebState> webState = std::move(_webState);
Sylvain Defresnef5d2d952017-11-14 11:15:31338 DCHECK(![self isWebStatePrerendered:webState.get()]);
edchincd32fdf2017-10-25 12:45:45339
Mohammad Refaate92aff52019-06-26 17:32:29340 web_deprecated::SetNativeProvider(webState.get(), nil);
Kurt Horimotoeb0081e2019-09-26 18:53:35341 webState->RemoveObserver(_webStateObserver.get());
mrefaat4aec47b2019-03-29 18:44:21342 breakpad::StopMonitoringURLsForWebState(webState.get());
Sylvain Defresnef5d2d952017-11-14 11:15:31343 webState->SetDelegate(nullptr);
Kurt Horimotoeb0081e2019-09-26 18:53:35344 _policyDeciderBridge.reset();
Gauthier Ambard5144bfbd2017-11-15 10:07:52345 HistoryTabHelper::FromWebState(webState.get())
Sylvain Defresnef5d2d952017-11-14 11:15:31346 ->SetDelayHistoryServiceNotification(false);
347
348 if (AccountConsistencyService* accountConsistencyService =
349 ios::AccountConsistencyServiceFactory::GetForBrowserState(
Kurt Horimotoeb0081e2019-09-26 18:53:35350 self.browserState)) {
Gauthier Ambard5144bfbd2017-11-15 10:07:52351 accountConsistencyService->RemoveWebStateHandler(webState.get());
Sylvain Defresnef5d2d952017-11-14 11:15:31352 }
353
mrefaata4bdb95b2018-12-04 18:36:10354 if (!webState->IsLoading()) {
mrefaatc977e372019-02-04 22:19:18355 [[OmniboxGeolocationController sharedInstance]
356 finishPageLoadForWebState:webState.get()
357 loadSuccess:YES];
sdefresne2c600c52017-04-04 16:49:59358 }
359
Sylvain Defresnef5d2d952017-11-14 11:15:31360 return webState;
sdefresned9217bc2016-12-19 13:58:32361}
362
Kurt Horimotoeb0081e2019-09-26 18:53:35363#pragma mark - CRConnectionTypeObserverBridge
364
sdefresned9217bc2016-12-19 13:58:32365- (void)connectionTypeChanged:(net::NetworkChangeNotifier::ConnectionType)type {
366 DCHECK_CURRENTLY_ON(web::WebThread::UI);
Kurt Horimotoeb0081e2019-09-26 18:53:35367 self.usingWWAN = net::NetworkChangeNotifier::IsConnectionCellular(type);
368 if (self.wifiOnly && self.usingWWAN)
sdefresned9217bc2016-12-19 13:58:32369 [self cancelPrerender];
370}
371
Sylvain Defresnef5d2d952017-11-14 11:15:31372#pragma mark - CRWWebStateDelegate
373
Eugene But6c41f5e2018-11-09 00:39:32374- (web::WebState*)webState:(web::WebState*)webState
375 createNewWebStateForURL:(const GURL&)URL
376 openerURL:(const GURL&)openerURL
377 initiatedByUser:(BOOL)initiatedByUser {
378 DCHECK([self isWebStatePrerendered:webState]);
379 [self schedulePrerenderCancel];
380 return nil;
381}
382
383- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
384 (web::WebState*)webState {
385 DCHECK([self isWebStatePrerendered:webState]);
Kurt Horimotoeb0081e2019-09-26 18:53:35386 return _dialogPresenter.get();
Eugene But6c41f5e2018-11-09 00:39:32387}
388
Sylvain Defresnef5d2d952017-11-14 11:15:31389- (void)webState:(web::WebState*)webState
390 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
391 proposedCredential:(NSURLCredential*)proposedCredential
392 completionHandler:(void (^)(NSString* username,
393 NSString* password))handler {
394 DCHECK([self isWebStatePrerendered:webState]);
395 [self schedulePrerenderCancel];
396 if (handler) {
397 handler(nil, nil);
398 }
399}
400
Sylvain Defresne949367262018-03-02 22:33:16401#pragma mark - CRWWebStateObserver
sdefresned9217bc2016-12-19 13:58:32402
Sylvain Defresne949367262018-03-02 22:33:16403- (void)webState:(web::WebState*)webState
404 didLoadPageWithSuccess:(BOOL)loadSuccess {
Kurt Horimotoeb0081e2019-09-26 18:53:35405 DCHECK_EQ(webState, _webState.get());
Sylvain Defresne949367262018-03-02 22:33:16406 // Cancel prerendering if response is "application/octet-stream". It can be a
407 // video file which should not be played from preload tab. See issue at
408 // http://crbug.com/436813 for more details.
409 const std::string& mimeType = webState->GetContentsMimeType();
410 if (mimeType == "application/octet-stream")
411 [self schedulePrerenderCancel];
412}
413
mrefaat3ad5e412018-08-02 20:11:34414#pragma mark - CRWWebStatePolicyDecider
415
416- (BOOL)shouldAllowRequest:(NSURLRequest*)request
417 requestInfo:(const WebStatePolicyDecider::RequestInfo&)info {
418 GURL requestURL = net::GURLWithNSURL(request.URL);
419 // Don't allow preloading for requests that are handled by opening another
420 // application or by presenting a native UI.
421 if (AppLauncherTabHelper::IsAppUrl(requestURL) ||
422 ITunesUrlsHandlerTabHelper::CanHandleUrl(requestURL)) {
423 [self schedulePrerenderCancel];
424 return NO;
425 }
426 return YES;
427}
Kurt Horimotoeb0081e2019-09-26 18:53:35428
429#pragma mark - ManageAccountsDelegate
430
431- (void)onManageAccounts {
432 [self schedulePrerenderCancel];
433}
434
435- (void)onAddAccount {
436 [self schedulePrerenderCancel];
437}
438
439- (void)onGoIncognito:(const GURL&)url {
440 [self schedulePrerenderCancel];
441}
442
443#pragma mark - PrefObserverDelegate
444
445- (void)onPreferenceChanged:(const std::string&)preferenceName {
446 if (preferenceName == prefs::kNetworkPredictionEnabled ||
447 preferenceName == prefs::kNetworkPredictionWifiOnly) {
448 DCHECK_CURRENTLY_ON(web::WebThread::UI);
449 // The logic is simpler if both preferences changes are handled equally.
450 self.preferenceEnabled = self.browserState->GetPrefs()->GetBoolean(
451 prefs::kNetworkPredictionEnabled);
452 self.wifiOnly = self.browserState->GetPrefs()->GetBoolean(
453 prefs::kNetworkPredictionWifiOnly);
454
455 if (self.wifiOnly && self.preferenceEnabled) {
456 if (!_connectionTypeObserver.get()) {
457 self.usingWWAN = net::NetworkChangeNotifier::IsConnectionCellular(
458 net::NetworkChangeNotifier::GetConnectionType());
459 _connectionTypeObserver.reset(new ConnectionTypeObserverBridge(self));
460 }
461 if (self.usingWWAN) {
462 [self cancelPrerender];
463 }
464 } else if (self.preferenceEnabled) {
465 _connectionTypeObserver.reset();
466 } else {
467 [self cancelPrerender];
468 _connectionTypeObserver.reset();
469 }
470 }
471}
472
473#pragma mark - PreloadCancelling
474
475- (void)schedulePrerenderCancel {
476 // TODO(crbug.com/228550): Instead of cancelling the prerender, should we mark
477 // it as failed instead? That way, subsequent prerender requests for the same
478 // URL will not kick off new prerenders.
479 [self removeScheduledPrerenderRequests];
480 [self performSelector:@selector(cancelPrerender) withObject:nil afterDelay:0];
481}
482
483#pragma mark - Cancellation Helpers
484
485- (void)removeScheduledPrerenderRequests {
486 [NSObject cancelPreviousPerformRequestsWithTarget:self];
487 _scheduledRequest = nullptr;
488}
489
490#pragma mark - Prerender Helpers
491
492- (void)startPrerender {
493 // Destroy any existing prerenders before starting a new one.
494 [self destroyPreviewContents];
495 self.prerenderedURL = self.scheduledURL;
496 std::unique_ptr<PrerenderRequest> request = std::move(_scheduledRequest);
497
498 if (!self.prerenderedURL.is_valid()) {
499 [self destroyPreviewContents];
500 return;
501 }
502
503 web::WebState::CreateParams createParams(self.browserState);
504 _webState = web::WebState::Create(createParams);
505 // Add the preload controller as a policyDecider before other tab helpers, so
506 // that it can block the navigation if needed before other policy deciders
507 // execute thier side effects (eg. AppLauncherTabHelper launching app).
508 _policyDeciderBridge =
509 std::make_unique<web::WebStatePolicyDeciderBridge>(_webState.get(), self);
510 AttachTabHelpers(_webState.get(), /*for_prerender=*/true);
511
512 web_deprecated::SetNativeProvider(_webState.get(), nil);
513
514 _webState->SetDelegate(_webStateDelegate.get());
515 _webState->AddObserver(_webStateObserver.get());
516 breakpad::MonitorURLsForWebState(_webState.get());
517 _webState->SetWebUsageEnabled(true);
518
519 if (AccountConsistencyService* accountConsistencyService =
520 ios::AccountConsistencyServiceFactory::GetForBrowserState(
521 self.browserState)) {
522 accountConsistencyService->SetWebStateHandler(_webState.get(), self);
523 }
524
525 HistoryTabHelper::FromWebState(_webState.get())
526 ->SetDelayHistoryServiceNotification(true);
527
528 web::NavigationManager::WebLoadParams loadParams(self.prerenderedURL);
529 loadParams.referrer = request->referrer();
530 loadParams.transition_type = request->transition();
531 if ([self.delegate preloadShouldUseDesktopUserAgent]) {
532 loadParams.user_agent_override_option =
533 web::NavigationManager::UserAgentOverrideOption::DESKTOP;
534 }
535 _webState->SetKeepRenderProcessAlive(true);
536 _webState->GetNavigationManager()->LoadURLWithParams(loadParams);
537
538 // LoadIfNecessary is needed because the view is not created (but needed) when
539 // loading the page. TODO(crbug.com/705819): Remove this call.
540 _webState->GetNavigationManager()->LoadIfNecessary();
541
542 self.startTime = base::TimeTicks::Now();
543}
544
545#pragma mark - Teardown Helpers
546
547- (void)destroyPreviewContents {
548 [self destroyPreviewContentsForReason:PRERENDER_FINAL_STATUS_CANCELLED];
549}
550
551- (void)destroyPreviewContentsForReason:(PrerenderFinalStatus)reason {
552 if (!_webState)
553 return;
554
555 UMA_HISTOGRAM_ENUMERATION(kPrerenderFinalStatusHistogramName, reason,
556 PRERENDER_FINAL_STATUS_MAX);
557
558 web_deprecated::SetNativeProvider(_webState.get(), nil);
559 _webState->RemoveObserver(_webStateObserver.get());
560 breakpad::StopMonitoringURLsForWebState(_webState.get());
561 _webState->SetDelegate(nullptr);
562 _webState.reset();
563
564 self.prerenderedURL = GURL();
565 self.startTime = base::TimeTicks();
566}
567
568#pragma mark - Notification Helpers
569
570- (void)didReceiveMemoryWarning {
571 [self cancelPrerenderForReason:PRERENDER_FINAL_STATUS_MEMORY_LIMIT_EXCEEDED];
572}
573
574#pragma mark - Metrics Helpers
575
576- (void)recordReleaseMetrics {
577 UMA_HISTOGRAM_ENUMERATION(kPrerenderFinalStatusHistogramName,
578 PRERENDER_FINAL_STATUS_USED,
579 PRERENDER_FINAL_STATUS_MAX);
580
581 DCHECK_NE(base::TimeTicks(), self.startTime);
582 UMA_HISTOGRAM_TIMES(kPrerenderStartToReleaseContentsTime,
583 base::TimeTicks::Now() - self.startTime);
584}
585
sdefresned9217bc2016-12-19 13:58:32586@end