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