summaryrefslogtreecommitdiffstats
path: root/chrome/browser/sync/sync_setup_flow.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/sync/sync_setup_flow.cc')
-rw-r--r--chrome/browser/sync/sync_setup_flow.cc503
1 files changed, 503 insertions, 0 deletions
diff --git a/chrome/browser/sync/sync_setup_flow.cc b/chrome/browser/sync/sync_setup_flow.cc
new file mode 100644
index 0000000..5eb62d6
--- /dev/null
+++ b/chrome/browser/sync/sync_setup_flow.cc
@@ -0,0 +1,503 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/sync/sync_setup_flow.h"
+
+#include "app/gfx/font_util.h"
+#include "base/callback.h"
+#include "base/histogram.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/browser.h"
+#include "chrome/browser/browser_list.h"
+#if defined(OS_MACOSX)
+#include "chrome/browser/cocoa/html_dialog_window_controller_cppsafe.h"
+#endif
+#include "chrome/browser/dom_ui/dom_ui_util.h"
+#include "chrome/browser/google_service_auth_error.h"
+#include "chrome/browser/platform_util.h"
+#include "chrome/browser/pref_service.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/sync/profile_sync_service.h"
+#include "chrome/browser/sync/syncable/model_type.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/common/pref_names.h"
+#include "gfx/font.h"
+#include "grit/locale_settings.h"
+
+// XPath expression for finding specific iframes.
+static const wchar_t* kLoginIFrameXPath = L"//iframe[@id='login']";
+static const wchar_t* kChooseDataTypesIFrameXPath =
+ L"//iframe[@id='choose_data_types']";
+static const wchar_t* kDoneIframeXPath = L"//iframe[@id='done']";
+
+void FlowHandler::RegisterMessages() {
+ dom_ui_->RegisterMessageCallback("SubmitAuth",
+ NewCallback(this, &FlowHandler::HandleSubmitAuth));
+ dom_ui_->RegisterMessageCallback("ChooseDataTypes",
+ NewCallback(this, &FlowHandler::HandleChooseDataTypes));
+}
+
+static bool GetAuthData(const std::string& json,
+ std::string* username, std::string* password, std::string* captcha) {
+ scoped_ptr<Value> parsed_value(base::JSONReader::Read(json, false));
+ if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY))
+ return false;
+
+ DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get());
+ if (!result->GetString(L"user", username) ||
+ !result->GetString(L"pass", password) ||
+ !result->GetString(L"captcha", captcha)) {
+ return false;
+ }
+ return true;
+}
+
+static bool GetDataTypeChoiceData(const std::string& json,
+ bool* sync_everything, syncable::ModelTypeSet* data_types) {
+ scoped_ptr<Value> parsed_value(base::JSONReader::Read(json, false));
+ if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY))
+ return false;
+
+ DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get());
+ if (!result->GetBoolean(L"keepEverythingSynced", sync_everything))
+ return false;
+
+ // These values need to be kept in sync with where they are written in
+ // choose_datatypes.html.
+ bool sync_bookmarks;
+ if (!result->GetBoolean(L"syncBookmarks", &sync_bookmarks))
+ return false;
+ if (sync_bookmarks)
+ data_types->insert(syncable::BOOKMARKS);
+
+ bool sync_preferences;
+ if (!result->GetBoolean(L"syncPreferences", &sync_preferences))
+ return false;
+ if (sync_preferences)
+ data_types->insert(syncable::PREFERENCES);
+
+ bool sync_themes;
+ if (!result->GetBoolean(L"syncThemes", &sync_themes))
+ return false;
+ if (sync_themes)
+ data_types->insert(syncable::THEMES);
+
+ bool sync_passwords;
+ if (!result->GetBoolean(L"syncPasswords", &sync_passwords))
+ return false;
+ if (sync_passwords)
+ data_types->insert(syncable::PASSWORDS);
+
+ bool sync_autofill;
+ if (!result->GetBoolean(L"syncAutofill", &sync_autofill))
+ return false;
+ if (sync_autofill)
+ data_types->insert(syncable::AUTOFILL);
+
+ bool sync_extensions;
+ if (!result->GetBoolean(L"syncExtensions", &sync_extensions))
+ return false;
+ if (sync_extensions)
+ data_types->insert(syncable::EXTENSIONS);
+
+ bool sync_typed_urls;
+ if (!result->GetBoolean(L"syncTypedUrls", &sync_typed_urls))
+ return false;
+ if (sync_typed_urls)
+ data_types->insert(syncable::TYPED_URLS);
+
+ return true;
+}
+
+void FlowHandler::HandleSubmitAuth(const Value* value) {
+ std::string json(dom_ui_util::GetJsonResponseFromFirstArgumentInList(value));
+ std::string username, password, captcha;
+ if (json.empty())
+ return;
+
+ if (!GetAuthData(json, &username, &password, &captcha)) {
+ // The page sent us something that we didn't understand.
+ // This probably indicates a programming error.
+ NOTREACHED();
+ return;
+ }
+
+ if (flow_)
+ flow_->OnUserSubmittedAuth(username, password, captcha);
+}
+
+void FlowHandler::HandleChooseDataTypes(const Value* value) {
+ std::string json(dom_ui_util::GetJsonResponseFromFirstArgumentInList(value));
+ bool sync_everything;
+ syncable::ModelTypeSet chosen_types;
+ if (json.empty())
+ return;
+
+ if (!GetDataTypeChoiceData(json, &sync_everything, &chosen_types)) {
+ // The page sent us something that we didn't understand.
+ // This probably indicates a programming error.
+ NOTREACHED();
+ return;
+ }
+
+ DCHECK(flow_);
+ flow_->OnUserChoseDataTypes(sync_everything, chosen_types);
+
+ return;
+}
+
+// Called by SyncSetupFlow::Advance.
+void FlowHandler::ShowGaiaLogin(const DictionaryValue& args) {
+ // Whenever you start a wizard, you pass in an arg so it starts on the right
+ // iframe (see setup_flow.html's showTheRightIframe() method). But when you
+ // transition from one flow to another, you have to explicitly call the JS
+ // function to show the next iframe.
+ // So if you ever made a wizard that involved a gaia login as not the first
+ // frame, this call would be necessary to ensure that this method actually
+ // shows the gaia login.
+ if (dom_ui_)
+ dom_ui_->CallJavascriptFunction(L"showGaiaLoginIframe");
+
+ std::string json;
+ base::JSONWriter::Write(&args, false, &json);
+ std::wstring javascript = std::wstring(L"showGaiaLogin") +
+ L"(" + UTF8ToWide(json) + L");";
+ ExecuteJavascriptInIFrame(kLoginIFrameXPath, javascript);
+}
+
+void FlowHandler::ShowGaiaSuccessAndClose() {
+ ExecuteJavascriptInIFrame(kLoginIFrameXPath, L"showGaiaSuccessAndClose();");
+}
+
+void FlowHandler::ShowGaiaSuccessAndSettingUp() {
+ ExecuteJavascriptInIFrame(kLoginIFrameXPath,
+ L"showGaiaSuccessAndSettingUp();");
+}
+
+// Called by SyncSetupFlow::Advance.
+void FlowHandler::ShowChooseDataTypes(const DictionaryValue& args) {
+
+ // If you're starting the wizard at the Choose Data Types screen (i.e. from
+ // "Customize Sync"), this will be redundant. However, if you're coming from
+ // another wizard state, this will make sure Choose Data Types is on top.
+ if (dom_ui_)
+ dom_ui_->CallJavascriptFunction(L"showChooseDataTypes");
+
+ std::string json;
+ base::JSONWriter::Write(&args, false, &json);
+ std::wstring javascript = std::wstring(L"setCheckboxesAndErrors") +
+ L"(" + UTF8ToWide(json) + L");";
+ ExecuteJavascriptInIFrame(kChooseDataTypesIFrameXPath, javascript);
+}
+
+void FlowHandler::ShowSetupDone(const std::wstring& user) {
+ StringValue synced_to_string(WideToUTF8(l10n_util::GetStringF(
+ IDS_SYNC_NTP_SYNCED_TO, user)));
+ std::string json;
+ base::JSONWriter::Write(&synced_to_string, false, &json);
+ std::wstring javascript = std::wstring(L"setSyncedToUser") +
+ L"(" + UTF8ToWide(json) + L");";
+ ExecuteJavascriptInIFrame(kDoneIframeXPath, javascript);
+
+ if (dom_ui_)
+ dom_ui_->CallJavascriptFunction(L"showSetupDone", synced_to_string);
+
+ ExecuteJavascriptInIFrame(kDoneIframeXPath,
+ L"onPageShown();");
+}
+
+void FlowHandler::ShowFirstTimeDone(const std::wstring& user) {
+ ExecuteJavascriptInIFrame(kDoneIframeXPath,
+ L"setShowFirstTimeSetupSummary();");
+ ShowSetupDone(user);
+}
+
+void FlowHandler::ExecuteJavascriptInIFrame(const std::wstring& iframe_xpath,
+ const std::wstring& js) {
+ if (dom_ui_) {
+ RenderViewHost* rvh = dom_ui_->tab_contents()->render_view_host();
+ rvh->ExecuteJavascriptInWebFrame(iframe_xpath, js);
+ }
+}
+
+// Use static Run method to get an instance.
+SyncSetupFlow::SyncSetupFlow(SyncSetupWizard::State start_state,
+ SyncSetupWizard::State end_state,
+ const std::string& args,
+ SyncSetupFlowContainer* container,
+ ProfileSyncService* service)
+ : container_(container),
+ dialog_start_args_(args),
+ current_state_(start_state),
+ end_state_(end_state),
+ login_start_time_(base::TimeTicks::Now()),
+ flow_handler_(new FlowHandler()),
+ owns_flow_handler_(true),
+ service_(service),
+ html_dialog_window_(NULL) {
+ flow_handler_->set_flow(this);
+}
+
+SyncSetupFlow::~SyncSetupFlow() {
+ flow_handler_->set_flow(NULL);
+ if (owns_flow_handler_) {
+ delete flow_handler_;
+ }
+}
+
+void SyncSetupFlow::GetDialogSize(gfx::Size* size) const {
+ PrefService* prefs = service_->profile()->GetPrefs();
+ gfx::Font approximate_web_font = gfx::Font::CreateFont(
+ UTF8ToWide(prefs->GetString(prefs::kWebKitSansSerifFontFamily)),
+ prefs->GetInteger(prefs::kWebKitDefaultFontSize));
+
+ *size = gfx::GetLocalizedContentsSizeForFont(
+ IDS_SYNC_SETUP_WIZARD_WIDTH_CHARS,
+ IDS_SYNC_SETUP_WIZARD_HEIGHT_LINES,
+ approximate_web_font);
+
+#if defined(OS_MACOSX)
+ // NOTE(akalin): This is a hack to work around a problem with font height on
+ // windows. Basically font metrics are incorrectly returned in logical units
+ // instead of pixels on Windows. Logical units are very commonly 96 DPI
+ // so our localized char/line counts are too small by a factor of 96/72.
+ // So we compensate for this on non-windows platform.
+ //
+ // TODO(akalin): Remove this hack once we fix the windows font problem (or at
+ // least work around it in some other place).
+ float scale_hack = 96.f/72.f;
+ size->set_width(size->width() * scale_hack);
+ size->set_height(size->height() * scale_hack);
+#endif
+}
+
+// A callback to notify the delegate that the dialog closed.
+void SyncSetupFlow::OnDialogClosed(const std::string& json_retval) {
+ DCHECK(json_retval.empty());
+ container_->set_flow(NULL); // Sever ties from the wizard.
+ if (current_state_ == SyncSetupWizard::DONE ||
+ current_state_ == SyncSetupWizard::DONE_FIRST_TIME) {
+ service_->SetSyncSetupCompleted();
+ }
+
+ // Record the state at which the user cancelled the signon dialog.
+ switch (current_state_) {
+ case SyncSetupWizard::GAIA_LOGIN:
+ ProfileSyncService::SyncEvent(
+ ProfileSyncService::CANCEL_FROM_SIGNON_WITHOUT_AUTH);
+ break;
+ case SyncSetupWizard::GAIA_SUCCESS:
+ ProfileSyncService::SyncEvent(
+ ProfileSyncService::CANCEL_DURING_SIGNON);
+ break;
+ case SyncSetupWizard::CHOOSE_DATA_TYPES:
+ ProfileSyncService::SyncEvent(
+ ProfileSyncService::CANCEL_FROM_CHOOSE_DATA_TYPES);
+ break;
+ case SyncSetupWizard::DONE_FIRST_TIME:
+ case SyncSetupWizard::DONE:
+ // TODO(sync): rename this histogram; it's tracking authorization AND
+ // initial sync download time.
+ UMA_HISTOGRAM_MEDIUM_TIMES("Sync.UserPerceivedAuthorizationTime",
+ base::TimeTicks::Now() - login_start_time_);
+ break;
+ default:
+ break;
+ }
+
+ service_->OnUserCancelledDialog();
+ delete this;
+}
+
+// static
+void SyncSetupFlow::GetArgsForGaiaLogin(const ProfileSyncService* service,
+ DictionaryValue* args) {
+ args->SetString(L"iframeToShow", "login");
+ const GoogleServiceAuthError& error = service->GetAuthError();
+ if (!service->last_attempted_user_email().empty()) {
+ args->SetString(L"user", service->last_attempted_user_email());
+ args->SetInteger(L"error", error.state());
+ } else {
+ std::wstring user(UTF16ToWide(service->GetAuthenticatedUsername()));
+ args->SetString(L"user", user);
+ args->SetInteger(L"error", user.empty() ? 0 : error.state());
+ }
+
+ args->SetString(L"captchaUrl", error.captcha().image_url.spec());
+}
+
+// static
+void SyncSetupFlow::GetArgsForChooseDataTypes(ProfileSyncService* service,
+ DictionaryValue* args) {
+ args->SetString(L"iframeToShow", "choose_data_types");
+ args->SetBoolean(L"keepEverythingSynced",
+ service->profile()->GetPrefs()->GetBoolean(prefs::kKeepEverythingSynced));
+
+ // Bookmarks, Preferences, and Themes are launched for good, there's no
+ // going back now. Check if the other data types are registered though.
+ syncable::ModelTypeSet registered_types;
+ service->GetRegisteredDataTypes(&registered_types);
+ args->SetBoolean(L"passwordsRegistered",
+ registered_types.count(syncable::PASSWORDS) > 0);
+ args->SetBoolean(L"autofillRegistered",
+ registered_types.count(syncable::AUTOFILL) > 0);
+ args->SetBoolean(L"extensionsRegistered",
+ registered_types.count(syncable::EXTENSIONS) > 0);
+ args->SetBoolean(L"typedUrlsRegistered",
+ registered_types.count(syncable::TYPED_URLS) > 0);
+
+ args->SetBoolean(L"syncBookmarks",
+ service->profile()->GetPrefs()->GetBoolean(prefs::kSyncBookmarks));
+ args->SetBoolean(L"syncPreferences",
+ service->profile()->GetPrefs()->GetBoolean(prefs::kSyncPreferences));
+ args->SetBoolean(L"syncThemes",
+ service->profile()->GetPrefs()->GetBoolean(prefs::kSyncThemes));
+ args->SetBoolean(L"syncPasswords",
+ service->profile()->GetPrefs()->GetBoolean(prefs::kSyncPasswords));
+ args->SetBoolean(L"syncAutofill",
+ service->profile()->GetPrefs()->GetBoolean(prefs::kSyncAutofill));
+ args->SetBoolean(L"syncExtensions",
+ service->profile()->GetPrefs()->GetBoolean(prefs::kSyncExtensions));
+ args->SetBoolean(L"syncTypedUrls",
+ service->profile()->GetPrefs()->GetBoolean(prefs::kSyncTypedUrls));
+}
+
+void SyncSetupFlow::GetDOMMessageHandlers(
+ std::vector<DOMMessageHandler*>* handlers) const {
+ handlers->push_back(flow_handler_);
+ // We don't own flow_handler_ anymore, but it sticks around until at least
+ // right after OnDialogClosed() is called (and this object is destroyed).
+ owns_flow_handler_ = false;
+}
+
+bool SyncSetupFlow::ShouldAdvance(SyncSetupWizard::State state) {
+ switch (state) {
+ case SyncSetupWizard::GAIA_LOGIN:
+ return current_state_ == SyncSetupWizard::FATAL_ERROR ||
+ current_state_ == SyncSetupWizard::GAIA_LOGIN;
+ case SyncSetupWizard::GAIA_SUCCESS:
+ return current_state_ == SyncSetupWizard::GAIA_LOGIN;
+ case SyncSetupWizard::CHOOSE_DATA_TYPES:
+ return current_state_ == SyncSetupWizard::GAIA_SUCCESS;
+ case SyncSetupWizard::SETUP_ABORTED_BY_PENDING_CLEAR:
+ return current_state_ == SyncSetupWizard::CHOOSE_DATA_TYPES;
+ case SyncSetupWizard::FATAL_ERROR:
+ return true; // You can always hit the panic button.
+ case SyncSetupWizard::DONE_FIRST_TIME:
+ case SyncSetupWizard::DONE:
+ return current_state_ == SyncSetupWizard::CHOOSE_DATA_TYPES;
+ default:
+ NOTREACHED() << "Unhandled State: " << state;
+ return false;
+ }
+}
+
+void SyncSetupFlow::Advance(SyncSetupWizard::State advance_state) {
+ if (!ShouldAdvance(advance_state))
+ return;
+
+ switch (advance_state) {
+ case SyncSetupWizard::GAIA_LOGIN: {
+ DictionaryValue args;
+ SyncSetupFlow::GetArgsForGaiaLogin(service_, &args);
+ flow_handler_->ShowGaiaLogin(args);
+ break;
+ }
+ case SyncSetupWizard::GAIA_SUCCESS:
+ if (end_state_ == SyncSetupWizard::GAIA_SUCCESS) {
+ flow_handler_->ShowGaiaSuccessAndClose();
+ break;
+ }
+ advance_state = SyncSetupWizard::CHOOSE_DATA_TYPES;
+ // Fall through.
+ case SyncSetupWizard::CHOOSE_DATA_TYPES: {
+ DictionaryValue args;
+ SyncSetupFlow::GetArgsForChooseDataTypes(service_, &args);
+ flow_handler_->ShowChooseDataTypes(args);
+ break;
+ }
+ case SyncSetupWizard::SETUP_ABORTED_BY_PENDING_CLEAR: {
+ DictionaryValue args;
+ SyncSetupFlow::GetArgsForChooseDataTypes(service_, &args);
+ args.SetBoolean(L"was_aborted", true);
+ flow_handler_->ShowChooseDataTypes(args);
+ break;
+ }
+ case SyncSetupWizard::FATAL_ERROR: {
+ // This shows the user the "Could not connect to server" error.
+ // TODO(sync): Update this error messaging.
+ DictionaryValue args;
+ SyncSetupFlow::GetArgsForGaiaLogin(service_, &args);
+ args.SetInteger(L"error", GoogleServiceAuthError::CONNECTION_FAILED);
+ flow_handler_->ShowGaiaLogin(args);
+ break;
+ }
+ case SyncSetupWizard::DONE_FIRST_TIME:
+ flow_handler_->ShowFirstTimeDone(
+ UTF16ToWide(service_->GetAuthenticatedUsername()));
+ break;
+ case SyncSetupWizard::DONE:
+ flow_handler_->ShowSetupDone(
+ UTF16ToWide(service_->GetAuthenticatedUsername()));
+ break;
+ default:
+ NOTREACHED() << "Invalid advance state: " << advance_state;
+ }
+ current_state_ = advance_state;
+}
+
+void SyncSetupFlow::Focus() {
+#if defined(OS_MACOSX)
+ if (html_dialog_window_) {
+ platform_util::ActivateWindow(html_dialog_window_);
+ }
+#else
+ // TODO(csilv): We don't currently have a way to get the reference to the
+ // dialog on windows/linux. This can be resolved by a cross platform
+ // implementation of HTML dialogs as described by akalin below.
+ NOTIMPLEMENTED();
+#endif // defined(OS_MACOSX)
+}
+
+// static
+SyncSetupFlow* SyncSetupFlow::Run(ProfileSyncService* service,
+ SyncSetupFlowContainer* container,
+ SyncSetupWizard::State start,
+ SyncSetupWizard::State end,
+ gfx::NativeWindow parent_window) {
+ DictionaryValue args;
+ if (start == SyncSetupWizard::GAIA_LOGIN)
+ SyncSetupFlow::GetArgsForGaiaLogin(service, &args);
+ else if (start == SyncSetupWizard::CHOOSE_DATA_TYPES)
+ SyncSetupFlow::GetArgsForChooseDataTypes(service, &args);
+ std::string json_args;
+ base::JSONWriter::Write(&args, false, &json_args);
+
+ SyncSetupFlow* flow = new SyncSetupFlow(start, end, json_args,
+ container, service);
+#if defined(OS_MACOSX)
+ // TODO(akalin): Figure out a cleaner way to do this than to have this
+ // gross per-OS behavior, i.e. have a cross-platform ShowHtmlDialog()
+ // function that is not tied to a browser instance. Note that if we do
+ // that, we'll have to fix sync_setup_wizard_unittest.cc as it relies on
+ // being able to intercept ShowHtmlDialog() calls.
+ flow->html_dialog_window_ =
+ html_dialog_window_controller::ShowHtmlDialog(
+ flow, service->profile());
+#else
+ Browser* b = BrowserList::GetLastActive();
+ if (b) {
+ b->BrowserShowHtmlDialog(flow, parent_window);
+ } else {
+ delete flow;
+ return NULL;
+ }
+#endif // defined(OS_MACOSX)
+ return flow;
+}