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 <list> 8 #include <vector> 9 10 #include "base/bind.h" 11 #include "base/bind_helpers.h" 12 #include "base/lazy_instance.h" 13 #include "base/prefs/pref_service.h" 14 #include "base/stl_util.h" 15 #include "base/strings/utf_string_conversions.h" 16 #include "chrome/browser/chrome_notification_types.h" 17 #include "chrome/browser/extensions/extension_service.h" 18 #include "chrome/browser/extensions/extension_system.h" 19 #include "chrome/browser/profiles/profile.h" 20 #include "chrome/common/extensions/extension_set.h" 21 #include "chrome/common/pref_names.h" 22 #include "content/public/browser/notification_details.h" 23 #include "content/public/browser/notification_service.h" 24 #include "content/public/browser/notification_source.h" 25 #include "extensions/common/constants.h" 26 #include "extensions/common/extension.h" 27 #include "extensions/common/feature_switch.h" 28 29 namespace extensions { 30 31 namespace { 32 33 const size_t kMaxErrorsPerExtension = 100; 34 35 // Iterate through an error list and remove and delete all errors which were 36 // from an incognito context. 37 void DeleteIncognitoErrorsFromList(ErrorConsole::ErrorList* list) { 38 ErrorConsole::ErrorList::iterator iter = list->begin(); 39 while (iter != list->end()) { 40 if ((*iter)->from_incognito()) { 41 delete *iter; 42 iter = list->erase(iter); 43 } else { 44 ++iter; 45 } 46 } 47 } 48 49 // Iterate through an error list and remove and delete all errors of a given 50 // |type|. 51 void DeleteErrorsOfTypeFromList(ErrorConsole::ErrorList* list, 52 ExtensionError::Type type) { 53 ErrorConsole::ErrorList::iterator iter = list->begin(); 54 while (iter != list->end()) { 55 if ((*iter)->type() == type) { 56 delete *iter; 57 iter = list->erase(iter); 58 } else { 59 ++iter; 60 } 61 } 62 } 63 64 base::LazyInstance<ErrorConsole::ErrorList> g_empty_error_list = 65 LAZY_INSTANCE_INITIALIZER; 66 67 } // namespace 68 69 void ErrorConsole::Observer::OnErrorConsoleDestroyed() { 70 } 71 72 ErrorConsole::ErrorConsole(Profile* profile, 73 ExtensionService* extension_service) 74 : enabled_(false), profile_(profile) { 75 // TODO(rdevlin.cronin): Remove once crbug.com/159265 is fixed. 76 #if !defined(ENABLE_EXTENSIONS) 77 return; 78 #endif 79 80 // If we don't have the necessary FeatureSwitch enabled, then return 81 // immediately. Since we never register for any notifications, this ensures 82 // the ErrorConsole will never be enabled. 83 if (!FeatureSwitch::error_console()->IsEnabled()) 84 return; 85 86 pref_registrar_.Init(profile_->GetPrefs()); 87 pref_registrar_.Add(prefs::kExtensionsUIDeveloperMode, 88 base::Bind(&ErrorConsole::OnPrefChanged, 89 base::Unretained(this))); 90 91 if (profile_->GetPrefs()->GetBoolean(prefs::kExtensionsUIDeveloperMode)) 92 Enable(extension_service); 93 } 94 95 ErrorConsole::~ErrorConsole() { 96 FOR_EACH_OBSERVER(Observer, observers_, OnErrorConsoleDestroyed()); 97 RemoveAllErrors(); 98 } 99 100 // static 101 ErrorConsole* ErrorConsole::Get(Profile* profile) { 102 return ExtensionSystem::Get(profile)->error_console(); 103 } 104 105 void ErrorConsole::ReportError(scoped_ptr<ExtensionError> error) { 106 DCHECK(thread_checker_.CalledOnValidThread()); 107 108 if (!enabled_ || !Extension::IdIsValid(error->extension_id())) 109 return; 110 111 ErrorList* extension_errors = &errors_[error->extension_id()]; 112 113 // First, check if it's a duplicate. 114 for (ErrorList::iterator iter = extension_errors->begin(); 115 iter != extension_errors->end(); ++iter) { 116 // If we find a duplicate error, remove the old error and add the new one, 117 // incrementing the occurrence count of the error. We use the new error 118 // for runtime errors, so we can link to the latest context, inspectable 119 // view, etc. 120 if (error->IsEqual(*iter)) { 121 error->set_occurrences((*iter)->occurrences() + 1); 122 delete *iter; 123 extension_errors->erase(iter); 124 break; 125 } 126 } 127 128 // If there are too many errors for an extension already, limit ourselves to 129 // the most recent ones. 130 if (extension_errors->size() >= kMaxErrorsPerExtension) { 131 delete extension_errors->front(); 132 extension_errors->pop_front(); 133 } 134 135 extension_errors->push_back(error.release()); 136 137 FOR_EACH_OBSERVER( 138 Observer, observers_, OnErrorAdded(extension_errors->back())); 139 } 140 141 const ErrorConsole::ErrorList& ErrorConsole::GetErrorsForExtension( 142 const std::string& extension_id) const { 143 ErrorMap::const_iterator iter = errors_.find(extension_id); 144 if (iter != errors_.end()) 145 return iter->second; 146 return g_empty_error_list.Get(); 147 } 148 149 void ErrorConsole::AddObserver(Observer* observer) { 150 DCHECK(thread_checker_.CalledOnValidThread()); 151 observers_.AddObserver(observer); 152 } 153 154 void ErrorConsole::RemoveObserver(Observer* observer) { 155 DCHECK(thread_checker_.CalledOnValidThread()); 156 observers_.RemoveObserver(observer); 157 } 158 159 void ErrorConsole::OnPrefChanged() { 160 bool developer_mode = 161 profile_->GetPrefs()->GetBoolean(prefs::kExtensionsUIDeveloperMode); 162 163 if (developer_mode && !enabled_) 164 Enable(ExtensionSystem::Get(profile_)->extension_service()); 165 else if (!developer_mode && enabled_) 166 Disable(); 167 } 168 169 void ErrorConsole::Enable(ExtensionService* extension_service) { 170 enabled_ = true; 171 172 notification_registrar_.Add( 173 this, 174 chrome::NOTIFICATION_PROFILE_DESTROYED, 175 content::NotificationService::AllBrowserContextsAndSources()); 176 notification_registrar_.Add( 177 this, 178 chrome::NOTIFICATION_EXTENSION_UNINSTALLED, 179 content::Source<Profile>(profile_)); 180 notification_registrar_.Add( 181 this, 182 chrome::NOTIFICATION_EXTENSION_INSTALLED, 183 content::Source<Profile>(profile_)); 184 185 if (extension_service) { 186 // Get manifest errors for extensions already installed. 187 const ExtensionSet* extensions = extension_service->extensions(); 188 for (ExtensionSet::const_iterator iter = extensions->begin(); 189 iter != extensions->end(); ++iter) { 190 AddManifestErrorsForExtension(iter->get()); 191 } 192 } 193 } 194 195 void ErrorConsole::Disable() { 196 notification_registrar_.RemoveAll(); 197 RemoveAllErrors(); 198 enabled_ = false; 199 } 200 201 void ErrorConsole::AddManifestErrorsForExtension(const Extension* extension) { 202 const std::vector<InstallWarning>& warnings = 203 extension->install_warnings(); 204 for (std::vector<InstallWarning>::const_iterator iter = warnings.begin(); 205 iter != warnings.end(); ++iter) { 206 ReportError(scoped_ptr<ExtensionError>(new ManifestError( 207 extension->id(), 208 base::UTF8ToUTF16(iter->message), 209 base::UTF8ToUTF16(iter->key), 210 base::UTF8ToUTF16(iter->specific)))); 211 } 212 } 213 214 void ErrorConsole::RemoveIncognitoErrors() { 215 for (ErrorMap::iterator iter = errors_.begin(); 216 iter != errors_.end(); ++iter) { 217 DeleteIncognitoErrorsFromList(&(iter->second)); 218 } 219 } 220 221 void ErrorConsole::RemoveErrorsForExtension(const std::string& extension_id) { 222 ErrorMap::iterator iter = errors_.find(extension_id); 223 if (iter != errors_.end()) { 224 STLDeleteContainerPointers(iter->second.begin(), iter->second.end()); 225 errors_.erase(iter); 226 } 227 } 228 229 void ErrorConsole::RemoveAllErrors() { 230 for (ErrorMap::iterator iter = errors_.begin(); iter != errors_.end(); ++iter) 231 STLDeleteContainerPointers(iter->second.begin(), iter->second.end()); 232 errors_.clear(); 233 } 234 235 void ErrorConsole::Observe(int type, 236 const content::NotificationSource& source, 237 const content::NotificationDetails& details) { 238 switch (type) { 239 case chrome::NOTIFICATION_PROFILE_DESTROYED: { 240 Profile* profile = content::Source<Profile>(source).ptr(); 241 // If incognito profile which we are associated with is destroyed, also 242 // destroy all incognito errors. 243 if (profile->IsOffTheRecord() && profile_->IsSameProfile(profile)) 244 RemoveIncognitoErrors(); 245 break; 246 } 247 case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: 248 // No need to check the profile here, since we registered to only receive 249 // notifications from our own. 250 RemoveErrorsForExtension( 251 content::Details<Extension>(details).ptr()->id()); 252 break; 253 case chrome::NOTIFICATION_EXTENSION_INSTALLED: { 254 const InstalledExtensionInfo* info = 255 content::Details<InstalledExtensionInfo>(details).ptr(); 256 257 // We don't want to have manifest errors from previous installs. We want 258 // to keep runtime errors, though, because extensions are reloaded on a 259 // refresh of chrome:extensions, and we don't want to wipe our history 260 // whenever that happens. 261 ErrorMap::iterator iter = errors_.find(info->extension->id()); 262 if (iter != errors_.end()) { 263 DeleteErrorsOfTypeFromList(&(iter->second), 264 ExtensionError::MANIFEST_ERROR); 265 } 266 267 AddManifestErrorsForExtension(info->extension); 268 break; 269 } 270 default: 271 NOTREACHED(); 272 } 273 } 274 275 } // namespace extensions 276