summaryrefslogtreecommitdiffstats
path: root/extensions
diff options
context:
space:
mode:
authorderat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-07-03 07:54:08 +0000
committerderat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-07-03 07:54:08 +0000
commit23c371de25c5e981599dcc0f6c381842d05ae80d (patch)
tree4af9eeb75ea21bf6de15e2a9631e1088018fec30 /extensions
parent9816ff759cec9ef4b8774b649c695cd9d51ade80 (diff)
downloadchromium_src-23c371de25c5e981599dcc0f6c381842d05ae80d.zip
chromium_src-23c371de25c5e981599dcc0f6c381842d05ae80d.tar.gz
chromium_src-23c371de25c5e981599dcc0f6c381842d05ae80d.tar.bz2
Move chrome.power API from chrome/ to extensions/.
BUG=389348 TBR=sky@chromium.org Review URL: https://codereview.chromium.org/363993003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@281220 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'extensions')
-rw-r--r--extensions/DEPS1
-rw-r--r--extensions/browser/api/power/OWNERS1
-rw-r--r--extensions/browser/api/power/power_api.cc27
-rw-r--r--extensions/browser/api/power/power_api.h38
-rw-r--r--extensions/browser/api/power/power_api_manager.cc114
-rw-r--r--extensions/browser/api/power/power_api_manager.h98
-rw-r--r--extensions/browser/api/power/power_api_unittest.cc288
-rw-r--r--extensions/common/api/BUILD.gn1
-rw-r--r--extensions/common/api/_api_features.json4
-rw-r--r--extensions/common/api/_permission_features.json4
-rw-r--r--extensions/common/api/api.gyp1
-rw-r--r--extensions/common/api/power.idl27
-rw-r--r--extensions/common/permissions/extensions_api_permissions.cc1
-rw-r--r--extensions/extensions.gyp4
14 files changed, 609 insertions, 0 deletions
diff --git a/extensions/DEPS b/extensions/DEPS
index 3e4f6ad..0505b02 100644
--- a/extensions/DEPS
+++ b/extensions/DEPS
@@ -37,6 +37,7 @@ specific_include_rules = {
"+chrome/common/chrome_paths.h",
"+chrome/common/extensions/features/feature_channel.h",
"+chrome/common/extensions/manifest_tests/extension_manifest_test.h",
+ "+chrome/test/base/browser_with_test_window_test.h",
"+chrome/test/base/testing_profile.h",
"+chrome/test/base/ui_test_utils.h",
],
diff --git a/extensions/browser/api/power/OWNERS b/extensions/browser/api/power/OWNERS
new file mode 100644
index 0000000..3c97e54
--- /dev/null
+++ b/extensions/browser/api/power/OWNERS
@@ -0,0 +1 @@
+derat@chromium.org
diff --git a/extensions/browser/api/power/power_api.cc b/extensions/browser/api/power/power_api.cc
new file mode 100644
index 0000000..d731b9f
--- /dev/null
+++ b/extensions/browser/api/power/power_api.cc
@@ -0,0 +1,27 @@
+// Copyright 2014 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 "extensions/browser/api/power/power_api.h"
+
+#include "extensions/browser/api/power/power_api_manager.h"
+#include "extensions/common/api/power.h"
+
+namespace extensions {
+
+bool PowerRequestKeepAwakeFunction::RunSync() {
+ scoped_ptr<core_api::power::RequestKeepAwake::Params> params(
+ core_api::power::RequestKeepAwake::Params::Create(*args_));
+ EXTENSION_FUNCTION_VALIDATE(params);
+ EXTENSION_FUNCTION_VALIDATE(params->level != core_api::power::LEVEL_NONE);
+ PowerApiManager::Get(browser_context())->AddRequest(
+ extension_id(), params->level);
+ return true;
+}
+
+bool PowerReleaseKeepAwakeFunction::RunSync() {
+ PowerApiManager::Get(browser_context())->RemoveRequest(extension_id());
+ return true;
+}
+
+} // namespace extensions
diff --git a/extensions/browser/api/power/power_api.h b/extensions/browser/api/power/power_api.h
new file mode 100644
index 0000000..505cc42
--- /dev/null
+++ b/extensions/browser/api/power/power_api.h
@@ -0,0 +1,38 @@
+// Copyright 2014 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 EXTENSIONS_BROWSER_API_POWER_POWER_API_H_
+#define EXTENSIONS_BROWSER_API_POWER_POWER_API_H_
+
+#include "extensions/browser/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 RunSync() 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 RunSync() OVERRIDE;
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_POWER_POWER_API_H_
diff --git a/extensions/browser/api/power/power_api_manager.cc b/extensions/browser/api/power/power_api_manager.cc
new file mode 100644
index 0000000..9a8d0d5
--- /dev/null
+++ b/extensions/browser/api/power/power_api_manager.cc
@@ -0,0 +1,114 @@
+// Copyright 2014 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 "extensions/browser/api/power/power_api_manager.h"
+
+#include "base/bind.h"
+#include "base/lazy_instance.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/extension.h"
+
+namespace extensions {
+
+namespace {
+
+const char kPowerSaveBlockerReason[] = "extension";
+
+content::PowerSaveBlocker::PowerSaveBlockerType
+LevelToPowerSaveBlockerType(core_api::power::Level level) {
+ switch (level) {
+ case core_api::power::LEVEL_SYSTEM:
+ return content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension;
+ case core_api::power::LEVEL_DISPLAY: // fallthrough
+ case core_api::power::LEVEL_NONE:
+ return content::PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep;
+ }
+ NOTREACHED() << "Unhandled level " << level;
+ return content::PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep;
+}
+
+base::LazyInstance<BrowserContextKeyedAPIFactory<PowerApiManager> > g_factory =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// static
+PowerApiManager* PowerApiManager::Get(content::BrowserContext* context) {
+ return BrowserContextKeyedAPIFactory<PowerApiManager>::Get(context);
+}
+
+// static
+BrowserContextKeyedAPIFactory<PowerApiManager>*
+PowerApiManager::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+void PowerApiManager::AddRequest(const std::string& extension_id,
+ core_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::OnExtensionUnloaded(
+ content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ RemoveRequest(extension->id());
+ UpdatePowerSaveBlocker();
+}
+
+PowerApiManager::PowerApiManager(content::BrowserContext* context)
+ : browser_context_(context),
+ create_blocker_function_(base::Bind(&content::PowerSaveBlocker::Create)),
+ current_level_(core_api::power::LEVEL_SYSTEM) {
+ ExtensionRegistry::Get(browser_context_)->AddObserver(this);
+}
+
+PowerApiManager::~PowerApiManager() {}
+
+void PowerApiManager::UpdatePowerSaveBlocker() {
+ if (extension_levels_.empty()) {
+ power_save_blocker_.reset();
+ return;
+ }
+
+ core_api::power::Level new_level = core_api::power::LEVEL_SYSTEM;
+ for (ExtensionLevelMap::const_iterator it = extension_levels_.begin();
+ it != extension_levels_.end(); ++it) {
+ if (it->second == core_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;
+ }
+}
+
+void PowerApiManager::Shutdown() {
+ // Unregister here rather than in the d'tor; otherwise this call will recreate
+ // the already-deleted ExtensionRegistry.
+ ExtensionRegistry::Get(browser_context_)->RemoveObserver(this);
+ power_save_blocker_.reset();
+}
+
+} // namespace extensions
diff --git a/extensions/browser/api/power/power_api_manager.h b/extensions/browser/api/power/power_api_manager.h
new file mode 100644
index 0000000..477113d
--- /dev/null
+++ b/extensions/browser/api/power/power_api_manager.h
@@ -0,0 +1,98 @@
+// Copyright 2014 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 EXTENSIONS_BROWSER_API_POWER_POWER_API_MANAGER_H_
+#define EXTENSIONS_BROWSER_API_POWER_POWER_API_MANAGER_H_
+
+#include <map>
+#include <string>
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "content/public/browser/power_save_blocker.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/common/api/power.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+// Handles calls made via the chrome.power API. There is a separate instance of
+// this class for each profile, as requests are tracked by extension ID, but a
+// regular and incognito profile will share the same instance.
+// TODO(derat): Move this to power_api.h and rename it to PowerApi.
+class PowerApiManager : public BrowserContextKeyedAPI,
+ public extensions::ExtensionRegistryObserver {
+ public:
+ typedef base::Callback<scoped_ptr<content::PowerSaveBlocker>(
+ content::PowerSaveBlocker::PowerSaveBlockerType,
+ const std::string&)> CreateBlockerFunction;
+
+ static PowerApiManager* Get(content::BrowserContext* context);
+
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<PowerApiManager>* GetFactoryInstance();
+
+ // Adds an extension lock at |level| for |extension_id|, replacing the
+ // extension's existing lock, if any.
+ void AddRequest(const std::string& extension_id,
+ core_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 extensions::ExtensionRegistryObserver.
+ virtual void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason)
+ OVERRIDE;
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<PowerApiManager>;
+
+ explicit PowerApiManager(content::BrowserContext* context);
+ virtual ~PowerApiManager();
+
+ // Updates |power_save_blocker_| and |current_level_| after iterating
+ // over |extension_levels_|.
+ void UpdatePowerSaveBlocker();
+
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "PowerApiManager"; }
+ static const bool kServiceRedirectedInIncognito = true;
+ static const bool kServiceIsCreatedWithBrowserContext = false;
+ virtual void Shutdown() OVERRIDE;
+
+ content::BrowserContext* browser_context_;
+
+ // 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.
+ core_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, core_api::power::Level> ExtensionLevelMap;
+ ExtensionLevelMap extension_levels_;
+
+ DISALLOW_COPY_AND_ASSIGN(PowerApiManager);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_POWER_POWER_API_MANAGER_H_
diff --git a/extensions/browser/api/power/power_api_unittest.cc b/extensions/browser/api/power/power_api_unittest.cc
new file mode 100644
index 0000000..ffb7e44
--- /dev/null
+++ b/extensions/browser/api/power/power_api_unittest.cc
@@ -0,0 +1,288 @@
+// Copyright 2014 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 "extensions/browser/api/power/power_api.h"
+
+#include <deque>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/extensions/extension_function_test_utils.h"
+#include "chrome/test/base/browser_with_test_window_test.h"
+#include "content/public/browser/power_save_blocker.h"
+#include "extensions/browser/api/power/power_api_manager.h"
+#include "extensions/common/extension.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:
+ explicit PowerSaveBlockerStubManager(content::BrowserContext* context)
+ : browser_context_(context),
+ weak_ptr_factory_(this) {
+ // Use base::Unretained since callbacks with return values can't use
+ // weak pointers.
+ PowerApiManager::Get(browser_context_)->SetCreateBlockerFunctionForTesting(
+ base::Bind(&PowerSaveBlockerStubManager::CreateStub,
+ base::Unretained(this)));
+ }
+
+ ~PowerSaveBlockerStubManager() {
+ PowerApiManager::Get(browser_context_)->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);
+ }
+
+ content::BrowserContext* browser_context_;
+
+ // 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(profile()));
+ extension_ = utils::CreateEmptyExtensionWithLocation(
+ extensions::Manifest::UNPACKED);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ extension_ = NULL;
+ manager_.reset();
+ BrowserWithTestWindowTest::TearDown();
+ }
+
+ 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) {
+ scoped_refptr<UIThreadExtensionFunction> function(
+ type == REQUEST ?
+ static_cast<UIThreadExtensionFunction*>(
+ new PowerRequestKeepAwakeFunction) :
+ static_cast<UIThreadExtensionFunction*>(
+ new PowerReleaseKeepAwakeFunction));
+ function->set_extension(extension);
+ return utils::RunFunction(function.get(), args, browser(), utils::NONE);
+ }
+
+ // Send a notification to PowerApiManager saying that |extension| has
+ // been unloaded.
+ void UnloadExtension(extensions::Extension* extension) {
+ PowerApiManager::Get(profile())->OnExtensionUnloaded(
+ profile(), extension, UnloadedExtensionInfo::REASON_UNINSTALL);
+ }
+
+ 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
diff --git a/extensions/common/api/BUILD.gn b/extensions/common/api/BUILD.gn
index 5a8c7db..b15addc 100644
--- a/extensions/common/api/BUILD.gn
+++ b/extensions/common/api/BUILD.gn
@@ -9,6 +9,7 @@ generated_extensions_api("extensions_api") {
"app_runtime.idl",
"dns.idl",
"extensions_manifest_types.json",
+ "power.idl",
"socket.idl",
"sockets_tcp.idl",
"sockets_tcp_server.idl",
diff --git a/extensions/common/api/_api_features.json b/extensions/common/api/_api_features.json
index 084428e..944436e 100644
--- a/extensions/common/api/_api_features.json
+++ b/extensions/common/api/_api_features.json
@@ -40,6 +40,10 @@
"dependencies": ["permission:dns"],
"contexts": ["blessed_extension"]
},
+ "power": {
+ "dependencies": ["permission:power"],
+ "contexts": ["blessed_extension"]
+ },
"runtime": {
"channel": "stable",
"extension_types": ["extension", "legacy_packaged_app", "platform_app"],
diff --git a/extensions/common/api/_permission_features.json b/extensions/common/api/_permission_features.json
index e0a8666..b1653fc 100644
--- a/extensions/common/api/_permission_features.json
+++ b/extensions/common/api/_permission_features.json
@@ -36,6 +36,10 @@
]
}
],
+ "power": {
+ "channel": "stable",
+ "extension_types": [ "extension", "legacy_packaged_app", "platform_app" ]
+ },
// Note: runtime is not actually a permission, but some systems check these
// values to verify restrictions.
"runtime": {
diff --git a/extensions/common/api/api.gyp b/extensions/common/api/api.gyp
index cfb880f..285c96e 100644
--- a/extensions/common/api/api.gyp
+++ b/extensions/common/api/api.gyp
@@ -26,6 +26,7 @@
'app_runtime.idl',
'dns.idl',
'extensions_manifest_types.json',
+ 'power.idl',
'runtime.json',
'socket.idl',
'sockets_tcp.idl',
diff --git a/extensions/common/api/power.idl b/extensions/common/api/power.idl
new file mode 100644
index 0000000..2a25353
--- /dev/null
+++ b/extensions/common/api/power.idl
@@ -0,0 +1,27 @@
+// Copyright 2014 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.
+
+// Use the <code>chrome.power</code> API to override the system's power
+// management features.
+namespace power {
+ [noinline_doc] enum Level {
+ // Prevent the system from sleeping in response to user inactivity.
+ system,
+
+ // Prevent the display from being turned off or dimmed or the system
+ // from sleeping in response to user inactivity.
+ display
+ };
+
+ interface Functions {
+ // Requests that power management be temporarily disabled. |level|
+ // describes the degree to which power management should be disabled.
+ // If a request previously made by the same app is still active, it
+ // will be replaced by the new request.
+ static void requestKeepAwake(Level level);
+
+ // Releases a request previously made via requestKeepAwake().
+ static void releaseKeepAwake();
+ };
+};
diff --git a/extensions/common/permissions/extensions_api_permissions.cc b/extensions/common/permissions/extensions_api_permissions.cc
index 9c71309..8305019 100644
--- a/extensions/common/permissions/extensions_api_permissions.cc
+++ b/extensions/common/permissions/extensions_api_permissions.cc
@@ -25,6 +25,7 @@ std::vector<APIPermissionInfo*> ExtensionsAPIPermissions::GetAllPermissions()
const {
APIPermissionInfo::InitInfo permissions_to_register[] = {
{APIPermission::kDns, "dns"},
+ {APIPermission::kPower, "power"},
// Because warning messages for the "socket" permission vary based
// on the permissions parameters, no message ID or message text is
// specified here. The message ID and text used will be
diff --git a/extensions/extensions.gyp b/extensions/extensions.gyp
index bf75120..79cbe9b 100644
--- a/extensions/extensions.gyp
+++ b/extensions/extensions.gyp
@@ -266,6 +266,10 @@
'browser/api/dns/host_resolver_wrapper.h',
'browser/api/extensions_api_client.cc',
'browser/api/extensions_api_client.h',
+ 'browser/api/power/power_api.cc',
+ 'browser/api/power/power_api.h',
+ 'browser/api/power/power_api_manager.cc',
+ 'browser/api/power/power_api_manager.h',
'browser/api/runtime/runtime_api.cc',
'browser/api/runtime/runtime_api.h',
'browser/api/runtime/runtime_api_delegate.cc',