diff options
author | derat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-03 07:54:08 +0000 |
---|---|---|
committer | derat@chromium.org <derat@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-03 07:54:08 +0000 |
commit | 23c371de25c5e981599dcc0f6c381842d05ae80d (patch) | |
tree | 4af9eeb75ea21bf6de15e2a9631e1088018fec30 /extensions | |
parent | 9816ff759cec9ef4b8774b649c695cd9d51ade80 (diff) | |
download | chromium_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/DEPS | 1 | ||||
-rw-r--r-- | extensions/browser/api/power/OWNERS | 1 | ||||
-rw-r--r-- | extensions/browser/api/power/power_api.cc | 27 | ||||
-rw-r--r-- | extensions/browser/api/power/power_api.h | 38 | ||||
-rw-r--r-- | extensions/browser/api/power/power_api_manager.cc | 114 | ||||
-rw-r--r-- | extensions/browser/api/power/power_api_manager.h | 98 | ||||
-rw-r--r-- | extensions/browser/api/power/power_api_unittest.cc | 288 | ||||
-rw-r--r-- | extensions/common/api/BUILD.gn | 1 | ||||
-rw-r--r-- | extensions/common/api/_api_features.json | 4 | ||||
-rw-r--r-- | extensions/common/api/_permission_features.json | 4 | ||||
-rw-r--r-- | extensions/common/api/api.gyp | 1 | ||||
-rw-r--r-- | extensions/common/api/power.idl | 27 | ||||
-rw-r--r-- | extensions/common/permissions/extensions_api_permissions.cc | 1 | ||||
-rw-r--r-- | extensions/extensions.gyp | 4 |
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', |