1 // Copyright 2017 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.
4
5 #include "third_party/blink/renderer/core/html/parser/html_document_parser.h"
6
7 #include "testing/gtest/include/gtest/gtest.h"
8 #include "third_party/blink/renderer/core/dom/document.h"
9 #include "third_party/blink/renderer/core/testing/sim/sim_request.h"
10 #include "third_party/blink/renderer/core/testing/sim/sim_test.h"
11 #include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
12
13 namespace blink {
14
15 class HTMLDocumentParserSimTest : public SimTest {
16 protected:
HTMLDocumentParserSimTest()17 HTMLDocumentParserSimTest() {
18 ResetDiscardedTokenCountForTesting();
19 Document::SetThreadedParsingEnabledForTesting(true);
20 }
21 };
22
23 class HTMLDocumentParserLoadingTest : public HTMLDocumentParserSimTest,
24 public testing::WithParamInterface<bool> {
25 protected:
HTMLDocumentParserLoadingTest()26 HTMLDocumentParserLoadingTest() {
27 Document::SetThreadedParsingEnabledForTesting(GetParam());
28 }
SheetInHeadBlocksParser()29 static bool SheetInHeadBlocksParser() {
30 return RuntimeEnabledFeatures::BlockHTMLParserOnStyleSheetsEnabled();
31 }
32 };
33
34 INSTANTIATE_TEST_SUITE_P(Threaded,
35 HTMLDocumentParserLoadingTest,
36 testing::Values(true));
37 INSTANTIATE_TEST_SUITE_P(NotThreaded,
38 HTMLDocumentParserLoadingTest,
39 testing::Values(false));
40
TEST_P(HTMLDocumentParserLoadingTest,PauseParsingForExternalStylesheetsInHead)41 TEST_P(HTMLDocumentParserLoadingTest,
42 PauseParsingForExternalStylesheetsInHead) {
43 SimRequest main_resource("https://example.com/test.html", "text/html");
44 SimSubresourceRequest css_head_resource("https://example.com/testHead.css",
45 "text/css");
46
47 LoadURL("https://example.com/test.html");
48
49 main_resource.Complete(R"HTML(
50 <!DOCTYPE html>
51 <html><head>
52 <link rel=stylesheet href=testHead.css>
53 </head><body>
54 <div id="bodyDiv"></div>
55 </body></html>
56 )HTML");
57
58 test::RunPendingTasks();
59 EXPECT_EQ(SheetInHeadBlocksParser(),
60 !GetDocument().getElementById("bodyDiv"));
61 css_head_resource.Complete("");
62 test::RunPendingTasks();
63 EXPECT_TRUE(GetDocument().getElementById("bodyDiv"));
64 }
65
TEST_P(HTMLDocumentParserLoadingTest,BlockingParsingForExternalStylesheetsImportedInHead)66 TEST_P(HTMLDocumentParserLoadingTest,
67 BlockingParsingForExternalStylesheetsImportedInHead) {
68 SimRequest main_resource("https://example.com/test.html", "text/html");
69 SimSubresourceRequest css_head_resource("https://example.com/testHead.css",
70 "text/css");
71
72 LoadURL("https://example.com/test.html");
73
74 main_resource.Complete(R"HTML(
75 <!DOCTYPE html>
76 <html><head>
77 <style>
78 @import 'testHead.css'
79 </style>
80 </head><body>
81 <div id="bodyDiv"></div>
82 </body></html>
83 )HTML");
84
85 test::RunPendingTasks();
86 EXPECT_EQ(SheetInHeadBlocksParser(),
87 !GetDocument().getElementById("bodyDiv"));
88 css_head_resource.Complete("");
89 test::RunPendingTasks();
90 EXPECT_TRUE(GetDocument().getElementById("bodyDiv"));
91 }
92
TEST_P(HTMLDocumentParserLoadingTest,ShouldPauseParsingForExternalStylesheetsInBody)93 TEST_P(HTMLDocumentParserLoadingTest,
94 ShouldPauseParsingForExternalStylesheetsInBody) {
95 SimRequest main_resource("https://example.com/test.html", "text/html");
96 SimSubresourceRequest css_head_resource("https://example.com/testHead.css",
97 "text/css");
98 SimSubresourceRequest css_body_resource("https://example.com/testBody.css",
99 "text/css");
100
101 LoadURL("https://example.com/test.html");
102
103 main_resource.Complete(R"HTML(
104 <!DOCTYPE html>
105 <html><head>
106 <link rel=stylesheet href=testHead.css>
107 </head><body>
108 <div id="before"></div>
109 <link rel=stylesheet href=testBody.css>
110 <div id="after"></div>
111 </body></html>
112 )HTML");
113
114 test::RunPendingTasks();
115 EXPECT_EQ(SheetInHeadBlocksParser(), !GetDocument().getElementById("before"));
116 EXPECT_FALSE(GetDocument().getElementById("after"));
117
118 // Completing the head css should progress parsing past #before.
119 css_head_resource.Complete("");
120 test::RunPendingTasks();
121 EXPECT_TRUE(GetDocument().getElementById("before"));
122 EXPECT_FALSE(GetDocument().getElementById("after"));
123
124 // Completing the body resource and pumping the tasks should continue parsing
125 // and create the "after" div.
126 css_body_resource.Complete("");
127 test::RunPendingTasks();
128 EXPECT_TRUE(GetDocument().getElementById("before"));
129 EXPECT_TRUE(GetDocument().getElementById("after"));
130 }
131
TEST_P(HTMLDocumentParserLoadingTest,ShouldPauseParsingForExternalStylesheetsInBodyIncremental)132 TEST_P(HTMLDocumentParserLoadingTest,
133 ShouldPauseParsingForExternalStylesheetsInBodyIncremental) {
134 SimRequest main_resource("https://example.com/test.html", "text/html");
135 SimSubresourceRequest css_head_resource("https://example.com/testHead.css",
136 "text/css");
137 SimSubresourceRequest css_body_resource1("https://example.com/testBody1.css",
138 "text/css");
139 SimSubresourceRequest css_body_resource2("https://example.com/testBody2.css",
140 "text/css");
141 SimSubresourceRequest css_body_resource3("https://example.com/testBody3.css",
142 "text/css");
143
144 LoadURL("https://example.com/test.html");
145
146 main_resource.Write(R"HTML(
147 <!DOCTYPE html>
148 <html><head>
149 <link rel=stylesheet href=testHead.css>
150 </head><body>
151 <div id="before"></div>
152 <link rel=stylesheet href=testBody1.css>
153 <div id="after1"></div>
154 )HTML");
155
156 test::RunPendingTasks();
157 EXPECT_EQ(SheetInHeadBlocksParser(), !GetDocument().getElementById("before"));
158 EXPECT_FALSE(GetDocument().getElementById("after1"));
159 EXPECT_FALSE(GetDocument().getElementById("after2"));
160 EXPECT_FALSE(GetDocument().getElementById("after3"));
161
162 main_resource.Write(
163 "<link rel=stylesheet href=testBody2.css>"
164 "<div id=\"after2\"></div>");
165
166 test::RunPendingTasks();
167 EXPECT_EQ(SheetInHeadBlocksParser(), !GetDocument().getElementById("before"));
168 EXPECT_FALSE(GetDocument().getElementById("after1"));
169 EXPECT_FALSE(GetDocument().getElementById("after2"));
170 EXPECT_FALSE(GetDocument().getElementById("after3"));
171
172 main_resource.Complete(R"HTML(
173 <link rel=stylesheet href=testBody3.css>
174 <div id="after3"></div>
175 </body></html>
176 )HTML");
177
178 test::RunPendingTasks();
179 EXPECT_EQ(SheetInHeadBlocksParser(), !GetDocument().getElementById("before"));
180 EXPECT_FALSE(GetDocument().getElementById("after1"));
181 EXPECT_FALSE(GetDocument().getElementById("after2"));
182 EXPECT_FALSE(GetDocument().getElementById("after3"));
183
184 // Completing the head css should progress parsing past #before.
185 css_head_resource.Complete("");
186 test::RunPendingTasks();
187 EXPECT_TRUE(GetDocument().getElementById("before"));
188 EXPECT_FALSE(GetDocument().getElementById("after1"));
189 EXPECT_FALSE(GetDocument().getElementById("after2"));
190 EXPECT_FALSE(GetDocument().getElementById("after3"));
191
192 // Completing the second css shouldn't change anything
193 css_body_resource2.Complete("");
194 test::RunPendingTasks();
195 EXPECT_TRUE(GetDocument().getElementById("before"));
196 EXPECT_FALSE(GetDocument().getElementById("after1"));
197 EXPECT_FALSE(GetDocument().getElementById("after2"));
198 EXPECT_FALSE(GetDocument().getElementById("after3"));
199
200 // Completing the first css should allow the parser to continue past it and
201 // the second css which was already completed and then pause again before the
202 // third css.
203 css_body_resource1.Complete("");
204 test::RunPendingTasks();
205 EXPECT_TRUE(GetDocument().getElementById("before"));
206 EXPECT_TRUE(GetDocument().getElementById("after1"));
207 EXPECT_TRUE(GetDocument().getElementById("after2"));
208 EXPECT_FALSE(GetDocument().getElementById("after3"));
209
210 // Completing the third css should let it continue to the end.
211 css_body_resource3.Complete("");
212 test::RunPendingTasks();
213 EXPECT_TRUE(GetDocument().getElementById("before"));
214 EXPECT_TRUE(GetDocument().getElementById("after1"));
215 EXPECT_TRUE(GetDocument().getElementById("after2"));
216 EXPECT_TRUE(GetDocument().getElementById("after3"));
217 }
218
TEST_P(HTMLDocumentParserLoadingTest,ShouldNotPauseParsingForExternalNonMatchingStylesheetsInBody)219 TEST_P(HTMLDocumentParserLoadingTest,
220 ShouldNotPauseParsingForExternalNonMatchingStylesheetsInBody) {
221 SimRequest main_resource("https://example.com/test.html", "text/html");
222 SimSubresourceRequest css_head_resource("https://example.com/testHead.css",
223 "text/css");
224
225 LoadURL("https://example.com/test.html");
226
227 main_resource.Complete(R"HTML(
228 <!DOCTYPE html>
229 <html><head>
230 <link rel=stylesheet href=testHead.css>
231 </head><body>
232 <div id="before"></div>
233 <link rel=stylesheet href=testBody.css type='print'>
234 <div id="after"></div>
235 </body></html>
236 )HTML");
237
238 test::RunPendingTasks();
239 EXPECT_EQ(SheetInHeadBlocksParser(), !GetDocument().getElementById("before"));
240 EXPECT_EQ(SheetInHeadBlocksParser(), !GetDocument().getElementById("after"));
241
242 // Completing the head css should progress parsing past both #before and
243 // #after.
244 css_head_resource.Complete("");
245 test::RunPendingTasks();
246 EXPECT_TRUE(GetDocument().getElementById("before"));
247 EXPECT_TRUE(GetDocument().getElementById("after"));
248 }
249
TEST_P(HTMLDocumentParserLoadingTest,ShouldPauseParsingForExternalStylesheetsImportedInBody)250 TEST_P(HTMLDocumentParserLoadingTest,
251 ShouldPauseParsingForExternalStylesheetsImportedInBody) {
252 SimRequest main_resource("https://example.com/test.html", "text/html");
253 SimSubresourceRequest css_head_resource("https://example.com/testHead.css",
254 "text/css");
255 SimSubresourceRequest css_body_resource("https://example.com/testBody.css",
256 "text/css");
257
258 LoadURL("https://example.com/test.html");
259
260 main_resource.Complete(R"HTML(
261 <!DOCTYPE html>
262 <html><head>
263 <link rel=stylesheet href=testHead.css>
264 </head><body>
265 <div id="before"></div>
266 <style>
267 @import 'testBody.css'
268 </style>
269 <div id="after"></div>
270 </body></html>
271 )HTML");
272
273 test::RunPendingTasks();
274 EXPECT_EQ(SheetInHeadBlocksParser(), !GetDocument().getElementById("before"));
275 EXPECT_FALSE(GetDocument().getElementById("after"));
276
277 // Completing the head css should progress parsing past #before.
278 css_head_resource.Complete("");
279 test::RunPendingTasks();
280 EXPECT_TRUE(GetDocument().getElementById("before"));
281 EXPECT_FALSE(GetDocument().getElementById("after"));
282
283 // Completing the body resource and pumping the tasks should continue parsing
284 // and create the "after" div.
285 css_body_resource.Complete("");
286 test::RunPendingTasks();
287 EXPECT_TRUE(GetDocument().getElementById("before"));
288 EXPECT_TRUE(GetDocument().getElementById("after"));
289 }
290
TEST_P(HTMLDocumentParserLoadingTest,ShouldPauseParsingForExternalStylesheetsWrittenInBody)291 TEST_P(HTMLDocumentParserLoadingTest,
292 ShouldPauseParsingForExternalStylesheetsWrittenInBody) {
293 SimRequest main_resource("https://example.com/test.html", "text/html");
294 SimSubresourceRequest css_head_resource("https://example.com/testHead.css",
295 "text/css");
296 SimSubresourceRequest css_body_resource("https://example.com/testBody.css",
297 "text/css");
298
299 LoadURL("https://example.com/test.html");
300
301 main_resource.Complete(R"HTML(
302 <!DOCTYPE html>
303 <html><head>
304 <link rel=stylesheet href=testHead.css>
305 </head><body>
306 <div id="before"></div>
307 <script>
308 document.write('<link rel=stylesheet href=testBody.css>');
309 </script>
310 <div id="after"></div>
311 </body></html>
312 )HTML");
313
314 test::RunPendingTasks();
315 EXPECT_EQ(SheetInHeadBlocksParser(), !GetDocument().getElementById("before"));
316 EXPECT_FALSE(GetDocument().getElementById("after"));
317
318 // Completing the head css should progress parsing past #before.
319 css_head_resource.Complete("");
320 test::RunPendingTasks();
321 EXPECT_TRUE(GetDocument().getElementById("before"));
322 EXPECT_FALSE(GetDocument().getElementById("after"));
323
324 // Completing the body resource and pumping the tasks should continue parsing
325 // and create the "after" div.
326 css_body_resource.Complete("");
327 test::RunPendingTasks();
328 EXPECT_TRUE(GetDocument().getElementById("before"));
329 EXPECT_TRUE(GetDocument().getElementById("after"));
330 }
331
TEST_P(HTMLDocumentParserLoadingTest,PendingHeadStylesheetBlockingParserForBodyInlineStyle)332 TEST_P(HTMLDocumentParserLoadingTest,
333 PendingHeadStylesheetBlockingParserForBodyInlineStyle) {
334 SimRequest main_resource("https://example.com/test.html", "text/html");
335 SimSubresourceRequest css_head_resource("https://example.com/testHead.css",
336 "text/css");
337
338 LoadURL("https://example.com/test.html");
339
340 main_resource.Complete(R"HTML(
341 <!DOCTYPE html>
342 <html><head>
343 <link rel=stylesheet href=testHead.css>
344 </head><body>
345 <div id="before"></div>
346 <style>
347 </style>
348 <div id="after"></div>
349 </body></html>
350 )HTML");
351
352 test::RunPendingTasks();
353 EXPECT_EQ(SheetInHeadBlocksParser(), !GetDocument().getElementById("before"));
354 EXPECT_EQ(SheetInHeadBlocksParser(), !GetDocument().getElementById("after"));
355 css_head_resource.Complete("");
356 test::RunPendingTasks();
357 EXPECT_TRUE(GetDocument().getElementById("before"));
358 EXPECT_TRUE(GetDocument().getElementById("after"));
359 }
360
TEST_P(HTMLDocumentParserLoadingTest,PendingHeadStylesheetBlockingParserForBodyShadowDom)361 TEST_P(HTMLDocumentParserLoadingTest,
362 PendingHeadStylesheetBlockingParserForBodyShadowDom) {
363 SimRequest main_resource("https://example.com/test.html", "text/html");
364 SimSubresourceRequest css_head_resource("https://example.com/testHead.css",
365 "text/css");
366
367 LoadURL("https://example.com/test.html");
368
369 // The marquee tag has a shadow DOM that synchronously applies a stylesheet.
370 main_resource.Complete(R"HTML(
371 <!DOCTYPE html>
372 <html><head>
373 <link rel=stylesheet href=testHead.css>
374 </head><body>
375 <div id="before"></div>
376 <marquee>Marquee</marquee>
377 <div id="after"></div>
378 </body></html>
379 )HTML");
380
381 test::RunPendingTasks();
382 EXPECT_EQ(SheetInHeadBlocksParser(), !GetDocument().getElementById("before"));
383 EXPECT_EQ(SheetInHeadBlocksParser(), !GetDocument().getElementById("after"));
384 css_head_resource.Complete("");
385 test::RunPendingTasks();
386 EXPECT_TRUE(GetDocument().getElementById("before"));
387 EXPECT_TRUE(GetDocument().getElementById("after"));
388 }
389
TEST_P(HTMLDocumentParserLoadingTest,ShouldNotPauseParsingForExternalStylesheetsAttachedInBody)390 TEST_P(HTMLDocumentParserLoadingTest,
391 ShouldNotPauseParsingForExternalStylesheetsAttachedInBody) {
392 SimRequest main_resource("https://example.com/test.html", "text/html");
393 SimSubresourceRequest css_async_resource("https://example.com/testAsync.css",
394 "text/css");
395
396 LoadURL("https://example.com/test.html");
397
398 main_resource.Complete(R"HTML(
399 <!DOCTYPE html>
400 <html><head>
401 </head><body>
402 <div id="before"></div>
403 <script>
404 var attach = document.getElementsByTagName('script')[0];
405 var link = document.createElement('link');
406 link.rel = 'stylesheet';
407 link.type = 'text/css';
408 link.href = 'testAsync.css';
409 link.media = 'all';
410 attach.appendChild(link);
411 </script>
412 <div id="after"></div>
413 </body></html>
414 )HTML");
415
416 test::RunPendingTasks();
417 EXPECT_TRUE(GetDocument().getElementById("before"));
418 EXPECT_TRUE(GetDocument().getElementById("after"));
419
420 css_async_resource.Complete("");
421 }
422
TEST_F(HTMLDocumentParserSimTest,NoRewindNoDocWrite)423 TEST_F(HTMLDocumentParserSimTest, NoRewindNoDocWrite) {
424 SimRequest main_resource("https://example.com/test.html", "text/html");
425 LoadURL("https://example.com/test.html");
426
427 main_resource.Complete(R"HTML(
428 <!DOCTYPE html>
429 <html><body>no doc write
430 </body></html>
431 )HTML");
432
433 test::RunPendingTasks();
434 EXPECT_EQ(0U, GetDiscardedTokenCountForTesting());
435 }
436
TEST_F(HTMLDocumentParserSimTest,RewindBrokenToken)437 TEST_F(HTMLDocumentParserSimTest, RewindBrokenToken) {
438 SimRequest main_resource("https://example.com/test.html", "text/html");
439 LoadURL("https://example.com/test.html");
440
441 main_resource.Complete(R"HTML(
442 <!DOCTYPE html>
443 <script>
444 document.write('<a');
445 </script>
446 )HTML");
447
448 test::RunPendingTasks();
449 EXPECT_EQ(2U, GetDiscardedTokenCountForTesting());
450 }
451
TEST_F(HTMLDocumentParserSimTest,RewindDifferentNamespace)452 TEST_F(HTMLDocumentParserSimTest, RewindDifferentNamespace) {
453 SimRequest main_resource("https://example.com/test.html", "text/html");
454 LoadURL("https://example.com/test.html");
455
456 main_resource.Complete(R"HTML(
457 <!DOCTYPE html>
458 <script>
459 document.write('<svg>');
460 </script>
461 )HTML");
462
463 test::RunPendingTasks();
464 EXPECT_EQ(2U, GetDiscardedTokenCountForTesting());
465 }
466
TEST_F(HTMLDocumentParserSimTest,NoRewindSaneDocWrite1)467 TEST_F(HTMLDocumentParserSimTest, NoRewindSaneDocWrite1) {
468 SimRequest main_resource("https://example.com/test.html", "text/html");
469 LoadURL("https://example.com/test.html");
470
471 main_resource.Complete(
472 "<!DOCTYPE html>"
473 "<script>"
474 "document.write('<script>console.log(\'hello world\');<\\/script>');"
475 "</script>");
476
477 test::RunPendingTasks();
478 EXPECT_EQ(0U, GetDiscardedTokenCountForTesting());
479 }
480
TEST_F(HTMLDocumentParserSimTest,NoRewindSaneDocWrite2)481 TEST_F(HTMLDocumentParserSimTest, NoRewindSaneDocWrite2) {
482 SimRequest main_resource("https://example.com/test.html", "text/html");
483 LoadURL("https://example.com/test.html");
484
485 main_resource.Complete(R"HTML(
486 <!DOCTYPE html>
487 <script>
488 document.write('<p>hello world<\\/p><a>yo');
489 </script>
490 )HTML");
491
492 test::RunPendingTasks();
493 EXPECT_EQ(0U, GetDiscardedTokenCountForTesting());
494 }
495
TEST_F(HTMLDocumentParserSimTest,NoRewindSaneDocWriteWithTitle)496 TEST_F(HTMLDocumentParserSimTest, NoRewindSaneDocWriteWithTitle) {
497 SimRequest main_resource("https://example.com/test.html", "text/html");
498 LoadURL("https://example.com/test.html");
499
500 main_resource.Complete(R"HTML(
501 <!DOCTYPE html>
502 <html>
503 <head>
504 <title></title>
505 <script>document.write('<p>testing');</script>
506 </head>
507 <body>
508 </body>
509 </html>
510 )HTML");
511
512 test::RunPendingTasks();
513 EXPECT_EQ(0U, GetDiscardedTokenCountForTesting());
514 }
515
516 } // namespace blink
517