blob: 37f32745a1effba0e03f091b8c2a283a37b35f36 [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"
Justin Cohen6836cfc52019-10-02 13:28:5835#include "ios/web/public/web_client.h"
Eugene Butd3564aaea2019-08-13 15:50:4536#import "ios/web/public/web_state.h"
Eugene But45bb9962019-09-16 17:34:2137#import "ios/web/public/web_state_observer_bridge.h"
sdefresned9217bc2016-12-19 13:58:3238#import "ios/web/web_state/ui/crw_web_controller.h"
39#import "net/base/mac/url_conversions.h"
sdefresned9217bc2016-12-19 13:58:3240#include "ui/base/page_transition_types.h"
41
stkhapugine757b63a2017-04-10 12:31:2742#if !defined(__has_feature) || !__has_feature(objc_arc)
43#error "This file requires ARC support."
44#endif
45
mrefaat3ad5e412018-08-02 20:11:3446using web::WebStatePolicyDecider;
47
Kurt Horimotoeb0081e2019-09-26 18:53:3548// Protocol used to cancel a scheduled preload request.
49@protocol PreloadCancelling <NSObject>
Kurt Horimoto271707392019-09-21 00:28:0850
51// Schedules the current prerender to be cancelled during the next run of the
52// event loop.
53- (void)schedulePrerenderCancel;
54
Kurt Horimoto271707392019-09-21 00:28:0855@end
56
57namespace {
58
59// PrerenderFinalStatus values are used in the "Prerender.FinalStatus" histogram
60// and new values needs to be kept in sync with histogram.xml.
61enum PrerenderFinalStatus {
62 PRERENDER_FINAL_STATUS_USED = 0,
63 PRERENDER_FINAL_STATUS_MEMORY_LIMIT_EXCEEDED = 12,
64 PRERENDER_FINAL_STATUS_CANCELLED = 32,
65 PRERENDER_FINAL_STATUS_MAX = 52,
66};
67
68// Delay before starting to prerender a URL.
69const NSTimeInterval kPrerenderDelay = 0.5;
70
71// The finch experiment to turn off prerendering as a field trial.
72const char kTabEvictionFieldTrialName[] = "TabEviction";
73// The associated group.
74const char kPrerenderTabEvictionTrialGroup[] = "NoPrerendering";
75// The name of the histogram for recording final status (e.g. used/cancelled)
76// of prerender requests.
77const char kPrerenderFinalStatusHistogramName[] = "Prerender.FinalStatus";
78// The name of the histogram for recording the number of successful prerenders.
79const char kPrerendersPerSessionCountHistogramName[] =
80 "Prerender.PrerendersPerSessionCount";
81// The name of the histogram for recording time until a successful prerender.
82const char kPrerenderStartToReleaseContentsTime[] =
83 "Prerender.PrerenderStartToReleaseContentsTime";
84
85// Is this install selected for this particular experiment.
86bool IsPrerenderTabEvictionExperimentalGroup() {
87 base::FieldTrial* trial =
88 base::FieldTrialList::Find(kTabEvictionFieldTrialName);
89 return trial && trial->group_name() == kPrerenderTabEvictionTrialGroup;
90}
91
Kurt Horimotoeb0081e2019-09-26 18:53:3592// Returns whether |url| can be prerendered.
93bool CanPrerenderURL(const GURL& url) {
94 // Prerendering is only enabled for http and https URLs.
95 return url.is_valid() &&
96 (url.SchemeIs(url::kHttpScheme) || url.SchemeIs(url::kHttpsScheme));
97}
98
99// Object used to schedule prerenders.
100class PrerenderRequest {
101 public:
102 PrerenderRequest() {}
103 PrerenderRequest(const GURL& url,
104 ui::PageTransition transition,
105 const web::Referrer& referrer)
106 : url_(url), transition_(transition), referrer_(referrer) {}
107
108 const GURL& url() const { return url_; }
109 ui::PageTransition transition() const { return transition_; }
110 const web::Referrer referrer() const { return referrer_; }
111
112 private:
113 const GURL url_;
114 const ui::PageTransition transition_ = ui::PAGE_TRANSITION_LINK;
115 const web::Referrer referrer_;
116};
117
Kurt Horimoto271707392019-09-21 00:28:08118// A no-op JavaScriptDialogPresenter that cancels prerendering when the
119// prerendered page attempts to show dialogs.
120class PreloadJavaScriptDialogPresenter : public web::JavaScriptDialogPresenter {
121 public:
Kurt Horimotoeb0081e2019-09-26 18:53:35122 explicit PreloadJavaScriptDialogPresenter(
123 id<PreloadCancelling> cancel_handler)
124 : cancel_handler_(cancel_handler) {
125 DCHECK(cancel_handler_);
Kurt Horimoto271707392019-09-21 00:28:08126 }
127
128 // web::JavaScriptDialogPresenter:
129 void RunJavaScriptDialog(web::WebState* web_state,
130 const GURL& origin_url,
131 web::JavaScriptDialogType dialog_type,
132 NSString* message_text,
133 NSString* default_prompt_text,
134 web::DialogClosedCallback callback) override {
135 std::move(callback).Run(NO, nil);
Kurt Horimotoeb0081e2019-09-26 18:53:35136 [cancel_handler_ schedulePrerenderCancel];
Kurt Horimoto271707392019-09-21 00:28:08137 }
138
139 void CancelDialogs(web::WebState* web_state) override {}
140
141 private:
Kurt Horimotoeb0081e2019-09-26 18:53:35142 __weak id<PreloadCancelling> cancel_handler_ = nil;
Kurt Horimoto271707392019-09-21 00:28:08143};
144} // namespace
145
Kurt Horimotoeb0081e2019-09-26 18:53:35146@interface PreloadController () <CRConnectionTypeObserverBridge,
147 CRWWebStateDelegate,
148 CRWWebStateObserver,
149 CRWWebStatePolicyDecider,
150 ManageAccountsDelegate,
151 PrefObserverDelegate,
152 PreloadCancelling> {
153 std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
154 std::unique_ptr<web::WebStateObserverBridge> _webStateObserver;
155 std::unique_ptr<PrefObserverBridge> _observerBridge;
156 std::unique_ptr<ConnectionTypeObserverBridge> _connectionTypeObserver;
157 std::unique_ptr<web::WebStatePolicyDeciderBridge> _policyDeciderBridge;
sdefresned9217bc2016-12-19 13:58:32158
Kurt Horimotoeb0081e2019-09-26 18:53:35159 // The WebState used for prerendering.
160 std::unique_ptr<web::WebState> _webState;
161
162 // The scheduled request.
163 std::unique_ptr<PrerenderRequest> _scheduledRequest;
164
165 // Registrar for pref changes notifications.
166 PrefChangeRegistrar _prefChangeRegistrar;
167
168 // The dialog presenter.
169 std::unique_ptr<web::JavaScriptDialogPresenter> _dialogPresenter;
170}
171
172// The ChromeBrowserState passed on initialization.
173@property(nonatomic) ios::ChromeBrowserState* browserState;
174
175// Redefine property as readwrite. The URL that is prerendered in |_webState|.
176// This can be different from the value returned by WebState last committed
177// navigation item, for example in cases where there was a redirect.
178//
179// When choosing whether or not to use a prerendered Tab,
180// BrowserViewController compares the URL being loaded by the omnibox with the
181// URL of the prerendered Tab. Comparing against the Tab's currently URL
182// could return false negatives in cases of redirect, hence the need to store
183// the originally prerendered URL.
184@property(nonatomic, readwrite, assign) GURL prerenderedURL;
185
186// The URL in the currently scheduled prerender request, or an empty one if
187// there is no prerender scheduled.
188@property(nonatomic, readonly) const GURL& scheduledURL;
189
190// Whether or not the preference is enabled.
191@property(nonatomic, getter=isPreferenceEnabled) BOOL preferenceEnabled;
192
193// Whether or not prerendering is only when on wifi.
194@property(nonatomic, getter=isWifiOnly) BOOL wifiOnly;
195
196// Whether or not the current connection is using WWAN.
197@property(nonatomic, getter=isUsingWWAN) BOOL usingWWAN;
198
199// Number of successful prerenders (i.e. the user viewed the prerendered page)
200// during the lifetime of this controller.
201@property(nonatomic) NSUInteger successfulPrerendersPerSessionCount;
202
203// Tracks the time of the last attempt to load a prerender URL. Used for UMA
204// reporting of load durations.
205@property(nonatomic) base::TimeTicks startTime;
206
207// Called to start any scheduled prerendering requests.
208- (void)startPrerender;
209
210// Destroys the preview Tab and resets |prerenderURL_| to the empty URL.
211- (void)destroyPreviewContents;
212
213// Removes any scheduled prerender requests and resets |scheduledURL| to the
214// empty URL.
215- (void)removeScheduledPrerenderRequests;
216
217// Records metric on a successful prerender.
218- (void)recordReleaseMetrics;
219
220@end
221
222@implementation PreloadController
Kurt Horimoto38bfb152019-09-25 21:05:41223
sdefresned9217bc2016-12-19 13:58:32224- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState {
225 DCHECK(browserState);
226 DCHECK_CURRENTLY_ON(web::WebThread::UI);
227 if ((self = [super init])) {
Kurt Horimotoeb0081e2019-09-26 18:53:35228 _browserState = browserState;
229 _preferenceEnabled =
230 _browserState->GetPrefs()->GetBoolean(prefs::kNetworkPredictionEnabled);
231 _wifiOnly = _browserState->GetPrefs()->GetBoolean(
sdefresned9217bc2016-12-19 13:58:32232 prefs::kNetworkPredictionWifiOnly);
Kurt Horimotoeb0081e2019-09-26 18:53:35233 _usingWWAN = net::NetworkChangeNotifier::IsConnectionCellular(
sdefresned9217bc2016-12-19 13:58:32234 net::NetworkChangeNotifier::GetConnectionType());
Kurt Horimotoeb0081e2019-09-26 18:53:35235 _webStateDelegate = std::make_unique<web::WebStateDelegateBridge>(self);
236 _webStateObserver = std::make_unique<web::WebStateObserverBridge>(self);
237 _observerBridge = std::make_unique<PrefObserverBridge>(self);
238 _prefChangeRegistrar.Init(_browserState->GetPrefs());
239 _observerBridge->ObserveChangesForPreference(
240 prefs::kNetworkPredictionEnabled, &_prefChangeRegistrar);
241 _observerBridge->ObserveChangesForPreference(
242 prefs::kNetworkPredictionWifiOnly, &_prefChangeRegistrar);
243 _dialogPresenter = std::make_unique<PreloadJavaScriptDialogPresenter>(self);
244 if (_preferenceEnabled && _wifiOnly) {
245 _connectionTypeObserver =
Sylvain Defresne949367262018-03-02 22:33:16246 std::make_unique<ConnectionTypeObserverBridge>(self);
sdefresned9217bc2016-12-19 13:58:32247 }
248
249 [[NSNotificationCenter defaultCenter]
250 addObserver:self
251 selector:@selector(didReceiveMemoryWarning)
252 name:UIApplicationDidReceiveMemoryWarningNotification
253 object:nil];
254 }
255 return self;
256}
257
sdefresned9217bc2016-12-19 13:58:32258- (void)dealloc {
Steven Holte95922222018-09-14 20:06:23259 UMA_HISTOGRAM_COUNTS_1M(kPrerendersPerSessionCountHistogramName,
Kurt Horimotoeb0081e2019-09-26 18:53:35260 self.successfulPrerendersPerSessionCount);
sdefresned9217bc2016-12-19 13:58:32261 [self cancelPrerender];
sdefresned9217bc2016-12-19 13:58:32262}
263
Kurt Horimotoeb0081e2019-09-26 18:53:35264#pragma mark - Accessors
265
266- (const GURL&)scheduledURL {
267 return _scheduledRequest ? _scheduledRequest->url() : GURL::EmptyGURL();
268}
269
270- (BOOL)isEnabled {
271 DCHECK_CURRENTLY_ON(web::WebThread::UI);
272 return !IsPrerenderTabEvictionExperimentalGroup() && self.preferenceEnabled &&
273 !ios::device_util::IsSingleCoreDevice() &&
274 ios::device_util::RamIsAtLeast512Mb() &&
275 (!self.wifiOnly || !self.usingWWAN);
276}
277
278#pragma mark - Public
279
280- (void)browserStateDestroyed {
281 [self cancelPrerender];
282 _connectionTypeObserver.reset();
283}
284
sdefresned9217bc2016-12-19 13:58:32285- (void)prerenderURL:(const GURL&)url
286 referrer:(const web::Referrer&)referrer
287 transition:(ui::PageTransition)transition
288 immediately:(BOOL)immediately {
Kurt Horimotoeb0081e2019-09-26 18:53:35289 // TODO(crbug.com/754050): If CanPrerenderURL() returns false, should we
Rohit Rao44f204302017-08-10 14:49:54290 // cancel any scheduled prerender requests?
Kurt Horimotoeb0081e2019-09-26 18:53:35291 if (!self.enabled || !CanPrerenderURL(url))
sdefresned9217bc2016-12-19 13:58:32292 return;
293
294 // Ignore this request if there is already a scheduled request for the same
295 // URL; or, if there is no scheduled request, but the currently prerendered
296 // page matches this URL.
Kurt Horimotoeb0081e2019-09-26 18:53:35297 if (url == self.scheduledURL ||
298 (self.scheduledURL.is_empty() && url == self.prerenderedURL)) {
sdefresned9217bc2016-12-19 13:58:32299 return;
300 }
301
302 [self removeScheduledPrerenderRequests];
Kurt Horimotoeb0081e2019-09-26 18:53:35303 _scheduledRequest =
304 std::make_unique<PrerenderRequest>(url, transition, referrer);
sdefresned9217bc2016-12-19 13:58:32305
306 NSTimeInterval delay = immediately ? 0.0 : kPrerenderDelay;
307 [self performSelector:@selector(startPrerender)
308 withObject:nil
309 afterDelay:delay];
310}
311
sdefresned9217bc2016-12-19 13:58:32312- (void)cancelPrerender {
313 [self cancelPrerenderForReason:PRERENDER_FINAL_STATUS_CANCELLED];
314}
315
316- (void)cancelPrerenderForReason:(PrerenderFinalStatus)reason {
317 [self removeScheduledPrerenderRequests];
318 [self destroyPreviewContentsForReason:reason];
319}
320
Sylvain Defresnef5d2d952017-11-14 11:15:31321- (BOOL)isWebStatePrerendered:(web::WebState*)webState {
Kurt Horimotoeb0081e2019-09-26 18:53:35322 return webState && _webState.get() == webState;
Sylvain Defresnef5d2d952017-11-14 11:15:31323}
324
Justin Cohenceff1a212019-10-01 20:43:43325- (std::unique_ptr<web::WebState>)releasePrerenderContents {
Justin Cohen6836cfc52019-10-02 13:28:58326 if (!_webState ||
327 _webState->GetNavigationManager()->IsRestoreSessionInProgress())
Kurt Horimotoeb0081e2019-09-26 18:53:35328 return nullptr;
329
330 self.successfulPrerendersPerSessionCount++;
Justin Cohen118fee6c2018-12-06 19:36:59331 [self recordReleaseMetrics];
sdefresned9217bc2016-12-19 13:58:32332 [self removeScheduledPrerenderRequests];
Kurt Horimotoeb0081e2019-09-26 18:53:35333 self.prerenderedURL = GURL();
334 self.startTime = base::TimeTicks();
Sylvain Defresnef5b8a8e2017-10-24 02:54:44335
Sylvain Defresnef5d2d952017-11-14 11:15:31336 // Move the pre-rendered WebState to a local variable so that it will no
337 // longer be considered as pre-rendering (otherwise tab helpers may early
338 // exist when invoked).
Kurt Horimotoeb0081e2019-09-26 18:53:35339 std::unique_ptr<web::WebState> webState = std::move(_webState);
Sylvain Defresnef5d2d952017-11-14 11:15:31340 DCHECK(![self isWebStatePrerendered:webState.get()]);
edchincd32fdf2017-10-25 12:45:45341
Mohammad Refaate92aff52019-06-26 17:32:29342 web_deprecated::SetNativeProvider(webState.get(), nil);
Kurt Horimotoeb0081e2019-09-26 18:53:35343 webState->RemoveObserver(_webStateObserver.get());
mrefaat4aec47b2019-03-29 18:44:21344 breakpad::StopMonitoringURLsForWebState(webState.get());
Sylvain Defresnef5d2d952017-11-14 11:15:31345 webState->SetDelegate(nullptr);
Kurt Horimotoeb0081e2019-09-26 18:53:35346 _policyDeciderBridge.reset();
Gauthier Ambard5144bfbd2017-11-15 10:07:52347 HistoryTabHelper::FromWebState(webState.get())
Sylvain Defresnef5d2d952017-11-14 11:15:31348 ->SetDelayHistoryServiceNotification(false);
349
350 if (AccountConsistencyService* accountConsistencyService =
351 ios::AccountConsistencyServiceFactory::GetForBrowserState(
Kurt Horimotoeb0081e2019-09-26 18:53:35352 self.browserState)) {
Gauthier Ambard5144bfbd2017-11-15 10:07:52353 accountConsistencyService->RemoveWebStateHandler(webState.get());
Sylvain Defresnef5d2d952017-11-14 11:15:31354 }
355
mrefaata4bdb95b2018-12-04 18:36:10356 if (!webState->IsLoading()) {
mrefaatc977e372019-02-04 22:19:18357 [[OmniboxGeolocationController sharedInstance]
358 finishPageLoadForWebState:webState.get()
359 loadSuccess:YES];
sdefresne2c600c52017-04-04 16:49:59360 }
361
Sylvain Defresnef5d2d952017-11-14 11:15:31362 return webState;
sdefresned9217bc2016-12-19 13:58:32363}
364
Kurt Horimotoeb0081e2019-09-26 18:53:35365#pragma mark - CRConnectionTypeObserverBridge
366
sdefresned9217bc2016-12-19 13:58:32367- (void)connectionTypeChanged:(net::NetworkChangeNotifier::ConnectionType)type {
368 DCHECK_CURRENTLY_ON(web::WebThread::UI);
Kurt Horimotoeb0081e2019-09-26 18:53:35369 self.usingWWAN = net::NetworkChangeNotifier::IsConnectionCellular(type);
370 if (self.wifiOnly && self.usingWWAN)
sdefresned9217bc2016-12-19 13:58:32371 [self cancelPrerender];
372}
373
Sylvain Defresnef5d2d952017-11-14 11:15:31374#pragma mark - CRWWebStateDelegate
375
Eugene But6c41f5e2018-11-09 00:39:32376- (web::WebState*)webState:(web::WebState*)webState
377 createNewWebStateForURL:(const GURL&)URL
378 openerURL:(const GURL&)openerURL
379 initiatedByUser:(BOOL)initiatedByUser {
380 DCHECK([self isWebStatePrerendered:webState]);
381 [self schedulePrerenderCancel];
382 return nil;
383}
384
385- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
386 (web::WebState*)webState {
387 DCHECK([self isWebStatePrerendered:webState]);
Kurt Horimotoeb0081e2019-09-26 18:53:35388 return _dialogPresenter.get();
Eugene But6c41f5e2018-11-09 00:39:32389}
390
Sylvain Defresnef5d2d952017-11-14 11:15:31391- (void)webState:(web::WebState*)webState
392 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
393 proposedCredential:(NSURLCredential*)proposedCredential
394 completionHandler:(void (^)(NSString* username,
395 NSString* password))handler {
396 DCHECK([self isWebStatePrerendered:webState]);
397 [self schedulePrerenderCancel];
398 if (handler) {
399 handler(nil, nil);
400 }
401}
402
Sylvain Defresne949367262018-03-02 22:33:16403#pragma mark - CRWWebStateObserver
sdefresned9217bc2016-12-19 13:58:32404
Sylvain Defresne949367262018-03-02 22:33:16405- (void)webState:(web::WebState*)webState
406 didLoadPageWithSuccess:(BOOL)loadSuccess {
Kurt Horimotoeb0081e2019-09-26 18:53:35407 DCHECK_EQ(webState, _webState.get());
Sylvain Defresne949367262018-03-02 22:33:16408 // Cancel prerendering if response is "application/octet-stream". It can be a
409 // video file which should not be played from preload tab. See issue at
410 // http://crbug.com/436813 for more details.
411 const std::string& mimeType = webState->GetContentsMimeType();
412 if (mimeType == "application/octet-stream")
413 [self schedulePrerenderCancel];
414}
415
mrefaat3ad5e412018-08-02 20:11:34416#pragma mark - CRWWebStatePolicyDecider
417
418- (BOOL)shouldAllowRequest:(NSURLRequest*)request
419 requestInfo:(const WebStatePolicyDecider::RequestInfo&)info {
420 GURL requestURL = net::GURLWithNSURL(request.URL);
421 // Don't allow preloading for requests that are handled by opening another
422 // application or by presenting a native UI.
423 if (AppLauncherTabHelper::IsAppUrl(requestURL) ||
424 ITunesUrlsHandlerTabHelper::CanHandleUrl(requestURL)) {
425 [self schedulePrerenderCancel];
426 return NO;
427 }
428 return YES;
429}
Kurt Horimotoeb0081e2019-09-26 18:53:35430
431#pragma mark - ManageAccountsDelegate
432
433- (void)onManageAccounts {
434 [self schedulePrerenderCancel];
435}
436
437- (void)onAddAccount {
438 [self schedulePrerenderCancel];
439}
440
441- (void)onGoIncognito:(const GURL&)url {
442 [self schedulePrerenderCancel];
443}
444
445#pragma mark - PrefObserverDelegate
446
447- (void)onPreferenceChanged:(const std::string&)preferenceName {
448 if (preferenceName == prefs::kNetworkPredictionEnabled ||
449 preferenceName == prefs::kNetworkPredictionWifiOnly) {
450 DCHECK_CURRENTLY_ON(web::WebThread::UI);
451 // The logic is simpler if both preferences changes are handled equally.
452 self.preferenceEnabled = self.browserState->GetPrefs()->GetBoolean(
453 prefs::kNetworkPredictionEnabled);
454 self.wifiOnly = self.browserState->GetPrefs()->GetBoolean(
455 prefs::kNetworkPredictionWifiOnly);
456
457 if (self.wifiOnly && self.preferenceEnabled) {
458 if (!_connectionTypeObserver.get()) {
459 self.usingWWAN = net::NetworkChangeNotifier::IsConnectionCellular(
460 net::NetworkChangeNotifier::GetConnectionType());
461 _connectionTypeObserver.reset(new ConnectionTypeObserverBridge(self));
462 }
463 if (self.usingWWAN) {
464 [self cancelPrerender];
465 }
466 } else if (self.preferenceEnabled) {
467 _connectionTypeObserver.reset();
468 } else {
469 [self cancelPrerender];
470 _connectionTypeObserver.reset();
471 }
472 }
473}
474
475#pragma mark - PreloadCancelling
476
477- (void)schedulePrerenderCancel {
478 // TODO(crbug.com/228550): Instead of cancelling the prerender, should we mark
479 // it as failed instead? That way, subsequent prerender requests for the same
480 // URL will not kick off new prerenders.
481 [self removeScheduledPrerenderRequests];
482 [self performSelector:@selector(cancelPrerender) withObject:nil afterDelay:0];
483}
484
485#pragma mark - Cancellation Helpers
486
487- (void)removeScheduledPrerenderRequests {
488 [NSObject cancelPreviousPerformRequestsWithTarget:self];
489 _scheduledRequest = nullptr;
490}
491
492#pragma mark - Prerender Helpers
493
494- (void)startPrerender {
495 // Destroy any existing prerenders before starting a new one.
496 [self destroyPreviewContents];
497 self.prerenderedURL = self.scheduledURL;
498 std::unique_ptr<PrerenderRequest> request = std::move(_scheduledRequest);
499
Justin Cohen6836cfc52019-10-02 13:28:58500 web::WebState* webStateToReplace = [self.delegate webStateToReplace];
501 if (!self.prerenderedURL.is_valid() || !webStateToReplace) {
Kurt Horimotoeb0081e2019-09-26 18:53:35502 [self destroyPreviewContents];
503 return;
504 }
505
506 web::WebState::CreateParams createParams(self.browserState);
Justin Cohen6836cfc52019-10-02 13:28:58507 if (web::GetWebClient()->IsSlimNavigationManagerEnabled()) {
508 _webState = web::WebState::CreateWithStorageSession(
509 createParams, webStateToReplace->BuildSessionStorage());
510 } else {
511 _webState = web::WebState::Create(createParams);
512 }
513
Kurt Horimotoeb0081e2019-09-26 18:53:35514 // Add the preload controller as a policyDecider before other tab helpers, so
515 // that it can block the navigation if needed before other policy deciders
516 // execute thier side effects (eg. AppLauncherTabHelper launching app).
517 _policyDeciderBridge =
518 std::make_unique<web::WebStatePolicyDeciderBridge>(_webState.get(), self);
519 AttachTabHelpers(_webState.get(), /*for_prerender=*/true);
520
521 web_deprecated::SetNativeProvider(_webState.get(), nil);
522
523 _webState->SetDelegate(_webStateDelegate.get());
524 _webState->AddObserver(_webStateObserver.get());
525 breakpad::MonitorURLsForWebState(_webState.get());
526 _webState->SetWebUsageEnabled(true);
527
528 if (AccountConsistencyService* accountConsistencyService =
529 ios::AccountConsistencyServiceFactory::GetForBrowserState(
530 self.browserState)) {
531 accountConsistencyService->SetWebStateHandler(_webState.get(), self);
532 }
533
534 HistoryTabHelper::FromWebState(_webState.get())
535 ->SetDelayHistoryServiceNotification(true);
536
537 web::NavigationManager::WebLoadParams loadParams(self.prerenderedURL);
538 loadParams.referrer = request->referrer();
539 loadParams.transition_type = request->transition();
540 if ([self.delegate preloadShouldUseDesktopUserAgent]) {
541 loadParams.user_agent_override_option =
542 web::NavigationManager::UserAgentOverrideOption::DESKTOP;
543 }
544 _webState->SetKeepRenderProcessAlive(true);
545 _webState->GetNavigationManager()->LoadURLWithParams(loadParams);
546
547 // LoadIfNecessary is needed because the view is not created (but needed) when
548 // loading the page. TODO(crbug.com/705819): Remove this call.
549 _webState->GetNavigationManager()->LoadIfNecessary();
550
551 self.startTime = base::TimeTicks::Now();
552}
553
554#pragma mark - Teardown Helpers
555
556- (void)destroyPreviewContents {
557 [self destroyPreviewContentsForReason:PRERENDER_FINAL_STATUS_CANCELLED];
558}
559
560- (void)destroyPreviewContentsForReason:(PrerenderFinalStatus)reason {
561 if (!_webState)
562 return;
563
564 UMA_HISTOGRAM_ENUMERATION(kPrerenderFinalStatusHistogramName, reason,
565 PRERENDER_FINAL_STATUS_MAX);
566
567 web_deprecated::SetNativeProvider(_webState.get(), nil);
568 _webState->RemoveObserver(_webStateObserver.get());
569 breakpad::StopMonitoringURLsForWebState(_webState.get());
570 _webState->SetDelegate(nullptr);
571 _webState.reset();
572
573 self.prerenderedURL = GURL();
574 self.startTime = base::TimeTicks();
Kurt Horimotoeb0081e2019-09-26 18:53:35575}
576
577#pragma mark - Notification Helpers
578
579- (void)didReceiveMemoryWarning {
580 [self cancelPrerenderForReason:PRERENDER_FINAL_STATUS_MEMORY_LIMIT_EXCEEDED];
581}
582
583#pragma mark - Metrics Helpers
584
585- (void)recordReleaseMetrics {
586 UMA_HISTOGRAM_ENUMERATION(kPrerenderFinalStatusHistogramName,
587 PRERENDER_FINAL_STATUS_USED,
588 PRERENDER_FINAL_STATUS_MAX);
589
590 DCHECK_NE(base::TimeTicks(), self.startTime);
591 UMA_HISTOGRAM_TIMES(kPrerenderStartToReleaseContentsTime,
592 base::TimeTicks::Now() - self.startTime);
593}
594
sdefresned9217bc2016-12-19 13:58:32595@end