@@ -3736,7 +3736,26 @@ public function set_modifiable_text( string $plaintext_content ): bool {
3736
3736
* that previously worked. Resolve this by not sending `</script`
3737
3737
*/
3738
3738
if ( false !== stripos ( $ plaintext_content , '</script ' ) ) {
3739
- return false ;
3739
+ /*
3740
+ * JavaScript can be safely escaped.
3741
+ * Non-JavaScript script tags have unknown semantics.
3742
+ *
3743
+ * @todo consider applying to JSON and importmap script tags as well.
3744
+ */
3745
+ if ( $ this ->is_javascript_script_tag () ) {
3746
+ $ plaintext_content = preg_replace_callback (
3747
+ '~<(/?)(s)(cript)~i ' ,
3748
+ static function ( $ matches ) {
3749
+ $ escaped_s_char = 's ' === $ matches [2 ]
3750
+ ? '\u0073 '
3751
+ : '\u0053 ' ;
3752
+ return "< {$ matches [1 ]}{$ escaped_s_char }{$ matches [3 ]}" ;
3753
+ },
3754
+ $ plaintext_content
3755
+ );
3756
+ } else {
3757
+ return false ;
3758
+ }
3740
3759
}
3741
3760
3742
3761
$ this ->lexical_updates ['modifiable text ' ] = new WP_HTML_Text_Replacement (
@@ -3794,6 +3813,123 @@ static function ( $tag_match ) {
3794
3813
return false ;
3795
3814
}
3796
3815
3816
+ /**
3817
+ * Indicates if the currently matched tag is a JavaScript script tag.
3818
+ *
3819
+ * @see https://html.spec.whatwg.org/multipage/scripting.html#prepare-the-script-element
3820
+ *
3821
+ * @since {WP_VERSION}
3822
+ *
3823
+ * @return boolean True if the script tag will be evaluated as JavaScript.
3824
+ */
3825
+ public function is_javascript_script_tag (): bool {
3826
+ if ( 'SCRIPT ' !== $ this ->get_tag () || $ this ->get_namespace () !== 'html ' ) {
3827
+ return false ;
3828
+ }
3829
+
3830
+ /*
3831
+ * > If any of the following are true:
3832
+ * > - el has a type attribute whose value is the empty string;
3833
+ * > - el has no type attribute but it has a language attribute and that attribute's
3834
+ * > value is the empty string; or
3835
+ * > - el has neither a type attribute nor a language attribute,
3836
+ * > then let the script block's type string for this script element be "text/javascript".
3837
+ */
3838
+ $ type_attr = $ this ->get_attribute ( 'type ' );
3839
+ $ language_attr = $ this ->get_attribute ( 'language ' );
3840
+
3841
+ if ( true === $ type_attr || '' === $ type_attr ) {
3842
+ return true ;
3843
+ }
3844
+ if (
3845
+ null === $ type_attr && (
3846
+ true === $ language_attr ||
3847
+ '' === $ language_attr ||
3848
+ null === $ language_attr
3849
+ )
3850
+ ) {
3851
+ return true ;
3852
+ }
3853
+
3854
+ /*
3855
+ * > Otherwise, if el has a type attribute, then let the script block's type string be
3856
+ * > the value of that attribute with leading and trailing ASCII whitespace stripped.
3857
+ * > Otherwise, el has a non-empty language attribute; let the script block's type string
3858
+ * > be the concatenation of "text/" and the value of el's language attribute.
3859
+ */
3860
+ $ type_string = $ type_attr ? trim ( $ type_attr , " \t\f\r\n" ) : "text/ {$ language_attr }" ;
3861
+
3862
+ /*
3863
+ * > If the script block's type string is a JavaScript MIME type essence match, then
3864
+ * > set el's type to "classic".
3865
+ *
3866
+ * > A string is a JavaScript MIME type essence match if it is an ASCII case-insensitive
3867
+ * > match for one of the JavaScript MIME type essence strings.
3868
+
3869
+ * > A JavaScript MIME type is any MIME type whose essence is one of the following:
3870
+ * >
3871
+ * > - application/ecmascript
3872
+ * > - application/javascript
3873
+ * > - application/x-ecmascript
3874
+ * > - application/x-javascript
3875
+ * > - text/ecmascript
3876
+ * > - text/javascript
3877
+ * > - text/javascript1.0
3878
+ * > - text/javascript1.1
3879
+ * > - text/javascript1.2
3880
+ * > - text/javascript1.3
3881
+ * > - text/javascript1.4
3882
+ * > - text/javascript1.5
3883
+ * > - text/jscript
3884
+ * > - text/livescript
3885
+ * > - text/x-ecmascript
3886
+ * > - text/x-javascript
3887
+ *
3888
+ * @see https://mimesniff.spec.whatwg.org/#javascript-mime-type
3889
+ * @see https://mimesniff.spec.whatwg.org/#javascript-mime-type-essence-match
3890
+ */
3891
+ switch ( strtolower ( $ type_string ) ) {
3892
+ case 'application/ecmascript ' :
3893
+ case 'application/javascript ' :
3894
+ case 'application/x-ecmascript ' :
3895
+ case 'application/x-javascript ' :
3896
+ case 'text/ecmascript ' :
3897
+ case 'text/javascript ' :
3898
+ case 'text/javascript1.0 ' :
3899
+ case 'text/javascript1.1 ' :
3900
+ case 'text/javascript1.2 ' :
3901
+ case 'text/javascript1.3 ' :
3902
+ case 'text/javascript1.4 ' :
3903
+ case 'text/javascript1.5 ' :
3904
+ case 'text/jscript ' :
3905
+ case 'text/livescript ' :
3906
+ case 'text/x-ecmascript ' :
3907
+ case 'text/x-javascript ' :
3908
+ return true ;
3909
+
3910
+ /*
3911
+ * > Otherwise, if the script block's type string is an ASCII case-insensitive match for
3912
+ * > the string "module", then set el's type to "module".
3913
+ *
3914
+ * A module is evaluated as JavaScript
3915
+ */
3916
+ case 'module ' :
3917
+ return true ;
3918
+ }
3919
+
3920
+ /*
3921
+ * > - Otherwise, if the script block's type string is an ASCII case-insensitive match for
3922
+ * > the string "importmap", then set el's type to "importmap".
3923
+ *
3924
+ * An importmap is JSON and not evaluated as JavaScript. This case is not handled here.
3925
+ */
3926
+
3927
+ /*
3928
+ * > Otherwise, return. (No script is executed, and el's type is left as null.)
3929
+ */
3930
+ return false ;
3931
+ }
3932
+
3797
3933
/**
3798
3934
* Updates or creates a new attribute on the currently matched tag with the passed value.
3799
3935
*
0 commit comments