diff options
author | battre@chromium.org <battre@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-11 18:53:25 +0000 |
---|---|---|
committer | battre@chromium.org <battre@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-10-11 18:53:25 +0000 |
commit | 7c1490dad18464a5ae6f748cbf645ff7fca4a108 (patch) | |
tree | 35bd01dc78a4d167c8e524fb15e55eae558ba82b /chrome | |
parent | 4647cbe2346b9db9444634e05198d1f5f84f50b9 (diff) | |
download | chromium_src-7c1490dad18464a5ae6f748cbf645ff7fca4a108.zip chromium_src-7c1490dad18464a5ae6f748cbf645ff7fca4a108.tar.gz chromium_src-7c1490dad18464a5ae6f748cbf645ff7fca4a108.tar.bz2 |
Warn user in case extension delays network traffic too much.
This CL adds a badge to the wrench menu and warning messages to chrome://extensions in case an extension delays network traffic too much and thereby causes a bad user experience.
BUG=82618
TEST=Install an extension using the webRequest API in a debug build. In debug build the extension should delay the network traffic enough to cause warning messages. You can use https://adblockplus.org/development-builds/experimental-adblock-plus-for-chrome-builds-available-with-better-blocking for example.
Review URL: http://codereview.chromium.org/8176001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@104929 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
21 files changed, 846 insertions, 20 deletions
diff --git a/chrome/app/chrome_command_ids.h b/chrome/app/chrome_command_ids.h index 21433b3..6bbad9b 100644 --- a/chrome/app/chrome_command_ids.h +++ b/chrome/app/chrome_command_ids.h @@ -170,6 +170,7 @@ #define IDC_FILE_MANAGER 40028 #define IDC_BOOKMARKS_MENU 40029 #define IDC_SHOW_SYNC_SETUP 40030 +#define IDC_EXTENSION_ERRORS 40031 // Spell-check // Insert any additional suggestions before _LAST; these have to be consecutive. diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index b84aefe..37e0ded 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -4115,6 +4115,17 @@ Keep your key file in a safe place. You will need it to create new versions of y Web Store </message> + <!-- Global error messages for extensions --> + <message name="IDS_EXTENSION_WARNINGS_WRENCH_MENU_ITEM" desc="The wrench menu item indicating that extensions caused problems"> + Misbehaving extension + </message> + <message name="IDS_EXTENSION_WARNINGS_TITLE" desc="The title of a section in chrome://extensions which contains the warning(s) that relates to one particular extension"> + Warning: + </message> + <message name="IDS_EXTENSION_WARNINGS_NETWORK_DELAY" desc="Warning message indicating that an extension caused excessive network delays for web requests"> + This extension is slowing down <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>. You should disable it to restore <ph name="PRODUCT_NAME">$1<ex>Google Chrome</ex></ph>'s performance. + </message> + <!-- Plugins --> <message name="IDS_PLUGINS_TITLE" desc="Title for the chrome://plugins page."> Plug-ins diff --git a/chrome/browser/extensions/extension_global_error_badge.cc b/chrome/browser/extensions/extension_global_error_badge.cc new file mode 100644 index 0000000..8124dc8 --- /dev/null +++ b/chrome/browser/extensions/extension_global_error_badge.cc @@ -0,0 +1,75 @@ +// Copyright (c) 2011 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 "chrome/browser/extensions/extension_global_error_badge.h" + +#include "base/logging.h" +#include "chrome/app/chrome_command_ids.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +ExtensionGlobalErrorBadge::ExtensionGlobalErrorBadge() {} + +ExtensionGlobalErrorBadge::~ExtensionGlobalErrorBadge() {} + +bool ExtensionGlobalErrorBadge::HasBadge() { + return true; +} + +bool ExtensionGlobalErrorBadge::HasMenuItem() { + return true; +} + +int ExtensionGlobalErrorBadge::MenuItemCommandID() { + return IDC_EXTENSION_ERRORS; +} + +string16 ExtensionGlobalErrorBadge::MenuItemLabel() { + return l10n_util::GetStringUTF16(IDS_EXTENSION_WARNINGS_WRENCH_MENU_ITEM); +} + +void ExtensionGlobalErrorBadge::ExecuteMenuItem(Browser* browser) { + ExtensionService* extension_service = + browser->GetProfile()->GetExtensionService(); + + // Suppress all current warnings in the extension service from triggering + // a badge on the wrench menu in the future of this session. + extension_service->extension_warnings()->SuppressBadgeForCurrentWarnings(); + + browser->ExecuteCommand(IDC_MANAGE_EXTENSIONS); +} + +bool ExtensionGlobalErrorBadge::HasBubbleView() { + return false; +} + +string16 ExtensionGlobalErrorBadge::GetBubbleViewTitle() { + return string16(); +} + +string16 ExtensionGlobalErrorBadge::GetBubbleViewMessage() { + return string16(); +} + +string16 ExtensionGlobalErrorBadge::GetBubbleViewAcceptButtonLabel() { + return string16(); +} + +string16 ExtensionGlobalErrorBadge::GetBubbleViewCancelButtonLabel() { + return string16(); +} + +void ExtensionGlobalErrorBadge::BubbleViewDidClose() { +} + +void ExtensionGlobalErrorBadge::BubbleViewAcceptButtonPressed() { + NOTREACHED(); +} + +void ExtensionGlobalErrorBadge::BubbleViewCancelButtonPressed() { + NOTREACHED(); +} diff --git a/chrome/browser/extensions/extension_global_error_badge.h b/chrome/browser/extensions/extension_global_error_badge.h new file mode 100644 index 0000000..f49ce4b --- /dev/null +++ b/chrome/browser/extensions/extension_global_error_badge.h @@ -0,0 +1,45 @@ +// Copyright (c) 2011 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 CHROME_BROWSER_EXTENSIONS_EXTENSION_GLOBAL_ERROR_BADGE_H_ +#define CHROME_BROWSER_EXTENSIONS_EXTENSION_GLOBAL_ERROR_BADGE_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "chrome/browser/ui/global_error.h" + +// Non-modal GlobalError implementation that warns the user if extensions +// created warnings or errors. If the user clicks on the wrench menu, the user +// is redirected to chrome://extensions to inspect the errors. +// +// This class is a candidate to be merged with ExtensionGlobalError once +// it is finished. +class ExtensionGlobalErrorBadge : public GlobalError { + public: + ExtensionGlobalErrorBadge(); + virtual ~ExtensionGlobalErrorBadge(); + + // Implementation for GlobalError: + virtual bool HasBadge() OVERRIDE; + + virtual bool HasMenuItem() OVERRIDE; + virtual int MenuItemCommandID() OVERRIDE; + virtual string16 MenuItemLabel() OVERRIDE; + virtual void ExecuteMenuItem(Browser* browser) OVERRIDE; + + virtual bool HasBubbleView() OVERRIDE; + virtual string16 GetBubbleViewTitle() OVERRIDE; + virtual string16 GetBubbleViewMessage() OVERRIDE; + virtual string16 GetBubbleViewAcceptButtonLabel() OVERRIDE; + virtual string16 GetBubbleViewCancelButtonLabel() OVERRIDE; + virtual void BubbleViewDidClose() OVERRIDE; + virtual void BubbleViewAcceptButtonPressed() OVERRIDE; + virtual void BubbleViewCancelButtonPressed() OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(ExtensionGlobalErrorBadge); +}; + +#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_GLOBAL_ERROR_BADGE_H_ diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc index 58ec01c..86004d7 100644 --- a/chrome/browser/extensions/extension_service.cc +++ b/chrome/browser/extensions/extension_service.cc @@ -603,7 +603,8 @@ ExtensionService::ExtensionService(Profile* profile, app_notification_manager_(new AppNotificationManager(profile)), permissions_manager_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), apps_promo_(profile->GetPrefs()), - event_routers_initialized_(false) { + event_routers_initialized_(false), + extension_warnings_(profile) { CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); // Figure out if extension installation should be enabled. @@ -971,6 +972,12 @@ bool ExtensionService::UninstallExtension( UserMetrics::RecordAction( UserMetricsAction("Extensions.ExtensionUninstalled")); + // Uninstalling one extension might have solved the problems of others. + // Therefore, we clear warnings of this type for all extensions. + std::set<ExtensionWarningSet::WarningType> warnings; + extension_warnings_.GetWarningsAffectingExtension(extension_id, &warnings); + extension_warnings_.ClearWarnings(warnings); + return true; } @@ -1071,6 +1078,12 @@ void ExtensionService::DisableExtension(const std::string& extension_id) { NotifyExtensionUnloaded(extension, extension_misc::UNLOAD_REASON_DISABLE); SyncExtensionChangeIfNeeded(*extension); + + // Deactivating one extension might have solved the problems of others. + // Therefore, we clear warnings of this type for all extensions. + std::set<ExtensionWarningSet::WarningType> warnings; + extension_warnings_.GetWarningsAffectingExtension(extension_id, &warnings); + extension_warnings_.ClearWarnings(warnings); } void ExtensionService::GrantPermissions(const Extension* extension) { diff --git a/chrome/browser/extensions/extension_service.h b/chrome/browser/extensions/extension_service.h index fa0a3cc..f30c480 100644 --- a/chrome/browser/extensions/extension_service.h +++ b/chrome/browser/extensions/extension_service.h @@ -30,6 +30,7 @@ #include "chrome/browser/extensions/extension_settings_frontend.h" #include "chrome/browser/extensions/extension_sync_data.h" #include "chrome/browser/extensions/extension_toolbar_model.h" +#include "chrome/browser/extensions/extension_warning_set.h" #include "chrome/browser/extensions/extensions_quota_service.h" #include "chrome/browser/extensions/external_extension_provider_interface.h" #include "chrome/browser/extensions/pending_extension_manager.h" @@ -554,6 +555,10 @@ class ExtensionService } #endif + ExtensionWarningSet* extension_warnings() { + return &extension_warnings_; + } + private: // Bundle of type (app or extension)-specific sync stuff. struct SyncBundle { @@ -817,6 +822,9 @@ class ExtensionService SyncBundle app_sync_bundle_; SyncBundle extension_sync_bundle_; + // Contains an entry for each warning that shall be currently shown. + ExtensionWarningSet extension_warnings_; + FRIEND_TEST_ALL_PREFIXES(ExtensionServiceTest, InstallAppsWithUnlimtedStorage); FRIEND_TEST_ALL_PREFIXES(ExtensionServiceTest, diff --git a/chrome/browser/extensions/extension_ui_unittest.cc b/chrome/browser/extensions/extension_ui_unittest.cc index 587645e..c92d742 100644 --- a/chrome/browser/extensions/extension_ui_unittest.cc +++ b/chrome/browser/extensions/extension_ui_unittest.cc @@ -40,7 +40,7 @@ namespace { EXPECT_EQ("", error); return ExtensionSettingsHandler::CreateExtensionDetailValue( - NULL, extension.get(), pages, true, false); + NULL, extension.get(), pages, NULL, true, false); } diff --git a/chrome/browser/extensions/extension_warning_set.cc b/chrome/browser/extensions/extension_warning_set.cc new file mode 100644 index 0000000..c99f18a2 --- /dev/null +++ b/chrome/browser/extensions/extension_warning_set.cc @@ -0,0 +1,167 @@ +// Copyright (c) 2011 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 "chrome/browser/extensions/extension_warning_set.h" + +#include "chrome/browser/extensions/extension_global_error_badge.h" +#include "chrome/browser/ui/global_error_service.h" +#include "chrome/browser/ui/global_error_service_factory.h" +#include "chrome/common/chrome_notification_types.h" +#include "content/common/notification_service.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +// This class is used to represent warnings if extensions misbehave. +class ExtensionWarning { + public: + // Default constructor for storing ExtensionServiceWarning in STL containers + // do not use. + ExtensionWarning(); + + // Constructs a warning of type |type| for extension |extension_id|. This + // could be for example the fact that an extension conflicted with others. + ExtensionWarning(ExtensionWarningSet::WarningType type, + const std::string& extension_id); + + ~ExtensionWarning(); + + // Returns the specific warning type. + ExtensionWarningSet::WarningType warning_type() const { return type_; } + + // Returns the id of the extension for which this warning is valid. + const std::string& extension_id() const { return extension_id_; } + + private: + ExtensionWarningSet::WarningType type_; + std::string extension_id_; + + // Allow implicit copy and assign operator. +}; + +ExtensionWarning::ExtensionWarning() : type_(ExtensionWarningSet::kInvalid) { +} + +ExtensionWarning::ExtensionWarning( + ExtensionWarningSet::WarningType type, + const std::string& extension_id) + : type_(type), extension_id_(extension_id) { +} + +ExtensionWarning::~ExtensionWarning() { +} + +bool operator<(const ExtensionWarning& a, const ExtensionWarning& b) { + if (a.warning_type() == b.warning_type()) + return a.extension_id() < b.extension_id(); + return a.warning_type() < b.warning_type(); +} + +// Static +string16 ExtensionWarningSet::GetLocalizedWarning( + ExtensionWarningSet::WarningType warning_type) { + switch (warning_type) { + case ExtensionWarningSet::kInvalid: + case ExtensionWarningSet::kMaxWarningType: + NOTREACHED(); + return string16(); + case ExtensionWarningSet::kNetworkDelay: + return l10n_util::GetStringFUTF16( + IDS_EXTENSION_WARNINGS_NETWORK_DELAY, + l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)); + } + NOTREACHED(); // Switch statement has no default branch. + return string16(); +} + +ExtensionWarningSet::ExtensionWarningSet(Profile* profile) + : extension_global_error_badge_(NULL), profile_(profile) { +} + +ExtensionWarningSet::~ExtensionWarningSet() { +} + +void ExtensionWarningSet::SetWarning(ExtensionWarningSet::WarningType type, + const std::string& extension_id) { + ExtensionWarning warning(type, extension_id); + bool inserted = warnings_.insert(warning).second; + if (inserted) { + NotifyWarningsChanged(); + UpdateWarningBadge(); + } +} + +void ExtensionWarningSet::ClearWarnings( + const std::set<ExtensionWarningSet::WarningType>& types) { + bool deleted_anything = false; + for (const_iterator i = warnings_.begin(); i != warnings_.end();) { + if (types.find(i->warning_type()) != types.end()) { + deleted_anything = true; + warnings_.erase(i++); + } else { + ++i; + } + } + + if (deleted_anything) { + NotifyWarningsChanged(); + UpdateWarningBadge(); + } +} + +void ExtensionWarningSet::GetWarningsAffectingExtension( + const std::string& extension_id, + std::set<ExtensionWarningSet::WarningType>* result) const { + result->clear(); + for (const_iterator i = warnings_.begin(); i != warnings_.end(); ++i) { + if (i->extension_id() == extension_id) + result->insert(i->warning_type()); + } +} + +void ExtensionWarningSet::SuppressBadgeForCurrentWarnings() { + badge_suppressions_.insert(warnings_.begin(), warnings_.end()); + UpdateWarningBadge(); +} + +void ExtensionWarningSet::NotifyWarningsChanged() { + NotificationService::current()->Notify( + chrome::NOTIFICATION_EXTENSION_WARNING_CHANGED, + Source<Profile>(profile_), + NotificationService::NoDetails()); +} + +void ExtensionWarningSet::ActivateBadge() { + DCHECK(!extension_global_error_badge_); + DCHECK(profile_); + extension_global_error_badge_ = new ExtensionGlobalErrorBadge; + GlobalErrorServiceFactory::GetForProfile(profile_)->AddGlobalError( + extension_global_error_badge_); +} + +void ExtensionWarningSet::DeactivateBadge() { + DCHECK(extension_global_error_badge_); + DCHECK(profile_); + GlobalErrorServiceFactory::GetForProfile(profile_)->RemoveGlobalError( + extension_global_error_badge_); + delete extension_global_error_badge_; + extension_global_error_badge_ = NULL; +} + +void ExtensionWarningSet::UpdateWarningBadge() { + // We need a badge if a warning exists that has not been suppressed. + bool need_warning_badge = false; + for (const_iterator i = warnings_.begin(); i != warnings_.end(); ++i) { + if (badge_suppressions_.find(*i) == badge_suppressions_.end()) { + need_warning_badge = true; + break; + } + } + + // Activate or hide the warning badge in case the current state is incorrect. + if (extension_global_error_badge_ && !need_warning_badge) + DeactivateBadge(); + else if (!extension_global_error_badge_ && need_warning_badge) + ActivateBadge(); +} diff --git a/chrome/browser/extensions/extension_warning_set.h b/chrome/browser/extensions/extension_warning_set.h new file mode 100644 index 0000000..7a44517 --- /dev/null +++ b/chrome/browser/extensions/extension_warning_set.h @@ -0,0 +1,90 @@ +// Copyright (c) 2011 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 CHROME_BROWSER_EXTENSIONS_EXTENSION_WARNING_SET_H_ +#define CHROME_BROWSER_EXTENSIONS_EXTENSION_WARNING_SET_H_ +#pragma once + +#include <set> +#include <string> + +#include "base/string16.h" + +class ExtensionWarning; +class ExtensionGlobalErrorBadge; +class Profile; + +// A set of warnings caused by extensions. These warnings (e.g. conflicting +// modifications of network requests by extensions, slow extensions, etc.) +// trigger a warning badge in the UI and and provide means to resolve them. +class ExtensionWarningSet { + public: + enum WarningType { + // Don't use this, it is only intended for the default constructor and + // does not have localized warning messages for the UI. + kInvalid = 0, + // An extension caused excessive network delays. + kNetworkDelay, + kMaxWarningType + }; + + // Returns a localized string describing |warning_type|. + static string16 GetLocalizedWarning(WarningType warning_type); + + // |profile| may be NULL for testing. In this case, be sure to not insert + // any warnings. + explicit ExtensionWarningSet(Profile* profile); + virtual ~ExtensionWarningSet(); + + // Adds a warning and triggers a chrome::NOTIFICATION_EXTENSION_WARNING + // message if this warning is is new. If the warning is new and has not + // been suppressed, this may activate a badge on the wrench menu. + void SetWarning(ExtensionWarningSet::WarningType type, + const std::string& extension_id); + + // Clears all warnings of types contained in |types| and triggers a + // chrome::NOTIFICATION_EXTENSION_WARNING message if such warnings existed. + // If no warning remains that is not suppressed, this may deactivate a + // warning badge on the wrench mennu. + void ClearWarnings(const std::set<WarningType>& types); + + // Suppresses showing a badge for all currently existing warnings in the + // future. + void SuppressBadgeForCurrentWarnings(); + + // Stores all warnings for extension |extension_id| in |result|. The previous + // content of |result| is erased. + void GetWarningsAffectingExtension( + const std::string& extension_id, + std::set<WarningType>* result) const; + + protected: + // Virtual for testing. + virtual void NotifyWarningsChanged(); + virtual void ActivateBadge(); + virtual void DeactivateBadge(); + + // Tracks the currently existing ExtensionGlobalErrorBadge that indicates in + // the UI that there are |extension_warnings_|. Weak pointer as the object is + // owned by the GlobalErrorService. NULL if there is no warning to be + // displayed on the wrench menu currently. + ExtensionGlobalErrorBadge* extension_global_error_badge_; + + private: + typedef std::set<ExtensionWarning>::const_iterator const_iterator; + + // Shows or hides the warning badge on the wrench menu depending on whether + // any non-suppressed warnings exist. + void UpdateWarningBadge(); + + // Currently existing warnings. + std::set<ExtensionWarning> warnings_; + + // Warnings that do not trigger a badge on the wrench menu. + std::set<ExtensionWarning> badge_suppressions_; + + Profile* profile_; +}; + +#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_WARNING_SET_H_ diff --git a/chrome/browser/extensions/extension_warning_set_unittest.cc b/chrome/browser/extensions/extension_warning_set_unittest.cc new file mode 100644 index 0000000..0831e5f --- /dev/null +++ b/chrome/browser/extensions/extension_warning_set_unittest.cc @@ -0,0 +1,142 @@ +// Copyright (c) 2011 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 "chrome/browser/extensions/extension_warning_set.h" + +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +class ExtensionglobalError; + +namespace { + +class MockExtensionWarningSet : public ExtensionWarningSet { + public: + MockExtensionWarningSet() : ExtensionWarningSet(NULL) { + ON_CALL(*this, ActivateBadge()).WillByDefault( + testing::Invoke(this, &MockExtensionWarningSet::ActivateBadgeImpl)); + ON_CALL(*this, DeactivateBadge()).WillByDefault( + testing::Invoke(this, &MockExtensionWarningSet::DeactivateBadgeImpl)); + } + virtual ~MockExtensionWarningSet() {} + + MOCK_METHOD0(NotifyWarningsChanged, void()); + MOCK_METHOD0(ActivateBadge, void()); + MOCK_METHOD0(DeactivateBadge, void()); + + void ActivateBadgeImpl() { + // Just fill the value so that we see that something would be there + // in a non-mocked execution. + extension_global_error_badge_ = + reinterpret_cast<ExtensionGlobalErrorBadge*>(1); + } + void DeactivateBadgeImpl() { + extension_global_error_badge_ = NULL; + } +}; + +const char* ext1_id = "extension1"; +const char* ext2_id = "extension2"; + +} // namespace + +// Check that inserting a warning triggers notifications, whereas inserting +// the same warning again is silent. +TEST(ExtensionWarningSet, SetWarning) { + MockExtensionWarningSet warnings; + + // Insert warning for the first time. + EXPECT_CALL(warnings, NotifyWarningsChanged()); + EXPECT_CALL(warnings, ActivateBadge()); + warnings.SetWarning(ExtensionWarningSet::kNetworkDelay, ext1_id); + testing::Mock::VerifyAndClearExpectations(&warnings); + + // Second insertion of same warning does not trigger anything. + warnings.SetWarning(ExtensionWarningSet::kNetworkDelay, ext1_id); + testing::Mock::VerifyAndClearExpectations(&warnings); +} + +// Check that ClearWarnings deletes exactly the specified warnings and +// triggers notifications where appropriate. +TEST(ExtensionWarningSet, ClearWarnings) { + MockExtensionWarningSet warnings; + + // TODO(battre): Replace kInvalid with something else once we have additional + // warning types. + ExtensionWarningSet::WarningType type1 = ExtensionWarningSet::kNetworkDelay; + ExtensionWarningSet::WarningType type2 = ExtensionWarningSet::kInvalid; + + // Insert two unique warnings. + EXPECT_CALL(warnings, NotifyWarningsChanged()).Times(2); + EXPECT_CALL(warnings, ActivateBadge()).Times(1); + warnings.SetWarning(type1, ext1_id); + warnings.SetWarning(type2, ext2_id); + testing::Mock::VerifyAndClearExpectations(&warnings); + + // Remove the one warning and check that the badge remains. + EXPECT_CALL(warnings, NotifyWarningsChanged()); + std::set<ExtensionWarningSet::WarningType> to_clear; + to_clear.insert(ExtensionWarningSet::kInvalid); + warnings.ClearWarnings(to_clear); + testing::Mock::VerifyAndClearExpectations(&warnings); + + // Check that the correct warnings appear in |warnings|. + std::set<ExtensionWarningSet::WarningType> existing_warnings; + warnings.GetWarningsAffectingExtension(ext1_id, &existing_warnings); + EXPECT_EQ(1u, existing_warnings.size()); + warnings.GetWarningsAffectingExtension(ext2_id, &existing_warnings); + EXPECT_EQ(0u, existing_warnings.size()); + + // Remove the other one warning and check that badge disappears. + EXPECT_CALL(warnings, NotifyWarningsChanged()); + EXPECT_CALL(warnings, DeactivateBadge()); + to_clear.insert(ExtensionWarningSet::kNetworkDelay); + warnings.ClearWarnings(to_clear); + testing::Mock::VerifyAndClearExpectations(&warnings); + + // Check that not warnings remain. + warnings.GetWarningsAffectingExtension(ext1_id, &existing_warnings); + EXPECT_EQ(0u, existing_warnings.size()); + warnings.GetWarningsAffectingExtension(ext2_id, &existing_warnings); + EXPECT_EQ(0u, existing_warnings.size()); +} + +// Check that no badge appears if it has been suppressed for a specific +// warning. +TEST(ExtensionWarningSet, SuppressBadgeForCurrentWarnings) { + MockExtensionWarningSet warnings; + // TODO(battre): Replace kInvalid with something else once we have additional + // warning types. + ExtensionWarningSet::WarningType type1 = ExtensionWarningSet::kNetworkDelay; + ExtensionWarningSet::WarningType type2 = ExtensionWarningSet::kInvalid; + + // Insert first warning. + EXPECT_CALL(warnings, NotifyWarningsChanged()); + EXPECT_CALL(warnings, ActivateBadge()); + warnings.SetWarning(type1, ext1_id); + testing::Mock::VerifyAndClearExpectations(&warnings); + + // Suppress first warning. + EXPECT_CALL(warnings, DeactivateBadge()); + warnings.SuppressBadgeForCurrentWarnings(); + testing::Mock::VerifyAndClearExpectations(&warnings); + + // Simulate deinstallation of extension. + std::set<ExtensionWarningSet::WarningType> to_clear; + warnings.GetWarningsAffectingExtension(ext1_id, &to_clear); + EXPECT_CALL(warnings, NotifyWarningsChanged()); + warnings.ClearWarnings(to_clear); + testing::Mock::VerifyAndClearExpectations(&warnings); + + // Set first warning again and verify that not badge is shown this time. + EXPECT_CALL(warnings, NotifyWarningsChanged()); + warnings.SetWarning(type1, ext1_id); + testing::Mock::VerifyAndClearExpectations(&warnings); + + // Set second warning and verify that it shows a badge. + EXPECT_CALL(warnings, NotifyWarningsChanged()); + EXPECT_CALL(warnings, ActivateBadge()); + warnings.SetWarning(type2, ext2_id); + testing::Mock::VerifyAndClearExpectations(&warnings); +} diff --git a/chrome/browser/extensions/extension_webrequest_api.cc b/chrome/browser/extensions/extension_webrequest_api.cc index a225112..0874731 100644 --- a/chrome/browser/extensions/extension_webrequest_api.cc +++ b/chrome/browser/extensions/extension_webrequest_api.cc @@ -496,7 +496,8 @@ int ExtensionWebRequestEventRouter::OnBeforeRequest( request_time_tracker_->LogRequestStartTime(request->identifier(), base::Time::Now(), - request->url()); + request->url(), + profile); int extra_info_spec = 0; std::vector<const EventListener*> listeners = diff --git a/chrome/browser/extensions/extension_webrequest_time_tracker.cc b/chrome/browser/extensions/extension_webrequest_time_tracker.cc index 9ccdef7..abae501 100644 --- a/chrome/browser/extensions/extension_webrequest_time_tracker.cc +++ b/chrome/browser/extensions/extension_webrequest_time_tracker.cc @@ -4,7 +4,13 @@ #include "chrome/browser/extensions/extension_webrequest_time_tracker.h" +#include "base/bind.h" +#include "base/compiler_specific.h" #include "base/metrics/histogram.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_warning_set.h" +#include "chrome/browser/profiles/profile_manager.h" // TODO(mpcomplete): tweak all these constants. namespace { @@ -25,23 +31,86 @@ const double kThresholdExcessiveDelay = 0.50; // delay, then we will warn the user. const size_t kNumModerateDelaysBeforeWarning = 50u; const size_t kNumExcessiveDelaysBeforeWarning = 10u; + +// Handles ExtensionWebRequestTimeTrackerDelegate calls on UI thread. +void NotifyNetworkDelaysOnUI(void* profile, + std::set<std::string> extension_ids) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + Profile* p = reinterpret_cast<Profile*>(profile); + if (!p || !g_browser_process->profile_manager()->IsValidProfile(p)) + return; + + ExtensionWarningSet* warnings = + p->GetExtensionService()->extension_warnings(); + + for (std::set<std::string>::const_iterator i = extension_ids.begin(); + i != extension_ids.end(); ++i) { + warnings->SetWarning(ExtensionWarningSet::kNetworkDelay, *i); + } +} + +// Default implementation for ExtensionWebRequestTimeTrackerDelegate +// that sets a warning in the extension service of |profile|. +class DefaultDelegate : public ExtensionWebRequestTimeTrackerDelegate { + public: + virtual ~DefaultDelegate() {} + + // Implementation of ExtensionWebRequestTimeTrackerDelegate. + virtual void NotifyExcessiveDelays( + void* profile, + size_t num_delayed_messages, + size_t total_num_messages, + const std::set<std::string>& extension_ids) OVERRIDE; + virtual void NotifyModerateDelays( + void* profile, + size_t num_delayed_messages, + size_t total_num_messages, + const std::set<std::string>& extension_ids) OVERRIDE; +}; + +void DefaultDelegate::NotifyExcessiveDelays( + void* profile, + size_t num_delayed_messages, + size_t total_num_messages, + const std::set<std::string>& extension_ids) { + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(&NotifyNetworkDelaysOnUI, profile, extension_ids)); +} + +void DefaultDelegate::NotifyModerateDelays( + void* profile, + size_t num_delayed_messages, + size_t total_num_messages, + const std::set<std::string>& extension_ids) { + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind(&NotifyNetworkDelaysOnUI, profile, extension_ids)); +} + } // namespace ExtensionWebRequestTimeTracker::RequestTimeLog::RequestTimeLog() - : completed(false) { + : profile(NULL), completed(false) { } ExtensionWebRequestTimeTracker::RequestTimeLog::~RequestTimeLog() { } -ExtensionWebRequestTimeTracker::ExtensionWebRequestTimeTracker() { +ExtensionWebRequestTimeTracker::ExtensionWebRequestTimeTracker() + : delegate_(new DefaultDelegate) { } ExtensionWebRequestTimeTracker::~ExtensionWebRequestTimeTracker() { } void ExtensionWebRequestTimeTracker::LogRequestStartTime( - int64 request_id, const base::Time& start_time, const GURL& url) { + int64 request_id, + const base::Time& start_time, + const GURL& url, + void* profile) { // Trim old completed request logs. while (request_ids_.size() > kMaxRequestsLogged) { int64 to_remove = request_ids_.front(); @@ -64,6 +133,7 @@ void ExtensionWebRequestTimeTracker::LogRequestStartTime( RequestTimeLog& log = request_time_logs_[request_id]; log.request_start_time = start_time; log.url = url; + log.profile = profile; } void ExtensionWebRequestTimeTracker::LogRequestEndTime( @@ -86,6 +156,18 @@ void ExtensionWebRequestTimeTracker::LogRequestEndTime( Analyze(request_id); } +std::set<std::string> ExtensionWebRequestTimeTracker::GetExtensionIds( + const RequestTimeLog& log) const { + std::set<std::string> result; + for (std::map<std::string, base::TimeDelta>::const_iterator i = + log.extension_block_durations.begin(); + i != log.extension_block_durations.end(); + ++i) { + result.insert(i->first); + } + return result; +} + void ExtensionWebRequestTimeTracker::Analyze(int64 request_id) { RequestTimeLog& log = request_time_logs_[request_id]; @@ -101,18 +183,31 @@ void ExtensionWebRequestTimeTracker::Analyze(int64 request_id) { log.block_duration.InMilliseconds() << "/" << log.request_duration.InMilliseconds() << " = " << percentage; - // TODO(mpcomplete): need actual UI for the warning. // TODO(mpcomplete): blame a specific extension. Maybe go through the list // of recent requests and find the extension that has caused the most delays. if (percentage > kThresholdExcessiveDelay) { excessive_delays_.insert(request_id); if (excessive_delays_.size() > kNumExcessiveDelaysBeforeWarning) { LOG(ERROR) << "WR excessive delays:" << excessive_delays_.size(); + if (delegate_.get()) { + delegate_->NotifyExcessiveDelays(log.profile, + excessive_delays_.size(), + request_ids_.size(), + GetExtensionIds(log)); + } } } else if (percentage > kThresholdModerateDelay) { moderate_delays_.insert(request_id); - if (moderate_delays_.size() > kNumModerateDelaysBeforeWarning) { + if (moderate_delays_.size() + excessive_delays_.size() > + kNumModerateDelaysBeforeWarning) { LOG(ERROR) << "WR moderate delays:" << moderate_delays_.size(); + if (delegate_.get()) { + delegate_->NotifyModerateDelays( + log.profile, + moderate_delays_.size() + excessive_delays_.size(), + request_ids_.size(), + GetExtensionIds(log)); + } } } } @@ -152,3 +247,8 @@ void ExtensionWebRequestTimeTracker::SetRequestRedirected(int64 request_id) { // down this request. Just ignore it. request_time_logs_.erase(request_id); } + +void ExtensionWebRequestTimeTracker::SetDelegate( + ExtensionWebRequestTimeTrackerDelegate* delegate) { + delegate_.reset(delegate); +} diff --git a/chrome/browser/extensions/extension_webrequest_time_tracker.h b/chrome/browser/extensions/extension_webrequest_time_tracker.h index b9911b1..b114141 100644 --- a/chrome/browser/extensions/extension_webrequest_time_tracker.h +++ b/chrome/browser/extensions/extension_webrequest_time_tracker.h @@ -11,14 +11,35 @@ #include <set> #include <string> -#include "base/time.h" #include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/time.h" #include "googleurl/src/gurl.h" namespace base { class Time; } +class ExtensionWebRequestTimeTrackerDelegate { + public: + virtual ~ExtensionWebRequestTimeTrackerDelegate() {} + + // Notifies the delegate that |num_delayed_messages| of the last + // |total_num_messages| inspected messages were excessively/moderately + // delayed. Every excessively delayed message is also counted as a moderately + // delayed message. + virtual void NotifyExcessiveDelays( + void* profile, + size_t num_delayed_messages, + size_t total_num_messages, + const std::set<std::string>& extension_ids) = 0; + virtual void NotifyModerateDelays( + void* profile, + size_t num_delayed_messages, + size_t total_num_messages, + const std::set<std::string>& extension_ids) = 0; +}; + // This class keeps monitors how much delay extensions add to network requests // by using the webRequest API. If the delay is sufficient, we will warn the // user that extensions are slowing down the browser. @@ -29,7 +50,7 @@ class ExtensionWebRequestTimeTracker { // Records the time that a request was created. void LogRequestStartTime(int64 request_id, const base::Time& start_time, - const GURL& url); + const GURL& url, void* profile); // Records the time that a request either completed or encountered an error. void LogRequestEndTime(int64 request_id, const base::Time& end_time); @@ -53,10 +74,14 @@ class ExtensionWebRequestTimeTracker { // Called when an extension has redirected the given request to another URL. void SetRequestRedirected(int64 request_id); + // Takes ownership of |delegate|. + void SetDelegate(ExtensionWebRequestTimeTrackerDelegate* delegate); + private: // Timing information for a single request. struct RequestTimeLog { GURL url; // used for debug purposes only + void* profile; // profile that created the request bool completed; base::Time request_start_time; base::TimeDelta request_duration; @@ -70,6 +95,9 @@ class ExtensionWebRequestTimeTracker { // if necessary. void Analyze(int64 request_id); + // Returns a list of all extension IDs that contributed to delay for |log|. + std::set<std::string> GetExtensionIds(const RequestTimeLog& log) const; + // A map of recent request IDs to timing info for each request. std::map<int64, RequestTimeLog> request_time_logs_; @@ -82,6 +110,9 @@ class ExtensionWebRequestTimeTracker { std::set<int64> excessive_delays_; std::set<int64> moderate_delays_; + // Defaults to a delegate that sets warnings in the extension service. + scoped_ptr<ExtensionWebRequestTimeTrackerDelegate> delegate_; + FRIEND_TEST_ALL_PREFIXES(ExtensionWebRequestTimeTrackerTest, Basic); FRIEND_TEST_ALL_PREFIXES(ExtensionWebRequestTimeTrackerTest, IgnoreFastRequests); diff --git a/chrome/browser/extensions/extension_webrequest_time_tracker_unittest.cc b/chrome/browser/extensions/extension_webrequest_time_tracker_unittest.cc index 9439354..522572e 100644 --- a/chrome/browser/extensions/extension_webrequest_time_tracker_unittest.cc +++ b/chrome/browser/extensions/extension_webrequest_time_tracker_unittest.cc @@ -4,6 +4,7 @@ #include "chrome/browser/extensions/extension_webrequest_time_tracker.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace { @@ -11,6 +12,16 @@ const base::TimeDelta kRequestDelta = base::TimeDelta::FromMilliseconds(100); const base::TimeDelta kTinyDelay = base::TimeDelta::FromMilliseconds(1); const base::TimeDelta kModerateDelay = base::TimeDelta::FromMilliseconds(25); const base::TimeDelta kExcessiveDelay = base::TimeDelta::FromMilliseconds(75); + +class ExtensionWebRequestTimeTrackerDelegateMock + : public ExtensionWebRequestTimeTrackerDelegate { + public: + MOCK_METHOD4(NotifyExcessiveDelays, + void (void*, size_t, size_t, const std::set<std::string>&)); + MOCK_METHOD4(NotifyModerateDelays, + void (void*, size_t, size_t, const std::set<std::string>&)); +}; + } // namespace //class ExtensionWebRequestTimeTrackerTest : public testing::Test {}; @@ -18,8 +29,9 @@ const base::TimeDelta kExcessiveDelay = base::TimeDelta::FromMilliseconds(75); TEST(ExtensionWebRequestTimeTrackerTest, Basic) { ExtensionWebRequestTimeTracker tracker; base::Time start; + void* profile = NULL; - tracker.LogRequestStartTime(42, start, GURL()); + tracker.LogRequestStartTime(42, start, GURL(), profile); EXPECT_EQ(1u, tracker.request_time_logs_.size()); ASSERT_EQ(1u, tracker.request_ids_.size()); EXPECT_EQ(42, tracker.request_ids_.front()); @@ -32,14 +44,15 @@ TEST(ExtensionWebRequestTimeTrackerTest, Basic) { TEST(ExtensionWebRequestTimeTrackerTest, CancelOrRedirect) { ExtensionWebRequestTimeTracker tracker; base::Time start; + void* profile = NULL; - tracker.LogRequestStartTime(1, start, GURL()); + tracker.LogRequestStartTime(1, start, GURL(), profile); EXPECT_EQ(1u, tracker.request_time_logs_.size()); tracker.SetRequestCanceled(1); tracker.LogRequestEndTime(1, start + kRequestDelta); EXPECT_EQ(0u, tracker.request_time_logs_.size()); - tracker.LogRequestStartTime(2, start, GURL()); + tracker.LogRequestStartTime(2, start, GURL(), profile); EXPECT_EQ(1u, tracker.request_time_logs_.size()); tracker.SetRequestRedirected(2); tracker.LogRequestEndTime(2, start + kRequestDelta); @@ -51,11 +64,12 @@ TEST(ExtensionWebRequestTimeTrackerTest, Delays) { base::Time start; std::string extension1_id("1"); std::string extension2_id("2"); + void* profile = NULL; // Start 3 requests with different amounts of delay from 2 extensions. - tracker.LogRequestStartTime(1, start, GURL()); - tracker.LogRequestStartTime(2, start, GURL()); - tracker.LogRequestStartTime(3, start, GURL()); + tracker.LogRequestStartTime(1, start, GURL(), profile); + tracker.LogRequestStartTime(2, start, GURL(), profile); + tracker.LogRequestStartTime(3, start, GURL(), profile); tracker.IncrementExtensionBlockTime(extension1_id, 1, kTinyDelay); tracker.IncrementExtensionBlockTime(extension1_id, 2, kTinyDelay); tracker.IncrementExtensionBlockTime(extension1_id, 3, kTinyDelay); @@ -76,9 +90,59 @@ TEST(ExtensionWebRequestTimeTrackerTest, Delays) { // Now issue a bunch more requests and ensure that the old delays are // forgotten. for (int64 i = 4; i < 500; ++i) { - tracker.LogRequestStartTime(i, start, GURL()); + tracker.LogRequestStartTime(i, start, GURL(), profile); tracker.LogRequestEndTime(i, start + kRequestDelta); } EXPECT_EQ(0u, tracker.moderate_delays_.size()); EXPECT_EQ(0u, tracker.excessive_delays_.size()); } + +TEST(ExtensionWebRequestTimeTrackerTest, Delegate) { + using testing::_; + using testing::Mock; + + ExtensionWebRequestTimeTrackerDelegateMock* delegate( + new ExtensionWebRequestTimeTrackerDelegateMock); + ExtensionWebRequestTimeTracker tracker; + tracker.SetDelegate(delegate); + base::Time start; + std::string extension1_id("1"); + void* profile = NULL; + // Set of all extensions that blocked network requests. + std::set<std::string> extensions; + extensions.insert(extension1_id); + + const int num_moderate_delays = 51; + const int num_excessive_delays = 11; + int request_nr = 0; + + // Check that (only) the last moderate delay triggers the delegate callback. + for (int64 i = 0; i < num_moderate_delays; ++i) { + request_nr++; + if (i == num_moderate_delays-1) { + EXPECT_CALL(*delegate, + NotifyModerateDelays(profile , i+1, request_nr, extensions)); + } + tracker.LogRequestStartTime(request_nr, start, GURL(), profile); + tracker.IncrementExtensionBlockTime(extension1_id, request_nr, + kModerateDelay); + tracker.IncrementTotalBlockTime(request_nr, kModerateDelay); + tracker.LogRequestEndTime(request_nr, start + kRequestDelta); + Mock::VerifyAndClearExpectations(delegate); + } + + // Check that (only) the last excessive delay triggers the delegate callback. + for (int64 i = 0; i < num_excessive_delays; ++i) { + request_nr++; + if (i == num_excessive_delays-1) { + EXPECT_CALL(*delegate, + NotifyExcessiveDelays(profile, i+1, request_nr, extensions)); + } + tracker.LogRequestStartTime(request_nr, start, GURL(), profile); + tracker.IncrementExtensionBlockTime(extension1_id, request_nr, + kExcessiveDelay); + tracker.IncrementTotalBlockTime(request_nr, kExcessiveDelay); + tracker.LogRequestEndTime(request_nr, start + kRequestDelta); + Mock::VerifyAndClearExpectations(delegate); + } +} diff --git a/chrome/browser/resources/options/extension_list.js b/chrome/browser/resources/options/extension_list.js index 0e852eb..d4c7121 100644 --- a/chrome/browser/resources/options/extension_list.js +++ b/chrome/browser/resources/options/extension_list.js @@ -239,6 +239,27 @@ cr.define('options', function() { vbox.appendChild(link); } + if (extension.warnings.length > 0) { + var warningsDiv = this.ownerDocument.createElement('div'); + warningsDiv.classList.add('extension-warnings'); + + var warningsHeader = this.ownerDocument.createElement('span'); + warningsHeader.classList.add('extension-warnings-title'); + warningsHeader.textContent = + localStrings.getString('extensionSettingsWarningsTitle'); + warningsDiv.appendChild(warningsHeader); + + var warningList = this.ownerDocument.createElement('ul'); + for (var j = 0; j < extension.warnings.length; ++j) { + var warningEntry = this.ownerDocument.createElement('li'); + warningEntry.textContent = extension.warnings[j]; + warningList.appendChild(warningEntry); + } + warningsDiv.appendChild(warningList); + + vbox.appendChild(warningsDiv); + } + // And now the details section that is normally hidden. var details = this.ownerDocument.createElement('div'); details.classList.add('vbox'); @@ -711,4 +732,4 @@ cr.define('options', function() { return { ExtensionsList: ExtensionsList }; -});
\ No newline at end of file +}); diff --git a/chrome/browser/resources/options/extension_settings.css b/chrome/browser/resources/options/extension_settings.css index 471d4814..f24a005 100644 --- a/chrome/browser/resources/options/extension_settings.css +++ b/chrome/browser/resources/options/extension_settings.css @@ -138,6 +138,22 @@ found in the LICENSE file. -webkit-user-select: none; } +.extension-warnings-title { + color: red; +} + +.extension-warnings { + margin-top: 6px; +} + +.extension-warnings ul { + margin: 0; +} + +.extension-warnings > * { + white-space: normal; +} + .informative-text { color: gray; } diff --git a/chrome/browser/ui/webui/options/extension_settings_handler.cc b/chrome/browser/ui/webui/options/extension_settings_handler.cc index 82234af..ccc5283 100644 --- a/chrome/browser/ui/webui/options/extension_settings_handler.cc +++ b/chrome/browser/ui/webui/options/extension_settings_handler.cc @@ -18,6 +18,7 @@ #include "chrome/browser/extensions/extension_disabled_infobar_delegate.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_updater.h" +#include "chrome/browser/extensions/extension_warning_set.h" #include "chrome/browser/google/google_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/tab_contents/background_contents.h" @@ -143,6 +144,8 @@ void ExtensionSettingsHandler::HandleRequestExtensionsData( // Add the extensions to the results structure. ListValue *extensions_list = new ListValue(); + ExtensionWarningSet* warnings = extension_service_->extension_warnings(); + const ExtensionList* extensions = extension_service_->extensions(); for (ExtensionList::const_iterator extension = extensions->begin(); extension != extensions->end(); ++extension) { @@ -151,6 +154,7 @@ void ExtensionSettingsHandler::HandleRequestExtensionsData( extension_service_, *extension, GetActivePagesForExtension(*extension), + warnings, true, false)); // enabled, terminated } } @@ -162,6 +166,7 @@ void ExtensionSettingsHandler::HandleRequestExtensionsData( extension_service_, *extension, GetActivePagesForExtension(*extension), + warnings, false, false)); // enabled, terminated } } @@ -174,6 +179,7 @@ void ExtensionSettingsHandler::HandleRequestExtensionsData( extension_service_, *extension, empty_pages, // Terminated process has no active pages. + warnings, false, true)); // enabled, terminated } } @@ -206,6 +212,8 @@ void ExtensionSettingsHandler::MaybeRegisterForNotifications() { Source<Profile>(profile)); registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UPDATE_DISABLED, Source<Profile>(profile)); + registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_WARNING_CHANGED, + Source<Profile>(profile)); registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED, NotificationService::AllBrowserContextsAndSources()); @@ -558,6 +566,8 @@ void ExtensionSettingsHandler::GetLocalizedValues( l10n_util::GetStringUTF16(IDS_EXTENSIONS_CRASHED_EXTENSION)); localized_strings->SetString("extensionSettingsInDevelopment", l10n_util::GetStringUTF16(IDS_EXTENSIONS_IN_DEVELOPMENT)); + localized_strings->SetString("extensionSettingsWarningsTitle", + l10n_util::GetStringUTF16(IDS_EXTENSION_WARNINGS_TITLE)); } void ExtensionSettingsHandler::Initialize() { @@ -625,6 +635,7 @@ void ExtensionSettingsHandler::Observe(int type, case chrome::NOTIFICATION_EXTENSION_PROCESS_CREATED: case chrome::NOTIFICATION_EXTENSION_UNLOADED: case chrome::NOTIFICATION_EXTENSION_UPDATE_DISABLED: + case chrome::NOTIFICATION_EXTENSION_WARNING_CHANGED: case chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_VISIBILITY_CHANGED: MaybeUpdateAfterNotification(); break; @@ -649,7 +660,9 @@ void ExtensionSettingsHandler::MaybeUpdateAfterNotification() { // Static DictionaryValue* ExtensionSettingsHandler::CreateExtensionDetailValue( ExtensionService* service, const Extension* extension, - const std::vector<ExtensionPage>& pages, bool enabled, bool terminated) { + const std::vector<ExtensionPage>& pages, + const ExtensionWarningSet* warnings_set, + bool enabled, bool terminated) { DictionaryValue* extension_data = new DictionaryValue(); GURL icon = ExtensionIconSource::GetIconURL(extension, @@ -713,6 +726,22 @@ DictionaryValue* ExtensionSettingsHandler::CreateExtensionDetailValue( extension->browser_action() || extension->page_action()); extension_data->SetString("homepageUrl", extension->GetHomepageURL().spec()); + // Add warnings. + ListValue* warnings_list = new ListValue; + if (warnings_set) { + std::set<ExtensionWarningSet::WarningType> warnings; + warnings_set->GetWarningsAffectingExtension(extension->id(), &warnings); + + for (std::set<ExtensionWarningSet::WarningType>::const_iterator iter = + warnings.begin(); + iter != warnings.end(); + ++iter) { + string16 warning_string(ExtensionWarningSet::GetLocalizedWarning(*iter)); + warnings_list->Append(Value::CreateStringValue(warning_string)); + } + } + extension_data->Set("warnings", warnings_list); + return extension_data; } diff --git a/chrome/browser/ui/webui/options/extension_settings_handler.h b/chrome/browser/ui/webui/options/extension_settings_handler.h index d326a1d..f0bfdf0 100644 --- a/chrome/browser/ui/webui/options/extension_settings_handler.h +++ b/chrome/browser/ui/webui/options/extension_settings_handler.h @@ -11,6 +11,7 @@ #include "chrome/browser/extensions/extension_install_ui.h" #include "chrome/browser/extensions/extension_uninstall_dialog.h" +#include "chrome/browser/extensions/extension_warning_set.h" #include "chrome/browser/ui/shell_dialogs.h" #include "chrome/browser/ui/webui/options/options_ui.h" #include "chrome/browser/ui/webui/chrome_web_ui.h" @@ -57,11 +58,12 @@ class ExtensionSettingsHandler : public OptionsPageUIHandler, static void RegisterUserPrefs(PrefService* prefs); // Extension Detail JSON Struct for page. (static for ease of testing). - // Note: service can be NULL in unit tests. + // Note: |service| and |warnings| can be NULL in unit tests. static base::DictionaryValue* CreateExtensionDetailValue( ExtensionService* service, const Extension* extension, const std::vector<ExtensionPage>& pages, + const ExtensionWarningSet* warnings, bool enabled, bool terminated); diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 08a77e9..2d5768b 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1002,6 +1002,8 @@ 'browser/extensions/extension_function_dispatcher.h', 'browser/extensions/extension_global_error.cc', 'browser/extensions/extension_global_error.h', + 'browser/extensions/extension_global_error_badge.cc', + 'browser/extensions/extension_global_error_badge.h', 'browser/extensions/extension_history_api.cc', 'browser/extensions/extension_history_api.h', 'browser/extensions/extension_history_api_constants.cc', @@ -1161,6 +1163,8 @@ 'browser/extensions/extension_uninstall_dialog.h', 'browser/extensions/extension_updater.cc', 'browser/extensions/extension_updater.h', + 'browser/extensions/extension_warning_set.cc', + 'browser/extensions/extension_warning_set.h', 'browser/extensions/extension_web_socket_proxy_private_api.cc', 'browser/extensions/extension_web_socket_proxy_private_api.h', 'browser/extensions/extension_web_ui.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 5de75e1..c35c027 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1299,6 +1299,7 @@ 'browser/extensions/extension_sync_data_unittest.cc', 'browser/extensions/extension_ui_unittest.cc', 'browser/extensions/extension_updater_unittest.cc', + 'browser/extensions/extension_warning_set_unittest.cc', 'browser/extensions/extension_webnavigation_unittest.cc', 'browser/extensions/extension_webrequest_api_unittest.cc', 'browser/extensions/extension_webrequest_time_tracker_unittest.cc', diff --git a/chrome/common/chrome_notification_types.h b/chrome/common/chrome_notification_types.h index 35d3349..0fb69fd 100644 --- a/chrome/common/chrome_notification_types.h +++ b/chrome/common/chrome_notification_types.h @@ -515,6 +515,11 @@ enum NotificationType { // (const std::string). NOTIFICATION_EXTENSION_UPDATE_FOUND, + // Sent when one or more extensions changed their warning status (like + // slowing down Chrome or conflicting with each other). + // The source is a Profile. + NOTIFICATION_EXTENSION_WARNING_CHANGED, + // An installed app changed notification state (added or removed // notifications). The source is a Profile, and the details are a string // with the extension id of the app. |