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

Unified 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 side-by-side diff with in-line comments
Download patch
« 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 »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: ios/chrome/browser/ui/preload_controller.mm
diff --git a/ios/chrome/browser/ui/preload_controller.mm b/ios/chrome/browser/ui/preload_controller.mm
new file mode 100644
index 0000000000000000000000000000000000000000..cc8fcf7b764af814526955f9d1579d549f5ac29d
--- /dev/null
+++ b/ios/chrome/browser/ui/preload_controller.mm
@@ -0,0 +1,407 @@
+// Copyright 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <UIKit/UIKit.h>
+
+#include "ios/chrome/browser/ui/preload_controller.h"
+
+#include "base/ios/device_util.h"
+#include "base/logging.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/strings/sys_string_conversions.h"
+#include "components/prefs/pref_service.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#include "ios/chrome/browser/pref_names.h"
+#import "ios/chrome/browser/tabs/tab.h"
+#include "ios/chrome/browser/ui/preload_controller_delegate.h"
+#include "ios/chrome/browser/ui/prerender_final_status.h"
+#import "ios/web/public/web_state/ui/crw_native_content.h"
+#include "ios/web/public/web_thread.h"
+#import "ios/web/web_state/ui/crw_web_controller.h"
+#import "net/base/mac/url_conversions.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "ui/base/page_transition_types.h"
+
+// ID of the URLFetcher responsible for prefetches.
+const int kPreloadControllerURLFetcherID = 1;
+
+namespace {
+// Delay before starting to prerender a URL.
+const NSTimeInterval kPrerenderDelay = 0.5;
+
+// The finch experiment to turn off prefetching as a field trial.
+const char kTabEvictionFieldTrialName[] = "TabEviction";
+// The associated group.
+const char kPrerenderTabEvictionTrialGroup[] = "NoPrerendering";
+// The name of the histogram for recording final status (e.g. used/cancelled)
+// of prerender requests.
+const char kPrerenderFinalStatusHistogramName[] = "Prerender.FinalStatus";
+// The name of the histogram for recording the number of sucessful prerenders.
+const char kPrerendersPerSessionCountHistogramName[] =
+ "Prerender.PrerendersPerSessionCount";
+
+// Is this install selected for this particular experiment.
+bool IsPrerenderTabEvictionExperimentalGroup() {
+ base::FieldTrial* trial =
+ base::FieldTrialList::Find(kTabEvictionFieldTrialName);
+ return trial && trial->group_name() == kPrerenderTabEvictionTrialGroup;
+}
+
+} // namespace
+
+@interface PreloadController (PrivateMethods)
+
+// Returns YES if prerendering is enabled.
+- (BOOL)isPrerenderingEnabled;
+
+// Returns YES if prefetching is enabled.
+- (BOOL)isPrefetchingEnabled;
+
+// Returns YES if the |url| is valid for prerendering and prefetching.
+- (BOOL)shouldPreloadURL:(const GURL&)url;
+
+// Called to start any scheduled prerendering requests.
+- (void)startPrerender;
+
+// Destroys the preview Tab and resets |prerenderURL_| to the empty URL.
+- (void)destroyPreviewContents;
+
+// Schedules the current prerender to be cancelled during the next run of the
+// event loop.
+- (void)schedulePrerenderCancel;
+
+// Removes any scheduled prerender requests and resets |scheduledURL| to the
+// empty URL.
+- (void)removeScheduledPrerenderRequests;
+
+// Starts the scheduled prefetch request.
+- (void)startPrefetch;
+
+// Cancels the scheduled prefetch request.
+- (void)cancelPrefetch;
+
+// Completes the current prefetch request. Called by the URLFetcher's delegate
+// when the URLFetcher has compleeted fetching the |prefetchedURL|.
+- (void)prefetchDidComplete:(const net::URLFetcher*)source;
+
+@end
+
+// Delegate to handle completion of URLFetcher operations.
+class PrefetchDelegate : public net::URLFetcherDelegate {
+ public:
+ explicit PrefetchDelegate(PreloadController* owner) : owner_(owner) {}
+ void OnURLFetchComplete(const net::URLFetcher* source) override {
+ [owner_ prefetchDidComplete:source];
+ }
+
+ private:
+ PreloadController* owner_; // weak
+};
+
+@implementation PreloadController
+
+@synthesize prerenderedURL = prerenderedURL_;
+@synthesize prefetchedURL = prefetchedURL_;
+@synthesize delegate = delegate_;
+
+- (instancetype)initWithBrowserState:(ios::ChromeBrowserState*)browserState {
+ DCHECK(browserState);
+ DCHECK_CURRENTLY_ON(web::WebThread::UI);
+ if ((self = [super init])) {
+ browserState_ = browserState;
+ enabled_ =
+ browserState_->GetPrefs()->GetBoolean(prefs::kNetworkPredictionEnabled);
+ wifiOnly_ = browserState_->GetPrefs()->GetBoolean(
+ prefs::kNetworkPredictionWifiOnly);
+ usingWWAN_ = net::NetworkChangeNotifier::IsConnectionCellular(
+ net::NetworkChangeNotifier::GetConnectionType());
+ observerBridge_.reset(new PrefObserverBridge(self));
+ prefChangeRegistrar_.Init(browserState_->GetPrefs());
+ observerBridge_->ObserveChangesForPreference(
+ prefs::kNetworkPredictionEnabled, &prefChangeRegistrar_);
+ observerBridge_->ObserveChangesForPreference(
+ prefs::kNetworkPredictionWifiOnly, &prefChangeRegistrar_);
+ if (enabled_ && wifiOnly_) {
+ connectionTypeObserverBridge_.reset(
+ new ConnectionTypeObserverBridge(self));
+ }
+
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(didReceiveMemoryWarning)
+ name:UIApplicationDidReceiveMemoryWarningNotification
+ object:nil];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ UMA_HISTOGRAM_COUNTS(kPrerendersPerSessionCountHistogramName,
+ successfulPrerendersPerSessionCount_);
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [self cancelPrerender];
+ [super dealloc];
+}
+
+- (void)prerenderURL:(const GURL&)url
+ referrer:(const web::Referrer&)referrer
+ transition:(ui::PageTransition)transition
+ immediately:(BOOL)immediately {
+ // TODO(rohitrao): If shouldPrerenderURL returns false, should we cancel any
+ // scheduled prerender requests?
+ if (![self isPrerenderingEnabled] || ![self shouldPreloadURL:url])
+ return;
+
+ // Ignore this request if there is already a scheduled request for the same
+ // URL; or, if there is no scheduled request, but the currently prerendered
+ // page matches this URL.
+ if (url == scheduledURL_ ||
+ (scheduledURL_.is_empty() && url == prerenderedURL_)) {
+ return;
+ }
+
+ [self removeScheduledPrerenderRequests];
+ scheduledURL_ = url;
+ scheduledTransition_ = transition;
+ scheduledReferrer_ = referrer;
+
+ NSTimeInterval delay = immediately ? 0.0 : kPrerenderDelay;
+ [self performSelector:@selector(startPrerender)
+ withObject:nil
+ afterDelay:delay];
+}
+
+- (void)prefetchURL:(const GURL&)url transition:(ui::PageTransition)transition {
+ if (![self isPrefetchingEnabled] || ![self shouldPreloadURL:url]) {
+ return;
+ }
+
+ // Ignore this request if the the currently prefetched page matches this URL.
+ if ([self hasPrefetchedURL:url]) {
+ return;
+ }
+
+ // Cancel any in-fight prefetches before starting a new one.
+ [self cancelPrefetch];
+
+ DCHECK(url.is_valid());
+ if (!url.is_valid()) {
+ return;
+ }
+
+ prefetchedURL_ = [self urlToPrefetchURL:url];
+ prefetcherDelegate_.reset(new PrefetchDelegate(self));
+ prefetcher_ =
+ net::URLFetcher::Create(kPreloadControllerURLFetcherID, prefetchedURL_,
+ net::URLFetcher::GET, prefetcherDelegate_.get());
+ prefetcher_->SetRequestContext(browserState_->GetRequestContext());
+ prefetcher_->Start();
+}
+
+- (void)cancelPrerender {
+ [self cancelPrerenderForReason:PRERENDER_FINAL_STATUS_CANCELLED];
+}
+
+- (void)cancelPrerenderForReason:(PrerenderFinalStatus)reason {
+ [self removeScheduledPrerenderRequests];
+ [self destroyPreviewContentsForReason:reason];
+}
+
+- (Tab*)releasePrerenderContents {
+ successfulPrerendersPerSessionCount_++;
+ UMA_HISTOGRAM_ENUMERATION(kPrerenderFinalStatusHistogramName,
+ PRERENDER_FINAL_STATUS_USED,
+ PRERENDER_FINAL_STATUS_MAX);
+ [self removeScheduledPrerenderRequests];
+ prerenderedURL_ = GURL();
+ [[tab_ webController] setNativeProvider:nil];
+ [tab_ setDelegate:nil];
+ return [tab_.release() autorelease];
+}
+
+- (void)connectionTypeChanged:(net::NetworkChangeNotifier::ConnectionType)type {
+ DCHECK_CURRENTLY_ON(web::WebThread::UI);
+ usingWWAN_ = net::NetworkChangeNotifier::IsConnectionCellular(type);
+ if (wifiOnly_ && usingWWAN_)
+ [self cancelPrerender];
+}
+
+- (void)onPreferenceChanged:(const std::string&)preferenceName {
+ if (preferenceName == prefs::kNetworkPredictionEnabled ||
+ preferenceName == prefs::kNetworkPredictionWifiOnly) {
+ DCHECK_CURRENTLY_ON(web::WebThread::UI);
+ // The logic is simpler if both preferences changes are handled equally.
+ enabled_ =
+ browserState_->GetPrefs()->GetBoolean(prefs::kNetworkPredictionEnabled);
+ wifiOnly_ = browserState_->GetPrefs()->GetBoolean(
+ prefs::kNetworkPredictionWifiOnly);
+
+ if (wifiOnly_ && enabled_) {
+ if (!connectionTypeObserverBridge_.get()) {
+ usingWWAN_ = net::NetworkChangeNotifier::IsConnectionCellular(
+ net::NetworkChangeNotifier::GetConnectionType());
+ connectionTypeObserverBridge_.reset(
+ new ConnectionTypeObserverBridge(self));
+ }
+ if (usingWWAN_) {
+ [self cancelPrerender];
+ }
+ } else if (enabled_) {
+ connectionTypeObserverBridge_.reset();
+ } else {
+ [self cancelPrerender];
+ connectionTypeObserverBridge_.reset();
+ }
+ }
+}
+
+- (void)didReceiveMemoryWarning {
+ [self cancelPrerenderForReason:PRERENDER_FINAL_STATUS_MEMORY_LIMIT_EXCEEDED];
+}
+
+#pragma mark -
+#pragma mark CRWNativeContentProvider implementation
+
+// Delegate the call to the original native provider.
+- (BOOL)hasControllerForURL:(const GURL&)url {
+ return [[tab_ webController].nativeProvider hasControllerForURL:url];
+}
+
+// Override the CRWNativeContentProvider methods to cancel any prerenders that
+// require native content.
+- (id<CRWNativeContent>)controllerForURL:(const GURL&)url {
+ [self schedulePrerenderCancel];
+ return nil;
+}
+
+// Override the CRWNativeContentProvider methods to cancel any prerenders that
+// require native content.
+- (id<CRWNativeContent>)controllerForURL:(const GURL&)url
+ withError:(NSError*)error
+ isPost:(BOOL)isPost {
+ [self schedulePrerenderCancel];
+ return nil;
+}
+
+#pragma mark -
+#pragma mark Private Methods
+
+- (BOOL)isPrerenderingEnabled {
+ DCHECK_CURRENTLY_ON(web::WebThread::UI);
+ return !IsPrerenderTabEvictionExperimentalGroup() && enabled_ &&
+ !ios::device_util::IsSingleCoreDevice() &&
+ ios::device_util::RamIsAtLeast512Mb() && (!wifiOnly_ || !usingWWAN_);
+}
+
+- (BOOL)isPrefetchingEnabled {
+ DCHECK_CURRENTLY_ON(web::WebThread::UI);
+ return enabled_ && (!wifiOnly_ || !usingWWAN_);
+}
+
+- (BOOL)shouldPreloadURL:(const GURL&)url {
+ return url.SchemeIs(url::kHttpScheme) || url.SchemeIs(url::kHttpsScheme);
+}
+
+- (void)startPrerender {
+ // Destroy any existing prerenders before starting a new one.
+ [self destroyPreviewContents];
+ prerenderedURL_ = scheduledURL_;
+ scheduledURL_ = GURL();
+
+ DCHECK(prerenderedURL_.is_valid());
+ if (!prerenderedURL_.is_valid()) {
+ [self destroyPreviewContents];
+ return;
+ }
+
+ Tab* tab = [Tab
+ newPreloadingTabWithBrowserState:browserState_
+ url:prerenderedURL_
+ referrer:scheduledReferrer_
+ transition:scheduledTransition_
+ provider:self
+ opener:nil
+ desktopUserAgent:[delegate_ shouldUseDesktopUserAgent]
+ configuration:^(Tab* tab) {
+ [tab setIsPrerenderTab:YES];
+ [tab setDelegate:self];
+ }];
+
+ // Create and set up the prerender.
+ tab_.reset([tab retain]);
+
+ // Trigger the page to start loading.
+ [tab_ view];
+}
+
+- (const GURL)urlToPrefetchURL:(const GURL&)url {
+ GURL::Replacements replacements;
+
+ // Add prefetch indicator to query params.
+ std::string query = url.query();
+ if (!query.empty()) {
+ query.append("&");
+ }
+ query.append("pf=i");
+ replacements.SetQueryStr(query);
+
+ return url.ReplaceComponents(replacements);
+}
+
+- (BOOL)hasPrefetchedURL:(const GURL&)url {
+ return prefetchedURL_ == [self urlToPrefetchURL:url];
+}
+
+- (void)cancelPrefetch {
+ prefetcher_.reset();
+ prefetchedURL_ = GURL();
+}
+
+- (void)prefetchDidComplete:(const net::URLFetcher*)source {
+ if (source) {
+ DLOG_IF(WARNING, source->GetResponseCode() != 200)
+ << "Prefetching URL got response code " << source->GetResponseCode();
+ }
+ prefetcher_.reset();
+}
+
+- (void)destroyPreviewContents {
+ [self destroyPreviewContentsForReason:PRERENDER_FINAL_STATUS_CANCELLED];
+}
+
+- (void)destroyPreviewContentsForReason:(PrerenderFinalStatus)reason {
+ if (!tab_.get())
+ return;
+
+ UMA_HISTOGRAM_ENUMERATION(kPrerenderFinalStatusHistogramName, reason,
+ PRERENDER_FINAL_STATUS_MAX);
+ [[tab_ webController] setNativeProvider:nil];
+ [tab_ setDelegate:nil];
+ [tab_ close];
+ tab_.reset();
+ prerenderedURL_ = GURL();
+}
+
+- (void)schedulePrerenderCancel {
+ // TODO(rohitrao): Instead of cancelling the prerender, should we mark it as
+ // failed instead? That way, subsequent prerender requests for the same URL
+ // will not kick off new prerenders. b/5944421
+ [self removeScheduledPrerenderRequests];
+ [self performSelector:@selector(cancelPrerender) withObject:nil afterDelay:0];
+}
+
+- (void)removeScheduledPrerenderRequests {
+ [NSObject cancelPreviousPerformRequestsWithTarget:self];
+ scheduledURL_ = GURL();
+}
+
+#pragma mark - TabDelegate
+
+- (void)discardPrerender {
+ [self schedulePrerenderCancel];
+}
+
+@end
« 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