1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/command_line.h"
6 #include "base/path_service.h"
7 #include "base/strings/string_util.h"
8 #include "base/strings/stringprintf.h"
9 #include "content/public/browser/web_contents.h"
10 #include "content/public/common/content_switches.h"
11 #include "content/public/common/manifest.h"
12 #include "content/public/test/browser_test_utils.h"
13 #include "content/public/test/content_browser_test.h"
14 #include "content/public/test/content_browser_test_utils.h"
15 #include "content/public/test/test_navigation_observer.h"
16 #include "content/shell/browser/shell.h"
17 #include "net/test/embedded_test_server/embedded_test_server.h"
18 #include "net/test/embedded_test_server/http_request.h"
19 #include "net/test/embedded_test_server/http_response.h"
24 class ManifestBrowserTest
;
26 // Mock of a WebContentsDelegate that catches messages sent to the console.
27 class MockWebContentsDelegate
: public WebContentsDelegate
{
29 MockWebContentsDelegate(WebContents
* web_contents
, ManifestBrowserTest
* test
)
30 : web_contents_(web_contents
),
34 bool AddMessageToConsole(WebContents
* source
,
36 const base::string16
& message
,
38 const base::string16
& source_id
) override
;
41 WebContents
* web_contents_
;
42 ManifestBrowserTest
* test_
;
45 class ManifestBrowserTest
: public ContentBrowserTest
{
47 friend MockWebContentsDelegate
;
50 : console_error_count_(0) {
51 cors_embedded_test_server_
.reset(new net::test_server::EmbeddedTestServer
);
52 base::FilePath test_data_dir
;
53 CHECK(PathService::Get(base::DIR_SOURCE_ROOT
, &test_data_dir
));
54 cors_embedded_test_server_
->ServeFilesFromDirectory(
55 test_data_dir
.AppendASCII("content/test/data/"));
58 ~ManifestBrowserTest() override
{}
60 void SetUpOnMainThread() override
{
61 ContentBrowserTest::SetUpOnMainThread();
62 DCHECK(shell()->web_contents());
64 mock_web_contents_delegate_
.reset(
65 new MockWebContentsDelegate(shell()->web_contents(), this));
66 shell()->web_contents()->SetDelegate(mock_web_contents_delegate_
.get());
69 void GetManifestAndWait() {
70 shell()->web_contents()->GetManifest(
71 base::Bind(&ManifestBrowserTest::OnGetManifest
,
72 base::Unretained(this)));
74 message_loop_runner_
= new MessageLoopRunner();
75 message_loop_runner_
->Run();
78 void OnGetManifest(const Manifest
& manifest
) {
80 message_loop_runner_
->Quit();
83 const Manifest
& manifest() const {
87 unsigned int console_error_count() const {
88 return console_error_count_
;
91 void OnReceivedConsoleError() {
92 console_error_count_
++;
95 net::test_server::EmbeddedTestServer
* cors_embedded_test_server() const {
96 return cors_embedded_test_server_
.get();
100 scoped_refptr
<MessageLoopRunner
> message_loop_runner_
;
101 scoped_ptr
<MockWebContentsDelegate
> mock_web_contents_delegate_
;
102 scoped_ptr
<net::test_server::EmbeddedTestServer
> cors_embedded_test_server_
;
104 int console_error_count_
;
106 DISALLOW_COPY_AND_ASSIGN(ManifestBrowserTest
);
109 // The implementation of AddMessageToConsole isn't inlined because it needs
110 // to know about |test_|.
111 bool MockWebContentsDelegate::AddMessageToConsole(
114 const base::string16
& message
,
116 const base::string16
& source_id
) {
117 DCHECK(source
== web_contents_
);
119 if (level
== logging::LOG_ERROR
)
120 test_
->OnReceivedConsoleError();
124 // If a page has no manifest, requesting a manifest should return the empty
126 IN_PROC_BROWSER_TEST_F(ManifestBrowserTest
, NoManifest
) {
127 GURL test_url
= GetTestUrl("manifest", "no-manifest.html");
129 TestNavigationObserver
navigation_observer(shell()->web_contents(), 1);
130 shell()->LoadURL(test_url
);
131 navigation_observer
.Wait();
133 GetManifestAndWait();
134 EXPECT_TRUE(manifest().IsEmpty());
135 EXPECT_EQ(0u, console_error_count());
138 // If a page manifest points to a 404 URL, requesting the manifest should return
139 // the empty manifest.
140 IN_PROC_BROWSER_TEST_F(ManifestBrowserTest
, 404Manifest
) {
141 GURL test_url
= GetTestUrl("manifest", "404-manifest.html");
143 TestNavigationObserver
navigation_observer(shell()->web_contents(), 1);
144 shell()->LoadURL(test_url
);
145 navigation_observer
.Wait();
147 GetManifestAndWait();
148 EXPECT_TRUE(manifest().IsEmpty());
149 EXPECT_EQ(0u, console_error_count());
152 // If a page has an empty manifest, requesting the manifest should return the
154 IN_PROC_BROWSER_TEST_F(ManifestBrowserTest
, EmptyManifest
) {
155 GURL test_url
= GetTestUrl("manifest", "empty-manifest.html");
157 TestNavigationObserver
navigation_observer(shell()->web_contents(), 1);
158 shell()->LoadURL(test_url
);
159 navigation_observer
.Wait();
161 GetManifestAndWait();
162 EXPECT_TRUE(manifest().IsEmpty());
163 EXPECT_EQ(0u, console_error_count());
166 // If a page's manifest can't be parsed correctly, requesting the manifest
167 // should return an empty manifest.
168 IN_PROC_BROWSER_TEST_F(ManifestBrowserTest
, ParseErrorManifest
) {
169 GURL test_url
= GetTestUrl("manifest", "parse-error-manifest.html");
171 TestNavigationObserver
navigation_observer(shell()->web_contents(), 1);
172 shell()->LoadURL(test_url
);
173 navigation_observer
.Wait();
175 GetManifestAndWait();
176 EXPECT_TRUE(manifest().IsEmpty());
177 EXPECT_EQ(1u, console_error_count());
180 // If a page has a manifest that can be fetched and parsed, requesting the
181 // manifest should return a properly filled manifest.
182 IN_PROC_BROWSER_TEST_F(ManifestBrowserTest
, DummyManifest
) {
183 GURL test_url
= GetTestUrl("manifest", "dummy-manifest.html");
185 TestNavigationObserver
navigation_observer(shell()->web_contents(), 1);
186 shell()->LoadURL(test_url
);
187 navigation_observer
.Wait();
189 GetManifestAndWait();
190 EXPECT_FALSE(manifest().IsEmpty());
191 EXPECT_EQ(0u, console_error_count());
194 // If a page changes manifest during its life-time, requesting the manifest
195 // should return the current manifest.
196 IN_PROC_BROWSER_TEST_F(ManifestBrowserTest
, DynamicManifest
) {
197 GURL test_url
= GetTestUrl("manifest", "dynamic-manifest.html");
199 TestNavigationObserver
navigation_observer(shell()->web_contents(), 1);
200 shell()->LoadURL(test_url
);
201 navigation_observer
.Wait();
204 GetManifestAndWait();
205 EXPECT_TRUE(manifest().IsEmpty());
209 std::string manifest_url
=
210 GetTestUrl("manifest", "dummy-manifest.json").spec();
211 ASSERT_TRUE(content::ExecuteScript(
212 shell()->web_contents(), "setManifestTo('" + manifest_url
+ "')"));
214 GetManifestAndWait();
215 EXPECT_FALSE(manifest().IsEmpty());
219 std::string manifest_url
=
220 GetTestUrl("manifest", "empty-manifest.json").spec();
221 ASSERT_TRUE(content::ExecuteScript(
222 shell()->web_contents(), "setManifestTo('" + manifest_url
+ "')"));
224 GetManifestAndWait();
225 EXPECT_TRUE(manifest().IsEmpty());
228 EXPECT_EQ(0u, console_error_count());
231 // If a page's manifest lives in a different origin, it should follow the CORS
232 // rules and requesting the manifest should return an empty manifest (unless the
233 // response contains CORS headers).
234 IN_PROC_BROWSER_TEST_F(ManifestBrowserTest
, CORSManifest
) {
235 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
236 ASSERT_TRUE(cors_embedded_test_server()->InitializeAndWaitUntilReady());
237 ASSERT_NE(embedded_test_server()->port(),
238 cors_embedded_test_server()->port());
241 embedded_test_server()->GetURL("/manifest/dynamic-manifest.html");
243 TestNavigationObserver
navigation_observer(shell()->web_contents(), 1);
244 shell()->LoadURL(test_url
);
245 navigation_observer
.Wait();
247 std::string manifest_url
= cors_embedded_test_server()->GetURL(
248 "/manifest/dummy-manifest.json").spec();
249 ASSERT_TRUE(content::ExecuteScript(shell()->web_contents(),
250 "setManifestTo('" + manifest_url
+ "')"));
252 GetManifestAndWait();
253 EXPECT_TRUE(manifest().IsEmpty());
255 EXPECT_EQ(0u, console_error_count());
257 // The purpose of this second load is to make sure the first load is fully
258 // finished. The first load will fail because of Access Control error but the
259 // underlying Blink loader will continue fetching the file. There is no
260 // reliable way to know when the fetch is finished from the browser test
261 // except by fetching the same file from same origin, making it succeed when
262 // it is actually fully loaded.
264 embedded_test_server()->GetURL("/manifest/dummy-manifest.json").spec();
265 ASSERT_TRUE(content::ExecuteScript(shell()->web_contents(),
266 "setManifestTo('" + manifest_url
+ "')"));
267 GetManifestAndWait();
270 // If a page's manifest lives in a different origin, it should be accessible if
271 // it has valid access controls headers.
272 IN_PROC_BROWSER_TEST_F(ManifestBrowserTest
, CORSManifestWithAcessControls
) {
273 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
274 ASSERT_TRUE(cors_embedded_test_server()->InitializeAndWaitUntilReady());
275 ASSERT_NE(embedded_test_server()->port(),
276 cors_embedded_test_server()->port());
279 embedded_test_server()->GetURL("/manifest/dynamic-manifest.html");
281 TestNavigationObserver
navigation_observer(shell()->web_contents(), 1);
282 shell()->LoadURL(test_url
);
283 navigation_observer
.Wait();
285 std::string manifest_url
= cors_embedded_test_server()->GetURL(
286 "/manifest/manifest-cors.json").spec();
287 ASSERT_TRUE(content::ExecuteScript(shell()->web_contents(),
288 "setManifestTo('" + manifest_url
+ "')"));
290 GetManifestAndWait();
291 EXPECT_FALSE(manifest().IsEmpty());
293 EXPECT_EQ(0u, console_error_count());
296 // If a page's manifest is in an insecure origin while the page is in a secure
297 // origin, requesting the manifest should return the empty manifest.
298 IN_PROC_BROWSER_TEST_F(ManifestBrowserTest
, MixedContentManifest
) {
299 scoped_ptr
<net::SpawnedTestServer
> https_server(new net::SpawnedTestServer(
300 net::SpawnedTestServer::TYPE_HTTPS
,
301 net::BaseTestServer::SSLOptions(net::BaseTestServer::SSLOptions::CERT_OK
),
302 base::FilePath(FILE_PATH_LITERAL("content/test/data"))));
304 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
305 ASSERT_TRUE(https_server
->Start());
308 embedded_test_server()->GetURL("/manifest/dynamic-manifest.html");
310 TestNavigationObserver
navigation_observer(shell()->web_contents(), 1);
311 shell()->LoadURL(test_url
);
312 navigation_observer
.Wait();
314 std::string manifest_url
=
315 https_server
->GetURL("/manifest/dummy-manifest.json").spec();
316 ASSERT_TRUE(content::ExecuteScript(shell()->web_contents(),
317 "setManifestTo('" + manifest_url
+ "')"));
319 GetManifestAndWait();
320 EXPECT_TRUE(manifest().IsEmpty());
322 EXPECT_EQ(0u, console_error_count());
325 // If a page's manifest has some parsing errors, they should show up in the
326 // developer console.
327 IN_PROC_BROWSER_TEST_F(ManifestBrowserTest
, ParsingErrorsManifest
) {
328 GURL test_url
= GetTestUrl("manifest", "parsing-errors.html");
330 TestNavigationObserver
navigation_observer(shell()->web_contents(), 1);
331 shell()->LoadURL(test_url
);
332 navigation_observer
.Wait();
334 GetManifestAndWait();
335 EXPECT_TRUE(manifest().IsEmpty());
336 EXPECT_EQ(6u, console_error_count());
339 // If a page has a manifest and the page is navigated to a page without a
340 // manifest, the page's manifest should be updated.
341 IN_PROC_BROWSER_TEST_F(ManifestBrowserTest
, Navigation
) {
342 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
345 embedded_test_server()->GetURL("/manifest/dummy-manifest.html");
347 TestNavigationObserver
navigation_observer(shell()->web_contents(), 1);
348 shell()->LoadURL(test_url
);
349 navigation_observer
.Wait();
351 GetManifestAndWait();
352 EXPECT_FALSE(manifest().IsEmpty());
353 EXPECT_EQ(0u, console_error_count());
358 embedded_test_server()->GetURL("/manifest/no-manifest.html");
360 TestNavigationObserver
navigation_observer(shell()->web_contents(), 1);
361 shell()->LoadURL(test_url
);
362 navigation_observer
.Wait();
364 GetManifestAndWait();
365 EXPECT_TRUE(manifest().IsEmpty());
366 EXPECT_EQ(0u, console_error_count());
370 // If a page has a manifest and the page is navigated using pushState (ie. same
371 // page), it should keep its manifest state.
372 IN_PROC_BROWSER_TEST_F(ManifestBrowserTest
, PushStateNavigation
) {
373 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
375 embedded_test_server()->GetURL("/manifest/dummy-manifest.html");
378 TestNavigationObserver
navigation_observer(shell()->web_contents(), 1);
379 shell()->LoadURL(test_url
);
380 navigation_observer
.Wait();
384 TestNavigationObserver
navigation_observer(shell()->web_contents(), 1);
385 ASSERT_TRUE(content::ExecuteScript(
386 shell()->web_contents(),
387 "history.pushState({foo: \"bar\"}, 'page', 'page.html');"));
388 navigation_observer
.Wait();
391 GetManifestAndWait();
392 EXPECT_FALSE(manifest().IsEmpty());
393 EXPECT_EQ(0u, console_error_count());
396 // If a page has a manifest and is navigated using an anchor (ie. same page), it
397 // should keep its manifest state.
398 IN_PROC_BROWSER_TEST_F(ManifestBrowserTest
, AnchorNavigation
) {
399 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
401 embedded_test_server()->GetURL("/manifest/dummy-manifest.html");
404 TestNavigationObserver
navigation_observer(shell()->web_contents(), 1);
405 shell()->LoadURL(test_url
);
406 navigation_observer
.Wait();
410 TestNavigationObserver
navigation_observer(shell()->web_contents(), 1);
411 ASSERT_TRUE(content::ExecuteScript(
412 shell()->web_contents(),
413 "var a = document.createElement('a'); a.href='#foo';"
414 "document.body.appendChild(a); a.click();"));
415 navigation_observer
.Wait();
418 GetManifestAndWait();
419 EXPECT_FALSE(manifest().IsEmpty());
420 EXPECT_EQ(0u, console_error_count());
425 scoped_ptr
<net::test_server::HttpResponse
> CustomHandleRequestForCookies(
426 const net::test_server::HttpRequest
& request
) {
427 if (request
.relative_url
== "/index.html") {
428 scoped_ptr
<net::test_server::BasicHttpResponse
> http_response(
429 new net::test_server::BasicHttpResponse());
430 http_response
->set_code(net::HTTP_OK
);
431 http_response
->set_content_type("text/html");
432 http_response
->set_content(
434 "<link rel=manifest crossorigin='use-credentials' href=/manifest.json>"
436 return http_response
.Pass();
439 const auto& iter
= request
.headers
.find("Cookie");
440 if (iter
== request
.headers
.end() || request
.relative_url
!= "/manifest.json")
441 return scoped_ptr
<net::test_server::HttpResponse
>();
443 scoped_ptr
<net::test_server::BasicHttpResponse
> http_response(
444 new net::test_server::BasicHttpResponse());
445 http_response
->set_code(net::HTTP_OK
);
446 http_response
->set_content_type("application/json");
447 http_response
->set_content(
448 base::StringPrintf("{\"name\": \"%s\"}", iter
->second
.c_str()));
450 return http_response
.Pass();
453 } // anonymous namespace
455 // This tests that when fetching a Manifest with 'use-credentials' set, the
456 // cookies associated with it are passed along the request.
457 IN_PROC_BROWSER_TEST_F(ManifestBrowserTest
, UseCredentialsSendCookies
) {
458 scoped_ptr
<net::test_server::EmbeddedTestServer
> custom_embedded_test_server(
459 new net::test_server::EmbeddedTestServer());
460 custom_embedded_test_server
->RegisterRequestHandler(
461 base::Bind(&CustomHandleRequestForCookies
));
463 ASSERT_TRUE(custom_embedded_test_server
->InitializeAndWaitUntilReady());
465 ASSERT_TRUE(SetCookie(shell()->web_contents()->GetBrowserContext(),
466 custom_embedded_test_server
->base_url(),
469 TestNavigationObserver
navigation_observer(shell()->web_contents(), 1);
470 shell()->LoadURL(custom_embedded_test_server
->GetURL("/index.html"));
471 navigation_observer
.Wait();
473 GetManifestAndWait();
474 EXPECT_FALSE(manifest().IsEmpty());
475 EXPECT_EQ(0u, console_error_count());
477 // The custom embedded test server will fill the name field with the cookie
479 EXPECT_TRUE(base::EqualsASCII(manifest().name
.string(), "foobar"));
484 scoped_ptr
<net::test_server::HttpResponse
> CustomHandleRequestForNoCookies(
485 const net::test_server::HttpRequest
& request
) {
486 if (request
.relative_url
== "/index.html") {
487 scoped_ptr
<net::test_server::BasicHttpResponse
> http_response(
488 new net::test_server::BasicHttpResponse());
489 http_response
->set_code(net::HTTP_OK
);
490 http_response
->set_content_type("text/html");
491 http_response
->set_content(
492 "<html><head><link rel=manifest href=/manifest.json></head></html>");
493 return http_response
.Pass();
496 const auto& iter
= request
.headers
.find("Cookie");
497 if (iter
!= request
.headers
.end() || request
.relative_url
!= "/manifest.json")
498 return scoped_ptr
<net::test_server::HttpResponse
>();
500 scoped_ptr
<net::test_server::BasicHttpResponse
> http_response(
501 new net::test_server::BasicHttpResponse());
502 http_response
->set_code(net::HTTP_OK
);
503 http_response
->set_content_type("application/json");
504 http_response
->set_content("{\"name\": \"no cookies\"}");
506 return http_response
.Pass();
509 } // anonymous namespace
511 // This tests that when fetching a Manifest without 'use-credentials' set, the
512 // cookies associated with it are not passed along the request.
513 IN_PROC_BROWSER_TEST_F(ManifestBrowserTest
, NoUseCredentialsNoCookies
) {
514 scoped_ptr
<net::test_server::EmbeddedTestServer
> custom_embedded_test_server(
515 new net::test_server::EmbeddedTestServer());
516 custom_embedded_test_server
->RegisterRequestHandler(
517 base::Bind(&CustomHandleRequestForNoCookies
));
519 ASSERT_TRUE(custom_embedded_test_server
->InitializeAndWaitUntilReady());
521 ASSERT_TRUE(SetCookie(shell()->web_contents()->GetBrowserContext(),
522 custom_embedded_test_server
->base_url(),
525 TestNavigationObserver
navigation_observer(shell()->web_contents(), 1);
526 shell()->LoadURL(custom_embedded_test_server
->GetURL("/index.html"));
527 navigation_observer
.Wait();
529 GetManifestAndWait();
530 EXPECT_FALSE(manifest().IsEmpty());
531 EXPECT_EQ(0u, console_error_count());
533 // The custom embedded test server will fill set the name to 'no cookies' if
534 // it did not find cookies.
535 EXPECT_TRUE(base::EqualsASCII(manifest().name
.string(), "no cookies"));
538 } // namespace content