| // 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_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 |