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

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

DEFINITIONS

This source file includes following definitions.
  1. final_state_
  2. WaitForStates
  3. final_state
  4. OnSessionRestoreStateChanged
  5. SetupGaiaServerForNewAccount
  6. SetupGaiaServerForUnexpiredAccount
  7. SetupGaiaServerForExpiredAccount
  8. LoginAsExistingUser
  9. TryToLogin
  10. GetOAuthStatusFromLocalState
  11. profile
  12. AddUserToSession
  13. SetupGaiaServerWithAccessTokens
  14. CheckSessionState
  15. WaitForMergeSessionCompletion
  16. StartNewUserSession
  17. ReadCookies
  18. GetCookieValue
  19. ReadCookiesOnIOThread
  20. OnGetAllCookiesOnUIThread
  21. OnCookiesReadyOnUIThread
  22. IN_PROC_BROWSER_TEST_F
  23. IN_PROC_BROWSER_TEST_F
  24. IN_PROC_BROWSER_TEST_F
  25. IN_PROC_BROWSER_TEST_F
  26. HandleRequest
  27. IsPageRequested
  28. WaitForPageRequest
  29. QuitRunnerOnUIThread
  30. start_event_
  31. UnblockMergeSession
  32. WaitForMergeSessionToStart
  33. HandleMergeSession
  34. QuitRunnerOnUIThread
  35. SetUpCommandLine
  36. SetUp
  37. UnblockMergeSession
  38. WaitForMergeSessionToStart
  39. JsExpect
  40. GetBackGroundPageUrl
  41. JsExpectOnBackgroundPage
  42. FindOrCreateVisibleBrowser
  43. IN_PROC_BROWSER_TEST_F
  44. IN_PROC_BROWSER_TEST_F

// 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 "base/message_loop/message_loop.h"
#include "base/prefs/pref_service.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/waitable_event.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.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/oobe_base_test.h"
#include "chrome/browser/chromeos/login/user_manager.h"
#include "chrome/browser/chromeos/login/wizard_controller.h"
#include "chrome/browser/extensions/extension_test_message_listener.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
#include "chrome/browser/ui/app_modal_dialogs/javascript_app_modal_dialog.h"
#include "chrome/browser/ui/app_modal_dialogs/native_app_modal_dialog.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/scoped_tabbed_browser_displayer.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/signin/core/browser/profile_oauth2_token_service.h"
#include "content/public/browser/notification_service.h"
#include "content/public/test/browser_test_utils.h"
#include "extensions/browser/process_manager.h"
#include "google_apis/gaia/gaia_constants.h"
#include "google_apis/gaia/gaia_urls.h"
#include "net/cookies/canonical_cookie.h"
#include "net/cookies/cookie_monster.h"
#include "net/cookies/cookie_store.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"

using net::test_server::BasicHttpResponse;
using net::test_server::HttpRequest;
using net::test_server::HttpResponse;

namespace chromeos {

namespace {

// Email of owner account for test.
const char kTestAccountId[] = "username@gmail.com";
const char kTestRawAccountId[] = "User.Name";
const char kTestAccountPassword[] = "fake-password";
const char kTestAuthCode[] = "fake-auth-code";
const char kTestGaiaUberToken[] = "fake-uber-token";
const char kTestAuthLoginAccessToken[] = "fake-access-token";
const char kTestRefreshToken[] = "fake-refresh-token";
const char kTestAuthSIDCookie[] = "fake-auth-SID-cookie";
const char kTestAuthLSIDCookie[] = "fake-auth-LSID-cookie";
const char kTestSessionSIDCookie[] = "fake-session-SID-cookie";
const char kTestSessionLSIDCookie[] = "fake-session-LSID-cookie";
const char kTestSession2SIDCookie[] = "fake-session2-SID-cookie";
const char kTestSession2LSIDCookie[] = "fake-session2-LSID-cookie";
const char kTestUserinfoToken[] = "fake-userinfo-token";
const char kTestLoginToken[] = "fake-login-token";
const char kTestSyncToken[] = "fake-sync-token";
const char kTestAuthLoginToken[] = "fake-oauthlogin-token";

class OAuth2LoginManagerStateWaiter : public OAuth2LoginManager::Observer {
 public:
  explicit OAuth2LoginManagerStateWaiter(Profile* profile)
     : profile_(profile),
       waiting_for_state_(false),
       final_state_(OAuth2LoginManager::SESSION_RESTORE_NOT_STARTED) {
  }

  void WaitForStates(
      const std::set<OAuth2LoginManager::SessionRestoreState>& states) {
    DCHECK(!waiting_for_state_);
    OAuth2LoginManager* login_manager =
         OAuth2LoginManagerFactory::GetInstance()->GetForProfile(profile_);
    states_ = states;
    if (states_.find(login_manager->state()) != states_.end()) {
      final_state_ = login_manager->state();
      return;
    }

    waiting_for_state_ = true;
    login_manager->AddObserver(this);
    runner_ = new content::MessageLoopRunner;
    runner_->Run();
    login_manager->RemoveObserver(this);
  }

  OAuth2LoginManager::SessionRestoreState final_state() { return final_state_; }

 private:
  // OAuth2LoginManager::Observer overrides.
  virtual void OnSessionRestoreStateChanged(
      Profile* user_profile,
      OAuth2LoginManager::SessionRestoreState state) OVERRIDE {
    if (!waiting_for_state_)
      return;

    if (states_.find(state) == states_.end())
      return;

    final_state_ = state;
    waiting_for_state_ = false;
    runner_->Quit();
  }

  Profile* profile_;
  std::set<OAuth2LoginManager::SessionRestoreState> states_;
  bool waiting_for_state_;
  OAuth2LoginManager::SessionRestoreState final_state_;
  scoped_refptr<content::MessageLoopRunner> runner_;

  DISALLOW_COPY_AND_ASSIGN(OAuth2LoginManagerStateWaiter);
};

}  // namespace

class OAuth2Test : public OobeBaseTest {
 protected:
  OAuth2Test() {}

  void SetupGaiaServerForNewAccount() {
    FakeGaia::MergeSessionParams params;
    params.auth_sid_cookie = kTestAuthSIDCookie;
    params.auth_lsid_cookie = kTestAuthLSIDCookie;
    params.auth_code = kTestAuthCode;
    params.refresh_token = kTestRefreshToken;
    params.access_token = kTestAuthLoginAccessToken;
    params.gaia_uber_token = kTestGaiaUberToken;
    params.session_sid_cookie = kTestSessionSIDCookie;
    params.session_lsid_cookie = kTestSessionLSIDCookie;
    fake_gaia_->SetMergeSessionParams(params);
    SetupGaiaServerWithAccessTokens();
  }

  void SetupGaiaServerForUnexpiredAccount() {
    FakeGaia::MergeSessionParams params;
    params.email = kTestAccountId;
    fake_gaia_->SetMergeSessionParams(params);
    SetupGaiaServerWithAccessTokens();
  }

  void SetupGaiaServerForExpiredAccount() {
    FakeGaia::MergeSessionParams params;
    params.gaia_uber_token = kTestGaiaUberToken;
    params.session_sid_cookie = kTestSession2SIDCookie;
    params.session_lsid_cookie = kTestSession2LSIDCookie;
    fake_gaia_->SetMergeSessionParams(params);
    SetupGaiaServerWithAccessTokens();
  }

  void LoginAsExistingUser() {
    content::WindowedNotificationObserver(
      chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
      content::NotificationService::AllSources()).Wait();

    JsExpect("!!document.querySelector('#account-picker')");
    JsExpect("!!document.querySelector('#pod-row')");

    EXPECT_EQ(GetOAuthStatusFromLocalState(kTestAccountId),
              User::OAUTH2_TOKEN_STATUS_VALID);

    EXPECT_TRUE(TryToLogin(kTestAccountId, kTestAccountPassword));
    Profile* profile = ProfileManager::GetPrimaryUserProfile();

    // Wait for the session merge to finish.
    WaitForMergeSessionCompletion(OAuth2LoginManager::SESSION_RESTORE_DONE);

    // Check for existance of refresh token.
    ProfileOAuth2TokenService* token_service =
          ProfileOAuth2TokenServiceFactory::GetForProfile(profile);
    EXPECT_TRUE(token_service->RefreshTokenIsAvailable(kTestAccountId));

    EXPECT_EQ(GetOAuthStatusFromLocalState(kTestAccountId),
              User::OAUTH2_TOKEN_STATUS_VALID);
  }

  bool TryToLogin(const std::string& username,
                  const std::string& password) {
    if (!AddUserToSession(username, password))
      return false;

    if (const User* active_user = UserManager::Get()->GetActiveUser())
      return active_user->email() == username;

    return false;
  }

  User::OAuthTokenStatus GetOAuthStatusFromLocalState(
      const std::string& user_id) const {
    PrefService* local_state = g_browser_process->local_state();
    const base::DictionaryValue* prefs_oauth_status =
        local_state->GetDictionary("OAuthTokenStatus");
    int oauth_token_status = User::OAUTH_TOKEN_STATUS_UNKNOWN;
    if (prefs_oauth_status &&
        prefs_oauth_status->GetIntegerWithoutPathExpansion(
            user_id, &oauth_token_status)) {
      User::OAuthTokenStatus result =
          static_cast<User::OAuthTokenStatus>(oauth_token_status);
      return result;
    }
    return User::OAUTH_TOKEN_STATUS_UNKNOWN;
  }

 protected:
  // OobeBaseTest overrides.
  virtual Profile* profile() OVERRIDE {
    if (UserManager::Get()->GetActiveUser())
      return ProfileManager::GetPrimaryUserProfile();

    return OobeBaseTest::profile();
  }

  bool AddUserToSession(const std::string& username,
                        const std::string& password) {
    ExistingUserController* controller =
        ExistingUserController::current_controller();
    if (!controller) {
      ADD_FAILURE();
      return false;
    }

    controller->Login(UserContext(username, password, std::string()));
    content::WindowedNotificationObserver(
        chrome::NOTIFICATION_SESSION_STARTED,
        content::NotificationService::AllSources()).Wait();
    const UserList& logged_users = UserManager::Get()->GetLoggedInUsers();
    for (UserList::const_iterator it = logged_users.begin();
         it != logged_users.end(); ++it) {
      if ((*it)->email() == username)
        return true;
    }
    return false;
  }

  void SetupGaiaServerWithAccessTokens() {
    // Configure OAuth authentication.
    GaiaUrls* gaia_urls = GaiaUrls::GetInstance();

    // This token satisfies the userinfo.email request from
    // DeviceOAuth2TokenService used in token validation.
    FakeGaia::AccessTokenInfo userinfo_token_info;
    userinfo_token_info.token = kTestUserinfoToken;
    userinfo_token_info.scopes.insert(
        "https://www.googleapis.com/auth/userinfo.email");
    userinfo_token_info.audience = gaia_urls->oauth2_chrome_client_id();
    userinfo_token_info.email = kTestAccountId;
    fake_gaia_->IssueOAuthToken(kTestRefreshToken, userinfo_token_info);

    FakeGaia::AccessTokenInfo userinfo_profile_token_info;
    userinfo_profile_token_info.token = kTestUserinfoToken;
    userinfo_profile_token_info.scopes.insert(
        "https://www.googleapis.com/auth/userinfo.profile");
    userinfo_profile_token_info.audience = gaia_urls->oauth2_chrome_client_id();
    userinfo_profile_token_info.email = kTestAccountId;
    fake_gaia_->IssueOAuthToken(kTestRefreshToken, userinfo_profile_token_info);

    // The any-api access token for accessing the token minting endpoint.
    FakeGaia::AccessTokenInfo login_token_info;
    login_token_info.token = kTestLoginToken;
    login_token_info.scopes.insert(GaiaConstants::kAnyApiOAuth2Scope);
    login_token_info.audience = gaia_urls->oauth2_chrome_client_id();
    fake_gaia_->IssueOAuthToken(kTestRefreshToken, login_token_info);

    // The /auth/chromesync access token for accessing sync endpoint.
    FakeGaia::AccessTokenInfo sync_token_info;
    sync_token_info.token = kTestSyncToken;
    sync_token_info.scopes.insert(GaiaConstants::kChromeSyncOAuth2Scope);
    sync_token_info.audience = gaia_urls->oauth2_chrome_client_id();
    fake_gaia_->IssueOAuthToken(kTestRefreshToken, sync_token_info);

    FakeGaia::AccessTokenInfo auth_login_token_info;
    auth_login_token_info.token = kTestAuthLoginToken;
    auth_login_token_info.scopes.insert(GaiaConstants::kOAuth1LoginScope);
    auth_login_token_info.audience = gaia_urls->oauth2_chrome_client_id();
    fake_gaia_->IssueOAuthToken(kTestRefreshToken, auth_login_token_info);
  }

  void CheckSessionState(OAuth2LoginManager::SessionRestoreState state) {
    OAuth2LoginManager* login_manager =
         OAuth2LoginManagerFactory::GetInstance()->GetForProfile(
             profile());
    ASSERT_EQ(state, login_manager->state());
  }

  void WaitForMergeSessionCompletion(
      OAuth2LoginManager::SessionRestoreState final_state) {
    // Wait for the session merge to finish.
    std::set<OAuth2LoginManager::SessionRestoreState> states;
    states.insert(OAuth2LoginManager::SESSION_RESTORE_DONE);
    states.insert(OAuth2LoginManager::SESSION_RESTORE_FAILED);
    states.insert(OAuth2LoginManager::SESSION_RESTORE_CONNECTION_FAILED);
    OAuth2LoginManagerStateWaiter merge_session_waiter(profile());
    merge_session_waiter.WaitForStates(states);
    EXPECT_EQ(merge_session_waiter.final_state(), final_state);
  }

  void StartNewUserSession(bool wait_for_merge) {
    SetupGaiaServerForNewAccount();
    SimulateNetworkOnline();
    chromeos::WizardController::SkipPostLoginScreensForTesting();
    chromeos::WizardController* wizard_controller =
        chromeos::WizardController::default_controller();
    wizard_controller->SkipToLoginForTesting(LoginScreenContext());

    content::WindowedNotificationObserver(
      chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
      content::NotificationService::AllSources()).Wait();

    // Use capitalized and dotted user name on purpose to make sure
    // our email normalization kicks in.
    GetLoginDisplay()->ShowSigninScreenForCreds(kTestRawAccountId,
                                                kTestAccountPassword);

    content::WindowedNotificationObserver(
      chrome::NOTIFICATION_SESSION_STARTED,
      content::NotificationService::AllSources()).Wait();

    if (wait_for_merge) {
      // Wait for the session merge to finish.
      WaitForMergeSessionCompletion(OAuth2LoginManager::SESSION_RESTORE_DONE);
    }
}

  DISALLOW_COPY_AND_ASSIGN(OAuth2Test);
};

class CookieReader : public base::RefCountedThreadSafe<CookieReader> {
 public:
  CookieReader() {
  }

  void ReadCookies(Profile* profile) {
    context_ = profile->GetRequestContext();
    content::BrowserThread::PostTask(
        content::BrowserThread::IO, FROM_HERE,
        base::Bind(&CookieReader::ReadCookiesOnIOThread,
                   this));
    runner_ = new content::MessageLoopRunner;
    runner_->Run();
  }

  std::string GetCookieValue(const std::string& name) {
    for (std::vector<net::CanonicalCookie>::const_iterator iter =
             cookie_list_.begin();
        iter != cookie_list_.end();
        ++iter) {
      if (iter->Name() == name) {
        return iter->Value();
      }
    }
    return std::string();
  }

 private:
  friend class base::RefCountedThreadSafe<CookieReader>;

  virtual ~CookieReader() {
  }

  void ReadCookiesOnIOThread() {
    context_->GetURLRequestContext()->cookie_store()->GetCookieMonster()->
        GetAllCookiesAsync(base::Bind(
            &CookieReader::OnGetAllCookiesOnUIThread,
            this));
  }

  void OnGetAllCookiesOnUIThread(const net::CookieList& cookies) {
    cookie_list_ = cookies;
    content::BrowserThread::PostTask(
        content::BrowserThread::UI, FROM_HERE,
        base::Bind(&CookieReader::OnCookiesReadyOnUIThread,
                   this));
  }

  void OnCookiesReadyOnUIThread() {
    runner_->Quit();
  }

  scoped_refptr<net::URLRequestContextGetter> context_;
  net::CookieList cookie_list_;
  scoped_refptr<content::MessageLoopRunner> runner_;

  DISALLOW_COPY_AND_ASSIGN(CookieReader);
};

// PRE_MergeSession is testing merge session for a new profile.
IN_PROC_BROWSER_TEST_F(OAuth2Test, PRE_PRE_PRE_MergeSession) {
  StartNewUserSession(true);
  // Check for existance of refresh token.
  ProfileOAuth2TokenService* token_service =
        ProfileOAuth2TokenServiceFactory::GetForProfile(
            profile());
  EXPECT_TRUE(token_service->RefreshTokenIsAvailable(kTestAccountId));

  EXPECT_EQ(GetOAuthStatusFromLocalState(kTestAccountId),
            User::OAUTH2_TOKEN_STATUS_VALID);

  scoped_refptr<CookieReader> cookie_reader(new CookieReader());
  cookie_reader->ReadCookies(profile());
  EXPECT_EQ(cookie_reader->GetCookieValue("SID"), kTestSessionSIDCookie);
  EXPECT_EQ(cookie_reader->GetCookieValue("LSID"), kTestSessionLSIDCookie);
}

// MergeSession test is running merge session process for an existing profile
// that was generated in PRE_PRE_PRE_MergeSession test. In this test, we
// are not running /MergeSession process since the /ListAccounts call confirms
// that the session is not stale.
IN_PROC_BROWSER_TEST_F(OAuth2Test, PRE_PRE_MergeSession) {
  SetupGaiaServerForUnexpiredAccount();
  SimulateNetworkOnline();
  LoginAsExistingUser();
  scoped_refptr<CookieReader> cookie_reader(new CookieReader());
  cookie_reader->ReadCookies(profile());
  // These are still cookie values form the initial session since
  // /ListAccounts
  EXPECT_EQ(cookie_reader->GetCookieValue("SID"), kTestSessionSIDCookie);
  EXPECT_EQ(cookie_reader->GetCookieValue("LSID"), kTestSessionLSIDCookie);
}

// MergeSession test is running merge session process for an existing profile
// that was generated in PRE_PRE_MergeSession test.
IN_PROC_BROWSER_TEST_F(OAuth2Test, PRE_MergeSession) {
  SetupGaiaServerForExpiredAccount();
  SimulateNetworkOnline();
  LoginAsExistingUser();
  scoped_refptr<CookieReader> cookie_reader(new CookieReader());
  cookie_reader->ReadCookies(profile());
  // These should be cookie values that we generated by calling /MergeSession,
  // since /ListAccounts should have tell us that the initial session cookies
  // are stale.
  EXPECT_EQ(cookie_reader->GetCookieValue("SID"), kTestSession2SIDCookie);
  EXPECT_EQ(cookie_reader->GetCookieValue("LSID"), kTestSession2LSIDCookie);
}

// MergeSession test is attempting to merge session for an existing profile
// that was generated in PRE_PRE_MergeSession test. This attempt should fail
// since FakeGaia instance isn't configured to return relevant tokens/cookies.
IN_PROC_BROWSER_TEST_F(OAuth2Test, MergeSession) {
  SimulateNetworkOnline();

  content::WindowedNotificationObserver(
    chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
    content::NotificationService::AllSources()).Wait();

  JsExpect("!!document.querySelector('#account-picker')");
  JsExpect("!!document.querySelector('#pod-row')");

  EXPECT_EQ(GetOAuthStatusFromLocalState(kTestAccountId),
            User::OAUTH2_TOKEN_STATUS_VALID);

  EXPECT_TRUE(TryToLogin(kTestAccountId, kTestAccountPassword));

  // Wait for the session merge to finish.
  WaitForMergeSessionCompletion(OAuth2LoginManager::SESSION_RESTORE_FAILED);

  EXPECT_EQ(GetOAuthStatusFromLocalState(kTestAccountId),
            User::OAUTH2_TOKEN_STATUS_INVALID);
}


const char kGooglePageContent[] =
    "<html><title>Hello!</title><script>alert('hello');</script>"
    "<body>Hello Google!</body></html>";
const char kRandomPageContent[] =
    "<html><title>SomthingElse</title><body>I am SomethingElse</body></html>";
const char kHelloPagePath[] = "/hello_google";
const char kRandomPagePath[] = "/non_google_page";


// FakeGoogle serves content of http://www.google.com/hello_google page for
// merge session tests.
class FakeGoogle {
 public:
  FakeGoogle() : start_event_(true, false) {
  }

  ~FakeGoogle() {}

  scoped_ptr<HttpResponse> HandleRequest(const HttpRequest& request) {
    // The scheme and host of the URL is actually not important but required to
    // get a valid GURL in order to parse |request.relative_url|.
    GURL request_url = GURL("http://localhost").Resolve(request.relative_url);
    LOG(WARNING) << "Requesting page " << request.relative_url;
    std::string request_path = request_url.path();
    scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse());
    if (request_path == kHelloPagePath) {  // Serving "google" page.
      start_event_.Signal();
      content::BrowserThread::PostTask(
          content::BrowserThread::UI, FROM_HERE,
          base::Bind(&FakeGoogle::QuitRunnerOnUIThread,
                     base::Unretained(this)));

      http_response->set_code(net::HTTP_OK);
      http_response->set_content_type("text/html");
      http_response->set_content(kGooglePageContent);
    } else if (request_path == kRandomPagePath) {  // Serving "non-google" page.
      http_response->set_code(net::HTTP_OK);
      http_response->set_content_type("text/html");
      http_response->set_content(kRandomPageContent);
    } else {
      return scoped_ptr<HttpResponse>();      // Request not understood.
    }

    return http_response.PassAs<HttpResponse>();
  }

  // True if we have already served the test page.
  bool IsPageRequested () {
    return start_event_.IsSignaled();
  }

  // Waits until we receive a request to serve the test page.
  void WaitForPageRequest() {
    // If we have already served the request, bail out.
    if (start_event_.IsSignaled())
      return;

    runner_ = new content::MessageLoopRunner;
    runner_->Run();
  }

 private:
  void QuitRunnerOnUIThread() {
    if (runner_.get())
      runner_->Quit();
  }
  // This event will tell us when we actually see HTTP request on the server
  // side. It should be signalled only after the page/XHR throttle had been
  // removed (after merge session completes).
  base::WaitableEvent start_event_;
  scoped_refptr<content::MessageLoopRunner> runner_;

  DISALLOW_COPY_AND_ASSIGN(FakeGoogle);
};

// FakeGaia specialization that can delay /MergeSession handler until
// we explicitly call DelayedFakeGaia::UnblockMergeSession().
class DelayedFakeGaia : public FakeGaia {
 public:
  DelayedFakeGaia()
     : blocking_event_(true, false),
       start_event_(true, false) {
  }

  void UnblockMergeSession() {
    blocking_event_.Signal();
  }

  void WaitForMergeSessionToStart() {
    // If we have already served the request, bail out.
    if (start_event_.IsSignaled())
      return;

    runner_ = new content::MessageLoopRunner;
    runner_->Run();
  }

 private:
  // FakeGaia overrides.
  virtual void HandleMergeSession(const HttpRequest& request,
                                  BasicHttpResponse* http_response) OVERRIDE {
    start_event_.Signal();
    content::BrowserThread::PostTask(
        content::BrowserThread::UI, FROM_HERE,
        base::Bind(&DelayedFakeGaia::QuitRunnerOnUIThread,
                   base::Unretained(this)));
    blocking_event_.Wait();
    FakeGaia::HandleMergeSession(request, http_response);
  }

  void QuitRunnerOnUIThread() {
    if (runner_.get())
      runner_->Quit();
  }

  base::WaitableEvent blocking_event_;
  base::WaitableEvent start_event_;
  scoped_refptr<content::MessageLoopRunner> runner_;

  DISALLOW_COPY_AND_ASSIGN(DelayedFakeGaia);
};

class MergeSessionTest : public OAuth2Test {
 protected:
  MergeSessionTest() : delayed_fake_gaia_(new DelayedFakeGaia()) {
    fake_gaia_.reset(delayed_fake_gaia_);
  }

  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
    OAuth2Test::SetUpCommandLine(command_line);

    // Get fake URL for fake google.com.
    const GURL& server_url = embedded_test_server()->base_url();
    std::string google_host("www.google.com");
    GURL::Replacements replace_google_host;
    replace_google_host.SetHostStr(google_host);
    GURL google_url = server_url.ReplaceComponents(replace_google_host);
    fake_google_page_url_ = google_url.Resolve(kHelloPagePath);

    std::string non_google_host("www.somethingelse.org");
    GURL::Replacements replace_non_google_host;
    replace_non_google_host.SetHostStr(non_google_host);
    GURL non_google_url = server_url.ReplaceComponents(replace_non_google_host);
    non_google_page_url_ = non_google_url.Resolve(kRandomPagePath);
}

  virtual void SetUp() OVERRIDE {
    embedded_test_server()->RegisterRequestHandler(
        base::Bind(&FakeGoogle::HandleRequest,
                   base::Unretained(&fake_google_)));
    OAuth2Test::SetUp();
  }

 protected:
  void UnblockMergeSession() {
    delayed_fake_gaia_->UnblockMergeSession();
  }

  void WaitForMergeSessionToStart() {
    delayed_fake_gaia_->WaitForMergeSessionToStart();
  }

  void JsExpect(content::WebContents* contents,
                const std::string& expression) {
    bool result;
    ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
        contents,
        "window.domAutomationController.send(!!(" + expression + "));",
         &result));
    ASSERT_TRUE(result) << expression;
  }

  const GURL& GetBackGroundPageUrl(const std::string& extension_id) {
    extensions::ProcessManager* manager =
        extensions::ExtensionSystem::Get(profile())->process_manager();
    extensions::ExtensionHost* host =
        manager->GetBackgroundHostForExtension(extension_id);
    return host->host_contents()->GetURL();
  }

  void JsExpectOnBackgroundPage(const std::string& extension_id,
                                const std::string& expression) {
    extensions::ProcessManager* manager =
        extensions::ExtensionSystem::Get(profile())->process_manager();
    extensions::ExtensionHost* host =
        manager->GetBackgroundHostForExtension(extension_id);
    if (host == NULL) {
      ADD_FAILURE() << "Extension " << extension_id
                    << " has no background page.";
      return;
    }

    JsExpect(host->host_contents(), expression);
  }

  FakeGoogle fake_google_;
  DelayedFakeGaia* delayed_fake_gaia_;
  GURL fake_google_page_url_;
  GURL non_google_page_url_;

 private:
  DISALLOW_COPY_AND_ASSIGN(MergeSessionTest);
};

Browser* FindOrCreateVisibleBrowser(Profile* profile) {
  chrome::ScopedTabbedBrowserDisplayer displayer(
      profile, chrome::GetActiveDesktop());
  Browser* browser = displayer.browser();
  if (browser->tab_strip_model()->count() == 0)
    chrome::AddTabAt(browser, GURL(), -1, true);
  return browser;
}

IN_PROC_BROWSER_TEST_F(MergeSessionTest, PageThrottle) {
  StartNewUserSession(false);

  // Try to open a page from google.com.
  Browser* browser =
      FindOrCreateVisibleBrowser(profile());
  ui_test_utils::NavigateToURLWithDisposition(
      browser,
      fake_google_page_url_,
      CURRENT_TAB, ui_test_utils::BROWSER_TEST_NONE);

  // Wait until we get send merge session request.
  WaitForMergeSessionToStart();

  // Make sure the page is blocked by the throttle.
  EXPECT_FALSE(fake_google_.IsPageRequested());

  // Check that throttle page is displayed instead.
  base::string16 title;
  ui_test_utils::GetCurrentTabTitle(browser, &title);
  DVLOG(1) << "Loaded page at the start : " << title;

  // Unblock GAIA request.
  UnblockMergeSession();

  // Wait for the session merge to finish.
  WaitForMergeSessionCompletion(OAuth2LoginManager::SESSION_RESTORE_DONE);

  // Make sure the test page is served.
  fake_google_.WaitForPageRequest();

  // Check that real page is no longer blocked by the throttle and that the
  // real page pops up JS dialog.
  AppModalDialog* dialog = ui_test_utils::WaitForAppModalDialog();
  ASSERT_TRUE(dialog->IsJavaScriptModalDialog());
  JavaScriptAppModalDialog* js_dialog =
      static_cast<JavaScriptAppModalDialog*>(dialog);
  js_dialog->native_dialog()->AcceptAppModalDialog();

  ui_test_utils::GetCurrentTabTitle(browser, &title);
  DVLOG(1) << "Loaded page at the end : " << title;
}

IN_PROC_BROWSER_TEST_F(MergeSessionTest, XHRThrottle) {
  StartNewUserSession(false);

  // Wait until we get send merge session request.
  WaitForMergeSessionToStart();

  // Reset ExtensionBrowserTest::observer_ to the right browser object.
  Browser* browser = FindOrCreateVisibleBrowser(profile());
  observer_.reset(new ExtensionTestNotificationObserver(browser));

  // Run background page tests. The tests will just wait for XHR request
  // to complete.
  ResultCatcher catcher;

  scoped_ptr<ExtensionTestMessageListener> non_google_xhr_listener(
      new ExtensionTestMessageListener("non-google-xhr-received", false));

  // Load extension with a background page. The background page will
  // attempt to load |fake_google_page_url_| via XHR.
  const extensions::Extension* ext = LoadExtension(
      test_data_dir_.AppendASCII("merge_session"));
  ASSERT_TRUE(ext);

  // Kick off XHR request from the extension.
  JsExpectOnBackgroundPage(
      ext->id(),
      base::StringPrintf("startThrottledTests('%s', '%s')",
                         fake_google_page_url_.spec().c_str(),
                         non_google_page_url_.spec().c_str()));

  // Verify that we've sent XHR request form the extension side...
  JsExpectOnBackgroundPage(ext->id(),
                           "googleRequestSent && !googleResponseReceived");

  // ...but didn't see it on the server side yet.
  EXPECT_FALSE(fake_google_.IsPageRequested());

  // Unblock GAIA request.
  UnblockMergeSession();

  // Wait for the session merge to finish.
  WaitForMergeSessionCompletion(OAuth2LoginManager::SESSION_RESTORE_DONE);

  // Wait until non-google XHR content to load first.
  ASSERT_TRUE(non_google_xhr_listener->WaitUntilSatisfied());

  if (!catcher.GetNextResult()) {
    std::string message = catcher.message();
    ADD_FAILURE() << "Tests failed: " << message;
  }

  EXPECT_TRUE(fake_google_.IsPageRequested());
}

}  // namespace chromeos

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