blob: 586cfc41cc7d25fb31c1c493162cb24c9677c823 [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 "third_party/blink/renderer/core/html/parser/html_document_parser.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
#include "third_party/blink/renderer/platform/testing/histogram_tester.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
namespace blink {
class HTMLDocumentParserSimTest : public SimTest {
protected:
HTMLDocumentParserSimTest() {
Document::SetThreadedParsingEnabledForTesting(true);
}
HistogramTester histogram_;
};
class HTMLDocumentParserLoadingTest : public HTMLDocumentParserSimTest,
public testing::WithParamInterface<bool> {
protected:
HTMLDocumentParserLoadingTest() {
Document::SetThreadedParsingEnabledForTesting(GetParam());
}
};
INSTANTIATE_TEST_CASE_P(Threaded,
HTMLDocumentParserLoadingTest,
testing::Values(true));
INSTANTIATE_TEST_CASE_P(NotThreaded,
HTMLDocumentParserLoadingTest,
testing::Values(false));
TEST_P(HTMLDocumentParserLoadingTest,
ShouldNotPauseParsingForExternalStylesheetsInHead) {
SimRequest main_resource("https://example.com/test.html", "text/html");
SimSubresourceRequest css_head_resource("https://example.com/testHead.css",
"text/css");
LoadURL("https://example.com/test.html");
main_resource.Complete(R"HTML(
<!DOCTYPE html>
<html><head>
<link rel=stylesheet href=testHead.css>
</head><body>
<div id="bodyDiv"></div>
</body></html>
)HTML");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("bodyDiv"));
css_head_resource.Complete("");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("bodyDiv"));
}
TEST_P(HTMLDocumentParserLoadingTest,
ShouldNotPauseParsingForExternalStylesheetsImportedInHead) {
SimRequest main_resource("https://example.com/test.html", "text/html");
SimSubresourceRequest css_head_resource("https://example.com/testHead.css",
"text/css");
LoadURL("https://example.com/test.html");
main_resource.Complete(R"HTML(
<!DOCTYPE html>
<html><head>
<style>
@import 'testHead.css'
</style>
</head><body>
<div id="bodyDiv"></div>
</body></html>
)HTML");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("bodyDiv"));
css_head_resource.Complete("");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("bodyDiv"));
}
TEST_P(HTMLDocumentParserLoadingTest,
ShouldPauseParsingForExternalStylesheetsInBody) {
SimRequest main_resource("https://example.com/test.html", "text/html");
SimSubresourceRequest css_head_resource("https://example.com/testHead.css",
"text/css");
SimSubresourceRequest css_body_resource("https://example.com/testBody.css",
"text/css");
LoadURL("https://example.com/test.html");
main_resource.Complete(R"HTML(
<!DOCTYPE html>
<html><head>
<link rel=stylesheet href=testHead.css>
</head><body>
<div id="before"></div>
<link rel=stylesheet href=testBody.css>
<div id="after"></div>
</body></html>
)HTML");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("before"));
EXPECT_FALSE(GetDocument().getElementById("after"));
// Completing the head css shouldn't change anything
css_head_resource.Complete("");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("before"));
EXPECT_FALSE(GetDocument().getElementById("after"));
// Completing the body resource and pumping the tasks should continue parsing
// and create the "after" div.
css_body_resource.Complete("");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("before"));
EXPECT_TRUE(GetDocument().getElementById("after"));
}
TEST_P(HTMLDocumentParserLoadingTest,
ShouldPauseParsingForExternalStylesheetsInBodyIncremental) {
SimRequest main_resource("https://example.com/test.html", "text/html");
SimSubresourceRequest css_head_resource("https://example.com/testHead.css",
"text/css");
SimSubresourceRequest css_body_resource1("https://example.com/testBody1.css",
"text/css");
SimSubresourceRequest css_body_resource2("https://example.com/testBody2.css",
"text/css");
SimSubresourceRequest css_body_resource3("https://example.com/testBody3.css",
"text/css");
LoadURL("https://example.com/test.html");
main_resource.Write(R"HTML(
<!DOCTYPE html>
<html><head>
<link rel=stylesheet href=testHead.css>
</head><body>
<div id="before"></div>
<link rel=stylesheet href=testBody1.css>
<div id="after1"></div>
)HTML");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("before"));
EXPECT_FALSE(GetDocument().getElementById("after1"));
EXPECT_FALSE(GetDocument().getElementById("after2"));
EXPECT_FALSE(GetDocument().getElementById("after3"));
main_resource.Write(
"<link rel=stylesheet href=testBody2.css>"
"<div id=\"after2\"></div>");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("before"));
EXPECT_FALSE(GetDocument().getElementById("after1"));
EXPECT_FALSE(GetDocument().getElementById("after2"));
EXPECT_FALSE(GetDocument().getElementById("after3"));
main_resource.Complete(R"HTML(
<link rel=stylesheet href=testBody3.css>
<div id="after3"></div>
</body></html>
)HTML");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("before"));
EXPECT_FALSE(GetDocument().getElementById("after1"));
EXPECT_FALSE(GetDocument().getElementById("after2"));
EXPECT_FALSE(GetDocument().getElementById("after3"));
// Completing the head css shouldn't change anything
css_head_resource.Complete("");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("before"));
EXPECT_FALSE(GetDocument().getElementById("after1"));
EXPECT_FALSE(GetDocument().getElementById("after2"));
EXPECT_FALSE(GetDocument().getElementById("after3"));
// Completing the second css shouldn't change anything
css_body_resource2.Complete("");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("before"));
EXPECT_FALSE(GetDocument().getElementById("after1"));
EXPECT_FALSE(GetDocument().getElementById("after2"));
EXPECT_FALSE(GetDocument().getElementById("after3"));
// Completing the first css should allow the parser to continue past it and
// the second css which was already completed and then pause again before the
// third css.
css_body_resource1.Complete("");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("before"));
EXPECT_TRUE(GetDocument().getElementById("after1"));
EXPECT_TRUE(GetDocument().getElementById("after2"));
EXPECT_FALSE(GetDocument().getElementById("after3"));
// Completing the third css should let it continue to the end.
css_body_resource3.Complete("");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("before"));
EXPECT_TRUE(GetDocument().getElementById("after1"));
EXPECT_TRUE(GetDocument().getElementById("after2"));
EXPECT_TRUE(GetDocument().getElementById("after3"));
}
TEST_P(HTMLDocumentParserLoadingTest,
ShouldNotPauseParsingForExternalNonMatchingStylesheetsInBody) {
SimRequest main_resource("https://example.com/test.html", "text/html");
SimSubresourceRequest css_head_resource("https://example.com/testHead.css",
"text/css");
LoadURL("https://example.com/test.html");
main_resource.Complete(R"HTML(
<!DOCTYPE html>
<html><head>
<link rel=stylesheet href=testHead.css>
</head><body>
<div id="before"></div>
<link rel=stylesheet href=testBody.css type='print'>
<div id="after"></div>
</body></html>
)HTML");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("before"));
EXPECT_TRUE(GetDocument().getElementById("after"));
// Completing the head css shouldn't change anything
css_head_resource.Complete("");
}
TEST_P(HTMLDocumentParserLoadingTest,
ShouldPauseParsingForExternalStylesheetsImportedInBody) {
SimRequest main_resource("https://example.com/test.html", "text/html");
SimSubresourceRequest css_head_resource("https://example.com/testHead.css",
"text/css");
SimSubresourceRequest css_body_resource("https://example.com/testBody.css",
"text/css");
LoadURL("https://example.com/test.html");
main_resource.Complete(R"HTML(
<!DOCTYPE html>
<html><head>
<link rel=stylesheet href=testHead.css>
</head><body>
<div id="before"></div>
<style>
@import 'testBody.css'
</style>
<div id="after"></div>
</body></html>
)HTML");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("before"));
EXPECT_FALSE(GetDocument().getElementById("after"));
// Completing the head css shouldn't change anything
css_head_resource.Complete("");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("before"));
EXPECT_FALSE(GetDocument().getElementById("after"));
// Completing the body resource and pumping the tasks should continue parsing
// and create the "after" div.
css_body_resource.Complete("");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("before"));
EXPECT_TRUE(GetDocument().getElementById("after"));
}
TEST_P(HTMLDocumentParserLoadingTest,
ShouldPauseParsingForExternalStylesheetsWrittenInBody) {
SimRequest main_resource("https://example.com/test.html", "text/html");
SimSubresourceRequest css_head_resource("https://example.com/testHead.css",
"text/css");
SimSubresourceRequest css_body_resource("https://example.com/testBody.css",
"text/css");
LoadURL("https://example.com/test.html");
main_resource.Complete(R"HTML(
<!DOCTYPE html>
<html><head>
<link rel=stylesheet href=testHead.css>
</head><body>
<div id="before"></div>
<script>
document.write('<link rel=stylesheet href=testBody.css>');
</script>
<div id="after"></div>
</body></html>
)HTML");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("before"));
EXPECT_FALSE(GetDocument().getElementById("after"));
// Completing the head css shouldn't change anything
css_head_resource.Complete("");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("before"));
EXPECT_FALSE(GetDocument().getElementById("after"));
// Completing the body resource and pumping the tasks should continue parsing
// and create the "after" div.
css_body_resource.Complete("");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("before"));
EXPECT_TRUE(GetDocument().getElementById("after"));
}
TEST_P(HTMLDocumentParserLoadingTest,
PendingHeadStylesheetShouldNotBlockParserForBodyInlineStyle) {
SimRequest main_resource("https://example.com/test.html", "text/html");
SimSubresourceRequest css_head_resource("https://example.com/testHead.css",
"text/css");
LoadURL("https://example.com/test.html");
main_resource.Complete(R"HTML(
<!DOCTYPE html>
<html><head>
<link rel=stylesheet href=testHead.css>
</head><body>
<div id="before"></div>
<style>
</style>
<div id="after"></div>
</body></html>
)HTML");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("before"));
EXPECT_TRUE(GetDocument().getElementById("after"));
css_head_resource.Complete("");
}
TEST_P(HTMLDocumentParserLoadingTest,
PendingHeadStylesheetShouldNotBlockParserForBodyShadowDom) {
SimRequest main_resource("https://example.com/test.html", "text/html");
SimSubresourceRequest css_head_resource("https://example.com/testHead.css",
"text/css");
LoadURL("https://example.com/test.html");
// The marquee tag has a shadow DOM that synchronously applies a stylesheet.
main_resource.Complete(R"HTML(
<!DOCTYPE html>
<html><head>
<link rel=stylesheet href=testHead.css>
</head><body>
<div id="before"></div>
<marquee>Marquee</marquee>
<div id="after"></div>
</body></html>
)HTML");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("before"));
EXPECT_TRUE(GetDocument().getElementById("after"));
css_head_resource.Complete("");
}
TEST_P(HTMLDocumentParserLoadingTest,
ShouldNotPauseParsingForExternalStylesheetsAttachedInBody) {
SimRequest main_resource("https://example.com/test.html", "text/html");
SimSubresourceRequest css_async_resource("https://example.com/testAsync.css",
"text/css");
LoadURL("https://example.com/test.html");
main_resource.Complete(R"HTML(
<!DOCTYPE html>
<html><head>
</head><body>
<div id="before"></div>
<script>
var attach = document.getElementsByTagName('script')[0];
var link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = 'testAsync.css';
link.media = 'all';
attach.appendChild(link);
</script>
<div id="after"></div>
</body></html>
)HTML");
test::RunPendingTasks();
EXPECT_TRUE(GetDocument().getElementById("before"));
EXPECT_TRUE(GetDocument().getElementById("after"));
css_async_resource.Complete("");
}
TEST_F(HTMLDocumentParserSimTest, NoRewindNoDocWrite) {
SimRequest main_resource("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
main_resource.Complete(R"HTML(
<!DOCTYPE html>
<html><body>no doc write
</body></html>
)HTML");
test::RunPendingTasks();
histogram_.ExpectTotalCount("Parser.DiscardedTokenCount", 0);
}
TEST_F(HTMLDocumentParserSimTest, RewindBrokenToken) {
SimRequest main_resource("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
main_resource.Complete(R"HTML(
<!DOCTYPE html>
<script>
document.write('<a');
</script>
)HTML");
test::RunPendingTasks();
histogram_.ExpectTotalCount("Parser.DiscardedTokenCount", 1);
}
TEST_F(HTMLDocumentParserSimTest, RewindDifferentNamespace) {
SimRequest main_resource("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
main_resource.Complete(R"HTML(
<!DOCTYPE html>
<script>
document.write('<svg>');
</script>
)HTML");
test::RunPendingTasks();
histogram_.ExpectTotalCount("Parser.DiscardedTokenCount", 1);
}
TEST_F(HTMLDocumentParserSimTest, NoRewindSaneDocWrite1) {
SimRequest main_resource("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
main_resource.Complete(
"<!DOCTYPE html>"
"<script>"
"document.write('<script>console.log(\'hello world\');<\\/script>');"
"</script>");
test::RunPendingTasks();
histogram_.ExpectTotalCount("Parser.DiscardedTokenCount", 0);
}
TEST_F(HTMLDocumentParserSimTest, NoRewindSaneDocWrite2) {
SimRequest main_resource("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
main_resource.Complete(R"HTML(
<!DOCTYPE html>
<script>
document.write('<p>hello world<\\/p><a>yo');
</script>
)HTML");
test::RunPendingTasks();
histogram_.ExpectTotalCount("Parser.DiscardedTokenCount", 0);
}
TEST_F(HTMLDocumentParserSimTest, NoRewindSaneDocWriteWithTitle) {
SimRequest main_resource("https://example.com/test.html", "text/html");
LoadURL("https://example.com/test.html");
main_resource.Complete(R"HTML(
<!DOCTYPE html>
<html>
<head>
<title></title>
<script>document.write('<p>testing');</script>
</head>
<body>
</body>
</html>
)HTML");
test::RunPendingTasks();
histogram_.ExpectTotalCount("Parser.DiscardedTokenCount", 0);
}
} // namespace blink