blob: a5497c9560430be55baedecc1e00f96b2fe545eb [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
Hans Wennborg3a2fb61f2020-05-11 15:51:169#include "base/check_op.h"
sdefresned9217bc2016-12-19 13:58:3210#include "base/ios/device_util.h"
sdefresned9217bc2016-12-19 13:58:3211#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"
Eugene Butde5ddcdc2019-07-23 00:23:4227#import "ios/web/public/navigation/navigation_item.h"
28#import "ios/web/public/navigation/navigation_manager.h"
29#import "ios/web/public/navigation/web_state_policy_decider_bridge.h"
Yi Sua98c08fb2019-06-13 09:03:5630#include "ios/web/public/thread/web_thread.h"
Kurt Horimoto271707392019-09-21 00:28:0831#import "ios/web/public/ui/java_script_dialog_presenter.h"
Justin Cohen6836cfc52019-10-02 13:28:5832#include "ios/web/public/web_client.h"
Eugene Butd3564aaea2019-08-13 15:50:4533#import "ios/web/public/web_state.h"
Eugene But45bb9962019-09-16 17:34:2134#import "ios/web/public/web_state_observer_bridge.h"
sdefresned9217bc2016-12-19 13:58:3235#import "net/base/mac/url_conversions.h"
sdefresned9217bc2016-12-19 13:58:3236#include "ui/base/page_transition_types.h"
37
stkhapugine757b63a2017-04-10 12:31:2738#if !defined(__has_feature) || !__has_feature(objc_arc)
39#error "This file requires ARC support."
40#endif
41
mrefaat3ad5e412018-08-02 20:11:3442using web::WebStatePolicyDecider;
43
Kurt Horimotoeb0081e2019-09-26 18:53:3544// Protocol used to cancel a scheduled preload request.
45@protocol PreloadCancelling <NSObject>
Kurt Horimoto271707392019-09-21 00:28:0846
47// Schedules the current prerender to be cancelled during the next run of the
48// event loop.
49- (void)schedulePrerenderCancel;
50
Kurt Horimoto271707392019-09-21 00:28:0851@end
52
53namespace {
54
55// PrerenderFinalStatus values are used in the "Prerender.FinalStatus" histogram
56// and new values needs to be kept in sync with histogram.xml.
57enum PrerenderFinalStatus {
58 PRERENDER_FINAL_STATUS_USED = 0,
59 PRERENDER_FINAL_STATUS_MEMORY_LIMIT_EXCEEDED = 12,
60 PRERENDER_FINAL_STATUS_CANCELLED = 32,
61 PRERENDER_FINAL_STATUS_MAX = 52,
62};
63
64// Delay before starting to prerender a URL.
65const NSTimeInterval kPrerenderDelay = 0.5;
66
67// The finch experiment to turn off prerendering as a field trial.
68const char kTabEvictionFieldTrialName[] = "TabEviction";
69// The associated group.
70const char kPrerenderTabEvictionTrialGroup[] = "NoPrerendering";
71// The name of the histogram for recording final status (e.g. used/cancelled)
72// of prerender requests.
73const char kPrerenderFinalStatusHistogramName[] = "Prerender.FinalStatus";
74// The name of the histogram for recording the number of successful prerenders.
75const char kPrerendersPerSessionCountHistogramName[] =
76 "Prerender.PrerendersPerSessionCount";
77// The name of the histogram for recording time until a successful prerender.
Gauthier Ambard6d3ed3472020-05-11 09:11:4878const char kPrerenderPrerenderTimeSaved[] = "Prerender.PrerenderTimeSaved";
79// Histogram to record that the load was complete when the prerender was used.
80// Not recorded if the pre-render isn't used.
81const char kPrerenderLoadComplete[] = "Prerender.PrerenderLoadComplete";
Kurt Horimoto271707392019-09-21 00:28:0882
83// Is this install selected for this particular experiment.
84bool IsPrerenderTabEvictionExperimentalGroup() {
85 base::FieldTrial* trial =
86 base::FieldTrialList::Find(kTabEvictionFieldTrialName);
87 return trial && trial->group_name() == kPrerenderTabEvictionTrialGroup;
88}
89
Kurt Horimotoeb0081e2019-09-26 18:53:3590// Returns whether |url| can be prerendered.
91bool CanPrerenderURL(const GURL& url) {
92 // Prerendering is only enabled for http and https URLs.
93 return url.is_valid() &&
94 (url.SchemeIs(url::kHttpScheme) || url.SchemeIs(url::kHttpsScheme));
95}
96
97// Object used to schedule prerenders.
98class PrerenderRequest {
99 public:
100 PrerenderRequest() {}
101 PrerenderRequest(const GURL& url,
102 ui::PageTransition transition,
103 const web::Referrer& referrer)
104 : url_(url), transition_(transition), referrer_(referrer) {}
105
106 const GURL& url() const { return url_; }
107 ui::PageTransition transition() const { return transition_; }
108 const web::Referrer referrer() const { return referrer_; }
109
110 private:
111 const GURL url_;
112 const ui::PageTransition transition_ = ui::PAGE_TRANSITION_LINK;
113 const web::Referrer referrer_;
114};
115
Kurt Horimoto271707392019-09-21 00:28:08116// A no-op JavaScriptDialogPresenter that cancels prerendering when the
117// prerendered page attempts to show dialogs.
118class PreloadJavaScriptDialogPresenter : public web::JavaScriptDialogPresenter {
119 public:
Kurt Horimotoeb0081e2019-09-26 18:53:35120 explicit PreloadJavaScriptDialogPresenter(
121 id<PreloadCancelling> cancel_handler)
122 : cancel_handler_(cancel_handler) {
123 DCHECK(cancel_handler_);
Kurt Horimoto271707392019-09-21 00:28:08124 }
125
126 // web::JavaScriptDialogPresenter:
127 void RunJavaScriptDialog(web::WebState* web_state,
128 const GURL& origin_url,
129 web::JavaScriptDialogType dialog_type,
130 NSString* message_text,
131 NSString* default_prompt_text,
132 web::DialogClosedCallback callback) override {
133 std::move(callback).Run(NO, nil);
Kurt Horimotoeb0081e2019-09-26 18:53:35134 [cancel_handler_ schedulePrerenderCancel];
Kurt Horimoto271707392019-09-21 00:28:08135 }
136
137 void CancelDialogs(web::WebState* web_state) override {}
138
139 private:
Kurt Horimotoeb0081e2019-09-26 18:53:35140 __weak id<PreloadCancelling> cancel_handler_ = nil;
Kurt Horimoto271707392019-09-21 00:28:08141};
142} // namespace
143
Kurt Horimotoeb0081e2019-09-26 18:53:35144@interface PreloadController () <CRConnectionTypeObserverBridge,
145 CRWWebStateDelegate,
146 CRWWebStateObserver,
147 CRWWebStatePolicyDecider,
148 ManageAccountsDelegate,
149 PrefObserverDelegate,
150 PreloadCancelling> {
151 std::unique_ptr<web::WebStateDelegateBridge> _webStateDelegate;
152 std::unique_ptr<web::WebStateObserverBridge> _webStateObserver;
153 std::unique_ptr<PrefObserverBridge> _observerBridge;
154 std::unique_ptr<ConnectionTypeObserverBridge> _connectionTypeObserver;
155 std::unique_ptr<web::WebStatePolicyDeciderBridge> _policyDeciderBridge;
sdefresned9217bc2016-12-19 13:58:32156
Kurt Horimotoeb0081e2019-09-26 18:53:35157 // The WebState used for prerendering.
158 std::unique_ptr<web::WebState> _webState;
159
160 // The scheduled request.
161 std::unique_ptr<PrerenderRequest> _scheduledRequest;
162
163 // Registrar for pref changes notifications.
164 PrefChangeRegistrar _prefChangeRegistrar;
165
166 // The dialog presenter.
167 std::unique_ptr<web::JavaScriptDialogPresenter> _dialogPresenter;
168}
169
170// The ChromeBrowserState passed on initialization.
Sylvain Defresne8f0cda2b2020-01-24 11:50:00171@property(nonatomic) ChromeBrowserState* browserState;
Kurt Horimotoeb0081e2019-09-26 18:53:35172
173// Redefine property as readwrite. The URL that is prerendered in |_webState|.
174// This can be different from the value returned by WebState last committed
175// navigation item, for example in cases where there was a redirect.
176//
177// When choosing whether or not to use a prerendered Tab,
178// BrowserViewController compares the URL being loaded by the omnibox with the
179// URL of the prerendered Tab. Comparing against the Tab's currently URL
180// could return false negatives in cases of redirect, hence the need to store
181// the originally prerendered URL.
182@property(nonatomic, readwrite, assign) GURL prerenderedURL;
183
184// The URL in the currently scheduled prerender request, or an empty one if
185// there is no prerender scheduled.
186@property(nonatomic, readonly) const GURL& scheduledURL;
187
188// Whether or not the preference is enabled.
189@property(nonatomic, getter=isPreferenceEnabled) BOOL preferenceEnabled;
190
191// Whether or not prerendering is only when on wifi.
192@property(nonatomic, getter=isWifiOnly) BOOL wifiOnly;
193
194// Whether or not the current connection is using WWAN.
195@property(nonatomic, getter=isUsingWWAN) BOOL usingWWAN;
196
197// Number of successful prerenders (i.e. the user viewed the prerendered page)
198// during the lifetime of this controller.
199@property(nonatomic) NSUInteger successfulPrerendersPerSessionCount;
200
201// Tracks the time of the last attempt to load a prerender URL. Used for UMA
202// reporting of load durations.
203@property(nonatomic) base::TimeTicks startTime;
204
Gauthier Ambard6d3ed3472020-05-11 09:11:48205// Whether the load was completed or not.
206@property(nonatomic, assign) BOOL loadCompleted;
207// The time between the start of the load and the completion (only valid if the
208// load completed).
209@property(nonatomic) base::TimeDelta completionTime;
210
Kurt Horimotoeb0081e2019-09-26 18:53:35211// Called to start any scheduled prerendering requests.
212- (void)startPrerender;
213
214// Destroys the preview Tab and resets |prerenderURL_| to the empty URL.
215- (void)destroyPreviewContents;
216
217// Removes any scheduled prerender requests and resets |scheduledURL| to the
218// empty URL.
219- (void)removeScheduledPrerenderRequests;
220
221// Records metric on a successful prerender.
222- (void)recordReleaseMetrics;
223
224@end
225
226@implementation PreloadController
Kurt Horimoto38bfb152019-09-25 21:05:41227
Sylvain Defresne8f0cda2b2020-01-24 11:50:00228- (instancetype)initWithBrowserState:(ChromeBrowserState*)browserState {
sdefresned9217bc2016-12-19 13:58:32229 DCHECK(browserState);
230 DCHECK_CURRENTLY_ON(web::WebThread::UI);
231 if ((self = [super init])) {
Kurt Horimotoeb0081e2019-09-26 18:53:35232 _browserState = browserState;
233 _preferenceEnabled =
234 _browserState->GetPrefs()->GetBoolean(prefs::kNetworkPredictionEnabled);
235 _wifiOnly = _browserState->GetPrefs()->GetBoolean(
sdefresned9217bc2016-12-19 13:58:32236 prefs::kNetworkPredictionWifiOnly);
Kurt Horimotoeb0081e2019-09-26 18:53:35237 _usingWWAN = net::NetworkChangeNotifier::IsConnectionCellular(
sdefresned9217bc2016-12-19 13:58:32238 net::NetworkChangeNotifier::GetConnectionType());
Kurt Horimotoeb0081e2019-09-26 18:53:35239 _webStateDelegate = std::make_unique<web::WebStateDelegateBridge>(self);
240 _webStateObserver = std::make_unique<web::WebStateObserverBridge>(self);
241 _observerBridge = std::make_unique<PrefObserverBridge>(self);
242 _prefChangeRegistrar.Init(_browserState->GetPrefs());
243 _observerBridge->ObserveChangesForPreference(
244 prefs::kNetworkPredictionEnabled, &_prefChangeRegistrar);
245 _observerBridge->ObserveChangesForPreference(
246 prefs::kNetworkPredictionWifiOnly, &_prefChangeRegistrar);
247 _dialogPresenter = std::make_unique<PreloadJavaScriptDialogPresenter>(self);
248 if (_preferenceEnabled && _wifiOnly) {
249 _connectionTypeObserver =
Sylvain Defresne949367262018-03-02 22:33:16250 std::make_unique<ConnectionTypeObserverBridge>(self);
sdefresned9217bc2016-12-19 13:58:32251 }
252
253 [[NSNotificationCenter defaultCenter]
254 addObserver:self
255 selector:@selector(didReceiveMemoryWarning)
256 name:UIApplicationDidReceiveMemoryWarningNotification
257 object:nil];
258 }
259 return self;
260}
261
sdefresned9217bc2016-12-19 13:58:32262- (void)dealloc {
Steven Holte95922222018-09-14 20:06:23263 UMA_HISTOGRAM_COUNTS_1M(kPrerendersPerSessionCountHistogramName,
Kurt Horimotoeb0081e2019-09-26 18:53:35264 self.successfulPrerendersPerSessionCount);
sdefresned9217bc2016-12-19 13:58:32265 [self cancelPrerender];
sdefresned9217bc2016-12-19 13:58:32266}
267
Kurt Horimotoeb0081e2019-09-26 18:53:35268#pragma mark - Accessors
269
270- (const GURL&)scheduledURL {
271 return _scheduledRequest ? _scheduledRequest->url() : GURL::EmptyGURL();
272}
273
274- (BOOL)isEnabled {
275 DCHECK_CURRENTLY_ON(web::WebThread::UI);
276 return !IsPrerenderTabEvictionExperimentalGroup() && self.preferenceEnabled &&
277 !ios::device_util::IsSingleCoreDevice() &&
278 ios::device_util::RamIsAtLeast512Mb() &&
Justin Cohenbe1e94272019-10-09 16:08:03279 !net::NetworkChangeNotifier::IsOffline() &&
Kurt Horimotoeb0081e2019-09-26 18:53:35280 (!self.wifiOnly || !self.usingWWAN);
281}
282
Gauthier Ambard6d3ed3472020-05-11 09:11:48283- (void)setLoadCompleted:(BOOL)loadCompleted {
284 if (_loadCompleted == loadCompleted)
285 return;
286
287 _loadCompleted = loadCompleted;
288
289 if (loadCompleted) {
290 self.completionTime = base::TimeTicks::Now() - self.startTime;
291 } else {
292 self.completionTime = base::TimeDelta();
293 }
294}
295
Kurt Horimotoeb0081e2019-09-26 18:53:35296#pragma mark - Public
297
298- (void)browserStateDestroyed {
299 [self cancelPrerender];
300 _connectionTypeObserver.reset();
301}
302
sdefresned9217bc2016-12-19 13:58:32303- (void)prerenderURL:(const GURL&)url
304 referrer:(const web::Referrer&)referrer
305 transition:(ui::PageTransition)transition
306 immediately:(BOOL)immediately {
Kurt Horimotoeb0081e2019-09-26 18:53:35307 // TODO(crbug.com/754050): If CanPrerenderURL() returns false, should we
Rohit Rao44f204302017-08-10 14:49:54308 // cancel any scheduled prerender requests?
Kurt Horimotoeb0081e2019-09-26 18:53:35309 if (!self.enabled || !CanPrerenderURL(url))
sdefresned9217bc2016-12-19 13:58:32310 return;
311
312 // Ignore this request if there is already a scheduled request for the same
313 // URL; or, if there is no scheduled request, but the currently prerendered
314 // page matches this URL.
Kurt Horimotoeb0081e2019-09-26 18:53:35315 if (url == self.scheduledURL ||
316 (self.scheduledURL.is_empty() && url == self.prerenderedURL)) {
sdefresned9217bc2016-12-19 13:58:32317 return;
318 }
319
320 [self removeScheduledPrerenderRequests];
Kurt Horimotoeb0081e2019-09-26 18:53:35321 _scheduledRequest =
322 std::make_unique<PrerenderRequest>(url, transition, referrer);
sdefresned9217bc2016-12-19 13:58:32323
324 NSTimeInterval delay = immediately ? 0.0 : kPrerenderDelay;
325 [self performSelector:@selector(startPrerender)
326 withObject:nil
327 afterDelay:delay];
328}
329
sdefresned9217bc2016-12-19 13:58:32330- (void)cancelPrerender {
331 [self cancelPrerenderForReason:PRERENDER_FINAL_STATUS_CANCELLED];
332}
333
334- (void)cancelPrerenderForReason:(PrerenderFinalStatus)reason {
335 [self removeScheduledPrerenderRequests];
336 [self destroyPreviewContentsForReason:reason];
337}
338
Sylvain Defresnef5d2d952017-11-14 11:15:31339- (BOOL)isWebStatePrerendered:(web::WebState*)webState {
Kurt Horimotoeb0081e2019-09-26 18:53:35340 return webState && _webState.get() == webState;
Sylvain Defresnef5d2d952017-11-14 11:15:31341}
342
Justin Cohenceff1a212019-10-01 20:43:43343- (std::unique_ptr<web::WebState>)releasePrerenderContents {
Justin Cohen6836cfc52019-10-02 13:28:58344 if (!_webState ||
345 _webState->GetNavigationManager()->IsRestoreSessionInProgress())
Kurt Horimotoeb0081e2019-09-26 18:53:35346 return nullptr;
347
348 self.successfulPrerendersPerSessionCount++;
Justin Cohen118fee6c2018-12-06 19:36:59349 [self recordReleaseMetrics];
sdefresned9217bc2016-12-19 13:58:32350 [self removeScheduledPrerenderRequests];
Kurt Horimotoeb0081e2019-09-26 18:53:35351 self.prerenderedURL = GURL();
352 self.startTime = base::TimeTicks();
Gauthier Ambard6d3ed3472020-05-11 09:11:48353 self.loadCompleted = NO;
Sylvain Defresnef5b8a8e2017-10-24 02:54:44354
Sylvain Defresnef5d2d952017-11-14 11:15:31355 // Move the pre-rendered WebState to a local variable so that it will no
356 // longer be considered as pre-rendering (otherwise tab helpers may early
357 // exist when invoked).
Kurt Horimotoeb0081e2019-09-26 18:53:35358 std::unique_ptr<web::WebState> webState = std::move(_webState);
Sylvain Defresnef5d2d952017-11-14 11:15:31359 DCHECK(![self isWebStatePrerendered:webState.get()]);
edchincd32fdf2017-10-25 12:45:45360
Kurt Horimotoeb0081e2019-09-26 18:53:35361 webState->RemoveObserver(_webStateObserver.get());
Olivier Robin984283a2020-06-22 11:08:50362 breakpad::StopMonitoringURLsForPreloadWebState(webState.get());
Sylvain Defresnef5d2d952017-11-14 11:15:31363 webState->SetDelegate(nullptr);
Kurt Horimotoeb0081e2019-09-26 18:53:35364 _policyDeciderBridge.reset();
Gauthier Ambard5144bfbd2017-11-15 10:07:52365 HistoryTabHelper::FromWebState(webState.get())
Sylvain Defresnef5d2d952017-11-14 11:15:31366 ->SetDelayHistoryServiceNotification(false);
367
368 if (AccountConsistencyService* accountConsistencyService =
369 ios::AccountConsistencyServiceFactory::GetForBrowserState(
Kurt Horimotoeb0081e2019-09-26 18:53:35370 self.browserState)) {
Gauthier Ambard5144bfbd2017-11-15 10:07:52371 accountConsistencyService->RemoveWebStateHandler(webState.get());
Sylvain Defresnef5d2d952017-11-14 11:15:31372 }
373
mrefaata4bdb95b2018-12-04 18:36:10374 if (!webState->IsLoading()) {
mrefaatc977e372019-02-04 22:19:18375 [[OmniboxGeolocationController sharedInstance]
376 finishPageLoadForWebState:webState.get()
377 loadSuccess:YES];
sdefresne2c600c52017-04-04 16:49:59378 }
379
Sylvain Defresnef5d2d952017-11-14 11:15:31380 return webState;
sdefresned9217bc2016-12-19 13:58:32381}
382
Kurt Horimotoeb0081e2019-09-26 18:53:35383#pragma mark - CRConnectionTypeObserverBridge
384
sdefresned9217bc2016-12-19 13:58:32385- (void)connectionTypeChanged:(net::NetworkChangeNotifier::ConnectionType)type {
386 DCHECK_CURRENTLY_ON(web::WebThread::UI);
Kurt Horimotoeb0081e2019-09-26 18:53:35387 self.usingWWAN = net::NetworkChangeNotifier::IsConnectionCellular(type);
388 if (self.wifiOnly && self.usingWWAN)
sdefresned9217bc2016-12-19 13:58:32389 [self cancelPrerender];
390}
391
Sylvain Defresnef5d2d952017-11-14 11:15:31392#pragma mark - CRWWebStateDelegate
393
Eugene But6c41f5e2018-11-09 00:39:32394- (web::WebState*)webState:(web::WebState*)webState
395 createNewWebStateForURL:(const GURL&)URL
396 openerURL:(const GURL&)openerURL
397 initiatedByUser:(BOOL)initiatedByUser {
398 DCHECK([self isWebStatePrerendered:webState]);
399 [self schedulePrerenderCancel];
400 return nil;
401}
402
403- (web::JavaScriptDialogPresenter*)javaScriptDialogPresenterForWebState:
404 (web::WebState*)webState {
405 DCHECK([self isWebStatePrerendered:webState]);
Kurt Horimotoeb0081e2019-09-26 18:53:35406 return _dialogPresenter.get();
Eugene But6c41f5e2018-11-09 00:39:32407}
408
Sylvain Defresnef5d2d952017-11-14 11:15:31409- (void)webState:(web::WebState*)webState
410 didRequestHTTPAuthForProtectionSpace:(NSURLProtectionSpace*)protectionSpace
411 proposedCredential:(NSURLCredential*)proposedCredential
412 completionHandler:(void (^)(NSString* username,
413 NSString* password))handler {
414 DCHECK([self isWebStatePrerendered:webState]);
415 [self schedulePrerenderCancel];
416 if (handler) {
417 handler(nil, nil);
418 }
419}
420
Gauthier Ambard99f408192020-08-03 12:56:46421- (UIView*)webViewContainerForWebState:(web::WebState*)webState {
422 return [self.delegate webViewContainer];
423}
424
Sylvain Defresne949367262018-03-02 22:33:16425#pragma mark - CRWWebStateObserver
sdefresned9217bc2016-12-19 13:58:32426
Sylvain Defresne949367262018-03-02 22:33:16427- (void)webState:(web::WebState*)webState
Gauthier Ambard2b604d82019-11-05 13:23:15428 didFinishNavigation:(web::NavigationContext*)navigation {
429 DCHECK_EQ(webState, _webState.get());
430 if ([self shouldCancelPreloadForMimeType:webState->GetContentsMimeType()])
431 [self schedulePrerenderCancel];
432}
433
434- (void)webState:(web::WebState*)webState
Sylvain Defresne949367262018-03-02 22:33:16435 didLoadPageWithSuccess:(BOOL)loadSuccess {
Kurt Horimotoeb0081e2019-09-26 18:53:35436 DCHECK_EQ(webState, _webState.get());
Gauthier Ambard2b604d82019-11-05 13:23:15437 // The load should have been cancelled when the navigation finishes, but this
438 // makes sure that we didn't miss one.
Gauthier Ambard6d3ed3472020-05-11 09:11:48439 if ([self shouldCancelPreloadForMimeType:webState->GetContentsMimeType()]) {
Sylvain Defresne949367262018-03-02 22:33:16440 [self schedulePrerenderCancel];
Gauthier Ambard6d3ed3472020-05-11 09:11:48441 } else if (loadSuccess) {
442 self.loadCompleted = YES;
443 }
Sylvain Defresne949367262018-03-02 22:33:16444}
445
mrefaat3ad5e412018-08-02 20:11:34446#pragma mark - CRWWebStatePolicyDecider
447
Mike Dougherty15f6db52020-04-09 19:13:07448- (WebStatePolicyDecider::PolicyDecision)
449 shouldAllowRequest:(NSURLRequest*)request
450 requestInfo:(const WebStatePolicyDecider::RequestInfo&)info {
mrefaat3ad5e412018-08-02 20:11:34451 GURL requestURL = net::GURLWithNSURL(request.URL);
452 // Don't allow preloading for requests that are handled by opening another
453 // application or by presenting a native UI.
454 if (AppLauncherTabHelper::IsAppUrl(requestURL) ||
455 ITunesUrlsHandlerTabHelper::CanHandleUrl(requestURL)) {
456 [self schedulePrerenderCancel];
Mike Dougherty15f6db52020-04-09 19:13:07457 return WebStatePolicyDecider::PolicyDecision::Cancel();
mrefaat3ad5e412018-08-02 20:11:34458 }
Mike Dougherty15f6db52020-04-09 19:13:07459 return WebStatePolicyDecider::PolicyDecision::Allow();
mrefaat3ad5e412018-08-02 20:11:34460}
Kurt Horimotoeb0081e2019-09-26 18:53:35461
462#pragma mark - ManageAccountsDelegate
463
464- (void)onManageAccounts {
465 [self schedulePrerenderCancel];
466}
467
Nohemi Fernandez753c2f62020-09-09 15:18:41468- (void)onShowConsistencyPromo {
469 [self schedulePrerenderCancel];
470}
471
Kurt Horimotoeb0081e2019-09-26 18:53:35472- (void)onAddAccount {
473 [self schedulePrerenderCancel];
474}
475
476- (void)onGoIncognito:(const GURL&)url {
477 [self schedulePrerenderCancel];
478}
479
480#pragma mark - PrefObserverDelegate
481
482- (void)onPreferenceChanged:(const std::string&)preferenceName {
483 if (preferenceName == prefs::kNetworkPredictionEnabled ||
484 preferenceName == prefs::kNetworkPredictionWifiOnly) {
485 DCHECK_CURRENTLY_ON(web::WebThread::UI);
486 // The logic is simpler if both preferences changes are handled equally.
487 self.preferenceEnabled = self.browserState->GetPrefs()->GetBoolean(
488 prefs::kNetworkPredictionEnabled);
489 self.wifiOnly = self.browserState->GetPrefs()->GetBoolean(
490 prefs::kNetworkPredictionWifiOnly);
491
492 if (self.wifiOnly && self.preferenceEnabled) {
493 if (!_connectionTypeObserver.get()) {
494 self.usingWWAN = net::NetworkChangeNotifier::IsConnectionCellular(
495 net::NetworkChangeNotifier::GetConnectionType());
496 _connectionTypeObserver.reset(new ConnectionTypeObserverBridge(self));
497 }
498 if (self.usingWWAN) {
499 [self cancelPrerender];
500 }
501 } else if (self.preferenceEnabled) {
502 _connectionTypeObserver.reset();
503 } else {
504 [self cancelPrerender];
505 _connectionTypeObserver.reset();
506 }
507 }
508}
509
510#pragma mark - PreloadCancelling
511
512- (void)schedulePrerenderCancel {
513 // TODO(crbug.com/228550): Instead of cancelling the prerender, should we mark
514 // it as failed instead? That way, subsequent prerender requests for the same
515 // URL will not kick off new prerenders.
516 [self removeScheduledPrerenderRequests];
517 [self performSelector:@selector(cancelPrerender) withObject:nil afterDelay:0];
518}
519
520#pragma mark - Cancellation Helpers
521
Gauthier Ambard2b604d82019-11-05 13:23:15522- (BOOL)shouldCancelPreloadForMimeType:(std::string)mimeType {
523 // Cancel prerendering if response is "application/octet-stream". It can be a
524 // video file which should not be played from preload tab. See issue at
525 // http://crbug.com/436813 for more details.
526 // On iOS 13, PDF are getting focused when loaded, preventing the user from
527 // typing in the omnibox. See crbug.com/1017352.
528 return mimeType == "application/octet-stream" ||
529 mimeType == "application/pdf";
530}
531
Kurt Horimotoeb0081e2019-09-26 18:53:35532- (void)removeScheduledPrerenderRequests {
533 [NSObject cancelPreviousPerformRequestsWithTarget:self];
534 _scheduledRequest = nullptr;
535}
536
537#pragma mark - Prerender Helpers
538
539- (void)startPrerender {
540 // Destroy any existing prerenders before starting a new one.
541 [self destroyPreviewContents];
542 self.prerenderedURL = self.scheduledURL;
543 std::unique_ptr<PrerenderRequest> request = std::move(_scheduledRequest);
544
Justin Cohen6836cfc52019-10-02 13:28:58545 web::WebState* webStateToReplace = [self.delegate webStateToReplace];
546 if (!self.prerenderedURL.is_valid() || !webStateToReplace) {
Kurt Horimotoeb0081e2019-09-26 18:53:35547 [self destroyPreviewContents];
548 return;
549 }
550
551 web::WebState::CreateParams createParams(self.browserState);
Yi Suee7be512019-12-09 13:25:10552 _webState = web::WebState::CreateWithStorageSession(
553 createParams, webStateToReplace->BuildSessionStorage());
Justin Cohen6836cfc52019-10-02 13:28:58554
Kurt Horimotoeb0081e2019-09-26 18:53:35555 // Add the preload controller as a policyDecider before other tab helpers, so
556 // that it can block the navigation if needed before other policy deciders
557 // execute thier side effects (eg. AppLauncherTabHelper launching app).
558 _policyDeciderBridge =
559 std::make_unique<web::WebStatePolicyDeciderBridge>(_webState.get(), self);
560 AttachTabHelpers(_webState.get(), /*for_prerender=*/true);
561
Kurt Horimotoeb0081e2019-09-26 18:53:35562 _webState->SetDelegate(_webStateDelegate.get());
563 _webState->AddObserver(_webStateObserver.get());
Olivier Robin984283a2020-06-22 11:08:50564 breakpad::MonitorURLsForPreloadWebState(_webState.get());
Kurt Horimotoeb0081e2019-09-26 18:53:35565 _webState->SetWebUsageEnabled(true);
566
567 if (AccountConsistencyService* accountConsistencyService =
568 ios::AccountConsistencyServiceFactory::GetForBrowserState(
569 self.browserState)) {
570 accountConsistencyService->SetWebStateHandler(_webState.get(), self);
571 }
572
573 HistoryTabHelper::FromWebState(_webState.get())
574 ->SetDelayHistoryServiceNotification(true);
575
576 web::NavigationManager::WebLoadParams loadParams(self.prerenderedURL);
577 loadParams.referrer = request->referrer();
578 loadParams.transition_type = request->transition();
Kurt Horimotoeb0081e2019-09-26 18:53:35579 _webState->SetKeepRenderProcessAlive(true);
580 _webState->GetNavigationManager()->LoadURLWithParams(loadParams);
581
582 // LoadIfNecessary is needed because the view is not created (but needed) when
583 // loading the page. TODO(crbug.com/705819): Remove this call.
584 _webState->GetNavigationManager()->LoadIfNecessary();
585
586 self.startTime = base::TimeTicks::Now();
Gauthier Ambard6d3ed3472020-05-11 09:11:48587 self.loadCompleted = NO;
Kurt Horimotoeb0081e2019-09-26 18:53:35588}
589
590#pragma mark - Teardown Helpers
591
592- (void)destroyPreviewContents {
593 [self destroyPreviewContentsForReason:PRERENDER_FINAL_STATUS_CANCELLED];
594}
595
596- (void)destroyPreviewContentsForReason:(PrerenderFinalStatus)reason {
597 if (!_webState)
598 return;
599
600 UMA_HISTOGRAM_ENUMERATION(kPrerenderFinalStatusHistogramName, reason,
601 PRERENDER_FINAL_STATUS_MAX);
602
Kurt Horimotoeb0081e2019-09-26 18:53:35603 _webState->RemoveObserver(_webStateObserver.get());
Olivier Robin984283a2020-06-22 11:08:50604 breakpad::StopMonitoringURLsForPreloadWebState(_webState.get());
Kurt Horimotoeb0081e2019-09-26 18:53:35605 _webState->SetDelegate(nullptr);
606 _webState.reset();
607
608 self.prerenderedURL = GURL();
609 self.startTime = base::TimeTicks();
Gauthier Ambard6d3ed3472020-05-11 09:11:48610 self.loadCompleted = NO;
Kurt Horimotoeb0081e2019-09-26 18:53:35611}
612
613#pragma mark - Notification Helpers
614
615- (void)didReceiveMemoryWarning {
616 [self cancelPrerenderForReason:PRERENDER_FINAL_STATUS_MEMORY_LIMIT_EXCEEDED];
617}
618
619#pragma mark - Metrics Helpers
620
621- (void)recordReleaseMetrics {
622 UMA_HISTOGRAM_ENUMERATION(kPrerenderFinalStatusHistogramName,
623 PRERENDER_FINAL_STATUS_USED,
624 PRERENDER_FINAL_STATUS_MAX);
625
Gauthier Ambard6d3ed3472020-05-11 09:11:48626 UMA_HISTOGRAM_BOOLEAN(kPrerenderLoadComplete, self.loadCompleted);
627
628 if (self.loadCompleted) {
629 DCHECK_NE(base::TimeDelta(), self.completionTime);
630 UMA_HISTOGRAM_TIMES(kPrerenderPrerenderTimeSaved, self.completionTime);
631 } else {
632 DCHECK_NE(base::TimeTicks(), self.startTime);
633 UMA_HISTOGRAM_TIMES(kPrerenderPrerenderTimeSaved,
634 base::TimeTicks::Now() - self.startTime);
635 }
Kurt Horimotoeb0081e2019-09-26 18:53:35636}
637
sdefresned9217bc2016-12-19 13:58:32638@end