diff options
Diffstat (limited to 'chromium/extensions/browser/api/power')
-rw-r--r-- | chromium/extensions/browser/api/power/OWNERS | 1 | ||||
-rw-r--r-- | chromium/extensions/browser/api/power/power_api.cc | 131 | ||||
-rw-r--r-- | chromium/extensions/browser/api/power/power_api.h | 122 | ||||
-rw-r--r-- | chromium/extensions/browser/api/power/power_api_unittest.cc | 278 |
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 |