#include "third_party/blink/public/common/manifest/manifest.h"
#include <stdint.h>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/path_service.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/test_future.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/public/browser/manifest_icon_downloader.h"
#include "content/public/browser/page.h"
#include "content/public/browser/page_manifest_manager.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/shell/browser/shell.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "third_party/blink/public/common/manifest/manifest_util.h"
#include "third_party/blink/public/mojom/favicon/favicon_url.mojom.h"
#include "third_party/blink/public/mojom/manifest/manifest.mojom.h"
#include "third_party/blink/public/mojom/manifest/manifest_manager.mojom.h"
namespace content {
namespace {
using ::testing::Contains;
using ::testing::HasSubstr;
class ManifestBrowserTest;
class MockWebContentsDelegate : public WebContentsDelegate {
public:
explicit MockWebContentsDelegate(ManifestBrowserTest* test) : test_(test) {}
bool DidAddMessageToConsole(WebContents* source,
blink::mojom::ConsoleMessageLevel log_level,
#if BUILDFLAG(ARKWEB_CONSOLE_LOGGING)
blink::mojom::ConsoleMessageSource log_source,
#endif
const std::u16string& message,
int32_t line_no,
const std::u16string& source_id) override;
PreloadingEligibility IsPrerender2Supported(
WebContents& web_contents,
PreloadingTriggerType trigger_type) override {
return PreloadingEligibility::kEligible;
}
private:
raw_ptr<ManifestBrowserTest> test_ = nullptr;
};
class ManifestBrowserTest : public ContentBrowserTest,
public WebContentsObserver {
protected:
friend MockWebContentsDelegate;
ManifestBrowserTest()
: cors_embedded_test_server_(
std::make_unique<net::EmbeddedTestServer>()) {
cors_embedded_test_server_->ServeFilesFromSourceDirectory(
GetTestDataFilePath());
}
ManifestBrowserTest(const ManifestBrowserTest&) = delete;
ManifestBrowserTest& operator=(const ManifestBrowserTest&) = delete;
~ManifestBrowserTest() override {}
void SetUpOnMainThread() override {
ContentBrowserTest::SetUpOnMainThread();
DCHECK(shell()->web_contents());
mock_web_contents_delegate_ =
std::make_unique<MockWebContentsDelegate>(this);
shell()->web_contents()->SetDelegate(mock_web_contents_delegate_.get());
Observe(shell()->web_contents());
ASSERT_TRUE(embedded_test_server()->Start());
}
void GetManifestAndWait() {
shell()->web_contents()->GetPrimaryPage().GetManifest(base::BindOnce(
&ManifestBrowserTest::OnGetManifest, base::Unretained(this)));
message_loop_runner_ = new MessageLoopRunner();
message_loop_runner_->Run();
}
void OnGetManifest(blink::mojom::ManifestRequestResult result,
const GURL& manifest_url,
blink::mojom::ManifestPtr manifest) {
manifest_url_ = manifest_url;
manifest_ = std::move(manifest);
message_loop_runner_->Quit();
}
const blink::mojom::Manifest& manifest() const {
DCHECK(manifest_);
return *manifest_;
}
const GURL& manifest_url() const { return manifest_url_; }
int GetConsoleErrorCount() const {
mojo::AssociatedRemote<blink::mojom::ManifestManager> remote;
shell()
->web_contents()
->GetPrimaryMainFrame()
->GetRemoteAssociatedInterfaces()
->GetInterface(&remote);
remote.FlushForTesting();
return console_errors_.size();
}
const std::vector<std::string>& console_errors() const {
return console_errors_;
}
void OnReceivedConsoleError(std::u16string_view message) {
console_errors_.push_back(base::UTF16ToUTF8(message));
}
net::EmbeddedTestServer* cors_embedded_test_server() const {
return cors_embedded_test_server_.get();
}
const std::vector<GURL>& reported_manifest_urls() {
return reported_manifest_urls_;
}
const std::vector<size_t>& manifests_reported_when_favicon_url_updated() {
return manifests_reported_when_favicon_url_updated_;
}
void DidUpdateFaviconURL(
RenderFrameHost* rfh,
const std::vector<blink::mojom::FaviconURLPtr>& candidates) override {
manifests_reported_when_favicon_url_updated_.push_back(
reported_manifest_urls_.size());
}
void DidUpdateWebManifestURL(RenderFrameHost* rfh,
const GURL& manifest_url) override {
if (manifest_url.is_empty()) {
reported_manifest_urls_.emplace_back();
return;
}
EXPECT_TRUE(manifest_url.is_valid());
reported_manifest_urls_.push_back(manifest_url);
}
private:
scoped_refptr<MessageLoopRunner> message_loop_runner_;
std::unique_ptr<MockWebContentsDelegate> mock_web_contents_delegate_;
std::unique_ptr<net::EmbeddedTestServer> cors_embedded_test_server_;
GURL manifest_url_;
blink::mojom::ManifestPtr manifest_ = blink::mojom::Manifest::New();
std::vector<std::string> console_errors_;
std::vector<GURL> reported_manifest_urls_;
std::vector<size_t> manifests_reported_when_favicon_url_updated_;
};
bool MockWebContentsDelegate::DidAddMessageToConsole(
WebContents* source,
blink::mojom::ConsoleMessageLevel log_level,
#if BUILDFLAG(ARKWEB_CONSOLE_LOGGING)
blink::mojom::ConsoleMessageSource log_source,
#endif
const std::u16string& message,
int32_t line_no,
const std::u16string& source_id) {
DCHECK_EQ(source->GetDelegate(), this);
if (log_level == blink::mojom::ConsoleMessageLevel::kError ||
log_level == blink::mojom::ConsoleMessageLevel::kWarning) {
test_->OnReceivedConsoleError(message);
}
return false;
}
IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, NoManifest) {
GURL test_url = embedded_test_server()->GetURL("/manifest/no-manifest.html");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
GetManifestAndWait();
EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
EXPECT_TRUE(blink::IsDefaultManifest(manifest(), test_url));
EXPECT_TRUE(manifest_url().is_empty());
EXPECT_EQ(0, GetConsoleErrorCount());
EXPECT_TRUE(reported_manifest_urls().empty());
ASSERT_EQ(1u, manifests_reported_when_favicon_url_updated().size());
EXPECT_EQ(0u, manifests_reported_when_favicon_url_updated()[0]);
}
IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, 404Manifest) {
GURL test_url = embedded_test_server()->GetURL("/manifest/404-manifest.html");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
GetManifestAndWait();
EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
EXPECT_TRUE(blink::IsDefaultManifest(manifest(), test_url));
EXPECT_FALSE(manifest_url().is_empty());
EXPECT_EQ(1, GetConsoleErrorCount());
ASSERT_EQ(1u, reported_manifest_urls().size());
EXPECT_EQ(manifest_url(), reported_manifest_urls()[0]);
EXPECT_EQ(1u, manifests_reported_when_favicon_url_updated().size());
}
IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, EmptyManifest) {
GURL test_url =
embedded_test_server()->GetURL("/manifest/empty-manifest.html");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
GetManifestAndWait();
EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
EXPECT_FALSE(manifest_url().is_empty());
ASSERT_EQ(test_url.GetWithoutFilename(), manifest().scope);
EXPECT_EQ(0, GetConsoleErrorCount());
ASSERT_EQ(1u, reported_manifest_urls().size());
EXPECT_EQ(manifest_url(), reported_manifest_urls()[0]);
ASSERT_EQ(1u, manifests_reported_when_favicon_url_updated().size());
EXPECT_EQ(1u, manifests_reported_when_favicon_url_updated()[0]);
}
IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, ParseErrorManifest) {
GURL test_url =
embedded_test_server()->GetURL("/manifest/parse-error-manifest.html");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
GetManifestAndWait();
EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
EXPECT_TRUE(blink::IsDefaultManifest(manifest(), test_url));
EXPECT_FALSE(manifest_url().is_empty());
EXPECT_EQ(1, GetConsoleErrorCount());
ASSERT_EQ(1u, reported_manifest_urls().size());
EXPECT_EQ(manifest_url(), reported_manifest_urls()[0]);
ASSERT_EQ(1u, manifests_reported_when_favicon_url_updated().size());
EXPECT_EQ(1u, manifests_reported_when_favicon_url_updated()[0]);
}
IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, SampleManifest) {
GURL test_url =
embedded_test_server()->GetURL("/manifest/sample-manifest.html");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
GetManifestAndWait();
EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
EXPECT_FALSE(manifest_url().is_empty());
EXPECT_EQ(0, GetConsoleErrorCount());
ASSERT_EQ(1u, reported_manifest_urls().size());
EXPECT_EQ(manifest_url(), reported_manifest_urls()[0]);
ASSERT_EQ(1u, manifests_reported_when_favicon_url_updated().size());
EXPECT_EQ(1u, manifests_reported_when_favicon_url_updated()[0]);
}
IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, DynamicManifest) {
GURL test_url =
embedded_test_server()->GetURL("/manifest/dynamic-manifest.html");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
std::vector<GURL> expected_manifest_urls;
{
GetManifestAndWait();
EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
EXPECT_TRUE(blink::IsDefaultManifest(manifest(), test_url));
EXPECT_TRUE(manifest_url().is_empty());
EXPECT_TRUE(reported_manifest_urls().empty());
}
{
std::string manifest_link =
embedded_test_server()->GetURL("/manifest/sample-manifest.json").spec();
ASSERT_TRUE(ExecJs(shell(), "setManifestTo('" + manifest_link + "')"));
GetManifestAndWait();
EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
EXPECT_FALSE(blink::IsDefaultManifest(manifest(), test_url));
EXPECT_FALSE(manifest_url().is_empty());
expected_manifest_urls.push_back(manifest_url());
EXPECT_EQ(expected_manifest_urls, reported_manifest_urls());
}
{
std::string manifest_link =
embedded_test_server()->GetURL("/manifest/empty-manifest.json").spec();
ASSERT_TRUE(ExecJs(shell(), "setManifestTo('" + manifest_link + "')"));
GetManifestAndWait();
EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
EXPECT_FALSE(manifest_url().is_empty());
expected_manifest_urls.push_back(manifest_url());
EXPECT_EQ(expected_manifest_urls, reported_manifest_urls());
}
{
ASSERT_TRUE(ExecJs(shell(), "clearManifest()"));
GetManifestAndWait();
EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
EXPECT_TRUE(blink::IsDefaultManifest(manifest(), test_url));
EXPECT_TRUE(manifest_url().is_empty());
expected_manifest_urls.push_back(manifest_url());
EXPECT_EQ(expected_manifest_urls, reported_manifest_urls());
ASSERT_EQ(1u, manifests_reported_when_favicon_url_updated().size());
EXPECT_EQ(0u, manifests_reported_when_favicon_url_updated()[0]);
}
EXPECT_EQ(0, GetConsoleErrorCount());
}
IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, FileHandlerManifest) {
GURL test_url =
embedded_test_server()->GetURL("/manifest/file-handler-manifest.html");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
GetManifestAndWait();
EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
EXPECT_FALSE(manifest_url().is_empty());
EXPECT_FALSE(manifest().file_handlers.empty());
EXPECT_EQ(0, GetConsoleErrorCount());
ASSERT_EQ(1u, reported_manifest_urls().size());
EXPECT_EQ(manifest_url(), reported_manifest_urls()[0]);
}
IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, DISABLED_CorsManifest) {
ASSERT_TRUE(cors_embedded_test_server()->Start());
ASSERT_NE(embedded_test_server()->port(),
cors_embedded_test_server()->port());
GURL test_url =
embedded_test_server()->GetURL("/manifest/dynamic-manifest.html");
std::vector<GURL> expected_manifest_urls;
ASSERT_TRUE(NavigateToURL(shell(), test_url));
std::string manifest_link = cors_embedded_test_server()
->GetURL("/manifest/sample-manifest.json")
.spec();
ASSERT_TRUE(ExecJs(shell(), "setManifestTo('" + manifest_link + "')"));
GetManifestAndWait();
EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
EXPECT_TRUE(blink::IsDefaultManifest(manifest(), test_url));
EXPECT_FALSE(manifest_url().is_empty());
EXPECT_THAT(console_errors(), Contains(HasSubstr("CORS")));
EXPECT_EQ(1, GetConsoleErrorCount());
expected_manifest_urls.push_back(manifest_url());
EXPECT_EQ(expected_manifest_urls, reported_manifest_urls());
manifest_link =
embedded_test_server()->GetURL("/manifest/sample-manifest.json").spec();
ASSERT_TRUE(ExecJs(shell(), "setManifestTo('" + manifest_link + "')"));
GetManifestAndWait();
expected_manifest_urls.push_back(manifest_url());
EXPECT_EQ(expected_manifest_urls, reported_manifest_urls());
ASSERT_EQ(1u, manifests_reported_when_favicon_url_updated().size());
EXPECT_EQ(0u, manifests_reported_when_favicon_url_updated()[0]);
}
IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, CorsManifestWithAcessControls) {
ASSERT_TRUE(cors_embedded_test_server()->Start());
ASSERT_NE(embedded_test_server()->port(),
cors_embedded_test_server()->port());
GURL test_url =
embedded_test_server()->GetURL("/manifest/dynamic-manifest.html");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
std::string manifest_link = cors_embedded_test_server()
->GetURL("/manifest/manifest-cors.json")
.spec();
ASSERT_TRUE(ExecJs(shell(), "setManifestTo('" + manifest_link + "')"));
GetManifestAndWait();
EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
EXPECT_FALSE(manifest_url().is_empty());
EXPECT_EQ(0, GetConsoleErrorCount());
ASSERT_EQ(1u, reported_manifest_urls().size());
EXPECT_EQ(manifest_url(), reported_manifest_urls()[0]);
ASSERT_EQ(1u, manifests_reported_when_favicon_url_updated().size());
EXPECT_EQ(0u, manifests_reported_when_favicon_url_updated()[0]);
}
IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, DISABLED_MixedContentManifest) {
ASSERT_TRUE(cors_embedded_test_server()->Start());
std::unique_ptr<net::EmbeddedTestServer> https_server(
new net::EmbeddedTestServer(net::EmbeddedTestServer::TYPE_HTTPS));
https_server->ServeFilesFromSourceDirectory(GetTestDataFilePath());
ASSERT_TRUE(https_server->Start());
GURL test_url = https_server->GetURL("/manifest/dynamic-manifest.html");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
GURL manifest_link = cors_embedded_test_server()->GetURL(
"insecure.example", "/manifest/manifest-cors.json");
ASSERT_FALSE(network::IsUrlPotentiallyTrustworthy(manifest_link));
ASSERT_TRUE(ExecJs(shell(), JsReplace("setManifestTo($1)", manifest_link)));
GetManifestAndWait();
EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
EXPECT_TRUE(blink::IsDefaultManifest(manifest(), test_url));
EXPECT_FALSE(manifest_url().is_empty());
EXPECT_THAT(console_errors(), Contains(HasSubstr("Mixed Content")));
ASSERT_EQ(1u, reported_manifest_urls().size());
EXPECT_EQ(manifest_url(), reported_manifest_urls()[0]);
ASSERT_EQ(1u, manifests_reported_when_favicon_url_updated().size());
EXPECT_EQ(0u, manifests_reported_when_favicon_url_updated()[0]);
}
IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, ParsingErrorsManifest) {
GURL test_url =
embedded_test_server()->GetURL("/manifest/parsing-errors.html");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
GetManifestAndWait();
EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
EXPECT_FALSE(manifest_url().is_empty());
ASSERT_EQ(test_url.GetWithoutFilename(), manifest().scope);
EXPECT_EQ(7, GetConsoleErrorCount());
ASSERT_EQ(1u, reported_manifest_urls().size());
EXPECT_EQ(manifest_url(), reported_manifest_urls()[0]);
ASSERT_EQ(1u, manifests_reported_when_favicon_url_updated().size());
EXPECT_EQ(1u, manifests_reported_when_favicon_url_updated()[0]);
}
IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, Navigation) {
std::vector<GURL> expected_manifest_urls;
{
GURL test_url =
embedded_test_server()->GetURL("/manifest/sample-manifest.html");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
GetManifestAndWait();
EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
EXPECT_FALSE(manifest_url().is_empty());
EXPECT_EQ(0, GetConsoleErrorCount());
expected_manifest_urls.push_back(manifest_url());
EXPECT_EQ(expected_manifest_urls, reported_manifest_urls());
ASSERT_EQ(1u, manifests_reported_when_favicon_url_updated().size());
EXPECT_EQ(1u, manifests_reported_when_favicon_url_updated()[0]);
}
{
GURL test_url =
embedded_test_server()->GetURL("/manifest/no-manifest.html");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
GetManifestAndWait();
EXPECT_TRUE(blink::IsDefaultManifest(manifest(), test_url));
EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
EXPECT_EQ(0, GetConsoleErrorCount());
EXPECT_TRUE(manifest_url().is_empty());
EXPECT_EQ(expected_manifest_urls, reported_manifest_urls());
ASSERT_EQ(2u, manifests_reported_when_favicon_url_updated().size());
EXPECT_EQ(1u, manifests_reported_when_favicon_url_updated()[1]);
}
{
GURL test_url =
embedded_test_server()->GetURL("/manifest/sample-manifest.html");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
GetManifestAndWait();
EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
EXPECT_FALSE(manifest_url().is_empty());
EXPECT_EQ(0, GetConsoleErrorCount());
expected_manifest_urls.push_back(manifest_url());
EXPECT_EQ(expected_manifest_urls, reported_manifest_urls());
ASSERT_EQ(3u, manifests_reported_when_favicon_url_updated().size());
EXPECT_EQ(2u, manifests_reported_when_favicon_url_updated()[2]);
}
}
base::expected<blink::mojom::ManifestPtr, blink::mojom::RequestManifestErrorPtr>
CopyMojoExpectedConstRef(
const base::expected<blink::mojom::ManifestPtr,
blink::mojom::RequestManifestErrorPtr>&
const_ref_result) {
if (const_ref_result.has_value()) {
return base::ok(const_ref_result->Clone());
} else {
return base::unexpected(const_ref_result.error()->Clone());
}
}
IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, ManifestSubscription) {
GURL test_url =
embedded_test_server()->GetURL("/manifest/dynamic-manifest.html");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
{
base::test::TestFuture<base::expected<
blink::mojom::ManifestPtr, blink::mojom::RequestManifestErrorPtr>>
manifest_future;
PageManifestManager* manifest_manager = PageManifestManager::GetOrCreate(
shell()->web_contents()->GetPrimaryPage());
auto subscription = manifest_manager->GetSpecifiedManifest(
base::BindOnce(&CopyMojoExpectedConstRef)
.Then(manifest_future.GetCallback()));
std::string manifest_link =
embedded_test_server()->GetURL("/manifest/sample-manifest.json").spec();
ASSERT_TRUE(ExecJs(shell(), "setManifestTo('" + manifest_link + "')"));
ASSERT_TRUE(manifest_future.Wait());
ASSERT_TRUE(manifest_future.Get().has_value());
blink::mojom::Manifest& manifest = *manifest_future.Get().value();
EXPECT_FALSE(blink::IsEmptyManifest(manifest));
EXPECT_FALSE(blink::IsDefaultManifest(manifest, test_url));
EXPECT_FALSE(manifest.manifest_url.is_empty());
}
GURL no_manifest_url =
embedded_test_server()->GetURL("/manifest/no-manifest.html");
ASSERT_TRUE(NavigateToURL(shell(), no_manifest_url));
ASSERT_TRUE(NavigateToURL(shell(), test_url));
{
base::test::TestFuture<base::expected<
blink::mojom::ManifestPtr, blink::mojom::RequestManifestErrorPtr>>
manifest_future;
PageManifestManager* manifest_manager = PageManifestManager::GetOrCreate(
shell()->web_contents()->GetPrimaryPage());
auto subscription = manifest_manager->GetSpecifiedManifest(
base::BindOnce(&CopyMojoExpectedConstRef)
.Then(manifest_future.GetCallback()));
std::string manifest_link =
embedded_test_server()->GetURL("/manifest/nomanifesthere.json").spec();
ASSERT_TRUE(ExecJs(shell(), "setManifestTo('" + manifest_link + "')"));
ASSERT_TRUE(manifest_future.Wait());
ASSERT_FALSE(manifest_future.Get().has_value())
<< manifest_future.Get().value()->start_url.spec();
blink::mojom::RequestManifestError& error = *manifest_future.Get().error();
EXPECT_EQ(blink::mojom::ManifestRequestResult::kManifestFailedToFetch,
error.error);
}
}
IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, PushStateNavigation) {
GURL test_url =
embedded_test_server()->GetURL("/manifest/sample-manifest.html");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
{
TestNavigationObserver navigation_observer(shell()->web_contents(), 1);
ASSERT_TRUE(ExecJs(
shell(), "history.pushState({foo: \"bar\"}, 'page', 'page.html');"));
navigation_observer.Wait();
}
GetManifestAndWait();
EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
EXPECT_FALSE(manifest_url().is_empty());
EXPECT_EQ(0, GetConsoleErrorCount());
ASSERT_EQ(1u, reported_manifest_urls().size());
EXPECT_EQ(manifest_url(), reported_manifest_urls()[0]);
ASSERT_EQ(2u, manifests_reported_when_favicon_url_updated().size());
EXPECT_EQ(1u, manifests_reported_when_favicon_url_updated()[0]);
EXPECT_EQ(1u, manifests_reported_when_favicon_url_updated()[1]);
}
IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, AnchorNavigation) {
GURL test_url =
embedded_test_server()->GetURL("/manifest/sample-manifest.html");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
{
TestNavigationObserver navigation_observer(shell()->web_contents(), 1);
ASSERT_TRUE(ExecJs(shell(),
"var a = document.createElement('a'); a.href='#foo';"
"document.body.appendChild(a); a.click();"));
navigation_observer.Wait();
}
GetManifestAndWait();
EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
EXPECT_FALSE(manifest_url().is_empty());
EXPECT_EQ(0, GetConsoleErrorCount());
ASSERT_EQ(1u, reported_manifest_urls().size());
EXPECT_EQ(manifest_url(), reported_manifest_urls()[0]);
ASSERT_EQ(2u, manifests_reported_when_favicon_url_updated().size());
EXPECT_EQ(1u, manifests_reported_when_favicon_url_updated()[0]);
EXPECT_EQ(1u, manifests_reported_when_favicon_url_updated()[1]);
}
using ManifestIconDownloaderBrowserTest = ManifestBrowserTest;
IN_PROC_BROWSER_TEST_F(ManifestIconDownloaderBrowserTest, BadIcon) {
base::HistogramTester tester;
GURL icon_url = embedded_test_server()->GetURL("/manifest/bad_icon.png");
base::test::TestFuture<const SkBitmap&> test_future;
content::ManifestIconDownloader::Download(
shell()->web_contents(), icon_url, 192,
192, 192,
test_future.GetCallback(), false);
EXPECT_TRUE(test_future.Wait());
EXPECT_TRUE(test_future.Get().drawsNothing());
tester.ExpectBucketCount("WebApp.ManifestIconDownloader.Result",
ManifestIconDownloader::Result::kNoImageFound, 1);
tester.ExpectTotalCount("WebApp.ManifestIconDownloader.ChromeUrl.Result", 0);
}
IN_PROC_BROWSER_TEST_F(ManifestIconDownloaderBrowserTest, HungIcon) {
base::HistogramTester tester;
GURL icon_url = embedded_test_server()->GetURL("/manifest/hung");
base::test::TestFuture<const SkBitmap&> test_future;
content::ManifestIconDownloader::Download(
shell()->web_contents(), icon_url, 64,
64, 64,
test_future.GetCallback(), false);
EXPECT_TRUE(test_future.Wait());
EXPECT_TRUE(test_future.Get().drawsNothing());
tester.ExpectBucketCount("WebApp.ManifestIconDownloader.Result",
ManifestIconDownloader::Result::kNoImageFound, 1);
tester.ExpectTotalCount("WebApp.ManifestIconDownloader.ChromeUrl.Result", 0);
}
IN_PROC_BROWSER_TEST_F(ManifestIconDownloaderBrowserTest, CorrectIcon) {
base::HistogramTester tester;
GURL test_url =
embedded_test_server()->GetURL("/manifest/icon-manifest.html");
GURL icon_url = embedded_test_server()->GetURL("/manifest/128x128-red.png");
base::test::TestFuture<const SkBitmap&> test_future;
ASSERT_TRUE(NavigateToURL(shell(), test_url));
content::ManifestIconDownloader::Download(
shell()->web_contents(), icon_url, 128,
128, 128,
test_future.GetCallback(), false);
EXPECT_TRUE(test_future.Wait());
EXPECT_FALSE(test_future.Get().drawsNothing());
tester.ExpectBucketCount("WebApp.ManifestIconDownloader.Result",
ManifestIconDownloader::Result::kSuccess, 1);
}
std::unique_ptr<net::test_server::HttpResponse> CustomHandleRequestForCookies(
const net::test_server::HttpRequest& request) {
if (request.relative_url == "/index.html") {
std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
new net::test_server::BasicHttpResponse());
http_response->set_code(net::HTTP_OK);
http_response->set_content_type("text/html");
http_response->set_content(
"<html><head>"
"<link rel=manifest crossorigin='use-credentials' href=/manifest.json>"
"</head></html>");
return std::move(http_response);
}
const auto& iter = request.headers.find("Cookie");
if (iter == request.headers.end() ||
request.relative_url != "/manifest.json") {
return nullptr;
}
std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
new net::test_server::BasicHttpResponse());
http_response->set_code(net::HTTP_OK);
http_response->set_content_type("application/json");
http_response->set_content(
base::StringPrintf("{\"name\": \"%s\"}", iter->second.c_str()));
return std::move(http_response);
}
IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, UseCredentialsSendCookies) {
std::unique_ptr<net::EmbeddedTestServer> custom_embedded_test_server(
new net::EmbeddedTestServer());
custom_embedded_test_server->RegisterRequestHandler(
base::BindRepeating(&CustomHandleRequestForCookies));
ASSERT_TRUE(custom_embedded_test_server->Start());
ASSERT_TRUE(SetCookie(shell()->web_contents()->GetBrowserContext(),
custom_embedded_test_server->base_url(), "foobar"));
ASSERT_TRUE(NavigateToURL(
shell(), custom_embedded_test_server->GetURL("/index.html")));
GetManifestAndWait();
EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
EXPECT_FALSE(manifest_url().is_empty());
EXPECT_EQ(0, GetConsoleErrorCount());
ASSERT_EQ(1u, reported_manifest_urls().size());
EXPECT_EQ(manifest_url(), reported_manifest_urls()[0]);
ASSERT_EQ(1u, manifests_reported_when_favicon_url_updated().size());
EXPECT_EQ(1u, manifests_reported_when_favicon_url_updated()[0]);
EXPECT_EQ(u"foobar", manifest().name);
}
std::unique_ptr<net::test_server::HttpResponse> CustomHandleRequestForNoCookies(
const net::test_server::HttpRequest& request) {
if (request.relative_url == "/index.html") {
std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
new net::test_server::BasicHttpResponse());
http_response->set_code(net::HTTP_OK);
http_response->set_content_type("text/html");
http_response->set_content(
"<html><head><link rel=manifest href=/manifest.json></head></html>");
return std::move(http_response);
}
const auto& iter = request.headers.find("Cookie");
if (iter != request.headers.end() ||
request.relative_url != "/manifest.json") {
return nullptr;
}
std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
new net::test_server::BasicHttpResponse());
http_response->set_code(net::HTTP_OK);
http_response->set_content_type("application/json");
http_response->set_content("{\"name\": \"no cookies\"}");
return std::move(http_response);
}
IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, NoUseCredentialsNoCookies) {
std::unique_ptr<net::EmbeddedTestServer> custom_embedded_test_server(
new net::EmbeddedTestServer());
custom_embedded_test_server->RegisterRequestHandler(
base::BindRepeating(&CustomHandleRequestForNoCookies));
ASSERT_TRUE(custom_embedded_test_server->Start());
ASSERT_TRUE(SetCookie(shell()->web_contents()->GetBrowserContext(),
custom_embedded_test_server->base_url(), "foobar"));
ASSERT_TRUE(NavigateToURL(
shell(), custom_embedded_test_server->GetURL("/index.html")));
GetManifestAndWait();
EXPECT_FALSE(blink::IsEmptyManifest(manifest()));
EXPECT_FALSE(manifest_url().is_empty());
EXPECT_EQ(0, GetConsoleErrorCount());
ASSERT_EQ(1u, reported_manifest_urls().size());
EXPECT_EQ(manifest_url(), reported_manifest_urls()[0]);
ASSERT_EQ(1u, manifests_reported_when_favicon_url_updated().size());
EXPECT_EQ(1u, manifests_reported_when_favicon_url_updated()[0]);
EXPECT_EQ(u"no cookies", manifest().name);
}
IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, UniqueOrigin) {
GURL test_url = embedded_test_server()->GetURL("/manifest/sandboxed.html");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
std::string manifest_link =
embedded_test_server()->GetURL("/manifest/sample-manifest.json").spec();
ASSERT_TRUE(ExecJs(shell(), "setManifestTo('" + manifest_link + "')"));
GetManifestAndWait();
EXPECT_TRUE(blink::IsEmptyManifest(manifest()));
EXPECT_FALSE(manifest_url().is_empty());
EXPECT_EQ(0, GetConsoleErrorCount());
EXPECT_EQ(0u, reported_manifest_urls().size());
manifest_link =
embedded_test_server()->GetURL("/manifest/manifest-cors.json").spec();
ASSERT_TRUE(ExecJs(shell(), "setManifestTo('" + manifest_link + "')"));
GetManifestAndWait();
EXPECT_TRUE(blink::IsEmptyManifest(manifest()));
EXPECT_FALSE(manifest_url().is_empty());
EXPECT_EQ(0, GetConsoleErrorCount());
EXPECT_EQ(0u, reported_manifest_urls().size());
}
IN_PROC_BROWSER_TEST_F(ManifestBrowserTest,
GetManifestInterruptedByDestruction) {
ASSERT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("/manifest/hung-manifest.html")));
base::RunLoop run_loop;
WebContents* web_contents = shell()->web_contents();
web_contents->GetPrimaryPage().GetManifest(base::BindLambdaForTesting(
[&](blink::mojom::ManifestRequestResult, const GURL& url,
blink::mojom::ManifestPtr manifest) {
EXPECT_TRUE(url.is_empty());
EXPECT_TRUE(blink::IsEmptyManifest(manifest));
std::ignore = web_contents->GetFaviconURLs().empty();
run_loop.Quit();
}));
auto* render_frame_host_impl =
static_cast<RenderFrameHostImpl*>(web_contents->GetPrimaryMainFrame());
render_frame_host_impl->ReinitializeDocumentAssociatedDataForTesting();
run_loop.Run();
}
class ManifestBrowserPrerenderingTest : public ManifestBrowserTest {
public:
ManifestBrowserPrerenderingTest()
: prerender_helper_(
base::BindRepeating(&ManifestBrowserPrerenderingTest::web_contents,
base::Unretained(this))) {}
~ManifestBrowserPrerenderingTest() override = default;
protected:
test::PrerenderTestHelper& prerender_helper() { return prerender_helper_; }
private:
test::PrerenderTestHelper prerender_helper_;
};
IN_PROC_BROWSER_TEST_F(ManifestBrowserPrerenderingTest,
GetManifestInPrerendering) {
GURL test_url =
embedded_test_server()->GetURL("/manifest/empty-manifest.html");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
{
base::test::TestFuture<blink::mojom::ManifestRequestResult, const GURL&,
blink::mojom::ManifestPtr>
manifest_future;
web_contents()->GetPrimaryPage().GetManifest(manifest_future.GetCallback());
ASSERT_TRUE(manifest_future.Wait());
EXPECT_FALSE(manifest_future.Get<GURL>().is_empty());
EXPECT_FALSE(blink::IsEmptyManifest(
*manifest_future.Get<blink::mojom::ManifestPtr>()));
}
GURL prerender_url =
embedded_test_server()->GetURL("/manifest/sample-manifest.html");
FrameTreeNodeId host_id = prerender_helper().AddPrerender(prerender_url);
content::RenderFrameHost* prerender_rfh =
prerender_helper().GetPrerenderedMainFrameHost(host_id);
{
base::test::TestFuture<blink::mojom::ManifestRequestResult, const GURL&,
blink::mojom::ManifestPtr>
manifest_future;
prerender_rfh->GetPage().GetManifest(manifest_future.GetCallback());
ASSERT_TRUE(manifest_future.Wait());
EXPECT_FALSE(manifest_future.Get<GURL>().is_empty());
EXPECT_FALSE(blink::IsEmptyManifest(
*manifest_future.Get<blink::mojom::ManifestPtr>()));
}
prerender_helper().NavigatePrimaryPage(prerender_url);
{
base::test::TestFuture<blink::mojom::ManifestRequestResult, const GURL&,
blink::mojom::ManifestPtr>
manifest_future;
prerender_rfh->GetPage().GetManifest(manifest_future.GetCallback());
ASSERT_TRUE(manifest_future.Wait());
EXPECT_FALSE(manifest_future.Get<GURL>().is_empty());
EXPECT_FALSE(blink::IsEmptyManifest(
*manifest_future.Get<blink::mojom::ManifestPtr>()));
}
}
class ManifestFencedFrameBrowserTest : public ManifestBrowserTest {
public:
ManifestFencedFrameBrowserTest() = default;
~ManifestFencedFrameBrowserTest() override = default;
protected:
test::FencedFrameTestHelper& fenced_frame_test_helper() {
return fenced_frame_test_helper_;
}
private:
test::FencedFrameTestHelper fenced_frame_test_helper_;
};
IN_PROC_BROWSER_TEST_F(ManifestFencedFrameBrowserTest,
GetManifestInFencedFrame) {
const GURL test_url =
embedded_test_server()->GetURL("/manifest/empty-manifest.html");
ASSERT_TRUE(NavigateToURL(shell(), test_url));
const GURL fenced_frame_url =
embedded_test_server()->GetURL("/fenced_frames/title1.html");
content::RenderFrameHost* fenced_frame_rfh =
fenced_frame_test_helper().CreateFencedFrame(
web_contents()->GetPrimaryMainFrame(), fenced_frame_url);
ASSERT_TRUE(ExecJs(fenced_frame_rfh,
R"( var link = document.createElement('link');
link.rel = 'manifest';
link.href = '../manifest/sample-manifest.json';
document.head.appendChild(link);)"));
base::test::TestFuture<blink::mojom::ManifestRequestResult, const GURL&,
blink::mojom::ManifestPtr>
manifest_future;
fenced_frame_rfh->GetPage().GetManifest(manifest_future.GetCallback());
ASSERT_TRUE(manifest_future.Wait());
EXPECT_FALSE(manifest_future.Get<GURL>().is_empty());
EXPECT_FALSE(blink::IsEmptyManifest(
*manifest_future.Get<blink::mojom::ManifestPtr>()));
}
}
}