Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(65)

Side by Side Diff: ios/chrome/browser/ui/preload_controller.mm

Issue 2589803002: Upstream Chrome on iOS source code [6/11]. (Closed)
Patch Set: Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // 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
7 #include "ios/chrome/browser/ui/preload_controller.h"
8
9 #include "base/ios/device_util.h"
10 #include "base/logging.h"
11 #include "base/metrics/field_trial.h"
12 #include "base/metrics/histogram.h"
13 #include "base/strings/sys_string_conversions.h"
14 #include "components/prefs/pref_service.h"
15 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
16 #include "ios/chrome/browser/pref_names.h"
17 #import "ios/chrome/browser/tabs/tab.h"
18 #include "ios/chrome/browser/ui/preload_controller_delegate.h"
19 #include "ios/chrome/browser/ui/prerender_final_status.h"
20 #import "ios/web/public/web_state/ui/crw_native_content.h"
21 #include "ios/web/public/web_thread.h"
22 #import "ios/web/web_state/ui/crw_web_controller.h"
23 #import "net/base/mac/url_conversions.h"
24 #include "net/url_request/url_fetcher.h"
25 #include "net/url_request/url_fetcher_delegate.h"
26 #include "ui/base/page_transition_types.h"
27
28 // ID of the URLFetcher responsible for prefetches.
29 const int kPreloadControllerURLFetcherID = 1;
30
31 namespace {
32 // Delay before starting to prerender a URL.
33 const NSTimeInterval kPrerenderDelay = 0.5;
34
35 // The finch experiment to turn off prefetching as a field trial.
36 const char kTabEvictionFieldTrialName[] = "TabEviction";
37 // The associated group.
38 const char kPrerenderTabEvictionTrialGroup[] = "NoPrerendering";
39 // The name of the histogram for recording final status (e.g. used/cancelled)
40 // of prerender requests.
41 const char kPrerenderFinalStatusHistogramName[] = "Prerender.FinalStatus";
42 // The name of the histogram for recording the number of sucessful prerenders.
43 const char kPrerendersPerSessionCountHistogramName[] =
44 "Prerender.PrerendersPerSessionCount";
45
46 // Is this install selected for this particular experiment.
47 bool IsPrerenderTabEvictionExperimentalGroup() {
48 base::FieldTrial* trial =
49 base::FieldTrialList::Find(kTabEvictionFieldTrialName);
50 return trial && trial->group_name() == kPrerenderTabEvictionTrialGroup;
51 }
52
53 } // namespace
54
55 @interface PreloadController (PrivateMethods)
56
57 // Returns YES if prerendering is enabled.
58 - (BOOL)isPrerenderingEnabled;
59
60 // Returns YES if prefetching is enabled.
61 - (BOOL)isPrefetchingEnabled;
62
63 // Returns YES if the |url| is valid for prerendering and prefetching.
64 - (BOOL)shouldPreloadURL:(const GURL&)url;
65
66 // Called to start any scheduled prerendering requests.
67 - (void)startPrerender;
68
69 // Destroys the preview Tab and resets |prerenderURL_| to the empty URL.
70 - (void)destroyPreviewContents;
71
72 // Schedules the current prerender to be cancelled during the next run of the
73 // event loop.
74 - (void)schedulePrerenderCancel;
75
76 // Removes any scheduled prerender requests and resets |scheduledURL| to the
77 // empty URL.
78 - (void)removeScheduledPrerenderRequests;
79
80 // Starts the scheduled prefetch request.
81 - (void)startPrefetch;
82
83 // Cancels the scheduled prefetch request.
84 - (void)cancelPrefetch;
85
86 // Completes the current prefetch request. Called by the URLFetcher's delegate
87 // when the URLFetcher has compleeted fetching the |prefetchedURL|.
88 - (void)prefetchDidComplete:(const net::URLFetcher*)source;
89
90 @end
91
92 // Delegate to handle completion of URLFetcher operations.
93 class PrefetchDelegate : public net::URLFetcherDelegate {
94 public:
95 explicit PrefetchDelegate(PreloadController* owner) : owner_(owner) {}
96 void OnURLFetchComplete(const net::URLFetcher* source) override {
97 [owner_ prefetchDidComplete:source];
98 }
99
100 private:
101 PreloadController* owner_; // weak
102 };
103
104 @implementation PreloadController
105
106 @synthesize prerenderedURL = prerenderedURL_;
107 @synthesize prefetchedURL = prefetchedURL_;
108 @synthesize delegate = delegate_;
109
110 - (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState {
111 DCHECK(browserState);
112 DCHECK_CURRENTLY_ON(web::WebThread::UI);
113 if ((self = [super init])) {
114 browserState_ = browserState;
115 enabled_ =
116 browserState_->GetPrefs()->GetBoolean(prefs::kNetworkPredictionEnabled);
117 wifiOnly_ = browserState_->GetPrefs()->GetBoolean(
118 prefs::kNetworkPredictionWifiOnly);
119 usingWWAN_ = net::NetworkChangeNotifier::IsConnectionCellular(
120 net::NetworkChangeNotifier::GetConnectionType());
121 observerBridge_.reset(new PrefObserverBridge(self));
122 prefChangeRegistrar_.Init(browserState_->GetPrefs());
123 observerBridge_->ObserveChangesForPreference(
124 prefs::kNetworkPredictionEnabled, &prefChangeRegistrar_);
125 observerBridge_->ObserveChangesForPreference(
126 prefs::kNetworkPredictionWifiOnly, &prefChangeRegistrar_);
127 if (enabled_ && wifiOnly_) {
128 connectionTypeObserverBridge_.reset(
129 new ConnectionTypeObserverBridge(self));
130 }
131
132 [[NSNotificationCenter defaultCenter]
133 addObserver:self
134 selector:@selector(didReceiveMemoryWarning)
135 name:UIApplicationDidReceiveMemoryWarningNotification
136 object:nil];
137 }
138 return self;
139 }
140
141 - (void)dealloc {
142 UMA_HISTOGRAM_COUNTS(kPrerendersPerSessionCountHistogramName,
143 successfulPrerendersPerSessionCount_);
144 [[NSNotificationCenter defaultCenter] removeObserver:self];
145 [self cancelPrerender];
146 [super dealloc];
147 }
148
149 - (void)prerenderURL:(const GURL&)url
150 referrer:(const web::Referrer&)referrer
151 transition:(ui::PageTransition)transition
152 immediately:(BOOL)immediately {
153 // TODO(rohitrao): If shouldPrerenderURL returns false, should we cancel any
154 // scheduled prerender requests?
155 if (![self isPrerenderingEnabled] || ![self shouldPreloadURL:url])
156 return;
157
158 // Ignore this request if there is already a scheduled request for the same
159 // URL; or, if there is no scheduled request, but the currently prerendered
160 // page matches this URL.
161 if (url == scheduledURL_ ||
162 (scheduledURL_.is_empty() && url == prerenderedURL_)) {
163 return;
164 }
165
166 [self removeScheduledPrerenderRequests];
167 scheduledURL_ = url;
168 scheduledTransition_ = transition;
169 scheduledReferrer_ = referrer;
170
171 NSTimeInterval delay = immediately ? 0.0 : kPrerenderDelay;
172 [self performSelector:@selector(startPrerender)
173 withObject:nil
174 afterDelay:delay];
175 }
176
177 - (void)prefetchURL:(const GURL&)url transition:(ui::PageTransition)transition {
178 if (![self isPrefetchingEnabled] || ![self shouldPreloadURL:url]) {
179 return;
180 }
181
182 // Ignore this request if the the currently prefetched page matches this URL.
183 if ([self hasPrefetchedURL:url]) {
184 return;
185 }
186
187 // Cancel any in-fight prefetches before starting a new one.
188 [self cancelPrefetch];
189
190 DCHECK(url.is_valid());
191 if (!url.is_valid()) {
192 return;
193 }
194
195 prefetchedURL_ = [self urlToPrefetchURL:url];
196 prefetcherDelegate_.reset(new PrefetchDelegate(self));
197 prefetcher_ =
198 net::URLFetcher::Create(kPreloadControllerURLFetcherID, prefetchedURL_,
199 net::URLFetcher::GET, prefetcherDelegate_.get());
200 prefetcher_->SetRequestContext(browserState_->GetRequestContext());
201 prefetcher_->Start();
202 }
203
204 - (void)cancelPrerender {
205 [self cancelPrerenderForReason:PRERENDER_FINAL_STATUS_CANCELLED];
206 }
207
208 - (void)cancelPrerenderForReason:(PrerenderFinalStatus)reason {
209 [self removeScheduledPrerenderRequests];
210 [self destroyPreviewContentsForReason:reason];
211 }
212
213 - (Tab*)releasePrerenderContents {
214 successfulPrerendersPerSessionCount_++;
215 UMA_HISTOGRAM_ENUMERATION(kPrerenderFinalStatusHistogramName,
216 PRERENDER_FINAL_STATUS_USED,
217 PRERENDER_FINAL_STATUS_MAX);
218 [self removeScheduledPrerenderRequests];
219 prerenderedURL_ = GURL();
220 [[tab_ webController] setNativeProvider:nil];
221 [tab_ setDelegate:nil];
222 return [tab_.release() autorelease];
223 }
224
225 - (void)connectionTypeChanged:(net::NetworkChangeNotifier::ConnectionType)type {
226 DCHECK_CURRENTLY_ON(web::WebThread::UI);
227 usingWWAN_ = net::NetworkChangeNotifier::IsConnectionCellular(type);
228 if (wifiOnly_ && usingWWAN_)
229 [self cancelPrerender];
230 }
231
232 - (void)onPreferenceChanged:(const std::string&)preferenceName {
233 if (preferenceName == prefs::kNetworkPredictionEnabled ||
234 preferenceName == prefs::kNetworkPredictionWifiOnly) {
235 DCHECK_CURRENTLY_ON(web::WebThread::UI);
236 // The logic is simpler if both preferences changes are handled equally.
237 enabled_ =
238 browserState_->GetPrefs()->GetBoolean(prefs::kNetworkPredictionEnabled);
239 wifiOnly_ = browserState_->GetPrefs()->GetBoolean(
240 prefs::kNetworkPredictionWifiOnly);
241
242 if (wifiOnly_ && enabled_) {
243 if (!connectionTypeObserverBridge_.get()) {
244 usingWWAN_ = net::NetworkChangeNotifier::IsConnectionCellular(
245 net::NetworkChangeNotifier::GetConnectionType());
246 connectionTypeObserverBridge_.reset(
247 new ConnectionTypeObserverBridge(self));
248 }
249 if (usingWWAN_) {
250 [self cancelPrerender];
251 }
252 } else if (enabled_) {
253 connectionTypeObserverBridge_.reset();
254 } else {
255 [self cancelPrerender];
256 connectionTypeObserverBridge_.reset();
257 }
258 }
259 }
260
261 - (void)didReceiveMemoryWarning {
262 [self cancelPrerenderForReason:PRERENDER_FINAL_STATUS_MEMORY_LIMIT_EXCEEDED];
263 }
264
265 #pragma mark -
266 #pragma mark CRWNativeContentProvider implementation
267
268 // Delegate the call to the original native provider.
269 - (BOOL)hasControllerForURL:(const GURL&)url {
270 return [[tab_ webController].nativeProvider hasControllerForURL:url];
271 }
272
273 // Override the CRWNativeContentProvider methods to cancel any prerenders that
274 // require native content.
275 - (id<CRWNativeContent>)controllerForURL:(const GURL&)url {
276 [self schedulePrerenderCancel];
277 return nil;
278 }
279
280 // Override the CRWNativeContentProvider methods to cancel any prerenders that
281 // require native content.
282 - (id<CRWNativeContent>)controllerForURL:(const GURL&)url
283 withError:(NSError*)error
284 isPost:(BOOL)isPost {
285 [self schedulePrerenderCancel];
286 return nil;
287 }
288
289 #pragma mark -
290 #pragma mark Private Methods
291
292 - (BOOL)isPrerenderingEnabled {
293 DCHECK_CURRENTLY_ON(web::WebThread::UI);
294 return !IsPrerenderTabEvictionExperimentalGroup() && enabled_ &&
295 !ios::device_util::IsSingleCoreDevice() &&
296 ios::device_util::RamIsAtLeast512Mb() && (!wifiOnly_ || !usingWWAN_);
297 }
298
299 - (BOOL)isPrefetchingEnabled {
300 DCHECK_CURRENTLY_ON(web::WebThread::UI);
301 return enabled_ && (!wifiOnly_ || !usingWWAN_);
302 }
303
304 - (BOOL)shouldPreloadURL:(const GURL&)url {
305 return url.SchemeIs(url::kHttpScheme) || url.SchemeIs(url::kHttpsScheme);
306 }
307
308 - (void)startPrerender {
309 // Destroy any existing prerenders before starting a new one.
310 [self destroyPreviewContents];
311 prerenderedURL_ = scheduledURL_;
312 scheduledURL_ = GURL();
313
314 DCHECK(prerenderedURL_.is_valid());
315 if (!prerenderedURL_.is_valid()) {
316 [self destroyPreviewContents];
317 return;
318 }
319
320 Tab* tab = [Tab
321 newPreloadingTabWithBrowserState:browserState_
322 url:prerenderedURL_
323 referrer:scheduledReferrer_
324 transition:scheduledTransition_
325 provider:self
326 opener:nil
327 desktopUserAgent:[delegate_ shouldUseDesktopUserAgent]
328 configuration:^(Tab* tab) {
329 [tab setIsPrerenderTab:YES];
330 [tab setDelegate:self];
331 }];
332
333 // Create and set up the prerender.
334 tab_.reset([tab retain]);
335
336 // Trigger the page to start loading.
337 [tab_ view];
338 }
339
340 - (const GURL)urlToPrefetchURL:(const GURL&)url {
341 GURL::Replacements replacements;
342
343 // Add prefetch indicator to query params.
344 std::string query = url.query();
345 if (!query.empty()) {
346 query.append("&");
347 }
348 query.append("pf=i");
349 replacements.SetQueryStr(query);
350
351 return url.ReplaceComponents(replacements);
352 }
353
354 - (BOOL)hasPrefetchedURL:(const GURL&)url {
355 return prefetchedURL_ == [self urlToPrefetchURL:url];
356 }
357
358 - (void)cancelPrefetch {
359 prefetcher_.reset();
360 prefetchedURL_ = GURL();
361 }
362
363 - (void)prefetchDidComplete:(const net::URLFetcher*)source {
364 if (source) {
365 DLOG_IF(WARNING, source->GetResponseCode() != 200)
366 << "Prefetching URL got response code " << source->GetResponseCode();
367 }
368 prefetcher_.reset();
369 }
370
371 - (void)destroyPreviewContents {
372 [self destroyPreviewContentsForReason:PRERENDER_FINAL_STATUS_CANCELLED];
373 }
374
375 - (void)destroyPreviewContentsForReason:(PrerenderFinalStatus)reason {
376 if (!tab_.get())
377 return;
378
379 UMA_HISTOGRAM_ENUMERATION(kPrerenderFinalStatusHistogramName, reason,
380 PRERENDER_FINAL_STATUS_MAX);
381 [[tab_ webController] setNativeProvider:nil];
382 [tab_ setDelegate:nil];
383 [tab_ close];
384 tab_.reset();
385 prerenderedURL_ = GURL();
386 }
387
388 - (void)schedulePrerenderCancel {
389 // TODO(rohitrao): Instead of cancelling the prerender, should we mark it as
390 // failed instead? That way, subsequent prerender requests for the same URL
391 // will not kick off new prerenders. b/5944421
392 [self removeScheduledPrerenderRequests];
393 [self performSelector:@selector(cancelPrerender) withObject:nil afterDelay:0];
394 }
395
396 - (void)removeScheduledPrerenderRequests {
397 [NSObject cancelPreviousPerformRequestsWithTarget:self];
398 scheduledURL_ = GURL();
399 }
400
401 #pragma mark - TabDelegate
402
403 - (void)discardPrerender {
404 [self schedulePrerenderCancel];
405 }
406
407 @end
OLDNEW
« no previous file with comments | « ios/chrome/browser/ui/preload_controller.h ('k') | ios/chrome/browser/ui/preload_controller_delegate.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698