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