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.
5 #include "extensions/browser/app_window/app_window_geometry_cache.h"
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"
25 using content::BrowserThread
;
27 namespace extensions
{
30 const char kWindowId
[] = "windowid";
31 const char kWindowId2
[] = "windowid2";
33 // Create a very simple extension with id.
34 scoped_refptr
<Extension
> CreateExtension(const std::string
& id
) {
35 return ExtensionBuilder()
36 .SetManifest(DictionaryBuilder().Set("name", "test").Set(
44 // Base class for tests.
45 class AppWindowGeometryCacheTest
: public ExtensionsTest
{
47 AppWindowGeometryCacheTest()
48 : ui_thread_(BrowserThread::UI
, &ui_message_loop_
) {}
50 // testing::Test overrides:
51 void SetUp() override
;
52 void TearDown() override
;
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
);
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.
64 void LoadExtension(const std::string
& extension_id
);
65 void UnloadExtension(const std::string
& extension_id
);
67 // Creates and adds an extension with associated prefs. Returns the extension
69 std::string
AddExtensionWithPrefs(const std::string
& name
);
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_
;
80 void AppWindowGeometryCacheTest::SetUp() {
81 ExtensionsTest::SetUp();
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();
94 extension_prefs_
.reset(ExtensionPrefs::Create(
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
*>()));
103 new AppWindowGeometryCache(browser_context(), extension_prefs_
.get()));
104 cache_
->SetSyncDelayForTests(0);
107 void AppWindowGeometryCacheTest::TearDown() {
109 extension_prefs_
.reset();
110 pref_service_
.reset();
111 extension_pref_value_map_
.reset();
113 ExtensionsTest::TearDown();
116 void 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
);
138 void AppWindowGeometryCacheTest::WaitForSync() {
139 content::RunAllPendingInMessageLoop();
142 void AppWindowGeometryCacheTest::LoadExtension(
143 const std::string
& extension_id
) {
144 cache_
->LoadGeometryFromStorage(extension_id
);
148 void AppWindowGeometryCacheTest::UnloadExtension(
149 const std::string
& extension_id
) {
150 scoped_refptr
<Extension
> extension
= CreateExtension(extension_id
);
151 cache_
->OnExtensionUnloaded(browser_context(),
153 UnloadedExtensionInfo::REASON_DISABLE
);
157 std::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
=
166 DictionaryBuilder().Set("name", "test").Set("version", "0.1"))
170 extension_prefs_
->OnExtensionInstalled(
173 syncer::StringOrdinal::CreateInitialOrdinal(),
175 return extension
->id();
178 // Test getting geometry from an empty store.
179 TEST_F(AppWindowGeometryCacheTest
, GetGeometryEmptyStore
) {
180 const std::string extension_id
= AddExtensionWithPrefs("ext1");
181 ASSERT_FALSE(cache_
->GetGeometry(extension_id
, kWindowId
, NULL
, NULL
, NULL
));
184 // Test getting geometry for an unknown extension.
185 TEST_F(AppWindowGeometryCacheTest
, GetGeometryUnkownExtension
) {
186 const std::string extension_id1
= AddExtensionWithPrefs("ext1");
187 const std::string extension_id2
= AddExtensionWithPrefs("ext2");
188 AddGeometryAndLoadExtension(extension_id1
,
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
));
196 // Test getting geometry for an unknown window in a known extension.
197 TEST_F(AppWindowGeometryCacheTest
, GetGeometryUnkownWindow
) {
198 const std::string extension_id
= AddExtensionWithPrefs("ext1");
199 AddGeometryAndLoadExtension(extension_id
,
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
));
207 // Test that loading geometry, screen_bounds and state from the store works
209 TEST_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
);
226 // Test corrupt bounds will not be loaded.
227 TEST_F(AppWindowGeometryCacheTest
, CorruptBounds
) {
228 const std::string extension_id
= AddExtensionWithPrefs("ext1");
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
);
244 // Test corrupt screen bounds will not be loaded.
245 TEST_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
);
262 // Test corrupt state will not be loaded.
263 TEST_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
);
280 // Test saving geometry, screen_bounds and state to the cache and state store,
281 // and reading it back.
282 TEST_F(AppWindowGeometryCacheTest
, SaveGeometryAndStateToStore
) {
283 const std::string extension_id
= AddExtensionWithPrefs("ext1");
284 const std::string
window_id(kWindowId
);
286 // inform cache of extension
287 LoadExtension(extension_id
);
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
);
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
);
305 // unload extension to force cache to save data to the state store
306 UnloadExtension(extension_id
);
308 // check if geometry got stored correctly in the state store
309 const base::DictionaryValue
* dict
=
310 extension_prefs_
->GetGeometryCache(extension_id
);
313 ASSERT_TRUE(dict
->HasKey(window_id
));
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
));
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
);
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.
346 TEST_F(AppWindowGeometryCacheTest
, NoDuplicateWrites
) {
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);
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);
359 MockPrefChangeCallback
observer(pref_service_
.get());
360 PrefChangeRegistrar registrar
;
361 registrar
.Init(pref_service_
.get());
362 registrar
.Add("extensions.settings", observer
.GetCallback());
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
);
369 Mock::VerifyAndClearExpectations(&observer
);
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
);
376 Mock::VerifyAndClearExpectations(&observer
);
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
);
383 Mock::VerifyAndClearExpectations(&observer
);
385 // Write a different state - it should also do > 0 writes.
386 EXPECT_CALL(observer
, OnPreferenceChanged(_
));
387 cache_
->SaveGeometry(extension_id
,
391 ui::SHOW_STATE_MAXIMIZED
);
393 Mock::VerifyAndClearExpectations(&observer
);
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
,
401 screen_bounds2_duplicate
,
402 ui::SHOW_STATE_MAXIMIZED
);
404 Mock::VerifyAndClearExpectations(&observer
);
407 // Tests that no more than kMaxCachedWindows windows will be cached.
408 TEST_F(AppWindowGeometryCacheTest
, MaxWindows
) {
409 const std::string extension_id
= AddExtensionWithPrefs("ext1");
410 // inform cache of extension
411 LoadExtension(extension_id
);
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
);
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
));
430 } // namespace extensions