Add a webstorePrivate API to show a permission prompt for delegated bundle installs
[chromium-blink-merge.git] / chrome / browser / extensions / external_install_manager.cc
blob27557d5779bf67ae56dcf02967f69e59a4ab6956
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.
5 #include "chrome/browser/extensions/external_install_manager.h"
7 #include <string>
9 #include "base/logging.h"
10 #include "base/metrics/histogram.h"
11 #include "chrome/browser/chrome_notification_types.h"
12 #include "chrome/browser/extensions/external_install_error.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "content/public/browser/notification_details.h"
15 #include "content/public/browser/notification_source.h"
16 #include "extensions/browser/extension_prefs.h"
17 #include "extensions/browser/extension_registry.h"
18 #include "extensions/common/extension.h"
19 #include "extensions/common/feature_switch.h"
20 #include "extensions/common/manifest.h"
21 #include "extensions/common/manifest_url_handlers.h"
23 namespace extensions {
25 namespace {
27 // Histogram values for logging events related to externally installed
28 // extensions.
29 enum ExternalExtensionEvent {
30 EXTERNAL_EXTENSION_INSTALLED = 0,
31 EXTERNAL_EXTENSION_IGNORED,
32 EXTERNAL_EXTENSION_REENABLED,
33 EXTERNAL_EXTENSION_UNINSTALLED,
34 EXTERNAL_EXTENSION_BUCKET_BOUNDARY,
37 // Prompt the user this many times before considering an extension acknowledged.
38 const int kMaxExtensionAcknowledgePromptCount = 3;
40 void LogExternalExtensionEvent(const Extension* extension,
41 ExternalExtensionEvent event) {
42 UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEvent",
43 event,
44 EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
45 if (ManifestURL::UpdatesFromGallery(extension)) {
46 UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventWebstore",
47 event,
48 EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
49 } else {
50 UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventNonWebstore",
51 event,
52 EXTERNAL_EXTENSION_BUCKET_BOUNDARY);
56 } // namespace
58 ExternalInstallManager::ExternalInstallManager(
59 content::BrowserContext* browser_context,
60 bool is_first_run)
61 : browser_context_(browser_context),
62 is_first_run_(is_first_run),
63 extension_prefs_(ExtensionPrefs::Get(browser_context_)),
64 extension_registry_observer_(this) {
65 DCHECK(browser_context_);
66 extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
67 registrar_.Add(
68 this,
69 extensions::NOTIFICATION_EXTENSION_REMOVED,
70 content::Source<Profile>(Profile::FromBrowserContext(browser_context_)));
73 ExternalInstallManager::~ExternalInstallManager() {
76 void ExternalInstallManager::AddExternalInstallError(const Extension* extension,
77 bool is_new_profile) {
78 if (HasExternalInstallError())
79 return; // Only have one external install error at a time.
81 ExternalInstallError::AlertType alert_type =
82 (ManifestURL::UpdatesFromGallery(extension) && !is_new_profile)
83 ? ExternalInstallError::BUBBLE_ALERT
84 : ExternalInstallError::MENU_ALERT;
86 error_.reset(new ExternalInstallError(
87 browser_context_, extension->id(), alert_type, this));
90 void ExternalInstallManager::RemoveExternalInstallError() {
91 error_.reset();
92 UpdateExternalExtensionAlert();
95 bool ExternalInstallManager::HasExternalInstallError() const {
96 return error_.get() != NULL;
99 void ExternalInstallManager::UpdateExternalExtensionAlert() {
100 // If the feature is not enabled, or there is already an error displayed, do
101 // nothing.
102 if (!FeatureSwitch::prompt_for_external_extensions()->IsEnabled() ||
103 HasExternalInstallError()) {
104 return;
107 // Look for any extensions that were disabled because of being unacknowledged
108 // external extensions.
109 const Extension* extension = NULL;
110 const ExtensionSet& disabled_extensions =
111 ExtensionRegistry::Get(browser_context_)->disabled_extensions();
112 for (ExtensionSet::const_iterator iter = disabled_extensions.begin();
113 iter != disabled_extensions.end();
114 ++iter) {
115 if (IsUnacknowledgedExternalExtension(iter->get())) {
116 extension = iter->get();
117 break;
121 if (!extension)
122 return; // No unacknowledged external extensions.
124 // Otherwise, warn the user about the suspicious extension.
125 if (extension_prefs_->IncrementAcknowledgePromptCount(extension->id()) >
126 kMaxExtensionAcknowledgePromptCount) {
127 // Stop prompting for this extension and record metrics.
128 extension_prefs_->AcknowledgeExternalExtension(extension->id());
129 LogExternalExtensionEvent(extension, EXTERNAL_EXTENSION_IGNORED);
131 // Check if there's another extension that needs prompting.
132 UpdateExternalExtensionAlert();
133 return;
136 if (is_first_run_)
137 extension_prefs_->SetExternalInstallFirstRun(extension->id());
139 // |first_run| is true if the extension was installed during a first run
140 // (even if it's post-first run now).
141 AddExternalInstallError(
142 extension, extension_prefs_->IsExternalInstallFirstRun(extension->id()));
145 void ExternalInstallManager::AcknowledgeExternalExtension(
146 const std::string& id) {
147 extension_prefs_->AcknowledgeExternalExtension(id);
148 UpdateExternalExtensionAlert();
151 bool ExternalInstallManager::HasExternalInstallBubbleForTesting() const {
152 return error_.get() &&
153 error_->alert_type() == ExternalInstallError::BUBBLE_ALERT;
156 void ExternalInstallManager::OnExtensionLoaded(
157 content::BrowserContext* browser_context,
158 const Extension* extension) {
159 if (!IsUnacknowledgedExternalExtension(extension))
160 return;
162 // We treat loading as acknowledgement (since the user consciously chose to
163 // re-enable the extension).
164 AcknowledgeExternalExtension(extension->id());
165 LogExternalExtensionEvent(extension, EXTERNAL_EXTENSION_REENABLED);
167 // If we had an error for this extension, remove it.
168 if (error_.get() && extension->id() == error_->extension_id())
169 RemoveExternalInstallError();
172 void ExternalInstallManager::OnExtensionInstalled(
173 content::BrowserContext* browser_context,
174 const Extension* extension,
175 bool is_update) {
176 // Certain extension locations are specific enough that we can
177 // auto-acknowledge any extension that came from one of them.
178 if (Manifest::IsPolicyLocation(extension->location()) ||
179 extension->location() == Manifest::EXTERNAL_COMPONENT) {
180 AcknowledgeExternalExtension(extension->id());
181 return;
184 if (!IsUnacknowledgedExternalExtension(extension))
185 return;
187 LogExternalExtensionEvent(extension, EXTERNAL_EXTENSION_INSTALLED);
189 UpdateExternalExtensionAlert();
192 void ExternalInstallManager::OnExtensionUninstalled(
193 content::BrowserContext* browser_context,
194 const Extension* extension,
195 extensions::UninstallReason reason) {
196 if (IsUnacknowledgedExternalExtension(extension))
197 LogExternalExtensionEvent(extension, EXTERNAL_EXTENSION_UNINSTALLED);
200 bool ExternalInstallManager::IsUnacknowledgedExternalExtension(
201 const Extension* extension) const {
202 if (!FeatureSwitch::prompt_for_external_extensions()->IsEnabled())
203 return false;
205 return (Manifest::IsExternalLocation(extension->location()) &&
206 !extension_prefs_->IsExternalExtensionAcknowledged(extension->id()) &&
207 !(extension_prefs_->GetDisableReasons(extension->id()) &
208 Extension::DISABLE_SIDELOAD_WIPEOUT));
211 void ExternalInstallManager::Observe(
212 int type,
213 const content::NotificationSource& source,
214 const content::NotificationDetails& details) {
215 DCHECK_EQ(extensions::NOTIFICATION_EXTENSION_REMOVED, type);
216 // The error is invalidated if the extension has been loaded or removed.
217 // It's a shame we have to use the notification system (instead of the
218 // registry observer) for this, but the ExtensionUnloaded notification is
219 // not sent out if the extension is disabled (which it is here).
220 if (error_.get() &&
221 content::Details<const Extension>(details).ptr()->id() ==
222 error_->extension_id()) {
223 RemoveExternalInstallError();
227 } // namespace extensions