1 // Copyright (c) 2012 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 "apps/shell_window_geometry_cache.h" 6 #include "base/memory/scoped_ptr.h" 7 #include "base/prefs/mock_pref_change_callback.h" 8 #include "base/strings/string_number_conversions.h" 9 #include "chrome/browser/extensions/extension_prefs.h" 10 #include "chrome/browser/extensions/test_extension_prefs.h" 11 #include "chrome/test/base/testing_profile.h" 12 #include "content/public/test/test_browser_thread.h" 13 #include "content/public/test/test_utils.h" 14 #include "testing/gtest/include/gtest/gtest.h" 15 16 namespace { 17 static const char kWindowId[] = "windowid"; 18 static const char kWindowId2[] = "windowid2"; 19 20 const char kWindowGeometryKey[] = "window_geometry"; 21 } // namespace 22 23 using content::BrowserThread; 24 25 namespace apps { 26 27 // Base class for tests. 28 class ShellWindowGeometryCacheTest : public testing::Test { 29 public: 30 ShellWindowGeometryCacheTest() : 31 ui_thread_(BrowserThread::UI, &ui_message_loop_) { 32 prefs_.reset(new extensions::TestExtensionPrefs( 33 ui_message_loop_.message_loop_proxy().get())); 34 cache_.reset(new ShellWindowGeometryCache(&profile_, prefs_->prefs())); 35 cache_->SetSyncDelayForTests(0); 36 } 37 38 void AddGeometryAndLoadExtension( 39 const std::string& extension_id, 40 const std::string& window_id, 41 const gfx::Rect& bounds, 42 const gfx::Rect& screen_bounds, 43 ui::WindowShowState state); 44 45 // Spins the UI threads' message loops to make sure any task 46 // posted to sync the geometry to the value store gets a chance to run. 47 void WaitForSync(); 48 49 void LoadExtension(const std::string& extension_id); 50 void UnloadExtension(const std::string& extension_id); 51 52 protected: 53 TestingProfile profile_; 54 base::MessageLoopForUI ui_message_loop_; 55 content::TestBrowserThread ui_thread_; 56 scoped_ptr<extensions::TestExtensionPrefs> prefs_; 57 scoped_ptr<ShellWindowGeometryCache> cache_; 58 }; 59 60 void ShellWindowGeometryCacheTest::AddGeometryAndLoadExtension( 61 const std::string& extension_id, 62 const std::string& window_id, 63 const gfx::Rect& bounds, 64 const gfx::Rect& screen_bounds, 65 ui::WindowShowState state) { 66 scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue); 67 base::DictionaryValue* value = new base::DictionaryValue; 68 value->SetInteger("x", bounds.x()); 69 value->SetInteger("y", bounds.y()); 70 value->SetInteger("w", bounds.width()); 71 value->SetInteger("h", bounds.height()); 72 value->SetInteger("screen_bounds_x", screen_bounds.x()); 73 value->SetInteger("screen_bounds_y", screen_bounds.y()); 74 value->SetInteger("screen_bounds_w", screen_bounds.width()); 75 value->SetInteger("screen_bounds_h", screen_bounds.height()); 76 value->SetInteger("state", state); 77 dict->SetWithoutPathExpansion(window_id, value); 78 prefs_->prefs()->SetGeometryCache(extension_id, dict.Pass()); 79 LoadExtension(extension_id); 80 } 81 82 void ShellWindowGeometryCacheTest::WaitForSync() { 83 content::RunAllPendingInMessageLoop(); 84 } 85 86 void ShellWindowGeometryCacheTest::LoadExtension( 87 const std::string& extension_id) { 88 cache_->LoadGeometryFromStorage(extension_id); 89 WaitForSync(); 90 } 91 92 void ShellWindowGeometryCacheTest::UnloadExtension( 93 const std::string& extension_id) { 94 cache_->OnExtensionUnloaded(extension_id); 95 WaitForSync(); 96 } 97 98 // Test getting geometry from an empty store. 99 TEST_F(ShellWindowGeometryCacheTest, GetGeometryEmptyStore) { 100 const std::string extension_id = prefs_->AddExtensionAndReturnId("ext1"); 101 ASSERT_FALSE(cache_->GetGeometry(extension_id, kWindowId, NULL, NULL, NULL)); 102 } 103 104 // Test getting geometry for an unknown extension. 105 TEST_F(ShellWindowGeometryCacheTest, GetGeometryUnkownExtension) { 106 const std::string extension_id1 = prefs_->AddExtensionAndReturnId("ext1"); 107 const std::string extension_id2 = prefs_->AddExtensionAndReturnId("ext2"); 108 AddGeometryAndLoadExtension(extension_id1, kWindowId, 109 gfx::Rect(4, 5, 31, 43), 110 gfx::Rect(0, 0, 1600, 900), 111 ui::SHOW_STATE_DEFAULT); 112 ASSERT_FALSE(cache_->GetGeometry(extension_id2, kWindowId, NULL, NULL, NULL)); 113 } 114 115 // Test getting geometry for an unknown window in a known extension. 116 TEST_F(ShellWindowGeometryCacheTest, GetGeometryUnkownWindow) { 117 const std::string extension_id = prefs_->AddExtensionAndReturnId("ext1"); 118 AddGeometryAndLoadExtension(extension_id, kWindowId, 119 gfx::Rect(4, 5, 31, 43), 120 gfx::Rect(0, 0, 1600, 900), 121 ui::SHOW_STATE_DEFAULT); 122 ASSERT_FALSE(cache_->GetGeometry(extension_id, kWindowId2, NULL, NULL, NULL)); 123 } 124 125 // Test that loading geometry, screen_bounds and state from the store works 126 // correctly. 127 TEST_F(ShellWindowGeometryCacheTest, GetGeometryAndStateFromStore) { 128 const std::string extension_id = prefs_->AddExtensionAndReturnId("ext1"); 129 gfx::Rect bounds(4, 5, 31, 43); 130 gfx::Rect screen_bounds(0, 0, 1600, 900); 131 ui::WindowShowState state = ui::SHOW_STATE_NORMAL; 132 AddGeometryAndLoadExtension(extension_id, kWindowId, bounds, 133 screen_bounds, state); 134 gfx::Rect new_bounds; 135 gfx::Rect new_screen_bounds; 136 ui::WindowShowState new_state = ui::SHOW_STATE_DEFAULT; 137 ASSERT_TRUE(cache_->GetGeometry( 138 extension_id, kWindowId, &new_bounds, &new_screen_bounds, &new_state)); 139 ASSERT_EQ(bounds, new_bounds); 140 ASSERT_EQ(screen_bounds, new_screen_bounds); 141 ASSERT_EQ(state, new_state); 142 } 143 144 // Test corrupt bounds will not be loaded. 145 TEST_F(ShellWindowGeometryCacheTest, CorruptBounds) { 146 const std::string extension_id = prefs_->AddExtensionAndReturnId("ext1"); 147 gfx::Rect bounds; 148 gfx::Rect screen_bounds(0, 0, 1600, 900); 149 ui::WindowShowState state = ui::SHOW_STATE_NORMAL; 150 AddGeometryAndLoadExtension(extension_id, kWindowId, bounds, 151 screen_bounds, state); 152 gfx::Rect new_bounds; 153 gfx::Rect new_screen_bounds; 154 ui::WindowShowState new_state = ui::SHOW_STATE_DEFAULT; 155 ASSERT_FALSE(cache_->GetGeometry( 156 extension_id, kWindowId, &new_bounds, &new_screen_bounds, &new_state)); 157 ASSERT_TRUE(new_bounds.IsEmpty()); 158 ASSERT_TRUE(new_screen_bounds.IsEmpty()); 159 ASSERT_EQ(new_state, ui::SHOW_STATE_DEFAULT); 160 } 161 162 // Test corrupt screen bounds will not be loaded. 163 TEST_F(ShellWindowGeometryCacheTest, CorruptScreenBounds) { 164 const std::string extension_id = prefs_->AddExtensionAndReturnId("ext1"); 165 gfx::Rect bounds(4, 5, 31, 43); 166 gfx::Rect screen_bounds; 167 ui::WindowShowState state = ui::SHOW_STATE_NORMAL; 168 AddGeometryAndLoadExtension(extension_id, kWindowId, bounds, 169 screen_bounds, state); 170 gfx::Rect new_bounds; 171 gfx::Rect new_screen_bounds; 172 ui::WindowShowState new_state = ui::SHOW_STATE_DEFAULT; 173 ASSERT_FALSE(cache_->GetGeometry( 174 extension_id, kWindowId, &new_bounds, &new_screen_bounds, &new_state)); 175 ASSERT_TRUE(new_bounds.IsEmpty()); 176 ASSERT_TRUE(new_screen_bounds.IsEmpty()); 177 ASSERT_EQ(new_state, ui::SHOW_STATE_DEFAULT); 178 } 179 180 // Test corrupt state will not be loaded. 181 TEST_F(ShellWindowGeometryCacheTest, CorruptState) { 182 const std::string extension_id = prefs_->AddExtensionAndReturnId("ext1"); 183 gfx::Rect bounds(4, 5, 31, 43); 184 gfx::Rect screen_bounds(0, 0, 1600, 900); 185 ui::WindowShowState state = ui::SHOW_STATE_DEFAULT; 186 AddGeometryAndLoadExtension(extension_id, kWindowId, bounds, 187 screen_bounds, state); 188 gfx::Rect new_bounds; 189 gfx::Rect new_screen_bounds; 190 ui::WindowShowState new_state = ui::SHOW_STATE_DEFAULT; 191 ASSERT_FALSE(cache_->GetGeometry( 192 extension_id, kWindowId, &new_bounds, &new_screen_bounds, &new_state)); 193 ASSERT_TRUE(new_bounds.IsEmpty()); 194 ASSERT_TRUE(new_screen_bounds.IsEmpty()); 195 ASSERT_EQ(new_state, ui::SHOW_STATE_DEFAULT); 196 } 197 198 // Test saving geometry, screen_bounds and state to the cache and state store, 199 // and reading it back. 200 TEST_F(ShellWindowGeometryCacheTest, SaveGeometryAndStateToStore) { 201 const std::string extension_id = prefs_->AddExtensionAndReturnId("ext1"); 202 const std::string window_id(kWindowId); 203 204 // inform cache of extension 205 LoadExtension(extension_id); 206 207 // update geometry stored in cache 208 gfx::Rect bounds(4, 5, 31, 43); 209 gfx::Rect screen_bounds(0, 0, 1600, 900); 210 ui::WindowShowState state = ui::SHOW_STATE_NORMAL; 211 cache_->SaveGeometry(extension_id, window_id, bounds, screen_bounds, state); 212 213 // make sure that immediately reading back geometry works 214 gfx::Rect new_bounds; 215 gfx::Rect new_screen_bounds; 216 ui::WindowShowState new_state = ui::SHOW_STATE_DEFAULT; 217 ASSERT_TRUE(cache_->GetGeometry( 218 extension_id, window_id, &new_bounds, &new_screen_bounds, &new_state)); 219 ASSERT_EQ(bounds, new_bounds); 220 ASSERT_EQ(screen_bounds, new_screen_bounds); 221 ASSERT_EQ(state, new_state); 222 223 // unload extension to force cache to save data to the state store 224 UnloadExtension(extension_id); 225 226 // check if geometry got stored correctly in the state store 227 const base::DictionaryValue* dict = 228 prefs_->prefs()->GetGeometryCache(extension_id); 229 ASSERT_TRUE(dict); 230 231 ASSERT_TRUE(dict->HasKey(window_id)); 232 int v; 233 ASSERT_TRUE(dict->GetInteger(window_id + ".x", &v)); 234 ASSERT_EQ(bounds.x(), v); 235 ASSERT_TRUE(dict->GetInteger(window_id + ".y", &v)); 236 ASSERT_EQ(bounds.y(), v); 237 ASSERT_TRUE(dict->GetInteger(window_id + ".w", &v)); 238 ASSERT_EQ(bounds.width(), v); 239 ASSERT_TRUE(dict->GetInteger(window_id + ".h", &v)); 240 ASSERT_EQ(bounds.height(), v); 241 ASSERT_TRUE(dict->GetInteger(window_id + ".screen_bounds_x", &v)); 242 ASSERT_EQ(screen_bounds.x(), v); 243 ASSERT_TRUE(dict->GetInteger(window_id + ".screen_bounds_y", &v)); 244 ASSERT_EQ(screen_bounds.y(), v); 245 ASSERT_TRUE(dict->GetInteger(window_id + ".screen_bounds_w", &v)); 246 ASSERT_EQ(screen_bounds.width(), v); 247 ASSERT_TRUE(dict->GetInteger(window_id + ".screen_bounds_h", &v)); 248 ASSERT_EQ(screen_bounds.height(), v); 249 ASSERT_TRUE(dict->GetInteger(window_id + ".state", &v)); 250 ASSERT_EQ(state, v); 251 252 // reload extension 253 LoadExtension(extension_id); 254 // and make sure the geometry got reloaded properly too 255 ASSERT_TRUE(cache_->GetGeometry( 256 extension_id, window_id, &new_bounds, &new_screen_bounds, &new_state)); 257 ASSERT_EQ(bounds, new_bounds); 258 ASSERT_EQ(screen_bounds, new_screen_bounds); 259 ASSERT_EQ(state, new_state); 260 } 261 262 // Tests that we won't do writes to the state store for SaveGeometry calls 263 // which don't change the state we already have. 264 TEST_F(ShellWindowGeometryCacheTest, NoDuplicateWrites) { 265 using testing::_; 266 using testing::Mock; 267 268 const std::string extension_id = prefs_->AddExtensionAndReturnId("ext1"); 269 gfx::Rect bounds1(100, 200, 300, 400); 270 gfx::Rect bounds2(200, 400, 600, 800); 271 gfx::Rect bounds2_duplicate(200, 400, 600, 800); 272 273 gfx::Rect screen_bounds1(0, 0, 1600, 900); 274 gfx::Rect screen_bounds2(0, 0, 1366, 768); 275 gfx::Rect screen_bounds2_duplicate(0, 0, 1366, 768); 276 277 MockPrefChangeCallback observer(prefs_->pref_service()); 278 PrefChangeRegistrar registrar; 279 registrar.Init(prefs_->pref_service()); 280 registrar.Add("extensions.settings", observer.GetCallback()); 281 282 // Write the first bounds - it should do > 0 writes. 283 EXPECT_CALL(observer, OnPreferenceChanged(_)); 284 cache_->SaveGeometry(extension_id, kWindowId, bounds1, 285 screen_bounds1, ui::SHOW_STATE_DEFAULT); 286 WaitForSync(); 287 Mock::VerifyAndClearExpectations(&observer); 288 289 // Write a different bounds - it should also do > 0 writes. 290 EXPECT_CALL(observer, OnPreferenceChanged(_)); 291 cache_->SaveGeometry(extension_id, kWindowId, bounds2, 292 screen_bounds1, ui::SHOW_STATE_DEFAULT); 293 WaitForSync(); 294 Mock::VerifyAndClearExpectations(&observer); 295 296 // Write a different screen bounds - it should also do > 0 writes. 297 EXPECT_CALL(observer, OnPreferenceChanged(_)); 298 cache_->SaveGeometry(extension_id, kWindowId, bounds2, 299 screen_bounds2, ui::SHOW_STATE_DEFAULT); 300 WaitForSync(); 301 Mock::VerifyAndClearExpectations(&observer); 302 303 // Write a different state - it should also do > 0 writes. 304 EXPECT_CALL(observer, OnPreferenceChanged(_)); 305 cache_->SaveGeometry(extension_id, kWindowId, bounds2, 306 screen_bounds2, ui::SHOW_STATE_NORMAL); 307 WaitForSync(); 308 Mock::VerifyAndClearExpectations(&observer); 309 310 // Write a bounds, screen bounds and state that's a duplicate of what we 311 // already have. This should not do any writes. 312 EXPECT_CALL(observer, OnPreferenceChanged(_)).Times(0); 313 cache_->SaveGeometry(extension_id, kWindowId, bounds2_duplicate, 314 screen_bounds2_duplicate, ui::SHOW_STATE_NORMAL); 315 WaitForSync(); 316 Mock::VerifyAndClearExpectations(&observer); 317 } 318 319 // Tests that no more than kMaxCachedWindows windows will be cached. 320 TEST_F(ShellWindowGeometryCacheTest, MaxWindows) { 321 const std::string extension_id = prefs_->AddExtensionAndReturnId("ext1"); 322 // inform cache of extension 323 LoadExtension(extension_id); 324 325 gfx::Rect bounds(4, 5, 31, 43); 326 gfx::Rect screen_bounds(0, 0, 1600, 900); 327 for (size_t i = 0; i < ShellWindowGeometryCache::kMaxCachedWindows + 1; ++i) { 328 std::string window_id = "window_" + base::IntToString(i); 329 cache_->SaveGeometry(extension_id, window_id, bounds, 330 screen_bounds, ui::SHOW_STATE_DEFAULT); 331 } 332 333 // The first added window should no longer have cached geometry. 334 EXPECT_FALSE(cache_->GetGeometry(extension_id, "window_0", NULL, NULL, NULL)); 335 // All other windows should still exist. 336 for (size_t i = 1; i < ShellWindowGeometryCache::kMaxCachedWindows + 1; ++i) { 337 std::string window_id = "window_" + base::IntToString(i); 338 EXPECT_TRUE(cache_->GetGeometry(extension_id, window_id, NULL, NULL, NULL)); 339 } 340 } 341 342 } // namespace extensions 343