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"
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
{
27 // Histogram values for logging events related to externally installed
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",
44 EXTERNAL_EXTENSION_BUCKET_BOUNDARY
);
45 if (ManifestURL::UpdatesFromGallery(extension
)) {
46 UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventWebstore",
48 EXTERNAL_EXTENSION_BUCKET_BOUNDARY
);
50 UMA_HISTOGRAM_ENUMERATION("Extensions.ExternalExtensionEventNonWebstore",
52 EXTERNAL_EXTENSION_BUCKET_BOUNDARY
);
58 ExternalInstallManager::ExternalInstallManager(
59 content::BrowserContext
* browser_context
,
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_
));
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() {
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
102 if (!FeatureSwitch::prompt_for_external_extensions()->IsEnabled() ||
103 HasExternalInstallError()) {
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();
115 if (IsUnacknowledgedExternalExtension(iter
->get())) {
116 extension
= iter
->get();
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();
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
))
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
,
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());
184 if (!IsUnacknowledgedExternalExtension(extension
))
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())
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(
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).
221 content::Details
<const Extension
>(details
).ptr()->id() ==
222 error_
->extension_id()) {
223 RemoveExternalInstallError();
227 } // namespace extensions