Chromium Code Reviews| 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..aea07cffc1c3d8cf4a662c92a8dbda62e911f343 100644 |
| --- a/chrome/browser/background/background_contents_service.cc |
| +++ b/chrome/browser/background/background_contents_service.cc |
| @@ -8,6 +8,7 @@ |
| #include "base/basictypes.h" |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| +#include "base/memory/weak_ptr.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/prefs/pref_service.h" |
| #include "base/strings/string_util.h" |
| @@ -22,6 +23,7 @@ |
| #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" |
| @@ -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, |
| @@ -205,9 +276,12 @@ 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) { |
| + : prefs_(NULL), weak_factory_(this) { |
| // Don't load/store preferences if the proper switch is not enabled, or if |
| // the parent profile is incognito. |
| if (!profile->IsOffTheRecord() && |
| @@ -225,6 +299,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 +432,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 +458,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 +509,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 "app/extension has crashed" balloons. |
|
bartfab (slow)
2013/08/27 19:21:31
Nit: This also removes any "app/extension is misbe
anitawoodruff
2013/08/28 09:03:06
Done.
|
| + ScheduleCloseBalloons(extension_id); |
| + misbehaving_extensions_.erase(extension_id); |
| + extension_crashlog_map_.erase(extension_id); |
| break; |
| } |
| @@ -431,6 +524,57 @@ 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(&BackgroundContentsService::ReloadExtension, |
| + weak_factory_.GetWeakPtr(), extension_id, profile), |
| + base::TimeDelta::FromMilliseconds(restart_delay_in_ms_)); |
| +} |
| + |
| +void BackgroundContentsService::ReloadExtension( |
| + const std::string& extension_id, Profile* profile) { |
| + 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. |
| + return; |
| + } |
| + extension_system->extension_service()->ReloadExtension(extension_id); |
| +} |
| + |
| // Loads all background contents whose urls have been stored in prefs. |
| void BackgroundContentsService::LoadBackgroundContentsFromPrefs( |
| Profile* profile) { |
| @@ -653,7 +797,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 |