Index: chrome/browser/background/background_contents_service.cc |
diff --git a/chrome/browser/background/background_contents_service.cc b/chrome/browser/background/background_contents_service.cc |
index 1e81d648bb7e383048e299743f62cf688b09ae99..f35f2cf07a0f7fefe450b8730c2daa7bab826c18 100644 |
--- a/chrome/browser/background/background_contents_service.cc |
+++ b/chrome/browser/background/background_contents_service.cc |
@@ -22,9 +22,11 @@ |
#include "chrome/browser/extensions/image_loader.h" |
#include "chrome/browser/notifications/desktop_notification_service.h" |
#include "chrome/browser/notifications/notification.h" |
+#include "chrome/browser/notifications/notification_delegate.h" |
#include "chrome/browser/notifications/notification_ui_manager.h" |
#include "chrome/browser/prefs/scoped_user_pref_update.h" |
#include "chrome/browser/profiles/profile.h" |
+#include "chrome/browser/profiles/profile_manager.h" |
#include "chrome/browser/ui/browser.h" |
#include "chrome/browser/ui/browser_finder.h" |
#include "chrome/browser/ui/browser_tabstrip.h" |
@@ -54,19 +56,39 @@ using extensions::UnloadedExtensionInfo; |
namespace { |
-const char kNotificationPrefix[] = "app.background.crashed."; |
+const char kCrashNotificationPrefix[] = "app.background.crashed."; |
+const char kMisbehaveNotificationPrefix[] = "app.background.misbehaved."; |
-void CloseBalloon(const std::string& id) { |
- g_browser_process->notification_ui_manager()->CancelById(id); |
+// Number of recent crashes of a force-installed app/extension that will |
+// trigger an 'App/Extension is misbehaving' balloon. |
+const unsigned int kMisbehaveCrashCountThreshold = 5; |
+ |
+void CloseBalloon(const std::string& balloon_id) { |
+ g_browser_process->notification_ui_manager()-> |
+ CancelById(balloon_id); |
} |
-void ScheduleCloseBalloon(const std::string& extension_id) { |
+// Closes the balloon with this id. |
+void ScheduleCloseBalloon(const std::string& balloon_id) { |
if (!base::MessageLoop::current()) // For unit_tests |
return; |
base::MessageLoop::current()->PostTask( |
- FROM_HERE, base::Bind(&CloseBalloon, kNotificationPrefix + extension_id)); |
+ FROM_HERE, base::Bind(&CloseBalloon, balloon_id)); |
+} |
+ |
+// Closes the crash notification balloon for the app/extension with this id. |
+void ScheduleCloseCrashBalloon(const std::string& extension_id) { |
+ ScheduleCloseBalloon(kCrashNotificationPrefix + extension_id); |
} |
+// Closes all notification balloons relating to the app/extension with this id. |
+void ScheduleCloseBalloons(const std::string& extension_id) { |
+ ScheduleCloseBalloon(kMisbehaveNotificationPrefix + extension_id); |
+ ScheduleCloseBalloon(kCrashNotificationPrefix + extension_id); |
+} |
+ |
+// Delegate for the 'app/extension has crashed' popup balloon. Restarts the |
+// app/extension when the balloon is clicked. |
class CrashNotificationDelegate : public NotificationDelegate { |
public: |
CrashNotificationDelegate(Profile* profile, |
@@ -107,15 +129,15 @@ class CrashNotificationDelegate : public NotificationDelegate { |
ReloadExtension(copied_extension_id); |
} |
- // Closing the balloon here should be OK, but it causes a crash on Mac |
- // http://crbug.com/78167 |
- ScheduleCloseBalloon(copied_extension_id); |
+ // Closing the 'app/extension has crashed' balloon here should be OK, but it |
+ // causes a crash on Mac (http://crbug.com/78167). |
+ ScheduleCloseCrashBalloon(copied_extension_id); |
} |
virtual bool HasClickedListener() OVERRIDE { return true; } |
virtual std::string id() const OVERRIDE { |
- return kNotificationPrefix + extension_id_; |
+ return kCrashNotificationPrefix + extension_id_; |
} |
virtual content::RenderViewHost* GetRenderViewHost() const OVERRIDE { |
@@ -133,12 +155,48 @@ class CrashNotificationDelegate : public NotificationDelegate { |
DISALLOW_COPY_AND_ASSIGN(CrashNotificationDelegate); |
}; |
+// Empty delegate for the 'app/extension is misbehaving' popup balloon, which is |
+// triggered if a force-installed app/extension gets stuck in a crash/reload |
+// cycle. Doesn't do anything on click because force-installed apps/extensions |
+// get restarted automatically. |
+class MisbehaveNotificationDelegate : public NotificationDelegate { |
+ public: |
+ explicit MisbehaveNotificationDelegate(const Extension* extension) |
+ : extension_id_(extension->id()) { |
+ } |
+ |
+ virtual void Display() OVERRIDE {} |
+ |
+ virtual void Error() OVERRIDE {} |
+ |
+ virtual void Close(bool by_user) OVERRIDE {} |
+ |
+ virtual void Click() OVERRIDE {} |
+ |
+ virtual bool HasClickedListener() OVERRIDE { return true; } |
+ |
+ virtual std::string id() const OVERRIDE { |
+ return kMisbehaveNotificationPrefix + extension_id_; |
+ } |
+ |
+ virtual content::RenderViewHost* GetRenderViewHost() const OVERRIDE { |
+ return NULL; |
+ } |
+ |
+ private: |
+ virtual ~MisbehaveNotificationDelegate() {} |
+ |
+ std::string extension_id_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(MisbehaveNotificationDelegate); |
+}; |
+ |
#if defined(ENABLE_NOTIFICATIONS) |
void NotificationImageReady( |
const std::string extension_name, |
const string16 message, |
const GURL extension_url, |
- scoped_refptr<CrashNotificationDelegate> delegate, |
+ scoped_refptr<NotificationDelegate> delegate, |
Profile* profile, |
const gfx::Image& icon) { |
gfx::Image notification_icon(icon); |
@@ -157,19 +215,32 @@ void NotificationImageReady( |
} |
#endif |
-void ShowBalloon(const Extension* extension, Profile* profile) { |
+// Show a popup notification balloon with a crash message for a given app/ |
+// extension. If |force_installed| is true we show an 'App/extension |
+// is misbehaving' message instead of a crash message. |
+void ShowBalloon(const Extension* extension, Profile* profile, |
+ bool force_installed) { |
#if defined(ENABLE_NOTIFICATIONS) |
- string16 message = l10n_util::GetStringFUTF16( |
- extension->is_app() ? IDS_BACKGROUND_CRASHED_APP_BALLOON_MESSAGE : |
- IDS_BACKGROUND_CRASHED_EXTENSION_BALLOON_MESSAGE, |
- UTF8ToUTF16(extension->name())); |
- |
+ string16 message; |
+ scoped_refptr<NotificationDelegate> delegate; |
+ if (force_installed) { |
+ message = l10n_util::GetStringFUTF16( |
+ extension->is_app() ? |
+ IDS_BACKGROUND_MISBEHAVING_APP_BALLOON_MESSAGE : |
+ IDS_BACKGROUND_MISBEHAVING_EXTENSION_BALLOON_MESSAGE, |
+ UTF8ToUTF16(extension->name())); |
+ delegate = new MisbehaveNotificationDelegate(extension); |
+ } else { |
+ message = l10n_util::GetStringFUTF16( |
+ extension->is_app() ? IDS_BACKGROUND_CRASHED_APP_BALLOON_MESSAGE : |
+ IDS_BACKGROUND_CRASHED_EXTENSION_BALLOON_MESSAGE, |
+ UTF8ToUTF16(extension->name())); |
+ delegate = new CrashNotificationDelegate(profile, extension); |
+ } |
extension_misc::ExtensionIcons size(extension_misc::EXTENSION_ICON_MEDIUM); |
extensions::ExtensionResource resource = |
extensions::IconsInfo::GetIconResource( |
extension, size, ExtensionIconSet::MATCH_SMALLER); |
- scoped_refptr<CrashNotificationDelegate> delegate = |
- new CrashNotificationDelegate(profile, extension); |
// We can't just load the image in the Observe method below because, despite |
// what this method is called, it may call the callback synchronously. |
// However, it's possible that the extension went away during the interim, |
@@ -188,6 +259,23 @@ void ShowBalloon(const Extension* extension, Profile* profile) { |
#endif |
} |
+void ReloadExtension( |
+ const std::string& extension_id, Profile* profile) { |
+ if (g_browser_process->IsShuttingDown() || |
+ !g_browser_process->profile_manager()->IsValidProfile(profile)) |
bartfab (slow)
2013/08/29 12:06:24
Nit 1: un-indent by two spaces.
Nit 2: Curly brace
anitawoodruff
2013/08/29 16:46:20
Done.
|
+ return; |
+ extensions::ExtensionSystem* extension_system = |
+ extensions::ExtensionSystem::Get(profile); |
+ if (!extension_system || !extension_system->extension_service()) |
+ return; |
+ if (!extension_system->extension_service()-> |
+ GetTerminatedExtension(extension_id)) { |
+ // Policy has changed. The app/extension is no longer force-installed. |
bartfab (slow)
2013/08/29 12:06:24
Nit: This will return |false| if the app/extension
anitawoodruff
2013/08/29 16:46:20
Done.
|
+ return; |
+ } |
+ extension_system->extension_service()->ReloadExtension(extension_id); |
+} |
+ |
} // namespace |
// Keys for the information we store about individual BackgroundContents in |
@@ -205,6 +293,9 @@ void ShowBalloon(const Extension* extension, Profile* profile) { |
const char kUrlKey[] = "url"; |
const char kFrameNameKey[] = "name"; |
+int BackgroundContentsService::restart_delay_in_ms_ = 3000; // 3 seconds. |
+int BackgroundContentsService::crash_window_in_ms_ = 1000; // 1 second. |
+ |
BackgroundContentsService::BackgroundContentsService( |
Profile* profile, const CommandLine* command_line) |
: prefs_(NULL) { |
@@ -225,6 +316,14 @@ BackgroundContentsService::~BackgroundContentsService() { |
DCHECK(contents_map_.empty()); |
} |
+// static |
+void BackgroundContentsService:: |
+ SetCrashDelaysForForceInstalledAppsAndExtensionsForTesting( |
+ int restart_delay_in_ms, int crash_window_in_ms) { |
+ restart_delay_in_ms_ = restart_delay_in_ms; |
+ crash_window_in_ms_ = crash_window_in_ms; |
+} |
+ |
std::vector<BackgroundContents*> |
BackgroundContentsService::GetBackgroundContents() const |
{ |
@@ -350,8 +449,8 @@ void BackgroundContentsService::Observe( |
} |
} |
- // Remove any "This extension has crashed" balloons. |
- ScheduleCloseBalloon(extension->id()); |
+ // Remove any "app/extension has crashed" balloons. |
+ ScheduleCloseCrashBalloon(extension->id()); |
SendChangeNotification(profile); |
break; |
} |
@@ -376,13 +475,21 @@ void BackgroundContentsService::Observe( |
if (!extension) |
break; |
- // When an extension crashes, EXTENSION_PROCESS_TERMINATED is followed by |
- // an EXTENSION_UNLOADED notification. This UNLOADED signal causes all the |
- // notifications for this extension to be cancelled by |
- // DesktopNotificationService. For this reason, instead of showing the |
- // balloon right now, we schedule it to show a little later. |
- base::MessageLoop::current()->PostTask( |
- FROM_HERE, base::Bind(&ShowBalloon, extension, profile)); |
+ const bool force_installed = extension->location() == |
+ extensions::Manifest::EXTERNAL_POLICY_DOWNLOAD; |
+ if (!force_installed) { |
+ // When an extension crashes, EXTENSION_PROCESS_TERMINATED is followed |
+ // by an EXTENSION_UNLOADED notification. This UNLOADED signal causes |
+ // all the notifications for this extension to be cancelled by |
+ // DesktopNotificationService. For this reason, we post the crash |
+ // handling code as a task here so that it is not executed before this |
+ // event. |
+ base::MessageLoop::current()->PostTask( |
+ FROM_HERE, base::Bind(&ShowBalloon, extension, profile, false)); |
+ } else { |
+ // Restart the extension; notify user if crash recurs frequently. |
+ RestartForceInstalledExtensionOnCrash(extension, profile); |
+ } |
break; |
} |
case chrome::NOTIFICATION_EXTENSION_UNLOADED: |
@@ -419,9 +526,12 @@ void BackgroundContentsService::Observe( |
break; |
case chrome::NOTIFICATION_EXTENSION_UNINSTALLED: { |
- // Remove any "This extension has crashed" balloons. |
- ScheduleCloseBalloon( |
- content::Details<const Extension>(details).ptr()->id()); |
+ const std::string& extension_id = |
+ content::Details<const Extension>(details).ptr()->id(); |
+ // Remove any balloons shown for this app/extension. |
+ ScheduleCloseBalloons(extension_id); |
+ misbehaving_extensions_.erase(extension_id); |
+ extension_crashlog_map_.erase(extension_id); |
break; |
} |
@@ -431,6 +541,42 @@ void BackgroundContentsService::Observe( |
} |
} |
+void BackgroundContentsService::RestartForceInstalledExtensionOnCrash( |
+ const Extension* extension, Profile* profile) { |
+ const std::string& extension_id = extension->id(); |
+ const bool already_notified = misbehaving_extensions_.find(extension_id) != |
+ misbehaving_extensions_.end(); |
+ std::queue<base::TimeTicks>& crashes = extension_crashlog_map_[extension_id]; |
+ const base::TimeDelta recent_time_window = |
+ base::TimeDelta::FromMilliseconds(kMisbehaveCrashCountThreshold * |
+ (restart_delay_in_ms_ + crash_window_in_ms_)); |
+ if (!already_notified) { |
+ // Show a notification if the threshold number of crashes has occurred |
+ // within a recent time period. |
+ const bool should_notify = |
+ crashes.size() == kMisbehaveCrashCountThreshold - 1 && |
+ base::TimeTicks::Now() - crashes.front() < recent_time_window; |
+ if (should_notify) { |
+ base::MessageLoop::current()->PostTask(FROM_HERE, |
+ base::Bind(&ShowBalloon, extension, profile, true)); |
+ misbehaving_extensions_.insert(extension_id); |
+ extension_crashlog_map_.erase(extension_id); |
+ } else { |
+ while (!crashes.empty() && |
+ base::TimeTicks::Now() - crashes.front() > recent_time_window) { |
+ // Remove old timestamps. |
+ crashes.pop(); |
+ } |
+ crashes.push(base::TimeTicks::Now()); |
+ if (crashes.size() == kMisbehaveCrashCountThreshold) |
+ crashes.pop(); |
+ } |
+ } |
+ base::MessageLoop::current()->PostDelayedTask(FROM_HERE, |
+ base::Bind(&ReloadExtension, extension_id, profile), |
+ base::TimeDelta::FromMilliseconds(restart_delay_in_ms_)); |
+} |
+ |
// Loads all background contents whose urls have been stored in prefs. |
void BackgroundContentsService::LoadBackgroundContentsFromPrefs( |
Profile* profile) { |
@@ -653,7 +799,7 @@ void BackgroundContentsService::BackgroundContentsOpened( |
contents_map_[details->application_id].contents = details->contents; |
contents_map_[details->application_id].frame_name = details->frame_name; |
- ScheduleCloseBalloon(UTF16ToASCII(details->application_id)); |
+ ScheduleCloseCrashBalloon(UTF16ToASCII(details->application_id)); |
} |
// Used by test code and debug checks to verify whether a given |