blob: 9debfbf59fa449f26bd8c8742d9f57f8e964dea2 [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() &&
Justin Cohenbe1e94272019-10-09 16:08:03275 !net::NetworkChangeNotifier::IsOffline() &&
Kurt Horimotoeb0081e2019-09-26 18:53:35276 (!self.wifiOnly || !self.usingWWAN);
277}
278
279#pragma mark - Public
280
281- (void)browserStateDestroyed {
282 [self cancelPrerender];
283 _connectionTypeObserver.reset();
284}
285
sdefresned9217bc2016-12-19 13:58:32286- (void)prerenderURL:(const GURL&)url
287 referrer:(const web::Referrer&)referrer
288 transition:(ui::PageTransition)transition
289 immediately:(BOOL)immediately {
Kurt Horimotoeb0081e2019-09-26 18:53:35290 // TODO(crbug.com/754050): If CanPrerenderURL() returns false, should we
Rohit Rao44f204302017-08-10 14:49:54291 // cancel any scheduled prerender requests?
Kurt Horimotoeb0081e2019-09-26 18:53:35292 if (!self.enabled || !CanPrerenderURL(url))
sdefresned9217bc2016-12-19 13:58:32293 return;
294
295 // Ignore this request if there is already a scheduled request for the same
296 // URL; or, if there is no scheduled request, but the currently prerendered
297 // page matches this URL.
Kurt Horimotoeb0081e2019-09-26 18:53:35298 if (url == self.scheduledURL ||
299 (self.scheduledURL.is_empty() && url == self.prerenderedURL)) {
sdefresned9217bc2016-12-19 13:58:32300 return;
301 }
302
303 [self removeScheduledPrerenderRequests];
Kurt Horimotoeb0081e2019-09-26 18:53:35304 _scheduledRequest =
305 std::make_unique<PrerenderRequest>(url, transition, referrer);
sdefresned9217bc2016-12-19 13:58:32306
307 NSTimeInterval delay = immediately ? 0.0 : kPrerenderDelay;
308 [self performSelector:@selector(startPrerender)
309 withObject:nil
310 afterDelay:delay];
311}
312
sdefresned9217bc2016-12-19 13:58:32313- (void)cancelPrerender {
314 [self cancelPrerenderForReason:PRERENDER_FINAL_STATUS_CANCELLED];
315}
316
317- (void)cancelPrerenderForReason:(PrerenderFinalStatus)reason {
318 [self removeScheduledPrerenderRequests];
319 [self destroyPreviewContentsForReason:reason];
320}
321
Sylvain Defresnef5d2d952017-11-14 11:15:31322- (BOOL)isWebStatePrerendered:(web::WebState*)webState {
Kurt Horimotoeb0081e2019-09-26 18:53:35323 return webState && _webState.get() == webState;
Sylvain Defresnef5d2d952017-11-14 11:15:31324}
325
Justin Cohenceff1a212019-10-01 20:43:43326- (std::unique_ptr<web::WebState>)releasePrerenderContents {
Justin Cohen6836cfc52019-10-02 13:28:58327 if (!_webState ||
328 _webState->GetNavigationManager()->IsRestoreSessionInProgress())
Kurt Horimotoeb0081e2019-09-26 18:53:35329 return nullptr;
330
331 self.successfulPrerendersPerSessionCount++;
Justin Cohen118fee6c2018-12-06 19:36:59332 [self recordReleaseMetrics];
sdefresned9217bc2016-12-19 13:58:32333 [self removeScheduledPrerenderRequests];
Kurt Horimotoeb0081e2019-09-26 18:53:35334 self.prerenderedURL = GURL();
335 self.startTime = base::TimeTicks();
Sylvain Defresnef5b8a8e2017-10-24 02:54:44336
Sylvain Defresnef5d2d952017-11-14 11:15:31337 // Move the pre-rendered WebState to a local variable so that it will no
338 // longer be considered as pre-rendering (otherwise tab helpers may early
339 // exist when invoked).
Kurt Horimotoeb0081e2019-09-26 18:53:35340 std::unique_ptr<web::WebState> webState = std::move(_webState);
Sylvain Defresnef5d2d952017-11-14 11:15:31341 DCHECK(![self isWebStatePrerendered:webState.get()]);
edchincd32fdf2017-10-25 12:45:45342
Mohammad Refaate92aff52019-06-26 17:32:29343 web_deprecated::SetNativeProvider(webState.get(), nil);
Kurt Horimotoeb0081e2019-09-26 18:53:35344 webState->RemoveObserver(_webStateObserver.get());
mrefaat4aec47b2019-03-29 18:44:21345 breakpad::StopMonitoringURLsForWebState(webState.get());
Sylvain Defresnef5d2d952017-11-14 11:15:31346 webState->SetDelegate(nullptr);
Kurt Horimotoeb0081e2019-09-26 18:53:35347 _policyDeciderBridge.reset();
Gauthier Ambard5144bfbd2017-11-15 10:07:52348 HistoryTabHelper::FromWebState(webState.get())
Sylvain Defresnef5d2d952017-11-14 11:15:31349 ->SetDelayHistoryServiceNotification(false);
350
351 if (AccountConsistencyService* accountConsistencyService =
352 ios::AccountConsistencyServiceFactory::GetForBrowserState(
Kurt Horimotoeb0081e2019-09-26 18:53:35353 self.browserState)) {
Gauthier Ambard5144bfbd2017-11-15 10:07:52354 accountConsistencyService->RemoveWebStateHandler(webState.get());
Sylvain Defresnef5d2d952017-11-14 11:15:31355 }
356
mrefaata4bdb95b2018-12-04 18:36:10357 if (!webState->IsLoading()) {
mrefaatc977e372019-02-04 22:19:18358 [[OmniboxGeolocationController sharedInstance]
359 finishPageLoadForWebState:webState.get()
360 loadSuccess:YES];
sdefresne2c600c52017-04-04 16:49:59361 }
362
Sylvain Defresnef5d2d952017-11-14 11:15:31363 return webState;
sdefresned9217bc2016-12-19 13:58:32364}
365
Kurt Horimotoeb0081e2019-09-26 18:53:35366#pragma mark - CRConnectionTypeObserverBridge
367
sdefresned9217bc2016-12-19 13:58:32368- (void)connectionTypeChanged:(net::NetworkChangeNotifier::ConnectionType)type {
369 DCHECK_CURRENTLY_ON(web::WebThread::UI);
Kurt Horimotoeb0081e2019-09-26 18:53:35370 self.usingWWAN = net::NetworkChangeNotifier::IsConnectionCellular(type);
371 if (self.wifiOnly && self.usingWWAN)
sdefresned9217bc2016-12-19 13:58:32372 [self cancelPrerender];
373}
374
Sylvain Defresnef5d2d952017-11-14 11:15:31375#pragma mark - CRWWebStateDelegate
376
Eugene But6c41f5e2018-11-09 00:39:32377- (web::WebState*)webState:(web::WebState*)webState
378 createNewWebStateForURL:(const GURL&)URL
379 openerURL:(const GURL&)openerURL
380 initiatedByUser:(BOOL)initiatedByUser {
381 DCHECK([self isWebStatePrerendered:webState]);
382 [self schedulePrerenderCancel];
383 return nil;
384}
385
386- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
387 (web::WebState*)webState {
388 DCHECK([self isWebStatePrerendered:webState]);
Kurt Horimotoeb0081e2019-09-26 18:53:35389 return _dialogPresenter.get();
Eugene But6c41f5e2018-11-09 00:39:32390}
391
Sylvain Defresnef5d2d952017-11-14 11:15:31392- (void)webState:(web::WebState*)webState
393 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
394 proposedCredential:(NSURLCredential*)proposedCredential
395 completionHandler:(void (^)(NSString* username,
396 NSString* password))handler {
397 DCHECK([self isWebStatePrerendered:webState]);
398 [self schedulePrerenderCancel];
399 if (handler) {
400 handler(nil, nil);
401 }
402}
403
Sylvain Defresne949367262018-03-02 22:33:16404#pragma mark - CRWWebStateObserver
sdefresned9217bc2016-12-19 13:58:32405
Sylvain Defresne949367262018-03-02 22:33:16406- (void)webState:(web::WebState*)webState
407 didLoadPageWithSuccess:(BOOL)loadSuccess {
Kurt Horimotoeb0081e2019-09-26 18:53:35408 DCHECK_EQ(webState, _webState.get());
Sylvain Defresne949367262018-03-02 22:33:16409 // Cancel prerendering if response is "application/octet-stream". It can be a
410 // video file which should not be played from preload tab. See issue at
411 // http://crbug.com/436813 for more details.
412 const std::string& mimeType = webState->GetContentsMimeType();
413 if (mimeType == "application/octet-stream")
414 [self schedulePrerenderCancel];
415}
416
mrefaat3ad5e412018-08-02 20:11:34417#pragma mark - CRWWebStatePolicyDecider
418
419- (BOOL)shouldAllowRequest:(NSURLRequest*)request
420 requestInfo:(const WebStatePolicyDecider::RequestInfo&)info {
421 GURL requestURL = net::GURLWithNSURL(request.URL);
422 // Don't allow preloading for requests that are handled by opening another
423 // application or by presenting a native UI.
424 if (AppLauncherTabHelper::IsAppUrl(requestURL) ||
425 ITunesUrlsHandlerTabHelper::CanHandleUrl(requestURL)) {
426 [self schedulePrerenderCancel];
427 return NO;
428 }
429 return YES;
430}
Kurt Horimotoeb0081e2019-09-26 18:53:35431
432#pragma mark - ManageAccountsDelegate
433
434- (void)onManageAccounts {
435 [self schedulePrerenderCancel];
436}
437
438- (void)onAddAccount {
439 [self schedulePrerenderCancel];
440}
441
442- (void)onGoIncognito:(const GURL&)url {
443 [self schedulePrerenderCancel];
444}
445
446#pragma mark - PrefObserverDelegate
447
448- (void)onPreferenceChanged:(const std::string&)preferenceName {
449 if (preferenceName == prefs::kNetworkPredictionEnabled ||
450 preferenceName == prefs::kNetworkPredictionWifiOnly) {
451 DCHECK_CURRENTLY_ON(web::WebThread::UI);
452 // The logic is simpler if both preferences changes are handled equally.
453 self.preferenceEnabled = self.browserState->GetPrefs()->GetBoolean(
454 prefs::kNetworkPredictionEnabled);
455 self.wifiOnly = self.browserState->GetPrefs()->GetBoolean(
456 prefs::kNetworkPredictionWifiOnly);
457
458 if (self.wifiOnly && self.preferenceEnabled) {
459 if (!_connectionTypeObserver.get()) {
460 self.usingWWAN = net::NetworkChangeNotifier::IsConnectionCellular(
461 net::NetworkChangeNotifier::GetConnectionType());
462 _connectionTypeObserver.reset(new ConnectionTypeObserverBridge(self));
463 }
464 if (self.usingWWAN) {
465 [self cancelPrerender];
466 }
467 } else if (self.preferenceEnabled) {
468 _connectionTypeObserver.reset();
469 } else {
470 [self cancelPrerender];
471 _connectionTypeObserver.reset();
472 }
473 }
474}
475
476#pragma mark - PreloadCancelling
477
478- (void)schedulePrerenderCancel {
479 // TODO(crbug.com/228550): Instead of cancelling the prerender, should we mark
480 // it as failed instead? That way, subsequent prerender requests for the same
481 // URL will not kick off new prerenders.
482 [self removeScheduledPrerenderRequests];
483 [self performSelector:@selector(cancelPrerender) withObject:nil afterDelay:0];
484}
485
486#pragma mark - Cancellation Helpers
487
488- (void)removeScheduledPrerenderRequests {
489 [NSObject cancelPreviousPerformRequestsWithTarget:self];
490 _scheduledRequest = nullptr;
491}
492
493#pragma mark - Prerender Helpers
494
495- (void)startPrerender {
496 // Destroy any existing prerenders before starting a new one.
497 [self destroyPreviewContents];
498 self.prerenderedURL = self.scheduledURL;
499 std::unique_ptr<PrerenderRequest> request = std::move(_scheduledRequest);
500
Justin Cohen6836cfc52019-10-02 13:28:58501 web::WebState* webStateToReplace = [self.delegate webStateToReplace];
502 if (!self.prerenderedURL.is_valid() || !webStateToReplace) {
Kurt Horimotoeb0081e2019-09-26 18:53:35503 [self destroyPreviewContents];
504 return;
505 }
506
507 web::WebState::CreateParams createParams(self.browserState);
Justin Cohen6836cfc52019-10-02 13:28:58508 if (web::GetWebClient()->IsSlimNavigationManagerEnabled()) {
509 _webState = web::WebState::CreateWithStorageSession(
510 createParams, webStateToReplace->BuildSessionStorage());
511 } else {
512 _webState = web::WebState::Create(createParams);
513 }
514
Kurt Horimotoeb0081e2019-09-26 18:53:35515 // Add the preload controller as a policyDecider before other tab helpers, so
516 // that it can block the navigation if needed before other policy deciders
517 // execute thier side effects (eg. AppLauncherTabHelper launching app).
518 _policyDeciderBridge =
519 std::make_unique<web::WebStatePolicyDeciderBridge>(_webState.get(), self);
520 AttachTabHelpers(_webState.get(), /*for_prerender=*/true);
521
522 web_deprecated::SetNativeProvider(_webState.get(), nil);
523
524 _webState->SetDelegate(_webStateDelegate.get());
525 _webState->AddObserver(_webStateObserver.get());
526 breakpad::MonitorURLsForWebState(_webState.get());
527 _webState->SetWebUsageEnabled(true);
528
529 if (AccountConsistencyService* accountConsistencyService =
530 ios::AccountConsistencyServiceFactory::GetForBrowserState(
531 self.browserState)) {
532 accountConsistencyService->SetWebStateHandler(_webState.get(), self);
533 }
534
535 HistoryTabHelper::FromWebState(_webState.get())
536 ->SetDelayHistoryServiceNotification(true);
537
538 web::NavigationManager::WebLoadParams loadParams(self.prerenderedURL);
539 loadParams.referrer = request->referrer();
540 loadParams.transition_type = request->transition();
541 if ([self.delegate preloadShouldUseDesktopUserAgent]) {
542 loadParams.user_agent_override_option =
543 web::NavigationManager::UserAgentOverrideOption::DESKTOP;
544 }
545 _webState->SetKeepRenderProcessAlive(true);
546 _webState->GetNavigationManager()->LoadURLWithParams(loadParams);
547
548 // LoadIfNecessary is needed because the view is not created (but needed) when
549 // loading the page. TODO(crbug.com/705819): Remove this call.
550 _webState->GetNavigationManager()->LoadIfNecessary();
551
552 self.startTime = base::TimeTicks::Now();
553}
554
555#pragma mark - Teardown Helpers
556
557- (void)destroyPreviewContents {
558 [self destroyPreviewContentsForReason:PRERENDER_FINAL_STATUS_CANCELLED];
559}
560
561- (void)destroyPreviewContentsForReason:(PrerenderFinalStatus)reason {
562 if (!_webState)
563 return;
564
565 UMA_HISTOGRAM_ENUMERATION(kPrerenderFinalStatusHistogramName, reason,
566 PRERENDER_FINAL_STATUS_MAX);
567
568 web_deprecated::SetNativeProvider(_webState.get(), nil);
569 _webState->RemoveObserver(_webStateObserver.get());
570 breakpad::StopMonitoringURLsForWebState(_webState.get());
571 _webState->SetDelegate(nullptr);
572 _webState.reset();
573
574 self.prerenderedURL = GURL();
575 self.startTime = base::TimeTicks();
Kurt Horimotoeb0081e2019-09-26 18:53:35576}
577
578#pragma mark - Notification Helpers
579
580- (void)didReceiveMemoryWarning {
581 [self cancelPrerenderForReason:PRERENDER_FINAL_STATUS_MEMORY_LIMIT_EXCEEDED];
582}
583
584#pragma mark - Metrics Helpers
585
586- (void)recordReleaseMetrics {
587 UMA_HISTOGRAM_ENUMERATION(kPrerenderFinalStatusHistogramName,
588 PRERENDER_FINAL_STATUS_USED,
589 PRERENDER_FINAL_STATUS_MAX);
590
591 DCHECK_NE(base::TimeTicks(), self.startTime);
592 UMA_HISTOGRAM_TIMES(kPrerenderStartToReleaseContentsTime,
593 base::TimeTicks::Now() - self.startTime);
594}
595
sdefresned9217bc2016-12-19 13:58:32596@end