@@ -6359,3 +6359,205 @@ function wp_get_image_editor_output_format( $filename, $mime_type ) {
63596359 */
63606360 return apply_filters ( 'image_editor_output_format ' , $ output_format , $ filename , $ mime_type );
63616361}
6362+
6363+ /**
6364+ * Checks whether client-side media processing is enabled.
6365+ *
6366+ * Client-side media processing uses the browser's capabilities to handle
6367+ * tasks like image resizing and compression before uploading to the server.
6368+ *
6369+ * @since 7.0.0
6370+ *
6371+ * @return bool Whether client-side media processing is enabled.
6372+ */
6373+ function wp_is_client_side_media_processing_enabled (): bool {
6374+ /**
6375+ * Filters whether client-side media processing is enabled.
6376+ *
6377+ * @since 7.0.0
6378+ *
6379+ * @param bool $enabled Whether client-side media processing is enabled. Default true.
6380+ */
6381+ return (bool ) apply_filters ( 'wp_client_side_media_processing_enabled ' , true );
6382+ }
6383+
6384+ /**
6385+ * Sets a global JS variable to indicate that client-side media processing is enabled.
6386+ *
6387+ * @since 7.0.0
6388+ */
6389+ function wp_set_client_side_media_processing_flag (): void {
6390+ if ( ! wp_is_client_side_media_processing_enabled () ) {
6391+ return ;
6392+ }
6393+
6394+ wp_add_inline_script ( 'wp-block-editor ' , 'window.__clientSideMediaProcessing = true ' , 'before ' );
6395+
6396+ /*
6397+ * Register the @wordpress/vips/worker script module as a dynamic dependency
6398+ * of the wp-upload-media classic script. This ensures it is included in the
6399+ * import map so that the dynamic import() in upload-media.js can resolve it.
6400+ */
6401+ wp_scripts ()->add_data (
6402+ 'wp-upload-media ' ,
6403+ 'module_dependencies ' ,
6404+ array ( '@wordpress/vips/worker ' )
6405+ );
6406+ }
6407+
6408+ /**
6409+ * Enables cross-origin isolation in the block editor.
6410+ *
6411+ * Required for enabling SharedArrayBuffer for WebAssembly-based
6412+ * media processing in the editor.
6413+ *
6414+ * @since 7.0.0
6415+ *
6416+ * @link https://web.dev/coop-coep/
6417+ */
6418+ function wp_set_up_cross_origin_isolation (): void {
6419+ if ( ! wp_is_client_side_media_processing_enabled () ) {
6420+ return ;
6421+ }
6422+
6423+ $ screen = get_current_screen ();
6424+
6425+ if ( ! $ screen ) {
6426+ return ;
6427+ }
6428+
6429+ if ( ! $ screen ->is_block_editor () && 'site-editor ' !== $ screen ->id && ! ( 'widgets ' === $ screen ->id && wp_use_widgets_block_editor () ) ) {
6430+ return ;
6431+ }
6432+
6433+ // Cross-origin isolation is not needed if users can't upload files anyway.
6434+ if ( ! current_user_can ( 'upload_files ' ) ) {
6435+ return ;
6436+ }
6437+
6438+ wp_start_cross_origin_isolation_output_buffer ();
6439+ }
6440+
6441+ /**
6442+ * Starts an output buffer to send cross-origin isolation headers.
6443+ *
6444+ * Sends headers and uses an output buffer to add crossorigin="anonymous"
6445+ * attributes where needed.
6446+ *
6447+ * @since 7.0.0
6448+ *
6449+ * @link https://web.dev/coop-coep/
6450+ *
6451+ * @global bool $is_safari
6452+ */
6453+ function wp_start_cross_origin_isolation_output_buffer (): void {
6454+ global $ is_safari ;
6455+
6456+ $ coep = $ is_safari ? 'require-corp ' : 'credentialless ' ;
6457+
6458+ ob_start (
6459+ static function ( string $ output ) use ( $ coep ): string {
6460+ header ( 'Cross-Origin-Opener-Policy: same-origin ' );
6461+ header ( "Cross-Origin-Embedder-Policy: $ coep " );
6462+
6463+ return wp_add_crossorigin_attributes ( $ output );
6464+ }
6465+ );
6466+ }
6467+
6468+ /**
6469+ * Adds crossorigin="anonymous" to relevant tags in the given HTML string.
6470+ *
6471+ * @since 7.0.0
6472+ *
6473+ * @param string $html HTML input.
6474+ * @return string Modified HTML.
6475+ */
6476+ function wp_add_crossorigin_attributes ( string $ html ): string {
6477+ $ site_url = site_url ();
6478+
6479+ $ processor = new WP_HTML_Tag_Processor ( $ html );
6480+
6481+ // See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin.
6482+ $ cross_origin_tag_attributes = array (
6483+ 'AUDIO ' => array ( 'src ' => false ),
6484+ 'IMG ' => array (
6485+ 'src ' => false ,
6486+ 'srcset ' => true ,
6487+ ),
6488+ 'LINK ' => array (
6489+ 'href ' => false ,
6490+ 'imagesrcset ' => true ,
6491+ ),
6492+ 'SCRIPT ' => array ( 'src ' => false ),
6493+ 'VIDEO ' => array (
6494+ 'src ' => false ,
6495+ 'poster ' => false ,
6496+ ),
6497+ 'SOURCE ' => array ( 'src ' => false ),
6498+ );
6499+
6500+ while ( $ processor ->next_tag () ) {
6501+ $ tag = $ processor ->get_tag ();
6502+
6503+ if ( ! isset ( $ cross_origin_tag_attributes [ $ tag ] ) ) {
6504+ continue ;
6505+ }
6506+
6507+ if ( 'AUDIO ' === $ tag || 'VIDEO ' === $ tag ) {
6508+ $ processor ->set_bookmark ( 'audio-video-parent ' );
6509+ }
6510+
6511+ $ processor ->set_bookmark ( 'resume ' );
6512+
6513+ $ sought = false ;
6514+
6515+ $ crossorigin = $ processor ->get_attribute ( 'crossorigin ' );
6516+
6517+ $ is_cross_origin = false ;
6518+
6519+ foreach ( $ cross_origin_tag_attributes [ $ tag ] as $ attr => $ is_srcset ) {
6520+ if ( $ is_srcset ) {
6521+ $ srcset = $ processor ->get_attribute ( $ attr );
6522+ if ( is_string ( $ srcset ) ) {
6523+ foreach ( explode ( ', ' , $ srcset ) as $ candidate ) {
6524+ $ candidate_url = strtok ( trim ( $ candidate ), ' ' );
6525+ if ( is_string ( $ candidate_url ) && '' !== $ candidate_url && ! str_starts_with ( $ candidate_url , $ site_url ) && ! str_starts_with ( $ candidate_url , '/ ' ) ) {
6526+ $ is_cross_origin = true ;
6527+ break ;
6528+ }
6529+ }
6530+ }
6531+ } else {
6532+ $ url = $ processor ->get_attribute ( $ attr );
6533+ if ( is_string ( $ url ) && ! str_starts_with ( $ url , $ site_url ) && ! str_starts_with ( $ url , '/ ' ) ) {
6534+ $ is_cross_origin = true ;
6535+ }
6536+ }
6537+
6538+ if ( $ is_cross_origin ) {
6539+ break ;
6540+ }
6541+ }
6542+
6543+ if ( $ is_cross_origin && ! is_string ( $ crossorigin ) ) {
6544+ if ( 'SOURCE ' === $ tag ) {
6545+ $ sought = $ processor ->seek ( 'audio-video-parent ' );
6546+
6547+ if ( $ sought ) {
6548+ $ processor ->set_attribute ( 'crossorigin ' , 'anonymous ' );
6549+ }
6550+ } else {
6551+ $ processor ->set_attribute ( 'crossorigin ' , 'anonymous ' );
6552+ }
6553+
6554+ if ( $ sought ) {
6555+ $ processor ->seek ( 'resume ' );
6556+ $ processor ->release_bookmark ( 'audio-video-parent ' );
6557+ }
6558+ }
6559+ }
6560+
6561+ return $ processor ->get_updated_html ();
6562+ }
6563+
0 commit comments