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.
5 #include "chrome/browser/extensions/extension_sync_data.h"
7 #include "base/logging.h"
8 #include "base/metrics/histogram_macros.h"
9 #include "base/strings/stringprintf.h"
10 #include "chrome/browser/extensions/extension_service.h"
11 #include "chrome/common/extensions/manifest_handlers/app_icon_color_info.h"
12 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
13 #include "chrome/common/extensions/manifest_handlers/linked_app_icons.h"
14 #include "components/crx_file/id_util.h"
15 #include "extensions/common/extension.h"
16 #include "extensions/common/manifest_url_handlers.h"
17 #include "sync/api/sync_data.h"
18 #include "sync/protocol/app_specifics.pb.h"
19 #include "sync/protocol/extension_specifics.pb.h"
20 #include "sync/protocol/sync.pb.h"
22 using syncer::StringOrdinal
;
24 namespace extensions
{
28 std::string
GetExtensionSpecificsLogMessage(
29 const sync_pb::ExtensionSpecifics
& specifics
) {
30 return base::StringPrintf(
31 "id: %s\nversion: %s\nupdate_url: %s\nenabled: %i\ndisable_reasons: %i",
32 specifics
.id().c_str(),
33 specifics
.version().c_str(),
34 specifics
.update_url().c_str(),
36 specifics
.disable_reasons());
39 enum BadSyncDataReason
{
40 // Invalid extension ID.
46 // Invalid update URL.
49 // No ExtensionSpecifics in the EntitySpecifics.
50 NO_EXTENSION_SPECIFICS
,
52 // Enabled extensions can't have disable reasons.
55 // Must be at the end.
56 NUM_BAD_SYNC_DATA_REASONS
59 void RecordBadSyncData(BadSyncDataReason reason
) {
60 UMA_HISTOGRAM_ENUMERATION("Extensions.BadSyncDataReason", reason
,
61 NUM_BAD_SYNC_DATA_REASONS
);
66 ExtensionSyncData::LinkedAppIconInfo::LinkedAppIconInfo() {
69 ExtensionSyncData::LinkedAppIconInfo::~LinkedAppIconInfo() {
72 ExtensionSyncData::ExtensionSyncData()
76 supports_disable_reasons_(false),
77 disable_reasons_(Extension::DISABLE_NONE
),
78 incognito_enabled_(false),
79 remote_install_(false),
80 all_urls_enabled_(BOOLEAN_UNSET
),
81 installed_by_custodian_(false),
82 launch_type_(LAUNCH_TYPE_INVALID
) {
85 ExtensionSyncData::ExtensionSyncData(const Extension
& extension
,
88 bool incognito_enabled
,
90 OptionalBoolean all_urls_enabled
)
91 : ExtensionSyncData(extension
, enabled
, disable_reasons
, incognito_enabled
,
92 remote_install
, all_urls_enabled
, StringOrdinal(),
93 StringOrdinal(), LAUNCH_TYPE_INVALID
) {
96 ExtensionSyncData::ExtensionSyncData(const Extension
& extension
,
99 bool incognito_enabled
,
101 OptionalBoolean all_urls_enabled
,
102 const StringOrdinal
& app_launch_ordinal
,
103 const StringOrdinal
& page_ordinal
,
104 extensions::LaunchType launch_type
)
105 : is_app_(extension
.is_app()),
109 supports_disable_reasons_(true),
110 disable_reasons_(disable_reasons
),
111 incognito_enabled_(incognito_enabled
),
112 remote_install_(remote_install
),
113 all_urls_enabled_(all_urls_enabled
),
114 installed_by_custodian_(extension
.was_installed_by_custodian()),
115 version_(extension
.from_bookmark() ? base::Version("0")
116 : *extension
.version()),
117 update_url_(ManifestURL::GetUpdateURL(&extension
)),
118 name_(extension
.non_localized_name()),
119 app_launch_ordinal_(app_launch_ordinal
),
120 page_ordinal_(page_ordinal
),
121 launch_type_(launch_type
) {
122 if (is_app_
&& extension
.from_bookmark()) {
123 bookmark_app_description_
= extension
.description();
124 bookmark_app_url_
= AppLaunchInfo::GetLaunchWebURL(&extension
).spec();
125 bookmark_app_icon_color_
= AppIconColorInfo::GetIconColorString(&extension
);
126 extensions::LinkedAppIcons icons
=
127 LinkedAppIcons::GetLinkedAppIcons(&extension
);
128 for (const auto& icon
: icons
.icons
) {
129 LinkedAppIconInfo linked_icon
;
130 linked_icon
.url
= icon
.url
;
131 linked_icon
.size
= icon
.size
;
132 linked_icons_
.push_back(linked_icon
);
137 ExtensionSyncData::~ExtensionSyncData() {}
140 scoped_ptr
<ExtensionSyncData
> ExtensionSyncData::CreateFromSyncData(
141 const syncer::SyncData
& sync_data
) {
142 scoped_ptr
<ExtensionSyncData
> data(new ExtensionSyncData
);
143 if (data
->PopulateFromSyncData(sync_data
))
149 scoped_ptr
<ExtensionSyncData
> ExtensionSyncData::CreateFromSyncChange(
150 const syncer::SyncChange
& sync_change
) {
151 scoped_ptr
<ExtensionSyncData
> data(
152 CreateFromSyncData(sync_change
.sync_data()));
156 data
->set_uninstalled(sync_change
.change_type() ==
157 syncer::SyncChange::ACTION_DELETE
);
161 syncer::SyncData
ExtensionSyncData::GetSyncData() const {
162 sync_pb::EntitySpecifics specifics
;
164 ToAppSpecifics(specifics
.mutable_app());
166 ToExtensionSpecifics(specifics
.mutable_extension());
168 return syncer::SyncData::CreateLocalData(id_
, name_
, specifics
);
171 syncer::SyncChange
ExtensionSyncData::GetSyncChange(
172 syncer::SyncChange::SyncChangeType change_type
) const {
173 return syncer::SyncChange(FROM_HERE
, change_type
, GetSyncData());
176 void ExtensionSyncData::ToExtensionSpecifics(
177 sync_pb::ExtensionSpecifics
* specifics
) const {
178 DCHECK(crx_file::id_util::IdIsValid(id_
));
179 specifics
->set_id(id_
);
180 specifics
->set_update_url(update_url_
.spec());
181 specifics
->set_version(version_
.GetString());
182 specifics
->set_enabled(enabled_
);
183 if (supports_disable_reasons_
)
184 specifics
->set_disable_reasons(disable_reasons_
);
185 specifics
->set_incognito_enabled(incognito_enabled_
);
186 specifics
->set_remote_install(remote_install_
);
187 if (all_urls_enabled_
!= BOOLEAN_UNSET
)
188 specifics
->set_all_urls_enabled(all_urls_enabled_
== BOOLEAN_TRUE
);
189 specifics
->set_installed_by_custodian(installed_by_custodian_
);
190 specifics
->set_name(name_
);
193 void ExtensionSyncData::ToAppSpecifics(sync_pb::AppSpecifics
* specifics
) const {
195 // Only sync the ordinal values and launch type if they are valid.
196 if (app_launch_ordinal_
.IsValid())
197 specifics
->set_app_launch_ordinal(app_launch_ordinal_
.ToInternalValue());
198 if (page_ordinal_
.IsValid())
199 specifics
->set_page_ordinal(page_ordinal_
.ToInternalValue());
201 sync_pb::AppSpecifics::LaunchType sync_launch_type
=
202 static_cast<sync_pb::AppSpecifics::LaunchType
>(launch_type_
);
204 // The corresponding validation of this value during processing of an
205 // ExtensionSyncData is in ExtensionSyncService::ApplySyncData.
206 if (launch_type_
>= LAUNCH_TYPE_FIRST
&& launch_type_
< NUM_LAUNCH_TYPES
&&
207 sync_pb::AppSpecifics_LaunchType_IsValid(sync_launch_type
)) {
208 specifics
->set_launch_type(sync_launch_type
);
211 if (!bookmark_app_url_
.empty())
212 specifics
->set_bookmark_app_url(bookmark_app_url_
);
214 if (!bookmark_app_description_
.empty())
215 specifics
->set_bookmark_app_description(bookmark_app_description_
);
217 if (!bookmark_app_icon_color_
.empty())
218 specifics
->set_bookmark_app_icon_color(bookmark_app_icon_color_
);
220 for (const auto& linked_icon
: linked_icons_
) {
221 sync_pb::LinkedAppIconInfo
* linked_app_icon_info
=
222 specifics
->add_linked_app_icons();
223 linked_app_icon_info
->set_url(linked_icon
.url
.spec());
224 linked_app_icon_info
->set_size(linked_icon
.size
);
227 ToExtensionSpecifics(specifics
->mutable_extension());
230 bool ExtensionSyncData::PopulateFromExtensionSpecifics(
231 const sync_pb::ExtensionSpecifics
& specifics
) {
232 if (!crx_file::id_util::IdIsValid(specifics
.id())) {
233 LOG(ERROR
) << "Attempt to sync bad ExtensionSpecifics (bad ID):\n"
234 << GetExtensionSpecificsLogMessage(specifics
);
235 RecordBadSyncData(BAD_EXTENSION_ID
);
239 Version
specifics_version(specifics
.version());
240 if (!specifics_version
.IsValid()) {
241 LOG(ERROR
) << "Attempt to sync bad ExtensionSpecifics (bad version):\n"
242 << GetExtensionSpecificsLogMessage(specifics
);
243 RecordBadSyncData(BAD_VERSION
);
247 // The update URL must be either empty or valid.
248 GURL
specifics_update_url(specifics
.update_url());
249 if (!specifics_update_url
.is_empty() && !specifics_update_url
.is_valid()) {
250 LOG(ERROR
) << "Attempt to sync bad ExtensionSpecifics (bad update URL):\n"
251 << GetExtensionSpecificsLogMessage(specifics
);
252 RecordBadSyncData(BAD_UPDATE_URL
);
256 // Enabled extensions can't have disable reasons. (The proto field may be
257 // unset, in which case it defaults to DISABLE_NONE.)
258 if (specifics
.enabled() &&
259 specifics
.disable_reasons() != Extension::DISABLE_NONE
) {
260 LOG(ERROR
) << "Attempt to sync bad ExtensionSpecifics "
261 << "(enabled extension can't have disable reasons):\n"
262 << GetExtensionSpecificsLogMessage(specifics
);
263 RecordBadSyncData(BAD_DISABLE_REASONS
);
267 id_
= specifics
.id();
268 update_url_
= specifics_update_url
;
269 version_
= specifics_version
;
270 enabled_
= specifics
.enabled();
271 supports_disable_reasons_
= specifics
.has_disable_reasons();
272 disable_reasons_
= specifics
.disable_reasons();
273 incognito_enabled_
= specifics
.incognito_enabled();
274 if (specifics
.has_all_urls_enabled()) {
276 specifics
.all_urls_enabled() ? BOOLEAN_TRUE
: BOOLEAN_FALSE
;
278 // Set this explicitly (even though it's the default) on the offchance
279 // that someone is re-using an ExtensionSyncData object.
280 all_urls_enabled_
= BOOLEAN_UNSET
;
282 remote_install_
= specifics
.remote_install();
283 installed_by_custodian_
= specifics
.installed_by_custodian();
284 name_
= specifics
.name();
288 bool ExtensionSyncData::PopulateFromAppSpecifics(
289 const sync_pb::AppSpecifics
& specifics
) {
290 if (!PopulateFromExtensionSpecifics(specifics
.extension()))
295 app_launch_ordinal_
= syncer::StringOrdinal(specifics
.app_launch_ordinal());
296 page_ordinal_
= syncer::StringOrdinal(specifics
.page_ordinal());
298 launch_type_
= specifics
.has_launch_type()
299 ? static_cast<extensions::LaunchType
>(specifics
.launch_type())
300 : LAUNCH_TYPE_INVALID
;
302 bookmark_app_url_
= specifics
.bookmark_app_url();
303 bookmark_app_description_
= specifics
.bookmark_app_description();
304 bookmark_app_icon_color_
= specifics
.bookmark_app_icon_color();
306 for (int i
= 0; i
< specifics
.linked_app_icons_size(); ++i
) {
307 const sync_pb::LinkedAppIconInfo
& linked_app_icon_info
=
308 specifics
.linked_app_icons(i
);
309 if (linked_app_icon_info
.has_url() && linked_app_icon_info
.has_size()) {
310 LinkedAppIconInfo linked_icon
;
311 linked_icon
.url
= GURL(linked_app_icon_info
.url());
312 linked_icon
.size
= linked_app_icon_info
.size();
313 linked_icons_
.push_back(linked_icon
);
320 void ExtensionSyncData::set_uninstalled(bool uninstalled
) {
321 uninstalled_
= uninstalled
;
324 bool ExtensionSyncData::PopulateFromSyncData(
325 const syncer::SyncData
& sync_data
) {
326 const sync_pb::EntitySpecifics
& entity_specifics
= sync_data
.GetSpecifics();
328 if (entity_specifics
.has_app())
329 return PopulateFromAppSpecifics(entity_specifics
.app());
331 if (entity_specifics
.has_extension())
332 return PopulateFromExtensionSpecifics(entity_specifics
.extension());
334 LOG(ERROR
) << "Attempt to sync bad EntitySpecifics: no extension data.";
335 RecordBadSyncData(NO_EXTENSION_SPECIFICS
);
339 } // namespace extensions