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