blob: 4c26d6b54cff29f3695d9070d85b9cf28b967935 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/web_applications/web_app_icon_downloader.h"
#include <stddef.h>
#include "base/bind.h"
#include "base/files/scoped_temp_dir.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/web_applications/test/web_app_test.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/web_contents_tester.h"
#include "net/http/http_status_code.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/mojom/favicon/favicon_url.mojom.h"
#include "third_party/skia/include/core/SkBitmap.h"
using content::RenderViewHostTester;
namespace web_app {
namespace {
// Creates valid SkBitmaps of the dimensions found in |sizes| and pushes them
// into |bitmaps|.
std::vector<SkBitmap> CreateTestBitmaps(const std::vector<gfx::Size>& sizes) {
std::vector<SkBitmap> bitmaps(sizes.size());
for (size_t i = 0; i < sizes.size(); ++i) {
SkBitmap& bitmap = bitmaps[i];
bitmap.allocN32Pixels(sizes[i].width(), sizes[i].height());
bitmap.eraseColor(SK_ColorRED);
}
return bitmaps;
}
class WebAppIconDownloaderTest : public WebAppTest {
public:
WebAppIconDownloaderTest(const WebAppIconDownloaderTest&) = delete;
WebAppIconDownloaderTest& operator=(const WebAppIconDownloaderTest&) = delete;
WebAppIconDownloaderTest() = default;
~WebAppIconDownloaderTest() override = default;
void SetUp() override {
WebAppTest::SetUp();
// Specifies HTTPS for web_contents()->GetLastCommittedURL().
web_contents_tester()->NavigateAndCommit(GURL("https://www.example.com"));
}
protected:
content::WebContentsTester* web_contents_tester() {
return content::WebContentsTester::For(web_contents());
}
};
class WebAppIconDownloaderPrerenderTest : public WebAppIconDownloaderTest {
public:
WebAppIconDownloaderPrerenderTest() {
scoped_feature_list_.InitWithFeatures(
{blink::features::kPrerender2},
// This feature is to run test on any bot.
{blink::features::kPrerender2MemoryControls});
}
~WebAppIconDownloaderPrerenderTest() override = default;
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
} // namespace
class TestWebAppIconDownloader : public WebAppIconDownloader {
public:
TestWebAppIconDownloader(content::WebContents* web_contents,
std::vector<GURL> extra_favicon_urls)
: WebAppIconDownloader(
web_contents,
extra_favicon_urls,
base::BindOnce(&TestWebAppIconDownloader::DownloadsComplete,
base::Unretained(this))),
id_counter_(0) {}
TestWebAppIconDownloader(const TestWebAppIconDownloader&) = delete;
TestWebAppIconDownloader& operator=(const TestWebAppIconDownloader&) = delete;
~TestWebAppIconDownloader() override = default;
int DownloadImage(const GURL& url) override { return id_counter_++; }
const std::vector<blink::mojom::FaviconURLPtr>&
GetFaviconURLsFromWebContents() override {
return initial_favicon_urls_;
}
size_t pending_requests() const { return in_progress_requests_.size(); }
void DownloadsComplete(IconsDownloadedResult result,
IconsMap icons_map,
DownloadedIconsHttpResults icons_http_results) {
icons_download_result_ = result;
icons_map_ = std::move(icons_map);
icons_http_results_ = std::move(icons_http_results);
run_loop_.Quit();
}
void AwaitDownloadsComplete() {
run_loop_.Run();
ASSERT_EQ(0u, pending_requests());
}
const IconsMap& icons_map() const { return icons_map_; }
void CompleteImageDownload(
int id,
int http_status_code,
const GURL& image_url,
const std::vector<gfx::Size>& original_bitmap_sizes) {
WebAppIconDownloader::DidDownloadFavicon(
id, http_status_code, image_url,
CreateTestBitmaps(original_bitmap_sizes), original_bitmap_sizes);
}
void UpdateFaviconURLs(
const std::vector<blink::mojom::FaviconURLPtr>& candidates) {
WebAppIconDownloader::DidUpdateFaviconURL(web_contents()->GetMainFrame(),
candidates);
}
void set_initial_favicon_urls(
const std::vector<blink::mojom::FaviconURLPtr>& urls) {
for (const auto& url : urls)
initial_favicon_urls_.push_back(url.Clone());
}
IconsDownloadedResult icons_download_result() const {
return icons_download_result_.value();
}
const DownloadedIconsHttpResults& icons_http_results() const {
return icons_http_results_;
}
private:
std::vector<blink::mojom::FaviconURLPtr> initial_favicon_urls_;
IconsMap icons_map_;
DownloadedIconsHttpResults icons_http_results_;
int id_counter_;
absl::optional<IconsDownloadedResult> icons_download_result_;
base::RunLoop run_loop_;
};
TEST_F(WebAppIconDownloaderTest, SimpleDownload) {
const GURL favicon_url("http://www.google.com/favicon.ico");
TestWebAppIconDownloader downloader(web_contents(), std::vector<GURL>());
std::vector<blink::mojom::FaviconURLPtr> favicon_urls;
favicon_urls.push_back(blink::mojom::FaviconURL::New(
favicon_url, blink::mojom::FaviconIconType::kFavicon,
std::vector<gfx::Size>()));
downloader.set_initial_favicon_urls(favicon_urls);
EXPECT_EQ(0u, downloader.pending_requests());
downloader.Start();
EXPECT_EQ(1u, downloader.pending_requests());
std::vector<gfx::Size> sizes(1, gfx::Size(32, 32));
downloader.CompleteImageDownload(0, 200, favicon_urls[0]->icon_url, sizes);
downloader.AwaitDownloadsComplete();
EXPECT_EQ(1u, downloader.icons_map().size());
EXPECT_EQ(1u, downloader.icons_map().at(favicon_url).size());
EXPECT_EQ(downloader.icons_download_result(),
IconsDownloadedResult::kCompleted);
EXPECT_EQ(1u, downloader.icons_http_results().size());
EXPECT_EQ(net::HttpStatusCode::HTTP_OK,
downloader.icons_http_results().at(favicon_url));
}
TEST_F(WebAppIconDownloaderTest, NoHTTPStatusCode) {
const GURL favicon_url("data:image/png,");
TestWebAppIconDownloader downloader(web_contents(), std::vector<GURL>());
std::vector<blink::mojom::FaviconURLPtr> favicon_urls;
favicon_urls.push_back(blink::mojom::FaviconURL::New(
favicon_url, blink::mojom::FaviconIconType::kFavicon,
std::vector<gfx::Size>()));
downloader.set_initial_favicon_urls(favicon_urls);
EXPECT_EQ(0u, downloader.pending_requests());
downloader.Start();
EXPECT_EQ(1u, downloader.pending_requests());
std::vector<gfx::Size> sizes = {gfx::Size(0, 0)};
// data: URLs have a 0 HTTP status code.
downloader.CompleteImageDownload(0, 0, favicon_urls[0]->icon_url, sizes);
downloader.AwaitDownloadsComplete();
EXPECT_EQ(1u, downloader.icons_map().size());
EXPECT_EQ(1u, downloader.icons_map().at(favicon_url).size());
EXPECT_EQ(downloader.icons_download_result(),
IconsDownloadedResult::kCompleted)
<< "Should not consider data: URL or HTTP status code of 0 a failure";
EXPECT_TRUE(downloader.icons_http_results().empty());
}
TEST_F(WebAppIconDownloaderTest, DownloadWithUrlsFromWebContentsNotification) {
const GURL favicon_url("http://www.google.com/favicon.ico");
TestWebAppIconDownloader downloader(web_contents(), std::vector<GURL>());
std::vector<blink::mojom::FaviconURLPtr> favicon_urls;
favicon_urls.push_back(blink::mojom::FaviconURL::New(
favicon_url, blink::mojom::FaviconIconType::kFavicon,
std::vector<gfx::Size>()));
EXPECT_EQ(0u, downloader.pending_requests());
// Start downloader before favicon URLs are loaded.
downloader.Start();
EXPECT_EQ(0u, downloader.pending_requests());
downloader.UpdateFaviconURLs(favicon_urls);
EXPECT_EQ(1u, downloader.pending_requests());
std::vector<gfx::Size> sizes(1, gfx::Size(32, 32));
downloader.CompleteImageDownload(0, 200, favicon_urls[0]->icon_url, sizes);
downloader.AwaitDownloadsComplete();
EXPECT_EQ(1u, downloader.icons_map().size());
EXPECT_EQ(1u, downloader.icons_map().at(favicon_url).size());
EXPECT_EQ(downloader.icons_download_result(),
IconsDownloadedResult::kCompleted);
EXPECT_EQ(1u, downloader.icons_http_results().size());
EXPECT_EQ(net::HttpStatusCode::HTTP_OK,
downloader.icons_http_results().at(favicon_url));
}
TEST_F(WebAppIconDownloaderTest, DownloadMultipleUrls) {
const GURL empty_favicon("http://www.google.com/empty_favicon.ico");
const GURL favicon_url_1("http://www.google.com/favicon.ico");
const GURL favicon_url_2("http://www.google.com/favicon2.ico");
std::vector<GURL> extra_urls;
// This should get downloaded.
extra_urls.push_back(favicon_url_2);
// This is duplicated in the favicon urls and should only be downloaded once.
extra_urls.push_back(empty_favicon);
TestWebAppIconDownloader downloader(web_contents(), extra_urls);
std::vector<blink::mojom::FaviconURLPtr> favicon_urls;
favicon_urls.push_back(blink::mojom::FaviconURL::New(
favicon_url_1, blink::mojom::FaviconIconType::kFavicon,
std::vector<gfx::Size>()));
// This is duplicated in the favicon urls and should only be downloaded once.
favicon_urls.push_back(blink::mojom::FaviconURL::New(
empty_favicon, blink::mojom::FaviconIconType::kFavicon,
std::vector<gfx::Size>()));
// Invalid icons shouldn't get put into the download queue.
favicon_urls.push_back(blink::mojom::FaviconURL::New(
GURL("http://www.google.com/invalid.ico"),
blink::mojom::FaviconIconType::kInvalid, std::vector<gfx::Size>()));
downloader.set_initial_favicon_urls(favicon_urls);
downloader.Start();
EXPECT_EQ(3u, downloader.pending_requests());
std::vector<gfx::Size> sizes_1(1, gfx::Size(16, 16));
downloader.CompleteImageDownload(0, 200, favicon_url_1, sizes_1);
std::vector<gfx::Size> sizes_2;
sizes_2.emplace_back(32, 32);
sizes_2.emplace_back(64, 64);
downloader.CompleteImageDownload(1, 200, favicon_url_2, sizes_2);
// Only 1 download should have been initiated for |empty_favicon| even though
// the URL was in both the web app info and the favicon urls.
downloader.CompleteImageDownload(2, 200, empty_favicon,
std::vector<gfx::Size>());
downloader.AwaitDownloadsComplete();
EXPECT_EQ(3u, downloader.icons_map().size());
EXPECT_EQ(0u, downloader.icons_map().at(empty_favicon).size());
EXPECT_EQ(1u, downloader.icons_map().at(favicon_url_1).size());
EXPECT_EQ(2u, downloader.icons_map().at(favicon_url_2).size());
EXPECT_EQ(downloader.icons_download_result(),
IconsDownloadedResult::kCompleted);
EXPECT_EQ(3u, downloader.icons_http_results().size());
EXPECT_EQ(net::HttpStatusCode::HTTP_OK,
downloader.icons_http_results().at(empty_favicon));
EXPECT_EQ(net::HttpStatusCode::HTTP_OK,
downloader.icons_http_results().at(favicon_url_1));
EXPECT_EQ(net::HttpStatusCode::HTTP_OK,
downloader.icons_http_results().at(favicon_url_2));
}
TEST_F(WebAppIconDownloaderTest, SkipPageFavicons) {
const GURL favicon_url_1("http://www.google.com/favicon.ico");
const GURL favicon_url_2("http://www.google.com/favicon2.ico");
std::vector<GURL> extra_urls;
extra_urls.push_back(favicon_url_1);
TestWebAppIconDownloader downloader(web_contents(), extra_urls);
// This favicon URL should be ignored.
std::vector<blink::mojom::FaviconURLPtr> favicon_urls;
favicon_urls.push_back(blink::mojom::FaviconURL::New(
favicon_url_2, blink::mojom::FaviconIconType::kFavicon,
std::vector<gfx::Size>()));
downloader.set_initial_favicon_urls(favicon_urls);
downloader.SkipPageFavicons();
downloader.Start();
EXPECT_EQ(1u, downloader.pending_requests());
std::vector<gfx::Size> sizes_1(1, gfx::Size(16, 16));
downloader.CompleteImageDownload(0, 200, favicon_url_1, sizes_1);
// This download should not be finished and inserted into the map.
std::vector<gfx::Size> sizes_2;
sizes_2.emplace_back(32, 32);
sizes_2.emplace_back(64, 64);
downloader.CompleteImageDownload(1, 200, favicon_url_2, sizes_2);
downloader.AwaitDownloadsComplete();
EXPECT_EQ(1u, downloader.icons_map().size());
EXPECT_EQ(1u, downloader.icons_map().at(favicon_url_1).size());
EXPECT_EQ(0u, downloader.icons_map().count(favicon_url_2));
EXPECT_EQ(downloader.icons_download_result(),
IconsDownloadedResult::kCompleted);
EXPECT_EQ(1u, downloader.icons_http_results().size());
EXPECT_EQ(net::HttpStatusCode::HTTP_OK,
downloader.icons_http_results().at(favicon_url_1));
}
TEST_F(WebAppIconDownloaderTest, PageNavigates) {
TestWebAppIconDownloader downloader(web_contents(), std::vector<GURL>());
std::vector<blink::mojom::FaviconURLPtr> favicon_urls;
favicon_urls.push_back(blink::mojom::FaviconURL::New(
GURL("http://www.google.com/favicon.ico"),
blink::mojom::FaviconIconType::kFavicon, std::vector<gfx::Size>()));
downloader.set_initial_favicon_urls(favicon_urls);
EXPECT_EQ(0u, downloader.pending_requests());
downloader.Start();
EXPECT_EQ(1u, downloader.pending_requests());
content::NavigationSimulator::CreateRendererInitiated(
GURL("https://foo.example"), main_rfh())
->Commit();
EXPECT_EQ(0u, downloader.pending_requests());
EXPECT_TRUE(downloader.icons_map().empty());
EXPECT_EQ(downloader.icons_download_result(),
IconsDownloadedResult::kPrimaryPageChanged);
}
TEST_F(WebAppIconDownloaderTest, PageNavigatesAfterDownload) {
const GURL url("http://www.google.com/icon.png");
TestWebAppIconDownloader downloader(web_contents(), {url});
downloader.SkipPageFavicons();
downloader.Start();
EXPECT_EQ(1u, downloader.pending_requests());
downloader.CompleteImageDownload(0, 200, url, {gfx::Size(32, 32)});
downloader.AwaitDownloadsComplete();
EXPECT_EQ(downloader.icons_download_result(),
IconsDownloadedResult::kCompleted);
// Navigating the renderer after downloads have completed should not crash.
content::NavigationSimulator::CreateRendererInitiated(
GURL("https://foo.example"), main_rfh())
->Commit();
}
TEST_F(WebAppIconDownloaderTest, PageNavigatesSameDocument) {
content::NavigationSimulator::NavigateAndCommitFromBrowser(
web_contents(), GURL("https://foo.example"));
const GURL favicon_url("http://www.google.com/favicon.ico");
TestWebAppIconDownloader downloader(web_contents(), std::vector<GURL>());
std::vector<blink::mojom::FaviconURLPtr> favicon_urls;
favicon_urls.push_back(blink::mojom::FaviconURL::New(
favicon_url, blink::mojom::FaviconIconType::kFavicon,
std::vector<gfx::Size>()));
downloader.set_initial_favicon_urls(favicon_urls);
EXPECT_EQ(0u, downloader.pending_requests());
downloader.Start();
EXPECT_EQ(1u, downloader.pending_requests());
content::NavigationSimulator::CreateRendererInitiated(
GURL("https://foo.example/#test"), main_rfh())
->CommitSameDocument();
EXPECT_EQ(1u, downloader.pending_requests());
std::vector<gfx::Size> sizes(1, gfx::Size(32, 32));
downloader.CompleteImageDownload(0, 200, favicon_url, sizes);
downloader.AwaitDownloadsComplete();
EXPECT_EQ(1u, downloader.icons_map().size());
EXPECT_EQ(1u, downloader.icons_map().at(favicon_url).size());
EXPECT_EQ(downloader.icons_download_result(),
IconsDownloadedResult::kCompleted);
EXPECT_EQ(1u, downloader.icons_http_results().size());
EXPECT_EQ(net::HttpStatusCode::HTTP_OK,
downloader.icons_http_results().at(favicon_url));
}
TEST_F(WebAppIconDownloaderPrerenderTest, PrerenderedPageNavigates) {
// Navigate to an initial page.
NavigateAndCommit(GURL("http://foo.example"));
const GURL favicon_url("http://www.google.com/favicon.ico");
TestWebAppIconDownloader downloader(web_contents(), std::vector<GURL>());
std::vector<blink::mojom::FaviconURLPtr> favicon_urls;
favicon_urls.push_back(blink::mojom::FaviconURL::New(
favicon_url, blink::mojom::FaviconIconType::kFavicon,
std::vector<gfx::Size>()));
downloader.set_initial_favicon_urls(favicon_urls);
EXPECT_EQ(0u, downloader.pending_requests());
downloader.Start();
EXPECT_EQ(1u, downloader.pending_requests());
// Start a prerender and navigate the test page.
const GURL& prerender_url = GURL("http://foo.example/bar");
content::RenderFrameHost* prerender_frame =
content::WebContentsTester::For(web_contents())
->AddPrerenderAndCommitNavigation(prerender_url);
ASSERT_EQ(prerender_frame->GetLifecycleState(),
content::RenderFrameHost::LifecycleState::kPrerendering);
// Ensure prerender navigation doesn't cancel pending download requests.
EXPECT_EQ(1u, downloader.pending_requests());
// Activate the prerendered page.
content::NavigationSimulator::CreateRendererInitiated(
prerender_url, web_contents()->GetMainFrame())
->Commit();
// Ensure prerender activation cancel pending download requests.
EXPECT_EQ(0u, downloader.pending_requests());
EXPECT_TRUE(downloader.icons_map().empty());
EXPECT_EQ(downloader.icons_download_result(),
IconsDownloadedResult::kPrimaryPageChanged);
}
} // namespace web_app