summaryrefslogtreecommitdiffstats
path: root/chrome/browser/extensions/api/power
diff options
context:
space:
mode:
authorderat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-20 12:18:26 +0000
committerderat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-20 12:18:26 +0000
commitee3f482917d9cdd169faf921dfef6455c33713a7 (patch)
tree98c3ba25792cb5a179f9af7df93241203b6fb3f1 /chrome/browser/extensions/api/power
parentb4beba41bff7c2f1e9437c0931a577c8d0daf49d (diff)
downloadchromium_src-ee3f482917d9cdd169faf921dfef6455c33713a7.zip
chromium_src-ee3f482917d9cdd169faf921dfef6455c33713a7.tar.gz
chromium_src-ee3f482917d9cdd169faf921dfef6455c33713a7.tar.bz2
Add chrome.power extension API.
This moves the chrome.experimental.power API to chrome.power, makes minor changes to its public interface (adding a "level" parameter to requestKeepAwake() and removing the callbacks from both methods), adds unit tests for it, and adds a sample "Keep Awake" extension meant to replace the existing Chrome OS-specific extension at http://goo.gl/CrUzi. BUG=178944 TBR=sky@chromium.org Review URL: https://chromiumcodereview.appspot.com/12576018 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@189253 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/extensions/api/power')
-rw-r--r--chrome/browser/extensions/api/power/power_api.cc26
-rw-r--r--chrome/browser/extensions/api/power/power_api.h38
-rw-r--r--chrome/browser/extensions/api/power/power_api_manager.cc109
-rw-r--r--chrome/browser/extensions/api/power/power_api_manager.h79
-rw-r--r--chrome/browser/extensions/api/power/power_api_unittest.cc282
5 files changed, 534 insertions, 0 deletions
diff --git a/chrome/browser/extensions/api/power/power_api.cc b/chrome/browser/extensions/api/power/power_api.cc
new file mode 100644
index 0000000..e82e4b5
--- /dev/null
+++ b/chrome/browser/extensions/api/power/power_api.cc
@@ -0,0 +1,26 @@
+// Copyright (c) 2013 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/api/power/power_api.h"
+
+#include "chrome/browser/extensions/api/power/power_api_manager.h"
+#include "chrome/common/extensions/api/power.h"
+
+namespace extensions {
+
+bool PowerRequestKeepAwakeFunction::RunImpl() {
+ scoped_ptr<api::power::RequestKeepAwake::Params> params(
+ api::power::RequestKeepAwake::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params);
+ EXTENSION_FUNCTION_VALIDATE(params->level != api::power::LEVEL_NONE);
+ PowerApiManager::GetInstance()->AddRequest(extension_id(), params->level);
+ return true;
+}
+
+bool PowerReleaseKeepAwakeFunction::RunImpl() {
+ PowerApiManager::GetInstance()->RemoveRequest(extension_id());
+ return true;
+}
+
+} // namespace extensions
diff --git a/chrome/browser/extensions/api/power/power_api.h b/chrome/browser/extensions/api/power/power_api.h
new file mode 100644
index 0000000..fcde9c5
--- /dev/null
+++ b/chrome/browser/extensions/api/power/power_api.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2012 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_API_POWER_POWER_API_H_
+#define CHROME_BROWSER_EXTENSIONS_API_POWER_POWER_API_H_
+
+#include "chrome/browser/extensions/extension_function.h"
+
+namespace extensions {
+
+// Implementation of the chrome.power.requestKeepAwake API.
+class PowerRequestKeepAwakeFunction : public SyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("power.requestKeepAwake", POWER_REQUESTKEEPAWAKE)
+
+ protected:
+ virtual ~PowerRequestKeepAwakeFunction() {}
+
+ // ExtensionFunction:
+ virtual bool RunImpl() OVERRIDE;
+};
+
+// Implementation of the chrome.power.releaseKeepAwake API.
+class PowerReleaseKeepAwakeFunction : public SyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("power.releaseKeepAwake", POWER_RELEASEKEEPAWAKE)
+
+ protected:
+ virtual ~PowerReleaseKeepAwakeFunction() {}
+
+ // ExtensionFunction:
+ virtual bool RunImpl() OVERRIDE;
+};
+
+} // namespace extensions
+
+#endif // CHROME_BROWSER_EXTENSIONS_API_POWER_POWER_API_H_
diff --git a/chrome/browser/extensions/api/power/power_api_manager.cc b/chrome/browser/extensions/api/power/power_api_manager.cc
new file mode 100644
index 0000000..1a1c37b
--- /dev/null
+++ b/chrome/browser/extensions/api/power/power_api_manager.cc
@@ -0,0 +1,109 @@
+// Copyright (c) 2013 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/api/power/power_api_manager.h"
+
+#include "base/bind.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/extensions/extension.h"
+#include "content/public/browser/notification_service.h"
+
+namespace extensions {
+
+namespace {
+
+const char kPowerSaveBlockerReason[] = "extension";
+
+content::PowerSaveBlocker::PowerSaveBlockerType
+LevelToPowerSaveBlockerType(api::power::Level level) {
+ switch (level) {
+ case api::power::LEVEL_SYSTEM:
+ return content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension;
+ case api::power::LEVEL_DISPLAY: // fallthrough
+ case api::power::LEVEL_NONE:
+ return content::PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep;
+ }
+ NOTREACHED() << "Unhandled level " << level;
+ return content::PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep;
+}
+
+} // namespace
+
+// static
+PowerApiManager* PowerApiManager::GetInstance() {
+ return Singleton<PowerApiManager>::get();
+}
+
+void PowerApiManager::AddRequest(const std::string& extension_id,
+ api::power::Level level) {
+ extension_levels_[extension_id] = level;
+ UpdatePowerSaveBlocker();
+}
+
+void PowerApiManager::RemoveRequest(const std::string& extension_id) {
+ extension_levels_.erase(extension_id);
+ UpdatePowerSaveBlocker();
+}
+
+void PowerApiManager::SetCreateBlockerFunctionForTesting(
+ CreateBlockerFunction function) {
+ create_blocker_function_ = !function.is_null() ? function :
+ base::Bind(&content::PowerSaveBlocker::Create);
+}
+
+void PowerApiManager::Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ switch (type) {
+ case chrome::NOTIFICATION_EXTENSION_UNLOADED:
+ RemoveRequest(content::Details<extensions::UnloadedExtensionInfo>(
+ details)->extension->id());
+ UpdatePowerSaveBlocker();
+ break;
+ case chrome::NOTIFICATION_APP_TERMINATING:
+ power_save_blocker_.reset();
+ break;
+ default:
+ NOTREACHED() << "Unexpected notification " << type;
+ }
+}
+
+PowerApiManager::PowerApiManager()
+ : create_blocker_function_(base::Bind(&content::PowerSaveBlocker::Create)),
+ current_level_(api::power::LEVEL_SYSTEM) {
+ registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
+ content::NotificationService::AllSources());
+ registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
+ content::NotificationService::AllSources());
+}
+
+PowerApiManager::~PowerApiManager() {}
+
+void PowerApiManager::UpdatePowerSaveBlocker() {
+ if (extension_levels_.empty()) {
+ power_save_blocker_.reset();
+ return;
+ }
+
+ api::power::Level new_level = api::power::LEVEL_SYSTEM;
+ for (ExtensionLevelMap::const_iterator it = extension_levels_.begin();
+ it != extension_levels_.end(); ++it) {
+ if (it->second == api::power::LEVEL_DISPLAY)
+ new_level = it->second;
+ }
+
+ // If the level changed and we need to create a new blocker, do a swap
+ // to ensure that there isn't a brief period where power management is
+ // unblocked.
+ if (!power_save_blocker_ || new_level != current_level_) {
+ content::PowerSaveBlocker::PowerSaveBlockerType type =
+ LevelToPowerSaveBlockerType(new_level);
+ scoped_ptr<content::PowerSaveBlocker> new_blocker(
+ create_blocker_function_.Run(type, kPowerSaveBlockerReason));
+ power_save_blocker_.swap(new_blocker);
+ current_level_ = new_level;
+ }
+}
+
+} // namespace extensions
diff --git a/chrome/browser/extensions/api/power/power_api_manager.h b/chrome/browser/extensions/api/power/power_api_manager.h
new file mode 100644
index 0000000..48ed9ee
--- /dev/null
+++ b/chrome/browser/extensions/api/power/power_api_manager.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2013 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_API_POWER_POWER_API_MANAGER_H_
+#define CHROME_BROWSER_EXTENSIONS_API_POWER_POWER_API_MANAGER_H_
+
+#include <map>
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/singleton.h"
+#include "chrome/common/extensions/api/power.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "content/public/browser/power_save_blocker.h"
+
+namespace extensions {
+
+class PowerApiManager : public content::NotificationObserver {
+ public:
+ typedef base::Callback<scoped_ptr<content::PowerSaveBlocker>(
+ content::PowerSaveBlocker::PowerSaveBlockerType,
+ const std::string&)> CreateBlockerFunction;
+
+ static PowerApiManager* GetInstance();
+
+ // Adds an extension lock at |level| for |extension_id|, replacing the
+ // extension's existing lock, if any.
+ void AddRequest(const std::string& extension_id, api::power::Level level);
+
+ // Removes an extension lock for an extension. Calling this for an
+ // extension id without a lock will do nothing.
+ void RemoveRequest(const std::string& extension_id);
+
+ // Replaces the function that will be called to create PowerSaveBlocker
+ // objects. Passing an empty callback will revert to the default.
+ void SetCreateBlockerFunctionForTesting(CreateBlockerFunction function);
+
+ // Overridden from content::NotificationObserver.
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ private:
+ friend struct DefaultSingletonTraits<PowerApiManager>;
+
+ PowerApiManager();
+ virtual ~PowerApiManager();
+
+ // Updates |power_save_blocker_| and |current_level_| after iterating
+ // over |extension_levels_|.
+ void UpdatePowerSaveBlocker();
+
+ content::NotificationRegistrar registrar_;
+
+ // Function that should be called to create PowerSaveBlocker objects.
+ // Tests can change this to record what would've been done instead of
+ // actually changing the system power-saving settings.
+ CreateBlockerFunction create_blocker_function_;
+
+ scoped_ptr<content::PowerSaveBlocker> power_save_blocker_;
+
+ // Current level used by |power_save_blocker_|. Meaningless if
+ // |power_save_blocker_| is NULL.
+ api::power::Level current_level_;
+
+ // Map from extension ID to the corresponding level for each extension
+ // that has an outstanding request.
+ typedef std::map<std::string, api::power::Level> ExtensionLevelMap;
+ ExtensionLevelMap extension_levels_;
+
+ DISALLOW_COPY_AND_ASSIGN(PowerApiManager);
+};
+
+} // namespace extensions
+
+#endif // CHROME_BROWSER_EXTENSIONS_API_POWER_POWER_API_MANAGER_H_
diff --git a/chrome/browser/extensions/api/power/power_api_unittest.cc b/chrome/browser/extensions/api/power/power_api_unittest.cc
new file mode 100644
index 0000000..7c2ce9a
--- /dev/null
+++ b/chrome/browser/extensions/api/power/power_api_unittest.cc
@@ -0,0 +1,282 @@
+// Copyright (c) 2013 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/api/power/power_api.h"
+
+#include <deque>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/extensions/api/power/power_api_manager.h"
+#include "chrome/browser/extensions/extension_function_test_utils.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/test/base/browser_with_test_window_test.h"
+#include "content/public/browser/power_save_blocker.h"
+
+namespace utils = extension_function_test_utils;
+
+namespace extensions {
+
+namespace {
+
+// Args commonly passed to PowerSaveBlockerStubManager::CallFunction().
+const char kDisplayArgs[] = "[\"display\"]";
+const char kSystemArgs[] = "[\"system\"]";
+const char kEmptyArgs[] = "[]";
+
+// Different actions that can be performed as a result of a
+// PowerSaveBlocker being created or destroyed.
+enum Request {
+ BLOCK_APP_SUSPENSION,
+ UNBLOCK_APP_SUSPENSION,
+ BLOCK_DISPLAY_SLEEP,
+ UNBLOCK_DISPLAY_SLEEP,
+ // Returned by PowerSaveBlockerStubManager::PopFirstRequest() when no
+ // requests are present.
+ NONE,
+};
+
+// Stub implementation of content::PowerSaveBlocker that just runs a
+// callback on destruction.
+class PowerSaveBlockerStub : public content::PowerSaveBlocker {
+ public:
+ explicit PowerSaveBlockerStub(base::Closure unblock_callback)
+ : unblock_callback_(unblock_callback) {
+ }
+
+ virtual ~PowerSaveBlockerStub() {
+ unblock_callback_.Run();
+ }
+
+ private:
+ base::Closure unblock_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(PowerSaveBlockerStub);
+};
+
+// Manages PowerSaveBlockerStub objects. Tests can instantiate this class
+// to make PowerApiManager's calls to create PowerSaveBlockers record the
+// actions that would've been performed instead of actually blocking and
+// unblocking power management.
+class PowerSaveBlockerStubManager {
+ public:
+ PowerSaveBlockerStubManager() : weak_ptr_factory_(this) {
+ // Use base::Unretained since callbacks with return values can't use
+ // weak pointers.
+ PowerApiManager::GetInstance()->SetCreateBlockerFunctionForTesting(
+ base::Bind(&PowerSaveBlockerStubManager::CreateStub,
+ base::Unretained(this)));
+ }
+
+ ~PowerSaveBlockerStubManager() {
+ PowerApiManager::GetInstance()->SetCreateBlockerFunctionForTesting(
+ PowerApiManager::CreateBlockerFunction());
+ }
+
+ // Removes and returns the first item from |requests_|. Returns NONE if
+ // |requests_| is empty.
+ Request PopFirstRequest() {
+ if (requests_.empty())
+ return NONE;
+
+ Request request = requests_.front();
+ requests_.pop_front();
+ return request;
+ }
+
+ private:
+ // Creates a new PowerSaveBlockerStub of type |type|.
+ scoped_ptr<content::PowerSaveBlocker> CreateStub(
+ content::PowerSaveBlocker::PowerSaveBlockerType type,
+ const std::string& reason) {
+ Request unblock_request = NONE;
+ switch (type) {
+ case content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension:
+ requests_.push_back(BLOCK_APP_SUSPENSION);
+ unblock_request = UNBLOCK_APP_SUSPENSION;
+ break;
+ case content::PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep:
+ requests_.push_back(BLOCK_DISPLAY_SLEEP);
+ unblock_request = UNBLOCK_DISPLAY_SLEEP;
+ break;
+ }
+ return scoped_ptr<content::PowerSaveBlocker>(
+ new PowerSaveBlockerStub(
+ base::Bind(&PowerSaveBlockerStubManager::AppendRequest,
+ weak_ptr_factory_.GetWeakPtr(),
+ unblock_request)));
+ }
+
+ void AppendRequest(Request request) {
+ requests_.push_back(request);
+ }
+
+ // Requests in chronological order.
+ std::deque<Request> requests_;
+
+ base::WeakPtrFactory<PowerSaveBlockerStubManager> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(PowerSaveBlockerStubManager);
+};
+
+} // namespace
+
+class PowerApiTest : public BrowserWithTestWindowTest {
+ public:
+ virtual void SetUp() OVERRIDE {
+ BrowserWithTestWindowTest::SetUp();
+ manager_.reset(new PowerSaveBlockerStubManager);
+ extension_ = utils::CreateEmptyExtensionWithLocation(
+ extensions::Manifest::UNPACKED);
+ }
+
+ protected:
+ // Shorthand for PowerRequestKeepAwakeFunction and
+ // PowerReleaseKeepAwakeFunction.
+ enum FunctionType {
+ REQUEST,
+ RELEASE,
+ };
+
+ // Calls the function described by |type| with |args|, a JSON list of
+ // arguments, on behalf of |extension|.
+ bool CallFunction(FunctionType type,
+ const std::string& args,
+ extensions::Extension* extension) {
+ UIThreadExtensionFunction* function =
+ type == REQUEST ?
+ static_cast<UIThreadExtensionFunction*>(
+ new PowerRequestKeepAwakeFunction) :
+ static_cast<UIThreadExtensionFunction*>(
+ new PowerReleaseKeepAwakeFunction);
+ function->set_extension(extension);
+ return utils::RunFunction(function, args, browser(), utils::NONE);
+ }
+
+ // Send a notification to PowerApiManager saying that |extension| has
+ // been unloaded.
+ void UnloadExtension(extensions::Extension* extension) {
+ UnloadedExtensionInfo details(
+ extension, extension_misc::UNLOAD_REASON_UNINSTALL);
+ PowerApiManager::GetInstance()->Observe(
+ chrome::NOTIFICATION_EXTENSION_UNLOADED,
+ content::Source<Profile>(browser()->profile()),
+ content::Details<UnloadedExtensionInfo>(&details));
+ }
+
+ scoped_ptr<PowerSaveBlockerStubManager> manager_;
+ scoped_refptr<extensions::Extension> extension_;
+};
+
+TEST_F(PowerApiTest, RequestAndRelease) {
+ // Simulate an extension making and releasing a "display" request and a
+ // "system" request.
+ ASSERT_TRUE(CallFunction(REQUEST, kDisplayArgs, extension_.get()));
+ EXPECT_EQ(BLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+ ASSERT_TRUE(CallFunction(RELEASE, kEmptyArgs, extension_.get()));
+ EXPECT_EQ(UNBLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+
+ ASSERT_TRUE(CallFunction(REQUEST, kSystemArgs, extension_.get()));
+ EXPECT_EQ(BLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+ ASSERT_TRUE(CallFunction(RELEASE, kEmptyArgs, extension_.get()));
+ EXPECT_EQ(UNBLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+}
+
+TEST_F(PowerApiTest, RequestWithoutRelease) {
+ // Simulate an extension calling requestKeepAwake() without calling
+ // releaseKeepAwake(). The override should be automatically removed when
+ // the extension is unloaded.
+ ASSERT_TRUE(CallFunction(REQUEST, kDisplayArgs, extension_.get()));
+ EXPECT_EQ(BLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+
+ UnloadExtension(extension_.get());
+ EXPECT_EQ(UNBLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+}
+
+TEST_F(PowerApiTest, ReleaseWithoutRequest) {
+ // Simulate an extension calling releaseKeepAwake() without having
+ // calling requestKeepAwake() earlier. The call should be ignored.
+ ASSERT_TRUE(CallFunction(RELEASE, kEmptyArgs, extension_.get()));
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+}
+
+TEST_F(PowerApiTest, UpgradeRequest) {
+ // Simulate an extension calling requestKeepAwake("system") and then
+ // requestKeepAwake("display"). When the second call is made, a
+ // display-sleep-blocking request should be made before the initial
+ // app-suspension-blocking request is released.
+ ASSERT_TRUE(CallFunction(REQUEST, kSystemArgs, extension_.get()));
+ EXPECT_EQ(BLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+
+ ASSERT_TRUE(CallFunction(REQUEST, kDisplayArgs, extension_.get()));
+ EXPECT_EQ(BLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(UNBLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+
+ ASSERT_TRUE(CallFunction(RELEASE, kEmptyArgs, extension_.get()));
+ EXPECT_EQ(UNBLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+}
+
+TEST_F(PowerApiTest, DowngradeRequest) {
+ // Simulate an extension calling requestKeepAwake("display") and then
+ // requestKeepAwake("system"). When the second call is made, an
+ // app-suspension-blocking request should be made before the initial
+ // display-sleep-blocking request is released.
+ ASSERT_TRUE(CallFunction(REQUEST, kDisplayArgs, extension_.get()));
+ EXPECT_EQ(BLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+
+ ASSERT_TRUE(CallFunction(REQUEST, kSystemArgs, extension_.get()));
+ EXPECT_EQ(BLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
+ EXPECT_EQ(UNBLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+
+ ASSERT_TRUE(CallFunction(RELEASE, kEmptyArgs, extension_.get()));
+ EXPECT_EQ(UNBLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+}
+
+TEST_F(PowerApiTest, MultipleExtensions) {
+ // Simulate an extension blocking the display from sleeping.
+ ASSERT_TRUE(CallFunction(REQUEST, kDisplayArgs, extension_.get()));
+ EXPECT_EQ(BLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+
+ // Create a second extension that blocks system suspend. No additional
+ // PowerSaveBlocker is needed; the blocker from the first extension
+ // already covers the behavior requested by the second extension.
+ scoped_ptr<base::DictionaryValue> extension_value(
+ utils::ParseDictionary("{\"name\": \"Test\", \"version\": \"1.0\"}"));
+ scoped_refptr<extensions::Extension> extension2(
+ utils::CreateExtension(extensions::Manifest::UNPACKED,
+ extension_value.get(), "second_extension"));
+ ASSERT_TRUE(CallFunction(REQUEST, kSystemArgs, extension2.get()));
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+
+ // When the first extension is unloaded, a new app-suspension blocker
+ // should be created before the display-sleep blocker is destroyed.
+ UnloadExtension(extension_.get());
+ EXPECT_EQ(BLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
+ EXPECT_EQ(UNBLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+
+ // Make the first extension block display-sleep again.
+ ASSERT_TRUE(CallFunction(REQUEST, kDisplayArgs, extension_.get()));
+ EXPECT_EQ(BLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(UNBLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+}
+
+} // namespace extensions