// 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 "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