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