sync_setup_flow.cc revision 3f50c38dc070f4bb515c1b64450dae14f316474e
1// Copyright (c) 2010 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/sync/sync_setup_flow.h"
6
7#include "app/gfx/font_util.h"
8#include "base/callback.h"
9#include "base/json/json_reader.h"
10#include "base/json/json_writer.h"
11#include "base/metrics/histogram.h"
12#include "base/string_util.h"
13#include "base/utf_string_conversions.h"
14#include "base/values.h"
15#include "chrome/browser/dom_ui/dom_ui_util.h"
16#include "chrome/browser/platform_util.h"
17#include "chrome/browser/prefs/pref_service.h"
18#include "chrome/browser/profiles/profile.h"
19#include "chrome/browser/renderer_host/render_view_host.h"
20#include "chrome/browser/sync/profile_sync_service.h"
21#include "chrome/browser/tab_contents/tab_contents.h"
22#include "chrome/browser/ui/browser.h"
23#include "chrome/browser/ui/browser_list.h"
24#if defined(OS_MACOSX)
25#include "chrome/browser/ui/cocoa/html_dialog_window_controller_cppsafe.h"
26#endif
27#include "chrome/common/net/gaia/google_service_auth_error.h"
28#include "chrome/common/pref_names.h"
29#include "gfx/font.h"
30#include "grit/locale_settings.h"
31
32// XPath expression for finding specific iframes.
33static const wchar_t* kLoginIFrameXPath = L"//iframe[@id='login']";
34static const wchar_t* kChooseDataTypesIFrameXPath =
35    L"//iframe[@id='configure']";
36static const wchar_t* kPassphraseIFrameXPath =
37    L"//iframe[@id='passphrase']";
38static const wchar_t* kDoneIframeXPath = L"//iframe[@id='done']";
39
40void FlowHandler::RegisterMessages() {
41  dom_ui_->RegisterMessageCallback("SubmitAuth",
42      NewCallback(this, &FlowHandler::HandleSubmitAuth));
43  dom_ui_->RegisterMessageCallback("Configure",
44      NewCallback(this, &FlowHandler::HandleConfigure));
45  dom_ui_->RegisterMessageCallback("Passphrase",
46      NewCallback(this, &FlowHandler::HandlePassphraseEntry));
47  dom_ui_->RegisterMessageCallback("FirstPassphrase",
48      NewCallback(this, &FlowHandler::HandleFirstPassphrase));
49  dom_ui_->RegisterMessageCallback("GoToDashboard",
50      NewCallback(this, &FlowHandler::HandleGoToDashboard));
51}
52
53static bool GetAuthData(const std::string& json,
54                        std::string* username,
55                        std::string* password,
56                        std::string* captcha,
57                        std::string* access_code) {
58  scoped_ptr<Value> parsed_value(base::JSONReader::Read(json, false));
59  if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY))
60    return false;
61
62  DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get());
63  if (!result->GetString("user", username) ||
64      !result->GetString("pass", password) ||
65      !result->GetString("captcha", captcha) ||
66      !result->GetString("access_code", access_code)) {
67      return false;
68  }
69  return true;
70}
71
72bool GetPassphrase(const std::string& json, std::string* passphrase,
73                   std::string* mode) {
74  scoped_ptr<Value> parsed_value(base::JSONReader::Read(json, false));
75  if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY))
76    return false;
77
78  DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get());
79  return result->GetString("passphrase", passphrase) &&
80         result->GetString("mode", mode);
81}
82
83bool GetFirstPassphrase(const std::string& json,
84                        std::string* option,
85                        std::string* passphrase) {
86  scoped_ptr<Value> parsed_value(base::JSONReader::Read(json, false));
87  if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY))
88    return false;
89
90  DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get());
91  return result->GetString("option", option) &&
92         result->GetString("passphrase", passphrase);
93}
94
95static bool GetConfiguration(const std::string& json,
96                             SyncConfiguration* config) {
97  scoped_ptr<Value> parsed_value(base::JSONReader::Read(json, false));
98  if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY))
99    return false;
100
101  DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get());
102  if (!result->GetBoolean("keepEverythingSynced", &config->sync_everything))
103    return false;
104
105  // These values need to be kept in sync with where they are written in
106  // choose_datatypes.html.
107  bool sync_bookmarks;
108  if (!result->GetBoolean("syncBookmarks", &sync_bookmarks))
109    return false;
110  if (sync_bookmarks)
111    config->data_types.insert(syncable::BOOKMARKS);
112
113  bool sync_preferences;
114  if (!result->GetBoolean("syncPreferences", &sync_preferences))
115    return false;
116  if (sync_preferences)
117    config->data_types.insert(syncable::PREFERENCES);
118
119  bool sync_themes;
120  if (!result->GetBoolean("syncThemes", &sync_themes))
121    return false;
122  if (sync_themes)
123    config->data_types.insert(syncable::THEMES);
124
125  bool sync_passwords;
126  if (!result->GetBoolean("syncPasswords", &sync_passwords))
127    return false;
128  if (sync_passwords)
129    config->data_types.insert(syncable::PASSWORDS);
130
131  bool sync_autofill;
132  if (!result->GetBoolean("syncAutofill", &sync_autofill))
133    return false;
134  if (sync_autofill)
135    config->data_types.insert(syncable::AUTOFILL);
136
137  bool sync_extensions;
138  if (!result->GetBoolean("syncExtensions", &sync_extensions))
139    return false;
140  if (sync_extensions)
141    config->data_types.insert(syncable::EXTENSIONS);
142
143  bool sync_typed_urls;
144  if (!result->GetBoolean("syncTypedUrls", &sync_typed_urls))
145    return false;
146  if (sync_typed_urls)
147    config->data_types.insert(syncable::TYPED_URLS);
148
149  bool sync_sessions;
150  if (!result->GetBoolean("syncSessions", &sync_sessions))
151    return false;
152  if (sync_sessions)
153    config->data_types.insert(syncable::SESSIONS);
154
155  bool sync_apps;
156  if (!result->GetBoolean("syncApps", &sync_apps))
157    return false;
158  if (sync_apps)
159    config->data_types.insert(syncable::APPS);
160
161  // Encyption settings.
162  if (!result->GetBoolean("usePassphrase", &config->use_secondary_passphrase))
163    return false;
164  if (config->use_secondary_passphrase &&
165      !result->GetString("passphrase", &config->secondary_passphrase))
166    return false;
167
168  return true;
169}
170
171void FlowHandler::HandleSubmitAuth(const ListValue* args) {
172  std::string json(dom_ui_util::GetJsonResponseFromFirstArgumentInList(args));
173  std::string username, password, captcha, access_code;
174  if (json.empty())
175    return;
176
177  if (!GetAuthData(json, &username, &password, &captcha, &access_code)) {
178    // The page sent us something that we didn't understand.
179    // This probably indicates a programming error.
180    NOTREACHED();
181    return;
182  }
183
184  if (flow_)
185    flow_->OnUserSubmittedAuth(username, password, captcha, access_code);
186}
187
188void FlowHandler::HandleConfigure(const ListValue* args) {
189  std::string json(dom_ui_util::GetJsonResponseFromFirstArgumentInList(args));
190  SyncConfiguration configuration;
191
192  if (json.empty())
193    return;
194
195  if (!GetConfiguration(json, &configuration)) {
196    // The page sent us something that we didn't understand.
197    // This probably indicates a programming error.
198    NOTREACHED();
199    return;
200  }
201
202  DCHECK(flow_);
203  flow_->OnUserConfigured(configuration);
204
205  return;
206}
207
208void FlowHandler::HandlePassphraseEntry(const ListValue* args) {
209  std::string json(dom_ui_util::GetJsonResponseFromFirstArgumentInList(args));
210
211  if (json.empty())
212    return;
213
214  std::string passphrase;
215  std::string mode;
216  if (!GetPassphrase(json, &passphrase, &mode)) {
217    // Couldn't understand what the page sent.  Indicates a programming error.
218    NOTREACHED();
219    return;
220  }
221
222  DCHECK(flow_);
223  flow_->OnPassphraseEntry(passphrase, mode);
224}
225
226void FlowHandler::HandleFirstPassphrase(const ListValue* args) {
227  std::string json(dom_ui_util::GetJsonResponseFromFirstArgumentInList(args));
228
229  if (json.empty())
230    return;
231
232  std::string option;
233  std::string passphrase;
234  if (!GetFirstPassphrase(json, &option, &passphrase)) {
235    // Page sent result which couldn't be parsed.  Programming error.
236    NOTREACHED();
237    return;
238  }
239
240  DCHECK(flow_);
241  flow_->OnFirstPassphraseEntry(option, passphrase);
242}
243
244void FlowHandler::HandleGoToDashboard(const ListValue* args) {
245  flow_->OnGoToDashboard();
246}
247
248// Called by SyncSetupFlow::Advance.
249void FlowHandler::ShowGaiaLogin(const DictionaryValue& args) {
250  // Whenever you start a wizard, you pass in an arg so it starts on the right
251  // iframe (see setup_flow.html's showTheRightIframe() method).  But when you
252  // transition from one flow to another, you have to explicitly call the JS
253  // function to show the next iframe.
254  // So if you ever made a wizard that involved a gaia login as not the first
255  // frame, this call would be necessary to ensure that this method actually
256  // shows the gaia login.
257  if (dom_ui_)
258    dom_ui_->CallJavascriptFunction(L"showGaiaLoginIframe");
259
260  std::string json;
261  base::JSONWriter::Write(&args, false, &json);
262  std::wstring javascript = std::wstring(L"showGaiaLogin") +
263      L"(" + UTF8ToWide(json) + L");";
264  ExecuteJavascriptInIFrame(kLoginIFrameXPath, javascript);
265}
266
267void FlowHandler::ShowGaiaSuccessAndClose() {
268  ExecuteJavascriptInIFrame(kLoginIFrameXPath, L"showGaiaSuccessAndClose();");
269}
270
271void FlowHandler::ShowGaiaSuccessAndSettingUp() {
272  ExecuteJavascriptInIFrame(kLoginIFrameXPath,
273                            L"showGaiaSuccessAndSettingUp();");
274}
275
276// Called by SyncSetupFlow::Advance.
277void FlowHandler::ShowConfigure(const DictionaryValue& args) {
278  // If you're starting the wizard at the configure screen (i.e. from
279  // "Customize Sync"), this will be redundant.  However, if you're coming from
280  // another wizard state, this will make sure Choose Data Types is on top.
281  if (dom_ui_)
282    dom_ui_->CallJavascriptFunction(L"showConfigure");
283
284  std::string json;
285  base::JSONWriter::Write(&args, false, &json);
286  std::wstring javascript = std::wstring(L"initializeConfigureDialog") +
287      L"(" + UTF8ToWide(json) + L");";
288  ExecuteJavascriptInIFrame(kChooseDataTypesIFrameXPath, javascript);
289}
290
291void FlowHandler::ShowPassphraseEntry(const DictionaryValue& args) {
292  if (dom_ui_)
293    dom_ui_->CallJavascriptFunction(L"showPassphrase");
294
295  std::string json;
296  base::JSONWriter::Write(&args, false, &json);
297  std::wstring script = std::wstring(L"setupPassphraseDialog") +
298      L"(" + UTF8ToWide(json) + L");";
299  ExecuteJavascriptInIFrame(kPassphraseIFrameXPath, script);
300}
301
302void FlowHandler::ShowFirstPassphrase(const DictionaryValue& args) {
303  if (dom_ui_)
304    dom_ui_->CallJavascriptFunction(L"showFirstPassphrase");
305
306  std::string json;
307  base::JSONWriter::Write(&args, false, &json);
308  std::wstring script = std::wstring(L"setupDialog") +
309      L"(" + UTF8ToWide(json) + L");";
310  ExecuteJavascriptInIFrame(kPassphraseIFrameXPath, script);
311}
312
313void FlowHandler::ShowSettingUp() {
314  if (dom_ui_)
315    dom_ui_->CallJavascriptFunction(L"showSettingUp");
316}
317
318void FlowHandler::ShowSetupDone(const std::wstring& user) {
319  StringValue synced_to_string(l10n_util::GetStringFUTF8(
320      IDS_SYNC_NTP_SYNCED_TO, WideToUTF16Hack(user)));
321  std::string json;
322  base::JSONWriter::Write(&synced_to_string, false, &json);
323  std::wstring javascript = std::wstring(L"setSyncedToUser") +
324      L"(" + UTF8ToWide(json) + L");";
325  ExecuteJavascriptInIFrame(kDoneIframeXPath, javascript);
326
327  if (dom_ui_)
328    dom_ui_->CallJavascriptFunction(L"showSetupDone", synced_to_string);
329
330  ExecuteJavascriptInIFrame(kDoneIframeXPath,
331                            L"onPageShown();");
332}
333
334void FlowHandler::ShowFirstTimeDone(const std::wstring& user) {
335  ExecuteJavascriptInIFrame(kDoneIframeXPath,
336                            L"setShowFirstTimeSetupSummary();");
337  ShowSetupDone(user);
338}
339
340void FlowHandler::ExecuteJavascriptInIFrame(const std::wstring& iframe_xpath,
341                                            const std::wstring& js) {
342  if (dom_ui_) {
343    RenderViewHost* rvh = dom_ui_->tab_contents()->render_view_host();
344    rvh->ExecuteJavascriptInWebFrame(iframe_xpath, js);
345  }
346}
347
348// Use static Run method to get an instance.
349SyncSetupFlow::SyncSetupFlow(SyncSetupWizard::State start_state,
350                             SyncSetupWizard::State end_state,
351                             const std::string& args,
352                             SyncSetupFlowContainer* container,
353                             ProfileSyncService* service)
354    : container_(container),
355      dialog_start_args_(args),
356      current_state_(start_state),
357      end_state_(end_state),
358      login_start_time_(base::TimeTicks::Now()),
359      flow_handler_(new FlowHandler()),
360      owns_flow_handler_(true),
361      service_(service),
362      html_dialog_window_(NULL) {
363  flow_handler_->set_flow(this);
364}
365
366SyncSetupFlow::~SyncSetupFlow() {
367  flow_handler_->set_flow(NULL);
368  if (owns_flow_handler_) {
369    delete flow_handler_;
370  }
371}
372
373void SyncSetupFlow::GetDialogSize(gfx::Size* size) const {
374  PrefService* prefs = service_->profile()->GetPrefs();
375  gfx::Font approximate_web_font = gfx::Font(
376      UTF8ToWide(prefs->GetString(prefs::kWebKitSansSerifFontFamily)),
377      prefs->GetInteger(prefs::kWebKitDefaultFontSize));
378
379  *size = gfx::GetLocalizedContentsSizeForFont(
380      IDS_SYNC_SETUP_WIZARD_WIDTH_CHARS,
381      IDS_SYNC_SETUP_WIZARD_HEIGHT_LINES,
382      approximate_web_font);
383
384#if defined(OS_MACOSX)
385  // NOTE(akalin): This is a hack to work around a problem with font height on
386  // windows.  Basically font metrics are incorrectly returned in logical units
387  // instead of pixels on Windows.  Logical units are very commonly 96 DPI
388  // so our localized char/line counts are too small by a factor of 96/72.
389  // So we compensate for this on non-windows platform.
390  //
391  // TODO(akalin): Remove this hack once we fix the windows font problem (or at
392  // least work around it in some other place).
393  float scale_hack = 96.f/72.f;
394  size->set_width(size->width() * scale_hack);
395  size->set_height(size->height() * scale_hack);
396#endif
397}
398
399std::string SyncSetupFlow::GetDialogArgs() const {
400  return dialog_start_args_;
401}
402
403// A callback to notify the delegate that the dialog closed.
404void SyncSetupFlow::OnDialogClosed(const std::string& json_retval) {
405  DCHECK(json_retval.empty());
406  container_->set_flow(NULL);  // Sever ties from the wizard.
407  if (current_state_ == SyncSetupWizard::DONE ||
408      current_state_ == SyncSetupWizard::DONE_FIRST_TIME) {
409    service_->SetSyncSetupCompleted();
410  }
411
412  // Record the state at which the user cancelled the signon dialog.
413  switch (current_state_) {
414    case SyncSetupWizard::GAIA_LOGIN:
415      ProfileSyncService::SyncEvent(
416          ProfileSyncService::CANCEL_FROM_SIGNON_WITHOUT_AUTH);
417      break;
418    case SyncSetupWizard::GAIA_SUCCESS:
419      ProfileSyncService::SyncEvent(
420          ProfileSyncService::CANCEL_DURING_SIGNON);
421      break;
422    case SyncSetupWizard::CONFIGURE:
423    case SyncSetupWizard::ENTER_PASSPHRASE:
424    case SyncSetupWizard::SETTING_UP:
425      ProfileSyncService::SyncEvent(
426          ProfileSyncService::CANCEL_DURING_CONFIGURE);
427      break;
428    case SyncSetupWizard::DONE_FIRST_TIME:
429    case SyncSetupWizard::DONE:
430      // TODO(sync): rename this histogram; it's tracking authorization AND
431      // initial sync download time.
432      UMA_HISTOGRAM_MEDIUM_TIMES("Sync.UserPerceivedAuthorizationTime",
433                                 base::TimeTicks::Now() - login_start_time_);
434      break;
435    default:
436      break;
437  }
438
439  service_->OnUserCancelledDialog();
440  delete this;
441}
442
443std::wstring SyncSetupFlow::GetDialogTitle() const {
444  return UTF16ToWideHack(
445      l10n_util::GetStringUTF16(IDS_SYNC_MY_BOOKMARKS_LABEL));
446}
447
448bool SyncSetupFlow::IsDialogModal() const {
449  return false;
450}
451
452bool SyncSetupFlow::ShouldShowDialogTitle() const {
453  return true;
454}
455
456// static
457void SyncSetupFlow::GetArgsForGaiaLogin(const ProfileSyncService* service,
458                                        DictionaryValue* args) {
459  args->SetString("iframeToShow", "login");
460  const GoogleServiceAuthError& error = service->GetAuthError();
461  if (!service->last_attempted_user_email().empty()) {
462    args->SetString("user", service->last_attempted_user_email());
463    args->SetInteger("error", error.state());
464    args->SetBoolean("editable_user", true);
465  } else {
466    string16 user;
467    if (!service->cros_user().empty())
468      user = UTF8ToUTF16(service->cros_user());
469    else
470      user = service->GetAuthenticatedUsername();
471    args->SetString("user", user);
472    args->SetInteger("error", 0);
473    args->SetBoolean("editable_user", user.empty());
474  }
475
476  args->SetString("captchaUrl", error.captcha().image_url.spec());
477}
478
479// static
480void SyncSetupFlow::GetArgsForEnterPassphrase(
481    const ProfileSyncService* service, DictionaryValue* args) {
482  args->SetString("iframeToShow", "passphrase");
483  if (service->IsUsingSecondaryPassphrase())
484    args->SetString("mode", "enter");
485  else
486    args->SetString("mode", "gaia");
487}
488
489// static
490void SyncSetupFlow::GetArgsForConfigure(ProfileSyncService* service,
491                                        DictionaryValue* args) {
492  args->SetString("iframeToShow", "configure");
493
494  // By default start on the data types tab.
495  args->SetString("initialTab", "data-type");
496
497  args->SetBoolean("keepEverythingSynced",
498      service->profile()->GetPrefs()->GetBoolean(prefs::kKeepEverythingSynced));
499
500  // Bookmarks, Preferences, and Themes are launched for good, there's no
501  // going back now.  Check if the other data types are registered though.
502  syncable::ModelTypeSet registered_types;
503  service->GetRegisteredDataTypes(&registered_types);
504  args->SetBoolean("passwordsRegistered",
505      registered_types.count(syncable::PASSWORDS) > 0);
506  args->SetBoolean("autofillRegistered",
507      registered_types.count(syncable::AUTOFILL) > 0);
508  args->SetBoolean("extensionsRegistered",
509      registered_types.count(syncable::EXTENSIONS) > 0);
510  args->SetBoolean("typedUrlsRegistered",
511      registered_types.count(syncable::TYPED_URLS) > 0);
512  args->SetBoolean("appsRegistered",
513      registered_types.count(syncable::APPS) > 0);
514  args->SetBoolean("sessionsRegistered",
515      registered_types.count(syncable::SESSIONS) > 0);
516  args->SetBoolean("syncBookmarks",
517      service->profile()->GetPrefs()->GetBoolean(prefs::kSyncBookmarks));
518  args->SetBoolean("syncPreferences",
519      service->profile()->GetPrefs()->GetBoolean(prefs::kSyncPreferences));
520  args->SetBoolean("syncThemes",
521      service->profile()->GetPrefs()->GetBoolean(prefs::kSyncThemes));
522  args->SetBoolean("syncPasswords",
523      service->profile()->GetPrefs()->GetBoolean(prefs::kSyncPasswords));
524  args->SetBoolean("syncAutofill",
525      service->profile()->GetPrefs()->GetBoolean(prefs::kSyncAutofill));
526  args->SetBoolean("syncExtensions",
527      service->profile()->GetPrefs()->GetBoolean(prefs::kSyncExtensions));
528  args->SetBoolean("syncSessions",
529      service->profile()->GetPrefs()->GetBoolean(prefs::kSyncSessions));
530  args->SetBoolean("syncTypedUrls",
531      service->profile()->GetPrefs()->GetBoolean(prefs::kSyncTypedUrls));
532  args->SetBoolean("syncApps",
533      service->profile()->GetPrefs()->GetBoolean(prefs::kSyncApps));
534
535  // Load the parameters for the encryption tab.
536  args->SetBoolean("usePassphrase", service->IsUsingSecondaryPassphrase());
537}
538
539void SyncSetupFlow::GetDOMMessageHandlers(
540    std::vector<DOMMessageHandler*>* handlers) const {
541  handlers->push_back(flow_handler_);
542  // We don't own flow_handler_ anymore, but it sticks around until at least
543  // right after OnDialogClosed() is called (and this object is destroyed).
544  owns_flow_handler_ = false;
545}
546
547// Returns true if the flow should advance to |state| based on |current_state_|.
548bool SyncSetupFlow::ShouldAdvance(SyncSetupWizard::State state) {
549  switch (state) {
550    case SyncSetupWizard::GAIA_LOGIN:
551      return current_state_ == SyncSetupWizard::FATAL_ERROR ||
552             current_state_ == SyncSetupWizard::GAIA_LOGIN;
553    case SyncSetupWizard::GAIA_SUCCESS:
554      return current_state_ == SyncSetupWizard::GAIA_LOGIN;
555    case SyncSetupWizard::CONFIGURE:
556      return current_state_ == SyncSetupWizard::GAIA_SUCCESS;
557    case SyncSetupWizard::ENTER_PASSPHRASE:
558      return current_state_ == SyncSetupWizard::CONFIGURE ||
559             current_state_ == SyncSetupWizard::SETTING_UP;
560    case SyncSetupWizard::SETUP_ABORTED_BY_PENDING_CLEAR:
561      return current_state_ == SyncSetupWizard::CONFIGURE;
562    case SyncSetupWizard::SETTING_UP:
563      return current_state_ == SyncSetupWizard::CONFIGURE ||
564             current_state_ == SyncSetupWizard::ENTER_PASSPHRASE ||
565             current_state_ == SyncSetupWizard::PASSPHRASE_MIGRATION;
566    case SyncSetupWizard::FATAL_ERROR:
567      return true;  // You can always hit the panic button.
568    case SyncSetupWizard::DONE_FIRST_TIME:
569    case SyncSetupWizard::DONE:
570      return current_state_ == SyncSetupWizard::SETTING_UP ||
571             current_state_ == SyncSetupWizard::ENTER_PASSPHRASE;
572    default:
573      NOTREACHED() << "Unhandled State: " << state;
574      return false;
575  }
576}
577
578void SyncSetupFlow::Advance(SyncSetupWizard::State advance_state) {
579  if (!ShouldAdvance(advance_state)) {
580    LOG(WARNING) << "Invalid state change from "
581                 << current_state_ << " to " << advance_state;
582    return;
583  }
584
585  switch (advance_state) {
586    case SyncSetupWizard::GAIA_LOGIN: {
587      DictionaryValue args;
588      SyncSetupFlow::GetArgsForGaiaLogin(service_, &args);
589      flow_handler_->ShowGaiaLogin(args);
590      break;
591    }
592    case SyncSetupWizard::GAIA_SUCCESS:
593      if (end_state_ == SyncSetupWizard::GAIA_SUCCESS) {
594        flow_handler_->ShowGaiaSuccessAndClose();
595        break;
596      }
597      advance_state = SyncSetupWizard::CONFIGURE;
598      //  Fall through.
599    case SyncSetupWizard::CONFIGURE: {
600      DictionaryValue args;
601      SyncSetupFlow::GetArgsForConfigure(service_, &args);
602      args.SetString("initialTab", "data-type");
603      flow_handler_->ShowConfigure(args);
604      break;
605    }
606    case SyncSetupWizard::ENTER_PASSPHRASE: {
607      DictionaryValue args;
608      SyncSetupFlow::GetArgsForEnterPassphrase(service_, &args);
609      flow_handler_->ShowPassphraseEntry(args);
610      break;
611    }
612    case SyncSetupWizard::PASSPHRASE_MIGRATION: {
613      DictionaryValue args;
614      args.SetString("iframeToShow", "firstpassphrase");
615      flow_handler_->ShowFirstPassphrase(args);
616      break;
617    }
618    case SyncSetupWizard::SETUP_ABORTED_BY_PENDING_CLEAR: {
619      DictionaryValue args;
620      SyncSetupFlow::GetArgsForConfigure(service_, &args);
621      args.SetBoolean("was_aborted", true);
622      flow_handler_->ShowConfigure(args);
623      break;
624    }
625    case SyncSetupWizard::SETTING_UP: {
626      flow_handler_->ShowSettingUp();
627      break;
628    }
629    case SyncSetupWizard::FATAL_ERROR: {
630      // This shows the user the "Could not connect to server" error.
631      // TODO(sync): Update this error messaging.
632      DictionaryValue args;
633      SyncSetupFlow::GetArgsForGaiaLogin(service_, &args);
634      args.SetInteger("error", GoogleServiceAuthError::CONNECTION_FAILED);
635      flow_handler_->ShowGaiaLogin(args);
636      break;
637    }
638    case SyncSetupWizard::DONE_FIRST_TIME:
639      flow_handler_->ShowFirstTimeDone(
640          UTF16ToWide(service_->GetAuthenticatedUsername()));
641      break;
642    case SyncSetupWizard::DONE:
643      flow_handler_->ShowSetupDone(
644          UTF16ToWide(service_->GetAuthenticatedUsername()));
645      break;
646    default:
647      NOTREACHED() << "Invalid advance state: " << advance_state;
648  }
649  current_state_ = advance_state;
650}
651
652void SyncSetupFlow::Focus() {
653#if defined(OS_MACOSX)
654  if (html_dialog_window_) {
655    platform_util::ActivateWindow(html_dialog_window_);
656  }
657#else
658  // TODO(csilv): We don't currently have a way to get the reference to the
659  // dialog on windows/linux.  This can be resolved by a cross platform
660  // implementation of HTML dialogs as described by akalin below.
661  NOTIMPLEMENTED();
662#endif  // defined(OS_MACOSX)
663}
664
665GURL SyncSetupFlow::GetDialogContentURL() const {
666  return GURL("chrome://syncresources/setup");
667}
668
669// static
670SyncSetupFlow* SyncSetupFlow::Run(ProfileSyncService* service,
671                                  SyncSetupFlowContainer* container,
672                                  SyncSetupWizard::State start,
673                                  SyncSetupWizard::State end,
674                                  gfx::NativeWindow parent_window) {
675  DictionaryValue args;
676  if (start == SyncSetupWizard::GAIA_LOGIN)
677    SyncSetupFlow::GetArgsForGaiaLogin(service, &args);
678  else if (start == SyncSetupWizard::CONFIGURE)
679    SyncSetupFlow::GetArgsForConfigure(service, &args);
680  else if (start == SyncSetupWizard::ENTER_PASSPHRASE)
681    SyncSetupFlow::GetArgsForEnterPassphrase(service, &args);
682  else if (start == SyncSetupWizard::PASSPHRASE_MIGRATION)
683    args.SetString("iframeToShow", "firstpassphrase");
684
685  std::string json_args;
686  base::JSONWriter::Write(&args, false, &json_args);
687
688  SyncSetupFlow* flow = new SyncSetupFlow(start, end, json_args,
689      container, service);
690#if defined(OS_MACOSX)
691  // TODO(akalin): Figure out a cleaner way to do this than to have this
692  // gross per-OS behavior, i.e. have a cross-platform ShowHtmlDialog()
693  // function that is not tied to a browser instance.  Note that if we do
694  // that, we'll have to fix sync_setup_wizard_unittest.cc as it relies on
695  // being able to intercept ShowHtmlDialog() calls.
696  flow->html_dialog_window_ =
697      html_dialog_window_controller::ShowHtmlDialog(
698          flow, service->profile());
699#else
700  Browser* b = BrowserList::GetLastActive();
701  if (b) {
702    b->BrowserShowHtmlDialog(flow, parent_window);
703  } else {
704    delete flow;
705    return NULL;
706  }
707#endif  // defined(OS_MACOSX)
708  return flow;
709}
710
711void SyncSetupFlow::OnUserSubmittedAuth(const std::string& username,
712                                        const std::string& password,
713                                        const std::string& captcha,
714                                        const std::string& access_code) {
715  service_->OnUserSubmittedAuth(username, password, captcha, access_code);
716}
717
718void SyncSetupFlow::OnUserConfigured(const SyncConfiguration& configuration) {
719  // Go to the "loading..." screen."
720  Advance(SyncSetupWizard::SETTING_UP);
721
722  // If we are activating the passphrase, we need to have one supplied.
723  DCHECK(service_->IsUsingSecondaryPassphrase() ||
724         !configuration.use_secondary_passphrase ||
725         configuration.secondary_passphrase.length() > 0);
726
727  if (configuration.use_secondary_passphrase &&
728      !service_->IsUsingSecondaryPassphrase()) {
729    service_->SetPassphrase(configuration.secondary_passphrase, true);
730  }
731
732  service_->OnUserChoseDatatypes(configuration.sync_everything,
733                                 configuration.data_types);
734}
735
736void SyncSetupFlow::OnPassphraseEntry(const std::string& passphrase,
737                                      const std::string& mode) {
738  Advance(SyncSetupWizard::SETTING_UP);
739  service_->SetPassphrase(passphrase, true);
740}
741
742void SyncSetupFlow::OnFirstPassphraseEntry(const std::string& option,
743                                           const std::string& passphrase) {
744  Advance(SyncSetupWizard::SETTING_UP);
745  if (option == "explicit") {
746    service_->SetPassphrase(passphrase, true);
747  } else if (option == "nothanks") {
748    // User opted out of encrypted sync, need to turn off encrypted
749    // data types.
750    syncable::ModelTypeSet registered_types;
751    service_->GetRegisteredDataTypes(&registered_types);
752    registered_types.erase(syncable::PASSWORDS);
753    service_->OnUserChoseDatatypes(false, registered_types);
754  } else if (option == "google") {
755    // Implicit passphrase already set up.
756    Advance(SyncSetupWizard::DONE);
757  }
758}
759
760void SyncSetupFlow::OnGoToDashboard() {
761  BrowserList::GetLastActive()->OpenPrivacyDashboardTabAndActivate();
762}
763