Home | History | Annotate | Download | only in themes
      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/themes/theme_syncable_service.h"
      6 
      7 #include "base/strings/stringprintf.h"
      8 #include "chrome/browser/extensions/extension_service.h"
      9 #include "chrome/browser/extensions/extension_system.h"
     10 #include "chrome/browser/profiles/profile.h"
     11 #include "chrome/browser/themes/theme_service.h"
     12 #include "chrome/common/extensions/manifest_url_handler.h"
     13 #include "chrome/common/extensions/sync_helper.h"
     14 #include "extensions/common/extension.h"
     15 #include "sync/protocol/sync.pb.h"
     16 #include "sync/protocol/theme_specifics.pb.h"
     17 
     18 using std::string;
     19 
     20 namespace {
     21 
     22 bool IsTheme(const extensions::Extension* extension) {
     23   return extension->is_theme();
     24 }
     25 
     26 // TODO(akalin): Remove this.
     27 bool IsSystemThemeDistinctFromDefaultTheme() {
     28 #if defined(TOOLKIT_GTK)
     29   return true;
     30 #else
     31   return false;
     32 #endif
     33 }
     34 
     35 }  // namespace
     36 
     37 const char ThemeSyncableService::kCurrentThemeClientTag[] = "current_theme";
     38 const char ThemeSyncableService::kCurrentThemeNodeTitle[] = "Current Theme";
     39 
     40 ThemeSyncableService::ThemeSyncableService(Profile* profile,
     41                                            ThemeService* theme_service)
     42     : profile_(profile),
     43       theme_service_(theme_service),
     44       use_system_theme_by_default_(false) {
     45   DCHECK(profile_);
     46   DCHECK(theme_service_);
     47 }
     48 
     49 ThemeSyncableService::~ThemeSyncableService() {
     50 }
     51 
     52 void ThemeSyncableService::OnThemeChange() {
     53   if (sync_processor_.get()) {
     54     sync_pb::ThemeSpecifics current_specifics;
     55     if (!GetThemeSpecificsFromCurrentTheme(&current_specifics))
     56       return;  // Current theme is unsyncable.
     57     ProcessNewTheme(syncer::SyncChange::ACTION_UPDATE, current_specifics);
     58     use_system_theme_by_default_ =
     59         current_specifics.use_system_theme_by_default();
     60   }
     61 }
     62 
     63 syncer::SyncMergeResult ThemeSyncableService::MergeDataAndStartSyncing(
     64     syncer::ModelType type,
     65     const syncer::SyncDataList& initial_sync_data,
     66     scoped_ptr<syncer::SyncChangeProcessor> sync_processor,
     67     scoped_ptr<syncer::SyncErrorFactory> error_handler) {
     68   DCHECK(thread_checker_.CalledOnValidThread());
     69   DCHECK(!sync_processor_.get());
     70   DCHECK(sync_processor.get());
     71   DCHECK(error_handler.get());
     72 
     73   syncer::SyncMergeResult merge_result(type);
     74   sync_processor_ = sync_processor.Pass();
     75   sync_error_handler_ = error_handler.Pass();
     76 
     77   if (initial_sync_data.size() > 1) {
     78     sync_error_handler_->CreateAndUploadError(
     79         FROM_HERE,
     80         base::StringPrintf("Received %d theme specifics.",
     81                            static_cast<int>(initial_sync_data.size())));
     82   }
     83 
     84   sync_pb::ThemeSpecifics current_specifics;
     85   if (!GetThemeSpecificsFromCurrentTheme(&current_specifics)) {
     86     // Current theme is unsyncable - don't overwrite from sync data, and don't
     87     // save the unsyncable theme to sync data.
     88     return merge_result;
     89   }
     90 
     91   // Find the last SyncData that has theme data and set the current theme from
     92   // it.
     93   for (syncer::SyncDataList::const_reverse_iterator sync_data =
     94       initial_sync_data.rbegin(); sync_data != initial_sync_data.rend();
     95       ++sync_data) {
     96     if (sync_data->GetSpecifics().has_theme()) {
     97       MaybeSetTheme(current_specifics, *sync_data);
     98       return merge_result;
     99     }
    100   }
    101 
    102   // No theme specifics are found. Create one according to current theme.
    103   merge_result.set_error(ProcessNewTheme(
    104       syncer::SyncChange::ACTION_ADD, current_specifics));
    105   return merge_result;
    106 }
    107 
    108 void ThemeSyncableService::StopSyncing(syncer::ModelType type) {
    109   DCHECK(thread_checker_.CalledOnValidThread());
    110   DCHECK_EQ(type, syncer::THEMES);
    111 
    112   sync_processor_.reset();
    113   sync_error_handler_.reset();
    114 }
    115 
    116 syncer::SyncDataList ThemeSyncableService::GetAllSyncData(
    117     syncer::ModelType type) const {
    118   DCHECK(thread_checker_.CalledOnValidThread());
    119   DCHECK_EQ(type, syncer::THEMES);
    120 
    121   syncer::SyncDataList list;
    122   sync_pb::EntitySpecifics entity_specifics;
    123   if (GetThemeSpecificsFromCurrentTheme(entity_specifics.mutable_theme())) {
    124     list.push_back(syncer::SyncData::CreateLocalData(kCurrentThemeClientTag,
    125                                                      kCurrentThemeNodeTitle,
    126                                                      entity_specifics));
    127   }
    128   return list;
    129 }
    130 
    131 syncer::SyncError ThemeSyncableService::ProcessSyncChanges(
    132     const tracked_objects::Location& from_here,
    133     const syncer::SyncChangeList& change_list) {
    134   DCHECK(thread_checker_.CalledOnValidThread());
    135 
    136   if (!sync_processor_.get()) {
    137     return syncer::SyncError(FROM_HERE,
    138                              syncer::SyncError::DATATYPE_ERROR,
    139                              "Theme syncable service is not started.",
    140                              syncer::THEMES);
    141   }
    142 
    143   // TODO(akalin): Normally, we should only have a single change and
    144   // it should be an update.  However, the syncapi may occasionally
    145   // generates multiple changes.  When we fix syncapi to not do that,
    146   // we can remove the extra logic below.  See:
    147   // http://code.google.com/p/chromium/issues/detail?id=41696 .
    148   if (change_list.size() != 1) {
    149     string err_msg = base::StringPrintf("Received %d theme changes: ",
    150                                         static_cast<int>(change_list.size()));
    151     for (size_t i = 0; i < change_list.size(); ++i) {
    152       base::StringAppendF(&err_msg, "[%s] ", change_list[i].ToString().c_str());
    153     }
    154     sync_error_handler_->CreateAndUploadError(FROM_HERE, err_msg);
    155   } else if (change_list.begin()->change_type() !=
    156           syncer::SyncChange::ACTION_ADD
    157       && change_list.begin()->change_type() !=
    158           syncer::SyncChange::ACTION_UPDATE) {
    159     sync_error_handler_->CreateAndUploadError(
    160         FROM_HERE,
    161         "Invalid theme change: " + change_list.begin()->ToString());
    162   }
    163 
    164   sync_pb::ThemeSpecifics current_specifics;
    165   if (!GetThemeSpecificsFromCurrentTheme(&current_specifics)) {
    166     // Current theme is unsyncable, so don't overwrite it.
    167     return syncer::SyncError();
    168   }
    169 
    170   // Set current theme from the theme specifics of the last change of type
    171   // |ACTION_ADD| or |ACTION_UPDATE|.
    172   for (syncer::SyncChangeList::const_reverse_iterator theme_change =
    173       change_list.rbegin(); theme_change != change_list.rend();
    174       ++theme_change) {
    175     if (theme_change->sync_data().GetSpecifics().has_theme() &&
    176         (theme_change->change_type() == syncer::SyncChange::ACTION_ADD ||
    177             theme_change->change_type() == syncer::SyncChange::ACTION_UPDATE)) {
    178       MaybeSetTheme(current_specifics, theme_change->sync_data());
    179       return syncer::SyncError();
    180     }
    181   }
    182 
    183   return syncer::SyncError(FROM_HERE,
    184                            syncer::SyncError::DATATYPE_ERROR,
    185                            "Didn't find valid theme specifics",
    186                            syncer::THEMES);
    187 }
    188 
    189 void ThemeSyncableService::MaybeSetTheme(
    190     const sync_pb::ThemeSpecifics& current_specs,
    191     const syncer::SyncData& sync_data) {
    192   const sync_pb::ThemeSpecifics& sync_theme = sync_data.GetSpecifics().theme();
    193   use_system_theme_by_default_ = sync_theme.use_system_theme_by_default();
    194   DVLOG(1) << "Set current theme from specifics: " << sync_data.ToString();
    195   if (!AreThemeSpecificsEqual(current_specs, sync_theme,
    196                               IsSystemThemeDistinctFromDefaultTheme())) {
    197     SetCurrentThemeFromThemeSpecifics(sync_theme);
    198   } else {
    199     DVLOG(1) << "Skip setting theme because specs are equal";
    200   }
    201 }
    202 
    203 void ThemeSyncableService::SetCurrentThemeFromThemeSpecifics(
    204     const sync_pb::ThemeSpecifics& theme_specifics) {
    205   if (theme_specifics.use_custom_theme()) {
    206     // TODO(akalin): Figure out what to do about third-party themes
    207     // (i.e., those not on either Google gallery).
    208     string id(theme_specifics.custom_theme_id());
    209     GURL update_url(theme_specifics.custom_theme_update_url());
    210     DVLOG(1) << "Applying theme " << id << " with update_url " << update_url;
    211     ExtensionService* extensions_service =
    212         extensions::ExtensionSystem::Get(profile_)->extension_service();
    213     CHECK(extensions_service);
    214     const extensions::Extension* extension =
    215         extensions_service->GetExtensionById(id, true);
    216     if (extension) {
    217       if (!extension->is_theme()) {
    218         DVLOG(1) << "Extension " << id << " is not a theme; aborting";
    219         return;
    220       }
    221       int disabled_reasons =
    222           extensions_service->extension_prefs()->GetDisableReasons(id);
    223       if (!extensions_service->IsExtensionEnabled(id) &&
    224           disabled_reasons != extensions::Extension::DISABLE_USER_ACTION) {
    225         DVLOG(1) << "Theme " << id << " is disabled with reason "
    226                  << disabled_reasons << "; aborting";
    227         return;
    228       }
    229       // An enabled theme extension with the given id was found, so
    230       // just set the current theme to it.
    231       theme_service_->SetTheme(extension);
    232     } else {
    233       // No extension with this id exists -- we must install it; we do
    234       // so by adding it as a pending extension and then triggering an
    235       // auto-update cycle.
    236       const bool kInstallSilently = true;
    237       if (!extensions_service->pending_extension_manager()->AddFromSync(
    238               id, update_url, &IsTheme, kInstallSilently)) {
    239         LOG(WARNING) << "Could not add pending extension for " << id;
    240         return;
    241       }
    242       extensions_service->CheckForUpdatesSoon();
    243     }
    244   } else if (theme_specifics.use_system_theme_by_default()) {
    245     DVLOG(1) << "Switch to use native theme";
    246     theme_service_->SetNativeTheme();
    247   } else {
    248     DVLOG(1) << "Switch to use default theme";
    249     theme_service_->UseDefaultTheme();
    250   }
    251 }
    252 
    253 bool ThemeSyncableService::GetThemeSpecificsFromCurrentTheme(
    254     sync_pb::ThemeSpecifics* theme_specifics) const {
    255   const extensions::Extension* current_theme =
    256       theme_service_->UsingDefaultTheme() ?
    257           NULL :
    258           extensions::ExtensionSystem::Get(profile_)->extension_service()->
    259               GetExtensionById(theme_service_->GetThemeID(), false);
    260   if (current_theme && !extensions::sync_helper::IsSyncable(current_theme)) {
    261     DVLOG(1) << "Ignoring extension from external source: " <<
    262         current_theme->location();
    263     return false;
    264   }
    265   bool use_custom_theme = (current_theme != NULL);
    266   theme_specifics->set_use_custom_theme(use_custom_theme);
    267   if (IsSystemThemeDistinctFromDefaultTheme()) {
    268     // On platform where system theme is different from default theme, set
    269     // use_system_theme_by_default to true if system theme is used, false
    270     // if default system theme is used. Otherwise restore it to value in sync.
    271     if (theme_service_->UsingNativeTheme()) {
    272       theme_specifics->set_use_system_theme_by_default(true);
    273     } else if (theme_service_->UsingDefaultTheme()) {
    274       theme_specifics->set_use_system_theme_by_default(false);
    275     } else {
    276       theme_specifics->set_use_system_theme_by_default(
    277           use_system_theme_by_default_);
    278     }
    279   } else {
    280     // Restore use_system_theme_by_default when platform doesn't distinguish
    281     // between default theme and system theme.
    282     theme_specifics->set_use_system_theme_by_default(
    283         use_system_theme_by_default_);
    284   }
    285 
    286   if (use_custom_theme) {
    287     DCHECK(current_theme);
    288     DCHECK(current_theme->is_theme());
    289     theme_specifics->set_custom_theme_name(current_theme->name());
    290     theme_specifics->set_custom_theme_id(current_theme->id());
    291     theme_specifics->set_custom_theme_update_url(
    292         extensions::ManifestURL::GetUpdateURL(current_theme).spec());
    293   } else {
    294     DCHECK(!current_theme);
    295     theme_specifics->clear_custom_theme_name();
    296     theme_specifics->clear_custom_theme_id();
    297     theme_specifics->clear_custom_theme_update_url();
    298   }
    299   return true;
    300 }
    301 
    302 /* static */
    303 bool ThemeSyncableService::AreThemeSpecificsEqual(
    304     const sync_pb::ThemeSpecifics& a,
    305     const sync_pb::ThemeSpecifics& b,
    306     bool is_system_theme_distinct_from_default_theme) {
    307   if (a.use_custom_theme() != b.use_custom_theme()) {
    308     return false;
    309   }
    310 
    311   if (a.use_custom_theme()) {
    312     // We're using a custom theme, so simply compare IDs since those
    313     // are guaranteed unique.
    314     return a.custom_theme_id() == b.custom_theme_id();
    315   } else if (is_system_theme_distinct_from_default_theme) {
    316     // We're not using a custom theme, but we care about system
    317     // vs. default.
    318     return a.use_system_theme_by_default() == b.use_system_theme_by_default();
    319   } else {
    320     // We're not using a custom theme, and we don't care about system
    321     // vs. default.
    322     return true;
    323   }
    324 }
    325 
    326 syncer::SyncError ThemeSyncableService::ProcessNewTheme(
    327     syncer::SyncChange::SyncChangeType change_type,
    328     const sync_pb::ThemeSpecifics& theme_specifics) {
    329   syncer::SyncChangeList changes;
    330   sync_pb::EntitySpecifics entity_specifics;
    331   entity_specifics.mutable_theme()->CopyFrom(theme_specifics);
    332 
    333   changes.push_back(
    334       syncer::SyncChange(FROM_HERE, change_type,
    335                          syncer::SyncData::CreateLocalData(
    336                              kCurrentThemeClientTag, kCurrentThemeNodeTitle,
    337                              entity_specifics)));
    338 
    339   DVLOG(1) << "Update theme specifics from current theme: "
    340       << changes.back().ToString();
    341 
    342   return sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
    343 }
    344