Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / themes / theme_syncable_service.cc
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_system.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 }