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