blob: 2c99068f661574c05e9391eadcaecf6676681e0e [file] [log] [blame]
// Copyright 2017 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 "base/logging.h"
#include "base/memory/ptr_util.h"
#import "base/test/ios/wait_util.h"
#include "ios/web/public/favicon/favicon_url.h"
#import "ios/web/public/test/web_test_with_web_state.h"
#import "ios/web/public/web_state/web_state.h"
#import "ios/web/public/web_state/web_state_observer.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using base::test::ios::kWaitForActionTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;
namespace web {
namespace {
// Observes and waits for FaviconUrlUpdated call.
class FaviconUrlObserver : public WebStateObserver {
public:
FaviconUrlObserver() = default;
// Returns vavicon url candidates received in FaviconUrlUpdated.
const std::vector<FaviconURL>& favicon_url_candidates() const {
return favicon_url_candidates_;
}
// Returns true if FaviconUrlUpdated was called.
bool favicon_url_updated() const { return favicon_url_updated_; }
// WebStateObserver overrides:
void FaviconUrlUpdated(WebState* web_state,
const std::vector<FaviconURL>& candidates) override {
favicon_url_candidates_ = candidates;
favicon_url_updated_ = true;
}
void WebStateDestroyed(WebState* web_state) override { NOTREACHED(); }
private:
bool favicon_url_updated_ = false;
std::vector<FaviconURL> favicon_url_candidates_;
DISALLOW_COPY_AND_ASSIGN(FaviconUrlObserver);
};
} // namespace
// Test fixture for WebStateDelegate::FaviconUrlUpdated and integration tests.
class FaviconCallbackTest : public web::WebTestWithWebState {
public:
FaviconCallbackTest() = default;
protected:
void SetUp() override {
web::WebTestWithWebState::SetUp();
web_state()->AddObserver(observer());
}
void TearDown() override {
web_state()->RemoveObserver(observer());
web::WebTestWithWebState::TearDown();
}
FaviconUrlObserver* observer() { return &observer_; }
private:
FaviconUrlObserver observer_;
DISALLOW_COPY_AND_ASSIGN(FaviconCallbackTest);
};
// Tests page with shortcut icon link.
TEST_F(FaviconCallbackTest, ShortcutIconFavicon) {
ASSERT_TRUE(observer()->favicon_url_candidates().empty());
LoadHtml(@"<link rel='shortcut icon' href='http://fav.ico'>");
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^{
return observer()->favicon_url_updated();
}));
const std::vector<FaviconURL>& favicons =
observer()->favicon_url_candidates();
ASSERT_EQ(1U, favicons.size());
EXPECT_EQ(GURL("http://fav.ico"), favicons[0].icon_url);
EXPECT_EQ(FaviconURL::IconType::kFavicon, favicons[0].icon_type);
ASSERT_TRUE(favicons[0].icon_sizes.empty());
}
// Tests page with icon link and no sizes attribute.
TEST_F(FaviconCallbackTest, IconFavicon) {
ASSERT_TRUE(observer()->favicon_url_candidates().empty());
LoadHtml(@"<link rel='icon' href='http://fav.ico'>");
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^{
return observer()->favicon_url_updated();
}));
const std::vector<FaviconURL>& favicons =
observer()->favicon_url_candidates();
ASSERT_EQ(1U, favicons.size());
EXPECT_EQ(GURL("http://fav.ico"), favicons[0].icon_url);
EXPECT_EQ(FaviconURL::IconType::kFavicon, favicons[0].icon_type);
ASSERT_TRUE(favicons[0].icon_sizes.empty());
}
// Tests page with apple-touch-icon link.
TEST_F(FaviconCallbackTest, AppleTouchIconFavicon) {
ASSERT_TRUE(observer()->favicon_url_candidates().empty());
LoadHtml(@"<link rel='apple-touch-icon' href='http://fav.ico'>",
GURL("https://chromium.test"));
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^{
return observer()->favicon_url_updated();
}));
const std::vector<FaviconURL>& favicons =
observer()->favicon_url_candidates();
ASSERT_EQ(2U, favicons.size());
EXPECT_EQ(GURL("http://fav.ico"), favicons[0].icon_url);
EXPECT_EQ(FaviconURL::IconType::kTouchIcon, favicons[0].icon_type);
ASSERT_TRUE(favicons[0].icon_sizes.empty());
EXPECT_EQ(GURL("https://chromium.test/favicon.ico"), favicons[1].icon_url);
EXPECT_EQ(FaviconURL::IconType::kFavicon, favicons[1].icon_type);
ASSERT_TRUE(favicons[1].icon_sizes.empty());
}
// Tests page with apple-touch-icon-precomposed link.
TEST_F(FaviconCallbackTest, AppleTouchIconPrecomposedFavicon) {
ASSERT_TRUE(observer()->favicon_url_candidates().empty());
LoadHtml(@"<link rel='apple-touch-icon-precomposed' href='http://fav.ico'>",
GURL("https://chromium.test"));
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^{
return observer()->favicon_url_updated();
}));
const std::vector<FaviconURL>& favicons =
observer()->favicon_url_candidates();
ASSERT_EQ(2U, favicons.size());
EXPECT_EQ(GURL("http://fav.ico"), favicons[0].icon_url);
EXPECT_EQ(FaviconURL::IconType::kTouchPrecomposedIcon, favicons[0].icon_type);
ASSERT_TRUE(favicons[0].icon_sizes.empty());
EXPECT_EQ(GURL("https://chromium.test/favicon.ico"), favicons[1].icon_url);
EXPECT_EQ(FaviconURL::IconType::kFavicon, favicons[1].icon_type);
ASSERT_TRUE(favicons[1].icon_sizes.empty());
}
// Tests page without favicon link.
TEST_F(FaviconCallbackTest, NoFavicon) {
ASSERT_TRUE(observer()->favicon_url_candidates().empty());
LoadHtml(@"<html></html>", GURL("https://chromium.test/test/test.html"));
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^{
return observer()->favicon_url_updated();
}));
const std::vector<FaviconURL>& favicons =
observer()->favicon_url_candidates();
ASSERT_EQ(1U, favicons.size());
EXPECT_EQ(GURL("https://chromium.test/favicon.ico"), favicons[0].icon_url);
EXPECT_EQ(FaviconURL::IconType::kFavicon, favicons[0].icon_type);
ASSERT_TRUE(favicons[0].icon_sizes.empty());
}
// Tests page without favicon link but with a query and a ref in the URL.
TEST_F(FaviconCallbackTest, NoFaviconWithQuery) {
ASSERT_TRUE(observer()->favicon_url_candidates().empty());
LoadHtml(@"<html></html>",
GURL("https://chromium.test/test/test.html?q1#h1"));
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^{
return observer()->favicon_url_updated();
}));
const std::vector<FaviconURL>& favicons =
observer()->favicon_url_candidates();
ASSERT_EQ(1U, favicons.size());
EXPECT_EQ(GURL("https://chromium.test/favicon.ico"), favicons[0].icon_url);
EXPECT_EQ(FaviconURL::IconType::kFavicon, favicons[0].icon_type);
ASSERT_TRUE(favicons[0].icon_sizes.empty());
}
// Tests page with multiple favicon links.
TEST_F(FaviconCallbackTest, MultipleFavicons) {
ASSERT_TRUE(observer()->favicon_url_candidates().empty());
LoadHtml(@"<link rel='shortcut icon' href='http://fav.ico'>"
"<link rel='icon' href='http://fav1.ico'>"
"<link rel='apple-touch-icon' href='http://fav2.ico'>"
"<link rel='apple-touch-icon-precomposed' href='http://fav3.ico'>");
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^{
return observer()->favicon_url_updated();
}));
const std::vector<FaviconURL>& favicons =
observer()->favicon_url_candidates();
ASSERT_EQ(4U, favicons.size());
EXPECT_EQ(GURL("http://fav.ico"), favicons[0].icon_url);
EXPECT_EQ(FaviconURL::IconType::kFavicon, favicons[0].icon_type);
ASSERT_TRUE(favicons[0].icon_sizes.empty());
EXPECT_EQ(GURL("http://fav1.ico"), favicons[1].icon_url);
EXPECT_EQ(FaviconURL::IconType::kFavicon, favicons[1].icon_type);
ASSERT_TRUE(favicons[1].icon_sizes.empty());
EXPECT_EQ(GURL("http://fav2.ico"), favicons[2].icon_url);
EXPECT_EQ(FaviconURL::IconType::kTouchIcon, favicons[2].icon_type);
ASSERT_TRUE(favicons[2].icon_sizes.empty());
EXPECT_EQ(GURL("http://fav3.ico"), favicons[3].icon_url);
EXPECT_EQ(FaviconURL::IconType::kTouchPrecomposedIcon, favicons[3].icon_type);
ASSERT_TRUE(favicons[3].icon_sizes.empty());
}
// Tests page with invalid favicon url.
TEST_F(FaviconCallbackTest, InvalidFaviconUrl) {
ASSERT_TRUE(observer()->favicon_url_candidates().empty());
LoadHtml(@"<html><head><link rel='icon' href='http://'></head></html>",
GURL("https://chromium.test"));
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^{
return observer()->favicon_url_updated();
}));
const std::vector<FaviconURL>& favicons =
observer()->favicon_url_candidates();
ASSERT_EQ(1U, favicons.size());
EXPECT_EQ(GURL("https://chromium.test/favicon.ico"), favicons[0].icon_url);
EXPECT_EQ(FaviconURL::IconType::kFavicon, favicons[0].icon_type);
ASSERT_TRUE(favicons[0].icon_sizes.empty());
}
// Tests page with empty favicon url.
TEST_F(FaviconCallbackTest, EmptyFaviconUrl) {
ASSERT_TRUE(observer()->favicon_url_candidates().empty());
LoadHtml(@"<head><link rel='icon' href=''></head>");
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^{
return observer()->favicon_url_updated();
}));
const std::vector<FaviconURL>& favicons =
observer()->favicon_url_candidates();
ASSERT_EQ(1U, favicons.size());
// TODO(crbug.com/721852): This result is not correct.
EXPECT_EQ(GURL("https://chromium.test/"), favicons[0].icon_url);
EXPECT_EQ(FaviconURL::IconType::kFavicon, favicons[0].icon_type);
ASSERT_TRUE(favicons[0].icon_sizes.empty());
}
// Tests page with icon links and a sizes attribute.
TEST_F(FaviconCallbackTest, IconFaviconSizes) {
ASSERT_TRUE(observer()->favicon_url_candidates().empty());
LoadHtml(
@"<link rel='icon' href='http://fav.ico' sizes='10x20 30x40'><link "
@"rel='apple-touch-icon' href='http://fav2.ico' sizes='10x20 asdfx'>");
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForActionTimeout, ^{
return observer()->favicon_url_updated();
}));
const std::vector<FaviconURL>& favicons =
observer()->favicon_url_candidates();
ASSERT_EQ(2U, favicons.size());
EXPECT_EQ(GURL("http://fav.ico"), favicons[0].icon_url);
EXPECT_EQ(FaviconURL::IconType::kFavicon, favicons[0].icon_type);
ASSERT_EQ(2U, favicons[0].icon_sizes.size());
EXPECT_EQ(10, favicons[0].icon_sizes[0].width());
EXPECT_EQ(20, favicons[0].icon_sizes[0].height());
EXPECT_EQ(30, favicons[0].icon_sizes[1].width());
EXPECT_EQ(40, favicons[0].icon_sizes[1].height());
EXPECT_EQ(GURL("http://fav2.ico"), favicons[1].icon_url);
EXPECT_EQ(FaviconURL::IconType::kTouchIcon, favicons[1].icon_type);
ASSERT_EQ(1U, favicons[1].icon_sizes.size());
EXPECT_EQ(10, favicons[1].icon_sizes[0].width());
EXPECT_EQ(20, favicons[1].icon_sizes[0].height());
}
} // namespace web