Home | History | Annotate | Download | only in signin
      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 
     33 using content::BrowserThread;
     34 using content::RenderViewHost;
     35 using content::ResourceType;
     36 using content::WebContents;
     37 
     38 namespace {
     39 
     40 const int64 kMaxSessionRestoreTimeInSec = 60;
     41 
     42 // The set of blocked profiles.
     43 class 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.
     62 static base::LazyInstance<ProfileSet> g_blocked_profiles =
     63     LAZY_INSTANCE_INITIALIZER;
     64 
     65 ProfileSet* ProfileSet::Get() {
     66   return g_blocked_profiles.Pointer();
     67 }
     68 
     69 }  // namespace
     70 
     71 base::AtomicRefCount MergeSessionThrottle::all_profiles_restored_(0);
     72 
     73 MergeSessionThrottle::MergeSessionThrottle(net::URLRequest* request,
     74                                            ResourceType resource_type)
     75     : request_(request),
     76       resource_type_(resource_type) {
     77 }
     78 
     79 MergeSessionThrottle::~MergeSessionThrottle() {
     80   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
     81 }
     82 
     83 void 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 
    105 const char* MergeSessionThrottle::GetNameForLogging() const {
    106   return "MergeSessionThrottle";
    107 }
    108 
    109 // static.
    110 bool MergeSessionThrottle::AreAllSessionMergedAlready() {
    111   return !base::AtomicRefCountIsZero(&all_profiles_restored_);
    112 }
    113 
    114 void MergeSessionThrottle::OnBlockingPageComplete() {
    115   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    116   controller()->Resume();
    117 }
    118 
    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);
    126 }
    127 
    128 // static
    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);
    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
    146 void 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
    160 bool 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.
    243 void 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