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.
5 #include "chrome/browser/chromeos/login/signin/merge_session_throttle.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"
33 using content::BrowserThread;
34 using content::RenderViewHost;
35 using content::ResourceType;
36 using content::WebContents;
40 const int64 kMaxSessionRestoreTimeInSec = 60;
42 // The set of blocked profiles.
43 class ProfileSet : public base::NonThreadSafe,
44 public std::set<Profile*> {
49 virtual ~ProfileSet() {
52 static ProfileSet* Get();
55 friend struct ::base::DefaultLazyInstanceTraits<ProfileSet>;
57 DISALLOW_COPY_AND_ASSIGN(ProfileSet);
60 // Set of all of profiles for which restore session is in progress.
61 // This static member is accessible only form UI thread.
62 static base::LazyInstance<ProfileSet> g_blocked_profiles =
63 LAZY_INSTANCE_INITIALIZER;
65 ProfileSet* ProfileSet::Get() {
66 return g_blocked_profiles.Pointer();
71 base::AtomicRefCount MergeSessionThrottle::all_profiles_restored_(0);
73 MergeSessionThrottle::MergeSessionThrottle(net::URLRequest* request,
74 ResourceType resource_type)
76 resource_type_(resource_type) {
79 MergeSessionThrottle::~MergeSessionThrottle() {
80 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
83 void MergeSessionThrottle::WillStartRequest(bool* defer) {
84 if (!ShouldDelayUrl(request_->url()))
87 DVLOG(1) << "WillStartRequest: defer " << request_->url();
88 const content::ResourceRequestInfo* info =
89 content::ResourceRequestInfo::ForRequest(request_);
90 BrowserThread::PostTask(
94 &MergeSessionThrottle::DeleayResourceLoadingOnUIThread,
100 &MergeSessionThrottle::OnBlockingPageComplete,
105 const char* MergeSessionThrottle::GetNameForLogging() const {
106 return "MergeSessionThrottle";
110 bool MergeSessionThrottle::AreAllSessionMergedAlready() {
111 return !base::AtomicRefCountIsZero(&all_profiles_restored_);
114 void MergeSessionThrottle::OnBlockingPageComplete() {
115 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
116 controller()->Resume();
119 bool 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);
129 void 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);
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!";
146 void MergeSessionThrottle::UnblockProfile(Profile* profile) {
147 // Have we blocked loading of pages for this this profile
149 DVLOG(1) << "Unblocking profile " << profile;
150 ProfileSet::Get()->erase(profile);
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_;
160 bool MergeSessionThrottle::ShouldDelayRequest(
161 int render_process_id,
162 int render_view_id) {
163 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
165 if (!user_manager::UserManager::Get()->IsUserLoggedIn()) {
167 } else if (!user_manager::UserManager::Get()->IsLoggedInAsRegularUser()) {
168 // This is not a regular user session, let's remove the throttle
170 if (!AreAllSessionMergedAlready())
171 base::AtomicRefCountInc(&all_profiles_restored_);
176 RenderViewHost* render_view_host =
177 RenderViewHost::FromID(render_process_id, render_view_id);
178 if (!render_view_host)
181 WebContents* web_contents =
182 WebContents::FromRenderViewHost(render_view_host);
186 content::BrowserContext* browser_context =
187 web_contents->GetBrowserContext();
188 if (!browser_context)
191 Profile* profile = Profile::FromBrowserContext(browser_context);
195 chromeos::OAuth2LoginManager* login_manager =
196 chromeos::OAuth2LoginManagerFactory::GetInstance()->GetForProfile(
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
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?";
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);
225 // Add a new profile to the list of those that we are currently blocking
226 // blocking page loading for.
227 BlockProfile(profile);
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);
243 void MergeSessionThrottle::DeleayResourceLoadingOnUIThread(
244 ResourceType resource_type,
245 int render_process_id,
248 const CompletionCallback& callback) {
249 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
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();
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();
271 BrowserThread::PostTask(
272 BrowserThread::IO, FROM_HERE, callback);