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