Home | History | Annotate | Download | only in sync
      1 // Copyright (c) 2012 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/ui/sync/one_click_signin_helper.h"
      6 
      7 #include <algorithm>
      8 #include <functional>
      9 #include <utility>
     10 #include <vector>
     11 
     12 #include "base/bind.h"
     13 #include "base/callback_forward.h"
     14 #include "base/callback_helpers.h"
     15 #include "base/compiler_specific.h"
     16 #include "base/memory/scoped_ptr.h"
     17 #include "base/message_loop/message_loop_proxy.h"
     18 #include "base/metrics/field_trial.h"
     19 #include "base/metrics/histogram.h"
     20 #include "base/prefs/pref_service.h"
     21 #include "base/prefs/scoped_user_pref_update.h"
     22 #include "base/strings/string_split.h"
     23 #include "base/strings/string_util.h"
     24 #include "base/strings/utf_string_conversions.h"
     25 #include "base/supports_user_data.h"
     26 #include "base/values.h"
     27 #include "chrome/browser/browser_process.h"
     28 #include "chrome/browser/chrome_notification_types.h"
     29 #include "chrome/browser/defaults.h"
     30 #include "chrome/browser/history/history_service.h"
     31 #include "chrome/browser/history/history_service_factory.h"
     32 #include "chrome/browser/profiles/profile.h"
     33 #include "chrome/browser/profiles/profile_info_cache.h"
     34 #include "chrome/browser/profiles/profile_io_data.h"
     35 #include "chrome/browser/profiles/profile_manager.h"
     36 #include "chrome/browser/search/search.h"
     37 #include "chrome/browser/signin/chrome_signin_client.h"
     38 #include "chrome/browser/signin/chrome_signin_client_factory.h"
     39 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
     40 #include "chrome/browser/signin/signin_manager_factory.h"
     41 #include "chrome/browser/signin/signin_names_io_thread.h"
     42 #include "chrome/browser/sync/profile_sync_service.h"
     43 #include "chrome/browser/sync/profile_sync_service_factory.h"
     44 #include "chrome/browser/tab_contents/tab_util.h"
     45 #include "chrome/browser/ui/browser_finder.h"
     46 #include "chrome/browser/ui/browser_list.h"
     47 #include "chrome/browser/ui/browser_tabstrip.h"
     48 #include "chrome/browser/ui/browser_window.h"
     49 #include "chrome/browser/ui/chrome_pages.h"
     50 #include "chrome/browser/ui/sync/one_click_signin_histogram.h"
     51 #include "chrome/browser/ui/sync/one_click_signin_sync_observer.h"
     52 #include "chrome/browser/ui/sync/one_click_signin_sync_starter.h"
     53 #include "chrome/browser/ui/sync/signin_histogram.h"
     54 #include "chrome/browser/ui/tab_modal_confirm_dialog.h"
     55 #include "chrome/browser/ui/tab_modal_confirm_dialog_delegate.h"
     56 #include "chrome/browser/ui/tabs/tab_strip_model.h"
     57 #include "chrome/common/chrome_version_info.h"
     58 #include "chrome/common/net/url_util.h"
     59 #include "chrome/common/pref_names.h"
     60 #include "chrome/common/url_constants.h"
     61 #include "components/autofill/core/common/password_form.h"
     62 #include "components/google/core/browser/google_util.h"
     63 #include "components/password_manager/core/browser/password_manager.h"
     64 #include "components/signin/core/browser/profile_oauth2_token_service.h"
     65 #include "components/signin/core/browser/signin_client.h"
     66 #include "components/signin/core/browser/signin_error_controller.h"
     67 #include "components/signin/core/browser/signin_manager.h"
     68 #include "components/signin/core/browser/signin_manager_cookie_helper.h"
     69 #include "components/signin/core/common/profile_management_switches.h"
     70 #include "components/sync_driver/sync_prefs.h"
     71 #include "content/public/browser/browser_thread.h"
     72 #include "content/public/browser/navigation_entry.h"
     73 #include "content/public/browser/page_navigator.h"
     74 #include "content/public/browser/render_process_host.h"
     75 #include "content/public/browser/web_contents.h"
     76 #include "content/public/browser/web_contents_delegate.h"
     77 #include "content/public/common/frame_navigate_params.h"
     78 #include "content/public/common/page_transition_types.h"
     79 #include "google_apis/gaia/gaia_auth_util.h"
     80 #include "google_apis/gaia/gaia_urls.h"
     81 #include "grit/chromium_strings.h"
     82 #include "grit/generated_resources.h"
     83 #include "grit/theme_resources.h"
     84 #include "ipc/ipc_message_macros.h"
     85 #include "net/base/url_util.h"
     86 #include "net/cookies/cookie_monster.h"
     87 #include "net/url_request/url_request.h"
     88 #include "ui/base/l10n/l10n_util.h"
     89 #include "ui/base/resource/resource_bundle.h"
     90 #include "url/gurl.h"
     91 
     92 
     93 namespace {
     94 
     95 // ConfirmEmailDialogDelegate -------------------------------------------------
     96 
     97 class ConfirmEmailDialogDelegate : public TabModalConfirmDialogDelegate {
     98  public:
     99   enum Action {
    100     CREATE_NEW_USER,
    101     START_SYNC,
    102     CLOSE
    103   };
    104 
    105   // Callback indicating action performed by the user.
    106   typedef base::Callback<void(Action)> Callback;
    107 
    108   // Ask the user for confirmation before starting to sync.
    109   static void AskForConfirmation(content::WebContents* contents,
    110                                  const std::string& last_email,
    111                                  const std::string& email,
    112                                  Callback callback);
    113 
    114  private:
    115   ConfirmEmailDialogDelegate(content::WebContents* contents,
    116                              const std::string& last_email,
    117                              const std::string& email,
    118                              Callback callback);
    119   virtual ~ConfirmEmailDialogDelegate();
    120 
    121   // TabModalConfirmDialogDelegate:
    122   virtual base::string16 GetTitle() OVERRIDE;
    123   virtual base::string16 GetDialogMessage() OVERRIDE;
    124   virtual base::string16 GetAcceptButtonTitle() OVERRIDE;
    125   virtual base::string16 GetCancelButtonTitle() OVERRIDE;
    126   virtual base::string16 GetLinkText() const OVERRIDE;
    127   virtual void OnAccepted() OVERRIDE;
    128   virtual void OnCanceled() OVERRIDE;
    129   virtual void OnClosed() OVERRIDE;
    130   virtual void OnLinkClicked(WindowOpenDisposition disposition) OVERRIDE;
    131 
    132   std::string last_email_;
    133   std::string email_;
    134   Callback callback_;
    135 
    136   // Web contents from which the "Learn more" link should be opened.
    137   content::WebContents* web_contents_;
    138 
    139   DISALLOW_COPY_AND_ASSIGN(ConfirmEmailDialogDelegate);
    140 };
    141 
    142 // static
    143 void ConfirmEmailDialogDelegate::AskForConfirmation(
    144     content::WebContents* contents,
    145     const std::string& last_email,
    146     const std::string& email,
    147     Callback callback) {
    148   TabModalConfirmDialog::Create(
    149       new ConfirmEmailDialogDelegate(contents, last_email, email,
    150                                      callback), contents);
    151 }
    152 
    153 ConfirmEmailDialogDelegate::ConfirmEmailDialogDelegate(
    154     content::WebContents* contents,
    155     const std::string& last_email,
    156     const std::string& email,
    157     Callback callback)
    158   : TabModalConfirmDialogDelegate(contents),
    159     last_email_(last_email),
    160     email_(email),
    161     callback_(callback),
    162     web_contents_(contents) {
    163 }
    164 
    165 ConfirmEmailDialogDelegate::~ConfirmEmailDialogDelegate() {
    166 }
    167 
    168 base::string16 ConfirmEmailDialogDelegate::GetTitle() {
    169   return l10n_util::GetStringUTF16(
    170       IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_TITLE);
    171 }
    172 
    173 base::string16 ConfirmEmailDialogDelegate::GetDialogMessage() {
    174   return l10n_util::GetStringFUTF16(
    175       IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_MESSAGE,
    176       base::UTF8ToUTF16(last_email_), base::UTF8ToUTF16(email_));
    177 }
    178 
    179 base::string16 ConfirmEmailDialogDelegate::GetAcceptButtonTitle() {
    180   return l10n_util::GetStringUTF16(
    181       IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_OK_BUTTON);
    182 }
    183 
    184 base::string16 ConfirmEmailDialogDelegate::GetCancelButtonTitle() {
    185   return l10n_util::GetStringUTF16(
    186       IDS_ONE_CLICK_SIGNIN_CONFIRM_EMAIL_DIALOG_CANCEL_BUTTON);
    187 }
    188 
    189 base::string16 ConfirmEmailDialogDelegate::GetLinkText() const {
    190   return l10n_util::GetStringUTF16(IDS_LEARN_MORE);
    191 }
    192 
    193 void ConfirmEmailDialogDelegate::OnAccepted() {
    194   base::ResetAndReturn(&callback_).Run(CREATE_NEW_USER);
    195 }
    196 
    197 void ConfirmEmailDialogDelegate::OnCanceled() {
    198   base::ResetAndReturn(&callback_).Run(START_SYNC);
    199 }
    200 
    201 void ConfirmEmailDialogDelegate::OnClosed() {
    202   base::ResetAndReturn(&callback_).Run(CLOSE);
    203 }
    204 
    205 void ConfirmEmailDialogDelegate::OnLinkClicked(
    206     WindowOpenDisposition disposition) {
    207   content::OpenURLParams params(
    208       GURL(chrome::kChromeSyncMergeTroubleshootingURL),
    209       content::Referrer(),
    210       NEW_POPUP,
    211       content::PAGE_TRANSITION_AUTO_TOPLEVEL,
    212       false);
    213   // It is guaranteed that |web_contents_| is valid here because when it's
    214   // deleted, the dialog is immediately closed and no further action can be
    215   // performed.
    216   web_contents_->OpenURL(params);
    217 }
    218 
    219 
    220 // Helpers --------------------------------------------------------------------
    221 
    222 // Add a specific email to the list of emails rejected for one-click
    223 // sign-in, for this profile.
    224 void AddEmailToOneClickRejectedList(Profile* profile,
    225                                     const std::string& email) {
    226   ListPrefUpdate updater(profile->GetPrefs(),
    227                          prefs::kReverseAutologinRejectedEmailList);
    228   updater->AppendIfNotPresent(new base::StringValue(email));
    229 }
    230 
    231 void LogOneClickHistogramValue(int action) {
    232   UMA_HISTOGRAM_ENUMERATION("Signin.OneClickActions", action,
    233                             one_click_signin::HISTOGRAM_MAX);
    234   UMA_HISTOGRAM_ENUMERATION("Signin.AllAccessPointActions", action,
    235                             one_click_signin::HISTOGRAM_MAX);
    236 }
    237 
    238 void RedirectToNtpOrAppsPageWithIds(int child_id,
    239                                     int route_id,
    240                                     signin::Source source) {
    241   content::WebContents* web_contents = tab_util::GetWebContentsByID(child_id,
    242                                                                     route_id);
    243   if (!web_contents)
    244     return;
    245 
    246   OneClickSigninHelper::RedirectToNtpOrAppsPage(web_contents, source);
    247 }
    248 
    249 // Start syncing with the given user information.
    250 void StartSync(const OneClickSigninHelper::StartSyncArgs& args,
    251                OneClickSigninSyncStarter::StartSyncMode start_mode) {
    252   if (start_mode == OneClickSigninSyncStarter::UNDO_SYNC) {
    253     LogOneClickHistogramValue(one_click_signin::HISTOGRAM_UNDO);
    254     return;
    255   }
    256 
    257   // The wrapper deletes itself once it's done.
    258   OneClickSigninHelper::SyncStarterWrapper* wrapper =
    259       new OneClickSigninHelper::SyncStarterWrapper(args, start_mode);
    260   wrapper->Start();
    261 
    262   int action = one_click_signin::HISTOGRAM_MAX;
    263   switch (args.auto_accept) {
    264     case OneClickSigninHelper::AUTO_ACCEPT_EXPLICIT:
    265       break;
    266     case OneClickSigninHelper::AUTO_ACCEPT_ACCEPTED:
    267       action =
    268           start_mode == OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS ?
    269               one_click_signin::HISTOGRAM_AUTO_WITH_DEFAULTS :
    270               one_click_signin::HISTOGRAM_AUTO_WITH_ADVANCED;
    271       break;
    272     case OneClickSigninHelper::AUTO_ACCEPT_CONFIGURE:
    273       DCHECK(start_mode == OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST);
    274       action = one_click_signin::HISTOGRAM_AUTO_WITH_ADVANCED;
    275       break;
    276     default:
    277       NOTREACHED() << "Invalid auto_accept: " << args.auto_accept;
    278       break;
    279   }
    280   if (action != one_click_signin::HISTOGRAM_MAX)
    281     LogOneClickHistogramValue(action);
    282 }
    283 
    284 void StartExplicitSync(const OneClickSigninHelper::StartSyncArgs& args,
    285                        content::WebContents* contents,
    286                        OneClickSigninSyncStarter::StartSyncMode start_mode,
    287                        ConfirmEmailDialogDelegate::Action action) {
    288   bool enable_inline = !switches::IsEnableWebBasedSignin();
    289   if (action == ConfirmEmailDialogDelegate::START_SYNC) {
    290     StartSync(args, start_mode);
    291     if (!enable_inline) {
    292       // Redirect/tab closing for inline flow is handled by the sync callback.
    293       OneClickSigninHelper::RedirectToNtpOrAppsPageIfNecessary(
    294           contents, args.source);
    295     }
    296   } else {
    297     // Perform a redirection to the NTP/Apps page to hide the blank page when
    298     // the action is CLOSE or CREATE_NEW_USER. The redirection is useful when
    299     // the action is CREATE_NEW_USER because the "Create new user" page might
    300     // be opened in a different tab that is already showing settings.
    301     if (enable_inline) {
    302       // Redirect/tab closing for inline flow is handled by the sync callback.
    303       args.callback.Run(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE);
    304     } else {
    305       // Don't redirect when the visible URL is not a blank page: if the
    306       // source is SOURCE_WEBSTORE_INSTALL, |contents| might be showing an app
    307       // page that shouldn't be hidden.
    308       //
    309       // If redirecting, don't do so immediately, otherwise there may be 2
    310       // nested navigations and a crash would occur (crbug.com/293261).  Post
    311       // the task to the current thread instead.
    312       if (signin::IsContinueUrlForWebBasedSigninFlow(
    313               contents->GetVisibleURL())) {
    314         base::MessageLoopProxy::current()->PostNonNestableTask(
    315             FROM_HERE,
    316             base::Bind(RedirectToNtpOrAppsPageWithIds,
    317                        contents->GetRenderProcessHost()->GetID(),
    318                        contents->GetRoutingID(),
    319                        args.source));
    320       }
    321     }
    322     if (action == ConfirmEmailDialogDelegate::CREATE_NEW_USER) {
    323       chrome::ShowSettingsSubPage(args.browser,
    324                                   std::string(chrome::kCreateProfileSubPage));
    325     }
    326   }
    327 }
    328 
    329 void ClearPendingEmailOnIOThread(content::ResourceContext* context) {
    330   ProfileIOData* io_data = ProfileIOData::FromResourceContext(context);
    331   DCHECK(io_data);
    332   io_data->set_reverse_autologin_pending_email(std::string());
    333 }
    334 
    335 // Determines the source of the sign in and the continue URL.  It's either one
    336 // of the known sign-in access points (first run, NTP, Apps page, menu, or
    337 // settings) or it's an implicit sign in via another Google property.  In the
    338 // former case, "service" is also checked to make sure its "chromiumsync".
    339 signin::Source GetSigninSource(const GURL& url, GURL* continue_url) {
    340   DCHECK(url.is_valid());
    341   std::string value;
    342   net::GetValueForKeyInQuery(url, "service", &value);
    343   bool possibly_an_explicit_signin = value == "chromiumsync";
    344 
    345   // Find the final continue URL for this sign in.  In some cases, Gaia can
    346   // continue to itself, with the original continue URL buried under a couple
    347   // of layers of indirection.  Peel those layers away.  The final destination
    348   // can also be "IsGaiaSignonRealm" so stop if we get to the end (but be sure
    349   // we always extract at least one "continue" value).
    350   GURL local_continue_url = signin::GetNextPageURLForPromoURL(url);
    351   while (gaia::IsGaiaSignonRealm(local_continue_url.GetOrigin())) {
    352     GURL next_continue_url =
    353         signin::GetNextPageURLForPromoURL(local_continue_url);
    354     if (!next_continue_url.is_valid())
    355       break;
    356     local_continue_url = next_continue_url;
    357   }
    358 
    359   if (continue_url && local_continue_url.is_valid()) {
    360     DCHECK(!continue_url->is_valid() || *continue_url == local_continue_url);
    361     *continue_url = local_continue_url;
    362   }
    363 
    364   return possibly_an_explicit_signin ?
    365       signin::GetSourceForPromoURL(local_continue_url) :
    366       signin::SOURCE_UNKNOWN;
    367 }
    368 
    369 // Returns true if |url| is a valid URL that can occur during the sign in
    370 // process.  Valid URLs are of the form:
    371 //
    372 //    https://accounts.google.{TLD}/...
    373 //    https://accounts.youtube.com/...
    374 //    https://accounts.blogger.com/...
    375 //
    376 // All special headers used by one click sign in occur on
    377 // https://accounts.google.com URLs.  However, the sign in process may redirect
    378 // to intermediate Gaia URLs that do not end with .com.  For example, an account
    379 // that uses SMS 2-factor outside the US may redirect to country specific URLs.
    380 //
    381 // The sign in process may also redirect to youtube and blogger account URLs
    382 // so that Gaia acts as a single signon service.
    383 bool IsValidGaiaSigninRedirectOrResponseURL(const GURL& url) {
    384   std::string hostname = url.host();
    385   if (google_util::IsGoogleHostname(hostname, google_util::ALLOW_SUBDOMAIN)) {
    386     // Also using IsGaiaSignonRealm() to handle overriding with command line.
    387     return gaia::IsGaiaSignonRealm(url.GetOrigin()) ||
    388         StartsWithASCII(hostname, "accounts.", false);
    389   }
    390 
    391   GURL origin = url.GetOrigin();
    392   if (origin == GURL("https://accounts.youtube.com") ||
    393       origin == GURL("https://accounts.blogger.com"))
    394     return true;
    395 
    396   return false;
    397 }
    398 
    399 // Tells when we are in the process of showing either the signin to chrome page
    400 // or the one click sign in to chrome page.
    401 // NOTE: This should only be used for logging purposes since it relies on hard
    402 // coded URLs that could change.
    403 bool AreWeShowingSignin(GURL url, signin::Source source, std::string email) {
    404   GURL::Replacements replacements;
    405   replacements.ClearQuery();
    406   GURL clean_login_url =
    407       GaiaUrls::GetInstance()->service_login_url().ReplaceComponents(
    408           replacements);
    409 
    410   return (url.ReplaceComponents(replacements) == clean_login_url &&
    411           source != signin::SOURCE_UNKNOWN) ||
    412       (IsValidGaiaSigninRedirectOrResponseURL(url) &&
    413        url.spec().find("ChromeLoginPrompt") != std::string::npos &&
    414        !email.empty());
    415 }
    416 
    417 // CurrentHistoryCleaner ------------------------------------------------------
    418 
    419 // Watch a webcontents and remove URL from the history once loading is complete.
    420 // We have to delay the cleaning until the new URL has finished loading because
    421 // we're not allowed to remove the last-loaded URL from the history.  Objects
    422 // of this type automatically self-destruct once they're finished their work.
    423 class CurrentHistoryCleaner : public content::WebContentsObserver {
    424  public:
    425   explicit CurrentHistoryCleaner(content::WebContents* contents);
    426   virtual ~CurrentHistoryCleaner();
    427 
    428   // content::WebContentsObserver:
    429   virtual void WebContentsDestroyed() OVERRIDE;
    430   virtual void DidCommitProvisionalLoadForFrame(
    431       int64 frame_id,
    432       const base::string16& frame_unique_name,
    433       bool is_main_frame,
    434       const GURL& url,
    435       content::PageTransition transition_type,
    436       content::RenderViewHost* render_view_host) OVERRIDE;
    437 
    438  private:
    439   scoped_ptr<content::WebContents> contents_;
    440   int history_index_to_remove_;
    441 
    442   DISALLOW_COPY_AND_ASSIGN(CurrentHistoryCleaner);
    443 };
    444 
    445 CurrentHistoryCleaner::CurrentHistoryCleaner(content::WebContents* contents)
    446     : WebContentsObserver(contents) {
    447   history_index_to_remove_ =
    448       web_contents()->GetController().GetLastCommittedEntryIndex();
    449 }
    450 
    451 CurrentHistoryCleaner::~CurrentHistoryCleaner() {
    452 }
    453 
    454 void CurrentHistoryCleaner::DidCommitProvisionalLoadForFrame(
    455     int64 frame_id,
    456     const base::string16& frame_unique_name,
    457     bool is_main_frame,
    458     const GURL& url,
    459     content::PageTransition transition_type,
    460     content::RenderViewHost* render_view_host) {
    461   // Return early if this is not top-level navigation.
    462   if (!is_main_frame)
    463     return;
    464 
    465   content::NavigationController* nc = &web_contents()->GetController();
    466   HistoryService* hs = HistoryServiceFactory::GetForProfile(
    467       Profile::FromBrowserContext(web_contents()->GetBrowserContext()),
    468       Profile::IMPLICIT_ACCESS);
    469 
    470   // Have to wait until something else gets added to history before removal.
    471   if (history_index_to_remove_ < nc->GetLastCommittedEntryIndex()) {
    472     content::NavigationEntry* entry =
    473         nc->GetEntryAtIndex(history_index_to_remove_);
    474     if (signin::IsContinueUrlForWebBasedSigninFlow(entry->GetURL())) {
    475       hs->DeleteURL(entry->GetURL());
    476       nc->RemoveEntryAtIndex(history_index_to_remove_);
    477       delete this;  // Success.
    478     }
    479   }
    480 }
    481 
    482 void CurrentHistoryCleaner::WebContentsDestroyed() {
    483   delete this;  // Failure.
    484 }
    485 
    486 }  // namespace
    487 
    488 
    489 // StartSyncArgs --------------------------------------------------------------
    490 
    491 OneClickSigninHelper::StartSyncArgs::StartSyncArgs()
    492     : profile(NULL),
    493       browser(NULL),
    494       auto_accept(AUTO_ACCEPT_NONE),
    495       web_contents(NULL),
    496       confirmation_required(OneClickSigninSyncStarter::NO_CONFIRMATION),
    497       source(signin::SOURCE_UNKNOWN) {}
    498 
    499 OneClickSigninHelper::StartSyncArgs::StartSyncArgs(
    500     Profile* profile,
    501     Browser* browser,
    502     OneClickSigninHelper::AutoAccept auto_accept,
    503     const std::string& session_index,
    504     const std::string& email,
    505     const std::string& password,
    506     const std::string& refresh_token,
    507     content::WebContents* web_contents,
    508     bool untrusted_confirmation_required,
    509     signin::Source source,
    510     OneClickSigninSyncStarter::Callback callback)
    511     : profile(profile),
    512       browser(browser),
    513       auto_accept(auto_accept),
    514       session_index(session_index),
    515       email(email),
    516       password(password),
    517       refresh_token(refresh_token),
    518       web_contents(web_contents),
    519       source(source),
    520       callback(callback) {
    521   DCHECK(session_index.empty() != refresh_token.empty());
    522   if (untrusted_confirmation_required) {
    523     confirmation_required = OneClickSigninSyncStarter::CONFIRM_UNTRUSTED_SIGNIN;
    524   } else if (source == signin::SOURCE_SETTINGS ||
    525              source == signin::SOURCE_WEBSTORE_INSTALL) {
    526     // Do not display a status confirmation for webstore installs or re-auth.
    527     confirmation_required = OneClickSigninSyncStarter::NO_CONFIRMATION;
    528   } else {
    529     confirmation_required = OneClickSigninSyncStarter::CONFIRM_AFTER_SIGNIN;
    530   }
    531 }
    532 
    533 OneClickSigninHelper::StartSyncArgs::~StartSyncArgs() {}
    534 
    535 // SyncStarterWrapper ---------------------------------------------------------
    536 
    537 OneClickSigninHelper::SyncStarterWrapper::SyncStarterWrapper(
    538       const OneClickSigninHelper::StartSyncArgs& args,
    539       OneClickSigninSyncStarter::StartSyncMode start_mode)
    540     : args_(args), start_mode_(start_mode), weak_pointer_factory_(this) {
    541   BrowserList::AddObserver(this);
    542 
    543   // Cache the parent desktop for the browser, so we can reuse that same
    544   // desktop for any UI we want to display.
    545   desktop_type_ = args_.browser ? args_.browser->host_desktop_type()
    546                                 : chrome::GetActiveDesktop();
    547 }
    548 
    549 OneClickSigninHelper::SyncStarterWrapper::~SyncStarterWrapper() {
    550   BrowserList::RemoveObserver(this);
    551 }
    552 
    553 void OneClickSigninHelper::SyncStarterWrapper::Start() {
    554   if (args_.refresh_token.empty()) {
    555     if (args_.password.empty()) {
    556       VerifyGaiaCookiesBeforeSignIn();
    557     } else {
    558       StartSigninOAuthHelper();
    559     }
    560   } else {
    561     OnSigninOAuthInformationAvailable(args_.email, args_.email,
    562                                       args_.refresh_token);
    563   }
    564 }
    565 
    566 void
    567 OneClickSigninHelper::SyncStarterWrapper::OnSigninOAuthInformationAvailable(
    568     const std::string& email,
    569     const std::string& display_email,
    570     const std::string& refresh_token) {
    571   if (!gaia::AreEmailsSame(display_email, args_.email)) {
    572     DisplayErrorBubble(
    573         GoogleServiceAuthError(
    574             GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS).ToString());
    575   } else {
    576     StartOneClickSigninSyncStarter(email, refresh_token);
    577   }
    578 
    579   base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
    580 }
    581 
    582 void OneClickSigninHelper::SyncStarterWrapper::OnSigninOAuthInformationFailure(
    583   const GoogleServiceAuthError& error) {
    584   DisplayErrorBubble(error.ToString());
    585   base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
    586 }
    587 
    588 void OneClickSigninHelper::SyncStarterWrapper::OnBrowserRemoved(
    589     Browser* browser) {
    590   if (args_.browser == browser)
    591     args_.browser = NULL;
    592 }
    593 
    594 void OneClickSigninHelper::SyncStarterWrapper::VerifyGaiaCookiesBeforeSignIn() {
    595   scoped_refptr<SigninManagerCookieHelper> cookie_helper(
    596       new SigninManagerCookieHelper(
    597           args_.profile->GetRequestContext(),
    598           content::BrowserThread::GetMessageLoopProxyForThread(
    599               content::BrowserThread::UI),
    600           content::BrowserThread::GetMessageLoopProxyForThread(
    601               content::BrowserThread::IO)));
    602   cookie_helper->StartFetchingGaiaCookiesOnUIThread(
    603       base::Bind(&SyncStarterWrapper::OnGaiaCookiesFetched,
    604                  weak_pointer_factory_.GetWeakPtr(),
    605                  args_.session_index));
    606 }
    607 
    608 void OneClickSigninHelper::SyncStarterWrapper::OnGaiaCookiesFetched(
    609     const std::string session_index, const net::CookieList& cookie_list) {
    610   net::CookieList::const_iterator it;
    611   bool success = false;
    612   for (it = cookie_list.begin(); it != cookie_list.end(); ++it) {
    613     // Make sure the LSID cookie is set on the GAIA host, instead of a super-
    614     // domain.
    615     if (it->Name() == "LSID") {
    616       if (it->IsHostCookie() && it->IsHttpOnly() && it->IsSecure()) {
    617         // Found a valid LSID cookie. Continue loop to make sure we don't have
    618         // invalid LSID cookies on any super-domain.
    619         success = true;
    620       } else {
    621         success = false;
    622         break;
    623       }
    624     }
    625   }
    626 
    627   if (success) {
    628     StartSigninOAuthHelper();
    629   } else {
    630     DisplayErrorBubble(
    631         GoogleServiceAuthError(
    632             GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS).ToString());
    633     base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
    634   }
    635 }
    636 
    637 void OneClickSigninHelper::SyncStarterWrapper::DisplayErrorBubble(
    638     const std::string& error_message) {
    639   args_.browser = OneClickSigninSyncStarter::EnsureBrowser(
    640       args_.browser, args_.profile, desktop_type_);
    641   args_.browser->window()->ShowOneClickSigninBubble(
    642       BrowserWindow::ONE_CLICK_SIGNIN_BUBBLE_TYPE_BUBBLE,
    643       base::string16(),  // No email required - this is not a SAML confirmation.
    644       base::UTF8ToUTF16(error_message),
    645       // Callback is ignored.
    646       BrowserWindow::StartSyncCallback());
    647 }
    648 
    649 void OneClickSigninHelper::SyncStarterWrapper::StartSigninOAuthHelper() {
    650   signin_oauth_helper_.reset(
    651       new SigninOAuthHelper(args_.profile->GetRequestContext(),
    652                             args_.session_index, this));
    653 }
    654 
    655 void
    656 OneClickSigninHelper::SyncStarterWrapper::StartOneClickSigninSyncStarter(
    657     const std::string& email,
    658     const std::string& refresh_token) {
    659   // The starter deletes itself once it's done.
    660   new OneClickSigninSyncStarter(args_.profile, args_.browser,
    661                                 email, args_.password,
    662                                 refresh_token, start_mode_,
    663                                 args_.web_contents,
    664                                 args_.confirmation_required,
    665                                 GURL(),
    666                                 args_.callback);
    667 }
    668 
    669 
    670 // OneClickSigninHelper -------------------------------------------------------
    671 
    672 DEFINE_WEB_CONTENTS_USER_DATA_KEY(OneClickSigninHelper);
    673 
    674 // static
    675 const int OneClickSigninHelper::kMaxNavigationsSince = 10;
    676 
    677 OneClickSigninHelper::OneClickSigninHelper(
    678     content::WebContents* web_contents,
    679     password_manager::PasswordManager* password_manager)
    680     : content::WebContentsObserver(web_contents),
    681       showing_signin_(false),
    682       auto_accept_(AUTO_ACCEPT_NONE),
    683       source_(signin::SOURCE_UNKNOWN),
    684       switched_to_advanced_(false),
    685       untrusted_navigations_since_signin_visit_(0),
    686       untrusted_confirmation_required_(false),
    687       do_not_clear_pending_email_(false),
    688       do_not_start_sync_for_testing_(false),
    689       weak_pointer_factory_(this) {
    690   // May be NULL during testing.
    691   if (password_manager) {
    692     password_manager->AddSubmissionCallback(
    693         base::Bind(&OneClickSigninHelper::PasswordSubmitted,
    694                    weak_pointer_factory_.GetWeakPtr()));
    695   }
    696 }
    697 
    698 OneClickSigninHelper::~OneClickSigninHelper() {}
    699 
    700 // static
    701 void OneClickSigninHelper::LogHistogramValue(
    702     signin::Source source, int action) {
    703   switch (source) {
    704     case signin::SOURCE_START_PAGE:
    705       UMA_HISTOGRAM_ENUMERATION("Signin.StartPageActions", action,
    706                                 one_click_signin::HISTOGRAM_MAX);
    707       break;
    708     case signin::SOURCE_NTP_LINK:
    709       UMA_HISTOGRAM_ENUMERATION("Signin.NTPLinkActions", action,
    710                                 one_click_signin::HISTOGRAM_MAX);
    711       break;
    712     case signin::SOURCE_MENU:
    713       UMA_HISTOGRAM_ENUMERATION("Signin.MenuActions", action,
    714                                 one_click_signin::HISTOGRAM_MAX);
    715       break;
    716     case signin::SOURCE_SETTINGS:
    717       UMA_HISTOGRAM_ENUMERATION("Signin.SettingsActions", action,
    718                                 one_click_signin::HISTOGRAM_MAX);
    719       break;
    720     case signin::SOURCE_EXTENSION_INSTALL_BUBBLE:
    721       UMA_HISTOGRAM_ENUMERATION("Signin.ExtensionInstallBubbleActions", action,
    722                                 one_click_signin::HISTOGRAM_MAX);
    723       break;
    724     case signin::SOURCE_WEBSTORE_INSTALL:
    725       UMA_HISTOGRAM_ENUMERATION("Signin.WebstoreInstallActions", action,
    726                                 one_click_signin::HISTOGRAM_MAX);
    727       break;
    728     case signin::SOURCE_APP_LAUNCHER:
    729       UMA_HISTOGRAM_ENUMERATION("Signin.AppLauncherActions", action,
    730                                 one_click_signin::HISTOGRAM_MAX);
    731       break;
    732     case signin::SOURCE_APPS_PAGE_LINK:
    733       UMA_HISTOGRAM_ENUMERATION("Signin.AppsPageLinkActions", action,
    734                                 one_click_signin::HISTOGRAM_MAX);
    735       break;
    736     case signin::SOURCE_BOOKMARK_BUBBLE:
    737       UMA_HISTOGRAM_ENUMERATION("Signin.BookmarkBubbleActions", action,
    738                                 one_click_signin::HISTOGRAM_MAX);
    739       break;
    740     case signin::SOURCE_AVATAR_BUBBLE_SIGN_IN:
    741       UMA_HISTOGRAM_ENUMERATION("Signin.AvatarBubbleActions", action,
    742                                 one_click_signin::HISTOGRAM_MAX);
    743       break;
    744     case signin::SOURCE_AVATAR_BUBBLE_ADD_ACCOUNT:
    745       UMA_HISTOGRAM_ENUMERATION("Signin.AvatarBubbleActions", action,
    746                                 one_click_signin::HISTOGRAM_MAX);
    747       break;
    748     case signin::SOURCE_DEVICES_PAGE:
    749       UMA_HISTOGRAM_ENUMERATION("Signin.DevicesPageActions", action,
    750                                 one_click_signin::HISTOGRAM_MAX);
    751       break;
    752     case signin::SOURCE_REAUTH:
    753       UMA_HISTOGRAM_ENUMERATION("Signin.ReauthActions", action,
    754                                 one_click_signin::HISTOGRAM_MAX);
    755       break;
    756     default:
    757       // This switch statement needs to be updated when the enum Source changes.
    758       COMPILE_ASSERT(signin::SOURCE_UNKNOWN == 13,
    759                      kSourceEnumHasChangedButNotThisSwitchStatement);
    760       UMA_HISTOGRAM_ENUMERATION("Signin.UnknownActions", action,
    761                                 one_click_signin::HISTOGRAM_MAX);
    762   }
    763   UMA_HISTOGRAM_ENUMERATION("Signin.AllAccessPointActions", action,
    764                             one_click_signin::HISTOGRAM_MAX);
    765 }
    766 
    767 // static
    768 void OneClickSigninHelper::CreateForWebContentsWithPasswordManager(
    769     content::WebContents* contents,
    770     password_manager::PasswordManager* password_manager) {
    771   if (!FromWebContents(contents)) {
    772     contents->SetUserData(UserDataKey(),
    773                           new OneClickSigninHelper(contents, password_manager));
    774   }
    775 }
    776 
    777 // static
    778 bool OneClickSigninHelper::CanOffer(content::WebContents* web_contents,
    779                                     CanOfferFor can_offer_for,
    780                                     const std::string& email,
    781                                     std::string* error_message) {
    782   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    783     VLOG(1) << "OneClickSigninHelper::CanOffer";
    784 
    785   if (error_message)
    786     error_message->clear();
    787 
    788   if (!web_contents)
    789     return false;
    790 
    791   if (web_contents->GetBrowserContext()->IsOffTheRecord())
    792     return false;
    793 
    794   Profile* profile =
    795       Profile::FromBrowserContext(web_contents->GetBrowserContext());
    796   if (!profile)
    797     return false;
    798 
    799   SigninManager* manager =
    800       SigninManagerFactory::GetForProfile(profile);
    801   if (manager && !manager->IsSigninAllowed())
    802     return false;
    803 
    804   if (can_offer_for == CAN_OFFER_FOR_INTERSTITAL_ONLY &&
    805       !profile->GetPrefs()->GetBoolean(prefs::kReverseAutologinEnabled))
    806     return false;
    807 
    808   if (!ChromeSigninClient::ProfileAllowsSigninCookies(profile))
    809     return false;
    810 
    811   if (!email.empty()) {
    812     if (!manager)
    813       return false;
    814 
    815     // Make sure this username is not prohibited by policy.
    816     if (!manager->IsAllowedUsername(email)) {
    817       if (error_message) {
    818         error_message->assign(
    819             l10n_util::GetStringUTF8(IDS_SYNC_LOGIN_NAME_PROHIBITED));
    820       }
    821       return false;
    822     }
    823 
    824     if (can_offer_for != CAN_OFFER_FOR_SECONDARY_ACCOUNT) {
    825       // If the signin manager already has an authenticated name, then this is a
    826       // re-auth scenario.  Make sure the email just signed in corresponds to
    827       // the one sign in manager expects.
    828       std::string current_email = manager->GetAuthenticatedUsername();
    829       const bool same_email = gaia::AreEmailsSame(current_email, email);
    830       if (!current_email.empty() && !same_email) {
    831         UMA_HISTOGRAM_ENUMERATION("Signin.Reauth",
    832                                   signin::HISTOGRAM_ACCOUNT_MISSMATCH,
    833                                   signin::HISTOGRAM_MAX);
    834         if (error_message) {
    835           error_message->assign(
    836               l10n_util::GetStringFUTF8(IDS_SYNC_WRONG_EMAIL,
    837                                         base::UTF8ToUTF16(current_email)));
    838         }
    839         return false;
    840       }
    841 
    842       // If some profile, not just the current one, is already connected to this
    843       // account, don't show the infobar.
    844       if (g_browser_process && !same_email) {
    845         ProfileManager* manager = g_browser_process->profile_manager();
    846         if (manager) {
    847           ProfileInfoCache& cache = manager->GetProfileInfoCache();
    848           for (size_t i = 0; i < cache.GetNumberOfProfiles(); ++i) {
    849             std::string current_email =
    850                 base::UTF16ToUTF8(cache.GetUserNameOfProfileAtIndex(i));
    851             if (gaia::AreEmailsSame(email, current_email)) {
    852               if (error_message) {
    853                 error_message->assign(
    854                     l10n_util::GetStringUTF8(IDS_SYNC_USER_NAME_IN_USE_ERROR));
    855               }
    856               return false;
    857             }
    858           }
    859         }
    860       }
    861     }
    862 
    863     // If email was already rejected by this profile for one-click sign-in.
    864     if (can_offer_for == CAN_OFFER_FOR_INTERSTITAL_ONLY) {
    865       const base::ListValue* rejected_emails = profile->GetPrefs()->GetList(
    866           prefs::kReverseAutologinRejectedEmailList);
    867       if (!rejected_emails->empty()) {
    868         base::ListValue::const_iterator iter = rejected_emails->Find(
    869             base::StringValue(email));
    870         if (iter != rejected_emails->end())
    871           return false;
    872       }
    873     }
    874   }
    875 
    876   VLOG(1) << "OneClickSigninHelper::CanOffer: yes we can";
    877   return true;
    878 }
    879 
    880 // static
    881 OneClickSigninHelper::Offer OneClickSigninHelper::CanOfferOnIOThread(
    882     net::URLRequest* request,
    883     ProfileIOData* io_data) {
    884   return CanOfferOnIOThreadImpl(request->url(), request, io_data);
    885 }
    886 
    887 // static
    888 OneClickSigninHelper::Offer OneClickSigninHelper::CanOfferOnIOThreadImpl(
    889     const GURL& url,
    890     base::SupportsUserData* request,
    891     ProfileIOData* io_data) {
    892   if (!gaia::IsGaiaSignonRealm(url.GetOrigin()))
    893     return IGNORE_REQUEST;
    894 
    895   if (!io_data)
    896     return DONT_OFFER;
    897 
    898   // Check for incognito before other parts of the io_data, since those
    899   // members may not be initalized.
    900   if (io_data->IsOffTheRecord())
    901     return DONT_OFFER;
    902 
    903   if (!io_data->signin_allowed()->GetValue())
    904     return DONT_OFFER;
    905 
    906   if (!io_data->reverse_autologin_enabled()->GetValue())
    907     return DONT_OFFER;
    908 
    909   if (!io_data->google_services_username()->GetValue().empty())
    910     return DONT_OFFER;
    911 
    912   if (!ChromeSigninClient::SettingsAllowSigninCookies(
    913           io_data->GetCookieSettings()))
    914     return DONT_OFFER;
    915 
    916   // The checks below depend on chrome already knowing what account the user
    917   // signed in with.  This happens only after receiving the response containing
    918   // the Google-Accounts-SignIn header.  Until then, if there is even a chance
    919   // that we want to connect the profile, chrome needs to tell Gaia that
    920   // it should offer the interstitial.  Therefore missing one click data on
    921   // the request means can offer is true.
    922   const std::string& pending_email = io_data->reverse_autologin_pending_email();
    923   if (!pending_email.empty()) {
    924     if (!SigninManager::IsUsernameAllowedByPolicy(pending_email,
    925             io_data->google_services_username_pattern()->GetValue())) {
    926       return DONT_OFFER;
    927     }
    928 
    929     std::vector<std::string> rejected_emails =
    930         io_data->one_click_signin_rejected_email_list()->GetValue();
    931     if (std::count_if(rejected_emails.begin(), rejected_emails.end(),
    932                       std::bind2nd(std::equal_to<std::string>(),
    933                                    pending_email)) > 0) {
    934       return DONT_OFFER;
    935     }
    936 
    937     if (io_data->signin_names()->GetEmails().count(
    938             base::UTF8ToUTF16(pending_email)) > 0) {
    939       return DONT_OFFER;
    940     }
    941   }
    942 
    943   return CAN_OFFER;
    944 }
    945 
    946 // static
    947 void OneClickSigninHelper::ShowInfoBarIfPossible(net::URLRequest* request,
    948                                                  ProfileIOData* io_data,
    949                                                  int child_id,
    950                                                  int route_id) {
    951   std::string google_chrome_signin_value;
    952   std::string google_accounts_signin_value;
    953   request->GetResponseHeaderByName("Google-Chrome-SignIn",
    954                                    &google_chrome_signin_value);
    955   request->GetResponseHeaderByName("Google-Accounts-SignIn",
    956                                    &google_accounts_signin_value);
    957 
    958   if (!google_accounts_signin_value.empty() ||
    959       !google_chrome_signin_value.empty()) {
    960     VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:"
    961             << " g-a-s='" << google_accounts_signin_value << "'"
    962             << " g-c-s='" << google_chrome_signin_value << "'";
    963   }
    964 
    965   if (!gaia::IsGaiaSignonRealm(request->url().GetOrigin()))
    966     return;
    967 
    968   // Parse Google-Accounts-SignIn.
    969   std::vector<std::pair<std::string, std::string> > pairs;
    970   base::SplitStringIntoKeyValuePairs(google_accounts_signin_value, '=', ',',
    971                                      &pairs);
    972   std::string session_index;
    973   std::string email;
    974   for (size_t i = 0; i < pairs.size(); ++i) {
    975     const std::pair<std::string, std::string>& pair = pairs[i];
    976     const std::string& key = pair.first;
    977     const std::string& value = pair.second;
    978     if (key == "email") {
    979       base::TrimString(value, "\"", &email);
    980     } else if (key == "sessionindex") {
    981       session_index = value;
    982     }
    983   }
    984 
    985   // Later in the chain of this request, we'll need to check the email address
    986   // in the IO thread (see CanOfferOnIOThread).  So save the email address as
    987   // user data on the request (only for web-based flow).
    988   if (!email.empty())
    989     io_data->set_reverse_autologin_pending_email(email);
    990 
    991   if (!email.empty() || !session_index.empty()) {
    992     VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:"
    993             << " email=" << email
    994             << " sessionindex=" << session_index;
    995   }
    996 
    997   // Parse Google-Chrome-SignIn.
    998   AutoAccept auto_accept = AUTO_ACCEPT_NONE;
    999   signin::Source source = signin::SOURCE_UNKNOWN;
   1000   GURL continue_url;
   1001   std::vector<std::string> tokens;
   1002   base::SplitString(google_chrome_signin_value, ',', &tokens);
   1003   for (size_t i = 0; i < tokens.size(); ++i) {
   1004     const std::string& token = tokens[i];
   1005     if (token == "accepted") {
   1006       auto_accept = AUTO_ACCEPT_ACCEPTED;
   1007     } else if (token == "configure") {
   1008       auto_accept = AUTO_ACCEPT_CONFIGURE;
   1009     } else if (token == "rejected-for-profile") {
   1010       auto_accept = AUTO_ACCEPT_REJECTED_FOR_PROFILE;
   1011     }
   1012   }
   1013 
   1014   // If this is an explicit sign in (i.e., first run, NTP, Apps page, menu,
   1015   // settings) then force the auto accept type to explicit.
   1016   source = GetSigninSource(request->url(), &continue_url);
   1017   if (source != signin::SOURCE_UNKNOWN)
   1018     auto_accept = AUTO_ACCEPT_EXPLICIT;
   1019 
   1020   if (auto_accept != AUTO_ACCEPT_NONE) {
   1021     VLOG(1) << "OneClickSigninHelper::ShowInfoBarIfPossible:"
   1022             << " auto_accept=" << auto_accept;
   1023   }
   1024 
   1025   // If |session_index|, |email|, |auto_accept|, and |continue_url| all have
   1026   // their default value, don't bother posting a task to the UI thread.
   1027   // It will be a noop anyway.
   1028   //
   1029   // The two headers above may (but not always) come in different http requests
   1030   // so a post to the UI thread is still needed if |auto_accept| is not its
   1031   // default value, but |email| and |session_index| are.
   1032   if (session_index.empty() && email.empty() &&
   1033       auto_accept == AUTO_ACCEPT_NONE && !continue_url.is_valid()) {
   1034     return;
   1035   }
   1036 
   1037   content::BrowserThread::PostTask(
   1038       content::BrowserThread::UI, FROM_HERE,
   1039       base::Bind(&OneClickSigninHelper::ShowInfoBarUIThread, session_index,
   1040                  email, auto_accept, source, continue_url, child_id, route_id));
   1041 }
   1042 
   1043 // static
   1044 void OneClickSigninHelper::LogConfirmHistogramValue(int action) {
   1045   UMA_HISTOGRAM_ENUMERATION("Signin.OneClickConfirmation", action,
   1046                             one_click_signin::HISTOGRAM_CONFIRM_MAX);
   1047 }
   1048 // static
   1049 void OneClickSigninHelper::ShowInfoBarUIThread(
   1050     const std::string& session_index,
   1051     const std::string& email,
   1052     AutoAccept auto_accept,
   1053     signin::Source source,
   1054     const GURL& continue_url,
   1055     int child_id,
   1056     int route_id) {
   1057   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   1058 
   1059   content::WebContents* web_contents = tab_util::GetWebContentsByID(child_id,
   1060                                                                     route_id);
   1061   if (!web_contents)
   1062     return;
   1063 
   1064   // TODO(mathp): The appearance of this infobar should be tested using a
   1065   // browser_test.
   1066   OneClickSigninHelper* helper =
   1067       OneClickSigninHelper::FromWebContents(web_contents);
   1068   if (!helper)
   1069     return;
   1070 
   1071   if (auto_accept != AUTO_ACCEPT_NONE)
   1072     helper->auto_accept_ = auto_accept;
   1073 
   1074   if (source != signin::SOURCE_UNKNOWN &&
   1075       helper->source_ == signin::SOURCE_UNKNOWN) {
   1076     helper->source_ = source;
   1077   }
   1078 
   1079   // Save the email in the one-click signin manager.  The manager may
   1080   // not exist if the contents is incognito or if the profile is already
   1081   // connected to a Google account.
   1082   if (!session_index.empty())
   1083     helper->session_index_ = session_index;
   1084 
   1085   if (!email.empty())
   1086     helper->email_ = email;
   1087 
   1088   CanOfferFor can_offer_for =
   1089       (auto_accept != AUTO_ACCEPT_EXPLICIT &&
   1090           helper->auto_accept_ != AUTO_ACCEPT_EXPLICIT) ?
   1091           CAN_OFFER_FOR_INTERSTITAL_ONLY : CAN_OFFER_FOR_ALL;
   1092 
   1093   std::string error_message;
   1094 
   1095   if (!web_contents || !CanOffer(web_contents, can_offer_for, email,
   1096                                  &error_message)) {
   1097     VLOG(1) << "OneClickSigninHelper::ShowInfoBarUIThread: not offering";
   1098     // TODO(rogerta): Can we just display our error now instead of keeping it
   1099     // around and doing it later?
   1100     if (helper && helper->error_message_.empty() && !error_message.empty())
   1101       helper->error_message_ = error_message;
   1102 
   1103     return;
   1104   }
   1105 
   1106   // Only allow the dedicated signin process to sign the user into
   1107   // Chrome without intervention, because it doesn't load any untrusted
   1108   // pages.  If at any point an untrusted page is detected, chrome will
   1109   // show a modal dialog asking the user to confirm.
   1110   Profile* profile =
   1111       Profile::FromBrowserContext(web_contents->GetBrowserContext());
   1112   SigninClient* signin_client =
   1113       profile ? ChromeSigninClientFactory::GetForProfile(profile) : NULL;
   1114   helper->untrusted_confirmation_required_ |=
   1115       (signin_client && !signin_client->IsSigninProcess(child_id));
   1116 
   1117   if (continue_url.is_valid()) {
   1118     // Set |original_continue_url_| if it is currently empty. |continue_url|
   1119     // could be modified by gaia pages, thus we need to record the original
   1120     // continue url to navigate back to the right page when sync setup is
   1121     // complete.
   1122     if (helper->original_continue_url_.is_empty())
   1123       helper->original_continue_url_ = continue_url;
   1124     helper->continue_url_ = continue_url;
   1125   }
   1126 }
   1127 
   1128 // static
   1129 void OneClickSigninHelper::RemoveSigninRedirectURLHistoryItem(
   1130     content::WebContents* web_contents) {
   1131   // Only actually remove the item if it's the blank.html continue url.
   1132   if (signin::IsContinueUrlForWebBasedSigninFlow(
   1133           web_contents->GetLastCommittedURL())) {
   1134     new CurrentHistoryCleaner(web_contents);  // will self-destruct when done
   1135   }
   1136 }
   1137 
   1138 // static
   1139 void OneClickSigninHelper::ShowSigninErrorBubble(Browser* browser,
   1140                                                  const std::string& error) {
   1141   DCHECK(!error.empty());
   1142 
   1143   browser->window()->ShowOneClickSigninBubble(
   1144       BrowserWindow::ONE_CLICK_SIGNIN_BUBBLE_TYPE_BUBBLE,
   1145       base::string16(), /* no SAML email */
   1146       base::UTF8ToUTF16(error),
   1147       // This callback is never invoked.
   1148       // TODO(rogerta): Separate out the bubble API so we don't have to pass
   1149       // ignored |email| and |callback| params.
   1150       BrowserWindow::StartSyncCallback());
   1151 }
   1152 
   1153 // static
   1154 bool OneClickSigninHelper::HandleCrossAccountError(
   1155     content::WebContents* contents,
   1156     const std::string& session_index,
   1157     const std::string& email,
   1158     const std::string& password,
   1159     const std::string& refresh_token,
   1160     OneClickSigninHelper::AutoAccept auto_accept,
   1161     signin::Source source,
   1162     OneClickSigninSyncStarter::StartSyncMode start_mode,
   1163     OneClickSigninSyncStarter::Callback sync_callback) {
   1164   Profile* profile =
   1165       Profile::FromBrowserContext(contents->GetBrowserContext());
   1166   std::string last_email =
   1167       profile->GetPrefs()->GetString(prefs::kGoogleServicesLastUsername);
   1168 
   1169   if (!last_email.empty() && !gaia::AreEmailsSame(last_email, email)) {
   1170     // If the new email address is different from the email address that
   1171     // just signed in, show a confirmation dialog.
   1172 
   1173     // No need to display a second confirmation so pass false below.
   1174     // TODO(atwilson): Move this into OneClickSigninSyncStarter.
   1175     // The tab modal dialog always executes its callback before |contents|
   1176     // is deleted.
   1177     Browser* browser = chrome::FindBrowserWithWebContents(contents);
   1178     ConfirmEmailDialogDelegate::AskForConfirmation(
   1179         contents,
   1180         last_email,
   1181         email,
   1182         base::Bind(
   1183             &StartExplicitSync,
   1184             StartSyncArgs(profile, browser, auto_accept,
   1185                           session_index, email, password, refresh_token,
   1186                           contents, false /* confirmation_required */, source,
   1187                           sync_callback),
   1188             contents,
   1189             start_mode));
   1190     return true;
   1191   }
   1192 
   1193   return false;
   1194 }
   1195 
   1196 // static
   1197 void OneClickSigninHelper::RedirectToNtpOrAppsPage(
   1198     content::WebContents* contents, signin::Source source) {
   1199   // Do nothing if a navigation is pending, since this call can be triggered
   1200   // from DidStartLoading. This avoids deleting the pending entry while we are
   1201   // still navigating to it. See crbug/346632.
   1202   if (contents->GetController().GetPendingEntry())
   1203     return;
   1204 
   1205   VLOG(1) << "RedirectToNtpOrAppsPage";
   1206   // Redirect to NTP/Apps page and display a confirmation bubble
   1207   GURL url(source == signin::SOURCE_APPS_PAGE_LINK ?
   1208            chrome::kChromeUIAppsURL : chrome::kChromeUINewTabURL);
   1209   content::OpenURLParams params(url,
   1210                                 content::Referrer(),
   1211                                 CURRENT_TAB,
   1212                                 content::PAGE_TRANSITION_AUTO_TOPLEVEL,
   1213                                 false);
   1214   contents->OpenURL(params);
   1215 }
   1216 
   1217 // static
   1218 void OneClickSigninHelper::RedirectToNtpOrAppsPageIfNecessary(
   1219     content::WebContents* contents, signin::Source source) {
   1220   if (source != signin::SOURCE_SETTINGS &&
   1221       source != signin::SOURCE_WEBSTORE_INSTALL) {
   1222     RedirectToNtpOrAppsPage(contents, source);
   1223   }
   1224 }
   1225 
   1226 void OneClickSigninHelper::RedirectToSignin() {
   1227   VLOG(1) << "OneClickSigninHelper::RedirectToSignin";
   1228 
   1229   // Extract the existing sounce=X value.  Default to "2" if missing.
   1230   signin::Source source = signin::GetSourceForPromoURL(continue_url_);
   1231   if (source == signin::SOURCE_UNKNOWN)
   1232     source = signin::SOURCE_MENU;
   1233   GURL page = signin::GetPromoURL(source, false);
   1234 
   1235   content::WebContents* contents = web_contents();
   1236   contents->GetController().LoadURL(page,
   1237                                     content::Referrer(),
   1238                                     content::PAGE_TRANSITION_AUTO_TOPLEVEL,
   1239                                     std::string());
   1240 }
   1241 
   1242 void OneClickSigninHelper::CleanTransientState() {
   1243   VLOG(1) << "OneClickSigninHelper::CleanTransientState";
   1244   showing_signin_ = false;
   1245   email_.clear();
   1246   password_.clear();
   1247   auto_accept_ = AUTO_ACCEPT_NONE;
   1248   source_ = signin::SOURCE_UNKNOWN;
   1249   switched_to_advanced_ = false;
   1250   continue_url_ = GURL();
   1251   untrusted_navigations_since_signin_visit_ = 0;
   1252   untrusted_confirmation_required_ = false;
   1253   error_message_.clear();
   1254 
   1255   // Post to IO thread to clear pending email.
   1256   if (!do_not_clear_pending_email_) {
   1257     Profile* profile =
   1258         Profile::FromBrowserContext(web_contents()->GetBrowserContext());
   1259     content::BrowserThread::PostTask(
   1260         content::BrowserThread::IO, FROM_HERE,
   1261         base::Bind(&ClearPendingEmailOnIOThread,
   1262                    base::Unretained(profile->GetResourceContext())));
   1263   }
   1264 }
   1265 
   1266 void OneClickSigninHelper::PasswordSubmitted(
   1267     const autofill::PasswordForm& form) {
   1268   // We only need to scrape the password for Gaia logins.
   1269   if (gaia::IsGaiaSignonRealm(GURL(form.signon_realm))) {
   1270     VLOG(1) << "OneClickSigninHelper::DidNavigateAnyFrame: got password";
   1271     password_ = base::UTF16ToUTF8(form.password_value);
   1272   }
   1273 }
   1274 
   1275 void OneClickSigninHelper::SetDoNotClearPendingEmailForTesting() {
   1276   do_not_clear_pending_email_ = true;
   1277 }
   1278 
   1279 void OneClickSigninHelper::set_do_not_start_sync_for_testing() {
   1280   do_not_start_sync_for_testing_ = true;
   1281 }
   1282 
   1283 void OneClickSigninHelper::DidStartNavigationToPendingEntry(
   1284     const GURL& url,
   1285     content::NavigationController::ReloadType reload_type) {
   1286   VLOG(1) << "OneClickSigninHelper::DidStartNavigationToPendingEntry: url=" <<
   1287       url.spec();
   1288   // If the tab navigates to a new page, and this page is not a valid Gaia
   1289   // sign in redirect or reponse, or the expected continue URL, make sure to
   1290   // clear the internal state.  This is needed to detect navigations in the
   1291   // middle of the sign in process that may redirect back to the sign in
   1292   // process (see crbug.com/181163 for details).
   1293   GURL::Replacements replacements;
   1294   replacements.ClearQuery();
   1295 
   1296   if (!IsValidGaiaSigninRedirectOrResponseURL(url) &&
   1297       continue_url_.is_valid() &&
   1298       url.ReplaceComponents(replacements) !=
   1299           continue_url_.ReplaceComponents(replacements)) {
   1300     if (++untrusted_navigations_since_signin_visit_ > kMaxNavigationsSince)
   1301       CleanTransientState();
   1302   }
   1303 }
   1304 
   1305 void OneClickSigninHelper::DidNavigateMainFrame(
   1306     const content::LoadCommittedDetails& details,
   1307     const content::FrameNavigateParams& params) {
   1308   if (!SigninManager::IsWebBasedSigninFlowURL(params.url)) {
   1309     // Make sure the renderer process is no longer considered the trusted
   1310     // sign-in process when a navigation to a non-sign-in URL occurs.
   1311     Profile* profile =
   1312         Profile::FromBrowserContext(web_contents()->GetBrowserContext());
   1313     SigninClient* signin_client =
   1314         profile ? ChromeSigninClientFactory::GetForProfile(profile) : NULL;
   1315     int process_id = web_contents()->GetRenderProcessHost()->GetID();
   1316     if (signin_client && signin_client->IsSigninProcess(process_id))
   1317       signin_client->ClearSigninProcess();
   1318 
   1319     // If the navigation to a non-sign-in URL hasn't been triggered by the web
   1320     // contents, the sign in flow has been aborted and the state must be
   1321     // cleaned (crbug.com/269421).
   1322     if (!content::PageTransitionIsWebTriggerable(params.transition) &&
   1323         auto_accept_ != AUTO_ACCEPT_NONE) {
   1324       CleanTransientState();
   1325     }
   1326   }
   1327 }
   1328 
   1329 void OneClickSigninHelper::DidStopLoading(
   1330     content::RenderViewHost* render_view_host) {
   1331   // If the user left the sign in process, clear all members.
   1332   // TODO(rogerta): might need to allow some youtube URLs.
   1333   content::WebContents* contents = web_contents();
   1334   const GURL url = contents->GetLastCommittedURL();
   1335   Profile* profile =
   1336       Profile::FromBrowserContext(contents->GetBrowserContext());
   1337   VLOG(1) << "OneClickSigninHelper::DidStopLoading: url=" << url.spec();
   1338 
   1339   if (url.scheme() == content::kChromeUIScheme) {
   1340     // Suppresses OneClickSigninHelper on webUI pages to avoid inteference with
   1341     // inline signin flows.
   1342     VLOG(1) << "OneClickSigninHelper::DidStopLoading: suppressed for url="
   1343             << url.spec();
   1344     CleanTransientState();
   1345     return;
   1346   }
   1347 
   1348   // If an error has already occured during the sign in flow, make sure to
   1349   // display it to the user and abort the process.  Do this only for
   1350   // explicit sign ins.
   1351   // TODO(rogerta): Could we move this code back up to ShowInfoBarUIThread()?
   1352   if (!error_message_.empty() && auto_accept_ == AUTO_ACCEPT_EXPLICIT) {
   1353     VLOG(1) << "OneClickSigninHelper::DidStopLoading: error=" << error_message_;
   1354     RemoveSigninRedirectURLHistoryItem(contents);
   1355     // After we redirect to NTP, our browser pointer gets corrupted because the
   1356     // WebContents have changed, so grab the browser pointer
   1357     // before the navigation.
   1358     Browser* browser = chrome::FindBrowserWithWebContents(contents);
   1359 
   1360     // Redirect to the landing page and display an error popup.
   1361     RedirectToNtpOrAppsPage(web_contents(), source_);
   1362     ShowSigninErrorBubble(browser, error_message_);
   1363     CleanTransientState();
   1364     return;
   1365   }
   1366 
   1367   if (AreWeShowingSignin(url, source_, email_)) {
   1368     if (!showing_signin_) {
   1369       if (source_ == signin::SOURCE_UNKNOWN)
   1370         LogOneClickHistogramValue(one_click_signin::HISTOGRAM_SHOWN);
   1371       else
   1372         LogHistogramValue(source_, one_click_signin::HISTOGRAM_SHOWN);
   1373     }
   1374     showing_signin_ = true;
   1375   }
   1376 
   1377   // When Gaia finally redirects to the continue URL, Gaia will add some
   1378   // extra query parameters.  So ignore the parameters when checking to see
   1379   // if the user has continued.  Sometimes locales will redirect to a country-
   1380   // specific TLD so just make sure it's a valid domain instead of comparing
   1381   // for an exact match.
   1382   GURL::Replacements replacements;
   1383   replacements.ClearQuery();
   1384   bool google_domain_url = google_util::IsGoogleDomainUrl(
   1385       url,
   1386       google_util::ALLOW_SUBDOMAIN,
   1387       google_util::DISALLOW_NON_STANDARD_PORTS);
   1388   const bool continue_url_match =
   1389       google_domain_url &&
   1390       url.ReplaceComponents(replacements).path() ==
   1391         continue_url_.ReplaceComponents(replacements).path();
   1392   const bool original_continue_url_match =
   1393       google_domain_url &&
   1394       url.ReplaceComponents(replacements).path() ==
   1395         original_continue_url_.ReplaceComponents(replacements).path();
   1396 
   1397   if (continue_url_match)
   1398     RemoveSigninRedirectURLHistoryItem(contents);
   1399 
   1400   // If there is no valid email yet, there is nothing to do.  As of M26, the
   1401   // password is allowed to be empty, since its no longer required to setup
   1402   // sync.
   1403   if (email_.empty()) {
   1404     VLOG(1) << "OneClickSigninHelper::DidStopLoading: nothing to do";
   1405     // Original-url check done because some user actions cans get us to a page
   1406     // via a POST instead of a GET (and thus to immediate "cuntinue url") but
   1407     // we still want redirects from the "blank.html" landing page to work for
   1408     // non-security related redirects like NTP.
   1409     // https://code.google.com/p/chromium/issues/detail?id=321938
   1410     if (original_continue_url_match) {
   1411       if (auto_accept_ == AUTO_ACCEPT_EXPLICIT)
   1412         RedirectToSignin();
   1413       std::string unused_value;
   1414       if (net::GetValueForKeyInQuery(url, "ntp", &unused_value)) {
   1415         signin::SetUserSkippedPromo(profile);
   1416         RedirectToNtpOrAppsPage(web_contents(), source_);
   1417       }
   1418     } else {
   1419       if (!IsValidGaiaSigninRedirectOrResponseURL(url) &&
   1420           ++untrusted_navigations_since_signin_visit_ > kMaxNavigationsSince) {
   1421         CleanTransientState();
   1422       }
   1423     }
   1424 
   1425     return;
   1426   }
   1427 
   1428   if (!continue_url_match && IsValidGaiaSigninRedirectOrResponseURL(url))
   1429     return;
   1430 
   1431   // During an explicit sign in, if the user has not yet reached the final
   1432   // continue URL, wait for it to arrive. Note that Gaia will add some extra
   1433   // query parameters to the continue URL.  Ignore them when checking to
   1434   // see if the user has continued.
   1435   //
   1436   // If this is not an explicit sign in, we don't need to check if we landed
   1437   // on the right continue URL.  This is important because the continue URL
   1438   // may itself lead to a redirect, which means this function will never see
   1439   // the continue URL go by.
   1440   if (auto_accept_ == AUTO_ACCEPT_EXPLICIT) {
   1441     DCHECK(source_ != signin::SOURCE_UNKNOWN);
   1442     if (!continue_url_match) {
   1443       VLOG(1) << "OneClickSigninHelper::DidStopLoading: invalid url='"
   1444               << url.spec()
   1445               << "' expected continue url=" << continue_url_;
   1446       CleanTransientState();
   1447       return;
   1448     }
   1449 
   1450     // In explicit sign ins, the user may have changed the box
   1451     // "Let me choose what to sync".  This is reflected as a change in the
   1452     // source of the continue URL.  Make one last check of the current URL
   1453     // to see if there is a valid source.  If so, it overrides the
   1454     // current source.
   1455     //
   1456     // If the source was changed to SOURCE_SETTINGS, we want
   1457     // OneClickSigninSyncStarter to reuse the current tab to display the
   1458     // advanced configuration.
   1459     signin::Source source = signin::GetSourceForPromoURL(url);
   1460     if (source != source_) {
   1461       source_ = source;
   1462       switched_to_advanced_ = source == signin::SOURCE_SETTINGS;
   1463     }
   1464   }
   1465 
   1466   Browser* browser = chrome::FindBrowserWithWebContents(contents);
   1467 
   1468   VLOG(1) << "OneClickSigninHelper::DidStopLoading: signin is go."
   1469           << " auto_accept=" << auto_accept_
   1470           << " source=" << source_;
   1471 
   1472   switch (auto_accept_) {
   1473     case AUTO_ACCEPT_NONE:
   1474       if (showing_signin_)
   1475         LogOneClickHistogramValue(one_click_signin::HISTOGRAM_DISMISSED);
   1476       break;
   1477     case AUTO_ACCEPT_ACCEPTED:
   1478       LogOneClickHistogramValue(one_click_signin::HISTOGRAM_ACCEPTED);
   1479       LogOneClickHistogramValue(one_click_signin::HISTOGRAM_WITH_DEFAULTS);
   1480       SigninManager::DisableOneClickSignIn(profile->GetPrefs());
   1481       // Start syncing with the default settings - prompt the user to sign in
   1482       // first.
   1483       if (!do_not_start_sync_for_testing_) {
   1484         StartSync(
   1485             StartSyncArgs(profile, browser, auto_accept_,
   1486                           session_index_, email_, password_, "",
   1487                           NULL  /* don't force sync setup in same tab */,
   1488                           true  /* confirmation_required */, source_,
   1489                           CreateSyncStarterCallback()),
   1490             OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS);
   1491       }
   1492       break;
   1493     case AUTO_ACCEPT_CONFIGURE:
   1494       LogOneClickHistogramValue(one_click_signin::HISTOGRAM_ACCEPTED);
   1495       LogOneClickHistogramValue(one_click_signin::HISTOGRAM_WITH_ADVANCED);
   1496       SigninManager::DisableOneClickSignIn(profile->GetPrefs());
   1497       // Display the extra confirmation (even in the SAML case) in case this
   1498       // was an untrusted renderer.
   1499       if (!do_not_start_sync_for_testing_) {
   1500         StartSync(
   1501             StartSyncArgs(profile, browser, auto_accept_,
   1502                           session_index_, email_, password_, "",
   1503                           NULL  /* don't force sync setup in same tab */,
   1504                           true  /* confirmation_required */, source_,
   1505                           CreateSyncStarterCallback()),
   1506             OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST);
   1507       }
   1508       break;
   1509     case AUTO_ACCEPT_EXPLICIT: {
   1510       signin::Source original_source =
   1511           signin::GetSourceForPromoURL(original_continue_url_);
   1512       if (switched_to_advanced_) {
   1513         LogHistogramValue(original_source,
   1514                           one_click_signin::HISTOGRAM_WITH_ADVANCED);
   1515         LogHistogramValue(original_source,
   1516                           one_click_signin::HISTOGRAM_ACCEPTED);
   1517       } else {
   1518         LogHistogramValue(source_, one_click_signin::HISTOGRAM_ACCEPTED);
   1519         LogHistogramValue(source_, one_click_signin::HISTOGRAM_WITH_DEFAULTS);
   1520       }
   1521 
   1522       // - If sign in was initiated from the NTP or the hotdog menu, sync with
   1523       //   default settings.
   1524       // - If sign in was initiated from the settings page for first time sync
   1525       //   set up, show the advanced sync settings dialog.
   1526       // - If sign in was initiated from the settings page due to a re-auth when
   1527       //   sync was already setup, simply navigate back to the settings page.
   1528       ProfileSyncService* sync_service =
   1529           ProfileSyncServiceFactory::GetForProfile(profile);
   1530       SigninErrorController* error_controller =
   1531           ProfileOAuth2TokenServiceFactory::GetForProfile(profile)->
   1532               signin_error_controller();
   1533 
   1534       OneClickSigninSyncStarter::StartSyncMode start_mode =
   1535           source_ == signin::SOURCE_SETTINGS ?
   1536               (error_controller->HasError() &&
   1537                sync_service && sync_service->HasSyncSetupCompleted()) ?
   1538                   OneClickSigninSyncStarter::SHOW_SETTINGS_WITHOUT_CONFIGURE :
   1539                   OneClickSigninSyncStarter::CONFIGURE_SYNC_FIRST :
   1540               OneClickSigninSyncStarter::SYNC_WITH_DEFAULT_SETTINGS;
   1541 
   1542       if (!HandleCrossAccountError(contents, session_index_, email_, password_,
   1543               "", auto_accept_, source_, start_mode,
   1544               CreateSyncStarterCallback())) {
   1545         if (!do_not_start_sync_for_testing_) {
   1546           StartSync(
   1547               StartSyncArgs(profile, browser, auto_accept_,
   1548                             session_index_, email_, password_, "",
   1549                             contents,
   1550                             untrusted_confirmation_required_, source_,
   1551                             CreateSyncStarterCallback()),
   1552               start_mode);
   1553         }
   1554 
   1555         // If this explicit sign in is not from settings page/webstore, show
   1556         // the NTP/Apps page after sign in completes. In the case of the
   1557         // settings page, it will get auto-closed after sync setup. In the case
   1558         // of webstore, it will redirect back to webstore.
   1559         RedirectToNtpOrAppsPageIfNecessary(web_contents(), source_);
   1560       }
   1561 
   1562       // Observe the sync service if the Webstore tab or the settings tab
   1563       // requested a gaia sign in, so that when sign in and sync setup are
   1564       // successful, we can redirect to the correct URL, or auto-close the gaia
   1565       // sign in tab.
   1566       if (original_source == signin::SOURCE_SETTINGS ||
   1567           (original_source == signin::SOURCE_WEBSTORE_INSTALL &&
   1568            source_ == signin::SOURCE_SETTINGS)) {
   1569         // The observer deletes itself once it's done.
   1570         new OneClickSigninSyncObserver(contents, original_continue_url_);
   1571       }
   1572       break;
   1573     }
   1574     case AUTO_ACCEPT_REJECTED_FOR_PROFILE:
   1575       AddEmailToOneClickRejectedList(profile, email_);
   1576       LogOneClickHistogramValue(one_click_signin::HISTOGRAM_REJECTED);
   1577       break;
   1578     default:
   1579       NOTREACHED() << "Invalid auto_accept=" << auto_accept_;
   1580       break;
   1581   }
   1582 
   1583   CleanTransientState();
   1584 }
   1585 
   1586 OneClickSigninSyncStarter::Callback
   1587     OneClickSigninHelper::CreateSyncStarterCallback() {
   1588   // The callback will only be invoked if this object is still alive when sync
   1589   // setup is completed. This is correct because this object is only deleted
   1590   // when the web contents that potentially shows a blank page is deleted.
   1591   return base::Bind(&OneClickSigninHelper::SyncSetupCompletedCallback,
   1592                     weak_pointer_factory_.GetWeakPtr());
   1593 }
   1594 
   1595 void OneClickSigninHelper::SyncSetupCompletedCallback(
   1596     OneClickSigninSyncStarter::SyncSetupResult result) {
   1597   if (result == OneClickSigninSyncStarter::SYNC_SETUP_FAILURE &&
   1598       web_contents()) {
   1599     GURL current_url = web_contents()->GetVisibleURL();
   1600 
   1601     // If the web contents is showing a blank page and not about to be closed,
   1602     // redirect to the NTP or apps page.
   1603     if (signin::IsContinueUrlForWebBasedSigninFlow(current_url) &&
   1604         !signin::IsAutoCloseEnabledInURL(original_continue_url_)) {
   1605       RedirectToNtpOrAppsPage(
   1606           web_contents(),
   1607           signin::GetSourceForPromoURL(original_continue_url_));
   1608     }
   1609   }
   1610 }
   1611