Reload force-installed extensions on crash/force-close

Previously, users could disable force-installed extensions by killing the
process via the Chrome Task Manager or other means. This fix prevents that
by reloading force-installed extensions if they crash.

Also added an 'extension misbehaving' popup to inform the user if we get
stuck in a crash/reload loop, so they know to inform their administrator.

BUG=175701

TEST=Updated browser_tests

Review URL: https://chromiumcodereview.appspot.com/23427003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@220586 0039d316-1c4b-4281-b951-d872f2087c98
parent 3fad6701
......@@ -271,6 +271,12 @@ are declared in build/common.gypi.
<message name="IDS_BACKGROUND_CRASHED_EXTENSION_BALLOON_MESSAGE" desc="The contents of the balloon that is displayed when an extension crashes">
<ph name="APP_NAME">$1<ex>Extension</ex></ph> has crashed. Click this balloon to reload the extension.
</message>
<message name="IDS_BACKGROUND_MISBEHAVING_APP_BALLOON_MESSAGE" desc="The contents of the balloon that is displayed when a background force-installed app crashes multiple times">
Managed app <ph name="APP_NAME">$1<ex>Background App</ex></ph> installed by enterprise policy is misbehaving. Please contact your administrator.
</message>
<message name="IDS_BACKGROUND_MISBEHAVING_EXTENSION_BALLOON_MESSAGE" desc="The contents of the balloon that is displayed when an extension installed by enterprise policy crashes repeatedly">
Managed extension <ph name="APP_NAME">$1<ex>Extension</ex></ph> installed by enterprise policy is misbehaving. Please contact your administrator.
</message>
<if expr="pp_ifdef('use_titlecase')">
<message name="IDS_BACKGROUND_APP_NOT_INSTALLED" desc="In Title Case: Label displayed in the status icon context menu when a profile does not have any background apps running.">
No Background Apps Running
......
......@@ -6,12 +6,16 @@
#define CHROME_BROWSER_BACKGROUND_BACKGROUND_CONTENTS_SERVICE_H_
#include <map>
#include <queue>
#include <set>
#include <string>
#include <vector>
#include "base/gtest_prod_util.h"
#include "base/memory/ref_counted.h"
#include "base/time/time.h"
#include "chrome/browser/tab_contents/background_contents.h"
#include "chrome/common/extensions/extension.h"
#include "components/browser_context_keyed_service/browser_context_keyed_service.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
......@@ -52,6 +56,12 @@ class BackgroundContentsService : private content::NotificationObserver,
BackgroundContentsService(Profile* profile, const CommandLine* command_line);
virtual ~BackgroundContentsService();
// Allows tests to reduce the time between a force-installed app/extension
// crashing and when we reload it, and the amount of time we wait for further
// crashes before showing a balloon saying the app/extension is misbehaving.
static void SetCrashDelaysForForceInstalledAppsAndExtensionsForTesting(
int restart_delay_in_ms, int crash_window_in_ms);
// Returns the BackgroundContents associated with the passed application id,
// or NULL if none.
BackgroundContents* GetAppBackgroundContents(const string16& appid);
......@@ -121,6 +131,11 @@ class BackgroundContentsService : private content::NotificationObserver,
const content::NotificationSource& source,
const content::NotificationDetails& details) OVERRIDE;
// Restarts a force-installed app/extension after a crash. Notifies user if
// crash recurs frequently.
void RestartForceInstalledExtensionOnCrash(
const extensions::Extension* extension, Profile* profile);
// Loads all registered BackgroundContents at startup.
void LoadBackgroundContentsFromPrefs(Profile* profile);
......@@ -170,6 +185,15 @@ class BackgroundContentsService : private content::NotificationObserver,
// set of background apps as new background contents are opened/closed).
void SendChangeNotification(Profile* profile);
// Delay (in ms) before restarting a force-installed extension that crashed.
static int restart_delay_in_ms_;
// When a force-installed app/extension crashes, we check if it's in a crash/
// reload loop by checking if the number of crashes exceeds a threshold in a
// given time window. The duration of that window is given by:
// kMisbehaveCrashCountThreshold * (restart_delay_in_ms + crash_window_in_ms)
static int crash_window_in_ms_;
// PrefService used to store list of background pages (or NULL if this is
// running under an incognito profile).
PrefService* prefs_;
......@@ -190,6 +214,16 @@ class BackgroundContentsService : private content::NotificationObserver,
typedef std::map<string16, BackgroundContentsInfo> BackgroundContentsMap;
BackgroundContentsMap contents_map_;
// Map associating IDs of force-installed extensions/apps with their most
// recent crash timestamps.
// Key: app/extension id.
// Value: queue containing up to 5 most recent crash timestamps.
std::map<std::string, std::queue<base::TimeTicks> > extension_crashlog_map_;
// Map containing ids of force-installed apps/extensions for which we have
// already shown an 'App/Extension is misbehaving' balloon.
std::set<std::string> misbehaving_extensions_;
DISALLOW_COPY_AND_ASSIGN(BackgroundContentsService);
};
......
......@@ -27,12 +27,15 @@
#include "base/values.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/autocomplete/autocomplete_controller.h"
#include "chrome/browser/background/background_contents_service.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/content_settings/tab_specific_content_settings.h"
#include "chrome/browser/devtools/devtools_window.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/extensions/extension_host.h"
#include "chrome/browser/extensions/extension_process_manager.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_system.h"
#include "chrome/browser/extensions/unpacked_installer.h"
......@@ -86,6 +89,8 @@
#include "content/public/browser/child_process_data.h"
#include "content/public/browser/download_item.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
......@@ -98,6 +103,7 @@
#include "content/public/common/content_paths.h"
#include "content/public/common/page_transition_types.h"
#include "content/public/common/process_type.h"
#include "content/public/common/result_codes.h"
#include "content/public/common/url_constants.h"
#include "content/public/common/webplugininfo.h"
#include "content/public/test/browser_test_utils.h"
......@@ -449,6 +455,64 @@ class TestAudioObserver : public chromeos::CrasAudioHandler::AudioObserver {
};
#endif
// This is a customized version of content::WindowedNotificationObserver that
// waits until either of the two provided notification types is observed.
// See content::WindowedNotificationObserver for further documentation.
class OneOfTwoNotificationsObserver : public content::NotificationObserver {
public:
// Set up to wait for one of two notifications.
OneOfTwoNotificationsObserver(int notification_type1, int notification_type2);
virtual ~OneOfTwoNotificationsObserver();
// Wait until one of the specified notifications is observed. If either
// notification has already been received, Wait() returns immediately.
void Wait();
// content::NotificationObserver:
virtual void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) OVERRIDE;
private:
bool seen_;
bool running_;
content::NotificationRegistrar registrar_;
scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
DISALLOW_COPY_AND_ASSIGN(OneOfTwoNotificationsObserver);
};
OneOfTwoNotificationsObserver::OneOfTwoNotificationsObserver(
int notification_type1, int notification_type2)
: seen_(false), running_(false) {
registrar_.Add(this, notification_type1,
content::NotificationService::AllSources());
registrar_.Add(this, notification_type2,
content::NotificationService::AllSources());
}
OneOfTwoNotificationsObserver::~OneOfTwoNotificationsObserver() {}
void OneOfTwoNotificationsObserver::Wait() {
if (seen_)
return;
running_ = true;
message_loop_runner_ = new content::MessageLoopRunner;
message_loop_runner_->Run();
EXPECT_TRUE(seen_);
}
// NotificationObserver:
void OneOfTwoNotificationsObserver::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
seen_ = true;
if (!running_)
return;
message_loop_runner_->Quit();
running_ = false;
}
} // namespace
class PolicyTest : public InProcessBrowserTest {
......@@ -1402,6 +1466,46 @@ IN_PROC_BROWSER_TEST_F(PolicyTest, ExtensionInstallForcelist) {
EXPECT_EQ(1, new_version->CompareTo(old_version));
EXPECT_EQ(0u, interceptor.GetPendingSize());
// Wait until any background pages belonging to force-installed extensions
// have been loaded.
ExtensionProcessManager* manager =
extensions::ExtensionSystem::Get(browser()->profile())->process_manager();
ExtensionProcessManager::ViewSet all_views = manager->GetAllViews();
for (ExtensionProcessManager::ViewSet::const_iterator iter =
all_views.begin();
iter != all_views.end();) {
if (!(*iter)->IsLoading()) {
++iter;
} else {
OneOfTwoNotificationsObserver(
content::NOTIFICATION_LOAD_STOP,
content::NOTIFICATION_WEB_CONTENTS_DESTROYED).Wait();
// Test activity may have modified the set of extension processes during
// message processing, so re-start the iteration to catch added/removed
// processes.
all_views = manager->GetAllViews();
iter = all_views.begin();
}
}
// Test policy-installed extensions are reloaded when killed.
BackgroundContentsService::
SetCrashDelaysForForceInstalledAppsAndExtensionsForTesting(0, 0);
content::WindowedNotificationObserver extension_crashed_observer(
chrome::NOTIFICATION_EXTENSION_PROCESS_TERMINATED,
content::NotificationService::AllSources());
content::WindowedNotificationObserver extension_loaded_observer(
chrome::NOTIFICATION_EXTENSION_LOADED,
content::NotificationService::AllSources());
extensions::ExtensionHost* extension_host =
extensions::ExtensionSystem::Get(browser()->profile())->
process_manager()->GetBackgroundHostForExtension(kGoodCrxId);
base::KillProcess(extension_host->render_process_host()->GetHandle(),
content::RESULT_CODE_KILLED, false);
extension_crashed_observer.Wait();
extension_loaded_observer.Wait();
}
IN_PROC_BROWSER_TEST_F(PolicyTest, ExtensionAllowedTypes) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment