summaryrefslogtreecommitdiff
path: root/chromium/extensions/browser/api/power
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/extensions/browser/api/power')
-rw-r--r--chromium/extensions/browser/api/power/OWNERS1
-rw-r--r--chromium/extensions/browser/api/power/power_api.cc131
-rw-r--r--chromium/extensions/browser/api/power/power_api.h122
-rw-r--r--chromium/extensions/browser/api/power/power_api_unittest.cc278
4 files changed, 532 insertions, 0 deletions
diff --git a/chromium/extensions/browser/api/power/OWNERS b/chromium/extensions/browser/api/power/OWNERS
new file mode 100644
index 00000000000..3c97e54df02
--- /dev/null
+++ b/chromium/extensions/browser/api/power/OWNERS
@@ -0,0 +1 @@
+derat@chromium.org
diff --git a/chromium/extensions/browser/api/power/power_api.cc b/chromium/extensions/browser/api/power/power_api.cc
new file mode 100644
index 00000000000..4c9ed0dcd98
--- /dev/null
+++ b/chromium/extensions/browser/api/power/power_api.cc
@@ -0,0 +1,131 @@
+// 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 "base/bind.h"
+#include "base/lazy_instance.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/api/power.h"
+#include "extensions/common/extension.h"
+
+namespace extensions {
+
+namespace {
+
+const char kPowerSaveBlockerDescription[] = "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;
+}
+
+base::LazyInstance<BrowserContextKeyedAPIFactory<PowerAPI>> g_factory =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+bool PowerRequestKeepAwakeFunction::RunSync() {
+ 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);
+ PowerAPI::Get(browser_context())->AddRequest(extension_id(), params->level);
+ return true;
+}
+
+bool PowerReleaseKeepAwakeFunction::RunSync() {
+ PowerAPI::Get(browser_context())->RemoveRequest(extension_id());
+ return true;
+}
+
+// static
+PowerAPI* PowerAPI::Get(content::BrowserContext* context) {
+ return BrowserContextKeyedAPIFactory<PowerAPI>::Get(context);
+}
+
+// static
+BrowserContextKeyedAPIFactory<PowerAPI>* PowerAPI::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+void PowerAPI::AddRequest(const std::string& extension_id,
+ api::power::Level level) {
+ extension_levels_[extension_id] = level;
+ UpdatePowerSaveBlocker();
+}
+
+void PowerAPI::RemoveRequest(const std::string& extension_id) {
+ extension_levels_.erase(extension_id);
+ UpdatePowerSaveBlocker();
+}
+
+void PowerAPI::SetCreateBlockerFunctionForTesting(
+ CreateBlockerFunction function) {
+ create_blocker_function_ =
+ !function.is_null() ? function
+ : base::Bind(&content::PowerSaveBlocker::Create);
+}
+
+void PowerAPI::OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) {
+ RemoveRequest(extension->id());
+ UpdatePowerSaveBlocker();
+}
+
+PowerAPI::PowerAPI(content::BrowserContext* context)
+ : browser_context_(context),
+ create_blocker_function_(base::Bind(&content::PowerSaveBlocker::Create)),
+ current_level_(api::power::LEVEL_SYSTEM) {
+ ExtensionRegistry::Get(browser_context_)->AddObserver(this);
+}
+
+PowerAPI::~PowerAPI() {
+}
+
+void PowerAPI::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,
+ content::PowerSaveBlocker::kReasonOther,
+ kPowerSaveBlockerDescription));
+ power_save_blocker_.swap(new_blocker);
+ current_level_ = new_level;
+ }
+}
+
+void PowerAPI::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/chromium/extensions/browser/api/power/power_api.h b/chromium/extensions/browser/api/power/power_api.h
new file mode 100644
index 00000000000..313151d7757
--- /dev/null
+++ b/chromium/extensions/browser/api/power/power_api.h
@@ -0,0 +1,122 @@
+// 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 <map>
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.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_function.h"
+#include "extensions/browser/extension_registry_observer.h"
+#include "extensions/common/api/power.h"
+
+namespace content {
+class BrowserContext;
+}
+
+namespace extensions {
+
+// Implementation of the chrome.power.requestKeepAwake API.
+class PowerRequestKeepAwakeFunction : public SyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("power.requestKeepAwake", POWER_REQUESTKEEPAWAKE)
+
+ protected:
+ ~PowerRequestKeepAwakeFunction() override {}
+
+ // ExtensionFunction:
+ bool RunSync() override;
+};
+
+// Implementation of the chrome.power.releaseKeepAwake API.
+class PowerReleaseKeepAwakeFunction : public SyncExtensionFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("power.releaseKeepAwake", POWER_RELEASEKEEPAWAKE)
+
+ protected:
+ ~PowerReleaseKeepAwakeFunction() override {}
+
+ // ExtensionFunction:
+ bool RunSync() override;
+};
+
+// 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.
+class PowerAPI : public BrowserContextKeyedAPI,
+ public extensions::ExtensionRegistryObserver {
+ public:
+ typedef base::Callback<scoped_ptr<content::PowerSaveBlocker>(
+ content::PowerSaveBlocker::PowerSaveBlockerType,
+ content::PowerSaveBlocker::Reason,
+ const std::string&)> CreateBlockerFunction;
+
+ static PowerAPI* Get(content::BrowserContext* context);
+
+ // BrowserContextKeyedAPI implementation.
+ static BrowserContextKeyedAPIFactory<PowerAPI>* 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, 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.
+ void OnExtensionUnloaded(content::BrowserContext* browser_context,
+ const Extension* extension,
+ UnloadedExtensionInfo::Reason reason) override;
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<PowerAPI>;
+
+ explicit PowerAPI(content::BrowserContext* context);
+ ~PowerAPI() override;
+
+ // Updates |power_save_blocker_| and |current_level_| after iterating
+ // over |extension_levels_|.
+ void UpdatePowerSaveBlocker();
+
+ // BrowserContextKeyedAPI implementation.
+ static const char* service_name() { return "PowerAPI"; }
+ static const bool kServiceRedirectedInIncognito = true;
+ static const bool kServiceIsCreatedWithBrowserContext = false;
+ 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.
+ 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(PowerAPI);
+};
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_API_POWER_POWER_API_H_
diff --git a/chromium/extensions/browser/api/power/power_api_unittest.cc b/chromium/extensions/browser/api/power/power_api_unittest.cc
new file mode 100644
index 00000000000..693abd10b04
--- /dev/null
+++ b/chromium/extensions/browser/api/power/power_api_unittest.cc
@@ -0,0 +1,278 @@
+// 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/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "content/public/browser/power_save_blocker.h"
+#include "extensions/browser/api_test_utils.h"
+#include "extensions/browser/api_unittest.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/test_util.h"
+
+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) {
+ }
+
+ ~PowerSaveBlockerStub() override { unblock_callback_.Run(); }
+
+ private:
+ base::Closure unblock_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(PowerSaveBlockerStub);
+};
+
+// Manages PowerSaveBlockerStub objects. Tests can instantiate this class
+// to make PowerAPI'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.
+ PowerAPI::Get(browser_context_)
+ ->SetCreateBlockerFunctionForTesting(base::Bind(
+ &PowerSaveBlockerStubManager::CreateStub, base::Unretained(this)));
+ }
+
+ ~PowerSaveBlockerStubManager() {
+ PowerAPI::Get(browser_context_)
+ ->SetCreateBlockerFunctionForTesting(PowerAPI::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,
+ content::PowerSaveBlocker::Reason reason,
+ const std::string& description) {
+ 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 ApiUnitTest {
+ public:
+ void SetUp() override {
+ ApiUnitTest::SetUp();
+ manager_.reset(new PowerSaveBlockerStubManager(browser_context()));
+ }
+
+ void TearDown() override {
+ manager_.reset();
+ ApiUnitTest::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,
+ const extensions::Extension* extension) {
+ scoped_refptr<UIThreadExtensionFunction> function(
+ type == REQUEST ?
+ static_cast<UIThreadExtensionFunction*>(
+ new PowerRequestKeepAwakeFunction) :
+ static_cast<UIThreadExtensionFunction*>(
+ new PowerReleaseKeepAwakeFunction));
+ function->set_extension(extension);
+ return api_test_utils::RunFunction(function.get(), args, browser_context());
+ }
+
+ // Send a notification to PowerAPI saying that |extension| has
+ // been unloaded.
+ void UnloadExtension(const extensions::Extension* extension) {
+ PowerAPI::Get(browser_context())
+ ->OnExtensionUnloaded(browser_context(), extension,
+ UnloadedExtensionInfo::REASON_UNINSTALL);
+ }
+
+ scoped_ptr<PowerSaveBlockerStubManager> manager_;
+};
+
+TEST_F(PowerAPITest, RequestAndRelease) {
+ // Simulate an extension making and releasing a "display" request and a
+ // "system" request.
+ ASSERT_TRUE(CallFunction(REQUEST, kDisplayArgs, extension()));
+ EXPECT_EQ(BLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+ ASSERT_TRUE(CallFunction(RELEASE, kEmptyArgs, extension()));
+ EXPECT_EQ(UNBLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+
+ ASSERT_TRUE(CallFunction(REQUEST, kSystemArgs, extension()));
+ EXPECT_EQ(BLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+ ASSERT_TRUE(CallFunction(RELEASE, kEmptyArgs, extension()));
+ 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()));
+ EXPECT_EQ(BLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+
+ UnloadExtension(extension());
+ 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()));
+ 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()));
+ EXPECT_EQ(BLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+
+ ASSERT_TRUE(CallFunction(REQUEST, kDisplayArgs, extension()));
+ 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()));
+ 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()));
+ EXPECT_EQ(BLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+
+ ASSERT_TRUE(CallFunction(REQUEST, kSystemArgs, extension()));
+ 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()));
+ 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()));
+ 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_refptr<Extension> extension2(test_util::CreateEmptyExtension("id2"));
+ 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());
+ 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()));
+ EXPECT_EQ(BLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
+ EXPECT_EQ(UNBLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
+ EXPECT_EQ(NONE, manager_->PopFirstRequest());
+}
+
+} // namespace extensions