Make WordPress Core


Ignore:
Timestamp:
09/03/2025 10:15:31 PM (12 months ago)
Author:
westonruter
Message:

Script Loader: Introduce fetchpriority for Scripts and Script Modules.

  • Allow scripts and script modules to be registered with a fetchpriority of auto (default), high, low:
    • When registering a script, add a fetchpriority arg to go alongside the strategy arg which was added for loading scripts with the defer and async loading strategies. See #12009.
    • For script modules, introduce an $args array parameter with a fetchpriority key to the wp_register_script_module(), and wp_enqueue_script_module() functions (and their respective underlying WP_Script_Modules::register() and WP_Script_Modules::enqueue() methods). This $args parameter corresponds with the same parameter used when registering non-module scripts.
    • Also for script modules, introduce WP_Script_Modules::set_fetchpriority() to override the fetchpriority for what was previously registered.
    • Emit a _doing_it_wrong() warning when an invalid fetchpriority value is used, and when fetchpriority is added to a script alias.
    • Include fetchpriority as an attribute on printed SCRIPT tags as well as on preload LINK tags for static script module dependencies.
  • Use a fetchpriority of low by default for:
    • Script modules used with the Interactivity API. For overriding this default in blocks, see Gutenberg#71366.
    • The comment-reply script.
  • Improve type checks and type hints.

Developed in GitHub PR, with companion for Gutenberg.

Props westonruter, jonsurrell, swissspidy, luisherranz, kraftbj, audrasjb, dennysdionigi.
Fixes #61734.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/tests/phpunit/tests/script-modules/wpScriptModules.php

    r58952 r60704  
    99 *
    1010 * @group script-modules
    11  *
    12  * @coversDefaultClass WP_Script_Modules
    1311 */
    1412class Tests_Script_Modules_WpScriptModules extends WP_UnitTestCase {
    1513
    1614        /**
     15         * @var WP_Script_Modules
     16         */
     17        protected $original_script_modules;
     18
     19        /**
     20         * @var string
     21         */
     22        protected $original_wp_version;
     23
     24        /**
    1725         * Instance of WP_Script_Modules.
    1826         *
     
    2533         */
    2634        public function set_up() {
     35                global $wp_script_modules, $wp_version;
    2736                parent::set_up();
    28                 // Set up the WP_Script_Modules instance.
    29                 $this->script_modules = new WP_Script_Modules();
     37                $this->original_script_modules = $wp_script_modules;
     38                $this->original_wp_version     = $wp_version;
     39                $wp_script_modules             = null;
     40                $this->script_modules          = wp_script_modules();
     41        }
     42
     43        /**
     44         * Tear down.
     45         */
     46        public function tear_down() {
     47                global $wp_script_modules, $wp_version;
     48                parent::tear_down();
     49                $wp_script_modules = $this->original_script_modules;
     50                $wp_version        = $this->original_wp_version;
    3051        }
    3152
     
    3556         * @return array Enqueued script module URLs, keyed by script module identifier.
    3657         */
    37         public function get_enqueued_script_modules() {
    38                 $script_modules_markup   = get_echo( array( $this->script_modules, 'print_enqueued_script_modules' ) );
    39                 $p                       = new WP_HTML_Tag_Processor( $script_modules_markup );
    40                 $enqueued_script_modules = array();
    41 
     58        public function get_enqueued_script_modules(): array {
     59                $modules = array();
     60
     61                $p = new WP_HTML_Tag_Processor( get_echo( array( $this->script_modules, 'print_enqueued_script_modules' ) ) );
    4262                while ( $p->next_tag( array( 'tag' => 'SCRIPT' ) ) ) {
    43                         if ( 'module' === $p->get_attribute( 'type' ) ) {
    44                                 $id                             = preg_replace( '/-js-module$/', '', $p->get_attribute( 'id' ) );
    45                                 $enqueued_script_modules[ $id ] = $p->get_attribute( 'src' );
     63                        $this->assertSame( 'module', $p->get_attribute( 'type' ) );
     64                        $this->assertIsString( $p->get_attribute( 'id' ) );
     65                        $this->assertIsString( $p->get_attribute( 'src' ) );
     66                        $this->assertStringEndsWith( '-js-module', $p->get_attribute( 'id' ) );
     67
     68                        $id             = preg_replace( '/-js-module$/', '', (string) $p->get_attribute( 'id' ) );
     69                        $fetchpriority  = $p->get_attribute( 'fetchpriority' );
     70                        $modules[ $id ] = array(
     71                                'url'           => $p->get_attribute( 'src' ),
     72                                'fetchpriority' => is_string( $fetchpriority ) ? $fetchpriority : 'auto',
     73                        );
     74                }
     75
     76                return $modules;
     77        }
     78
     79        /**
     80         * Gets the script modules listed in the import map.
     81         *
     82         * @return array<string, string> Import map entry URLs, keyed by script module identifier.
     83         */
     84        public function get_import_map(): array {
     85                $p = new WP_HTML_Tag_Processor( get_echo( array( $this->script_modules, 'print_import_map' ) ) );
     86                if ( $p->next_tag( array( 'tag' => 'SCRIPT' ) ) ) {
     87                        $this->assertSame( 'importmap', $p->get_attribute( 'type' ) );
     88                        $this->assertSame( 'wp-importmap', $p->get_attribute( 'id' ) );
     89                        $data = json_decode( $p->get_modifiable_text(), true );
     90                        $this->assertIsArray( $data );
     91                        $this->assertArrayHasKey( 'imports', $data );
     92                        return $data['imports'];
     93                } else {
     94                        return array();
     95                }
     96        }
     97
     98        /**
     99         * Gets a list of preloaded script modules.
     100         *
     101         * @return array Preloaded script module URLs, keyed by script module identifier.
     102         */
     103        public function get_preloaded_script_modules(): array {
     104                $preloads = array();
     105
     106                $p = new WP_HTML_Tag_Processor( get_echo( array( $this->script_modules, 'print_script_module_preloads' ) ) );
     107                while ( $p->next_tag( array( 'tag' => 'LINK' ) ) ) {
     108                        $this->assertSame( 'modulepreload', $p->get_attribute( 'rel' ) );
     109                        $this->assertIsString( $p->get_attribute( 'id' ) );
     110                        $this->assertIsString( $p->get_attribute( 'href' ) );
     111                        $this->assertStringEndsWith( '-js-modulepreload', $p->get_attribute( 'id' ) );
     112
     113                        $id              = preg_replace( '/-js-modulepreload$/', '', $p->get_attribute( 'id' ) );
     114                        $fetchpriority   = $p->get_attribute( 'fetchpriority' );
     115                        $preloads[ $id ] = array(
     116                                'url'           => $p->get_attribute( 'href' ),
     117                                'fetchpriority' => is_string( $fetchpriority ) ? $fetchpriority : 'auto',
     118                        );
     119                }
     120
     121                return $preloads;
     122        }
     123
     124        /**
     125         * Test wp_script_modules().
     126         *
     127         * @covers ::wp_script_modules()
     128         */
     129        public function test_wp_script_modules() {
     130                $this->assertSame( $this->script_modules, wp_script_modules() );
     131        }
     132
     133        /**
     134         * Tests various ways of registering, enqueueing, dequeuing, and deregistering a script module.
     135         *
     136         * This ensures that the global function aliases pass all the same parameters as the class methods.
     137         *
     138         * @ticket 56313
     139         *
     140         * @dataProvider data_test_register_and_enqueue_script_module
     141         *
     142         * @covers ::wp_register_script_module()
     143         * @covers WP_Script_Modules::register()
     144         * @covers ::wp_enqueue_script_module()
     145         * @covers WP_Script_Modules::enqueue()
     146         * @covers ::wp_dequeue_script_module()
     147         * @covers WP_Script_Modules::dequeue()
     148         * @covers ::wp_deregister_script_module()
     149         * @covers WP_Script_Modules::deregister()
     150         * @covers WP_Script_Modules::set_fetchpriority()
     151         * @covers WP_Script_Modules::print_enqueued_script_modules()
     152         * @covers WP_Script_Modules::print_import_map()
     153         * @covers WP_Script_Modules::print_script_module_preloads()
     154         */
     155        public function test_comprehensive_methods( bool $use_global_function, bool $only_enqueue ) {
     156                global $wp_version;
     157                $wp_version = '99.9.9';
     158
     159                $register = static function ( ...$args ) use ( $use_global_function ) {
     160                        if ( $use_global_function ) {
     161                                wp_register_script_module( ...$args );
     162                        } else {
     163                                wp_script_modules()->register( ...$args );
     164                        }
     165                };
     166
     167                $register_and_enqueue = static function ( ...$args ) use ( $use_global_function, $only_enqueue ) {
     168                        if ( $use_global_function ) {
     169                                if ( $only_enqueue ) {
     170                                        wp_enqueue_script_module( ...$args );
     171                                } else {
     172                                        wp_register_script_module( ...$args );
     173                                        wp_enqueue_script_module( $args[0] );
     174                                }
     175                        } else {
     176                                if ( $only_enqueue ) {
     177                                        wp_script_modules()->enqueue( ...$args );
     178                                } else {
     179                                        wp_script_modules()->register( ...$args );
     180                                        wp_script_modules()->enqueue( $args[0] );
     181                                }
     182                        }
     183                };
     184
     185                // Minimal args.
     186                $register_and_enqueue( 'a', '/a.js' );
     187
     188                // One Dependency.
     189                $register( 'b-dep', '/b-dep.js' );
     190                $register_and_enqueue( 'b', '/b.js', array( 'b-dep' ) );
     191                $this->assertTrue( wp_script_modules()->set_fetchpriority( 'b', 'low' ) );
     192
     193                // Two dependencies with different formats and a false version.
     194                $register( 'c-dep', '/c-static.js', array(), false, array( 'fetchpriority' => 'low' ) );
     195                $register( 'c-static-dep', '/c-static-dep.js', array(), false, array( 'fetchpriority' => 'high' ) );
     196                $register_and_enqueue(
     197                        'c',
     198                        '/c.js',
     199                        array(
     200                                'c-dep',
     201                                array(
     202                                        'id'     => 'c-static-dep',
     203                                        'import' => 'static',
     204                                ),
     205                        ),
     206                        false
     207                );
     208
     209                // Two dependencies, one imported statically and the other dynamically, with a null version.
     210                $register( 'd-static-dep', '/d-static-dep.js', array(), false, array( 'fetchpriority' => 'auto' ) );
     211                $register( 'd-dynamic-dep', '/d-dynamic-dep.js', array(), false, array( 'fetchpriority' => 'high' ) ); // Because this is a dynamic import dependency, the fetchpriority will not be reflected in the markup since no SCRIPT tag and no preload LINK are printed, and the importmap SCRIPT does not support designating a priority.
     212                $register_and_enqueue(
     213                        'd',
     214                        '/d.js',
     215                        array(
     216                                array(
     217                                        'id'     => 'd-static-dep',
     218                                        'import' => 'static',
     219                                ),
     220                                array(
     221                                        'id'     => 'd-dynamic-dep',
     222                                        'import' => 'dynamic',
     223                                ),
     224                        ),
     225                        null
     226                );
     227
     228                // No dependencies, with a string version version.
     229                $register_and_enqueue(
     230                        'e',
     231                        '/e.js',
     232                        array(),
     233                        '1.0.0'
     234                );
     235
     236                // No dependencies, with a string version and fetch priority.
     237                $register_and_enqueue(
     238                        'f',
     239                        '/f.js',
     240                        array(),
     241                        '2.0.0',
     242                        array( 'fetchpriority' => 'auto' )
     243                );
     244
     245                // No dependencies, with a string version and fetch priority of low.
     246                $register_and_enqueue(
     247                        'g',
     248                        '/g.js',
     249                        array(),
     250                        '2.0.0',
     251                        array( 'fetchpriority' => 'low' )
     252                );
     253
     254                // No dependencies, with a string version and fetch priority of high.
     255                $register_and_enqueue(
     256                        'h',
     257                        '/h.js',
     258                        array(),
     259                        '3.0.0',
     260                        array( 'fetchpriority' => 'high' )
     261                );
     262
     263                $actual = array(
     264                        'preload_links' => $this->get_preloaded_script_modules(),
     265                        'script_tags'   => $this->get_enqueued_script_modules(),
     266                        'import_map'    => $this->get_import_map(),
     267                );
     268
     269                $this->assertSame(
     270                        array(
     271                                'preload_links' => array(
     272                                        'b-dep'        => array(
     273                                                'url'           => '/b-dep.js?ver=99.9.9',
     274                                                'fetchpriority' => 'auto',
     275                                        ),
     276                                        'c-dep'        => array(
     277                                                'url'           => '/c-static.js?ver=99.9.9',
     278                                                'fetchpriority' => 'low',
     279                                        ),
     280                                        'c-static-dep' => array(
     281                                                'url'           => '/c-static-dep.js?ver=99.9.9',
     282                                                'fetchpriority' => 'high',
     283                                        ),
     284                                        'd-static-dep' => array(
     285                                                'url'           => '/d-static-dep.js?ver=99.9.9',
     286                                                'fetchpriority' => 'auto',
     287                                        ),
     288                                ),
     289                                'script_tags'   => array(
     290                                        'a' => array(
     291                                                'url'           => '/a.js?ver=99.9.9',
     292                                                'fetchpriority' => 'auto',
     293                                        ),
     294                                        'b' => array(
     295                                                'url'           => '/b.js?ver=99.9.9',
     296                                                'fetchpriority' => 'low',
     297                                        ),
     298                                        'c' => array(
     299                                                'url'           => '/c.js?ver=99.9.9',
     300                                                'fetchpriority' => 'auto',
     301                                        ),
     302                                        'd' => array(
     303                                                'url'           => '/d.js',
     304                                                'fetchpriority' => 'auto',
     305                                        ),
     306                                        'e' => array(
     307                                                'url'           => '/e.js?ver=1.0.0',
     308                                                'fetchpriority' => 'auto',
     309                                        ),
     310                                        'f' => array(
     311                                                'url'           => '/f.js?ver=2.0.0',
     312                                                'fetchpriority' => 'auto',
     313                                        ),
     314                                        'g' => array(
     315                                                'url'           => '/g.js?ver=2.0.0',
     316                                                'fetchpriority' => 'low',
     317                                        ),
     318                                        'h' => array(
     319                                                'url'           => '/h.js?ver=3.0.0',
     320                                                'fetchpriority' => 'high',
     321                                        ),
     322                                ),
     323                                'import_map'    => array(
     324                                        'b-dep'         => '/b-dep.js?ver=99.9.9',
     325                                        'c-dep'         => '/c-static.js?ver=99.9.9',
     326                                        'c-static-dep'  => '/c-static-dep.js?ver=99.9.9',
     327                                        'd-static-dep'  => '/d-static-dep.js?ver=99.9.9',
     328                                        'd-dynamic-dep' => '/d-dynamic-dep.js?ver=99.9.9',
     329                                ),
     330                        ),
     331                        $actual,
     332                        "Snapshot:\n" . var_export( $actual, true )
     333                );
     334
     335                // Dequeue the first half of the scripts.
     336                foreach ( array( 'a', 'b', 'c', 'd' ) as $id ) {
     337                        if ( $use_global_function ) {
     338                                wp_dequeue_script_module( $id );
     339                        } else {
     340                                wp_script_modules()->dequeue( $id );
    46341                        }
    47342                }
    48343
    49                 return $enqueued_script_modules;
    50         }
    51 
    52         /**
    53          * Gets the script modules listed in the import map.
    54          *
    55          * @return array Import map entry URLs, keyed by script module identifier.
    56          */
    57         public function get_import_map() {
    58                 $import_map_markup = get_echo( array( $this->script_modules, 'print_import_map' ) );
    59                 preg_match( '/<script type="importmap" id="wp-importmap">.*?(\{.*\}).*?<\/script>/s', $import_map_markup, $import_map_string );
    60                 return json_decode( $import_map_string[1], true )['imports'];
    61         }
    62 
    63         /**
    64          * Gets a list of preloaded script modules.
    65          *
    66          * @return array Preloaded script module URLs, keyed by script module identifier.
    67          */
    68         public function get_preloaded_script_modules() {
    69                 $preloaded_markup         = get_echo( array( $this->script_modules, 'print_script_module_preloads' ) );
    70                 $p                        = new WP_HTML_Tag_Processor( $preloaded_markup );
    71                 $preloaded_script_modules = array();
    72 
    73                 while ( $p->next_tag( array( 'tag' => 'LINK' ) ) ) {
    74                         if ( 'modulepreload' === $p->get_attribute( 'rel' ) ) {
    75                                 $id                              = preg_replace( '/-js-modulepreload$/', '', $p->get_attribute( 'id' ) );
    76                                 $preloaded_script_modules[ $id ] = $p->get_attribute( 'href' );
     344                $actual = array(
     345                        'preload_links' => $this->get_preloaded_script_modules(),
     346                        'script_tags'   => $this->get_enqueued_script_modules(),
     347                        'import_map'    => $this->get_import_map(),
     348                );
     349                $this->assertSame(
     350                        array(
     351                                'preload_links' => array(),
     352                                'script_tags'   => array(
     353                                        'e' => array(
     354                                                'url'           => '/e.js?ver=1.0.0',
     355                                                'fetchpriority' => 'auto',
     356                                        ),
     357                                        'f' => array(
     358                                                'url'           => '/f.js?ver=2.0.0',
     359                                                'fetchpriority' => 'auto',
     360                                        ),
     361                                        'g' => array(
     362                                                'url'           => '/g.js?ver=2.0.0',
     363                                                'fetchpriority' => 'low',
     364                                        ),
     365                                        'h' => array(
     366                                                'url'           => '/h.js?ver=3.0.0',
     367                                                'fetchpriority' => 'high',
     368                                        ),
     369                                ),
     370                                'import_map'    => array(),
     371                        ),
     372                        $actual,
     373                        "Snapshot:\n" . var_export( $actual, true )
     374                );
     375
     376                // Unregister the remaining scripts.
     377                foreach ( array( 'e', 'f', 'g', 'h' ) as $id ) {
     378                        if ( $use_global_function ) {
     379                                wp_dequeue_script_module( $id );
     380                        } else {
     381                                wp_script_modules()->dequeue( $id );
    77382                        }
    78383                }
    79384
    80                 return $preloaded_script_modules;
     385                $actual = array(
     386                        'preload_links' => $this->get_preloaded_script_modules(),
     387                        'script_tags'   => $this->get_enqueued_script_modules(),
     388                        'import_map'    => $this->get_import_map(),
     389                );
     390                $this->assertSame(
     391                        array(
     392                                'preload_links' => array(),
     393                                'script_tags'   => array(),
     394                                'import_map'    => array(),
     395                        ),
     396                        $actual,
     397                        "Snapshot:\n" . var_export( $actual, true )
     398                );
     399        }
     400
     401        /**
     402         * Data provider.
     403         *
     404         * @return array<string, array{ use_global_function: bool, only_enqueue: bool }>
     405         */
     406        public function data_test_register_and_enqueue_script_module(): array {
     407                $data = array();
     408
     409                foreach ( array( true, false ) as $use_global_function ) {
     410                        foreach ( array( true, false ) as $only_enqueue ) {
     411                                $test_case = compact( 'use_global_function', 'only_enqueue' );
     412                                $key_parts = array();
     413                                foreach ( $test_case as $param_name => $param_value ) {
     414                                        $key_parts[] = sprintf( '%s_%s', $param_name, json_encode( $param_value ) );
     415                                }
     416                                $data[ join( '_', $key_parts ) ] = $test_case;
     417                        }
     418                }
     419
     420                return $data;
    81421        }
    82422
     
    86426         * @ticket 56313
    87427         *
    88          * @covers ::register()
    89          * @covers ::enqueue()
    90          * @covers ::print_enqueued_script_modules()
     428         * @covers WP_Script_Modules::register()
     429         * @covers WP_Script_Modules::enqueue()
     430         * @covers WP_Script_Modules::print_enqueued_script_modules()
     431         * @covers WP_Script_Modules::set_fetchpriority()
    91432         */
    92433        public function test_wp_enqueue_script_module() {
    93434                $this->script_modules->register( 'foo', '/foo.js' );
    94                 $this->script_modules->register( 'bar', '/bar.js' );
     435                $this->script_modules->register( 'bar', '/bar.js', array(), false, array( 'fetchpriority' => 'high' ) );
     436                $this->script_modules->register( 'baz', '/baz.js' );
     437                $this->assertTrue( $this->script_modules->set_fetchpriority( 'baz', 'low' ) );
    95438                $this->script_modules->enqueue( 'foo' );
    96439                $this->script_modules->enqueue( 'bar' );
     440                $this->script_modules->enqueue( 'baz' );
    97441
    98442                $enqueued_script_modules = $this->get_enqueued_script_modules();
    99443
    100                 $this->assertCount( 2, $enqueued_script_modules );
    101                 $this->assertStringStartsWith( '/foo.js', $enqueued_script_modules['foo'] );
    102                 $this->assertStringStartsWith( '/bar.js', $enqueued_script_modules['bar'] );
     444                $this->assertCount( 3, $enqueued_script_modules );
     445                $this->assertStringStartsWith( '/foo.js', $enqueued_script_modules['foo']['url'] );
     446                $this->assertSame( 'auto', $enqueued_script_modules['foo']['fetchpriority'] );
     447                $this->assertStringStartsWith( '/bar.js', $enqueued_script_modules['bar']['url'] );
     448                $this->assertSame( 'high', $enqueued_script_modules['bar']['fetchpriority'] );
     449                $this->assertStringStartsWith( '/baz.js', $enqueued_script_modules['baz']['url'] );
     450                $this->assertSame( 'low', $enqueued_script_modules['baz']['fetchpriority'] );
    103451        }
    104452
     
    108456        * @ticket 56313
    109457        *
    110         * @covers ::register()
    111         * @covers ::enqueue()
    112         * @covers ::dequeue()
    113         * @covers ::print_enqueued_script_modules()
     458        * @covers WP_Script_Modules::register()
     459        * @covers WP_Script_Modules::enqueue()
     460        * @covers WP_Script_Modules::dequeue()
     461        * @covers WP_Script_Modules::print_enqueued_script_modules()
    114462        */
    115463        public function test_wp_dequeue_script_module() {
     
    135483         * @ticket 60463
    136484         *
    137          * @covers ::register()
    138          * @covers ::enqueue()
    139          * @covers ::deregister()
    140          * @covers ::get_enqueued_script_modules()
     485         * @covers WP_Script_Modules::register()
     486         * @covers WP_Script_Modules::enqueue()
     487         * @covers WP_Script_Modules::deregister()
     488         * @covers WP_Script_Modules::get_enqueued_script_modules()
    141489         */
    142490        public function test_wp_deregister_script_module() {
     
    161509         * @ticket 60463
    162510         *
    163          * @covers ::deregister()
    164          * @covers ::get_enqueued_script_modules()
     511         * @covers WP_Script_Modules::deregister()
     512         * @covers WP_Script_Modules::get_enqueued_script_modules()
    165513         */
    166514        public function test_wp_deregister_unexistent_script_module() {
     
    179527         * @ticket 60463
    180528         *
    181          * @covers ::get_enqueued_script_modules()
    182          * @covers ::register()
    183          * @covers ::deregister()
    184          * @covers ::enqueue()
     529         * @covers WP_Script_Modules::get_enqueued_script_modules()
     530         * @covers WP_Script_Modules::register()
     531         * @covers WP_Script_Modules::deregister()
     532         * @covers WP_Script_Modules::enqueue()
    185533         */
    186534        public function test_wp_deregister_already_deregistered_script_module() {
     
    206554        * @ticket 56313
    207555        *
    208         * @covers ::register()
    209         * @covers ::enqueue()
    210         * @covers ::print_enqueued_script_modules()
     556        * @covers WP_Script_Modules::register()
     557        * @covers WP_Script_Modules::enqueue()
     558        * @covers WP_Script_Modules::print_enqueued_script_modules()
    211559        */
    212560        public function test_wp_enqueue_script_module_works_before_register() {
     
    218566
    219567                $this->assertCount( 1, $enqueued_script_modules );
    220                 $this->assertStringStartsWith( '/foo.js', $enqueued_script_modules['foo'] );
     568                $this->assertStringStartsWith( '/foo.js', $enqueued_script_modules['foo']['url'] );
     569                $this->assertSame( 'auto', $enqueued_script_modules['foo']['fetchpriority'] );
    221570                $this->assertArrayNotHasKey( 'bar', $enqueued_script_modules );
    222571        }
     
    228577         * @ticket 56313
    229578         *
    230          * @covers ::register()
    231          * @covers ::enqueue()
    232          * @covers ::dequeue()
    233          * @covers ::print_enqueued_script_modules()
     579         * @covers WP_Script_Modules::register()
     580         * @covers WP_Script_Modules::enqueue()
     581         * @covers WP_Script_Modules::dequeue()
     582         * @covers WP_Script_Modules::print_enqueued_script_modules()
    234583         */
    235584        public function test_wp_dequeue_script_module_works_before_register() {
     
    253602         * @ticket 56313
    254603         *
    255          * @covers ::register()
    256          * @covers ::enqueue()
    257          * @covers ::print_import_map()
     604         * @covers WP_Script_Modules::register()
     605         * @covers WP_Script_Modules::enqueue()
     606         * @covers WP_Script_Modules::print_import_map()
    258607         */
    259608        public function test_wp_import_map_dependencies() {
     
    276625         * @ticket 56313
    277626         *
    278          * @covers ::register()
    279          * @covers ::enqueue()
    280          * @covers ::print_import_map()
     627         * @covers WP_Script_Modules::register()
     628         * @covers WP_Script_Modules::enqueue()
     629         * @covers WP_Script_Modules::print_import_map()
    281630         */
    282631        public function test_wp_import_map_no_duplicate_dependencies() {
     
    299648         * @ticket 56313
    300649         *
    301          * @covers ::register()
    302          * @covers ::enqueue()
    303          * @covers ::print_import_map()
     650         * @covers WP_Script_Modules::register()
     651         * @covers WP_Script_Modules::enqueue()
     652         * @covers WP_Script_Modules::print_import_map()
    304653         */
    305654        public function test_wp_import_map_recursive_dependencies() {
     
    350699         * @ticket 56313
    351700         *
    352          * @covers ::register()
    353          * @covers ::enqueue()
    354          * @covers ::print_import_map()
     701         * @covers WP_Script_Modules::register()
     702         * @covers WP_Script_Modules::enqueue()
     703         * @covers WP_Script_Modules::print_import_map()
    355704         */
    356705        public function test_wp_import_map_doesnt_print_if_no_dependencies() {
     
    369718         * @ticket 56313
    370719         *
    371          * @covers ::register()
    372          * @covers ::enqueue()
    373          * @covers ::print_script_module_preloads()
     720         * @covers WP_Script_Modules::register()
     721         * @covers WP_Script_Modules::enqueue()
     722         * @covers WP_Script_Modules::print_script_module_preloads()
    374723         */
    375724        public function test_wp_enqueue_preloaded_static_dependencies() {
     
    397746                                        'import' => 'dynamic',
    398747                                ),
    399                         )
     748                        ),
     749                        false,
     750                        array( 'fetchpriority' => 'high' )
    400751                );
    401752                $this->script_modules->register( 'dynamic-dep', '/dynamic-dep.js' );
     
    408759
    409760                $this->assertCount( 2, $preloaded_script_modules );
    410                 $this->assertStringStartsWith( '/static-dep.js', $preloaded_script_modules['static-dep'] );
    411                 $this->assertStringStartsWith( '/nested-static-dep.js', $preloaded_script_modules['nested-static-dep'] );
     761                $this->assertStringStartsWith( '/static-dep.js', $preloaded_script_modules['static-dep']['url'] );
     762                $this->assertSame( 'high', $preloaded_script_modules['static-dep']['fetchpriority'] );
     763                $this->assertStringStartsWith( '/nested-static-dep.js', $preloaded_script_modules['nested-static-dep']['url'] );
     764                $this->assertSame( 'auto', $preloaded_script_modules['nested-static-dep']['fetchpriority'] );
    412765                $this->assertArrayNotHasKey( 'dynamic-dep', $preloaded_script_modules );
    413766                $this->assertArrayNotHasKey( 'nested-dynamic-dep', $preloaded_script_modules );
     
    420773         * @ticket 56313
    421774         *
    422          * @covers ::register()
    423          * @covers ::enqueue()
    424          * @covers ::print_script_module_preloads()
     775         * @covers WP_Script_Modules::register()
     776         * @covers WP_Script_Modules::enqueue()
     777         * @covers WP_Script_Modules::print_script_module_preloads()
    425778         */
    426779        public function test_wp_dont_preload_static_dependencies_of_dynamic_dependencies() {
     
    445798
    446799                $this->assertCount( 1, $preloaded_script_modules );
    447                 $this->assertStringStartsWith( '/static-dep.js', $preloaded_script_modules['static-dep'] );
     800                $this->assertStringStartsWith( '/static-dep.js', $preloaded_script_modules['static-dep']['url'] );
     801                $this->assertSame( 'auto', $preloaded_script_modules['static-dep']['fetchpriority'] );
    448802                $this->assertArrayNotHasKey( 'dynamic-dep', $preloaded_script_modules );
    449803                $this->assertArrayNotHasKey( 'nested-dynamic-dep', $preloaded_script_modules );
     
    456810         * @ticket 56313
    457811         *
    458          * @covers ::register()
    459          * @covers ::enqueue()
    460          * @covers ::print_script_module_preloads()
     812         * @covers WP_Script_Modules::register()
     813         * @covers WP_Script_Modules::enqueue()
     814         * @covers WP_Script_Modules::print_script_module_preloads()
    461815         */
    462816        public function test_wp_preloaded_dependencies_filter_enqueued_script_modules() {
     
    487841         * @ticket 56313
    488842         *
    489          * @covers ::register()
    490          * @covers ::enqueue()
    491          * @covers ::print_import_map()
     843         * @covers WP_Script_Modules::register()
     844         * @covers WP_Script_Modules::enqueue()
     845         * @covers WP_Script_Modules::print_import_map()
    492846         */
    493847        public function test_wp_enqueued_script_modules_with_dependants_add_import_map() {
     
    518872         * @ticket 56313
    519873         *
    520          * @covers ::get_src()
     874         * @covers WP_Script_Modules::get_src()
    521875         */
    522876        public function test_get_src() {
     
    587941         * @ticket 56313
    588942         *
    589          * @covers ::register()
    590          * @covers ::enqueue()
    591          * @covers ::print_enqueued_script_modules()
    592          * @covers ::print_import_map()
    593          * @covers ::print_script_module_preloads()
    594          * @covers ::get_version_query_string()
     943         * @covers WP_Script_Modules::register()
     944         * @covers WP_Script_Modules::enqueue()
     945         * @covers WP_Script_Modules::print_enqueued_script_modules()
     946         * @covers WP_Script_Modules::print_import_map()
     947         * @covers WP_Script_Modules::print_script_module_preloads()
     948         * @covers WP_Script_Modules::get_version_query_string()
    595949         */
    596950        public function test_version_is_propagated_correctly() {
     
    601955                                'dep',
    602956                        ),
    603                         '1.0'
    604                 );
    605                 $this->script_modules->register( 'dep', '/dep.js', array(), '2.0' );
     957                        '1.0',
     958                        array( 'fetchpriority' => 'auto' )
     959                );
     960                $this->script_modules->register( 'dep', '/dep.js', array(), '2.0', array( 'fetchpriority' => 'high' ) );
    606961                $this->script_modules->enqueue( 'foo' );
    607962
    608963                $enqueued_script_modules = $this->get_enqueued_script_modules();
    609                 $this->assertSame( '/foo.js?ver=1.0', $enqueued_script_modules['foo'] );
     964                $this->assertSame( '/foo.js?ver=1.0', $enqueued_script_modules['foo']['url'] );
     965                $this->assertSame( 'auto', $enqueued_script_modules['foo']['fetchpriority'] );
    610966
    611967                $import_map = $this->get_import_map();
     
    613969
    614970                $preloaded_script_modules = $this->get_preloaded_script_modules();
    615                 $this->assertSame( '/dep.js?ver=2.0', $preloaded_script_modules['dep'] );
     971                $this->assertSame( '/dep.js?ver=2.0', $preloaded_script_modules['dep']['url'] );
     972                $this->assertSame( 'high', $preloaded_script_modules['dep']['fetchpriority'] );
    616973        }
    617974
     
    622979         * @ticket 56313
    623980         *
    624          * @covers ::enqueue()
    625          * @covers ::print_enqueued_script_modules()
     981         * @covers WP_Script_Modules::enqueue()
     982         * @covers WP_Script_Modules::print_enqueued_script_modules()
    626983         */
    627984        public function test_wp_enqueue_script_module_doesnt_register_without_a_valid_src() {
     
    640997         * @ticket 56313
    641998         *
    642          * @covers ::enqueue()
    643          * @covers ::print_enqueued_script_modules()
     999         * @covers WP_Script_Modules::enqueue()
     1000         * @covers WP_Script_Modules::print_enqueued_script_modules()
    6441001         */
    6451002        public function test_wp_enqueue_script_module_registers_with_valid_src() {
     
    6491006
    6501007                $this->assertCount( 1, $enqueued_script_modules );
    651                 $this->assertStringStartsWith( '/foo.js', $enqueued_script_modules['foo'] );
     1008                $this->assertStringStartsWith( '/foo.js', $enqueued_script_modules['foo']['url'] );
     1009                $this->assertSame( 'auto', $enqueued_script_modules['foo']['fetchpriority'] );
    6521010        }
    6531011
     
    6581016         * @ticket 56313
    6591017         *
    660          * @covers ::enqueue()
    661          * @covers ::print_enqueued_script_modules()
     1018         * @covers WP_Script_Modules::enqueue()
     1019         * @covers WP_Script_Modules::print_enqueued_script_modules()
    6621020         */
    6631021        public function test_wp_enqueue_script_module_registers_with_valid_src_the_second_time() {
     
    6741032
    6751033                $this->assertCount( 1, $enqueued_script_modules );
    676                 $this->assertStringStartsWith( '/foo.js', $enqueued_script_modules['foo'] );
     1034                $this->assertStringStartsWith( '/foo.js', $enqueued_script_modules['foo']['url'] );
     1035                $this->assertSame( 'auto', $enqueued_script_modules['foo']['fetchpriority'] );
    6771036        }
    6781037
     
    6831042         * @ticket 56313
    6841043         *
    685          * @covers ::register()
    686          * @covers ::enqueue()
    687          * @covers ::print_enqueued_script_modules()
    688          * @covers ::print_import_map()
     1044         * @covers WP_Script_Modules::register()
     1045         * @covers WP_Script_Modules::enqueue()
     1046         * @covers WP_Script_Modules::print_enqueued_script_modules()
     1047         * @covers WP_Script_Modules::print_import_map()
    6891048         */
    6901049        public function test_wp_enqueue_script_module_registers_all_params() {
    691                 $this->script_modules->enqueue( 'foo', '/foo.js', array( 'dep' ), '1.0' );
     1050                $this->script_modules->enqueue( 'foo', '/foo.js', array( 'dep' ), '1.0', array( 'fetchpriority' => 'low' ) );
    6921051                $this->script_modules->register( 'dep', '/dep.js' );
    6931052
     
    6961055
    6971056                $this->assertCount( 1, $enqueued_script_modules );
    698                 $this->assertSame( '/foo.js?ver=1.0', $enqueued_script_modules['foo'] );
     1057                $this->assertSame( '/foo.js?ver=1.0', $enqueued_script_modules['foo']['url'] );
     1058                $this->assertSame( 'low', $enqueued_script_modules['foo']['fetchpriority'] );
    6991059                $this->assertCount( 1, $import_map );
    7001060                $this->assertStringStartsWith( '/dep.js', $import_map['dep'] );
     
    8531213
    8541214                        // Non UTF-8
    855                         'Solidus'                                => array( '/', '/', 'iso-8859-1' ),
    856                         'Less than'                              => array( '<', '\u003C', 'iso-8859-1' ),
    857                         'Greater than'                           => array( '>', '\u003E', 'iso-8859-1' ),
    858                         'Ampersand'                              => array( '&', '&', 'iso-8859-1' ),
    859                         'Newline'                                => array( "\n", "\\n", 'iso-8859-1' ),
    860                         'Tab'                                    => array( "\t", "\\t", 'iso-8859-1' ),
    861                         'Form feed'                              => array( "\f", "\\f", 'iso-8859-1' ),
    862                         'Carriage return'                        => array( "\r", "\\r", 'iso-8859-1' ),
    863                         'Line separator'                         => array( "\u{2028}", "\u2028", 'iso-8859-1' ),
    864                         'Paragraph separator'                    => array( "\u{2029}", "\u2029", 'iso-8859-1' ),
     1215                        'Solidus non-utf8'                       => array( '/', '/', 'iso-8859-1' ),
     1216                        'Less than non-utf8'                     => array( '<', '\u003C', 'iso-8859-1' ),
     1217                        'Greater than non-utf8'                  => array( '>', '\u003E', 'iso-8859-1' ),
     1218                        'Ampersand non-utf8'                     => array( '&', '&', 'iso-8859-1' ),
     1219                        'Newline non-utf8'                       => array( "\n", "\\n", 'iso-8859-1' ),
     1220                        'Tab non-utf8'                           => array( "\t", "\\t", 'iso-8859-1' ),
     1221                        'Form feed non-utf8'                     => array( "\f", "\\f", 'iso-8859-1' ),
     1222                        'Carriage return non-utf8'               => array( "\r", "\\r", 'iso-8859-1' ),
     1223                        'Line separator non-utf8'                => array( "\u{2028}", "\u2028", 'iso-8859-1' ),
     1224                        'Paragraph separator non-utf8'           => array( "\u{2029}", "\u2029", 'iso-8859-1' ),
    8651225                        /*
    8661226                         * The following is the Flag of England emoji
    8671227                         * PHP: "\u{1F3F4}\u{E0067}\u{E0062}\u{E0065}\u{E006E}\u{E0067}\u{E007F}"
    8681228                         */
    869                         'Flag of england'                        => array( '🏴󠁧󠁢󠁥󠁮󠁧󠁿', "\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f", 'iso-8859-1' ),
    870                         'Malicious script closer'                => array( '</script>', '\u003C/script\u003E', 'iso-8859-1' ),
    871                         'Entity-encoded malicious script closer' => array( '&lt;/script&gt;', '&lt;/script&gt;', 'iso-8859-1' ),
     1229                        'Flag of england non-utf8'               => array( '🏴󠁧󠁢󠁥󠁮󠁧󠁿', "\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f", 'iso-8859-1' ),
     1230                        'Malicious script closer non-utf8'       => array( '</script>', '\u003C/script\u003E', 'iso-8859-1' ),
     1231                        'Entity-encoded malicious script closer non-utf8' => array( '&lt;/script&gt;', '&lt;/script&gt;', 'iso-8859-1' ),
    8721232
    8731233                );
     
    8951255
    8961256        /**
     1257         * Data provider for test_fetchpriority_values.
     1258         *
     1259         * @return array<string, array{fetchpriority: string}>
     1260         */
     1261        public function data_provider_fetchpriority_values(): array {
     1262                return array(
     1263                        'auto' => array( 'fetchpriority' => 'auto' ),
     1264                        'low'  => array( 'fetchpriority' => 'low' ),
     1265                        'high' => array( 'fetchpriority' => 'high' ),
     1266                );
     1267        }
     1268
     1269        /**
     1270         * Tests that valid fetchpriority values are correctly added to the registered module.
     1271         *
     1272         * @ticket 61734
     1273         *
     1274         * @covers WP_Script_Modules::register
     1275         * @covers WP_Script_Modules::set_fetchpriority
     1276         *
     1277         * @dataProvider data_provider_fetchpriority_values
     1278         *
     1279         * @param string $fetchpriority The fetchpriority value to test.
     1280         */
     1281        public function test_fetchpriority_values( string $fetchpriority ) {
     1282                $this->script_modules->register( 'test-script', '/test-script.js', array(), null, array( 'fetchpriority' => $fetchpriority ) );
     1283                $registered_modules = $this->get_registered_script_modules( $this->script_modules );
     1284                $this->assertSame( $fetchpriority, $registered_modules['test-script']['fetchpriority'] );
     1285
     1286                $this->script_modules->register( 'test-script-2', '/test-script-2.js' );
     1287                $this->assertTrue( $this->script_modules->set_fetchpriority( 'test-script-2', $fetchpriority ) );
     1288                $registered_modules = $this->get_registered_script_modules( $this->script_modules );
     1289                $this->assertSame( $fetchpriority, $registered_modules['test-script-2']['fetchpriority'] );
     1290
     1291                $this->assertTrue( $this->script_modules->set_fetchpriority( 'test-script-2', '' ) );
     1292                $registered_modules = $this->get_registered_script_modules( $this->script_modules );
     1293                $this->assertSame( 'auto', $registered_modules['test-script-2']['fetchpriority'] );
     1294        }
     1295
     1296        /**
     1297         * Tests that a script module with an invalid fetchpriority value gets a value of auto.
     1298         *
     1299         * @ticket 61734
     1300         *
     1301         * @covers WP_Script_Modules::register
     1302         * @expectedIncorrectUsage WP_Script_Modules::register
     1303         */
     1304        public function test_register_script_module_having_fetchpriority_with_invalid_value() {
     1305                $this->script_modules->register( 'foo', '/foo.js', array(), false, array( 'fetchpriority' => 'silly' ) );
     1306                $registered_modules = $this->get_registered_script_modules( $this->script_modules );
     1307                $this->assertSame( 'auto', $registered_modules['foo']['fetchpriority'] );
     1308                $this->assertArrayHasKey( 'WP_Script_Modules::register', $this->caught_doing_it_wrong );
     1309                $this->assertStringContainsString( 'Invalid fetchpriority `silly`', $this->caught_doing_it_wrong['WP_Script_Modules::register'] );
     1310        }
     1311
     1312        /**
     1313         * Tests that a script module with an invalid fetchpriority value type gets a value of auto.
     1314         *
     1315         * @ticket 61734
     1316         *
     1317         * @covers WP_Script_Modules::register
     1318         * @expectedIncorrectUsage WP_Script_Modules::register
     1319         */
     1320        public function test_register_script_module_having_fetchpriority_with_invalid_value_type() {
     1321                $this->script_modules->register( 'foo', '/foo.js', array(), false, array( 'fetchpriority' => array( 'WHY AM I NOT A STRING???' ) ) );
     1322                $registered_modules = $this->get_registered_script_modules( $this->script_modules );
     1323                $this->assertSame( 'auto', $registered_modules['foo']['fetchpriority'] );
     1324                $this->assertArrayHasKey( 'WP_Script_Modules::register', $this->caught_doing_it_wrong );
     1325                $this->assertStringContainsString( 'Invalid fetchpriority `array`', $this->caught_doing_it_wrong['WP_Script_Modules::register'] );
     1326        }
     1327
     1328        /**
     1329         * Tests that a setting the fetchpriority for script module with an invalid value is ignored so that it remains auto.
     1330         *
     1331         * @ticket 61734
     1332         *
     1333         * @covers WP_Script_Modules::register
     1334         * @covers WP_Script_Modules::set_fetchpriority
     1335         * @expectedIncorrectUsage WP_Script_Modules::set_fetchpriority
     1336         */
     1337        public function test_set_fetchpriority_with_invalid_value() {
     1338                $this->script_modules->register( 'foo', '/foo.js' );
     1339                $this->script_modules->set_fetchpriority( 'foo', 'silly' );
     1340                $registered_modules = $this->get_registered_script_modules( $this->script_modules );
     1341                $this->assertSame( 'auto', $registered_modules['foo']['fetchpriority'] );
     1342        }
     1343
     1344        /**
     1345         * Gets registered script modules.
     1346         *
     1347         * @param WP_Script_Modules $script_modules
     1348         * @return array<string, array> Registered modules.
     1349         */
     1350        private function get_registered_script_modules( WP_Script_Modules $script_modules ): array {
     1351                $reflection_class    = new ReflectionClass( $script_modules );
     1352                $registered_property = $reflection_class->getProperty( 'registered' );
     1353                $registered_property->setAccessible( true );
     1354                return $registered_property->getValue( $script_modules );
     1355        }
     1356
     1357        /**
    8971358         * Data provider.
    8981359         *
Note: See TracChangeset for help on using the changeset viewer.