1 // Copyright 2013 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 "chrome/browser/web_applications/components/web_app_icon_downloader.h"
6
7 #include <stddef.h>
8
9 #include "base/bind.h"
10 #include "base/files/scoped_temp_dir.h"
11 #include "base/test/metrics/histogram_tester.h"
12 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
13 #include "content/public/browser/web_contents.h"
14 #include "content/public/test/navigation_simulator.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16 #include "third_party/blink/public/mojom/favicon/favicon_url.mojom.h"
17 #include "third_party/skia/include/core/SkBitmap.h"
18
19 using content::RenderViewHostTester;
20
21 namespace web_app {
22
23 namespace {
24
25 // Creates valid SkBitmaps of the dimensions found in |sizes| and pushes them
26 // into |bitmaps|.
CreateTestBitmaps(const std::vector<gfx::Size> & sizes)27 std::vector<SkBitmap> CreateTestBitmaps(const std::vector<gfx::Size>& sizes) {
28 std::vector<SkBitmap> bitmaps(sizes.size());
29 for (size_t i = 0; i < sizes.size(); ++i) {
30 SkBitmap& bitmap = bitmaps[i];
31 bitmap.allocN32Pixels(sizes[i].width(), sizes[i].height());
32 bitmap.eraseColor(SK_ColorRED);
33 }
34 return bitmaps;
35 }
36
37 class WebAppIconDownloaderTest : public ChromeRenderViewHostTestHarness {
38 public:
39 WebAppIconDownloaderTest(const WebAppIconDownloaderTest&) = delete;
40 WebAppIconDownloaderTest& operator=(const WebAppIconDownloaderTest&) = delete;
41
42 protected:
WebAppIconDownloaderTest()43 WebAppIconDownloaderTest() {}
~WebAppIconDownloaderTest()44 ~WebAppIconDownloaderTest() override {}
45
46 protected:
47 base::HistogramTester histogram_tester_;
48
49 };
50
51 const char* kHistogramForCreateName = "WebApp.Icon.HttpStatusCodeClassOnCreate";
52
53 } // namespace
54
55 class TestWebAppIconDownloader : public WebAppIconDownloader {
56 public:
TestWebAppIconDownloader(content::WebContents * web_contents,std::vector<GURL> extra_favicon_urls)57 TestWebAppIconDownloader(content::WebContents* web_contents,
58 std::vector<GURL> extra_favicon_urls)
59 : WebAppIconDownloader(
60 web_contents,
61 extra_favicon_urls,
62 Histogram::kForCreate,
63 base::BindOnce(&TestWebAppIconDownloader::DownloadsComplete,
64 base::Unretained(this))),
65 id_counter_(0) {}
66 TestWebAppIconDownloader(const TestWebAppIconDownloader&) = delete;
67 TestWebAppIconDownloader& operator=(const TestWebAppIconDownloader&) = delete;
~TestWebAppIconDownloader()68 ~TestWebAppIconDownloader() override {}
69
DownloadImage(const GURL & url)70 int DownloadImage(const GURL& url) override { return id_counter_++; }
71
72 const std::vector<blink::mojom::FaviconURLPtr>&
GetFaviconURLsFromWebContents()73 GetFaviconURLsFromWebContents() override {
74 return initial_favicon_urls_;
75 }
76
pending_requests() const77 size_t pending_requests() const { return in_progress_requests_.size(); }
78
DownloadsComplete(bool success,IconsMap map)79 void DownloadsComplete(bool success, IconsMap map) {
80 downloads_succeeded_ = success;
81 favicon_map_ = std::move(map);
82 }
83
favicon_map() const84 IconsMap favicon_map() const { return favicon_map_; }
85
CompleteImageDownload(int id,int http_status_code,const GURL & image_url,const std::vector<gfx::Size> & original_bitmap_sizes)86 void CompleteImageDownload(
87 int id,
88 int http_status_code,
89 const GURL& image_url,
90 const std::vector<gfx::Size>& original_bitmap_sizes) {
91 WebAppIconDownloader::DidDownloadFavicon(
92 id, http_status_code, image_url,
93 CreateTestBitmaps(original_bitmap_sizes), original_bitmap_sizes);
94 }
95
UpdateFaviconURLs(const std::vector<blink::mojom::FaviconURLPtr> & candidates)96 void UpdateFaviconURLs(
97 const std::vector<blink::mojom::FaviconURLPtr>& candidates) {
98 WebAppIconDownloader::DidUpdateFaviconURL(web_contents()->GetMainFrame(),
99 candidates);
100 }
101
set_initial_favicon_urls(const std::vector<blink::mojom::FaviconURLPtr> & urls)102 void set_initial_favicon_urls(
103 const std::vector<blink::mojom::FaviconURLPtr>& urls) {
104 for (const auto& url : urls)
105 initial_favicon_urls_.push_back(url.Clone());
106 }
107
downloads_succeeded()108 bool downloads_succeeded() { return downloads_succeeded_.value(); }
109
110 private:
111 std::vector<blink::mojom::FaviconURLPtr> initial_favicon_urls_;
112 IconsMap favicon_map_;
113 int id_counter_;
114 base::Optional<bool> downloads_succeeded_;
115
116 };
117
TEST_F(WebAppIconDownloaderTest,SimpleDownload)118 TEST_F(WebAppIconDownloaderTest, SimpleDownload) {
119 const GURL favicon_url("http://www.google.com/favicon.ico");
120 TestWebAppIconDownloader downloader(web_contents(), std::vector<GURL>());
121
122 std::vector<blink::mojom::FaviconURLPtr> favicon_urls;
123 favicon_urls.push_back(blink::mojom::FaviconURL::New(
124 favicon_url, blink::mojom::FaviconIconType::kFavicon,
125 std::vector<gfx::Size>()));
126 downloader.set_initial_favicon_urls(favicon_urls);
127 EXPECT_EQ(0u, downloader.pending_requests());
128
129 downloader.Start();
130 EXPECT_EQ(1u, downloader.pending_requests());
131
132 std::vector<gfx::Size> sizes(1, gfx::Size(32, 32));
133 downloader.CompleteImageDownload(0, 200, favicon_urls[0]->icon_url, sizes);
134 EXPECT_EQ(0u, downloader.pending_requests());
135
136 EXPECT_EQ(1u, downloader.favicon_map().size());
137 EXPECT_EQ(1u, downloader.favicon_map()[favicon_url].size());
138 EXPECT_TRUE(downloader.downloads_succeeded());
139 histogram_tester_.ExpectUniqueSample(kHistogramForCreateName, 2, 1);
140 }
141
TEST_F(WebAppIconDownloaderTest,NoHTTPStatusCode)142 TEST_F(WebAppIconDownloaderTest, NoHTTPStatusCode) {
143 const GURL favicon_url("data:image/png,");
144 TestWebAppIconDownloader downloader(web_contents(), std::vector<GURL>());
145
146 std::vector<blink::mojom::FaviconURLPtr> favicon_urls;
147 favicon_urls.push_back(blink::mojom::FaviconURL::New(
148 favicon_url, blink::mojom::FaviconIconType::kFavicon,
149 std::vector<gfx::Size>()));
150 downloader.set_initial_favicon_urls(favicon_urls);
151 EXPECT_EQ(0u, downloader.pending_requests());
152
153 downloader.Start();
154 EXPECT_EQ(1u, downloader.pending_requests());
155
156 std::vector<gfx::Size> sizes = {gfx::Size(0, 0)};
157 // data: URLs have a 0 HTTP status code.
158 downloader.CompleteImageDownload(0, 0, favicon_urls[0]->icon_url, sizes);
159 EXPECT_EQ(0u, downloader.pending_requests());
160
161 EXPECT_EQ(1u, downloader.favicon_map().size());
162 EXPECT_EQ(1u, downloader.favicon_map()[favicon_url].size());
163 EXPECT_TRUE(downloader.downloads_succeeded())
164 << "Should not consider data: URL or HTTP status code of 0 a failure";
165 histogram_tester_.ExpectTotalCount(kHistogramForCreateName, 0);
166 }
167
TEST_F(WebAppIconDownloaderTest,DownloadWithUrlsFromWebContentsNotification)168 TEST_F(WebAppIconDownloaderTest, DownloadWithUrlsFromWebContentsNotification) {
169 const GURL favicon_url("http://www.google.com/favicon.ico");
170 TestWebAppIconDownloader downloader(web_contents(), std::vector<GURL>());
171
172 std::vector<blink::mojom::FaviconURLPtr> favicon_urls;
173 favicon_urls.push_back(blink::mojom::FaviconURL::New(
174 favicon_url, blink::mojom::FaviconIconType::kFavicon,
175 std::vector<gfx::Size>()));
176 EXPECT_EQ(0u, downloader.pending_requests());
177
178 // Start downloader before favicon URLs are loaded.
179 downloader.Start();
180 EXPECT_EQ(0u, downloader.pending_requests());
181
182 downloader.UpdateFaviconURLs(favicon_urls);
183 EXPECT_EQ(1u, downloader.pending_requests());
184
185 std::vector<gfx::Size> sizes(1, gfx::Size(32, 32));
186 downloader.CompleteImageDownload(0, 200, favicon_urls[0]->icon_url, sizes);
187 EXPECT_EQ(0u, downloader.pending_requests());
188
189 EXPECT_EQ(1u, downloader.favicon_map().size());
190 EXPECT_EQ(1u, downloader.favicon_map()[favicon_url].size());
191 EXPECT_TRUE(downloader.downloads_succeeded());
192 histogram_tester_.ExpectUniqueSample(kHistogramForCreateName, 2, 1);
193 }
194
TEST_F(WebAppIconDownloaderTest,DownloadMultipleUrls)195 TEST_F(WebAppIconDownloaderTest, DownloadMultipleUrls) {
196 const GURL empty_favicon("http://www.google.com/empty_favicon.ico");
197 const GURL favicon_url_1("http://www.google.com/favicon.ico");
198 const GURL favicon_url_2("http://www.google.com/favicon2.ico");
199
200 std::vector<GURL> extra_urls;
201 // This should get downloaded.
202 extra_urls.push_back(favicon_url_2);
203 // This is duplicated in the favicon urls and should only be downloaded once.
204 extra_urls.push_back(empty_favicon);
205
206 TestWebAppIconDownloader downloader(web_contents(), extra_urls);
207 std::vector<blink::mojom::FaviconURLPtr> favicon_urls;
208 favicon_urls.push_back(blink::mojom::FaviconURL::New(
209 favicon_url_1, blink::mojom::FaviconIconType::kFavicon,
210 std::vector<gfx::Size>()));
211 // This is duplicated in the favicon urls and should only be downloaded once.
212 favicon_urls.push_back(blink::mojom::FaviconURL::New(
213 empty_favicon, blink::mojom::FaviconIconType::kFavicon,
214 std::vector<gfx::Size>()));
215 // Invalid icons shouldn't get put into the download queue.
216 favicon_urls.push_back(blink::mojom::FaviconURL::New(
217 GURL("http://www.google.com/invalid.ico"),
218 blink::mojom::FaviconIconType::kInvalid, std::vector<gfx::Size>()));
219 downloader.set_initial_favicon_urls(favicon_urls);
220 downloader.Start();
221 EXPECT_EQ(3u, downloader.pending_requests());
222
223 std::vector<gfx::Size> sizes_1(1, gfx::Size(16, 16));
224 downloader.CompleteImageDownload(0, 200, favicon_url_1, sizes_1);
225
226 std::vector<gfx::Size> sizes_2;
227 sizes_2.push_back(gfx::Size(32, 32));
228 sizes_2.push_back(gfx::Size(64, 64));
229 downloader.CompleteImageDownload(1, 200, favicon_url_2, sizes_2);
230
231 // Only 1 download should have been initiated for |empty_favicon| even though
232 // the URL was in both the web app info and the favicon urls.
233 downloader.CompleteImageDownload(2, 200, empty_favicon,
234 std::vector<gfx::Size>());
235 EXPECT_EQ(0u, downloader.pending_requests());
236
237 EXPECT_EQ(3u, downloader.favicon_map().size());
238 EXPECT_EQ(0u, downloader.favicon_map()[empty_favicon].size());
239 EXPECT_EQ(1u, downloader.favicon_map()[favicon_url_1].size());
240 EXPECT_EQ(2u, downloader.favicon_map()[favicon_url_2].size());
241 EXPECT_TRUE(downloader.downloads_succeeded());
242 histogram_tester_.ExpectUniqueSample(kHistogramForCreateName, 2, 3);
243 }
244
TEST_F(WebAppIconDownloaderTest,SkipPageFavicons)245 TEST_F(WebAppIconDownloaderTest, SkipPageFavicons) {
246 const GURL favicon_url_1("http://www.google.com/favicon.ico");
247 const GURL favicon_url_2("http://www.google.com/favicon2.ico");
248
249 std::vector<GURL> extra_urls;
250 extra_urls.push_back(favicon_url_1);
251
252 TestWebAppIconDownloader downloader(web_contents(), extra_urls);
253
254 // This favicon URL should be ignored.
255 std::vector<blink::mojom::FaviconURLPtr> favicon_urls;
256 favicon_urls.push_back(blink::mojom::FaviconURL::New(
257 favicon_url_2, blink::mojom::FaviconIconType::kFavicon,
258 std::vector<gfx::Size>()));
259 downloader.set_initial_favicon_urls(favicon_urls);
260 downloader.SkipPageFavicons();
261 downloader.Start();
262 EXPECT_EQ(1u, downloader.pending_requests());
263
264 std::vector<gfx::Size> sizes_1(1, gfx::Size(16, 16));
265 downloader.CompleteImageDownload(0, 200, favicon_url_1, sizes_1);
266
267 // This download should not be finished and inserted into the map.
268 std::vector<gfx::Size> sizes_2;
269 sizes_2.push_back(gfx::Size(32, 32));
270 sizes_2.push_back(gfx::Size(64, 64));
271 downloader.CompleteImageDownload(1, 200, favicon_url_2, sizes_2);
272 EXPECT_EQ(0u, downloader.pending_requests());
273
274 EXPECT_EQ(1u, downloader.favicon_map().size());
275 EXPECT_EQ(1u, downloader.favicon_map()[favicon_url_1].size());
276 EXPECT_EQ(0u, downloader.favicon_map()[favicon_url_2].size());
277 EXPECT_TRUE(downloader.downloads_succeeded());
278 histogram_tester_.ExpectUniqueSample(kHistogramForCreateName, 2, 1);
279 }
280
TEST_F(WebAppIconDownloaderTest,PageNavigates)281 TEST_F(WebAppIconDownloaderTest, PageNavigates) {
282 TestWebAppIconDownloader downloader(web_contents(), std::vector<GURL>());
283
284 std::vector<blink::mojom::FaviconURLPtr> favicon_urls;
285 favicon_urls.push_back(blink::mojom::FaviconURL::New(
286 GURL("http://www.google.com/favicon.ico"),
287 blink::mojom::FaviconIconType::kFavicon, std::vector<gfx::Size>()));
288 downloader.set_initial_favicon_urls(favicon_urls);
289 EXPECT_EQ(0u, downloader.pending_requests());
290
291 downloader.Start();
292 EXPECT_EQ(1u, downloader.pending_requests());
293
294 content::NavigationSimulator::CreateRendererInitiated(
295 GURL("https://foo.example"), main_rfh())
296 ->Commit();
297
298 EXPECT_EQ(0u, downloader.pending_requests());
299 EXPECT_TRUE(downloader.favicon_map().empty());
300 EXPECT_FALSE(downloader.downloads_succeeded());
301 }
302
TEST_F(WebAppIconDownloaderTest,PageNavigatesAfterDownload)303 TEST_F(WebAppIconDownloaderTest, PageNavigatesAfterDownload) {
304 const GURL url("http://www.google.com/icon.png");
305 TestWebAppIconDownloader downloader(web_contents(), {url});
306 downloader.SkipPageFavicons();
307
308 downloader.Start();
309 EXPECT_EQ(1u, downloader.pending_requests());
310
311 downloader.CompleteImageDownload(0, 200, url, {gfx::Size(32, 32)});
312 EXPECT_EQ(0u, downloader.pending_requests());
313 EXPECT_TRUE(downloader.downloads_succeeded());
314
315 // Navigating the renderer after downloads have completed should not crash.
316 content::NavigationSimulator::CreateRendererInitiated(
317 GURL("https://foo.example"), main_rfh())
318 ->Commit();
319 }
320
TEST_F(WebAppIconDownloaderTest,PageNavigatesSameDocument)321 TEST_F(WebAppIconDownloaderTest, PageNavigatesSameDocument) {
322 content::NavigationSimulator::NavigateAndCommitFromBrowser(
323 web_contents(), GURL("https://foo.example"));
324
325 const GURL favicon_url("http://www.google.com/favicon.ico");
326 TestWebAppIconDownloader downloader(web_contents(), std::vector<GURL>());
327 std::vector<blink::mojom::FaviconURLPtr> favicon_urls;
328 favicon_urls.push_back(blink::mojom::FaviconURL::New(
329 favicon_url, blink::mojom::FaviconIconType::kFavicon,
330 std::vector<gfx::Size>()));
331
332 downloader.set_initial_favicon_urls(favicon_urls);
333 EXPECT_EQ(0u, downloader.pending_requests());
334
335 downloader.Start();
336 EXPECT_EQ(1u, downloader.pending_requests());
337
338 content::NavigationSimulator::CreateRendererInitiated(
339 GURL("https://foo.example/#test"), main_rfh())
340 ->CommitSameDocument();
341
342 EXPECT_EQ(1u, downloader.pending_requests());
343
344 std::vector<gfx::Size> sizes(1, gfx::Size(32, 32));
345 downloader.CompleteImageDownload(0, 200, favicon_url, sizes);
346 EXPECT_EQ(0u, downloader.pending_requests());
347
348 EXPECT_EQ(1u, downloader.favicon_map().size());
349 EXPECT_EQ(1u, downloader.favicon_map()[favicon_url].size());
350 EXPECT_TRUE(downloader.downloads_succeeded());
351 histogram_tester_.ExpectUniqueSample(kHistogramForCreateName, 2, 1);
352 }
353
354 } // namespace web_app
355