1// Copyright 2014-2017 The html5ever Project Developers. See the
2// COPYRIGHT file at the top-level directory of this distribution.
3//
4// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7// option. This file may not be copied, modified, or distributed
8// except according to those terms.
9
10// The tree builder rules, as a single, enormous nested match expression.
11
12use crate::interface::Quirks;
13use crate::tokenizer::states::{Rawtext, Rcdata, ScriptData};
14use crate::tokenizer::TagKind::{EndTag, StartTag};
15use crate::tree_builder::tag_sets::*;
16use crate::tree_builder::types::*;
17use crate::tree_builder::{
18 create_element, html_elem, ElemName, NodeOrText::AppendNode, StrTendril, Tag, TreeBuilder,
19 TreeSink,
20};
21use crate::QualName;
22use markup5ever::{expanded_name, local_name, namespace_url, ns};
23use std::borrow::Cow::Borrowed;
24
25use crate::tendril::SliceExt;
26use match_token::match_token;
27
28fn any_not_whitespace(x: &StrTendril) -> bool {
29 // FIXME: this might be much faster as a byte scan
30 x.chars().any(|c: char| !c.is_ascii_whitespace())
31}
32
33fn current_node<Handle>(open_elems: &[Handle]) -> &Handle {
34 open_elems.last().expect(msg:"no current element")
35}
36
37#[doc(hidden)]
38impl<Handle, Sink> TreeBuilder<Handle, Sink>
39where
40 Handle: Clone,
41 Sink: TreeSink<Handle = Handle>,
42{
43 pub(crate) fn step(&self, mode: InsertionMode, token: Token) -> ProcessResult<Handle> {
44 self.debug_step(mode, &token);
45
46 match mode {
47 //§ the-initial-insertion-mode
48 Initial => match_token!(token {
49 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
50 CharacterTokens(Whitespace, _) => Done,
51 CommentToken(text) => self.append_comment_to_doc(text),
52 token => {
53 if !self.opts.iframe_srcdoc {
54 self.unexpected(&token);
55 self.set_quirks_mode(Quirks);
56 }
57 Reprocess(BeforeHtml, token)
58 }
59 }),
60
61 //§ the-before-html-insertion-mode
62 BeforeHtml => match_token!(token {
63 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
64 CharacterTokens(Whitespace, _) => Done,
65 CommentToken(text) => self.append_comment_to_doc(text),
66
67 tag @ <html> => {
68 self.create_root(tag.attrs);
69 self.mode.set(BeforeHead);
70 Done
71 }
72
73 </head> </body> </html> </br> => else,
74
75 tag @ </_> => self.unexpected(&tag),
76
77 token => {
78 self.create_root(vec!());
79 Reprocess(BeforeHead, token)
80 }
81 }),
82
83 //§ the-before-head-insertion-mode
84 BeforeHead => match_token!(token {
85 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
86 CharacterTokens(Whitespace, _) => Done,
87 CommentToken(text) => self.append_comment(text),
88
89 <html> => self.step(InBody, token),
90
91 tag @ <head> => {
92 *self.head_elem.borrow_mut() = Some(self.insert_element_for(tag));
93 self.mode.set(InHead);
94 Done
95 }
96
97 </head> </body> </html> </br> => else,
98
99 tag @ </_> => self.unexpected(&tag),
100
101 token => {
102 *self.head_elem.borrow_mut() = Some(self.insert_phantom(local_name!("head")));
103 Reprocess(InHead, token)
104 }
105 }),
106
107 //§ parsing-main-inhead
108 // https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inhead
109 InHead => match_token!(token {
110 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
111 CharacterTokens(Whitespace, text) => self.append_text(text),
112 CommentToken(text) => self.append_comment(text),
113
114 <html> => self.step(InBody, token),
115
116 tag @ <base> <basefont> <bgsound> <link> <meta> => {
117 // FIXME: handle <meta charset=...> and <meta http-equiv="Content-Type">
118 self.insert_and_pop_element_for(tag);
119 DoneAckSelfClosing
120 }
121
122 tag @ <title> => {
123 self.parse_raw_data(tag, Rcdata)
124 }
125
126 tag @ <noframes> <style> <noscript> => {
127 if (!self.opts.scripting_enabled) && (tag.name == local_name!("noscript")) {
128 self.insert_element_for(tag);
129 self.mode.set(InHeadNoscript);
130 Done
131 } else {
132 self.parse_raw_data(tag, Rawtext)
133 }
134 }
135
136 tag @ <script> => {
137 let elem = create_element(
138 &self.sink, QualName::new(None, ns!(html), local_name!("script")),
139 tag.attrs);
140 if self.is_fragment() {
141 self.sink.mark_script_already_started(&elem);
142 }
143 self.insert_appropriately(AppendNode(elem.clone()), None);
144 self.open_elems.borrow_mut().push(elem);
145 self.to_raw_text_mode(ScriptData)
146 }
147
148 </head> => {
149 self.pop();
150 self.mode.set(AfterHead);
151 Done
152 }
153
154 </body> </html> </br> => else,
155
156 tag @ <template> => {
157 self.active_formatting.borrow_mut().push(Marker);
158 self.frameset_ok.set(false);
159 self.mode.set(InTemplate);
160 self.template_modes.borrow_mut().push(InTemplate);
161
162 if (self.should_attach_declarative_shadow(&tag)) {
163 // Attach shadow path
164
165 // Step 1. Let declarative shadow host element be adjusted current node.
166 let mut shadow_host = self.open_elems.borrow().last().unwrap().clone();
167 if self.is_fragment() && self.open_elems.borrow().len() == 1 {
168 shadow_host = self.context_elem.borrow().clone().unwrap();
169 }
170
171 // Step 2. Let template be the result of insert a foreign element for template start tag, with HTML namespace and true.
172 let template = self.insert_foreign_element(tag.clone(), ns!(html), true);
173
174 // Step 3 - 8.
175 // Attach a shadow root with declarative shadow host element, mode, clonable, serializable, delegatesFocus, and "named".
176 if self.attach_declarative_shadow(&tag, &shadow_host, &template).is_err() {
177 // Step 8.1.1. Insert an element at the adjusted insertion location with template.
178 // Pop the current template element created in step 2 first.
179 self.pop();
180 self.insert_element_for(tag);
181 }
182 } else {
183 self.insert_element_for(tag);
184 }
185
186 Done
187 }
188
189 tag @ </template> => {
190 if !self.in_html_elem_named(local_name!("template")) {
191 self.unexpected(&tag);
192 } else {
193 self.generate_implied_end(thorough_implied_end);
194 self.expect_to_close(local_name!("template"));
195 self.clear_active_formatting_to_marker();
196 self.template_modes.borrow_mut().pop();
197 self.mode.set(self.reset_insertion_mode());
198 }
199 Done
200 }
201
202 <head> => self.unexpected(&token),
203 tag @ </_> => self.unexpected(&tag),
204
205 token => {
206 self.pop();
207 Reprocess(AfterHead, token)
208 }
209 }),
210
211 //§ parsing-main-inheadnoscript
212 InHeadNoscript => match_token!(token {
213 <html> => self.step(InBody, token),
214
215 </noscript> => {
216 self.pop();
217 self.mode.set(InHead);
218 Done
219 },
220
221 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
222 CharacterTokens(Whitespace, _) => self.step(InHead, token),
223
224 CommentToken(_) => self.step(InHead, token),
225
226 <basefont> <bgsound> <link> <meta> <noframes> <style>
227 => self.step(InHead, token),
228
229 </br> => else,
230
231 <head> <noscript> => self.unexpected(&token),
232 tag @ </_> => self.unexpected(&tag),
233
234 token => {
235 self.unexpected(&token);
236 self.pop();
237 Reprocess(InHead, token)
238 },
239 }),
240
241 //§ the-after-head-insertion-mode
242 AfterHead => match_token!(token {
243 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
244 CharacterTokens(Whitespace, text) => self.append_text(text),
245 CommentToken(text) => self.append_comment(text),
246
247 <html> => self.step(InBody, token),
248
249 tag @ <body> => {
250 self.insert_element_for(tag);
251 self.frameset_ok.set(false);
252 self.mode.set(InBody);
253 Done
254 }
255
256 tag @ <frameset> => {
257 self.insert_element_for(tag);
258 self.mode.set(InFrameset);
259 Done
260 }
261
262 <base> <basefont> <bgsound> <link> <meta>
263 <noframes> <script> <style> <template> <title> => {
264 self.unexpected(&token);
265 let head = self.head_elem.borrow().as_ref().expect("no head element").clone();
266 self.push(&head);
267 let result = self.step(InHead, token);
268 self.remove_from_stack(&head);
269 result
270 }
271
272 </template> => self.step(InHead, token),
273
274 </body> </html> </br> => else,
275
276 <head> => self.unexpected(&token),
277 tag @ </_> => self.unexpected(&tag),
278
279 token => {
280 self.insert_phantom(local_name!("body"));
281 Reprocess(InBody, token)
282 }
283 }),
284
285 //§ parsing-main-inbody
286 InBody => match_token!(token {
287 NullCharacterToken => self.unexpected(&token),
288
289 CharacterTokens(_, text) => {
290 self.reconstruct_formatting();
291 if any_not_whitespace(&text) {
292 self.frameset_ok.set(false);
293 }
294 self.append_text(text)
295 }
296
297 CommentToken(text) => self.append_comment(text),
298
299 tag @ <html> => {
300 self.unexpected(&tag);
301 if !self.in_html_elem_named(local_name!("template")) {
302 let open_elems = self.open_elems.borrow();
303 let top = html_elem(&open_elems);
304 self.sink.add_attrs_if_missing(top, tag.attrs);
305 }
306 Done
307 }
308
309 <base> <basefont> <bgsound> <link> <meta> <noframes>
310 <script> <style> <template> <title> </template> => {
311 self.step(InHead, token)
312 }
313
314 tag @ <body> => {
315 self.unexpected(&tag);
316 let body_elem = self.body_elem().as_deref().cloned();
317 match body_elem {
318 Some(ref node) if self.open_elems.borrow().len() != 1 &&
319 !self.in_html_elem_named(local_name!("template")) => {
320 self.frameset_ok.set(false);
321 self.sink.add_attrs_if_missing(node, tag.attrs)
322 },
323 _ => {}
324 }
325 Done
326 }
327
328 tag @ <frameset> => {
329 self.unexpected(&tag);
330 if !self.frameset_ok.get() { return Done; }
331
332 let body = unwrap_or_return!(self.body_elem(), Done).clone();
333 self.sink.remove_from_parent(&body);
334
335 // FIXME: can we get here in the fragment case?
336 // What to do with the first element then?
337 self.open_elems.borrow_mut().truncate(1);
338 self.insert_element_for(tag);
339 self.mode.set(InFrameset);
340 Done
341 }
342
343 EOFToken => {
344 if !self.template_modes.borrow().is_empty() {
345 self.step(InTemplate, token)
346 } else {
347 self.check_body_end();
348 self.stop_parsing()
349 }
350 }
351
352 </body> => {
353 if self.in_scope_named(default_scope, local_name!("body")) {
354 self.check_body_end();
355 self.mode.set(AfterBody);
356 } else {
357 self.sink.parse_error(Borrowed("</body> with no <body> in scope"));
358 }
359 Done
360 }
361
362 </html> => {
363 if self.in_scope_named(default_scope, local_name!("body")) {
364 self.check_body_end();
365 Reprocess(AfterBody, token)
366 } else {
367 self.sink.parse_error(Borrowed("</html> with no <body> in scope"));
368 Done
369 }
370 }
371
372 tag @ <address> <article> <aside> <blockquote> <center> <details> <dialog>
373 <dir> <div> <dl> <fieldset> <figcaption> <figure> <footer> <header>
374 <hgroup> <main> <nav> <ol> <p> <search> <section> <summary> <ul> => {
375 self.close_p_element_in_button_scope();
376 self.insert_element_for(tag);
377 Done
378 }
379
380 tag @ <menu> => {
381 self.close_p_element_in_button_scope();
382 self.insert_element_for(tag);
383 Done
384 }
385
386 tag @ <h1> <h2> <h3> <h4> <h5> <h6> => {
387 self.close_p_element_in_button_scope();
388 if self.current_node_in(heading_tag) {
389 self.sink.parse_error(Borrowed("nested heading tags"));
390 self.pop();
391 }
392 self.insert_element_for(tag);
393 Done
394 }
395
396 tag @ <pre> <listing> => {
397 self.close_p_element_in_button_scope();
398 self.insert_element_for(tag);
399 self.ignore_lf.set(true);
400 self.frameset_ok.set(false);
401 Done
402 }
403
404 tag @ <form> => {
405 if self.form_elem.borrow().is_some() &&
406 !self.in_html_elem_named(local_name!("template")) {
407 self.sink.parse_error(Borrowed("nested forms"));
408 } else {
409 self.close_p_element_in_button_scope();
410 let elem = self.insert_element_for(tag);
411 if !self.in_html_elem_named(local_name!("template")) {
412 *self.form_elem.borrow_mut() = Some(elem);
413 }
414 }
415 Done
416 }
417
418 tag @ <li> <dd> <dt> => {
419 declare_tag_set!(close_list = "li");
420 declare_tag_set!(close_defn = "dd" "dt");
421 declare_tag_set!(extra_special = [special_tag] - "address" "div" "p");
422 let list = match tag.name {
423 local_name!("li") => true,
424 local_name!("dd") | local_name!("dt") => false,
425 _ => unreachable!(),
426 };
427
428 self.frameset_ok.set(false);
429
430 let mut to_close = None;
431 for node in self.open_elems.borrow().iter().rev() {
432 let elem_name = self.sink.elem_name(node);
433 let name = elem_name.expanded();
434 let can_close = if list {
435 close_list(name)
436 } else {
437 close_defn(name)
438 };
439 if can_close {
440 to_close = Some(name.local.clone());
441 break;
442 }
443 if extra_special(name) {
444 break;
445 }
446 }
447
448 if let Some(name) = to_close {
449 self.generate_implied_end_except(name.clone());
450 self.expect_to_close(name);
451 }
452
453 self.close_p_element_in_button_scope();
454 self.insert_element_for(tag);
455 Done
456 }
457
458 tag @ <plaintext> => {
459 self.close_p_element_in_button_scope();
460 self.insert_element_for(tag);
461 ToPlaintext
462 }
463
464 tag @ <button> => {
465 if self.in_scope_named(default_scope, local_name!("button")) {
466 self.sink.parse_error(Borrowed("nested buttons"));
467 self.generate_implied_end(cursory_implied_end);
468 self.pop_until_named(local_name!("button"));
469 }
470 self.reconstruct_formatting();
471 self.insert_element_for(tag);
472 self.frameset_ok.set(false);
473 Done
474 }
475
476 tag @ </address> </article> </aside> </blockquote> </button> </center>
477 </details> </dialog> </dir> </div> </dl> </fieldset> </figcaption>
478 </figure> </footer> </header> </hgroup> </listing> </main> </menu>
479 </nav> </ol> </pre> </search> </section> </summary> </ul> => {
480 if !self.in_scope_named(default_scope, tag.name.clone()) {
481 self.unexpected(&tag);
482 } else {
483 self.generate_implied_end(cursory_implied_end);
484 self.expect_to_close(tag.name);
485 }
486 Done
487 }
488
489 </form> => {
490 if !self.in_html_elem_named(local_name!("template")) {
491 // Can't use unwrap_or_return!() due to rust-lang/rust#16617.
492 let node = match self.form_elem.take() {
493 None => {
494 self.sink.parse_error(Borrowed("Null form element pointer on </form>"));
495 return Done;
496 }
497 Some(x) => x,
498 };
499 if !self.in_scope(default_scope, |n| self.sink.same_node(&node, &n)) {
500 self.sink.parse_error(Borrowed("Form element not in scope on </form>"));
501 return Done;
502 }
503 self.generate_implied_end(cursory_implied_end);
504 let current = self.current_node().clone();
505 self.remove_from_stack(&node);
506 if !self.sink.same_node(&current, &node) {
507 self.sink.parse_error(Borrowed("Bad open element on </form>"));
508 }
509 } else {
510 if !self.in_scope_named(default_scope, local_name!("form")) {
511 self.sink.parse_error(Borrowed("Form element not in scope on </form>"));
512 return Done;
513 }
514 self.generate_implied_end(cursory_implied_end);
515 if !self.current_node_named(local_name!("form")) {
516 self.sink.parse_error(Borrowed("Bad open element on </form>"));
517 }
518 self.pop_until_named(local_name!("form"));
519 }
520 Done
521 }
522
523 </p> => {
524 if !self.in_scope_named(button_scope, local_name!("p")) {
525 self.sink.parse_error(Borrowed("No <p> tag to close"));
526 self.insert_phantom(local_name!("p"));
527 }
528 self.close_p_element();
529 Done
530 }
531
532 tag @ </li> </dd> </dt> => {
533 let in_scope = if tag.name == local_name!("li") {
534 self.in_scope_named(list_item_scope, tag.name.clone())
535 } else {
536 self.in_scope_named(default_scope, tag.name.clone())
537 };
538 if in_scope {
539 self.generate_implied_end_except(tag.name.clone());
540 self.expect_to_close(tag.name);
541 } else {
542 self.sink.parse_error(Borrowed("No matching tag to close"));
543 }
544 Done
545 }
546
547 tag @ </h1> </h2> </h3> </h4> </h5> </h6> => {
548 if self.in_scope(default_scope, |n| self.elem_in(&n, heading_tag)) {
549 self.generate_implied_end(cursory_implied_end);
550 if !self.current_node_named(tag.name) {
551 self.sink.parse_error(Borrowed("Closing wrong heading tag"));
552 }
553 self.pop_until(heading_tag);
554 } else {
555 self.sink.parse_error(Borrowed("No heading tag to close"));
556 }
557 Done
558 }
559
560 tag @ <a> => {
561 self.handle_misnested_a_tags(&tag);
562 self.reconstruct_formatting();
563 self.create_formatting_element_for(tag);
564 Done
565 }
566
567 tag @ <b> <big> <code> <em> <font> <i> <s> <small> <strike> <strong> <tt> <u> => {
568 self.reconstruct_formatting();
569 self.create_formatting_element_for(tag);
570 Done
571 }
572
573 tag @ <nobr> => {
574 self.reconstruct_formatting();
575 if self.in_scope_named(default_scope, local_name!("nobr")) {
576 self.sink.parse_error(Borrowed("Nested <nobr>"));
577 self.adoption_agency(local_name!("nobr"));
578 self.reconstruct_formatting();
579 }
580 self.create_formatting_element_for(tag);
581 Done
582 }
583
584 tag @ </a> </b> </big> </code> </em> </font> </i> </nobr>
585 </s> </small> </strike> </strong> </tt> </u> => {
586 self.adoption_agency(tag.name);
587 Done
588 }
589
590 tag @ <applet> <marquee> <object> => {
591 self.reconstruct_formatting();
592 self.insert_element_for(tag);
593 self.active_formatting.borrow_mut().push(Marker);
594 self.frameset_ok.set(false);
595 Done
596 }
597
598 tag @ </applet> </marquee> </object> => {
599 if !self.in_scope_named(default_scope, tag.name.clone()) {
600 self.unexpected(&tag);
601 } else {
602 self.generate_implied_end(cursory_implied_end);
603 self.expect_to_close(tag.name);
604 self.clear_active_formatting_to_marker();
605 }
606 Done
607 }
608
609 tag @ <table> => {
610 if self.quirks_mode.get() != Quirks {
611 self.close_p_element_in_button_scope();
612 }
613 self.insert_element_for(tag);
614 self.frameset_ok.set(false);
615 self.mode.set(InTable);
616 Done
617 }
618
619 tag @ </br> => {
620 self.unexpected(&tag);
621 self.step(InBody, TagToken(Tag {
622 kind: StartTag,
623 attrs: vec!(),
624 ..tag
625 }))
626 }
627
628 tag @ <area> <br> <embed> <img> <keygen> <wbr> <input> => {
629 let keep_frameset_ok = match tag.name {
630 local_name!("input") => self.is_type_hidden(&tag),
631 _ => false,
632 };
633 self.reconstruct_formatting();
634 self.insert_and_pop_element_for(tag);
635 if !keep_frameset_ok {
636 self.frameset_ok.set(false);
637 }
638 DoneAckSelfClosing
639 }
640
641 tag @ <param> <source> <track> => {
642 self.insert_and_pop_element_for(tag);
643 DoneAckSelfClosing
644 }
645
646 tag @ <hr> => {
647 self.close_p_element_in_button_scope();
648 self.insert_and_pop_element_for(tag);
649 self.frameset_ok.set(false);
650 DoneAckSelfClosing
651 }
652
653 tag @ <image> => {
654 self.unexpected(&tag);
655 self.step(InBody, TagToken(Tag {
656 name: local_name!("img"),
657 ..tag
658 }))
659 }
660
661 tag @ <textarea> => {
662 self.ignore_lf.set(true);
663 self.frameset_ok.set(false);
664 self.parse_raw_data(tag, Rcdata)
665 }
666
667 tag @ <xmp> => {
668 self.close_p_element_in_button_scope();
669 self.reconstruct_formatting();
670 self.frameset_ok.set(false);
671 self.parse_raw_data(tag, Rawtext)
672 }
673
674 tag @ <iframe> => {
675 self.frameset_ok.set(false);
676 self.parse_raw_data(tag, Rawtext)
677 }
678
679 tag @ <noembed> => {
680 self.parse_raw_data(tag, Rawtext)
681 }
682
683 // <noscript> handled in wildcard case below
684
685 tag @ <select> => {
686 self.reconstruct_formatting();
687 self.insert_element_for(tag);
688 self.frameset_ok.set(false);
689 // NB: mode == InBody but possibly self.mode != mode, if
690 // we're processing "as in the rules for InBody".
691 self.mode.set(match self.mode.get() {
692 InTable | InCaption | InTableBody
693 | InRow | InCell => InSelectInTable,
694 _ => InSelect,
695 });
696 Done
697 }
698
699 tag @ <optgroup> <option> => {
700 if self.current_node_named(local_name!("option")) {
701 self.pop();
702 }
703 self.reconstruct_formatting();
704 self.insert_element_for(tag);
705 Done
706 }
707
708 tag @ <rb> <rtc> => {
709 if self.in_scope_named(default_scope, local_name!("ruby")) {
710 self.generate_implied_end(cursory_implied_end);
711 }
712 if !self.current_node_named(local_name!("ruby")) {
713 self.unexpected(&tag);
714 }
715 self.insert_element_for(tag);
716 Done
717 }
718
719 tag @ <rp> <rt> => {
720 if self.in_scope_named(default_scope, local_name!("ruby")) {
721 self.generate_implied_end_except(local_name!("rtc"));
722 }
723 if !self.current_node_named(local_name!("rtc")) && !self.current_node_named(local_name!("ruby")) {
724 self.unexpected(&tag);
725 }
726 self.insert_element_for(tag);
727 Done
728 }
729
730 tag @ <math> => self.enter_foreign(tag, ns!(mathml)),
731
732 tag @ <svg> => self.enter_foreign(tag, ns!(svg)),
733
734 <caption> <col> <colgroup> <frame> <head>
735 <tbody> <td> <tfoot> <th> <thead> <tr> => {
736 self.unexpected(&token);
737 Done
738 }
739
740 tag @ <_> => {
741 if self.opts.scripting_enabled && tag.name == local_name!("noscript") {
742 self.parse_raw_data(tag, Rawtext)
743 } else {
744 self.reconstruct_formatting();
745 self.insert_element_for(tag);
746 Done
747 }
748 }
749
750 tag @ </_> => {
751 self.process_end_tag_in_body(tag);
752 Done
753 }
754
755 // FIXME: This should be unreachable, but match_token requires a
756 // catch-all case.
757 _ => panic!("impossible case in InBody mode"),
758 }),
759
760 //§ parsing-main-incdata
761 Text => match_token!(token {
762 CharacterTokens(_, text) => self.append_text(text),
763
764 EOFToken => {
765 self.unexpected(&token);
766 if self.current_node_named(local_name!("script")) {
767 let open_elems = self.open_elems.borrow();
768 let current = current_node(&open_elems);
769 self.sink.mark_script_already_started(current);
770 }
771 self.pop();
772 Reprocess(self.orig_mode.take().unwrap(), token)
773 }
774
775 tag @ </_> => {
776 let node = self.pop();
777 self.mode.set(self.orig_mode.take().unwrap());
778 if tag.name == local_name!("script") {
779 return Script(node);
780 }
781 Done
782 }
783
784 // The spec doesn't say what to do here.
785 // Other tokens are impossible?
786 _ => panic!("impossible case in Text mode"),
787 }),
788
789 //§ parsing-main-intable
790 InTable => match_token!(token {
791 // FIXME: hack, should implement pat | pat for match_token instead
792 NullCharacterToken => self.process_chars_in_table(token),
793
794 CharacterTokens(..) => self.process_chars_in_table(token),
795
796 CommentToken(text) => self.append_comment(text),
797
798 tag @ <caption> => {
799 self.pop_until_current(table_scope);
800 self.active_formatting.borrow_mut().push(Marker);
801 self.insert_element_for(tag);
802 self.mode.set(InCaption);
803 Done
804 }
805
806 tag @ <colgroup> => {
807 self.pop_until_current(table_scope);
808 self.insert_element_for(tag);
809 self.mode.set(InColumnGroup);
810 Done
811 }
812
813 <col> => {
814 self.pop_until_current(table_scope);
815 self.insert_phantom(local_name!("colgroup"));
816 Reprocess(InColumnGroup, token)
817 }
818
819 tag @ <tbody> <tfoot> <thead> => {
820 self.pop_until_current(table_scope);
821 self.insert_element_for(tag);
822 self.mode.set(InTableBody);
823 Done
824 }
825
826 <td> <th> <tr> => {
827 self.pop_until_current(table_scope);
828 self.insert_phantom(local_name!("tbody"));
829 Reprocess(InTableBody, token)
830 }
831
832 <table> => {
833 self.unexpected(&token);
834 if self.in_scope_named(table_scope, local_name!("table")) {
835 self.pop_until_named(local_name!("table"));
836 Reprocess(self.reset_insertion_mode(), token)
837 } else {
838 Done
839 }
840 }
841
842 </table> => {
843 if self.in_scope_named(table_scope, local_name!("table")) {
844 self.pop_until_named(local_name!("table"));
845 self.mode.set(self.reset_insertion_mode());
846 } else {
847 self.unexpected(&token);
848 }
849 Done
850 }
851
852 </body> </caption> </col> </colgroup> </html>
853 </tbody> </td> </tfoot> </th> </thead> </tr> =>
854 self.unexpected(&token),
855
856 <style> <script> <template> </template>
857 => self.step(InHead, token),
858
859 tag @ <input> => {
860 self.unexpected(&tag);
861 if self.is_type_hidden(&tag) {
862 self.insert_and_pop_element_for(tag);
863 DoneAckSelfClosing
864 } else {
865 self.foster_parent_in_body(TagToken(tag))
866 }
867 }
868
869 tag @ <form> => {
870 self.unexpected(&tag);
871 if !self.in_html_elem_named(local_name!("template")) && self.form_elem.borrow().is_none() {
872 *self.form_elem.borrow_mut() = Some(self.insert_and_pop_element_for(tag));
873 }
874 Done
875 }
876
877 EOFToken => self.step(InBody, token),
878
879 token => {
880 self.unexpected(&token);
881 self.foster_parent_in_body(token)
882 }
883 }),
884
885 //§ parsing-main-intabletext
886 InTableText => match_token!(token {
887 NullCharacterToken => self.unexpected(&token),
888
889 CharacterTokens(split, text) => {
890 self.pending_table_text.borrow_mut().push((split, text));
891 Done
892 }
893
894 token => {
895 let pending = self.pending_table_text.take();
896 let contains_nonspace = pending.iter().any(|&(split, ref text)| {
897 match split {
898 Whitespace => false,
899 NotWhitespace => true,
900 NotSplit => any_not_whitespace(text),
901 }
902 });
903
904 if contains_nonspace {
905 self.sink.parse_error(Borrowed("Non-space table text"));
906 for (split, text) in pending.into_iter() {
907 match self.foster_parent_in_body(CharacterTokens(split, text)) {
908 Done => (),
909 _ => panic!("not prepared to handle this!"),
910 }
911 }
912 } else {
913 for (_, text) in pending.into_iter() {
914 self.append_text(text);
915 }
916 }
917
918 Reprocess(self.orig_mode.take().unwrap(), token)
919 }
920 }),
921
922 //§ parsing-main-incaption
923 InCaption => match_token!(token {
924 tag @ <caption> <col> <colgroup> <tbody> <td> <tfoot>
925 <th> <thead> <tr> </table> </caption> => {
926 if self.in_scope_named(table_scope, local_name!("caption")) {
927 self.generate_implied_end(cursory_implied_end);
928 self.expect_to_close(local_name!("caption"));
929 self.clear_active_formatting_to_marker();
930 match tag {
931 Tag { kind: EndTag, name: local_name!("caption"), .. } => {
932 self.mode.set(InTable);
933 Done
934 }
935 _ => Reprocess(InTable, TagToken(tag))
936 }
937 } else {
938 self.unexpected(&tag);
939 Done
940 }
941 }
942
943 </body> </col> </colgroup> </html> </tbody>
944 </td> </tfoot> </th> </thead> </tr> => self.unexpected(&token),
945
946 token => self.step(InBody, token),
947 }),
948
949 //§ parsing-main-incolgroup
950 InColumnGroup => match_token!(token {
951 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
952 CharacterTokens(Whitespace, text) => self.append_text(text),
953 CommentToken(text) => self.append_comment(text),
954
955 <html> => self.step(InBody, token),
956
957 tag @ <col> => {
958 self.insert_and_pop_element_for(tag);
959 DoneAckSelfClosing
960 }
961
962 </colgroup> => {
963 if self.current_node_named(local_name!("colgroup")) {
964 self.pop();
965 self.mode.set(InTable);
966 } else {
967 self.unexpected(&token);
968 }
969 Done
970 }
971
972 </col> => self.unexpected(&token),
973
974 <template> </template> => self.step(InHead, token),
975
976 EOFToken => self.step(InBody, token),
977
978 token => {
979 if self.current_node_named(local_name!("colgroup")) {
980 self.pop();
981 Reprocess(InTable, token)
982 } else {
983 self.unexpected(&token)
984 }
985 }
986 }),
987
988 //§ parsing-main-intbody
989 InTableBody => match_token!(token {
990 tag @ <tr> => {
991 self.pop_until_current(table_body_context);
992 self.insert_element_for(tag);
993 self.mode.set(InRow);
994 Done
995 }
996
997 <th> <td> => {
998 self.unexpected(&token);
999 self.pop_until_current(table_body_context);
1000 self.insert_phantom(local_name!("tr"));
1001 Reprocess(InRow, token)
1002 }
1003
1004 tag @ </tbody> </tfoot> </thead> => {
1005 if self.in_scope_named(table_scope, tag.name.clone()) {
1006 self.pop_until_current(table_body_context);
1007 self.pop();
1008 self.mode.set(InTable);
1009 } else {
1010 self.unexpected(&tag);
1011 }
1012 Done
1013 }
1014
1015 <caption> <col> <colgroup> <tbody> <tfoot> <thead> </table> => {
1016 declare_tag_set!(table_outer = "table" "tbody" "tfoot");
1017 if self.in_scope(table_scope, |e| self.elem_in(&e, table_outer)) {
1018 self.pop_until_current(table_body_context);
1019 self.pop();
1020 Reprocess(InTable, token)
1021 } else {
1022 self.unexpected(&token)
1023 }
1024 }
1025
1026 </body> </caption> </col> </colgroup> </html> </td> </th> </tr>
1027 => self.unexpected(&token),
1028
1029 token => self.step(InTable, token),
1030 }),
1031
1032 //§ parsing-main-intr
1033 InRow => match_token!(token {
1034 tag @ <th> <td> => {
1035 self.pop_until_current(table_row_context);
1036 self.insert_element_for(tag);
1037 self.mode.set(InCell);
1038 self.active_formatting.borrow_mut().push(Marker);
1039 Done
1040 }
1041
1042 </tr> => {
1043 if self.in_scope_named(table_scope, local_name!("tr")) {
1044 self.pop_until_current(table_row_context);
1045 let node = self.pop();
1046 self.assert_named(&node, local_name!("tr"));
1047 self.mode.set(InTableBody);
1048 } else {
1049 self.unexpected(&token);
1050 }
1051 Done
1052 }
1053
1054 <caption> <col> <colgroup> <tbody> <tfoot> <thead> <tr> </table> => {
1055 if self.in_scope_named(table_scope, local_name!("tr")) {
1056 self.pop_until_current(table_row_context);
1057 let node = self.pop();
1058 self.assert_named(&node, local_name!("tr"));
1059 Reprocess(InTableBody, token)
1060 } else {
1061 self.unexpected(&token)
1062 }
1063 }
1064
1065 tag @ </tbody> </tfoot> </thead> => {
1066 if self.in_scope_named(table_scope, tag.name.clone()) {
1067 if self.in_scope_named(table_scope, local_name!("tr")) {
1068 self.pop_until_current(table_row_context);
1069 let node = self.pop();
1070 self.assert_named(&node, local_name!("tr"));
1071 Reprocess(InTableBody, TagToken(tag))
1072 } else {
1073 Done
1074 }
1075 } else {
1076 self.unexpected(&tag)
1077 }
1078 }
1079
1080 </body> </caption> </col> </colgroup> </html> </td> </th>
1081 => self.unexpected(&token),
1082
1083 token => self.step(InTable, token),
1084 }),
1085
1086 //§ parsing-main-intd
1087 InCell => match_token!(token {
1088 tag @ </td> </th> => {
1089 if self.in_scope_named(table_scope, tag.name.clone()) {
1090 self.generate_implied_end(cursory_implied_end);
1091 self.expect_to_close(tag.name);
1092 self.clear_active_formatting_to_marker();
1093 self.mode.set(InRow);
1094 } else {
1095 self.unexpected(&tag);
1096 }
1097 Done
1098 }
1099
1100 <caption> <col> <colgroup> <tbody> <td> <tfoot> <th> <thead> <tr> => {
1101 if self.in_scope(table_scope, |n| self.elem_in(&n, td_th)) {
1102 self.close_the_cell();
1103 Reprocess(InRow, token)
1104 } else {
1105 self.unexpected(&token)
1106 }
1107 }
1108
1109 </body> </caption> </col> </colgroup> </html>
1110 => self.unexpected(&token),
1111
1112 tag @ </table> </tbody> </tfoot> </thead> </tr> => {
1113 if self.in_scope_named(table_scope, tag.name.clone()) {
1114 self.close_the_cell();
1115 Reprocess(InRow, TagToken(tag))
1116 } else {
1117 self.unexpected(&tag)
1118 }
1119 }
1120
1121 token => self.step(InBody, token),
1122 }),
1123
1124 //§ parsing-main-inselect
1125 InSelect => match_token!(token {
1126 NullCharacterToken => self.unexpected(&token),
1127 CharacterTokens(_, text) => self.append_text(text),
1128 CommentToken(text) => self.append_comment(text),
1129
1130 <html> => self.step(InBody, token),
1131
1132 tag @ <option> => {
1133 if self.current_node_named(local_name!("option")) {
1134 self.pop();
1135 }
1136 self.insert_element_for(tag);
1137 Done
1138 }
1139
1140 tag @ <optgroup> => {
1141 if self.current_node_named(local_name!("option")) {
1142 self.pop();
1143 }
1144 if self.current_node_named(local_name!("optgroup")) {
1145 self.pop();
1146 }
1147 self.insert_element_for(tag);
1148 Done
1149 }
1150
1151 tag @ <hr> => {
1152 if self.current_node_named(local_name!("option")) {
1153 self.pop();
1154 }
1155 if self.current_node_named(local_name!("optgroup")) {
1156 self.pop();
1157 }
1158 self.insert_element_for(tag);
1159 self.pop();
1160 DoneAckSelfClosing
1161 }
1162
1163 </optgroup> => {
1164 if self.open_elems.borrow().len() >= 2
1165 && self.current_node_named(local_name!("option"))
1166 && self.html_elem_named(&self.open_elems.borrow()[self.open_elems.borrow().len() - 2],
1167 local_name!("optgroup")) {
1168 self.pop();
1169 }
1170 if self.current_node_named(local_name!("optgroup")) {
1171 self.pop();
1172 } else {
1173 self.unexpected(&token);
1174 }
1175 Done
1176 }
1177
1178 </option> => {
1179 if self.current_node_named(local_name!("option")) {
1180 self.pop();
1181 } else {
1182 self.unexpected(&token);
1183 }
1184 Done
1185 }
1186
1187 tag @ <select> </select> => {
1188 let in_scope = self.in_scope_named(select_scope, local_name!("select"));
1189
1190 if !in_scope || tag.kind == StartTag {
1191 self.unexpected(&tag);
1192 }
1193
1194 if in_scope {
1195 self.pop_until_named(local_name!("select"));
1196 self.mode.set(self.reset_insertion_mode());
1197 }
1198 Done
1199 }
1200
1201 <input> <keygen> <textarea> => {
1202 self.unexpected(&token);
1203 if self.in_scope_named(select_scope, local_name!("select")) {
1204 self.pop_until_named(local_name!("select"));
1205 Reprocess(self.reset_insertion_mode(), token)
1206 } else {
1207 Done
1208 }
1209 }
1210
1211 <script> <template> </template> => self.step(InHead, token),
1212
1213 EOFToken => self.step(InBody, token),
1214
1215 token => self.unexpected(&token),
1216 }),
1217
1218 //§ parsing-main-inselectintable
1219 InSelectInTable => match_token!(token {
1220 <caption> <table> <tbody> <tfoot> <thead> <tr> <td> <th> => {
1221 self.unexpected(&token);
1222 self.pop_until_named(local_name!("select"));
1223 Reprocess(self.reset_insertion_mode(), token)
1224 }
1225
1226 tag @ </caption> </table> </tbody> </tfoot> </thead> </tr> </td> </th> => {
1227 self.unexpected(&tag);
1228 if self.in_scope_named(table_scope, tag.name.clone()) {
1229 self.pop_until_named(local_name!("select"));
1230 Reprocess(self.reset_insertion_mode(), TagToken(tag))
1231 } else {
1232 Done
1233 }
1234 }
1235
1236 token => self.step(InSelect, token),
1237 }),
1238
1239 //§ parsing-main-intemplate
1240 InTemplate => match_token!(token {
1241 CharacterTokens(_, _) => self.step(InBody, token),
1242 CommentToken(_) => self.step(InBody, token),
1243
1244 <base> <basefont> <bgsound> <link> <meta> <noframes> <script>
1245 <style> <template> <title> </template> => {
1246 self.step(InHead, token)
1247 }
1248
1249 <caption> <colgroup> <tbody> <tfoot> <thead> => {
1250 self.template_modes.borrow_mut().pop();
1251 self.template_modes.borrow_mut().push(InTable);
1252 Reprocess(InTable, token)
1253 }
1254
1255 <col> => {
1256 self.template_modes.borrow_mut().pop();
1257 self.template_modes.borrow_mut().push(InColumnGroup);
1258 Reprocess(InColumnGroup, token)
1259 }
1260
1261 <tr> => {
1262 self.template_modes.borrow_mut().pop();
1263 self.template_modes.borrow_mut().push(InTableBody);
1264 Reprocess(InTableBody, token)
1265 }
1266
1267 <td> <th> => {
1268 self.template_modes.borrow_mut().pop();
1269 self.template_modes.borrow_mut().push(InRow);
1270 Reprocess(InRow, token)
1271 }
1272
1273 EOFToken => {
1274 if !self.in_html_elem_named(local_name!("template")) {
1275 self.stop_parsing()
1276 } else {
1277 self.unexpected(&token);
1278 self.pop_until_named(local_name!("template"));
1279 self.clear_active_formatting_to_marker();
1280 self.template_modes.borrow_mut().pop();
1281 self.mode.set(self.reset_insertion_mode());
1282 Reprocess(self.reset_insertion_mode(), token)
1283 }
1284 }
1285
1286 tag @ <_> => {
1287 self.template_modes.borrow_mut().pop();
1288 self.template_modes.borrow_mut().push(InBody);
1289 Reprocess(InBody, TagToken(tag))
1290 }
1291
1292 token => self.unexpected(&token),
1293 }),
1294
1295 //§ parsing-main-afterbody
1296 AfterBody => match_token!(token {
1297 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
1298 CharacterTokens(Whitespace, _) => self.step(InBody, token),
1299 CommentToken(text) => self.append_comment_to_html(text),
1300
1301 <html> => self.step(InBody, token),
1302
1303 </html> => {
1304 if self.is_fragment() {
1305 self.unexpected(&token);
1306 } else {
1307 self.mode.set(AfterAfterBody);
1308 }
1309 Done
1310 }
1311
1312 EOFToken => self.stop_parsing(),
1313
1314 token => {
1315 self.unexpected(&token);
1316 Reprocess(InBody, token)
1317 }
1318 }),
1319
1320 //§ parsing-main-inframeset
1321 InFrameset => match_token!(token {
1322 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
1323 CharacterTokens(Whitespace, text) => self.append_text(text),
1324 CommentToken(text) => self.append_comment(text),
1325
1326 <html> => self.step(InBody, token),
1327
1328 tag @ <frameset> => {
1329 self.insert_element_for(tag);
1330 Done
1331 }
1332
1333 </frameset> => {
1334 if self.open_elems.borrow().len() == 1 {
1335 self.unexpected(&token);
1336 } else {
1337 self.pop();
1338 if !self.is_fragment() && !self.current_node_named(local_name!("frameset")) {
1339 self.mode.set(AfterFrameset);
1340 }
1341 }
1342 Done
1343 }
1344
1345 tag @ <frame> => {
1346 self.insert_and_pop_element_for(tag);
1347 DoneAckSelfClosing
1348 }
1349
1350 <noframes> => self.step(InHead, token),
1351
1352 EOFToken => {
1353 if self.open_elems.borrow().len() != 1 {
1354 self.unexpected(&token);
1355 }
1356 self.stop_parsing()
1357 }
1358
1359 token => self.unexpected(&token),
1360 }),
1361
1362 //§ parsing-main-afterframeset
1363 AfterFrameset => match_token!(token {
1364 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
1365 CharacterTokens(Whitespace, text) => self.append_text(text),
1366 CommentToken(text) => self.append_comment(text),
1367
1368 <html> => self.step(InBody, token),
1369
1370 </html> => {
1371 self.mode.set(AfterAfterFrameset);
1372 Done
1373 }
1374
1375 <noframes> => self.step(InHead, token),
1376
1377 EOFToken => self.stop_parsing(),
1378
1379 token => self.unexpected(&token),
1380 }),
1381
1382 //§ the-after-after-body-insertion-mode
1383 AfterAfterBody => match_token!(token {
1384 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
1385 CharacterTokens(Whitespace, _) => self.step(InBody, token),
1386 CommentToken(text) => self.append_comment_to_doc(text),
1387
1388 <html> => self.step(InBody, token),
1389
1390 EOFToken => self.stop_parsing(),
1391
1392 token => {
1393 self.unexpected(&token);
1394 Reprocess(InBody, token)
1395 }
1396 }),
1397
1398 //§ the-after-after-frameset-insertion-mode
1399 AfterAfterFrameset => match_token!(token {
1400 CharacterTokens(NotSplit, text) => SplitWhitespace(text),
1401 CharacterTokens(Whitespace, _) => self.step(InBody, token),
1402 CommentToken(text) => self.append_comment_to_doc(text),
1403
1404 <html> => self.step(InBody, token),
1405
1406 EOFToken => self.stop_parsing(),
1407
1408 <noframes> => self.step(InHead, token),
1409
1410 token => self.unexpected(&token),
1411 }),
1412 //§ END
1413 }
1414 }
1415
1416 pub(crate) fn step_foreign(&self, token: Token) -> ProcessResult<Handle> {
1417 match_token!(token {
1418 NullCharacterToken => {
1419 self.unexpected(&token);
1420 self.append_text("\u{fffd}".to_tendril())
1421 }
1422
1423 CharacterTokens(_, text) => {
1424 if any_not_whitespace(&text) {
1425 self.frameset_ok.set(false);
1426 }
1427 self.append_text(text)
1428 }
1429
1430 CommentToken(text) => self.append_comment(text),
1431
1432 tag @ <b> <big> <blockquote> <body> <br> <center> <code> <dd> <div> <dl>
1433 <dt> <em> <embed> <h1> <h2> <h3> <h4> <h5> <h6> <head> <hr> <i>
1434 <img> <li> <listing> <menu> <meta> <nobr> <ol> <p> <pre> <ruby>
1435 <s> <small> <span> <strong> <strike> <sub> <sup> <table> <tt>
1436 <u> <ul> <var> </br> </p> => self.unexpected_start_tag_in_foreign_content(tag),
1437
1438 tag @ <font> => {
1439 let unexpected = tag.attrs.iter().any(|attr| {
1440 matches!(attr.name.expanded(),
1441 expanded_name!("", "color") |
1442 expanded_name!("", "face") |
1443 expanded_name!("", "size"))
1444 });
1445 if unexpected {
1446 self.unexpected_start_tag_in_foreign_content(tag)
1447 } else {
1448 self.foreign_start_tag(tag)
1449 }
1450 }
1451
1452 tag @ <_> => self.foreign_start_tag(tag),
1453
1454 // FIXME(#118): </script> in SVG
1455
1456 tag @ </_> => {
1457 let mut first = true;
1458 let mut stack_idx = self.open_elems.borrow().len() - 1;
1459 loop {
1460 if stack_idx == 0 {
1461 return Done;
1462 }
1463
1464 let html;
1465 let eq;
1466 {
1467 let open_elems = self.open_elems.borrow();
1468 let node_name = self.sink.elem_name(&open_elems[stack_idx]);
1469 html = *node_name.ns() == ns!(html);
1470 eq = node_name.local_name().eq_ignore_ascii_case(&tag.name);
1471 }
1472 if !first && html {
1473 let mode = self.mode.get();
1474 return self.step(mode, TagToken(tag));
1475 }
1476
1477 if eq {
1478 self.open_elems.borrow_mut().truncate(stack_idx);
1479 return Done;
1480 }
1481
1482 if first {
1483 self.unexpected(&tag);
1484 first = false;
1485 }
1486 stack_idx -= 1;
1487 }
1488 }
1489
1490 // FIXME: This should be unreachable, but match_token requires a
1491 // catch-all case.
1492 _ => panic!("impossible case in foreign content"),
1493 })
1494 }
1495}
1496