Logo Search packages:      
Sourcecode: chromium-browser version File versions  Download package

sync_setup_flow.cc

// 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;
}

Generated by  Doxygen 1.6.0   Back to index