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