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/app_window/app_window_geometry_cache.h" 6 7#include "base/files/file_path.h" 8#include "base/memory/scoped_ptr.h" 9#include "base/prefs/mock_pref_change_callback.h" 10#include "base/prefs/pref_service_factory.h" 11#include "base/prefs/testing_pref_store.h" 12#include "base/strings/string_number_conversions.h" 13#include "components/pref_registry/pref_registry_syncable.h" 14#include "content/public/test/test_browser_context.h" 15#include "content/public/test/test_browser_thread.h" 16#include "content/public/test/test_utils.h" 17#include "extensions/browser/extension_pref_value_map.h" 18#include "extensions/browser/extension_prefs.h" 19#include "extensions/browser/extensions_test.h" 20#include "extensions/browser/null_app_sorting.h" 21#include "extensions/common/extension_builder.h" 22#include "extensions/common/value_builder.h" 23#include "testing/gtest/include/gtest/gtest.h" 24 25using content::BrowserThread; 26 27namespace extensions { 28 29namespace { 30const char kWindowId[] = "windowid"; 31const char kWindowId2[] = "windowid2"; 32 33// Create a very simple extension with id. 34scoped_refptr<Extension> CreateExtension(const std::string& id) { 35 return ExtensionBuilder() 36 .SetManifest(DictionaryBuilder().Set("name", "test").Set( 37 "version", "0.1")) 38 .SetID(id) 39 .Build(); 40} 41 42} // namespace 43 44// Base class for tests. 45class AppWindowGeometryCacheTest : public ExtensionsTest { 46 public: 47 AppWindowGeometryCacheTest() 48 : ui_thread_(BrowserThread::UI, &ui_message_loop_) {} 49 50 // testing::Test overrides: 51 virtual void SetUp() OVERRIDE; 52 virtual void TearDown() OVERRIDE; 53 54 void AddGeometryAndLoadExtension(const std::string& extension_id, 55 const std::string& window_id, 56 const gfx::Rect& bounds, 57 const gfx::Rect& screen_bounds, 58 ui::WindowShowState state); 59 60 // Spins the UI threads' message loops to make sure any task 61 // posted to sync the geometry to the value store gets a chance to run. 62 void WaitForSync(); 63 64 void LoadExtension(const std::string& extension_id); 65 void UnloadExtension(const std::string& extension_id); 66 67 // Creates and adds an extension with associated prefs. Returns the extension 68 // ID. 69 std::string AddExtensionWithPrefs(const std::string& name); 70 71 protected: 72 base::MessageLoopForUI ui_message_loop_; 73 content::TestBrowserThread ui_thread_; 74 scoped_ptr<ExtensionPrefValueMap> extension_pref_value_map_; 75 scoped_ptr<PrefService> pref_service_; 76 scoped_ptr<ExtensionPrefs> extension_prefs_; 77 scoped_ptr<AppWindowGeometryCache> cache_; 78}; 79 80void AppWindowGeometryCacheTest::SetUp() { 81 ExtensionsTest::SetUp(); 82 83 // Set up all the dependencies of ExtensionPrefs. 84 extension_pref_value_map_.reset(new ExtensionPrefValueMap); 85 base::PrefServiceFactory factory; 86 factory.set_user_prefs(new TestingPrefStore); 87 factory.set_extension_prefs(new TestingPrefStore); 88 user_prefs::PrefRegistrySyncable* pref_registry = 89 new user_prefs::PrefRegistrySyncable; 90 // Prefs should be registered before the PrefService is created. 91 ExtensionPrefs::RegisterProfilePrefs(pref_registry); 92 pref_service_ = factory.Create(pref_registry).Pass(); 93 94 extension_prefs_.reset(ExtensionPrefs::Create( 95 pref_service_.get(), 96 browser_context()->GetPath().AppendASCII("Extensions"), 97 extension_pref_value_map_.get(), 98 scoped_ptr<AppSorting>(new NullAppSorting), 99 false /* extensions_disabled */, 100 std::vector<ExtensionPrefsObserver*>())); 101 102 cache_.reset( 103 new AppWindowGeometryCache(browser_context(), extension_prefs_.get())); 104 cache_->SetSyncDelayForTests(0); 105} 106 107void AppWindowGeometryCacheTest::TearDown() { 108 cache_.reset(); 109 extension_prefs_.reset(); 110 pref_service_.reset(); 111 extension_pref_value_map_.reset(); 112 113 ExtensionsTest::TearDown(); 114} 115 116void AppWindowGeometryCacheTest::AddGeometryAndLoadExtension( 117 const std::string& extension_id, 118 const std::string& window_id, 119 const gfx::Rect& bounds, 120 const gfx::Rect& screen_bounds, 121 ui::WindowShowState state) { 122 scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue); 123 base::DictionaryValue* value = new base::DictionaryValue; 124 value->SetInteger("x", bounds.x()); 125 value->SetInteger("y", bounds.y()); 126 value->SetInteger("w", bounds.width()); 127 value->SetInteger("h", bounds.height()); 128 value->SetInteger("screen_bounds_x", screen_bounds.x()); 129 value->SetInteger("screen_bounds_y", screen_bounds.y()); 130 value->SetInteger("screen_bounds_w", screen_bounds.width()); 131 value->SetInteger("screen_bounds_h", screen_bounds.height()); 132 value->SetInteger("state", state); 133 dict->SetWithoutPathExpansion(window_id, value); 134 extension_prefs_->SetGeometryCache(extension_id, dict.Pass()); 135 LoadExtension(extension_id); 136} 137 138void AppWindowGeometryCacheTest::WaitForSync() { 139 content::RunAllPendingInMessageLoop(); 140} 141 142void AppWindowGeometryCacheTest::LoadExtension( 143 const std::string& extension_id) { 144 cache_->LoadGeometryFromStorage(extension_id); 145 WaitForSync(); 146} 147 148void AppWindowGeometryCacheTest::UnloadExtension( 149 const std::string& extension_id) { 150 scoped_refptr<Extension> extension = CreateExtension(extension_id); 151 cache_->OnExtensionUnloaded(browser_context(), 152 extension.get(), 153 UnloadedExtensionInfo::REASON_DISABLE); 154 WaitForSync(); 155} 156 157std::string AppWindowGeometryCacheTest::AddExtensionWithPrefs( 158 const std::string& name) { 159 // Generate the extension with a path based on the name so that extensions 160 // with different names will have different IDs. 161 base::FilePath path = 162 browser_context()->GetPath().AppendASCII("Extensions").AppendASCII(name); 163 scoped_refptr<Extension> extension = 164 ExtensionBuilder() 165 .SetManifest( 166 DictionaryBuilder().Set("name", "test").Set("version", "0.1")) 167 .SetPath(path) 168 .Build(); 169 170 extension_prefs_->OnExtensionInstalled( 171 extension.get(), 172 Extension::ENABLED, 173 syncer::StringOrdinal::CreateInitialOrdinal(), 174 std::string()); 175 return extension->id(); 176} 177 178// Test getting geometry from an empty store. 179TEST_F(AppWindowGeometryCacheTest, GetGeometryEmptyStore) { 180 const std::string extension_id = AddExtensionWithPrefs("ext1"); 181 ASSERT_FALSE(cache_->GetGeometry(extension_id, kWindowId, NULL, NULL, NULL)); 182} 183 184// Test getting geometry for an unknown extension. 185TEST_F(AppWindowGeometryCacheTest, GetGeometryUnkownExtension) { 186 const std::string extension_id1 = AddExtensionWithPrefs("ext1"); 187 const std::string extension_id2 = AddExtensionWithPrefs("ext2"); 188 AddGeometryAndLoadExtension(extension_id1, 189 kWindowId, 190 gfx::Rect(4, 5, 31, 43), 191 gfx::Rect(0, 0, 1600, 900), 192 ui::SHOW_STATE_NORMAL); 193 ASSERT_FALSE(cache_->GetGeometry(extension_id2, kWindowId, NULL, NULL, NULL)); 194} 195 196// Test getting geometry for an unknown window in a known extension. 197TEST_F(AppWindowGeometryCacheTest, GetGeometryUnkownWindow) { 198 const std::string extension_id = AddExtensionWithPrefs("ext1"); 199 AddGeometryAndLoadExtension(extension_id, 200 kWindowId, 201 gfx::Rect(4, 5, 31, 43), 202 gfx::Rect(0, 0, 1600, 900), 203 ui::SHOW_STATE_NORMAL); 204 ASSERT_FALSE(cache_->GetGeometry(extension_id, kWindowId2, NULL, NULL, NULL)); 205} 206 207// Test that loading geometry, screen_bounds and state from the store works 208// correctly. 209TEST_F(AppWindowGeometryCacheTest, GetGeometryAndStateFromStore) { 210 const std::string extension_id = AddExtensionWithPrefs("ext1"); 211 gfx::Rect bounds(4, 5, 31, 43); 212 gfx::Rect screen_bounds(0, 0, 1600, 900); 213 ui::WindowShowState state = ui::SHOW_STATE_NORMAL; 214 AddGeometryAndLoadExtension( 215 extension_id, kWindowId, bounds, screen_bounds, state); 216 gfx::Rect new_bounds; 217 gfx::Rect new_screen_bounds; 218 ui::WindowShowState new_state = ui::SHOW_STATE_DEFAULT; 219 ASSERT_TRUE(cache_->GetGeometry( 220 extension_id, kWindowId, &new_bounds, &new_screen_bounds, &new_state)); 221 ASSERT_EQ(bounds, new_bounds); 222 ASSERT_EQ(screen_bounds, new_screen_bounds); 223 ASSERT_EQ(state, new_state); 224} 225 226// Test corrupt bounds will not be loaded. 227TEST_F(AppWindowGeometryCacheTest, CorruptBounds) { 228 const std::string extension_id = AddExtensionWithPrefs("ext1"); 229 gfx::Rect bounds; 230 gfx::Rect screen_bounds(0, 0, 1600, 900); 231 ui::WindowShowState state = ui::SHOW_STATE_NORMAL; 232 AddGeometryAndLoadExtension( 233 extension_id, kWindowId, bounds, screen_bounds, state); 234 gfx::Rect new_bounds; 235 gfx::Rect new_screen_bounds; 236 ui::WindowShowState new_state = ui::SHOW_STATE_DEFAULT; 237 ASSERT_FALSE(cache_->GetGeometry( 238 extension_id, kWindowId, &new_bounds, &new_screen_bounds, &new_state)); 239 ASSERT_TRUE(new_bounds.IsEmpty()); 240 ASSERT_TRUE(new_screen_bounds.IsEmpty()); 241 ASSERT_EQ(new_state, ui::SHOW_STATE_DEFAULT); 242} 243 244// Test corrupt screen bounds will not be loaded. 245TEST_F(AppWindowGeometryCacheTest, CorruptScreenBounds) { 246 const std::string extension_id = AddExtensionWithPrefs("ext1"); 247 gfx::Rect bounds(4, 5, 31, 43); 248 gfx::Rect screen_bounds; 249 ui::WindowShowState state = ui::SHOW_STATE_NORMAL; 250 AddGeometryAndLoadExtension( 251 extension_id, kWindowId, bounds, screen_bounds, state); 252 gfx::Rect new_bounds; 253 gfx::Rect new_screen_bounds; 254 ui::WindowShowState new_state = ui::SHOW_STATE_DEFAULT; 255 ASSERT_FALSE(cache_->GetGeometry( 256 extension_id, kWindowId, &new_bounds, &new_screen_bounds, &new_state)); 257 ASSERT_TRUE(new_bounds.IsEmpty()); 258 ASSERT_TRUE(new_screen_bounds.IsEmpty()); 259 ASSERT_EQ(new_state, ui::SHOW_STATE_DEFAULT); 260} 261 262// Test corrupt state will not be loaded. 263TEST_F(AppWindowGeometryCacheTest, CorruptState) { 264 const std::string extension_id = AddExtensionWithPrefs("ext1"); 265 gfx::Rect bounds(4, 5, 31, 43); 266 gfx::Rect screen_bounds(0, 0, 1600, 900); 267 ui::WindowShowState state = ui::SHOW_STATE_DEFAULT; 268 AddGeometryAndLoadExtension( 269 extension_id, kWindowId, bounds, screen_bounds, state); 270 gfx::Rect new_bounds; 271 gfx::Rect new_screen_bounds; 272 ui::WindowShowState new_state = ui::SHOW_STATE_DEFAULT; 273 ASSERT_FALSE(cache_->GetGeometry( 274 extension_id, kWindowId, &new_bounds, &new_screen_bounds, &new_state)); 275 ASSERT_TRUE(new_bounds.IsEmpty()); 276 ASSERT_TRUE(new_screen_bounds.IsEmpty()); 277 ASSERT_EQ(new_state, ui::SHOW_STATE_DEFAULT); 278} 279 280// Test saving geometry, screen_bounds and state to the cache and state store, 281// and reading it back. 282TEST_F(AppWindowGeometryCacheTest, SaveGeometryAndStateToStore) { 283 const std::string extension_id = AddExtensionWithPrefs("ext1"); 284 const std::string window_id(kWindowId); 285 286 // inform cache of extension 287 LoadExtension(extension_id); 288 289 // update geometry stored in cache 290 gfx::Rect bounds(4, 5, 31, 43); 291 gfx::Rect screen_bounds(0, 0, 1600, 900); 292 ui::WindowShowState state = ui::SHOW_STATE_NORMAL; 293 cache_->SaveGeometry(extension_id, window_id, bounds, screen_bounds, state); 294 295 // make sure that immediately reading back geometry works 296 gfx::Rect new_bounds; 297 gfx::Rect new_screen_bounds; 298 ui::WindowShowState new_state = ui::SHOW_STATE_DEFAULT; 299 ASSERT_TRUE(cache_->GetGeometry( 300 extension_id, window_id, &new_bounds, &new_screen_bounds, &new_state)); 301 ASSERT_EQ(bounds, new_bounds); 302 ASSERT_EQ(screen_bounds, new_screen_bounds); 303 ASSERT_EQ(state, new_state); 304 305 // unload extension to force cache to save data to the state store 306 UnloadExtension(extension_id); 307 308 // check if geometry got stored correctly in the state store 309 const base::DictionaryValue* dict = 310 extension_prefs_->GetGeometryCache(extension_id); 311 ASSERT_TRUE(dict); 312 313 ASSERT_TRUE(dict->HasKey(window_id)); 314 int v; 315 ASSERT_TRUE(dict->GetInteger(window_id + ".x", &v)); 316 ASSERT_EQ(bounds.x(), v); 317 ASSERT_TRUE(dict->GetInteger(window_id + ".y", &v)); 318 ASSERT_EQ(bounds.y(), v); 319 ASSERT_TRUE(dict->GetInteger(window_id + ".w", &v)); 320 ASSERT_EQ(bounds.width(), v); 321 ASSERT_TRUE(dict->GetInteger(window_id + ".h", &v)); 322 ASSERT_EQ(bounds.height(), v); 323 ASSERT_TRUE(dict->GetInteger(window_id + ".screen_bounds_x", &v)); 324 ASSERT_EQ(screen_bounds.x(), v); 325 ASSERT_TRUE(dict->GetInteger(window_id + ".screen_bounds_y", &v)); 326 ASSERT_EQ(screen_bounds.y(), v); 327 ASSERT_TRUE(dict->GetInteger(window_id + ".screen_bounds_w", &v)); 328 ASSERT_EQ(screen_bounds.width(), v); 329 ASSERT_TRUE(dict->GetInteger(window_id + ".screen_bounds_h", &v)); 330 ASSERT_EQ(screen_bounds.height(), v); 331 ASSERT_TRUE(dict->GetInteger(window_id + ".state", &v)); 332 ASSERT_EQ(state, v); 333 334 // reload extension 335 LoadExtension(extension_id); 336 // and make sure the geometry got reloaded properly too 337 ASSERT_TRUE(cache_->GetGeometry( 338 extension_id, window_id, &new_bounds, &new_screen_bounds, &new_state)); 339 ASSERT_EQ(bounds, new_bounds); 340 ASSERT_EQ(screen_bounds, new_screen_bounds); 341 ASSERT_EQ(state, new_state); 342} 343 344// Tests that we won't do writes to the state store for SaveGeometry calls 345// which don't change the state we already have. 346TEST_F(AppWindowGeometryCacheTest, NoDuplicateWrites) { 347 using testing::_; 348 using testing::Mock; 349 350 const std::string extension_id = AddExtensionWithPrefs("ext1"); 351 gfx::Rect bounds1(100, 200, 300, 400); 352 gfx::Rect bounds2(200, 400, 600, 800); 353 gfx::Rect bounds2_duplicate(200, 400, 600, 800); 354 355 gfx::Rect screen_bounds1(0, 0, 1600, 900); 356 gfx::Rect screen_bounds2(0, 0, 1366, 768); 357 gfx::Rect screen_bounds2_duplicate(0, 0, 1366, 768); 358 359 MockPrefChangeCallback observer(pref_service_.get()); 360 PrefChangeRegistrar registrar; 361 registrar.Init(pref_service_.get()); 362 registrar.Add("extensions.settings", observer.GetCallback()); 363 364 // Write the first bounds - it should do > 0 writes. 365 EXPECT_CALL(observer, OnPreferenceChanged(_)); 366 cache_->SaveGeometry( 367 extension_id, kWindowId, bounds1, screen_bounds1, ui::SHOW_STATE_NORMAL); 368 WaitForSync(); 369 Mock::VerifyAndClearExpectations(&observer); 370 371 // Write a different bounds - it should also do > 0 writes. 372 EXPECT_CALL(observer, OnPreferenceChanged(_)); 373 cache_->SaveGeometry( 374 extension_id, kWindowId, bounds2, screen_bounds1, ui::SHOW_STATE_NORMAL); 375 WaitForSync(); 376 Mock::VerifyAndClearExpectations(&observer); 377 378 // Write a different screen bounds - it should also do > 0 writes. 379 EXPECT_CALL(observer, OnPreferenceChanged(_)); 380 cache_->SaveGeometry( 381 extension_id, kWindowId, bounds2, screen_bounds2, ui::SHOW_STATE_NORMAL); 382 WaitForSync(); 383 Mock::VerifyAndClearExpectations(&observer); 384 385 // Write a different state - it should also do > 0 writes. 386 EXPECT_CALL(observer, OnPreferenceChanged(_)); 387 cache_->SaveGeometry(extension_id, 388 kWindowId, 389 bounds2, 390 screen_bounds2, 391 ui::SHOW_STATE_MAXIMIZED); 392 WaitForSync(); 393 Mock::VerifyAndClearExpectations(&observer); 394 395 // Write a bounds, screen bounds and state that's a duplicate of what we 396 // already have. This should not do any writes. 397 EXPECT_CALL(observer, OnPreferenceChanged(_)).Times(0); 398 cache_->SaveGeometry(extension_id, 399 kWindowId, 400 bounds2_duplicate, 401 screen_bounds2_duplicate, 402 ui::SHOW_STATE_MAXIMIZED); 403 WaitForSync(); 404 Mock::VerifyAndClearExpectations(&observer); 405} 406 407// Tests that no more than kMaxCachedWindows windows will be cached. 408TEST_F(AppWindowGeometryCacheTest, MaxWindows) { 409 const std::string extension_id = AddExtensionWithPrefs("ext1"); 410 // inform cache of extension 411 LoadExtension(extension_id); 412 413 gfx::Rect bounds(4, 5, 31, 43); 414 gfx::Rect screen_bounds(0, 0, 1600, 900); 415 for (size_t i = 0; i < AppWindowGeometryCache::kMaxCachedWindows + 1; ++i) { 416 std::string window_id = "window_" + base::IntToString(i); 417 cache_->SaveGeometry( 418 extension_id, window_id, bounds, screen_bounds, ui::SHOW_STATE_NORMAL); 419 } 420 421 // The first added window should no longer have cached geometry. 422 EXPECT_FALSE(cache_->GetGeometry(extension_id, "window_0", NULL, NULL, NULL)); 423 // All other windows should still exist. 424 for (size_t i = 1; i < AppWindowGeometryCache::kMaxCachedWindows + 1; ++i) { 425 std::string window_id = "window_" + base::IntToString(i); 426 EXPECT_TRUE(cache_->GetGeometry(extension_id, window_id, NULL, NULL, NULL)); 427 } 428} 429 430} // namespace extensions 431