OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2017 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 <memory> | |
6 | |
7 #include "base/macros.h" | |
8 #include "chrome/browser/extensions/chrome_app_icon.h" | |
9 #include "chrome/browser/extensions/chrome_app_icon_delegate.h" | |
10 #include "chrome/browser/extensions/chrome_app_icon_loader.h" | |
11 #include "chrome/browser/extensions/chrome_app_icon_service.h" | |
12 #include "chrome/browser/extensions/extension_service.h" | |
13 #include "chrome/browser/extensions/extension_service_test_base.h" | |
14 #include "chrome/browser/profiles/profile.h" | |
15 #include "chrome/browser/ui/app_icon_loader_delegate.h" | |
16 #include "chrome/common/chrome_constants.h" | |
17 #include "chrome/test/base/testing_profile.h" | |
18 #include "extensions/common/constants.h" | |
19 #include "ui/app_list/app_list_item.h" | |
20 #include "ui/gfx/image/image_unittest_util.h" | |
21 | |
22 #if defined(OS_CHROMEOS) | |
23 #include "chrome/browser/chromeos/arc/arc_util.h" | |
24 #include "chrome/browser/ui/app_list/arc/arc_app_test.h" | |
25 #include "chrome/browser/ui/app_list/extension_app_model_builder.h" | |
26 #include "chrome/browser/ui/app_list/search/extension_app_result.h" | |
27 #include "chrome/browser/ui/app_list/test/test_app_list_controller_delegate.h" | |
28 #include "components/arc/test/fake_app_instance.h" | |
29 #endif | |
30 | |
31 namespace extensions { | |
32 | |
33 namespace { | |
34 | |
35 constexpr char kTestAppId[] = "emfkafnhnpcmabnnkckkchdilgeoekbo"; | |
36 | |
37 // Receives icon image updates from ChromeAppIcon. | |
38 class TestAppIcon : public ChromeAppIconDelegate { | |
39 public: | |
40 TestAppIcon(content::BrowserContext* context, | |
41 const std::string& app_id, | |
42 int size) { | |
43 app_icon_ = | |
44 ChromeAppIconService::Get(context)->CreateIcon(this, app_id, size); | |
45 DCHECK(app_icon_); | |
46 } | |
47 | |
48 ~TestAppIcon() override = default; | |
49 | |
50 void Reset() { app_icon_.reset(); } | |
51 | |
52 int GetIconUpdateCountAndReset() { | |
53 int icon_update_count = icon_update_count_; | |
54 icon_update_count_ = 0; | |
55 return icon_update_count; | |
56 } | |
57 | |
58 size_t icon_update_count() const { return icon_update_count_; } | |
59 ChromeAppIcon* app_icon() { return app_icon_.get(); } | |
60 const gfx::ImageSkia& image_skia() const { return app_icon_->image_skia(); } | |
61 | |
62 void WaitIconUpdated() { | |
msw
2017/05/08 19:59:54
nit: WaitForIconUpdates
khmel
2017/05/09 00:05:05
Done.
| |
63 base::RunLoop run_loop; | |
64 icon_update_count_expected_ = | |
65 icon_update_count_ + image_skia().image_reps().size(); | |
66 icon_updated_callback_ = run_loop.QuitClosure(); | |
67 run_loop.Run(); | |
68 } | |
69 | |
70 private: | |
71 void OnIconUpdated(ChromeAppIcon* icon) override { | |
72 ++icon_update_count_; | |
73 if (icon_update_count_ == icon_update_count_expected_) { | |
74 if (!icon_updated_callback_.is_null()) | |
msw
2017/05/08 19:59:54
nit: combine nested ifs
khmel
2017/05/09 00:05:05
Done.
| |
75 base::ResetAndReturn(&icon_updated_callback_).Run(); | |
76 } | |
77 } | |
78 | |
79 std::unique_ptr<ChromeAppIcon> app_icon_; | |
80 | |
81 size_t icon_update_count_ = 0; | |
82 size_t icon_update_count_expected_ = 0; | |
83 | |
84 base::OnceClosure icon_updated_callback_; | |
85 | |
86 DISALLOW_COPY_AND_ASSIGN(TestAppIcon); | |
87 }; | |
88 | |
89 // Receives icon image updates from ChromeAppIconLoader. | |
90 class TestAppIconLoader : public AppIconLoaderDelegate { | |
91 public: | |
92 TestAppIconLoader() = default; | |
93 ~TestAppIconLoader() override = default; | |
94 | |
95 // AppIconLoaderDelegate: | |
96 void OnAppImageUpdated(const std::string& app_id, | |
97 const gfx::ImageSkia& image) override { | |
98 image_skia_ = image; | |
99 } | |
100 | |
101 gfx::ImageSkia& icon() { return image_skia_; } | |
102 const gfx::ImageSkia& icon() const { return image_skia_; } | |
103 | |
104 private: | |
105 gfx::ImageSkia image_skia_; | |
106 | |
107 DISALLOW_COPY_AND_ASSIGN(TestAppIconLoader); | |
108 }; | |
109 | |
110 // Returns true if provided |image| consists from only empty pixels. | |
111 bool IsBlankImage(const gfx::ImageSkia& image) { | |
msw
2017/05/08 19:59:54
Can you just call image->isNull() || image->bitmap
khmel
2017/05/09 00:05:05
drawsNothing validates width/heights <= 0 but not
| |
112 if (!image.width() || !image.height()) | |
113 return false; | |
114 | |
115 const SkBitmap* bitmap = image.bitmap(); | |
116 DCHECK(bitmap); | |
117 DCHECK_EQ(bitmap->width(), image.width()); | |
118 DCHECK_EQ(bitmap->height(), image.height()); | |
119 | |
120 for (int x = 0; x < bitmap->width(); ++x) { | |
121 for (int y = 0; y < bitmap->height(); ++y) { | |
122 if (*bitmap->getAddr32(x, y)) | |
123 return false; | |
124 } | |
125 } | |
126 return true; | |
127 } | |
128 | |
129 // Returns true if provided |image| is grayscale. | |
130 bool IsGrayscaleImage(const gfx::ImageSkia& image) { | |
msw
2017/05/08 19:59:54
Can you just check the bitmap's SkColorType instea
khmel
2017/05/09 00:05:05
IIUC CreateHSLShiftedImage does not change color t
| |
131 const SkBitmap* bitmap = image.bitmap(); | |
132 DCHECK(bitmap); | |
133 for (int x = 0; x < bitmap->width(); ++x) { | |
134 for (int y = 0; y < bitmap->height(); ++y) { | |
135 const unsigned int pixel = *bitmap->getAddr32(x, y); | |
136 if ((pixel & 0xff) != ((pixel >> 8) & 0xff) || | |
137 (pixel & 0xff) != ((pixel >> 16) & 0xff)) { | |
138 return false; | |
139 } | |
140 } | |
141 } | |
142 return true; | |
143 } | |
144 | |
145 // Returns true if provided |image1| and |image2| are equal. | |
146 bool AreEqual(const gfx::ImageSkia& image1, const gfx::ImageSkia& image2) { | |
msw
2017/05/08 19:59:54
nit: all callers are accessing the ImageSkia withi
khmel
2017/05/09 00:05:05
TestAppIcon reference_icon is not gfx::ImageSkia.
msw
2017/05/10 19:05:21
Ah, nevermind.
| |
147 return gfx::test::AreImagesEqual(gfx::Image(image1), gfx::Image(image2)); | |
148 } | |
149 | |
150 #if defined(OS_CHROMEOS) | |
151 | |
152 bool AreAllImageRepresentationsDifferent(const gfx::ImageSkia& image1, | |
153 const gfx::ImageSkia& image2) { | |
154 const std::vector<gfx::ImageSkiaRep> image1_reps = image1.image_reps(); | |
155 const std::vector<gfx::ImageSkiaRep> image2_reps = image2.image_reps(); | |
156 DCHECK_EQ(image1_reps.size(), image2_reps.size()); | |
157 for (size_t i = 0; i < image1_reps.size(); ++i) { | |
158 const float scale = image1_reps[i].scale(); | |
159 const gfx::ImageSkiaRep& image_rep2 = image2.GetRepresentation(scale); | |
160 if (gfx::test::AreBitmapsClose(image1_reps[i].sk_bitmap(), | |
161 image_rep2.sk_bitmap(), 0)) { | |
162 return false; | |
163 } | |
164 } | |
165 return true; | |
166 } | |
167 | |
168 // Waits until icon content of the item is changed for all representations. | |
169 template <typename T> | |
170 void WaitIconUpdated(const T& item) { | |
msw
2017/05/08 19:59:54
nit: WaitForIconUpdates
khmel
2017/05/09 00:05:05
Done.
| |
171 item.icon().EnsureRepsForSupportedScales(); | |
172 std::unique_ptr<gfx::ImageSkia> reference_image = item.icon().DeepCopy(); | |
173 while (!AreAllImageRepresentationsDifferent(*reference_image, item.icon())) | |
174 base::RunLoop().RunUntilIdle(); | |
175 } | |
176 #endif | |
msw
2017/05/08 19:59:54
nit: blank line before and // defined(OS_CHROMEOS)
khmel
2017/05/09 00:05:05
Done.
| |
177 | |
178 } // namespace | |
179 | |
180 class ChromeAppIconTest : public ExtensionServiceTestBase { | |
181 public: | |
182 ChromeAppIconTest() = default; | |
183 ~ChromeAppIconTest() override = default; | |
184 | |
185 // ExtensionServiceTestBase: | |
186 void SetUp() override { | |
187 ExtensionServiceTestBase::SetUp(); | |
188 | |
189 const base::FilePath source_install_dir = | |
190 data_dir().AppendASCII("app_list").AppendASCII("Extensions"); | |
191 const base::FilePath pref_path = | |
192 source_install_dir.DirName().Append(chrome::kPreferencesFilename); | |
193 InitializeInstalledExtensionService(pref_path, source_install_dir); | |
194 service_->Init(); | |
195 } | |
196 | |
197 private: | |
198 DISALLOW_COPY_AND_ASSIGN(ChromeAppIconTest); | |
199 }; | |
200 | |
201 TEST_F(ChromeAppIconTest, IconLifeCycle) { | |
202 TestAppIcon reference_icon(profile(), kTestAppId, | |
203 extension_misc::EXTENSION_ICON_MEDIUM); | |
204 EXPECT_EQ(1U, reference_icon.icon_update_count()); | |
205 // By default no representation in image. | |
206 EXPECT_FALSE(reference_icon.image_skia().HasRepresentation(1.0f)); | |
207 | |
208 // Default blank image must be provided without an update. | |
209 EXPECT_FALSE(reference_icon.image_skia().GetRepresentation(1.0f).is_null()); | |
210 EXPECT_EQ(1U, reference_icon.icon_update_count()); | |
211 EXPECT_TRUE(reference_icon.image_skia().HasRepresentation(1.0f)); | |
msw
2017/05/08 19:59:54
nit: remove this if it's redundant with line 206
khmel
2017/05/09 00:05:05
206 has EXPECT_FALSE here EXPECT_TRUE
msw
2017/05/10 19:05:21
Oops, sorry about that!
| |
212 EXPECT_EQ(extension_misc::EXTENSION_ICON_MEDIUM, | |
213 reference_icon.image_skia().width()); | |
214 EXPECT_EQ(extension_misc::EXTENSION_ICON_MEDIUM, | |
215 reference_icon.image_skia().height()); | |
216 EXPECT_TRUE(IsBlankImage(reference_icon.image_skia())); | |
217 | |
218 // Wait until real image is loaded. | |
219 reference_icon.WaitIconUpdated(); | |
220 EXPECT_EQ(2U, reference_icon.icon_update_count()); | |
221 EXPECT_FALSE(IsBlankImage(reference_icon.image_skia())); | |
222 EXPECT_FALSE(IsGrayscaleImage(reference_icon.image_skia())); | |
223 | |
224 const gfx::ImageSkia image_before_disable = reference_icon.image_skia(); | |
225 // Disable extension. This should update icon and provide grayed image inline. | |
226 // Note update might be sent twice in CrOS. | |
227 service()->DisableExtension(kTestAppId, Extension::DISABLE_CORRUPTED); | |
228 const size_t update_count_after_disable = reference_icon.icon_update_count(); | |
229 EXPECT_NE(2U, update_count_after_disable); | |
230 EXPECT_FALSE(IsBlankImage(reference_icon.image_skia())); | |
231 EXPECT_TRUE(IsGrayscaleImage(reference_icon.image_skia())); | |
232 | |
233 // Reenable extension. It should match previous enabled image | |
234 service()->EnableExtension(kTestAppId); | |
235 EXPECT_NE(update_count_after_disable, reference_icon.icon_update_count()); | |
236 EXPECT_TRUE(AreEqual(reference_icon.image_skia(), image_before_disable)); | |
237 } | |
238 | |
239 // Validates that icon release is safe. | |
240 TEST_F(ChromeAppIconTest, IconRelease) { | |
241 TestAppIcon test_icon1(profile(), kTestAppId, | |
242 extension_misc::EXTENSION_ICON_MEDIUM); | |
243 TestAppIcon test_icon2(profile(), kTestAppId, | |
244 extension_misc::EXTENSION_ICON_MEDIUM); | |
245 EXPECT_FALSE(test_icon1.image_skia().GetRepresentation(1.0f).is_null()); | |
246 EXPECT_FALSE(test_icon2.image_skia().GetRepresentation(1.0f).is_null()); | |
247 | |
248 // Reset before service is stopped. | |
249 test_icon1.Reset(); | |
250 | |
251 // Reset after service is stopped. | |
252 profile_.reset(); | |
253 test_icon2.Reset(); | |
254 } | |
255 | |
256 #if defined(OS_CHROMEOS) | |
257 class ChromeAppIconWithModelTest : public ChromeAppIconTest { | |
258 public: | |
259 ChromeAppIconWithModelTest() = default; | |
260 ~ChromeAppIconWithModelTest() override = default; | |
261 | |
262 protected: | |
263 void CreateBuilder() { | |
264 model_.reset(new app_list::AppListModel); | |
265 controller_.reset(new test::TestAppListControllerDelegate); | |
266 builder_.reset(new ExtensionAppModelBuilder(controller_.get())); | |
267 builder_->InitializeWithProfile(profile(), model_.get()); | |
268 } | |
269 | |
270 void ResetBuilder() { | |
271 builder_.reset(); | |
272 controller_.reset(); | |
273 model_.reset(); | |
274 } | |
275 | |
276 app_list::AppListItem* FindAppListItem(const std::string& app_id) { | |
277 return model_->FindItem(app_id); | |
278 } | |
279 | |
280 test::TestAppListControllerDelegate* app_list_controller() { | |
281 return controller_.get(); | |
282 } | |
283 | |
284 private: | |
285 std::unique_ptr<app_list::AppListModel> model_; | |
286 std::unique_ptr<test::TestAppListControllerDelegate> controller_; | |
287 std::unique_ptr<ExtensionAppModelBuilder> builder_; | |
288 | |
289 DISALLOW_COPY_AND_ASSIGN(ChromeAppIconWithModelTest); | |
290 }; | |
291 | |
292 // Validates that icons are the same for different consumers. | |
293 TEST_F(ChromeAppIconWithModelTest, IconsTheSame) { | |
294 CreateBuilder(); | |
295 | |
296 // App list item is already created. Wait until all image representations are | |
297 // updated and take image snapshot. | |
298 app_list::AppListItem* app_list_item = FindAppListItem(kTestAppId); | |
299 ASSERT_TRUE(app_list_item); | |
300 WaitIconUpdated<app_list::AppListItem>(*app_list_item); | |
301 std::unique_ptr<gfx::ImageSkia> app_list_item_image = | |
302 app_list_item->icon().DeepCopy(); | |
303 | |
304 // Load reference icon. | |
305 TestAppIcon reference_icon(profile(), kTestAppId, | |
306 extension_misc::EXTENSION_ICON_MEDIUM); | |
307 | |
308 // Wait until reference data is loaded. | |
309 reference_icon.image_skia().EnsureRepsForSupportedScales(); | |
310 reference_icon.WaitIconUpdated(); | |
311 EXPECT_FALSE(IsBlankImage(reference_icon.image_skia())); | |
312 | |
313 // Now compare with app list icon snapshot. | |
314 EXPECT_TRUE(AreEqual(reference_icon.image_skia(), *app_list_item_image)); | |
315 | |
316 app_list::ExtensionAppResult search(profile(), kTestAppId, | |
317 app_list_controller(), true); | |
318 WaitIconUpdated<app_list::ExtensionAppResult>(search); | |
319 EXPECT_TRUE(AreEqual(reference_icon.image_skia(), search.icon())); | |
320 | |
321 TestAppIconLoader loader_delegate; | |
322 ChromeAppIconLoader loader(profile(), extension_misc::EXTENSION_ICON_MEDIUM, | |
323 &loader_delegate); | |
324 loader.FetchImage(kTestAppId); | |
325 WaitIconUpdated<TestAppIconLoader>(loader_delegate); | |
326 EXPECT_TRUE(AreEqual(reference_icon.image_skia(), loader_delegate.icon())); | |
327 | |
328 ResetBuilder(); | |
329 } | |
330 | |
331 TEST_F(ChromeAppIconWithModelTest, ChromeBadging) { | |
msw
2017/05/08 19:59:54
Should this just be a ChromeAppIconTest? I don't s
khmel
2017/05/09 00:05:05
Done.
| |
332 ArcAppTest arc_test; | |
333 arc_test.SetUp(profile()); | |
334 | |
335 TestAppIcon reference_icon(profile(), kTestAppId, | |
336 extension_misc::EXTENSION_ICON_MEDIUM); | |
337 // Wait until reference data is loaded. | |
338 EXPECT_FALSE(reference_icon.image_skia().GetRepresentation(1.0f).is_null()); | |
339 reference_icon.WaitIconUpdated(); | |
340 EXPECT_FALSE(IsBlankImage(reference_icon.image_skia())); | |
341 | |
342 reference_icon.GetIconUpdateCountAndReset(); | |
343 const gfx::ImageSkia image_before_badging = reference_icon.image_skia(); | |
344 | |
345 // Badging should be applied once package is installed. | |
346 arc_test.app_instance()->RefreshAppList(); | |
347 std::vector<arc::mojom::AppInfo> fake_apps = arc_test.fake_apps(); | |
348 fake_apps[0].package_name = arc_test.fake_packages()[0].package_name; | |
349 arc_test.app_instance()->SendRefreshAppList(fake_apps); | |
350 arc_test.app_instance()->SendRefreshPackageList(arc_test.fake_packages()); | |
351 EXPECT_EQ(1U, reference_icon.icon_update_count()); | |
352 EXPECT_FALSE(AreEqual(reference_icon.image_skia(), image_before_badging)); | |
353 | |
354 // Opts out the Play Store. Badge should be gone and icon image is the same | |
355 // as it was before badging. | |
356 arc::SetArcPlayStoreEnabledForProfile(profile(), false); | |
357 EXPECT_EQ(2U, reference_icon.icon_update_count()); | |
358 EXPECT_TRUE(AreEqual(reference_icon.image_skia(), image_before_badging)); | |
359 } | |
360 #endif | |
msw
2017/05/08 19:59:54
nit: // defined(OS_CHROMEOS), optionally put a bla
khmel
2017/05/09 00:05:05
Done.
| |
361 | |
362 } // namespace extensions | |
OLD | NEW |