1// Copyright 2014 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#include "chrome/browser/chromeos/login/signin/merge_session_throttle.h" 6 7#include "base/bind.h" 8#include "base/lazy_instance.h" 9#include "base/logging.h" 10#include "base/memory/singleton.h" 11#include "base/metrics/histogram.h" 12#include "base/strings/string_util.h" 13#include "base/threading/non_thread_safe.h" 14#include "base/time/time.h" 15#include "chrome/browser/chromeos/login/signin/merge_session_load_page.h" 16#include "chrome/browser/chromeos/login/signin/merge_session_xhr_request_waiter.h" 17#include "chrome/browser/chromeos/login/signin/oauth2_login_manager.h" 18#include "chrome/browser/chromeos/login/signin/oauth2_login_manager_factory.h" 19#include "chrome/common/url_constants.h" 20#include "components/google/core/browser/google_util.h" 21#include "components/user_manager/user_manager.h" 22#include "content/public/browser/browser_thread.h" 23#include "content/public/browser/render_view_host.h" 24#include "content/public/browser/resource_controller.h" 25#include "content/public/browser/resource_request_info.h" 26#include "content/public/browser/web_contents.h" 27#include "net/base/net_errors.h" 28#include "net/base/net_util.h" 29#include "net/base/network_change_notifier.h" 30#include "net/url_request/url_request.h" 31#include "net/url_request/url_request_context.h" 32 33using content::BrowserThread; 34using content::RenderViewHost; 35using content::ResourceType; 36using content::WebContents; 37 38namespace { 39 40const int64 kMaxSessionRestoreTimeInSec = 60; 41 42// The set of blocked profiles. 43class ProfileSet : public base::NonThreadSafe, 44 public std::set<Profile*> { 45 public: 46 ProfileSet() { 47 } 48 49 virtual ~ProfileSet() { 50 } 51 52 static ProfileSet* Get(); 53 54 private: 55 friend struct ::base::DefaultLazyInstanceTraits<ProfileSet>; 56 57 DISALLOW_COPY_AND_ASSIGN(ProfileSet); 58}; 59 60// Set of all of profiles for which restore session is in progress. 61// This static member is accessible only form UI thread. 62static base::LazyInstance<ProfileSet> g_blocked_profiles = 63 LAZY_INSTANCE_INITIALIZER; 64 65ProfileSet* ProfileSet::Get() { 66 return g_blocked_profiles.Pointer(); 67} 68 69} // namespace 70 71base::AtomicRefCount MergeSessionThrottle::all_profiles_restored_(0); 72 73MergeSessionThrottle::MergeSessionThrottle(net::URLRequest* request, 74 ResourceType resource_type) 75 : request_(request), 76 resource_type_(resource_type) { 77} 78 79MergeSessionThrottle::~MergeSessionThrottle() { 80 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 81} 82 83void MergeSessionThrottle::WillStartRequest(bool* defer) { 84 if (!ShouldDelayUrl(request_->url())) 85 return; 86 87 DVLOG(1) << "WillStartRequest: defer " << request_->url(); 88 const content::ResourceRequestInfo* info = 89 content::ResourceRequestInfo::ForRequest(request_); 90 BrowserThread::PostTask( 91 BrowserThread::UI, 92 FROM_HERE, 93 base::Bind( 94 &MergeSessionThrottle::DeleayResourceLoadingOnUIThread, 95 resource_type_, 96 info->GetChildID(), 97 info->GetRouteID(), 98 request_->url(), 99 base::Bind( 100 &MergeSessionThrottle::OnBlockingPageComplete, 101 AsWeakPtr()))); 102 *defer = true; 103} 104 105const char* MergeSessionThrottle::GetNameForLogging() const { 106 return "MergeSessionThrottle"; 107} 108 109// static. 110bool MergeSessionThrottle::AreAllSessionMergedAlready() { 111 return !base::AtomicRefCountIsZero(&all_profiles_restored_); 112} 113 114void MergeSessionThrottle::OnBlockingPageComplete() { 115 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 116 controller()->Resume(); 117} 118 119bool MergeSessionThrottle::ShouldDelayUrl(const GURL& url) const { 120 // If we are loading google properties while merge session is in progress, 121 // we will show delayed loading page instead. 122 return !net::NetworkChangeNotifier::IsOffline() && 123 !AreAllSessionMergedAlready() && 124 google_util::IsGoogleHostname(url.host(), 125 google_util::ALLOW_SUBDOMAIN); 126} 127 128// static 129void MergeSessionThrottle::BlockProfile(Profile* profile) { 130 // Add a new profile to the list of those that we are currently blocking 131 // blocking page loading for. 132 if (ProfileSet::Get()->find(profile) == ProfileSet::Get()->end()) { 133 DVLOG(1) << "Blocking profile " << profile; 134 ProfileSet::Get()->insert(profile); 135 136 // Since a new profile just got blocked, we can not assume that 137 // all sessions are merged anymore. 138 if (AreAllSessionMergedAlready()) { 139 base::AtomicRefCountDec(&all_profiles_restored_); 140 DVLOG(1) << "Marking all sessions unmerged!"; 141 } 142 } 143} 144 145// static 146void MergeSessionThrottle::UnblockProfile(Profile* profile) { 147 // Have we blocked loading of pages for this this profile 148 // before? 149 DVLOG(1) << "Unblocking profile " << profile; 150 ProfileSet::Get()->erase(profile); 151 152 // Check if there is any other profile to block on. 153 if (ProfileSet::Get()->size() == 0) { 154 base::AtomicRefCountInc(&all_profiles_restored_); 155 DVLOG(1) << "All profiles merged " << all_profiles_restored_; 156 } 157} 158 159// static 160bool MergeSessionThrottle::ShouldDelayRequest( 161 int render_process_id, 162 int render_view_id) { 163 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 164 165 if (!user_manager::UserManager::Get()->IsUserLoggedIn()) { 166 return false; 167 } else if (!user_manager::UserManager::Get()->IsLoggedInAsRegularUser()) { 168 // This is not a regular user session, let's remove the throttle 169 // permanently. 170 if (!AreAllSessionMergedAlready()) 171 base::AtomicRefCountInc(&all_profiles_restored_); 172 173 return false; 174 } 175 176 RenderViewHost* render_view_host = 177 RenderViewHost::FromID(render_process_id, render_view_id); 178 if (!render_view_host) 179 return false; 180 181 WebContents* web_contents = 182 WebContents::FromRenderViewHost(render_view_host); 183 if (!web_contents) 184 return false; 185 186 content::BrowserContext* browser_context = 187 web_contents->GetBrowserContext(); 188 if (!browser_context) 189 return false; 190 191 Profile* profile = Profile::FromBrowserContext(browser_context); 192 if (!profile) 193 return false; 194 195 chromeos::OAuth2LoginManager* login_manager = 196 chromeos::OAuth2LoginManagerFactory::GetInstance()->GetForProfile( 197 profile); 198 if (!login_manager) 199 return false; 200 201 switch (login_manager->state()) { 202 case chromeos::OAuth2LoginManager::SESSION_RESTORE_NOT_STARTED: 203 // The session restore for this profile hasn't even started yet. Don't 204 // block for now. 205 // In theory this should not happen since we should 206 // kick off the session restore process for the newly added profile 207 // before we attempt loading any page. 208 if (user_manager::UserManager::Get()->IsLoggedInAsRegularUser() && 209 !user_manager::UserManager::Get()->IsLoggedInAsStub()) { 210 LOG(WARNING) << "Loading content for a profile without " 211 << "session restore?"; 212 } 213 return false; 214 case chromeos::OAuth2LoginManager::SESSION_RESTORE_PREPARING: 215 case chromeos::OAuth2LoginManager::SESSION_RESTORE_IN_PROGRESS: { 216 // Check if the session restore has been going on for a while already. 217 // If so, don't attempt to block page loading. 218 if ((base::Time::Now() - 219 login_manager->session_restore_start()).InSeconds() > 220 kMaxSessionRestoreTimeInSec) { 221 UnblockProfile(profile); 222 return false; 223 } 224 225 // Add a new profile to the list of those that we are currently blocking 226 // blocking page loading for. 227 BlockProfile(profile); 228 return true; 229 } 230 case chromeos::OAuth2LoginManager::SESSION_RESTORE_DONE: 231 case chromeos::OAuth2LoginManager::SESSION_RESTORE_FAILED: 232 case chromeos::OAuth2LoginManager::SESSION_RESTORE_CONNECTION_FAILED: { 233 UnblockProfile(profile); 234 return false; 235 } 236 } 237 238 NOTREACHED(); 239 return false; 240} 241 242// static. 243void MergeSessionThrottle::DeleayResourceLoadingOnUIThread( 244 ResourceType resource_type, 245 int render_process_id, 246 int render_view_id, 247 const GURL& url, 248 const CompletionCallback& callback) { 249 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 250 251 if (ShouldDelayRequest(render_process_id, render_view_id)) { 252 // There is a chance that the tab closed after we decided to show 253 // the offline page on the IO thread and before we actually show the 254 // offline page here on the UI thread. 255 RenderViewHost* render_view_host = 256 RenderViewHost::FromID(render_process_id, render_view_id); 257 WebContents* web_contents = render_view_host ? 258 WebContents::FromRenderViewHost(render_view_host) : NULL; 259 if (resource_type == content::RESOURCE_TYPE_MAIN_FRAME) { 260 DVLOG(1) << "Creating page waiter for " << url.spec(); 261 (new chromeos::MergeSessionLoadPage(web_contents, url, callback))->Show(); 262 } else { 263 DVLOG(1) << "Creating XHR waiter for " << url.spec(); 264 DCHECK(resource_type == content::RESOURCE_TYPE_XHR); 265 Profile* profile = Profile::FromBrowserContext( 266 web_contents->GetBrowserContext()); 267 (new chromeos::MergeSessionXHRRequestWaiter(profile, 268 callback))->StartWaiting(); 269 } 270 } else { 271 BrowserThread::PostTask( 272 BrowserThread::IO, FROM_HERE, callback); 273 } 274} 275