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/extension.h"
     13 #include "chrome/common/extensions/manifest_url_handler.h"
     14 #include "chrome/common/extensions/sync_helper.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     ExtensionServiceInterface* 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       if (!extensions_service->IsExtensionEnabled(id)) {
    222         DVLOG(1) << "Theme " << id << " is not enabled; aborting";
    223         return;
    224       }
    225       // An enabled theme extension with the given id was found, so
    226       // just set the current theme to it.
    227       theme_service_->SetTheme(extension);
    228     } else {
    229       // No extension with this id exists -- we must install it; we do
    230       // so by adding it as a pending extension and then triggering an
    231       // auto-update cycle.
    232       const bool kInstallSilently = true;
    233       if (!extensions_service->pending_extension_manager()->AddFromSync(
    234               id, update_url, &IsTheme, kInstallSilently)) {
    235         LOG(WARNING) << "Could not add pending extension for " << id;
    236         return;
    237       }
    238       extensions_service->CheckForUpdatesSoon();
    239     }
    240   } else if (theme_specifics.use_system_theme_by_default()) {
    241     DVLOG(1) << "Switch to use native theme";
    242     theme_service_->SetNativeTheme();
    243   } else {
    244     DVLOG(1) << "Switch to use default theme";
    245     theme_service_->UseDefaultTheme();
    246   }
    247 }
    248 
    249 bool ThemeSyncableService::GetThemeSpecificsFromCurrentTheme(
    250     sync_pb::ThemeSpecifics* theme_specifics) const {
    251   const extensions::Extension* current_theme =
    252       theme_service_->UsingDefaultTheme() ?
    253           NULL :
    254           extensions::ExtensionSystem::Get(profile_)->extension_service()->
    255               GetExtensionById(theme_service_->GetThemeID(), false);
    256   if (current_theme && !extensions::sync_helper::IsSyncable(current_theme)) {
    257     DVLOG(1) << "Ignoring extension from external source: " <<
    258         current_theme->location();
    259     return false;
    260   }
    261   bool use_custom_theme = (current_theme != NULL);
    262   theme_specifics->set_use_custom_theme(use_custom_theme);
    263   if (IsSystemThemeDistinctFromDefaultTheme()) {
    264     // On platform where system theme is different from default theme, set
    265     // use_system_theme_by_default to true if system theme is used, false
    266     // if default system theme is used. Otherwise restore it to value in sync.
    267     if (theme_service_->UsingNativeTheme()) {
    268       theme_specifics->set_use_system_theme_by_default(true);
    269     } else if (theme_service_->UsingDefaultTheme()) {
    270       theme_specifics->set_use_system_theme_by_default(false);
    271     } else {
    272       theme_specifics->set_use_system_theme_by_default(
    273           use_system_theme_by_default_);
    274     }
    275   } else {
    276     // Restore use_system_theme_by_default when platform doesn't distinguish
    277     // between default theme and system theme.
    278     theme_specifics->set_use_system_theme_by_default(
    279         use_system_theme_by_default_);
    280   }
    281 
    282   if (use_custom_theme) {
    283     DCHECK(current_theme);
    284     DCHECK(current_theme->is_theme());
    285     theme_specifics->set_custom_theme_name(current_theme->name());
    286     theme_specifics->set_custom_theme_id(current_theme->id());
    287     theme_specifics->set_custom_theme_update_url(
    288         extensions::ManifestURL::GetUpdateURL(current_theme).spec());
    289   } else {
    290     DCHECK(!current_theme);
    291     theme_specifics->clear_custom_theme_name();
    292     theme_specifics->clear_custom_theme_id();
    293     theme_specifics->clear_custom_theme_update_url();
    294   }
    295   return true;
    296 }
    297 
    298 /* static */
    299 bool ThemeSyncableService::AreThemeSpecificsEqual(
    300     const sync_pb::ThemeSpecifics& a,
    301     const sync_pb::ThemeSpecifics& b,
    302     bool is_system_theme_distinct_from_default_theme) {
    303   if (a.use_custom_theme() != b.use_custom_theme()) {
    304     return false;
    305   }
    306 
    307   if (a.use_custom_theme()) {
    308     // We're using a custom theme, so simply compare IDs since those
    309     // are guaranteed unique.
    310     return a.custom_theme_id() == b.custom_theme_id();
    311   } else if (is_system_theme_distinct_from_default_theme) {
    312     // We're not using a custom theme, but we care about system
    313     // vs. default.
    314     return a.use_system_theme_by_default() == b.use_system_theme_by_default();
    315   } else {
    316     // We're not using a custom theme, and we don't care about system
    317     // vs. default.
    318     return true;
    319   }
    320 }
    321 
    322 syncer::SyncError ThemeSyncableService::ProcessNewTheme(
    323     syncer::SyncChange::SyncChangeType change_type,
    324     const sync_pb::ThemeSpecifics& theme_specifics) {
    325   syncer::SyncChangeList changes;
    326   sync_pb::EntitySpecifics entity_specifics;
    327   entity_specifics.mutable_theme()->CopyFrom(theme_specifics);
    328 
    329   changes.push_back(
    330       syncer::SyncChange(FROM_HERE, change_type,
    331                          syncer::SyncData::CreateLocalData(
    332                              kCurrentThemeClientTag, kCurrentThemeNodeTitle,
    333                              entity_specifics)));
    334 
    335   DVLOG(1) << "Update theme specifics from current theme: "
    336       << changes.back().ToString();
    337 
    338   return sync_processor_->ProcessSyncChanges(FROM_HERE, changes);
    339 }
    340