| // Copyright 2014 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 <stdint.h> |
| #include <utility> |
| |
| #include "base/command_line.h" |
| #include "base/macros.h" |
| #include "base/path_service.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/manifest.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/test_navigation_observer.h" |
| #include "content/shell/browser/shell.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" |
| |
| |
| namespace content { |
| |
| class ManifestBrowserTest; |
| |
| // Mock of a WebContentsDelegate that catches messages sent to the console. |
| class MockWebContentsDelegate : public WebContentsDelegate { |
| public: |
| MockWebContentsDelegate(WebContents* web_contents, ManifestBrowserTest* test) |
| : web_contents_(web_contents), |
| test_(test) { |
| } |
| |
| bool DidAddMessageToConsole(WebContents* source, |
| int32_t level, |
| const base::string16& message, |
| int32_t line_no, |
| const base::string16& source_id) override; |
| |
| private: |
| WebContents* web_contents_; |
| ManifestBrowserTest* test_; |
| }; |
| |
| class ManifestBrowserTest : public ContentBrowserTest { |
| protected: |
| friend MockWebContentsDelegate; |
| |
| ManifestBrowserTest() : console_error_count_(0) { |
| cors_embedded_test_server_.reset(new net::EmbeddedTestServer); |
| cors_embedded_test_server_->ServeFilesFromSourceDirectory( |
| "content/test/data"); |
| } |
| |
| ~ManifestBrowserTest() override {} |
| |
| void SetUpOnMainThread() override { |
| ContentBrowserTest::SetUpOnMainThread(); |
| DCHECK(shell()->web_contents()); |
| |
| mock_web_contents_delegate_.reset( |
| new MockWebContentsDelegate(shell()->web_contents(), this)); |
| shell()->web_contents()->SetDelegate(mock_web_contents_delegate_.get()); |
| } |
| |
| void GetManifestAndWait() { |
| shell()->web_contents()->GetManifest( |
| base::Bind(&ManifestBrowserTest::OnGetManifest, |
| base::Unretained(this))); |
| |
| message_loop_runner_ = new MessageLoopRunner(); |
| message_loop_runner_->Run(); |
| } |
| |
| void OnGetManifest(const GURL& manifest_url, const Manifest& manifest) { |
| manifest_url_ = manifest_url; |
| manifest_ = manifest; |
| message_loop_runner_->Quit(); |
| } |
| |
| const Manifest& manifest() const { |
| return manifest_; |
| } |
| |
| const GURL& manifest_url() const { |
| return manifest_url_; |
| } |
| |
| unsigned int console_error_count() const { |
| return console_error_count_; |
| } |
| |
| void OnReceivedConsoleError() { |
| console_error_count_++; |
| } |
| |
| net::EmbeddedTestServer* cors_embedded_test_server() const { |
| return cors_embedded_test_server_.get(); |
| } |
| |
| 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_; |
| Manifest manifest_; |
| int console_error_count_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ManifestBrowserTest); |
| }; |
| |
| // The implementation of DidAddMessageToConsole isn't inlined because it needs |
| // to know about |test_|. |
| bool MockWebContentsDelegate::DidAddMessageToConsole( |
| WebContents* source, |
| int32_t level, |
| const base::string16& message, |
| int32_t line_no, |
| const base::string16& source_id) { |
| DCHECK(source == web_contents_); |
| |
| if (level == logging::LOG_ERROR || level == logging::LOG_WARNING) |
| test_->OnReceivedConsoleError(); |
| return false; |
| } |
| |
| // If a page has no manifest, requesting a manifest should return the empty |
| // manifest. The URL should be empty. |
| IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, NoManifest) { |
| GURL test_url = GetTestUrl("manifest", "no-manifest.html"); |
| |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| shell()->LoadURL(test_url); |
| navigation_observer.Wait(); |
| |
| GetManifestAndWait(); |
| EXPECT_TRUE(manifest().IsEmpty()); |
| EXPECT_TRUE(manifest_url().is_empty()); |
| EXPECT_EQ(0u, console_error_count()); |
| } |
| |
| // If a page manifest points to a 404 URL, requesting the manifest should return |
| // the empty manifest. However, the manifest URL will be non-empty. |
| IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, 404Manifest) { |
| GURL test_url = GetTestUrl("manifest", "404-manifest.html"); |
| |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| shell()->LoadURL(test_url); |
| navigation_observer.Wait(); |
| |
| GetManifestAndWait(); |
| EXPECT_TRUE(manifest().IsEmpty()); |
| EXPECT_FALSE(manifest_url().is_empty()); |
| EXPECT_EQ(0u, console_error_count()); |
| } |
| |
| // If a page has an empty manifest, requesting the manifest should return the |
| // empty manifest. The manifest URL should be non-empty. |
| IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, EmptyManifest) { |
| GURL test_url = GetTestUrl("manifest", "empty-manifest.html"); |
| |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| shell()->LoadURL(test_url); |
| navigation_observer.Wait(); |
| |
| GetManifestAndWait(); |
| EXPECT_TRUE(manifest().IsEmpty()); |
| EXPECT_FALSE(manifest_url().is_empty()); |
| EXPECT_EQ(0u, console_error_count()); |
| } |
| |
| // If a page's manifest can't be parsed correctly, requesting the manifest |
| // should return an empty manifest. The manifest URL should be non-empty. |
| IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, ParseErrorManifest) { |
| GURL test_url = GetTestUrl("manifest", "parse-error-manifest.html"); |
| |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| shell()->LoadURL(test_url); |
| navigation_observer.Wait(); |
| |
| GetManifestAndWait(); |
| EXPECT_TRUE(manifest().IsEmpty()); |
| EXPECT_FALSE(manifest_url().is_empty()); |
| EXPECT_EQ(1u, console_error_count()); |
| } |
| |
| // If a page has a manifest that can be fetched and parsed, requesting the |
| // manifest should return a properly filled manifest. The manifest URL should be |
| // non-empty. |
| IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, DummyManifest) { |
| GURL test_url = GetTestUrl("manifest", "dummy-manifest.html"); |
| |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| shell()->LoadURL(test_url); |
| navigation_observer.Wait(); |
| |
| GetManifestAndWait(); |
| EXPECT_FALSE(manifest().IsEmpty()); |
| EXPECT_FALSE(manifest_url().is_empty()); |
| |
| EXPECT_EQ(0u, console_error_count()); |
| } |
| |
| // If a page changes manifest during its life-time, requesting the manifest |
| // should return the current manifest. |
| IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, DynamicManifest) { |
| GURL test_url = GetTestUrl("manifest", "dynamic-manifest.html"); |
| |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| shell()->LoadURL(test_url); |
| navigation_observer.Wait(); |
| |
| { |
| GetManifestAndWait(); |
| EXPECT_TRUE(manifest().IsEmpty()); |
| EXPECT_TRUE(manifest_url().is_empty()); |
| } |
| |
| { |
| std::string manifest_link = |
| GetTestUrl("manifest", "dummy-manifest.json").spec(); |
| ASSERT_TRUE(content::ExecuteScript( |
| shell(), "setManifestTo('" + manifest_link + "')")); |
| |
| GetManifestAndWait(); |
| EXPECT_FALSE(manifest().IsEmpty()); |
| EXPECT_FALSE(manifest_url().is_empty()); |
| } |
| |
| { |
| std::string manifest_link = |
| GetTestUrl("manifest", "empty-manifest.json").spec(); |
| ASSERT_TRUE(content::ExecuteScript( |
| shell(), "setManifestTo('" + manifest_link + "')")); |
| |
| GetManifestAndWait(); |
| EXPECT_TRUE(manifest().IsEmpty()); |
| EXPECT_FALSE(manifest_url().is_empty()); |
| } |
| |
| EXPECT_EQ(0u, console_error_count()); |
| } |
| |
| // If a page's manifest lives in a different origin, it should follow the CORS |
| // rules and requesting the manifest should return an empty manifest (unless the |
| // response contains CORS headers). |
| IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, CORSManifest) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| 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"); |
| |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| shell()->LoadURL(test_url); |
| navigation_observer.Wait(); |
| |
| std::string manifest_link = cors_embedded_test_server()->GetURL( |
| "/manifest/dummy-manifest.json").spec(); |
| ASSERT_TRUE(content::ExecuteScript(shell(), |
| "setManifestTo('" + manifest_link + "')")); |
| |
| GetManifestAndWait(); |
| EXPECT_TRUE(manifest().IsEmpty()); |
| EXPECT_FALSE(manifest_url().is_empty()); |
| EXPECT_EQ(0u, console_error_count()); |
| |
| // The purpose of this second load is to make sure the first load is fully |
| // finished. The first load will fail because of Access Control error but the |
| // underlying Blink loader will continue fetching the file. There is no |
| // reliable way to know when the fetch is finished from the browser test |
| // except by fetching the same file from same origin, making it succeed when |
| // it is actually fully loaded. |
| manifest_link = |
| embedded_test_server()->GetURL("/manifest/dummy-manifest.json").spec(); |
| ASSERT_TRUE(content::ExecuteScript(shell(), |
| "setManifestTo('" + manifest_link + "')")); |
| GetManifestAndWait(); |
| } |
| |
| // If a page's manifest lives in a different origin, it should be accessible if |
| // it has valid access controls headers. |
| IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, CORSManifestWithAcessControls) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| 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"); |
| |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| shell()->LoadURL(test_url); |
| navigation_observer.Wait(); |
| |
| std::string manifest_link = cors_embedded_test_server()->GetURL( |
| "/manifest/manifest-cors.json").spec(); |
| ASSERT_TRUE(content::ExecuteScript(shell(), |
| "setManifestTo('" + manifest_link + "')")); |
| |
| GetManifestAndWait(); |
| EXPECT_FALSE(manifest().IsEmpty()); |
| EXPECT_FALSE(manifest_url().is_empty()); |
| EXPECT_EQ(0u, console_error_count()); |
| } |
| |
| // If a page's manifest is in an insecure origin while the page is in a secure |
| // origin, requesting the manifest should return the empty manifest. |
| IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, MixedContentManifest) { |
| std::unique_ptr<net::EmbeddedTestServer> https_server( |
| new net::EmbeddedTestServer(net::EmbeddedTestServer::TYPE_HTTPS)); |
| https_server->ServeFilesFromSourceDirectory("content/test/data"); |
| |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| ASSERT_TRUE(https_server->Start()); |
| |
| GURL test_url = |
| embedded_test_server()->GetURL("/manifest/dynamic-manifest.html"); |
| |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| shell()->LoadURL(test_url); |
| navigation_observer.Wait(); |
| |
| std::string manifest_link = |
| https_server->GetURL("/manifest/dummy-manifest.json").spec(); |
| ASSERT_TRUE(content::ExecuteScript(shell(), |
| "setManifestTo('" + manifest_link + "')")); |
| |
| GetManifestAndWait(); |
| EXPECT_TRUE(manifest().IsEmpty()); |
| EXPECT_FALSE(manifest_url().is_empty()); |
| EXPECT_EQ(0u, console_error_count()); |
| } |
| |
| // If a page's manifest has some parsing errors, they should show up in the |
| // developer console. |
| IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, ParsingErrorsManifest) { |
| GURL test_url = GetTestUrl("manifest", "parsing-errors.html"); |
| |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| shell()->LoadURL(test_url); |
| navigation_observer.Wait(); |
| |
| GetManifestAndWait(); |
| EXPECT_TRUE(manifest().IsEmpty()); |
| EXPECT_FALSE(manifest_url().is_empty()); |
| EXPECT_EQ(6u, console_error_count()); |
| } |
| |
| // If a page has a manifest and the page is navigated to a page without a |
| // manifest, the page's manifest should be updated. |
| IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, Navigation) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| { |
| GURL test_url = |
| embedded_test_server()->GetURL("/manifest/dummy-manifest.html"); |
| |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| shell()->LoadURL(test_url); |
| navigation_observer.Wait(); |
| |
| GetManifestAndWait(); |
| EXPECT_FALSE(manifest().IsEmpty()); |
| EXPECT_FALSE(manifest_url().is_empty()); |
| EXPECT_EQ(0u, console_error_count()); |
| } |
| |
| { |
| GURL test_url = |
| embedded_test_server()->GetURL("/manifest/no-manifest.html"); |
| |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| shell()->LoadURL(test_url); |
| navigation_observer.Wait(); |
| |
| GetManifestAndWait(); |
| EXPECT_TRUE(manifest().IsEmpty()); |
| EXPECT_EQ(0u, console_error_count()); |
| EXPECT_TRUE(manifest_url().is_empty()); |
| } |
| } |
| |
| // If a page has a manifest and the page is navigated using pushState (ie. same |
| // page), it should keep its manifest state. |
| IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, PushStateNavigation) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL test_url = |
| embedded_test_server()->GetURL("/manifest/dummy-manifest.html"); |
| |
| { |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| shell()->LoadURL(test_url); |
| navigation_observer.Wait(); |
| } |
| |
| { |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| ASSERT_TRUE(content::ExecuteScript( |
| shell(), "history.pushState({foo: \"bar\"}, 'page', 'page.html');")); |
| navigation_observer.Wait(); |
| } |
| |
| GetManifestAndWait(); |
| EXPECT_FALSE(manifest().IsEmpty()); |
| EXPECT_FALSE(manifest_url().is_empty()); |
| EXPECT_EQ(0u, console_error_count()); |
| } |
| |
| // If a page has a manifest and is navigated using an anchor (ie. same page), it |
| // should keep its manifest state. |
| IN_PROC_BROWSER_TEST_F(ManifestBrowserTest, AnchorNavigation) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| GURL test_url = |
| embedded_test_server()->GetURL("/manifest/dummy-manifest.html"); |
| |
| { |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| shell()->LoadURL(test_url); |
| navigation_observer.Wait(); |
| } |
| |
| { |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| ASSERT_TRUE(content::ExecuteScript( |
| shell(), |
| "var a = document.createElement('a'); a.href='#foo';" |
| "document.body.appendChild(a); a.click();")); |
| navigation_observer.Wait(); |
| } |
| |
| GetManifestAndWait(); |
| EXPECT_FALSE(manifest().IsEmpty()); |
| EXPECT_FALSE(manifest_url().is_empty()); |
| EXPECT_EQ(0u, console_error_count()); |
| } |
| |
| namespace { |
| |
| 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 std::unique_ptr<net::test_server::HttpResponse>(); |
| |
| 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); |
| } |
| |
| } // anonymous namespace |
| |
| // This tests that when fetching a Manifest with 'use-credentials' set, the |
| // cookies associated with it are passed along the request. |
| 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::Bind(&CustomHandleRequestForCookies)); |
| |
| ASSERT_TRUE(custom_embedded_test_server->Start()); |
| |
| ASSERT_TRUE(SetCookie(shell()->web_contents()->GetBrowserContext(), |
| custom_embedded_test_server->base_url(), |
| "foobar")); |
| |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| shell()->LoadURL(custom_embedded_test_server->GetURL("/index.html")); |
| navigation_observer.Wait(); |
| |
| GetManifestAndWait(); |
| EXPECT_FALSE(manifest().IsEmpty()); |
| EXPECT_FALSE(manifest_url().is_empty()); |
| EXPECT_EQ(0u, console_error_count()); |
| |
| // The custom embedded test server will fill the name field with the cookie |
| // content. |
| EXPECT_TRUE(base::EqualsASCII(manifest().name.string(), "foobar")); |
| } |
| |
| namespace { |
| |
| 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 std::unique_ptr<net::test_server::HttpResponse>(); |
| |
| 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); |
| } |
| |
| } // anonymous namespace |
| |
| // This tests that when fetching a Manifest without 'use-credentials' set, the |
| // cookies associated with it are not passed along the request. |
| 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::Bind(&CustomHandleRequestForNoCookies)); |
| |
| ASSERT_TRUE(custom_embedded_test_server->Start()); |
| |
| ASSERT_TRUE(SetCookie(shell()->web_contents()->GetBrowserContext(), |
| custom_embedded_test_server->base_url(), |
| "foobar")); |
| |
| TestNavigationObserver navigation_observer(shell()->web_contents(), 1); |
| shell()->LoadURL(custom_embedded_test_server->GetURL("/index.html")); |
| navigation_observer.Wait(); |
| |
| GetManifestAndWait(); |
| EXPECT_FALSE(manifest().IsEmpty()); |
| EXPECT_FALSE(manifest_url().is_empty()); |
| EXPECT_EQ(0u, console_error_count()); |
| |
| // The custom embedded test server will fill set the name to 'no cookies' if |
| // it did not find cookies. |
| EXPECT_TRUE(base::EqualsASCII(manifest().name.string(), "no cookies")); |
| } |
| |
| } // namespace content |