1// Copyright 2014 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/sync/glue/extension_backed_data_type_controller.h"
6
7#include "base/sha1.h"
8#include "base/strings/string_number_conversions.h"
9#include "chrome/browser/profiles/profile.h"
10#include "chrome/browser/sync/glue/chrome_report_unrecoverable_error.h"
11#include "chrome/browser/sync/profile_sync_service.h"
12#include "chrome/browser/sync/profile_sync_service_factory.h"
13#include "content/public/browser/browser_thread.h"
14#include "extensions/browser/extension_registry.h"
15#include "extensions/browser/extension_registry_factory.h"
16
17using content::BrowserThread;
18
19namespace browser_sync {
20
21namespace {
22
23// Helper method to generate a hash from an extension id.
24std::string IdToHash(const std::string extension_id) {
25  std::string hash = base::SHA1HashString(extension_id);
26  hash = base::HexEncode(hash.c_str(), hash.length());
27  return hash;
28}
29
30}  // namespace
31
32ExtensionBackedDataTypeController::ExtensionBackedDataTypeController(
33    syncer::ModelType type,
34    const std::string& extension_hash,
35    sync_driver::SyncApiComponentFactory* sync_factory,
36    Profile* profile)
37    : UIDataTypeController(
38          BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI),
39          base::Bind(&ChromeReportUnrecoverableError),
40          type,
41          sync_factory),
42      extension_hash_(extension_hash),
43      profile_(profile) {
44  extensions::ExtensionRegistry* registry =
45      extensions::ExtensionRegistryFactory::GetForBrowserContext(profile_);
46  registry->AddObserver(this);
47}
48
49ExtensionBackedDataTypeController::~ExtensionBackedDataTypeController() {
50  extensions::ExtensionRegistry* registry =
51      extensions::ExtensionRegistryFactory::GetForBrowserContext(profile_);
52  registry->RemoveObserver(this);
53}
54
55bool ExtensionBackedDataTypeController::ReadyForStart() const {
56  // TODO(zea): consider checking if the extension was uninstalled without
57  // sync noticing, in which case the datatype should be purged.
58  return IsSyncingExtensionEnabled();
59}
60
61void ExtensionBackedDataTypeController::OnExtensionLoaded(
62    content::BrowserContext* browser_context,
63    const extensions::Extension* extension) {
64  if (DoesExtensionMatch(*extension)) {
65    ProfileSyncService* sync_service =
66        ProfileSyncServiceFactory::GetForProfile(profile_);
67    sync_service->ReenableDatatype(type());
68  }
69}
70
71void ExtensionBackedDataTypeController::OnExtensionUnloaded(
72    content::BrowserContext* browser_context,
73    const extensions::Extension* extension,
74    extensions::UnloadedExtensionInfo::Reason reason) {
75  if (DoesExtensionMatch(*extension)) {
76    // This will trigger purging the datatype from the sync directory. This
77    // may not always be the right choice, especially in the face of transient
78    // unloads (e.g. for permission changes). If that becomes a large enough
79    // issue, we should consider using the extension unload reason to just
80    // trigger a reconfiguration without disabling (type will be unready).
81    syncer::SyncError error(FROM_HERE,
82                            syncer::SyncError::DATATYPE_POLICY_ERROR,
83                            "Extension unloaded",
84                            type());
85    OnSingleDataTypeUnrecoverableError(error);
86  }
87}
88
89bool ExtensionBackedDataTypeController::IsSyncingExtensionEnabled() const {
90  if (extension_hash_.empty())
91    return true;  // For use while extension is in development.
92
93  // TODO(synced notifications): rather than rely on a hash of the extension
94  // id, look through the manifests to see if the extension has permissions
95  // for this model type.
96  extensions::ExtensionRegistry* registry =
97      extensions::ExtensionRegistryFactory::GetForBrowserContext(profile_);
98  const extensions::ExtensionSet& enabled_extensions(
99      registry->enabled_extensions());
100  for (extensions::ExtensionSet::const_iterator iter =
101           enabled_extensions.begin();
102       iter != enabled_extensions.end(); ++iter) {
103    if (DoesExtensionMatch(*iter->get()))
104      return true;
105  }
106  return false;
107}
108
109bool ExtensionBackedDataTypeController::DoesExtensionMatch(
110    const extensions::Extension& extension) const {
111  return IdToHash(extension.id()) == extension_hash_;
112}
113
114bool ExtensionBackedDataTypeController::StartModels() {
115  if (IsSyncingExtensionEnabled())
116    return true;
117
118  // If the extension is not currently enabled, it means that it has been
119  // disabled since the call to ReadyForStart(), and the notification
120  // observer should have already posted a call to disable the type.
121  return false;
122}
123
124}  // namespace browser_sync
125