1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "extensions/browser/api/power/power_api.h"
6
7#include <deque>
8#include <string>
9
10#include "base/basictypes.h"
11#include "base/memory/ref_counted.h"
12#include "base/memory/scoped_ptr.h"
13#include "base/memory/weak_ptr.h"
14#include "content/public/browser/power_save_blocker.h"
15#include "extensions/browser/api/power/power_api_manager.h"
16#include "extensions/browser/api_test_utils.h"
17#include "extensions/browser/api_unittest.h"
18#include "extensions/common/extension.h"
19#include "extensions/common/test_util.h"
20
21namespace extensions {
22
23namespace {
24
25// Args commonly passed to PowerSaveBlockerStubManager::CallFunction().
26const char kDisplayArgs[] = "[\"display\"]";
27const char kSystemArgs[] = "[\"system\"]";
28const char kEmptyArgs[] = "[]";
29
30// Different actions that can be performed as a result of a
31// PowerSaveBlocker being created or destroyed.
32enum Request {
33  BLOCK_APP_SUSPENSION,
34  UNBLOCK_APP_SUSPENSION,
35  BLOCK_DISPLAY_SLEEP,
36  UNBLOCK_DISPLAY_SLEEP,
37  // Returned by PowerSaveBlockerStubManager::PopFirstRequest() when no
38  // requests are present.
39  NONE,
40};
41
42// Stub implementation of content::PowerSaveBlocker that just runs a
43// callback on destruction.
44class PowerSaveBlockerStub : public content::PowerSaveBlocker {
45 public:
46  explicit PowerSaveBlockerStub(base::Closure unblock_callback)
47      : unblock_callback_(unblock_callback) {
48  }
49
50  virtual ~PowerSaveBlockerStub() {
51    unblock_callback_.Run();
52  }
53
54 private:
55  base::Closure unblock_callback_;
56
57  DISALLOW_COPY_AND_ASSIGN(PowerSaveBlockerStub);
58};
59
60// Manages PowerSaveBlockerStub objects.  Tests can instantiate this class
61// to make PowerApiManager's calls to create PowerSaveBlockers record the
62// actions that would've been performed instead of actually blocking and
63// unblocking power management.
64class PowerSaveBlockerStubManager {
65 public:
66  explicit PowerSaveBlockerStubManager(content::BrowserContext* context)
67      : browser_context_(context),
68        weak_ptr_factory_(this) {
69    // Use base::Unretained since callbacks with return values can't use
70    // weak pointers.
71    PowerApiManager::Get(browser_context_)->SetCreateBlockerFunctionForTesting(
72        base::Bind(&PowerSaveBlockerStubManager::CreateStub,
73                   base::Unretained(this)));
74  }
75
76  ~PowerSaveBlockerStubManager() {
77    PowerApiManager::Get(browser_context_)->SetCreateBlockerFunctionForTesting(
78        PowerApiManager::CreateBlockerFunction());
79  }
80
81  // Removes and returns the first item from |requests_|.  Returns NONE if
82  // |requests_| is empty.
83  Request PopFirstRequest() {
84    if (requests_.empty())
85      return NONE;
86
87    Request request = requests_.front();
88    requests_.pop_front();
89    return request;
90  }
91
92 private:
93  // Creates a new PowerSaveBlockerStub of type |type|.
94  scoped_ptr<content::PowerSaveBlocker> CreateStub(
95      content::PowerSaveBlocker::PowerSaveBlockerType type,
96      const std::string& reason) {
97    Request unblock_request = NONE;
98    switch (type) {
99      case content::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension:
100        requests_.push_back(BLOCK_APP_SUSPENSION);
101        unblock_request = UNBLOCK_APP_SUSPENSION;
102        break;
103      case content::PowerSaveBlocker::kPowerSaveBlockPreventDisplaySleep:
104        requests_.push_back(BLOCK_DISPLAY_SLEEP);
105        unblock_request = UNBLOCK_DISPLAY_SLEEP;
106        break;
107    }
108    return scoped_ptr<content::PowerSaveBlocker>(
109        new PowerSaveBlockerStub(
110            base::Bind(&PowerSaveBlockerStubManager::AppendRequest,
111                       weak_ptr_factory_.GetWeakPtr(),
112                       unblock_request)));
113  }
114
115  void AppendRequest(Request request) {
116    requests_.push_back(request);
117  }
118
119  content::BrowserContext* browser_context_;
120
121  // Requests in chronological order.
122  std::deque<Request> requests_;
123
124  base::WeakPtrFactory<PowerSaveBlockerStubManager> weak_ptr_factory_;
125
126  DISALLOW_COPY_AND_ASSIGN(PowerSaveBlockerStubManager);
127};
128
129}  // namespace
130
131class PowerApiTest : public ApiUnitTest {
132 public:
133  virtual void SetUp() OVERRIDE {
134    ApiUnitTest::SetUp();
135    manager_.reset(new PowerSaveBlockerStubManager(browser_context()));
136  }
137
138  virtual void TearDown() OVERRIDE {
139    manager_.reset();
140    ApiUnitTest::TearDown();
141  }
142
143 protected:
144  // Shorthand for PowerRequestKeepAwakeFunction and
145  // PowerReleaseKeepAwakeFunction.
146  enum FunctionType {
147    REQUEST,
148    RELEASE,
149  };
150
151  // Calls the function described by |type| with |args|, a JSON list of
152  // arguments, on behalf of |extension|.
153  bool CallFunction(FunctionType type,
154                    const std::string& args,
155                    const extensions::Extension* extension) {
156    scoped_refptr<UIThreadExtensionFunction> function(
157        type == REQUEST ?
158        static_cast<UIThreadExtensionFunction*>(
159            new PowerRequestKeepAwakeFunction) :
160        static_cast<UIThreadExtensionFunction*>(
161            new PowerReleaseKeepAwakeFunction));
162    function->set_extension(extension);
163    return api_test_utils::RunFunction(function.get(), args, browser_context());
164  }
165
166  // Send a notification to PowerApiManager saying that |extension| has
167  // been unloaded.
168  void UnloadExtension(const extensions::Extension* extension) {
169    PowerApiManager::Get(browser_context())->OnExtensionUnloaded(
170        browser_context(), extension, UnloadedExtensionInfo::REASON_UNINSTALL);
171  }
172
173  scoped_ptr<PowerSaveBlockerStubManager> manager_;
174};
175
176TEST_F(PowerApiTest, RequestAndRelease) {
177  // Simulate an extension making and releasing a "display" request and a
178  // "system" request.
179  ASSERT_TRUE(CallFunction(REQUEST, kDisplayArgs, extension()));
180  EXPECT_EQ(BLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
181  EXPECT_EQ(NONE, manager_->PopFirstRequest());
182  ASSERT_TRUE(CallFunction(RELEASE, kEmptyArgs, extension()));
183  EXPECT_EQ(UNBLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
184  EXPECT_EQ(NONE, manager_->PopFirstRequest());
185
186  ASSERT_TRUE(CallFunction(REQUEST, kSystemArgs, extension()));
187  EXPECT_EQ(BLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
188  EXPECT_EQ(NONE, manager_->PopFirstRequest());
189  ASSERT_TRUE(CallFunction(RELEASE, kEmptyArgs, extension()));
190  EXPECT_EQ(UNBLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
191  EXPECT_EQ(NONE, manager_->PopFirstRequest());
192}
193
194TEST_F(PowerApiTest, RequestWithoutRelease) {
195  // Simulate an extension calling requestKeepAwake() without calling
196  // releaseKeepAwake().  The override should be automatically removed when
197  // the extension is unloaded.
198  ASSERT_TRUE(CallFunction(REQUEST, kDisplayArgs, extension()));
199  EXPECT_EQ(BLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
200  EXPECT_EQ(NONE, manager_->PopFirstRequest());
201
202  UnloadExtension(extension());
203  EXPECT_EQ(UNBLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
204  EXPECT_EQ(NONE, manager_->PopFirstRequest());
205}
206
207TEST_F(PowerApiTest, ReleaseWithoutRequest) {
208  // Simulate an extension calling releaseKeepAwake() without having
209  // calling requestKeepAwake() earlier.  The call should be ignored.
210  ASSERT_TRUE(CallFunction(RELEASE, kEmptyArgs, extension()));
211  EXPECT_EQ(NONE, manager_->PopFirstRequest());
212}
213
214TEST_F(PowerApiTest, UpgradeRequest) {
215  // Simulate an extension calling requestKeepAwake("system") and then
216  // requestKeepAwake("display").  When the second call is made, a
217  // display-sleep-blocking request should be made before the initial
218  // app-suspension-blocking request is released.
219  ASSERT_TRUE(CallFunction(REQUEST, kSystemArgs, extension()));
220  EXPECT_EQ(BLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
221  EXPECT_EQ(NONE, manager_->PopFirstRequest());
222
223  ASSERT_TRUE(CallFunction(REQUEST, kDisplayArgs, extension()));
224  EXPECT_EQ(BLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
225  EXPECT_EQ(UNBLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
226  EXPECT_EQ(NONE, manager_->PopFirstRequest());
227
228  ASSERT_TRUE(CallFunction(RELEASE, kEmptyArgs, extension()));
229  EXPECT_EQ(UNBLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
230  EXPECT_EQ(NONE, manager_->PopFirstRequest());
231}
232
233TEST_F(PowerApiTest, DowngradeRequest) {
234  // Simulate an extension calling requestKeepAwake("display") and then
235  // requestKeepAwake("system").  When the second call is made, an
236  // app-suspension-blocking request should be made before the initial
237  // display-sleep-blocking request is released.
238  ASSERT_TRUE(CallFunction(REQUEST, kDisplayArgs, extension()));
239  EXPECT_EQ(BLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
240  EXPECT_EQ(NONE, manager_->PopFirstRequest());
241
242  ASSERT_TRUE(CallFunction(REQUEST, kSystemArgs, extension()));
243  EXPECT_EQ(BLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
244  EXPECT_EQ(UNBLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
245  EXPECT_EQ(NONE, manager_->PopFirstRequest());
246
247  ASSERT_TRUE(CallFunction(RELEASE, kEmptyArgs, extension()));
248  EXPECT_EQ(UNBLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
249  EXPECT_EQ(NONE, manager_->PopFirstRequest());
250}
251
252TEST_F(PowerApiTest, MultipleExtensions) {
253  // Simulate an extension blocking the display from sleeping.
254  ASSERT_TRUE(CallFunction(REQUEST, kDisplayArgs, extension()));
255  EXPECT_EQ(BLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
256  EXPECT_EQ(NONE, manager_->PopFirstRequest());
257
258  // Create a second extension that blocks system suspend.  No additional
259  // PowerSaveBlocker is needed; the blocker from the first extension
260  // already covers the behavior requested by the second extension.
261  scoped_refptr<Extension> extension2(test_util::CreateEmptyExtension("id2"));
262  ASSERT_TRUE(CallFunction(REQUEST, kSystemArgs, extension2.get()));
263  EXPECT_EQ(NONE, manager_->PopFirstRequest());
264
265  // When the first extension is unloaded, a new app-suspension blocker
266  // should be created before the display-sleep blocker is destroyed.
267  UnloadExtension(extension());
268  EXPECT_EQ(BLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
269  EXPECT_EQ(UNBLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
270  EXPECT_EQ(NONE, manager_->PopFirstRequest());
271
272  // Make the first extension block display-sleep again.
273  ASSERT_TRUE(CallFunction(REQUEST, kDisplayArgs, extension()));
274  EXPECT_EQ(BLOCK_DISPLAY_SLEEP, manager_->PopFirstRequest());
275  EXPECT_EQ(UNBLOCK_APP_SUSPENSION, manager_->PopFirstRequest());
276  EXPECT_EQ(NONE, manager_->PopFirstRequest());
277}
278
279}  // namespace extensions
280