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