diff options
-rw-r--r-- | chrome/browser/extensions/error_console/error_console.cc | 166 | ||||
-rw-r--r-- | chrome/browser/extensions/error_console/error_console.h | 45 | ||||
-rw-r--r-- | chrome/browser/extensions/error_console/error_console_browsertest.cc | 13 | ||||
-rw-r--r-- | chrome/browser/extensions/error_console/error_console_unittest.cc | 199 | ||||
-rw-r--r-- | chrome/browser/ui/webui/extensions/extension_settings_handler.cc | 4 | ||||
-rw-r--r-- | chrome/chrome_tests_unit.gypi | 3 | ||||
-rw-r--r-- | extensions/browser/error_map.cc | 138 | ||||
-rw-r--r-- | extensions/browser/error_map.h | 84 | ||||
-rw-r--r-- | extensions/browser/error_map_unittest.cc | 147 | ||||
-rw-r--r-- | extensions/browser/extension_error_test_util.cc | 64 | ||||
-rw-r--r-- | extensions/browser/extension_error_test_util.h | 35 | ||||
-rw-r--r-- | extensions/extensions.gyp | 2 |
12 files changed, 629 insertions, 271 deletions
diff --git a/chrome/browser/extensions/error_console/error_console.cc b/chrome/browser/extensions/error_console/error_console.cc index 7156622..44dc307 100644 --- a/chrome/browser/extensions/error_console/error_console.cc +++ b/chrome/browser/extensions/error_console/error_console.cc @@ -29,49 +29,21 @@ namespace extensions { namespace { +// The key into the Extension prefs for an Extension's specific reporting +// settings. +const char kStoreExtensionErrorsPref[] = "store_extension_errors"; -const size_t kMaxErrorsPerExtension = 100; - -// Iterate through an error list and remove and delete all errors which were -// from an incognito context. -void DeleteIncognitoErrorsFromList(ErrorConsole::ErrorList* list) { - ErrorConsole::ErrorList::iterator iter = list->begin(); - while (iter != list->end()) { - if ((*iter)->from_incognito()) { - delete *iter; - iter = list->erase(iter); - } else { - ++iter; - } - } -} - -// Iterate through an error list and remove and delete all errors of a given -// |type|. -void DeleteErrorsOfTypeFromList(ErrorConsole::ErrorList* list, - ExtensionError::Type type) { - ErrorConsole::ErrorList::iterator iter = list->begin(); - while (iter != list->end()) { - if ((*iter)->type() == type) { - delete *iter; - iter = list->erase(iter); - } else { - ++iter; - } - } +// The default mask (for the time being) is to report everything. +const int32 kDefaultMask = (1 << ExtensionError::MANIFEST_ERROR) | + (1 << ExtensionError::RUNTIME_ERROR); } -base::LazyInstance<ErrorConsole::ErrorList> g_empty_error_list = - LAZY_INSTANCE_INITIALIZER; - -} // namespace - void ErrorConsole::Observer::OnErrorConsoleDestroyed() { } ErrorConsole::ErrorConsole(Profile* profile, ExtensionService* extension_service) - : enabled_(false), profile_(profile) { + : enabled_(false), default_mask_(kDefaultMask), profile_(profile) { // TODO(rdevlin.cronin): Remove once crbug.com/159265 is fixed. #if !defined(ENABLE_EXTENSIONS) return; @@ -94,7 +66,6 @@ ErrorConsole::ErrorConsole(Profile* profile, ErrorConsole::~ErrorConsole() { FOR_EACH_OBSERVER(Observer, observers_, OnErrorConsoleDestroyed()); - RemoveAllErrors(); } // static @@ -102,48 +73,64 @@ ErrorConsole* ErrorConsole::Get(Profile* profile) { return ExtensionSystem::Get(profile)->error_console(); } -void ErrorConsole::ReportError(scoped_ptr<ExtensionError> error) { +void ErrorConsole::SetReportingForExtension(const std::string& extension_id, + ExtensionError::Type type, + bool enabled) { DCHECK(thread_checker_.CalledOnValidThread()); - - if (!enabled_ || !Extension::IdIsValid(error->extension_id())) + if (!enabled_ || !Extension::IdIsValid(extension_id)) return; - ErrorList* extension_errors = &errors_[error->extension_id()]; - - // First, check if it's a duplicate. - for (ErrorList::iterator iter = extension_errors->begin(); - iter != extension_errors->end(); ++iter) { - // If we find a duplicate error, remove the old error and add the new one, - // incrementing the occurrence count of the error. We use the new error - // for runtime errors, so we can link to the latest context, inspectable - // view, etc. - if (error->IsEqual(*iter)) { - error->set_occurrences((*iter)->occurrences() + 1); - delete *iter; - extension_errors->erase(iter); - break; - } - } + ErrorPreferenceMap::iterator pref = pref_map_.find(extension_id); - // If there are too many errors for an extension already, limit ourselves to - // the most recent ones. - if (extension_errors->size() >= kMaxErrorsPerExtension) { - delete extension_errors->front(); - extension_errors->pop_front(); + if (pref == pref_map_.end()) { + pref = pref_map_.insert( + std::pair<std::string, int32>(extension_id, default_mask_)).first; } - extension_errors->push_back(error.release()); + pref->second = + enabled ? pref->second | (1 << type) : pref->second &~(1 << type); + + ExtensionPrefs::Get(profile_)->UpdateExtensionPref( + extension_id, + kStoreExtensionErrorsPref, + base::Value::CreateIntegerValue(pref->second)); +} - FOR_EACH_OBSERVER( - Observer, observers_, OnErrorAdded(extension_errors->back())); +void ErrorConsole::UseDefaultReportingForExtension( + const std::string& extension_id) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (!enabled_ || !Extension::IdIsValid(extension_id)) + return; + + pref_map_.erase(extension_id); + ExtensionPrefs::Get(profile_)->UpdateExtensionPref( + extension_id, + kStoreExtensionErrorsPref, + NULL); } -const ErrorConsole::ErrorList& ErrorConsole::GetErrorsForExtension( +void ErrorConsole::ReportError(scoped_ptr<ExtensionError> error) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (!enabled_ || !Extension::IdIsValid(error->extension_id())) + return; + + ErrorPreferenceMap::const_iterator pref = + pref_map_.find(error->extension_id()); + // Check the mask to see if we report the error. If we don't have a specific + // entry, use the default mask. + if ((pref == pref_map_.end() && + ((default_mask_ & (1 << error->type())) == 0)) || + (pref != pref_map_.end() && (pref->second & (1 << error->type())) == 0)) { + return; + } + + const ExtensionError* weak_error = errors_.AddError(error.Pass()); + FOR_EACH_OBSERVER(Observer, observers_, OnErrorAdded(weak_error)); +} + +const ErrorList& ErrorConsole::GetErrorsForExtension( const std::string& extension_id) const { - ErrorMap::const_iterator iter = errors_.find(extension_id); - if (iter != errors_.end()) - return iter->second; - return g_empty_error_list.Get(); + return errors_.GetErrorsForExtension(extension_id); } void ErrorConsole::AddObserver(Observer* observer) { @@ -183,10 +170,16 @@ void ErrorConsole::Enable(ExtensionService* extension_service) { content::Source<Profile>(profile_)); if (extension_service) { - // Get manifest errors for extensions already installed. const ExtensionSet* extensions = extension_service->extensions(); + ExtensionPrefs* prefs = ExtensionPrefs::Get(profile_); for (ExtensionSet::const_iterator iter = extensions->begin(); iter != extensions->end(); ++iter) { + int mask = 0; + if (prefs->ReadPrefAsInteger(iter->get()->id(), + kStoreExtensionErrorsPref, + &mask)) { + pref_map_[iter->get()->id()] = mask; + } AddManifestErrorsForExtension(iter->get()); } } @@ -194,7 +187,7 @@ void ErrorConsole::Enable(ExtensionService* extension_service) { void ErrorConsole::Disable() { notification_registrar_.RemoveAll(); - RemoveAllErrors(); + errors_.RemoveAllErrors(); enabled_ = false; } @@ -211,27 +204,6 @@ void ErrorConsole::AddManifestErrorsForExtension(const Extension* extension) { } } -void ErrorConsole::RemoveIncognitoErrors() { - for (ErrorMap::iterator iter = errors_.begin(); - iter != errors_.end(); ++iter) { - DeleteIncognitoErrorsFromList(&(iter->second)); - } -} - -void ErrorConsole::RemoveErrorsForExtension(const std::string& extension_id) { - ErrorMap::iterator iter = errors_.find(extension_id); - if (iter != errors_.end()) { - STLDeleteContainerPointers(iter->second.begin(), iter->second.end()); - errors_.erase(iter); - } -} - -void ErrorConsole::RemoveAllErrors() { - for (ErrorMap::iterator iter = errors_.begin(); iter != errors_.end(); ++iter) - STLDeleteContainerPointers(iter->second.begin(), iter->second.end()); - errors_.clear(); -} - void ErrorConsole::Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) { @@ -241,14 +213,13 @@ void ErrorConsole::Observe(int type, // If incognito profile which we are associated with is destroyed, also // destroy all incognito errors. if (profile->IsOffTheRecord() && profile_->IsSameProfile(profile)) - RemoveIncognitoErrors(); + errors_.RemoveIncognitoErrors(); break; } case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: // No need to check the profile here, since we registered to only receive // notifications from our own. - RemoveErrorsForExtension( - content::Details<Extension>(details).ptr()->id()); + errors_.Remove(content::Details<Extension>(details).ptr()->id()); break; case chrome::NOTIFICATION_EXTENSION_INSTALLED: { const InstalledExtensionInfo* info = @@ -258,11 +229,8 @@ void ErrorConsole::Observe(int type, // to keep runtime errors, though, because extensions are reloaded on a // refresh of chrome:extensions, and we don't want to wipe our history // whenever that happens. - ErrorMap::iterator iter = errors_.find(info->extension->id()); - if (iter != errors_.end()) { - DeleteErrorsOfTypeFromList(&(iter->second), - ExtensionError::MANIFEST_ERROR); - } + errors_.RemoveErrorsForExtensionOfType(info->extension->id(), + ExtensionError::MANIFEST_ERROR); AddManifestErrorsForExtension(info->extension); break; diff --git a/chrome/browser/extensions/error_console/error_console.h b/chrome/browser/extensions/error_console/error_console.h index f4b0122..0dd155b 100644 --- a/chrome/browser/extensions/error_console/error_console.h +++ b/chrome/browser/extensions/error_console/error_console.h @@ -16,6 +16,7 @@ #include "base/threading/thread_checker.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" +#include "extensions/browser/error_map.h" #include "extensions/browser/extension_error.h" namespace content { @@ -38,9 +39,6 @@ class Extension; // BrowserContext-keyed service. class ErrorConsole : public content::NotificationObserver { public: - typedef std::deque<const ExtensionError*> ErrorList; - typedef std::map<std::string, ErrorList> ErrorMap; - class Observer { public: // Sent when a new error is reported to the error console. @@ -57,6 +55,16 @@ class ErrorConsole : public content::NotificationObserver { // Convenience method to return the ErrorConsole for a given profile. static ErrorConsole* Get(Profile* profile); + // Set whether or not errors of the specified |type| are stored for the + // extension with the given |extension_id|. This will be stored in the + // preferences. + void SetReportingForExtension(const std::string& extension_id, + ExtensionError::Type type, + bool enabled); + + // Restore default reporting to the given extension. + void UseDefaultReportingForExtension(const std::string& extension_id); + // Report an extension error, and add it to the list. void ReportError(scoped_ptr<ExtensionError> error); @@ -70,14 +78,25 @@ class ErrorConsole : public content::NotificationObserver { void RemoveObserver(Observer* observer); bool enabled() const { return enabled_; } - const ErrorMap& errors() const { return errors_; } + + // Return the number of entries (extensions) in the error map. + size_t get_num_entries_for_test() const { return errors_.size(); } + + // Set the default reporting for all extensions. + void set_default_reporting_for_test(ExtensionError::Type type, bool enabled) { + default_mask_ = + enabled ? default_mask_ | (1 << type) : default_mask_ & ~(1 << type); + } private: - FRIEND_TEST_ALL_PREFIXES(ErrorConsoleUnitTest, AddAndRemoveErrors); + // A map which stores the reporting preferences for each Extension. If there + // is no entry in the map, it signals that the |default_mask_| should be used. + typedef std::map<std::string, int32> ErrorPreferenceMap; // Enable the error console for error collection and retention. This involves // subscribing to the appropriate notifications and fetching manifest errors. void Enable(ExtensionService* extension_service); + // Disable the error console, removing the subscriptions to notifications and // removing all current errors. void Disable(); @@ -90,16 +109,6 @@ class ErrorConsole : public content::NotificationObserver { // Add manifest errors from an extension's install warnings. void AddManifestErrorsForExtension(const Extension* extension); - // Remove all errors which happened while incognito; we have to do this once - // the incognito profile is destroyed. - void RemoveIncognitoErrors(); - - // Remove all errors relating to a particular |extension_id|. - void RemoveErrorsForExtension(const std::string& extension_id); - - // Remove all errors for all extensions. - void RemoveAllErrors(); - // content::NotificationObserver implementation. virtual void Observe(int type, const content::NotificationSource& source, @@ -119,6 +128,12 @@ class ErrorConsole : public content::NotificationObserver { // The errors which we have received so far. ErrorMap errors_; + // The mapping of Extension's error-reporting preferences. + ErrorPreferenceMap pref_map_; + + // The default mask to use if an Extension does not have specific settings. + int32 default_mask_; + // The profile with which the ErrorConsole is associated. Only collect errors // from extensions and RenderViews associated with this Profile (and it's // incognito fellow). diff --git a/chrome/browser/extensions/error_console/error_console_browsertest.cc b/chrome/browser/extensions/error_console/error_console_browsertest.cc index c403cdf..cc430c1 100644 --- a/chrome/browser/extensions/error_console/error_console_browsertest.cc +++ b/chrome/browser/extensions/error_console/error_console_browsertest.cc @@ -267,7 +267,8 @@ class ErrorConsoleBrowserTest : public ExtensionBrowserTest { // We should only have errors for a single extension, or should have no // entries, if no errors were expected. - ASSERT_EQ(errors_expected > 0 ? 1u : 0u, error_console()->errors().size()); + ASSERT_EQ(errors_expected > 0 ? 1u : 0u, + error_console()->get_num_entries_for_test()); ASSERT_EQ( errors_expected, error_console()->GetErrorsForExtension((*extension)->id()).size()); @@ -294,7 +295,7 @@ IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, ReportManifestErrors) { ACTION_NONE, &extension); - const ErrorConsole::ErrorList& errors = + const ErrorList& errors = error_console()->GetErrorsForExtension(extension->id()); // Unfortunately, there's not always a hard guarantee of order in parsing the @@ -373,7 +374,7 @@ IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, std::string script_url = extension->url().Resolve("content_script.js").spec(); - const ErrorConsole::ErrorList& errors = + const ErrorList& errors = error_console()->GetErrorsForExtension(extension->id()); // The first error should be a console log. @@ -431,7 +432,7 @@ IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, BrowserActionRuntimeError) { std::string script_url = extension->url().Resolve("browser_action.js").spec(); - const ErrorConsole::ErrorList& errors = + const ErrorList& errors = error_console()->GetErrorsForExtension(extension->id()); std::string event_bindings_str = @@ -471,7 +472,7 @@ IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, BadAPIArgumentsRuntimeError) { ACTION_NONE, &extension); - const ErrorConsole::ErrorList& errors = + const ErrorList& errors = error_console()->GetErrorsForExtension(extension->id()); std::string schema_utils_str = @@ -510,7 +511,7 @@ IN_PROC_BROWSER_TEST_F(ErrorConsoleBrowserTest, BadAPIPermissionsRuntimeError) { std::string script_url = extension->url().Resolve("background.js").spec(); - const ErrorConsole::ErrorList& errors = + const ErrorList& errors = error_console()->GetErrorsForExtension(extension->id()); CheckRuntimeError( diff --git a/chrome/browser/extensions/error_console/error_console_unittest.cc b/chrome/browser/extensions/error_console/error_console_unittest.cc index e1738c8..981a0ff 100644 --- a/chrome/browser/extensions/error_console/error_console_unittest.cc +++ b/chrome/browser/extensions/error_console/error_console_unittest.cc @@ -4,64 +4,23 @@ #include "chrome/browser/extensions/error_console/error_console.h" -#include "base/json/json_writer.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/prefs/pref_service.h" -#include "base/strings/string16.h" #include "base/strings/string_number_conversions.h" -#include "base/strings/string_util.h" -#include "base/strings/utf_string_conversions.h" #include "chrome/common/pref_names.h" #include "chrome/test/base/testing_profile.h" -#include "content/public/common/url_constants.h" #include "extensions/browser/extension_error.h" +#include "extensions/browser/extension_error_test_util.h" #include "extensions/common/constants.h" #include "extensions/common/feature_switch.h" #include "extensions/common/id_util.h" #include "testing/gtest/include/gtest/gtest.h" -#include "url/gurl.h" - -using base::string16; namespace extensions { -namespace { - -const char kDefaultStackTrace[] = "function_name (https://url.com:1:1)"; - -StackTrace GetDefaultStackTrace() { - StackTrace stack_trace; - scoped_ptr<StackFrame> frame = - StackFrame::CreateFromText(base::UTF8ToUTF16(kDefaultStackTrace)); - CHECK(frame.get()); - stack_trace.push_back(*frame); - return stack_trace; -} - -base::string16 GetSourceForExtensionId(const std::string& extension_id) { - return base::UTF8ToUTF16( - std::string(kExtensionScheme) + - content::kStandardSchemeSeparator + - extension_id); -} - -scoped_ptr<ExtensionError> CreateNewRuntimeError( - bool from_incognito, - const std::string& extension_id, - const base::string16& message) { - return scoped_ptr<ExtensionError>(new RuntimeError( - extension_id, - from_incognito, - GetSourceForExtensionId(extension_id), - message, - GetDefaultStackTrace(), - GURL::EmptyGURL(), // no context url - logging::LOG_INFO, - 0, 0 /* Render [View|Process] ID */ )); -} - -} // namespace +using error_test_util::CreateNewManifestError; +using error_test_util::CreateNewRuntimeError; class ErrorConsoleUnitTest : public testing::Test { public: @@ -85,124 +44,66 @@ class ErrorConsoleUnitTest : public testing::Test { ErrorConsole* error_console_; }; -// Test adding errors, and removing them by reference, by incognito status, -// and in bulk. -TEST_F(ErrorConsoleUnitTest, AddAndRemoveErrors) { - ASSERT_EQ(0u, error_console_->errors().size()); - +// Test that errors are successfully reported. This is a simple test, since it +// is tested more thoroughly in extensions/browser/error_map_unittest.cc +TEST_F(ErrorConsoleUnitTest, ReportErrors) { const size_t kNumTotalErrors = 6; - const size_t kNumNonIncognitoErrors = 3; const std::string kId = id_util::GenerateId("id"); - // Populate with both incognito and non-incognito errors (evenly distributed). - for (size_t i = 0; i < kNumTotalErrors; ++i) { - error_console_->ReportError( - CreateNewRuntimeError(i % 2 == 0, kId, base::UintToString16(i))); - } - - // There should only be one entry in the map, since errors are stored in lists - // keyed by extension id. - ASSERT_EQ(1u, error_console_->errors().size()); - - ASSERT_EQ(kNumTotalErrors, error_console_->GetErrorsForExtension(kId).size()); - - // Remove the incognito errors; three errors should remain, and all should - // be from non-incognito contexts. - error_console_->RemoveIncognitoErrors(); - const ErrorConsole::ErrorList& errors = - error_console_->GetErrorsForExtension(kId); - ASSERT_EQ(kNumNonIncognitoErrors, errors.size()); - for (size_t i = 0; i < errors.size(); ++i) - ASSERT_FALSE(errors[i]->from_incognito()); - - // Add another error for a different extension id. - const std::string kSecondId = id_util::GenerateId("id2"); - error_console_->ReportError( - CreateNewRuntimeError(false, kSecondId, base::string16())); - - // There should be two entries now, one for each id, and there should be one - // error for the second extension. - ASSERT_EQ(2u, error_console_->errors().size()); - ASSERT_EQ(1u, error_console_->GetErrorsForExtension(kSecondId).size()); - - // Remove all errors for the second id. - error_console_->RemoveErrorsForExtension(kSecondId); - ASSERT_EQ(1u, error_console_->errors().size()); - ASSERT_EQ(0u, error_console_->GetErrorsForExtension(kSecondId).size()); - // First extension should be unaffected. - ASSERT_EQ(kNumNonIncognitoErrors, - error_console_->GetErrorsForExtension(kId).size()); - - // Remove remaining errors. - error_console_->RemoveAllErrors(); - ASSERT_EQ(0u, error_console_->errors().size()); ASSERT_EQ(0u, error_console_->GetErrorsForExtension(kId).size()); -} - -// Test that if we add enough errors, only the most recent -// kMaxErrorsPerExtension are kept. -TEST_F(ErrorConsoleUnitTest, ExcessiveErrorsGetCropped) { - ASSERT_EQ(0u, error_console_->errors().size()); - - // This constant matches one of the same name in error_console.cc. - const size_t kMaxErrorsPerExtension = 100; - const size_t kNumExtraErrors = 5; - const std::string kId = id_util::GenerateId("id"); - // Add new errors, with each error's message set to its number. - for (size_t i = 0; i < kMaxErrorsPerExtension + kNumExtraErrors; ++i) { + for (size_t i = 0; i < kNumTotalErrors; ++i) { error_console_->ReportError( - CreateNewRuntimeError(false, kId, base::UintToString16(i))); + CreateNewManifestError(kId, base::UintToString(i))); } - ASSERT_EQ(1u, error_console_->errors().size()); - - const ErrorConsole::ErrorList& errors = - error_console_->GetErrorsForExtension(kId); - ASSERT_EQ(kMaxErrorsPerExtension, errors.size()); - - // We should have popped off errors in the order they arrived, so the - // first stored error should be the 6th reported (zero-based)... - ASSERT_EQ(base::UintToString16(kNumExtraErrors), - errors.front()->message()); - // ..and the last stored should be the 105th reported. - ASSERT_EQ(base::UintToString16(kMaxErrorsPerExtension + kNumExtraErrors - 1), - errors.back()->message()); + ASSERT_EQ(kNumTotalErrors, error_console_->GetErrorsForExtension(kId).size()); } -// Test to ensure that the error console will not add duplicate errors, but will -// keep the latest version of an error. -TEST_F(ErrorConsoleUnitTest, DuplicateErrorsAreReplaced) { - ASSERT_EQ(0u, error_console_->errors().size()); +TEST_F(ErrorConsoleUnitTest, DontStoreErrorsWithoutEnablingType) { + // Disable default runtime error reporting. + error_console_->set_default_reporting_for_test(ExtensionError::RUNTIME_ERROR, + false); const std::string kId = id_util::GenerateId("id"); - const size_t kNumErrors = 3u; - // Report three errors. - for (size_t i = 0; i < kNumErrors; ++i) { - error_console_->ReportError( - CreateNewRuntimeError(false, kId, base::UintToString16(i))); - } + // Try to report a runtime error - it should be ignored. + error_console_->ReportError(CreateNewRuntimeError(kId, "a")); + ASSERT_EQ(0u, error_console_->GetErrorsForExtension(kId).size()); - // Create an error identical to the second error reported, and save its - // location. - scoped_ptr<ExtensionError> runtime_error2 = - CreateNewRuntimeError(false, kId, base::UintToString16(1u)); - const ExtensionError* weak_error = runtime_error2.get(); - - // Add it to the error console. - error_console_->ReportError(runtime_error2.Pass()); - - // We should only have three errors stored, since two of the four reported - // were identical, and the older should have been replaced. - ASSERT_EQ(1u, error_console_->errors().size()); - const ErrorConsole::ErrorList& errors = - error_console_->GetErrorsForExtension(kId); - ASSERT_EQ(kNumErrors, errors.size()); - - // The duplicate error should be the last reported (pointer comparison)... - ASSERT_EQ(weak_error, errors.back()); - // ... and should have two reported occurrences. - ASSERT_EQ(2u, errors.back()->occurrences()); + // Check that manifest errors are reported successfully. + error_console_->ReportError(CreateNewManifestError(kId, "b")); + ASSERT_EQ(1u, error_console_->GetErrorsForExtension(kId).size()); + + // We should still ignore runtime errors. + error_console_->ReportError(CreateNewRuntimeError(kId, "c")); + ASSERT_EQ(1u, error_console_->GetErrorsForExtension(kId).size()); + + // Enable runtime errors specifically for this extension, and disable the use + // of the default mask. + error_console_->SetReportingForExtension( + kId, ExtensionError::RUNTIME_ERROR, true); + + // We should now accept runtime and manifest errors. + error_console_->ReportError(CreateNewManifestError(kId, "d")); + ASSERT_EQ(2u, error_console_->GetErrorsForExtension(kId).size()); + error_console_->ReportError(CreateNewRuntimeError(kId, "e")); + ASSERT_EQ(3u, error_console_->GetErrorsForExtension(kId).size()); + + // All other extensions should still use the default mask, and ignore runtime + // errors but report manifest errors. + const std::string kId2 = id_util::GenerateId("id2"); + error_console_->ReportError(CreateNewRuntimeError(kId2, "f")); + ASSERT_EQ(0u, error_console_->GetErrorsForExtension(kId2).size()); + error_console_->ReportError(CreateNewManifestError(kId2, "g")); + ASSERT_EQ(1u, error_console_->GetErrorsForExtension(kId2).size()); + + // By reverting to default reporting, we should again allow manifest errors, + // but not runtime errors. + error_console_->UseDefaultReportingForExtension(kId); + error_console_->ReportError(CreateNewManifestError(kId, "h")); + ASSERT_EQ(4u, error_console_->GetErrorsForExtension(kId).size()); + error_console_->ReportError(CreateNewRuntimeError(kId, "i")); + ASSERT_EQ(4u, error_console_->GetErrorsForExtension(kId).size()); } } // namespace extensions diff --git a/chrome/browser/ui/webui/extensions/extension_settings_handler.cc b/chrome/browser/ui/webui/extensions/extension_settings_handler.cc index 1fd9ecd..520cdbe 100644 --- a/chrome/browser/ui/webui/extensions/extension_settings_handler.cc +++ b/chrome/browser/ui/webui/extensions/extension_settings_handler.cc @@ -315,12 +315,12 @@ base::DictionaryValue* ExtensionSettingsHandler::CreateExtensionDetailValue( ErrorConsole* error_console = ErrorConsole::Get(extension_service_->profile()); if (error_console->enabled()) { - const ErrorConsole::ErrorList& errors = + const ErrorList& errors = error_console->GetErrorsForExtension(extension->id()); if (!errors.empty()) { scoped_ptr<base::ListValue> manifest_errors(new base::ListValue); scoped_ptr<base::ListValue> runtime_errors(new base::ListValue); - for (ErrorConsole::ErrorList::const_iterator iter = errors.begin(); + for (ErrorList::const_iterator iter = errors.begin(); iter != errors.end(); ++iter) { if ((*iter)->type() == ExtensionError::MANIFEST_ERROR) { manifest_errors->Append((*iter)->ToValue().release()); diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 4fc3639..7b4b0b3 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -527,8 +527,11 @@ '../components/autofill/content/renderer/test_password_autofill_agent.cc', '../components/autofill/content/renderer/test_password_autofill_agent.h', '../extensions/browser/admin_policy_unittest.cc', + '../extensions/browser/error_map_unittest.cc', '../extensions/browser/event_listener_map_unittest.cc', '../extensions/browser/event_router_unittest.cc', + '../extensions/browser/extension_error_test_util.cc', + '../extensions/browser/extension_error_test_util.h', '../extensions/browser/extension_pref_value_map_unittest.cc', '../extensions/browser/extension_registry_unittest.cc', '../extensions/browser/file_highlighter_unittest.cc', diff --git a/extensions/browser/error_map.cc b/extensions/browser/error_map.cc new file mode 100644 index 0000000..dfcef9f --- /dev/null +++ b/extensions/browser/error_map.cc @@ -0,0 +1,138 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/browser/error_map.h" + +#include "base/lazy_instance.h" +#include "base/stl_util.h" +#include "extensions/common/extension.h" + +namespace extensions { +namespace { + +// The maximum number of errors to be stored per extension. +const size_t kMaxErrorsPerExtension = 100; + +base::LazyInstance<ErrorList> g_empty_error_list = LAZY_INSTANCE_INITIALIZER; + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// ErrorMap::ExtensionEntry +ErrorMap::ExtensionEntry::ExtensionEntry() { +} + +ErrorMap::ExtensionEntry::~ExtensionEntry() { + DeleteAllErrors(); +} + +void ErrorMap::ExtensionEntry::DeleteAllErrors() { + STLDeleteContainerPointers(list_.begin(), list_.end()); + list_.clear(); +} + +void ErrorMap::ExtensionEntry::DeleteIncognitoErrors() { + ErrorList::iterator iter = list_.begin(); + while (iter != list_.end()) { + if ((*iter)->from_incognito()) { + delete *iter; + iter = list_.erase(iter); + } else { + ++iter; + } + } +} + +void ErrorMap::ExtensionEntry::DeleteErrorsOfType(ExtensionError::Type type) { + ErrorList::iterator iter = list_.begin(); + while (iter != list_.end()) { + if ((*iter)->type() == type) { + delete *iter; + iter = list_.erase(iter); + } else { + ++iter; + } + } +} + +const ExtensionError* ErrorMap::ExtensionEntry::AddError( + scoped_ptr<ExtensionError> error) { + for (ErrorList::iterator iter = list_.begin(); iter != list_.end(); ++iter) { + // If we find a duplicate error, remove the old error and add the new one, + // incrementing the occurrence count of the error. We use the new error + // for runtime errors, so we can link to the latest context, inspectable + // view, etc. + if (error->IsEqual(*iter)) { + error->set_occurrences((*iter)->occurrences() + 1); + delete *iter; + list_.erase(iter); + break; + } + } + + // If there are too many errors for an extension already, limit ourselves to + // the most recent ones. + if (list_.size() >= kMaxErrorsPerExtension) { + delete list_.front(); + list_.pop_front(); + } + + list_.push_back(error.release()); + return list_.back(); +} + +//////////////////////////////////////////////////////////////////////////////// +// ErrorMap +ErrorMap::ErrorMap() { +} + +ErrorMap::~ErrorMap() { + RemoveAllErrors(); +} + +const ErrorList& ErrorMap::GetErrorsForExtension( + const std::string& extension_id) const { + EntryMap::const_iterator iter = map_.find(extension_id); + return iter != map_.end() ? *iter->second->list() : g_empty_error_list.Get(); +} + +const ExtensionError* ErrorMap::AddError(scoped_ptr<ExtensionError> error) { + EntryMap::iterator iter = map_.find(error->extension_id()); + if (iter == map_.end()) { + iter = map_.insert(std::pair<std::string, ExtensionEntry*>( + error->extension_id(), new ExtensionEntry)).first; + } + return iter->second->AddError(error.Pass()); +} + +void ErrorMap::Remove(const std::string& extension_id) { + EntryMap::iterator iter = map_.find(extension_id); + if (iter == map_.end()) + return; + + delete iter->second; + map_.erase(iter); +} + +void ErrorMap::RemoveErrorsForExtensionOfType(const std::string& extension_id, + ExtensionError::Type type) { + EntryMap::iterator iter = map_.find(extension_id); + if (iter != map_.end()) + iter->second->DeleteErrorsOfType(type); +} + +void ErrorMap::RemoveIncognitoErrors() { + for (EntryMap::iterator iter = map_.begin(); iter != map_.end(); ++iter) + iter->second->DeleteIncognitoErrors(); +} + +void ErrorMap::RemoveAllErrors() { + for (EntryMap::iterator iter = map_.begin(); iter != map_.end(); ++iter) { + iter->second->DeleteAllErrors(); + delete iter->second; + } + map_.clear(); +} + +} // namespace extensions diff --git a/extensions/browser/error_map.h b/extensions/browser/error_map.h new file mode 100644 index 0000000..2dcff22 --- /dev/null +++ b/extensions/browser/error_map.h @@ -0,0 +1,84 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_BROWSER_ERROR_MAP_H_ +#define EXTENSIONS_BROWSER_ERROR_MAP_H_ + +#include <deque> +#include <map> +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "extensions/browser/extension_error.h" + +namespace extensions { + +typedef std::deque<const ExtensionError*> ErrorList; + +// An ErrorMap is responsible for storing Extension-related errors, keyed by +// Extension ID. The errors are owned by the ErrorMap, and are deleted upon +// destruction. +class ErrorMap { + public: + explicit ErrorMap(); + ~ErrorMap(); + + // Return the list of all errors associated with the given extension. + const ErrorList& GetErrorsForExtension(const std::string& extension_id) const; + + // Add the |error| to the ErrorMap. + const ExtensionError* AddError(scoped_ptr<ExtensionError> error); + + // Remove an extension from the ErrorMap, deleting all associated errors. + void Remove(const std::string& extension_id); + // Remove all errors of a given type for an extension. + void RemoveErrorsForExtensionOfType(const std::string& extension_id, + ExtensionError::Type type); + // Remove all incognito errors for all extensions. + void RemoveIncognitoErrors(); + // Remove all errors for all extensions, and clear the map. + void RemoveAllErrors(); + + size_t size() const { return map_.size(); } + + private: + // An Entry is created for each Extension ID, and stores the errors related to + // that Extension. + class ExtensionEntry { + public: + explicit ExtensionEntry(); + ~ExtensionEntry(); + + // Delete all errors associated with this extension. + void DeleteAllErrors(); + // Delete all incognito errors associated with this extension. + void DeleteIncognitoErrors(); + // Delete all errors of the given |type| associated with this extension. + void DeleteErrorsOfType(ExtensionError::Type type); + + // Add the error to the list, and return a weak reference. + const ExtensionError* AddError(scoped_ptr<ExtensionError> error); + + const ErrorList* list() const { return &list_; } + + private: + // The list of all errors associated with the extension. The errors are + // owned by the Entry (in turn owned by the ErrorMap) and are deleted upon + // destruction. + ErrorList list_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionEntry); + }; + typedef std::map<std::string, ExtensionEntry*> EntryMap; + + // The mapping between Extension IDs and their corresponding Entries. + EntryMap map_; + + DISALLOW_COPY_AND_ASSIGN(ErrorMap); +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_ERROR_MAP_H_ diff --git a/extensions/browser/error_map_unittest.cc b/extensions/browser/error_map_unittest.cc new file mode 100644 index 0000000..8330c6a --- /dev/null +++ b/extensions/browser/error_map_unittest.cc @@ -0,0 +1,147 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/browser/error_map.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_number_conversions.h" +#include "extensions/browser/extension_error.h" +#include "extensions/browser/extension_error_test_util.h" +#include "extensions/common/constants.h" +#include "extensions/common/id_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace extensions { + +using error_test_util::CreateNewRuntimeError; + +class ErrorMapUnitTest : public testing::Test { + public: + ErrorMapUnitTest() { } + virtual ~ErrorMapUnitTest() { } + + virtual void SetUp() OVERRIDE { + testing::Test::SetUp(); + } + + protected: + ErrorMap errors_; +}; + +// Test adding errors, and removing them by reference, by incognito status, +// and in bulk. +TEST_F(ErrorMapUnitTest, AddAndRemoveErrors) { + ASSERT_EQ(0u, errors_.size()); + + const size_t kNumTotalErrors = 6; + const size_t kNumNonIncognitoErrors = 3; + const std::string kId = id_util::GenerateId("id"); + // Populate with both incognito and non-incognito errors (evenly distributed). + for (size_t i = 0; i < kNumTotalErrors; ++i) { + ASSERT_TRUE(errors_.AddError( + CreateNewRuntimeError(kId, base::UintToString(i), i % 2 == 0))); + } + + // There should only be one entry in the map, since errors are stored in lists + // keyed by extension id. + ASSERT_EQ(1u, errors_.size()); + + ASSERT_EQ(kNumTotalErrors, errors_.GetErrorsForExtension(kId).size()); + + // Remove the incognito errors; three errors should remain, and all should + // be from non-incognito contexts. + errors_.RemoveIncognitoErrors(); + const ErrorList& list = errors_.GetErrorsForExtension(kId); + ASSERT_EQ(kNumNonIncognitoErrors, list.size()); + for (size_t i = 0; i < list.size(); ++i) + ASSERT_FALSE(list[i]->from_incognito()); + + // Add another error for a different extension id. + const std::string kSecondId = id_util::GenerateId("id2"); + ASSERT_TRUE(errors_.AddError(CreateNewRuntimeError(kSecondId, "foo"))); + + // There should be two entries now, one for each id, and there should be one + // error for the second extension. + ASSERT_EQ(2u, errors_.size()); + ASSERT_EQ(1u, errors_.GetErrorsForExtension(kSecondId).size()); + + // Remove all errors for the second id. + errors_.Remove(kSecondId); + ASSERT_EQ(1u, errors_.size()); + ASSERT_EQ(0u, errors_.GetErrorsForExtension(kSecondId).size()); + // First extension should be unaffected. + ASSERT_EQ(kNumNonIncognitoErrors, + errors_.GetErrorsForExtension(kId).size()); + + // Remove remaining errors. + errors_.RemoveAllErrors(); + ASSERT_EQ(0u, errors_.size()); + ASSERT_EQ(0u, errors_.GetErrorsForExtension(kId).size()); +} + +// Test that if we add enough errors, only the most recent +// kMaxErrorsPerExtension are kept. +TEST_F(ErrorMapUnitTest, ExcessiveErrorsGetCropped) { + ASSERT_EQ(0u, errors_.size()); + + // This constant matches one of the same name in error_console.cc. + const size_t kMaxErrorsPerExtension = 100; + const size_t kNumExtraErrors = 5; + const std::string kId = id_util::GenerateId("id"); + + // Add new errors, with each error's message set to its number. + for (size_t i = 0; i < kMaxErrorsPerExtension + kNumExtraErrors; ++i) { + ASSERT_TRUE(errors_.AddError( + CreateNewRuntimeError(kId, base::UintToString(i)))); + } + + ASSERT_EQ(1u, errors_.size()); + + const ErrorList& list = errors_.GetErrorsForExtension(kId); + ASSERT_EQ(kMaxErrorsPerExtension, list.size()); + + // We should have popped off errors in the order they arrived, so the + // first stored error should be the 6th reported (zero-based)... + ASSERT_EQ(base::UintToString16(kNumExtraErrors), + list.front()->message()); + // ..and the last stored should be the 105th reported. + ASSERT_EQ(base::UintToString16(kMaxErrorsPerExtension + kNumExtraErrors - 1), + list.back()->message()); +} + +// Test to ensure that the error console will not add duplicate errors, but will +// keep the latest version of an error. +TEST_F(ErrorMapUnitTest, DuplicateErrorsAreReplaced) { + ASSERT_EQ(0u, errors_.size()); + + const std::string kId = id_util::GenerateId("id"); + const size_t kNumErrors = 3u; + + // Report three errors. + for (size_t i = 0; i < kNumErrors; ++i) { + ASSERT_TRUE(errors_.AddError( + CreateNewRuntimeError(kId, base::UintToString(i)))); + } + + // Create an error identical to the second error reported, save its + // location, and add it to the error map. + scoped_ptr<ExtensionError> runtime_error2 = + CreateNewRuntimeError(kId, base::UintToString(1u)); + const ExtensionError* weak_error = runtime_error2.get(); + ASSERT_TRUE(errors_.AddError(runtime_error2.Pass())); + + // We should only have three errors stored, since two of the four reported + // were identical, and the older should have been replaced. + ASSERT_EQ(1u, errors_.size()); + const ErrorList& list = errors_.GetErrorsForExtension(kId); + ASSERT_EQ(kNumErrors, list.size()); + + // The duplicate error should be the last reported (pointer comparison)... + ASSERT_EQ(weak_error, list.back()); + // ... and should have two reported occurrences. + ASSERT_EQ(2u, list.back()->occurrences()); +} + +} // namespace extensions diff --git a/extensions/browser/extension_error_test_util.cc b/extensions/browser/extension_error_test_util.cc new file mode 100644 index 0000000..cdf4917 --- /dev/null +++ b/extensions/browser/extension_error_test_util.cc @@ -0,0 +1,64 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/browser/extension_error_test_util.h" + +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "content/public/common/url_constants.h" +#include "extensions/browser/extension_error.h" +#include "extensions/common/constants.h" +#include "extensions/common/stack_frame.h" +#include "url/gurl.h" + +namespace extensions { +namespace error_test_util { + +namespace { +const char kDefaultStackTrace[] = "function_name (https://url.com:1:1)"; +} + +scoped_ptr<ExtensionError> CreateNewRuntimeError( + const std::string& extension_id, + const std::string& message, + bool from_incognito) { + StackTrace stack_trace; + scoped_ptr<StackFrame> frame = + StackFrame::CreateFromText(base::UTF8ToUTF16(kDefaultStackTrace)); + CHECK(frame.get()); + stack_trace.push_back(*frame); + + base::string16 source = + base::UTF8ToUTF16(std::string(kExtensionScheme) + + content::kStandardSchemeSeparator + + extension_id); + + return scoped_ptr<ExtensionError>(new RuntimeError( + extension_id, + from_incognito, + source, + base::UTF8ToUTF16(message), + stack_trace, + GURL::EmptyGURL(), // no context url + logging::LOG_INFO, + 0, 0 /* Render [View|Process] ID */ )); +} + +scoped_ptr<ExtensionError> CreateNewRuntimeError( + const std::string& extension_id, const std::string& message) { + return CreateNewRuntimeError(extension_id, message, false); +} + +scoped_ptr<ExtensionError> CreateNewManifestError( + const std::string& extension_id, const std::string& message) { + return scoped_ptr<ExtensionError>( + new ManifestError(extension_id, + base::UTF8ToUTF16(message), + base::EmptyString16(), + base::EmptyString16())); +} + +} // namespace error_test_util +} // namespace extensions diff --git a/extensions/browser/extension_error_test_util.h b/extensions/browser/extension_error_test_util.h new file mode 100644 index 0000000..4866cef --- /dev/null +++ b/extensions/browser/extension_error_test_util.h @@ -0,0 +1,35 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_BROWSER_EXTENSION_ERROR_TEST_UTIL_H_ +#define EXTENSIONS_BROWSER_EXTENSION_ERROR_TEST_UTIL_H_ + +#include <string> + +#include "base/memory/scoped_ptr.h" + +namespace extensions { + +class ExtensionError; + +namespace error_test_util { + +// Create a new RuntimeError. +scoped_ptr<ExtensionError> CreateNewRuntimeError( + const std::string& extension_id, + const std::string& message, + bool from_incognito); + +// Create a new RuntimeError; incognito defaults to "false". +scoped_ptr<ExtensionError> CreateNewRuntimeError( + const std::string& extension_id, const std::string& message); + +// Create a new ManifestError. +scoped_ptr<ExtensionError> CreateNewManifestError( + const std::string& extension_id, const std::string& mnessage); + +} // namespace error_test_util +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_EXTENSION_ERROR_TEST_UTIL_H_ diff --git a/extensions/extensions.gyp b/extensions/extensions.gyp index e58aaa3..770f2d4 100644 --- a/extensions/extensions.gyp +++ b/extensions/extensions.gyp @@ -168,6 +168,8 @@ 'browser/admin_policy.cc', 'browser/admin_policy.h', 'browser/app_sorting.h', + 'browser/error_map.cc', + 'browser/error_map.h', 'browser/event_listener_map.cc', 'browser/event_listener_map.h', 'browser/event_router.cc', |