Chromium Code Reviews| 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/extension_app_icon.h" | |
| 9 #include "chrome/browser/extensions/extension_app_icon_delegate.h" | |
| 10 #include "chrome/browser/extensions/extension_app_icon_loader.h" | |
| 11 #include "chrome/browser/extensions/extension_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 "extensions/common/constants.h" | |
| 18 #include "ui/app_list/app_list_item.h" | |
| 19 | |
| 20 #if defined(OS_CHROMEOS) | |
| 21 #include "chrome/browser/chromeos/arc/arc_util.h" | |
| 22 #include "chrome/browser/ui/app_list/arc/arc_app_test.h" | |
| 23 #include "chrome/browser/ui/app_list/extension_app_model_builder.h" | |
| 24 #include "chrome/browser/ui/app_list/search/extension_app_result.h" | |
| 25 #include "chrome/browser/ui/app_list/test/test_app_list_controller_delegate.h" | |
| 26 #include "components/arc/test/fake_app_instance.h" | |
| 27 #endif | |
| 28 | |
| 29 namespace extensions { | |
| 30 | |
| 31 namespace { | |
| 32 | |
| 33 constexpr char kTestAppId[] = "emfkafnhnpcmabnnkckkchdilgeoekbo"; | |
| 34 | |
| 35 // Receives icon image updates from ExtensionAppIcon. | |
| 36 class TestAppIcon : public ExtensionAppIconDelegate { | |
| 37 public: | |
| 38 TestAppIcon(content::BrowserContext* context, | |
| 39 const std::string& app_id, | |
| 40 int size) { | |
| 41 app_icon_ = | |
| 42 ExtensionAppIconService::Get(context)->CreateIcon(this, app_id, size); | |
| 43 DCHECK(app_icon_); | |
| 44 } | |
| 45 | |
| 46 ~TestAppIcon() override = default; | |
| 47 | |
| 48 int icon_update_count() const { return icon_update_count_; } | |
| 49 ExtensionAppIcon* app_icon() { return app_icon_.get(); } | |
| 50 const gfx::ImageSkia& image_skia() const { return app_icon_->image_skia(); } | |
| 51 | |
| 52 void WaitIconUpdated() { | |
| 53 base::RunLoop run_loop; | |
| 54 icon_updated_callback_ = run_loop.QuitClosure(); | |
| 55 run_loop.Run(); | |
| 56 icon_updated_callback_.Reset(); | |
|
xiyuan
2017/04/20 16:36:32
nit: We can avoid this call by making |icon_update
khmel
2017/04/20 17:46:18
I also found base::ResetAndReturn approach. Would
xiyuan
2017/04/20 17:52:46
That works.
| |
| 57 } | |
| 58 | |
| 59 private: | |
| 60 void OnIconUpdated(ExtensionAppIcon* icon) override { | |
| 61 ++icon_update_count_; | |
| 62 if (!icon_updated_callback_.is_null()) | |
| 63 icon_updated_callback_.Run(); | |
| 64 } | |
| 65 | |
| 66 std::unique_ptr<ExtensionAppIcon> app_icon_; | |
| 67 | |
| 68 int icon_update_count_ = 0; | |
| 69 | |
| 70 base::Closure icon_updated_callback_; | |
| 71 | |
| 72 DISALLOW_COPY_AND_ASSIGN(TestAppIcon); | |
| 73 }; | |
| 74 | |
| 75 // Receives icon image updates from ExtensionAppIconLoader. | |
| 76 class TestAppIconLoader : public AppIconLoaderDelegate { | |
| 77 public: | |
| 78 TestAppIconLoader() = default; | |
| 79 ~TestAppIconLoader() override = default; | |
| 80 | |
| 81 // AppIconLoaderDelegate: | |
| 82 void OnAppImageUpdated(const std::string& app_id, | |
| 83 const gfx::ImageSkia& image) override { | |
| 84 image_skia_ = image; | |
| 85 } | |
| 86 | |
| 87 const gfx::ImageSkia& image_skia() { return image_skia_; } | |
| 88 | |
| 89 private: | |
| 90 gfx::ImageSkia image_skia_; | |
| 91 | |
| 92 DISALLOW_COPY_AND_ASSIGN(TestAppIconLoader); | |
| 93 }; | |
| 94 | |
| 95 // Returns true if provided |image| consists from only empty pixels. | |
| 96 bool IsBlankImage(const gfx::ImageSkia& image) { | |
| 97 if (!image.width() || !image.height()) | |
| 98 return false; | |
| 99 | |
| 100 const SkBitmap* bitmap = image.bitmap(); | |
| 101 DCHECK(bitmap); | |
| 102 DCHECK_EQ(bitmap->width(), image.width()); | |
| 103 DCHECK_EQ(bitmap->height(), image.height()); | |
| 104 | |
| 105 for (int x = 0; x < bitmap->width(); ++x) { | |
| 106 for (int y = 0; y < bitmap->height(); ++y) { | |
| 107 if (*bitmap->getAddr32(x, y)) | |
| 108 return false; | |
| 109 } | |
| 110 } | |
| 111 return true; | |
| 112 } | |
| 113 | |
| 114 // Returns true if provided |image| is grayscale. | |
| 115 bool IsGrayscaleImage(const gfx::ImageSkia& image) { | |
| 116 const SkBitmap* bitmap = image.bitmap(); | |
| 117 DCHECK(bitmap); | |
| 118 for (int x = 0; x < bitmap->width(); ++x) { | |
| 119 for (int y = 0; y < bitmap->height(); ++y) { | |
| 120 const unsigned int pixel = *bitmap->getAddr32(x, y); | |
| 121 if ((pixel & 0xff) != ((pixel >> 8) & 0xff) || | |
| 122 (pixel & 0xff) != ((pixel >> 16) & 0xff)) { | |
| 123 return false; | |
| 124 } | |
| 125 } | |
| 126 } | |
| 127 return true; | |
| 128 } | |
| 129 | |
| 130 // Returns true if provided |image1| and |image2| are equal. | |
| 131 bool AreEqual(const gfx::ImageSkia& image1, const gfx::ImageSkia& image2) { | |
|
xiyuan
2017/04/20 16:36:32
You can use gfx::test::AreImagesEqual for this.
e
khmel
2017/04/20 17:46:18
Done.
| |
| 132 const SkBitmap* bitmap1 = image1.bitmap(); | |
| 133 DCHECK(bitmap1); | |
| 134 const SkBitmap* bitmap2 = image2.bitmap(); | |
| 135 DCHECK(bitmap2); | |
| 136 | |
| 137 if (bitmap1->width() != bitmap2->width() || | |
| 138 bitmap1->height() != bitmap2->height()) { | |
| 139 return false; | |
| 140 } | |
| 141 | |
| 142 for (int x = 0; x < bitmap1->width(); ++x) { | |
| 143 for (int y = 0; y < bitmap2->height(); ++y) { | |
| 144 if (*bitmap1->getAddr32(x, y) != *bitmap2->getAddr32(x, y)) | |
| 145 return false; | |
| 146 } | |
| 147 } | |
| 148 return true; | |
| 149 } | |
| 150 | |
| 151 } // namespace | |
| 152 | |
| 153 class ExtensionAppIconTest : public ExtensionServiceTestBase { | |
| 154 public: | |
| 155 ExtensionAppIconTest() = default; | |
| 156 ~ExtensionAppIconTest() override = default; | |
| 157 | |
| 158 // ExtensionServiceTestBase: | |
| 159 void SetUp() override { | |
| 160 ExtensionServiceTestBase::SetUp(); | |
| 161 | |
| 162 const base::FilePath source_install_dir = | |
| 163 data_dir().AppendASCII("app_list").AppendASCII("Extensions"); | |
| 164 const base::FilePath pref_path = | |
| 165 source_install_dir.DirName().Append(chrome::kPreferencesFilename); | |
| 166 InitializeInstalledExtensionService(pref_path, source_install_dir); | |
| 167 service_->Init(); | |
| 168 } | |
| 169 | |
| 170 private: | |
| 171 DISALLOW_COPY_AND_ASSIGN(ExtensionAppIconTest); | |
| 172 }; | |
| 173 | |
| 174 TEST_F(ExtensionAppIconTest, IconLifeCycle) { | |
| 175 TestAppIcon reference_icon(profile(), kTestAppId, | |
| 176 extension_misc::EXTENSION_ICON_MEDIUM); | |
| 177 EXPECT_EQ(1, reference_icon.icon_update_count()); | |
| 178 // By default no representation in image. | |
| 179 EXPECT_FALSE(reference_icon.image_skia().HasRepresentation(1.0f)); | |
| 180 | |
| 181 // Accessing to bitmap requests default representations. | |
| 182 // Defualt blank image must be provided without an update. | |
| 183 EXPECT_TRUE(reference_icon.image_skia().bitmap()); | |
|
xiyuan
2017/04/20 16:36:32
nit: avoid bitmap() since it is going way. Use Get
khmel
2017/04/20 17:46:18
Done.
| |
| 184 EXPECT_EQ(1, reference_icon.icon_update_count()); | |
| 185 EXPECT_TRUE(reference_icon.image_skia().HasRepresentation(1.0f)); | |
| 186 EXPECT_EQ(extension_misc::EXTENSION_ICON_MEDIUM, | |
| 187 reference_icon.image_skia().width()); | |
| 188 EXPECT_EQ(extension_misc::EXTENSION_ICON_MEDIUM, | |
| 189 reference_icon.image_skia().height()); | |
| 190 EXPECT_TRUE(IsBlankImage(reference_icon.image_skia())); | |
| 191 | |
| 192 // Wait until real image is loaded. | |
| 193 reference_icon.WaitIconUpdated(); | |
| 194 EXPECT_EQ(2, reference_icon.icon_update_count()); | |
| 195 EXPECT_FALSE(IsBlankImage(reference_icon.image_skia())); | |
| 196 EXPECT_FALSE(IsGrayscaleImage(reference_icon.image_skia())); | |
| 197 | |
| 198 const gfx::ImageSkia image_before_disable = reference_icon.image_skia(); | |
| 199 // Disable extension. This should update icon and provide grayed image inline. | |
| 200 // Note update might be sent twice in CrOS. | |
| 201 service()->DisableExtension(kTestAppId, Extension::DISABLE_CORRUPTED); | |
| 202 const int update_count_after_disable = reference_icon.icon_update_count(); | |
| 203 EXPECT_NE(2, update_count_after_disable); | |
| 204 EXPECT_FALSE(IsBlankImage(reference_icon.image_skia())); | |
| 205 EXPECT_TRUE(IsGrayscaleImage(reference_icon.image_skia())); | |
| 206 | |
| 207 // Reenable extension. It should match previous enabled image | |
| 208 service()->EnableExtension(kTestAppId); | |
| 209 EXPECT_NE(update_count_after_disable, reference_icon.icon_update_count()); | |
| 210 EXPECT_TRUE(AreEqual(reference_icon.image_skia(), image_before_disable)); | |
| 211 } | |
| 212 | |
| 213 #if defined(OS_CHROMEOS) | |
| 214 class ExtensionAppIconWithModelTest : public ExtensionAppIconTest { | |
| 215 public: | |
| 216 ExtensionAppIconWithModelTest() = default; | |
| 217 ~ExtensionAppIconWithModelTest() override = default; | |
| 218 | |
| 219 protected: | |
| 220 void CreateBuilder() { | |
| 221 model_.reset(new app_list::AppListModel); | |
| 222 controller_.reset(new test::TestAppListControllerDelegate); | |
| 223 builder_.reset(new ExtensionAppModelBuilder(controller_.get())); | |
| 224 builder_->InitializeWithProfile(profile(), model_.get()); | |
| 225 } | |
| 226 | |
| 227 void ResetBuilder() { | |
| 228 builder_.reset(); | |
| 229 controller_.reset(); | |
| 230 model_.reset(); | |
| 231 } | |
| 232 | |
| 233 app_list::AppListItem* FindAppListItem(const std::string& app_id) { | |
| 234 return model_->FindItem(app_id); | |
| 235 } | |
| 236 | |
| 237 test::TestAppListControllerDelegate* app_list_controller() { | |
| 238 return controller_.get(); | |
| 239 } | |
| 240 | |
| 241 private: | |
| 242 std::unique_ptr<app_list::AppListModel> model_; | |
| 243 std::unique_ptr<test::TestAppListControllerDelegate> controller_; | |
| 244 std::unique_ptr<ExtensionAppModelBuilder> builder_; | |
| 245 | |
| 246 DISALLOW_COPY_AND_ASSIGN(ExtensionAppIconWithModelTest); | |
| 247 }; | |
| 248 | |
| 249 // Validates that icons are the same for different consumers. | |
| 250 TEST_F(ExtensionAppIconWithModelTest, IconsTheSame) { | |
| 251 CreateBuilder(); | |
| 252 | |
| 253 TestAppIcon reference_icon(profile(), kTestAppId, | |
| 254 extension_misc::EXTENSION_ICON_MEDIUM); | |
| 255 | |
| 256 // Wait until reference data is loaded. | |
| 257 EXPECT_TRUE(reference_icon.image_skia().bitmap()); | |
|
xiyuan
2017/04/20 16:36:31
nit: GetRepresentation(1.0f)
khmel
2017/04/20 17:46:18
Done.
| |
| 258 while (IsBlankImage(reference_icon.image_skia())) | |
|
xiyuan
2017/04/20 16:36:31
Why using a while loop instead of WaitIconUpdated
khmel
2017/04/20 17:46:18
Hmmm...., you are right.
| |
| 259 base::RunLoop().RunUntilIdle(); | |
| 260 | |
| 261 app_list::ExtensionAppResult search(profile(), kTestAppId, | |
| 262 app_list_controller(), true); | |
| 263 while (!AreEqual(reference_icon.image_skia(), search.icon())) | |
| 264 base::RunLoop().RunUntilIdle(); | |
| 265 | |
| 266 app_list::AppListItem* app_list_item = FindAppListItem(kTestAppId); | |
| 267 ASSERT_TRUE(app_list_item); | |
| 268 while (!AreEqual(reference_icon.image_skia(), app_list_item->icon())) | |
| 269 base::RunLoop().RunUntilIdle(); | |
| 270 | |
| 271 TestAppIconLoader loader_delegate; | |
| 272 ExtensionAppIconLoader loader( | |
| 273 profile(), extension_misc::EXTENSION_ICON_MEDIUM, &loader_delegate); | |
| 274 loader.FetchImage(kTestAppId); | |
| 275 while (!AreEqual(reference_icon.image_skia(), loader_delegate.image_skia())) | |
| 276 base::RunLoop().RunUntilIdle(); | |
| 277 | |
| 278 ResetBuilder(); | |
| 279 } | |
| 280 | |
| 281 TEST_F(ExtensionAppIconWithModelTest, ChromeBadging) { | |
| 282 ArcAppTest arc_test; | |
| 283 arc_test.SetUp(profile()); | |
| 284 | |
| 285 TestAppIcon reference_icon(profile(), kTestAppId, | |
| 286 extension_misc::EXTENSION_ICON_MEDIUM); | |
| 287 // Wait until reference data is loaded. | |
| 288 EXPECT_TRUE(reference_icon.image_skia().bitmap()); | |
|
xiyuan
2017/04/20 16:36:31
nit: GetRepresentation(1.0f)
khmel
2017/04/20 17:46:18
Done.
| |
| 289 while (IsBlankImage(reference_icon.image_skia())) | |
| 290 base::RunLoop().RunUntilIdle(); | |
| 291 | |
| 292 const int icon_update_count_before_badging = | |
| 293 reference_icon.icon_update_count(); | |
| 294 const gfx::ImageSkia image_before_badging = reference_icon.image_skia(); | |
| 295 | |
| 296 // Badging should be applied once package is installed. | |
| 297 arc_test.app_instance()->RefreshAppList(); | |
| 298 std::vector<arc::mojom::AppInfo> fake_apps = arc_test.fake_apps(); | |
| 299 fake_apps[0].package_name = arc_test.fake_packages()[0].package_name; | |
| 300 arc_test.app_instance()->SendRefreshAppList(fake_apps); | |
| 301 arc_test.app_instance()->SendRefreshPackageList(arc_test.fake_packages()); | |
| 302 EXPECT_EQ(icon_update_count_before_badging + 1, | |
|
xiyuan
2017/04/20 16:36:32
nit: Other tests are using a GetCountAndReset that
khmel
2017/04/20 17:46:18
Done.
| |
| 303 reference_icon.icon_update_count()); | |
| 304 EXPECT_FALSE(AreEqual(reference_icon.image_skia(), image_before_badging)); | |
| 305 | |
| 306 // Opts out the Play Store. Badge should be gone and icon image is the same | |
| 307 // as it was before badging. | |
| 308 arc::SetArcPlayStoreEnabledForProfile(profile(), false); | |
| 309 EXPECT_EQ(icon_update_count_before_badging + 2, | |
| 310 reference_icon.icon_update_count()); | |
| 311 EXPECT_TRUE(AreEqual(reference_icon.image_skia(), image_before_badging)); | |
| 312 } | |
| 313 #endif | |
| 314 | |
| 315 } // namespace extensions | |
| OLD | NEW |