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