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