Skip to content

Commit

Permalink
HTML API: Add support for SVG and MathML (Foreign content)
Browse files Browse the repository at this point in the history
As part of work to add more spec support to the HTML API, this patch adds
support for SVG and MathML elements, or more generally, "foreign content."

The rules in foreign content are a mix of XML and HTML parsing rules and
introduce additional complexity into the processor, but is important in
order to avoid getting lost when inside these elements.

Developed in #6006
Discussed in https://core.trac.wordpress.org/ticket/61576

Props: dmsnell, jonsurrell, westonruter.
See #61576.


git-svn-id: https://develop.svn.wordpress.org/trunk@58867 602fd350-edb4-49c9-b593-d223f7449a82
  • Loading branch information
dmsnell committed Aug 8, 2024
1 parent 4026237 commit de084d7
Show file tree
Hide file tree
Showing 10 changed files with 1,199 additions and 431 deletions.
113 changes: 65 additions & 48 deletions src/wp-includes/html-api/class-wp-html-open-elements.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,13 @@ public function set_push_handler( Closure $handler ): void {
*
* @param int $nth Retrieve the nth item on the stack, with 1 being
* the top element, 2 being the second, etc...
* @return string|null Name of the node on the stack at the given location,
* or `null` if the location isn't on the stack.
* @return WP_HTML_Token|null Name of the node on the stack at the given location,
* or `null` if the location isn't on the stack.
*/
public function at( int $nth ): ?string {
public function at( int $nth ): ?WP_HTML_Token {
foreach ( $this->walk_down() as $item ) {
if ( 0 === --$nth ) {
return $item->node_name;
return $item;
}
}

Expand Down Expand Up @@ -242,18 +242,22 @@ public function current_node_is( string $identity ): bool {
*/
public function has_element_in_specific_scope( string $tag_name, $termination_list ): bool {
foreach ( $this->walk_up() as $node ) {
if ( $node->node_name === $tag_name ) {
$namespaced_name = 'html' === $node->namespace
? $node->node_name
: "{$node->namespace} {$node->node_name}";

if ( $namespaced_name === $tag_name ) {
return true;
}

if (
'(internal: H1 through H6 - do not use)' === $tag_name &&
in_array( $node->node_name, array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), true )
in_array( $namespaced_name, array( 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ), true )
) {
return true;
}

if ( in_array( $node->node_name, $termination_list, true ) ) {
if ( in_array( $namespaced_name, $termination_list, true ) ) {
return false;
}
}
Expand Down Expand Up @@ -288,7 +292,7 @@ public function has_element_in_specific_scope( string $tag_name, $termination_li
* > - SVG title
*
* @since 6.4.0
* @since 6.7.0 Supports all required HTML elements.
* @since 6.7.0 Full support.
*
* @see https://html.spec.whatwg.org/#has-an-element-in-scope
*
Expand All @@ -309,19 +313,16 @@ public function has_element_in_scope( string $tag_name ): bool {
'OBJECT',
'TEMPLATE',

/*
* @todo Support SVG and MathML nodes when support for foreign content is added.
*
* - MathML mi
* - MathML mo
* - MathML mn
* - MathML ms
* - MathML mtext
* - MathML annotation-xml
* - SVG foreignObject
* - SVG desc
* - SVG title
*/
'math MI',
'math MO',
'math MN',
'math MS',
'math MTEXT',
'math ANNOTATION-XML',

'svg FOREIGNOBJECT',
'svg DESC',
'svg TITLE',
)
);
}
Expand Down Expand Up @@ -363,19 +364,16 @@ public function has_element_in_list_item_scope( string $tag_name ): bool {
'TEMPLATE',
'UL',

/*
* @todo Support SVG and MathML nodes when support for foreign content is added.
*
* - MathML mi
* - MathML mo
* - MathML mn
* - MathML ms
* - MathML mtext
* - MathML annotation-xml
* - SVG foreignObject
* - SVG desc
* - SVG title
*/
'math MI',
'math MO',
'math MN',
'math MS',
'math MTEXT',
'math ANNOTATION-XML',

'svg FOREIGNOBJECT',
'svg DESC',
'svg TITLE',
)
);
}
Expand Down Expand Up @@ -413,19 +411,16 @@ public function has_element_in_button_scope( string $tag_name ): bool {
'OBJECT',
'TEMPLATE',

/*
* @todo Support SVG and MathML nodes when support for foreign content is added.
*
* - MathML mi
* - MathML mo
* - MathML mn
* - MathML ms
* - MathML mtext
* - MathML annotation-xml
* - SVG foreignObject
* - SVG desc
* - SVG title
*/
'math MI',
'math MO',
'math MN',
'math MS',
'math MTEXT',
'math ANNOTATION-XML',

'svg FOREIGNOBJECT',
'svg DESC',
'svg TITLE',
)
);
}
Expand Down Expand Up @@ -692,11 +687,15 @@ public function walk_up( ?WP_HTML_Token $above_this_node = null ) {
* @param WP_HTML_Token $item Element that was added to the stack of open elements.
*/
public function after_element_push( WP_HTML_Token $item ): void {
$namespaced_name = 'html' === $item->namespace
? $item->node_name
: "{$item->namespace} {$item->node_name}";

/*
* When adding support for new elements, expand this switch to trap
* cases where the precalculated value needs to change.
*/
switch ( $item->node_name ) {
switch ( $namespaced_name ) {
case 'APPLET':
case 'BUTTON':
case 'CAPTION':
Expand All @@ -707,6 +706,15 @@ public function after_element_push( WP_HTML_Token $item ): void {
case 'MARQUEE':
case 'OBJECT':
case 'TEMPLATE':
case 'math MI':
case 'math MO':
case 'math MN':
case 'math MS':
case 'math MTEXT':
case 'math ANNOTATION-XML':
case 'svg FOREIGNOBJECT':
case 'svg DESC':
case 'svg TITLE':
$this->has_p_in_button_scope = false;
break;

Expand Down Expand Up @@ -750,6 +758,15 @@ public function after_element_pop( WP_HTML_Token $item ): void {
case 'MARQUEE':
case 'OBJECT':
case 'TEMPLATE':
case 'math MI':
case 'math MO':
case 'math MN':
case 'math MS':
case 'math MTEXT':
case 'math ANNOTATION-XML':
case 'svg FOREIGNOBJECT':
case 'svg DESC':
case 'svg TITLE':
$this->has_p_in_button_scope = $this->has_element_in_button_scope( 'P' );
break;
}
Expand Down
12 changes: 0 additions & 12 deletions src/wp-includes/html-api/class-wp-html-processor-state.php
Original file line number Diff line number Diff line change
Expand Up @@ -299,18 +299,6 @@ class WP_HTML_Processor_State {
*/
const INSERTION_MODE_AFTER_AFTER_FRAMESET = 'insertion-mode-after-after-frameset';

/**
* In foreign content insertion mode for full HTML parser.
*
* @since 6.7.0
*
* @see https://html.spec.whatwg.org/#parsing-main-inforeign
* @see WP_HTML_Processor_State::$insertion_mode
*
* @var string
*/
const INSERTION_MODE_IN_FOREIGN_CONTENT = 'insertion-mode-in-foreign-content';

/**
* No-quirks mode document compatability mode.
*
Expand Down
Loading

0 comments on commit de084d7

Please sign in to comment.