root/chrome/browser/chromeos/login/merge_session_throttle.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. Get
  2. resource_type_
  3. WillStartRequest
  4. GetNameForLogging
  5. AreAllSessionMergedAlready
  6. OnBlockingPageComplete
  7. ShouldDelayUrl
  8. BlockProfile
  9. UnblockProfile
  10. ShouldDelayRequest
  11. DeleayResourceLoadingOnUIThread

// Copyright (c) 2013 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.

#include "chrome/browser/chromeos/login/merge_session_throttle.h"

#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_util.h"
#include "base/threading/non_thread_safe.h"
#include "base/time/time.h"
#include "chrome/browser/chromeos/login/merge_session_load_page.h"
#include "chrome/browser/chromeos/login/merge_session_xhr_request_waiter.h"
#include "chrome/browser/chromeos/login/oauth2_login_manager.h"
#include "chrome/browser/chromeos/login/oauth2_login_manager_factory.h"
#include "chrome/browser/chromeos/login/user_manager.h"
#include "chrome/browser/chromeos/login/user_manager.h"
#include "chrome/browser/google/google_util.h"
#include "chrome/browser/net/chrome_url_request_context.h"
#include "chrome/common/url_constants.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/resource_controller.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/browser/web_contents.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
#include "net/base/network_change_notifier.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"

using content::BrowserThread;
using content::RenderViewHost;
using content::WebContents;

namespace {

const int64 kMaxSessionRestoreTimeInSec = 60;

// The set of blocked profiles.
class ProfileSet : public base::NonThreadSafe,
                   public std::set<Profile*> {
 public:
  ProfileSet() {
  }

  virtual ~ProfileSet() {
  }

  static ProfileSet* Get();

 private:
  friend struct ::base::DefaultLazyInstanceTraits<ProfileSet>;

  DISALLOW_COPY_AND_ASSIGN(ProfileSet);
};

// Set of all of profiles for which restore session is in progress.
// This static member is accessible only form UI thread.
static base::LazyInstance<ProfileSet> g_blocked_profiles =
    LAZY_INSTANCE_INITIALIZER;

ProfileSet* ProfileSet::Get() {
  return g_blocked_profiles.Pointer();
}

}  // namespace

base::AtomicRefCount MergeSessionThrottle::all_profiles_restored_(0);

MergeSessionThrottle::MergeSessionThrottle(net::URLRequest* request,
                                           ResourceType::Type resource_type)
    : request_(request),
      resource_type_(resource_type) {
}

MergeSessionThrottle::~MergeSessionThrottle() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
}

void MergeSessionThrottle::WillStartRequest(bool* defer) {
  if (!ShouldDelayUrl(request_->url()))
    return;

  DVLOG(1) << "WillStartRequest: defer " << request_->url();
  const content::ResourceRequestInfo* info =
    content::ResourceRequestInfo::ForRequest(request_);
  BrowserThread::PostTask(
      BrowserThread::UI,
      FROM_HERE,
      base::Bind(
          &MergeSessionThrottle::DeleayResourceLoadingOnUIThread,
          resource_type_,
          info->GetChildID(),
          info->GetRouteID(),
          request_->url(),
          base::Bind(
              &MergeSessionThrottle::OnBlockingPageComplete,
              AsWeakPtr())));
  *defer = true;
}

const char* MergeSessionThrottle::GetNameForLogging() const {
  return "MergeSessionThrottle";
}

// static.
bool MergeSessionThrottle::AreAllSessionMergedAlready() {
  return !base::AtomicRefCountIsZero(&all_profiles_restored_);
}

void MergeSessionThrottle::OnBlockingPageComplete() {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
  controller()->Resume();
}

bool MergeSessionThrottle::ShouldDelayUrl(const GURL& url) const {
  // If we are loading google properties while merge session is in progress,
  // we will show delayed loading page instead.
  return !net::NetworkChangeNotifier::IsOffline() &&
         !AreAllSessionMergedAlready() &&
         google_util::IsGoogleHostname(url.host(),
                                       google_util::ALLOW_SUBDOMAIN);
}

// static
void MergeSessionThrottle::BlockProfile(Profile* profile) {
  // Add a new profile to the list of those that we are currently blocking
  // blocking page loading for.
  if (ProfileSet::Get()->find(profile) == ProfileSet::Get()->end()) {
    DVLOG(1) << "Blocking profile " << profile;
    ProfileSet::Get()->insert(profile);

    // Since a new profile just got blocked, we can not assume that
    // all sessions are merged anymore.
    if (AreAllSessionMergedAlready()) {
      base::AtomicRefCountDec(&all_profiles_restored_);
      DVLOG(1) << "Marking all sessions unmerged!";
    }
  }
}

// static
void MergeSessionThrottle::UnblockProfile(Profile* profile) {
  // Have we blocked loading of pages for this this profile
  // before?
  DVLOG(1) << "Unblocking profile " << profile;
  ProfileSet::Get()->erase(profile);

  // Check if there is any other profile to block on.
  if (ProfileSet::Get()->size() == 0) {
    base::AtomicRefCountInc(&all_profiles_restored_);
    DVLOG(1) << "All profiles merged " << all_profiles_restored_;
  }
}

// static
bool MergeSessionThrottle::ShouldDelayRequest(
    int render_process_id,
    int render_view_id) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  if (!chromeos::UserManager::Get()->IsUserLoggedIn()) {
    return false;
  } else if (!chromeos::UserManager::Get()->IsLoggedInAsRegularUser()) {
    // This is not a regular user session, let's remove the throttle
    // permanently.
    if (!AreAllSessionMergedAlready())
      base::AtomicRefCountInc(&all_profiles_restored_);

    return false;
  }

  RenderViewHost* render_view_host =
      RenderViewHost::FromID(render_process_id, render_view_id);
  if (!render_view_host)
    return false;

  WebContents* web_contents =
      WebContents::FromRenderViewHost(render_view_host);
  if (!web_contents)
    return false;

  content::BrowserContext* browser_context =
      web_contents->GetBrowserContext();
  if (!browser_context)
    return false;

  Profile* profile = Profile::FromBrowserContext(browser_context);
  if (!profile)
    return false;

  chromeos::OAuth2LoginManager* login_manager =
      chromeos::OAuth2LoginManagerFactory::GetInstance()->GetForProfile(
          profile);
  if (!login_manager)
    return false;

  switch (login_manager->state()) {
    case chromeos::OAuth2LoginManager::SESSION_RESTORE_NOT_STARTED:
      // The session restore for this profile hasn't even started yet. Don't
      // block for now.
      // In theory this should not happen since we should
      // kick off the session restore process for the newly added profile
      // before we attempt loading any page.
      if (chromeos::UserManager::Get()->IsLoggedInAsRegularUser() &&
          !chromeos::UserManager::Get()->IsLoggedInAsStub()) {
        LOG(WARNING) << "Loading content for a profile without "
                     << "session restore?";
      }
      return false;
    case chromeos::OAuth2LoginManager::SESSION_RESTORE_PREPARING:
    case chromeos::OAuth2LoginManager::SESSION_RESTORE_IN_PROGRESS: {
      // Check if the session restore has been going on for a while already.
      // If so, don't attempt to block page loading.
      if ((base::Time::Now() -
           login_manager->session_restore_start()).InSeconds() >
               kMaxSessionRestoreTimeInSec) {
        UnblockProfile(profile);
        return false;
      }

      // Add a new profile to the list of those that we are currently blocking
      // blocking page loading for.
      BlockProfile(profile);
      return true;
    }
    case chromeos::OAuth2LoginManager::SESSION_RESTORE_DONE:
    case chromeos::OAuth2LoginManager::SESSION_RESTORE_FAILED:
    case chromeos::OAuth2LoginManager::SESSION_RESTORE_CONNECTION_FAILED: {
      UnblockProfile(profile);
      return false;
    }
  }

  NOTREACHED();
  return false;
}

// static.
void MergeSessionThrottle::DeleayResourceLoadingOnUIThread(
    ResourceType::Type resource_type,
    int render_process_id,
    int render_view_id,
    const GURL& url,
    const CompletionCallback& callback) {
  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));

  if (ShouldDelayRequest(render_process_id, render_view_id)) {
    // There is a chance that the tab closed after we decided to show
    // the offline page on the IO thread and before we actually show the
    // offline page here on the UI thread.
    RenderViewHost* render_view_host =
        RenderViewHost::FromID(render_process_id, render_view_id);
    WebContents* web_contents = render_view_host ?
        WebContents::FromRenderViewHost(render_view_host) : NULL;
    if (resource_type == ResourceType::MAIN_FRAME) {
      DVLOG(1) << "Creating page waiter for " << url.spec();
      (new chromeos::MergeSessionLoadPage(web_contents, url, callback))->Show();
    } else {
      DVLOG(1) << "Creating XHR waiter for " << url.spec();
      DCHECK(resource_type == ResourceType::XHR);
      Profile* profile = Profile::FromBrowserContext(
          web_contents->GetBrowserContext());
      (new chromeos::MergeSessionXHRRequestWaiter(profile,
                                                  callback))->StartWaiting();
    }
  } else {
    BrowserThread::PostTask(
        BrowserThread::IO, FROM_HERE, callback);
  }
}

/* [<][>][^][v][top][bottom][index][help] */