Make WordPress Core

Changeset 57649


Ignore:
Timestamp:
02/17/2024 03:26:43 PM (5 months ago)
Author:
swissspidy
Message:

Interactivity API: Skip instead of bail out if HTML contains SVG or MATH.

Addresses an issue with server-side processing of directives when there is e.g. an SVG icon a navigation menu.

Props cbravobernal, westonruter, dmsnell, swissspidy.
Fixes #60517.

Location:
trunk
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/interactivity-api/class-wp-interactivity-api-directives-processor.php

    r57563 r57649  
    182182
    183183    /**
     184     * Skips processing the content between tags.
     185     *
     186     * It positions the cursor in the closer tag of the foreign element, if it
     187     * exists.
     188     *
     189     * This function is intended to skip processing SVG and MathML inner content
     190     * instead of bailing out the whole processing.
     191     *
     192     * @since 6.5.0
     193     *
     194     * @access private
     195     *
     196     * @return bool Whether the foreign content was successfully skipped.
     197     */
     198    public function skip_to_tag_closer(): bool {
     199        $depth    = 1;
     200        $tag_name = $this->get_tag();
     201        while ( $depth > 0 && $this->next_tag(
     202            array(
     203                'tag_name'    => $tag_name,
     204                'tag_closers' => 'visit',
     205            )
     206        ) ) {
     207            if ( $this->has_self_closing_flag() ) {
     208                continue;
     209            }
     210            $depth += $this->is_tag_closer() ? -1 : 1;
     211        }
     212
     213        return 0 === $depth;
     214    }
     215
     216    /**
    184217     * Finds the matching closing tag for an opening tag.
    185218     *
  • trunk/src/wp-includes/interactivity-api/class-wp-interactivity-api.php

    r57646 r57649  
    236236            $tag_name = $p->get_tag();
    237237
     238            /*
     239             * Directives inside SVG and MATH tags are not processed,
     240             * as they are not compatible with the Tag Processor yet.
     241             * We still process the rest of the HTML.
     242             */
    238243            if ( 'SVG' === $tag_name || 'MATH' === $tag_name ) {
    239                 $unbalanced = true;
    240                 break;
     244                $p->skip_to_tag_closer();
     245                continue;
    241246            }
    242247
  • trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php

    r57563 r57649  
    1212 * @coversDefaultClass WP_Interactivity_API
    1313 */
    14 class Tests_WP_Interactivity_API extends WP_UnitTestCase {
     14class Tests_Interactivity_API_WpInteractivityAPI extends WP_UnitTestCase {
    1515    /**
    1616     * Instance of WP_Interactivity_API.
     
    509509
    510510    /**
    511      * Tests that the `process_directives` returns the same HTML if it finds an
    512      * SVG tag.
    513      *
    514      * @ticket 60356
    515      *
    516      * @covers ::process_directives
    517      */
    518     public function test_process_directives_doesnt_change_html_if_contains_svgs() {
    519         $this->interactivity->state( 'myPlugin', array( 'id' => 'some-id' ) );
     511     * Tests that the `process_directives` process the HTML outside a SVG tag.
     512     *
     513     * @ticket 60517
     514     *
     515     * @covers ::process_directives
     516     */
     517    public function test_process_directives_changes_html_if_contains_svgs() {
     518        $this->interactivity->state(
     519            'myPlugin',
     520            array(
     521                'id'    => 'some-id',
     522                'width' => '100',
     523            )
     524        );
    520525        $html           = '
    521             <div data-wp-bind--id="myPlugin::state.id">
    522                 <svg height="100" width="100">
     526            <header>
     527                <svg height="100" data-wp-bind--width="myPlugin::state.width">
     528                    <title>Red Circle</title>
    523529                    <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
    524                 </svg>
    525             </div>
     530                </svg>
     531                <div data-wp-bind--id="myPlugin::state.id"></div>
     532                <div data-wp-bind--id="myPlugin::state.width"></div>
     533            </header>
    526534        ';
    527535        $processed_html = $this->interactivity->process_directives( $html );
    528536        $p              = new WP_HTML_Tag_Processor( $processed_html );
    529         $p->next_tag();
     537        $p->next_tag( 'svg' );
     538        $this->assertNull( $p->get_attribute( 'width' ) );
     539        $p->next_tag( 'div' );
     540        $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) );
     541        $p->next_tag( 'div' );
     542        $this->assertEquals( '100', $p->get_attribute( 'id' ) );
     543    }
     544
     545    /**
     546     * Tests that the `process_directives` does not process the HTML
     547     * inside SVG tags.
     548     *
     549     * @ticket 60517
     550     *
     551     * @covers ::process_directives
     552     */
     553    public function test_process_directives_does_not_change_inner_html_in_svgs() {
     554        $this->interactivity->state(
     555            'myPlugin',
     556            array(
     557                'id' => 'some-id',
     558            )
     559        );
     560        $html           = '
     561            <header>
     562                <svg height="100">
     563                    <circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
     564                    <g data-wp-bind--id="myPlugin::state.id" />
     565                </svg>
     566            </header>
     567        ';
     568        $processed_html = $this->interactivity->process_directives( $html );
     569        $p              = new WP_HTML_Tag_Processor( $processed_html );
     570        $p->next_tag( 'div' );
    530571        $this->assertNull( $p->get_attribute( 'id' ) );
    531572    }
    532573
    533574    /**
    534      * Tests that the `process_directives` returns the same HTML if it finds an
     575     * Tests that the `process_directives` process the HTML outside the
    535576     * MathML tag.
    536577     *
    537      * @ticket 60356
    538      *
    539      * @covers ::process_directives
    540      */
    541     public function test_process_directives_doesnt_change_html_if_contains_math() {
    542         $this->interactivity->state( 'myPlugin', array( 'id' => 'some-id' ) );
     578     * @ticket 60517
     579     *
     580     * @covers ::process_directives
     581     */
     582    public function test_process_directives_change_html_if_contains_math() {
     583        $this->interactivity->state(
     584            'myPlugin',
     585            array(
     586                'id'   => 'some-id',
     587                'math' => 'ml-id',
     588            )
     589        );
    543590        $html           = '
    544             <div data-wp-bind--id="myPlugin::state.id">
    545                 <math>
     591            <header>
     592                <math data-wp-bind--id="myPlugin::state.math">
    546593                    <mi>x</mi>
    547594                    <mo>=</mo>
    548595                    <mi>1</mi>
    549596                </math>
    550             </div>
     597                <div data-wp-bind--id="myPlugin::state.id"></div>
     598            </header>
    551599        ';
    552600        $processed_html = $this->interactivity->process_directives( $html );
    553601        $p              = new WP_HTML_Tag_Processor( $processed_html );
    554         $p->next_tag();
     602        $p->next_tag( 'math' );
     603        $this->assertNull( $p->get_attribute( 'id' ) );
     604        $p->next_tag( 'div' );
     605        $this->assertEquals( 'some-id', $p->get_attribute( 'id' ) );
     606    }
     607
     608    /**
     609     * Tests that the `process_directives` does not process the HTML
     610     * inside MathML tags.
     611     *
     612     * @ticket 60517
     613     *
     614     * @covers ::process_directives
     615     */
     616    public function test_process_directives_does_not_change_inner_html_in_math() {
     617        $this->interactivity->state(
     618            'myPlugin',
     619            array(
     620                'id' => 'some-id',
     621            )
     622        );
     623        $html           = '
     624            <header>
     625                <math data-wp-bind--id="myPlugin::state.math">
     626                    <mrow data-wp-bind--id="myPlugin::state.id" />
     627                    <mi>x</mi>
     628                    <mo>=</mo>
     629                    <mi>1</mi>
     630                </math>
     631            </header>
     632        ';
     633        $processed_html = $this->interactivity->process_directives( $html );
     634        $p              = new WP_HTML_Tag_Processor( $processed_html );
     635        $p->next_tag( 'div' );
    555636        $this->assertNull( $p->get_attribute( 'id' ) );
    556637    }
  • trunk/tests/phpunit/tests/interactivity-api/wpInteractivityAPIDirectivesProcessor.php

    r57563 r57649  
    1212 * @coversDefaultClass WP_Interactivity_API_Directives_Processor
    1313 */
    14 class Tests_WP_Interactivity_API_Directives_Processor extends WP_UnitTestCase {
     14class Tests_Interactivity_API_WpInteractivityAPIDirectivesProcessor extends WP_UnitTestCase {
    1515    /**
    1616     * Tests the `get_content_between_balanced_template_tags` method on template
     
    779779        $this->assertFalse( $p->next_balanced_tag_closer_tag() );
    780780    }
     781
     782    /**
     783     * Tests that skip_to_tag_closer skips to the next tag,
     784     * independant of the content.
     785     *
     786     * @ticket 60517
     787     *
     788     * @covers ::skip_to_tag_closer
     789     */
     790    public function test_skip_to_tag_closer() {
     791        $content = '<div><span>Not closed</div>';
     792        $p       = new WP_Interactivity_API_Directives_Processor( $content );
     793        $p->next_tag();
     794        $this->assertTrue( $p->skip_to_tag_closer() );
     795        $this->assertTrue( $p->is_tag_closer() );
     796        $this->assertEquals( 'DIV', $p->get_tag() );
     797    }
     798
     799    /**
     800     * Tests that skip_to_tag_closer does not skip to the
     801     * next tag if there is no closing tag.
     802     *
     803     * @ticket 60517
     804     *
     805     * @covers ::skip_to_tag_closer
     806     */
     807    public function test_skip_to_tag_closer_bails_not_closed() {
     808        $content = '<div>Not closed parent';
     809        $p       = new WP_Interactivity_API_Directives_Processor( $content );
     810        $p->next_tag();
     811        $this->assertFalse( $p->skip_to_tag_closer() );
     812    }
     813
     814    /**
     815     * Tests that skip_to_tag_closer does not skip to the next
     816     * tag if the closing tag is different from the current tag.
     817     *
     818     * @ticket 60517
     819     *
     820     * @covers ::skip_to_tag_closer
     821     */
     822    public function test_skip_to_tag_closer_bails_different_tags() {
     823        $content = '<div></span>';
     824        $p       = new WP_Interactivity_API_Directives_Processor( $content );
     825        $p->next_tag();
     826        $this->assertFalse( $p->skip_to_tag_closer() );
     827    }
    781828}
Note: See TracChangeset for help on using the changeset viewer.