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