1 // Copyright 2019 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/ui/webui/favicon_source.h"
6 
7 #include <memory>
8 #include <utility>
9 
10 #include "base/callback_helpers.h"
11 #include "base/memory/ptr_util.h"
12 #include "base/strings/strcat.h"
13 #include "base/test/bind.h"
14 #include "base/test/metrics/histogram_tester.h"
15 #include "chrome/browser/favicon/favicon_service_factory.h"
16 #include "chrome/browser/favicon/history_ui_favicon_request_handler_factory.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/common/webui_url_constants.h"
19 #include "chrome/test/base/testing_profile.h"
20 #include "components/favicon/core/history_ui_favicon_request_handler.h"
21 #include "components/favicon/core/test/mock_favicon_service.h"
22 #include "components/favicon_base/favicon_url_parser.h"
23 #include "content/public/browser/browser_context.h"
24 #include "content/public/browser/web_contents.h"
25 #include "content/public/test/browser_task_environment.h"
26 #include "content/public/test/test_renderer_host.h"
27 #include "content/public/test/web_contents_tester.h"
28 #include "extensions/browser/extension_registry.h"
29 #include "extensions/common/extension_builder.h"
30 #include "extensions/common/manifest.h"
31 #include "testing/gmock/include/gmock/gmock.h"
32 #include "testing/gtest/include/gtest/gtest.h"
33 #include "ui/native_theme/test_native_theme.h"
34 #include "ui/resources/grit/ui_resources.h"
35 
36 using GotDataCallback = content::URLDataSource::GotDataCallback;
37 using WebContentsGetter = content::WebContents::Getter;
38 using testing::_;
39 using testing::Return;
40 using testing::ReturnArg;
41 
42 namespace {
43 
44 const int kDummyTaskId = 1;
45 const char kDummyPrefix[] = "chrome://any-host/";
46 
47 }  // namespace
48 
Noop(scoped_refptr<base::RefCountedMemory>)49 void Noop(scoped_refptr<base::RefCountedMemory>) {}
50 
51 class MockHistoryUiFaviconRequestHandler
52     : public favicon::HistoryUiFaviconRequestHandler {
53  public:
54   MockHistoryUiFaviconRequestHandler() = default;
55   ~MockHistoryUiFaviconRequestHandler() override = default;
56 
57   MOCK_METHOD4(
58       GetRawFaviconForPageURL,
59       void(const GURL& page_url,
60            int desired_size_in_pixel,
61            favicon_base::FaviconRawBitmapCallback callback,
62            favicon::HistoryUiFaviconRequestOrigin request_origin_for_uma));
63 
64   MOCK_METHOD3(
65       GetFaviconImageForPageURL,
66       void(const GURL& page_url,
67            favicon_base::FaviconImageCallback callback,
68            favicon::HistoryUiFaviconRequestOrigin request_origin_for_uma));
69 };
70 
71 class TestFaviconSource : public FaviconSource {
72  public:
TestFaviconSource(chrome::FaviconUrlFormat format,Profile * profile,ui::NativeTheme * theme)73   TestFaviconSource(chrome::FaviconUrlFormat format,
74                     Profile* profile,
75                     ui::NativeTheme* theme)
76       : FaviconSource(profile, format), theme_(theme) {}
77 
~TestFaviconSource()78   ~TestFaviconSource() override {}
79 
80   MOCK_METHOD2(LoadIconBytes, base::RefCountedMemory*(float, int));
81 
82  protected:
GetNativeTheme()83   ui::NativeTheme* GetNativeTheme() override { return theme_; }
84 
85  private:
86   ui::NativeTheme* const theme_;
87 };
88 
89 class FaviconSourceTestBase : public testing::Test {
90  public:
FaviconSourceTestBase(chrome::FaviconUrlFormat format)91   explicit FaviconSourceTestBase(chrome::FaviconUrlFormat format)
92       : source_(format, &profile_, &theme_) {
93     // Setup testing factories for main dependencies.
94     BrowserContextKeyedServiceFactory::TestingFactory
95         history_ui_favicon_request_handler_factory =
96             base::BindRepeating([](content::BrowserContext*) {
97               return base::WrapUnique<KeyedService>(
98                   new MockHistoryUiFaviconRequestHandler());
99             });
100     mock_history_ui_favicon_request_handler_ =
101         static_cast<MockHistoryUiFaviconRequestHandler*>(
102             HistoryUiFaviconRequestHandlerFactory::GetInstance()
103                 ->SetTestingFactoryAndUse(
104                     &profile_, history_ui_favicon_request_handler_factory));
105     BrowserContextKeyedServiceFactory::TestingFactory favicon_service_factory =
106         base::BindRepeating([](content::BrowserContext*) {
107           return static_cast<std::unique_ptr<KeyedService>>(
108               std::make_unique<favicon::MockFaviconService>());
109         });
110     mock_favicon_service_ = static_cast<favicon::MockFaviconService*>(
111         FaviconServiceFactory::GetInstance()->SetTestingFactoryAndUse(
112             &profile_, favicon_service_factory));
113 
114     // Setup TestWebContents.
115     test_web_contents_ =
116         content::WebContentsTester::CreateTestWebContents(&profile_, nullptr);
117     test_web_contents_getter_ = base::BindLambdaForTesting(
118         [&] { return (content::WebContents*)test_web_contents_.get(); });
119 
120     // On call, dependencies will return empty favicon by default.
121     ON_CALL(*mock_favicon_service_, GetRawFaviconForPageURL(_, _, _, _, _, _))
122         .WillByDefault([](auto, auto, auto, auto,
123                           favicon_base::FaviconRawBitmapCallback callback,
124                           auto) {
125           std::move(callback).Run(favicon_base::FaviconRawBitmapResult());
126           return kDummyTaskId;
127         });
128     ON_CALL(*mock_history_ui_favicon_request_handler_,
129             GetRawFaviconForPageURL(_, _, _, _))
130         .WillByDefault([](auto, auto,
131                           favicon_base::FaviconRawBitmapCallback callback,
132                           auto) {
133           std::move(callback).Run(favicon_base::FaviconRawBitmapResult());
134         });
135 
136     // Mock default icon loading.
137     ON_CALL(*source(), LoadIconBytes(_, _))
138         .WillByDefault(Return(kDummyIconBytes.get()));
139   }
140 
SetDarkMode(bool dark_mode)141   void SetDarkMode(bool dark_mode) { theme_.SetDarkMode(dark_mode); }
142 
source()143   TestFaviconSource* source() { return &source_; }
144 
145  protected:
146   const scoped_refptr<base::RefCountedBytes> kDummyIconBytes;
147   content::BrowserTaskEnvironment task_environment_;
148   content::RenderViewHostTestEnabler test_render_host_factories_;
149   ui::TestNativeTheme theme_;
150   TestingProfile profile_;
151   MockHistoryUiFaviconRequestHandler* mock_history_ui_favicon_request_handler_;
152   favicon::MockFaviconService* mock_favicon_service_;
153   std::unique_ptr<content::WebContents> test_web_contents_;
154   WebContentsGetter test_web_contents_getter_;
155   TestFaviconSource source_;
156 };
157 
158 class FaviconSourceTestWithLegacyFormat : public FaviconSourceTestBase {
159  public:
FaviconSourceTestWithLegacyFormat()160   FaviconSourceTestWithLegacyFormat()
161       : FaviconSourceTestBase(chrome::FaviconUrlFormat::kFaviconLegacy) {}
162 };
163 
164 class FaviconSourceTestWithFavicon2Format : public FaviconSourceTestBase {
165  public:
FaviconSourceTestWithFavicon2Format()166   FaviconSourceTestWithFavicon2Format()
167       : FaviconSourceTestBase(chrome::FaviconUrlFormat::kFavicon2) {}
168 };
169 
TEST_F(FaviconSourceTestWithLegacyFormat,DarkDefault)170 TEST_F(FaviconSourceTestWithLegacyFormat, DarkDefault) {
171   SetDarkMode(true);
172   EXPECT_CALL(*source(), LoadIconBytes(_, IDR_DEFAULT_FAVICON_DARK));
173   source()->StartDataRequest(GURL(kDummyPrefix), test_web_contents_getter_,
174                              base::BindOnce(&Noop));
175 }
176 
TEST_F(FaviconSourceTestWithLegacyFormat,LightDefault)177 TEST_F(FaviconSourceTestWithLegacyFormat, LightDefault) {
178   SetDarkMode(false);
179   EXPECT_CALL(*source(), LoadIconBytes(_, IDR_DEFAULT_FAVICON));
180   source()->StartDataRequest(GURL(kDummyPrefix), test_web_contents_getter_,
181                              base::BindOnce(&Noop));
182 }
183 
TEST_F(FaviconSourceTestWithLegacyFormat,ShouldNotQueryHistoryUiFaviconRequestHandler)184 TEST_F(FaviconSourceTestWithLegacyFormat,
185        ShouldNotQueryHistoryUiFaviconRequestHandler) {
186   content::WebContentsTester::For(test_web_contents_.get())
187       ->SetLastCommittedURL(GURL(chrome::kChromeUIHistoryURL));
188 
189   EXPECT_CALL(*mock_history_ui_favicon_request_handler_,
190               GetRawFaviconForPageURL)
191       .Times(0);
192 
193   source()->StartDataRequest(
194       GURL(base::StrCat({kDummyPrefix, "size/16@1x/https://www.google.com"})),
195       test_web_contents_getter_, base::BindOnce(&Noop));
196 }
197 
TEST_F(FaviconSourceTestWithLegacyFormat,ShouldRecordFaviconResourceHistogram_NonExtensionOrigin)198 TEST_F(FaviconSourceTestWithLegacyFormat,
199        ShouldRecordFaviconResourceHistogram_NonExtensionOrigin) {
200   base::HistogramTester tester;
201   source()->StartDataRequest(
202       GURL(base::StrCat({kDummyPrefix, "size/16@1x/https://www.google.com"})),
203       test_web_contents_getter_, base::DoNothing());
204   tester.ExpectBucketCount("Extensions.FaviconResourceRequested",
205                            extensions::Manifest::TYPE_EXTENSION, 0);
206 }
207 
TEST_F(FaviconSourceTestWithLegacyFormat,ShouldRecordFaviconResourceHistogram_ExtensionOrigin)208 TEST_F(FaviconSourceTestWithLegacyFormat,
209        ShouldRecordFaviconResourceHistogram_ExtensionOrigin) {
210   scoped_refptr<const extensions::Extension> extension =
211       extensions::ExtensionBuilder("one").Build();
212   extensions::ExtensionRegistry::Get(&profile_)->AddEnabled(extension);
213   content::WebContentsTester::For(test_web_contents_.get())
214       ->SetLastCommittedURL(extension->url());
215   base::HistogramTester tester;
216   source()->StartDataRequest(
217       GURL(base::StrCat({kDummyPrefix, "size/16@1x/https://www.google.com"})),
218       test_web_contents_getter_, base::DoNothing());
219   tester.ExpectBucketCount("Extensions.FaviconResourceRequested",
220                            extensions::Manifest::TYPE_EXTENSION, 1);
221 }
222 
TEST_F(FaviconSourceTestWithFavicon2Format,ShouldNotRecordFaviconResourceHistogram)223 TEST_F(FaviconSourceTestWithFavicon2Format,
224        ShouldNotRecordFaviconResourceHistogram) {
225   base::HistogramTester tester;
226   source()->StartDataRequest(
227       GURL(base::StrCat({kDummyPrefix, "size/16@1x/https://www.google.com"})),
228       test_web_contents_getter_, base::BindOnce(&Noop));
229   std::unique_ptr<base::HistogramSamples> samples(
230       tester.GetHistogramSamplesSinceCreation(
231           "Extensions.FaviconResourceUsed"));
232   EXPECT_TRUE(samples);
233   EXPECT_EQ(0, samples->TotalCount());
234 }
235 
TEST_F(FaviconSourceTestWithFavicon2Format,DarkDefault)236 TEST_F(FaviconSourceTestWithFavicon2Format, DarkDefault) {
237   SetDarkMode(true);
238   EXPECT_CALL(*source(), LoadIconBytes(_, IDR_DEFAULT_FAVICON_DARK));
239   source()->StartDataRequest(GURL(kDummyPrefix), test_web_contents_getter_,
240                              base::BindOnce(&Noop));
241 }
242 
TEST_F(FaviconSourceTestWithFavicon2Format,LightDefault)243 TEST_F(FaviconSourceTestWithFavicon2Format, LightDefault) {
244   SetDarkMode(false);
245   EXPECT_CALL(*source(), LoadIconBytes(_, IDR_DEFAULT_FAVICON));
246   source()->StartDataRequest(GURL(kDummyPrefix), test_web_contents_getter_,
247                              base::BindOnce(&Noop));
248 }
249 
TEST_F(FaviconSourceTestWithFavicon2Format,ShouldNotQueryHistoryUiFaviconRequestHandlerIfNotAllowed)250 TEST_F(FaviconSourceTestWithFavicon2Format,
251        ShouldNotQueryHistoryUiFaviconRequestHandlerIfNotAllowed) {
252   content::WebContentsTester::For(test_web_contents_.get())
253       ->SetLastCommittedURL(GURL(chrome::kChromeUIHistoryURL));
254 
255   EXPECT_CALL(*mock_history_ui_favicon_request_handler_,
256               GetRawFaviconForPageURL)
257       .Times(0);
258 
259   source()->StartDataRequest(
260       GURL(base::StrCat(
261           {kDummyPrefix,
262            "?size=16&scale_factor=1x&page_url=https%3A%2F%2Fwww.google."
263            "com&allow_google_server_fallback=0"})),
264       test_web_contents_getter_, base::BindOnce(&Noop));
265 }
266 
TEST_F(FaviconSourceTestWithFavicon2Format,ShouldNotQueryHistoryUiFaviconRequestHandlerIfHasNotHistoryUiOrigin)267 TEST_F(FaviconSourceTestWithFavicon2Format,
268        ShouldNotQueryHistoryUiFaviconRequestHandlerIfHasNotHistoryUiOrigin) {
269   content::WebContentsTester::For(test_web_contents_.get())
270       ->SetLastCommittedURL(GURL("chrome://non-history-url"));
271 
272   EXPECT_CALL(*mock_history_ui_favicon_request_handler_,
273               GetRawFaviconForPageURL)
274       .Times(0);
275 
276   source()->StartDataRequest(
277       GURL(base::StrCat(
278           {kDummyPrefix,
279            "?size=16&scale_factor=1x&page_url=https%3A%2F%2Fwww.google."
280            "com&allow_google_server_fallback=1"})),
281       test_web_contents_getter_, base::BindOnce(&Noop));
282 }
283 
TEST_F(FaviconSourceTestWithFavicon2Format,ShouldQueryHistoryUiFaviconRequestHandlerIfHasHistoryUiOriginAndAllowed)284 TEST_F(
285     FaviconSourceTestWithFavicon2Format,
286     ShouldQueryHistoryUiFaviconRequestHandlerIfHasHistoryUiOriginAndAllowed) {
287   content::WebContentsTester::For(test_web_contents_.get())
288       ->SetLastCommittedURL(GURL(chrome::kChromeUIHistoryURL));
289 
290   EXPECT_CALL(*mock_history_ui_favicon_request_handler_,
291               GetRawFaviconForPageURL(GURL("https://www.google.com"), _, _, _))
292       .Times(1);
293 
294   source()->StartDataRequest(
295       GURL(base::StrCat(
296           {kDummyPrefix,
297            "?size=16&scale_factor=1x&page_url=https%3A%2F%2Fwww.google."
298            "com&allow_google_server_fallback=1"})),
299       test_web_contents_getter_, base::BindOnce(&Noop));
300 }
301