1 // Copyright 2013 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/extensions/error_console/error_console.h" 6 7 #include <vector> 8 9 #include "base/bind.h" 10 #include "base/bind_helpers.h" 11 #include "base/lazy_instance.h" 12 #include "base/prefs/pref_service.h" 13 #include "base/stl_util.h" 14 #include "base/strings/utf_string_conversions.h" 15 #include "chrome/browser/chrome_notification_types.h" 16 #include "chrome/browser/profiles/profile.h" 17 #include "chrome/common/chrome_version_info.h" 18 #include "chrome/common/extensions/features/feature_channel.h" 19 #include "chrome/common/pref_names.h" 20 #include "components/crx_file/id_util.h" 21 #include "content/public/browser/notification_details.h" 22 #include "content/public/browser/notification_service.h" 23 #include "content/public/browser/notification_source.h" 24 #include "extensions/browser/extension_prefs.h" 25 #include "extensions/browser/extension_registry.h" 26 #include "extensions/browser/extension_system.h" 27 #include "extensions/common/constants.h" 28 #include "extensions/common/extension.h" 29 #include "extensions/common/extension_set.h" 30 #include "extensions/common/feature_switch.h" 31 32 namespace extensions { 33 34 namespace { 35 36 // The key into the Extension prefs for an Extension's specific reporting 37 // settings. 38 const char kStoreExtensionErrorsPref[] = "store_extension_errors"; 39 40 // This is the default mask for which errors to report. That is, if an extension 41 // does not have specific preference set, this will be used instead. 42 const int kDefaultMask = 0; 43 44 const char kAppsDeveloperToolsExtensionId[] = 45 "ohmmkhmmmpcnpikjeljgnaoabkaalbgc"; 46 47 } // namespace 48 49 void ErrorConsole::Observer::OnErrorConsoleDestroyed() { 50 } 51 52 ErrorConsole::ErrorConsole(Profile* profile) 53 : enabled_(false), 54 default_mask_(kDefaultMask), 55 profile_(profile), 56 prefs_(NULL), 57 registry_observer_(this) { 58 pref_registrar_.Init(profile_->GetPrefs()); 59 pref_registrar_.Add(prefs::kExtensionsUIDeveloperMode, 60 base::Bind(&ErrorConsole::OnPrefChanged, 61 base::Unretained(this))); 62 63 registry_observer_.Add(ExtensionRegistry::Get(profile_)); 64 65 CheckEnabled(); 66 } 67 68 ErrorConsole::~ErrorConsole() { 69 FOR_EACH_OBSERVER(Observer, observers_, OnErrorConsoleDestroyed()); 70 } 71 72 // static 73 ErrorConsole* ErrorConsole::Get(Profile* profile) { 74 return ExtensionSystem::Get(profile)->error_console(); 75 } 76 77 void ErrorConsole::SetReportingForExtension(const std::string& extension_id, 78 ExtensionError::Type type, 79 bool enabled) { 80 DCHECK(thread_checker_.CalledOnValidThread()); 81 if (!enabled_ || !crx_file::id_util::IdIsValid(extension_id)) 82 return; 83 84 int mask = default_mask_; 85 // This call can fail if the preference isn't set, but we don't really care 86 // if it does, because we just use the default mask instead. 87 prefs_->ReadPrefAsInteger(extension_id, kStoreExtensionErrorsPref, &mask); 88 89 if (enabled) 90 mask |= 1 << type; 91 else 92 mask &= ~(1 << type); 93 94 prefs_->UpdateExtensionPref(extension_id, 95 kStoreExtensionErrorsPref, 96 new base::FundamentalValue(mask)); 97 } 98 99 void ErrorConsole::SetReportingAllForExtension( 100 const std::string& extension_id, bool enabled) { 101 DCHECK(thread_checker_.CalledOnValidThread()); 102 if (!enabled_ || !crx_file::id_util::IdIsValid(extension_id)) 103 return; 104 105 int mask = 0; 106 if (enabled) 107 mask = (1 << ExtensionError::NUM_ERROR_TYPES) - 1; 108 109 prefs_->UpdateExtensionPref(extension_id, 110 kStoreExtensionErrorsPref, 111 new base::FundamentalValue(mask)); 112 } 113 114 bool ErrorConsole::IsReportingEnabledForExtension( 115 const std::string& extension_id) const { 116 DCHECK(thread_checker_.CalledOnValidThread()); 117 if (!enabled_ || !crx_file::id_util::IdIsValid(extension_id)) 118 return false; 119 120 return GetMaskForExtension(extension_id) != 0; 121 } 122 123 void ErrorConsole::UseDefaultReportingForExtension( 124 const std::string& extension_id) { 125 DCHECK(thread_checker_.CalledOnValidThread()); 126 if (!enabled_ || !crx_file::id_util::IdIsValid(extension_id)) 127 return; 128 129 prefs_->UpdateExtensionPref(extension_id, kStoreExtensionErrorsPref, NULL); 130 } 131 132 void ErrorConsole::ReportError(scoped_ptr<ExtensionError> error) { 133 DCHECK(thread_checker_.CalledOnValidThread()); 134 if (!enabled_ || !crx_file::id_util::IdIsValid(error->extension_id())) 135 return; 136 137 int mask = GetMaskForExtension(error->extension_id()); 138 if (!(mask & (1 << error->type()))) 139 return; 140 141 const ExtensionError* weak_error = errors_.AddError(error.Pass()); 142 FOR_EACH_OBSERVER(Observer, observers_, OnErrorAdded(weak_error)); 143 } 144 145 const ErrorList& ErrorConsole::GetErrorsForExtension( 146 const std::string& extension_id) const { 147 return errors_.GetErrorsForExtension(extension_id); 148 } 149 150 void ErrorConsole::AddObserver(Observer* observer) { 151 DCHECK(thread_checker_.CalledOnValidThread()); 152 observers_.AddObserver(observer); 153 } 154 155 void ErrorConsole::RemoveObserver(Observer* observer) { 156 DCHECK(thread_checker_.CalledOnValidThread()); 157 observers_.RemoveObserver(observer); 158 } 159 160 bool ErrorConsole::IsEnabledForChromeExtensionsPage() const { 161 return profile_->GetPrefs()->GetBoolean(prefs::kExtensionsUIDeveloperMode) && 162 (FeatureSwitch::error_console()->IsEnabled() || 163 GetCurrentChannel() <= chrome::VersionInfo::CHANNEL_DEV); 164 } 165 166 bool ErrorConsole::IsEnabledForAppsDeveloperTools() const { 167 return ExtensionRegistry::Get(profile_)->enabled_extensions() 168 .Contains(kAppsDeveloperToolsExtensionId); 169 } 170 171 void ErrorConsole::CheckEnabled() { 172 bool should_be_enabled = IsEnabledForChromeExtensionsPage() || 173 IsEnabledForAppsDeveloperTools(); 174 if (should_be_enabled && !enabled_) 175 Enable(); 176 if (!should_be_enabled && enabled_) 177 Disable(); 178 } 179 180 void ErrorConsole::Enable() { 181 enabled_ = true; 182 183 // We postpone the initialization of |prefs_| until now because they can be 184 // NULL in unit_tests. Any unit tests that enable the error console should 185 // also create an ExtensionPrefs object. 186 prefs_ = ExtensionPrefs::Get(profile_); 187 188 notification_registrar_.Add( 189 this, 190 chrome::NOTIFICATION_PROFILE_DESTROYED, 191 content::NotificationService::AllBrowserContextsAndSources()); 192 193 const ExtensionSet& extensions = 194 ExtensionRegistry::Get(profile_)->enabled_extensions(); 195 for (ExtensionSet::const_iterator iter = extensions.begin(); 196 iter != extensions.end(); 197 ++iter) { 198 AddManifestErrorsForExtension(iter->get()); 199 } 200 } 201 202 void ErrorConsole::Disable() { 203 notification_registrar_.RemoveAll(); 204 errors_.RemoveAllErrors(); 205 enabled_ = false; 206 } 207 208 void ErrorConsole::OnPrefChanged() { 209 CheckEnabled(); 210 } 211 212 void ErrorConsole::OnExtensionUnloaded(content::BrowserContext* browser_context, 213 const Extension* extension, 214 UnloadedExtensionInfo::Reason reason) { 215 CheckEnabled(); 216 } 217 218 void ErrorConsole::OnExtensionLoaded(content::BrowserContext* browser_context, 219 const Extension* extension) { 220 CheckEnabled(); 221 } 222 223 void ErrorConsole::OnExtensionInstalled( 224 content::BrowserContext* browser_context, 225 const Extension* extension, 226 bool is_update) { 227 // We don't want to have manifest errors from previous installs. We want 228 // to keep runtime errors, though, because extensions are reloaded on a 229 // refresh of chrome:extensions, and we don't want to wipe our history 230 // whenever that happens. 231 errors_.RemoveErrorsForExtensionOfType(extension->id(), 232 ExtensionError::MANIFEST_ERROR); 233 AddManifestErrorsForExtension(extension); 234 } 235 236 void ErrorConsole::OnExtensionUninstalled( 237 content::BrowserContext* browser_context, 238 const Extension* extension, 239 extensions::UninstallReason reason) { 240 errors_.Remove(extension->id()); 241 } 242 243 void ErrorConsole::AddManifestErrorsForExtension(const Extension* extension) { 244 const std::vector<InstallWarning>& warnings = 245 extension->install_warnings(); 246 for (std::vector<InstallWarning>::const_iterator iter = warnings.begin(); 247 iter != warnings.end(); ++iter) { 248 ReportError(scoped_ptr<ExtensionError>(new ManifestError( 249 extension->id(), 250 base::UTF8ToUTF16(iter->message), 251 base::UTF8ToUTF16(iter->key), 252 base::UTF8ToUTF16(iter->specific)))); 253 } 254 } 255 256 void ErrorConsole::Observe(int type, 257 const content::NotificationSource& source, 258 const content::NotificationDetails& details) { 259 DCHECK_EQ(chrome::NOTIFICATION_PROFILE_DESTROYED, type); 260 Profile* profile = content::Source<Profile>(source).ptr(); 261 // If incognito profile which we are associated with is destroyed, also 262 // destroy all incognito errors. 263 if (profile->IsOffTheRecord() && profile_->IsSameProfile(profile)) 264 errors_.RemoveIncognitoErrors(); 265 } 266 267 int ErrorConsole::GetMaskForExtension(const std::string& extension_id) const { 268 // Registered preferences take priority over everything else. 269 int pref = 0; 270 if (prefs_->ReadPrefAsInteger(extension_id, kStoreExtensionErrorsPref, &pref)) 271 return pref; 272 273 // If the extension is unpacked, we report all error types by default. 274 const Extension* extension = 275 ExtensionRegistry::Get(profile_)->GetExtensionById( 276 extension_id, ExtensionRegistry::EVERYTHING); 277 if (extension && extension->location() == Manifest::UNPACKED) 278 return (1 << ExtensionError::NUM_ERROR_TYPES) - 1; 279 280 // Otherwise, use the default mask. 281 return default_mask_; 282 } 283 284 } // namespace extensions 285