• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
ShellWindowGeometryCacheTest()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 
AddGeometryAndLoadExtension(const std::string & extension_id,const std::string & window_id,const gfx::Rect & bounds,const gfx::Rect & screen_bounds,ui::WindowShowState state)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 
WaitForSync()78 void ShellWindowGeometryCacheTest::WaitForSync() {
79   content::RunAllPendingInMessageLoop();
80 }
81 
LoadExtension(const std::string & extension_id)82 void ShellWindowGeometryCacheTest::LoadExtension(
83     const std::string& extension_id) {
84   cache_->LoadGeometryFromStorage(extension_id);
85   WaitForSync();
86 }
87 
UnloadExtension(const std::string & extension_id)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.
TEST_F(ShellWindowGeometryCacheTest,GetGeometryEmptyStore)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.
TEST_F(ShellWindowGeometryCacheTest,GetGeometryUnkownExtension)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.
TEST_F(ShellWindowGeometryCacheTest,GetGeometryUnkownWindow)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.
TEST_F(ShellWindowGeometryCacheTest,GetGeometryAndStateFromStore)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.
TEST_F(ShellWindowGeometryCacheTest,CorruptBounds)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.
TEST_F(ShellWindowGeometryCacheTest,CorruptScreenBounds)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.
TEST_F(ShellWindowGeometryCacheTest,CorruptState)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.
TEST_F(ShellWindowGeometryCacheTest,SaveGeometryAndStateToStore)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.
TEST_F(ShellWindowGeometryCacheTest,NoDuplicateWrites)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.
TEST_F(ShellWindowGeometryCacheTest,MaxWindows)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