diff options
11 files changed, 114 insertions, 9 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 0bf1ea4..3ca5235 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -3817,6 +3817,9 @@ each locale. --> <message name="IDS_EXTENSIONS_NONE_INSTALLED_SUGGEST_GALLERY" desc="Text on next line after IDS_EXTENSIONS_NONE_INSTALLED that suggests the user look in the gallery for extensions to install."> Want to <ph name="BEGIN_LINK">$1</ph>browse the gallery<ph name="END_LINK">$2</ph> instead? </message> + <message name="IDS_EXTENSIONS_CRASHED_EXTENSION" desc="Text that signifies that the extension process was terminated, usually because it has crashed."> + (Crashed) + </message> <message name="IDS_EXTENSIONS_DISABLED_EXTENSION" desc="Text that signifies that the extension is currently disabled."> (Disabled) </message> diff --git a/chrome/browser/extensions/extension_crash_recovery_browsertest.cc b/chrome/browser/extensions/extension_crash_recovery_browsertest.cc index cb489a1..cc5e62e 100644 --- a/chrome/browser/extensions/extension_crash_recovery_browsertest.cc +++ b/chrome/browser/extensions/extension_crash_recovery_browsertest.cc @@ -111,25 +111,38 @@ class ExtensionCrashRecoveryTest : public ExtensionBrowserTest { IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest, Basic) { const size_t size_before = GetExtensionService()->extensions()->size(); + const size_t crash_size_before = + GetExtensionService()->terminated_extensions()->size(); LoadTestExtension(); CrashExtension(size_before); ASSERT_EQ(size_before, GetExtensionService()->extensions()->size()); + ASSERT_EQ(crash_size_before + 1, + GetExtensionService()->terminated_extensions()->size()); AcceptCrashedExtensionInfobar(0); SCOPED_TRACE("after clicking the infobar"); CheckExtensionConsistency(size_before); + ASSERT_EQ(crash_size_before, + GetExtensionService()->terminated_extensions()->size()); } IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest, CloseAndReload) { const size_t size_before = GetExtensionService()->extensions()->size(); + const size_t crash_size_before = + GetExtensionService()->terminated_extensions()->size(); LoadTestExtension(); CrashExtension(size_before); ASSERT_EQ(size_before, GetExtensionService()->extensions()->size()); + ASSERT_EQ(crash_size_before + 1, + GetExtensionService()->terminated_extensions()->size()); + CancelCrashedExtensionInfobar(0); ReloadExtension(first_extension_id_); SCOPED_TRACE("after reloading"); CheckExtensionConsistency(size_before); + ASSERT_EQ(crash_size_before, + GetExtensionService()->terminated_extensions()->size()); } IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest, ReloadIndependently) { @@ -316,12 +329,18 @@ IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest, TwoExtensionsCrashSecond) { IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest, TwoExtensionsCrashBothAtOnce) { const size_t size_before = GetExtensionService()->extensions()->size(); + const size_t crash_size_before = + GetExtensionService()->terminated_extensions()->size(); LoadTestExtension(); LoadSecondExtension(); CrashExtension(size_before); ASSERT_EQ(size_before + 1, GetExtensionService()->extensions()->size()); + ASSERT_EQ(crash_size_before + 1, + GetExtensionService()->terminated_extensions()->size()); CrashExtension(size_before); ASSERT_EQ(size_before, GetExtensionService()->extensions()->size()); + ASSERT_EQ(crash_size_before + 2, + GetExtensionService()->terminated_extensions()->size()); { SCOPED_TRACE("first infobar"); @@ -421,3 +440,19 @@ IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest, CheckExtensionConsistency(size_before + 1); } } + +IN_PROC_BROWSER_TEST_F(ExtensionCrashRecoveryTest, CrashAndUnloadAll) { + const size_t size_before = GetExtensionService()->extensions()->size(); + const size_t crash_size_before = + GetExtensionService()->terminated_extensions()->size(); + LoadTestExtension(); + LoadSecondExtension(); + CrashExtension(size_before); + ASSERT_EQ(size_before + 1, GetExtensionService()->extensions()->size()); + ASSERT_EQ(crash_size_before + 1, + GetExtensionService()->terminated_extensions()->size()); + + GetExtensionService()->UnloadAllExtensions(); + ASSERT_EQ(crash_size_before, + GetExtensionService()->terminated_extensions()->size()); +} diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc index 8140d2b..67cb44e 100644 --- a/chrome/browser/extensions/extension_service.cc +++ b/chrome/browser/extensions/extension_service.cc @@ -603,6 +603,10 @@ const ExtensionList* ExtensionService::disabled_extensions() const { return &disabled_extensions_; } +const ExtensionList* ExtensionService::terminated_extensions() const { + return &terminated_extensions_; +} + const PendingExtensionMap& ExtensionService::pending_extensions() const { return pending_extensions_; } @@ -1534,6 +1538,8 @@ void ExtensionService::UnloadExtension( void ExtensionService::UnloadAllExtensions() { extensions_.clear(); disabled_extensions_.clear(); + terminated_extension_ids_.clear(); + terminated_extensions_.clear(); extension_runtime_data_.clear(); // TODO(erikkay) should there be a notification for this? We can't use @@ -1590,6 +1596,9 @@ void ExtensionService::OnExtensionLoaded(const Extension* extension) { // The extension is now loaded, remove its data from unloaded extension map. unloaded_extension_paths_.erase(extension->id()); + // If a terminated extension is loaded, remove it from the terminated list. + UntrackTerminatedExtension(extension->id()); + // If the extension was disabled for a reload, then enable it. if (disabled_extension_paths_.erase(extension->id()) > 0) EnableExtension(extension->id()); @@ -1844,6 +1853,25 @@ const Extension* ExtensionService::GetExtensionByIdInternal( return NULL; } +void ExtensionService::TrackTerminatedExtension(const Extension* extension) { + if (terminated_extension_ids_.insert(extension->id()).second) + terminated_extensions_.push_back(make_scoped_refptr(extension)); +} + +void ExtensionService::UntrackTerminatedExtension(const std::string& id) { + if (terminated_extension_ids_.erase(id) <= 0) + return; + + std::string lowercase_id = StringToLowerASCII(id); + for (ExtensionList::iterator iter = terminated_extensions_.begin(); + iter != terminated_extensions_.end(); ++iter) { + if ((*iter)->id() == lowercase_id) { + terminated_extensions_.erase(iter); + return; + } + } +} + const Extension* ExtensionService::GetWebStoreApp() { return GetExtensionById(extension_misc::kWebStoreAppId, false); } @@ -1997,6 +2025,7 @@ void ExtensionService::Observe(NotificationType type, break; ExtensionHost* host = Details<ExtensionHost>(details).ptr(); + TrackTerminatedExtension(host->extension()); // Unload the entire extension. We want it to be in a consistent state: // either fully working or not loaded at all, but never half-crashed. diff --git a/chrome/browser/extensions/extension_service.h b/chrome/browser/extensions/extension_service.h index 9e4206c..21c4a57 100644 --- a/chrome/browser/extensions/extension_service.h +++ b/chrome/browser/extensions/extension_service.h @@ -156,6 +156,7 @@ class ExtensionService // Gets the list of currently installed extensions. virtual const ExtensionList* extensions() const; virtual const ExtensionList* disabled_extensions() const; + virtual const ExtensionList* terminated_extensions() const; // Gets the set of pending extensions. virtual const PendingExtensionMap& pending_extensions() const; @@ -468,6 +469,11 @@ class ExtensionService bool include_enabled, bool include_disabled); + + // Keep track of terminated extensions. + void TrackTerminatedExtension(const Extension* extension); + void UntrackTerminatedExtension(const std::string& id); + // Like AddPendingExtension*() functions above, but assumes an // extension with the same id is not already installed. void AddPendingExtensionInternal( @@ -508,6 +514,12 @@ class ExtensionService // The list of installed extensions that have been disabled. ExtensionList disabled_extensions_; + // The list of installed extensions that have been terminated. + ExtensionList terminated_extensions_; + + // Used to quickly check if an extension was terminated. + std::set<std::string> terminated_extension_ids_; + // The set of pending extensions. PendingExtensionMap pending_extensions_; diff --git a/chrome/browser/extensions/extension_ui_unittest.cc b/chrome/browser/extensions/extension_ui_unittest.cc index 1bda60a..5980109 100644 --- a/chrome/browser/extensions/extension_ui_unittest.cc +++ b/chrome/browser/extensions/extension_ui_unittest.cc @@ -53,7 +53,7 @@ namespace { // Produce test output. scoped_ptr<DictionaryValue> actual_output_data( ExtensionsDOMHandler::CreateExtensionDetailValue(NULL, extension.get(), - pages, true)); + pages, true, false)); // Compare the outputs. return expected_output_data->Equals(actual_output_data.get()); diff --git a/chrome/browser/extensions/extensions_ui.cc b/chrome/browser/extensions/extensions_ui.cc index e191a89..a653ccc 100644 --- a/chrome/browser/extensions/extensions_ui.cc +++ b/chrome/browser/extensions/extensions_ui.cc @@ -123,6 +123,8 @@ void ExtensionsUIHTMLSource::StartDataRequest(const std::string& path, ASCIIToUTF16("'>") + l10n_util::GetStringUTF16(IDS_GET_MORE_EXTENSIONS) + ASCIIToUTF16("</a>")); + localized_strings.SetString("extensionCrashed", + l10n_util::GetStringUTF16(IDS_EXTENSIONS_CRASHED_EXTENSION)); localized_strings.SetString("extensionDisabled", l10n_util::GetStringUTF16(IDS_EXTENSIONS_DISABLED_EXTENSION)); localized_strings.SetString("inDevelopment", @@ -349,7 +351,7 @@ void ExtensionsDOMHandler::HandleRequestExtensionsData(const ListValue* args) { extensions_service_.get(), *extension, GetActivePagesForExtension(*extension), - true)); // enabled + true, false)); // enabled, terminated extension_icons->push_back(PickExtensionIcon(*extension)); } } @@ -361,7 +363,20 @@ void ExtensionsDOMHandler::HandleRequestExtensionsData(const ListValue* args) { extensions_service_.get(), *extension, GetActivePagesForExtension(*extension), - false)); // enabled + false, false)); // enabled, terminated + extension_icons->push_back(PickExtensionIcon(*extension)); + } + } + extensions = extensions_service_->terminated_extensions(); + std::vector<ExtensionPage> empty_pages; + for (ExtensionList::const_iterator extension = extensions->begin(); + extension != extensions->end(); ++extension) { + if (ShouldShowExtension(*extension)) { + extensions_list->Append(CreateExtensionDetailValue( + extensions_service_.get(), + *extension, + empty_pages, // Terminated process has no active pages. + false, true)); // enabled, terminated extension_icons->push_back(PickExtensionIcon(*extension)); } } @@ -790,7 +805,7 @@ static bool ExtensionWantsFileAccess(const Extension* extension) { // Static DictionaryValue* ExtensionsDOMHandler::CreateExtensionDetailValue( ExtensionService* service, const Extension* extension, - const std::vector<ExtensionPage>& pages, bool enabled) { + const std::vector<ExtensionPage>& pages, bool enabled, bool terminated) { DictionaryValue* extension_data = new DictionaryValue(); extension_data->SetString("id", extension->id()); @@ -798,6 +813,7 @@ DictionaryValue* ExtensionsDOMHandler::CreateExtensionDetailValue( extension_data->SetString("description", extension->description()); extension_data->SetString("version", extension->version()->GetString()); extension_data->SetBoolean("enabled", enabled); + extension_data->SetBoolean("terminated", terminated); extension_data->SetBoolean("enabledIncognito", service ? service->IsIncognitoEnabled(extension) : false); extension_data->SetBoolean("wantsFileAccess", diff --git a/chrome/browser/extensions/extensions_ui.h b/chrome/browser/extensions/extensions_ui.h index b7a9b69..37037ef 100644 --- a/chrome/browser/extensions/extensions_ui.h +++ b/chrome/browser/extensions/extensions_ui.h @@ -114,7 +114,8 @@ class ExtensionsDOMHandler ExtensionService* service, const Extension* extension, const std::vector<ExtensionPage>& pages, - bool enabled); + bool enabled, + bool terminated); // ContentScript JSON Struct for page. (static for ease of testing). static DictionaryValue* CreateContentScriptDetailValue( diff --git a/chrome/browser/resources/extensions_ui.html b/chrome/browser/resources/extensions_ui.html index e5f3976..5e92d43 100644 --- a/chrome/browser/resources/extensions_ui.html +++ b/chrome/browser/resources/extensions_ui.html @@ -332,6 +332,7 @@ var extensionDataFormat = { 'description': 'Extension long format description', 'version': '1.0.231', 'enabled': 'true', + 'terminated': 'false', 'enabledIncognito': 'false', 'wantsFileAccess': 'false', 'allowFileAccess': 'false', @@ -378,6 +379,7 @@ var extensionDataFormat = { 'description': 'Extension long format description', 'version': '1.0.231', 'enabled': 'true', + 'terminated': 'false', 'enabledIncognito': 'false', 'wantsFileAccess': 'false', 'allowFileAccess': 'false', @@ -832,8 +834,10 @@ document.addEventListener('DOMContentLoaded', requestExtensionsData); jscontent="name">EXTENSION NAME</span> - <span i18n-content="extensionVersion">VERSION</span> <span jscontent="version">x.x.x.x</span> - <span jsdisplay="!enabled" + <span jsdisplay="!enabled && !terminated" i18n-content="extensionDisabled">(DISABLED)</span> + <span jsdisplay="terminated" + i18n-content="extensionCrashed">(CRASHED)</span> <span jsdisplay="order == 1" i18n-content="inDevelopment">(IN DEVELOPMENT)</span> </div> @@ -869,7 +873,7 @@ document.addEventListener('DOMContentLoaded', requestExtensionsData); <span class="extension-actions"> <a jsvalues=".extensionId:id" - jsdisplay="enabled && allow_reload" + jsdisplay="(enabled && allow_reload) || terminated" onclick="handleReloadExtension(this)" href="javascript:void 0;" i18n-content="reload" @@ -884,13 +888,15 @@ document.addEventListener('DOMContentLoaded', requestExtensionsData); >DISABLE</a> <a jsvalues=".extensionId:id" - jsdisplay="!enabled" + jsdisplay="!enabled && !terminated" onclick="handleEnableExtension(this, true)" href="javascript:void 0;" i18n-content="enable" - >ENABLE</a> - + >ENABLE</a> + <span jsdisplay="!terminated">-</span> <a jsvalues=".extensionId:id" + jsdisplay="!terminated" onclick="handleUninstallExtension(this)" href="javascript:void 0;" i18n-content="uninstall" diff --git a/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension1.json b/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension1.json index 283dd5a..ab9e575 100644 --- a/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension1.json +++ b/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension1.json @@ -3,6 +3,7 @@ "version": "1.0.0.0", "name": "__MSG_chrome_extension_name__", "enabled": true, + "terminated": false, "description": "__MSG_chrome_extension_description__", "permissions": ["http://*.google.com/*", "https://*.google.com/*"], "allow_reload": false, diff --git a/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension2.json b/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension2.json index 790bb00..078d343 100644 --- a/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension2.json +++ b/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension2.json @@ -3,6 +3,7 @@ "version": "2", "name": "My extension 2", "enabled": true, + "terminated": false, "description": "", "permissions": [], "allow_reload": false, diff --git a/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension3.json b/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension3.json index 62077f8..7cfb429 100644 --- a/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension3.json +++ b/chrome/test/data/extensions/ui/create_extension_detail_value_expected_output/good-extension3.json @@ -3,6 +3,7 @@ "version": "1.0", "name": "My extension 3", "enabled": true, + "terminated": false, "description": "", "permissions": [], "allow_reload": false, |