if ($this->cfg_js_defer === 2) {
// Drop type attribute from $attrs
$attrs = Utility::remove_attr( $attrs, 'type' );
// Replace DOMContentLoaded
$con = str_replace('DOMContentLoaded', 'DOMContentLiteSpeedLoaded', $con);
return '<script' . $attrs . ' type="litespeed/javascript">' . $con . '</script>';
// return '<script' . $attrs . ' type="litespeed/javascript" src="data:text/javascript;base64,' . base64_encode( $con ) . '"></script>';
// return '<script' . $attrs . ' type="litespeed/javascript">' . $con . '</script>';
return '<script' . $attrs . ' src="data:text/javascript;base64,' . base64_encode($con) . '" defer></script>';
* Replace ESI to JS inline var (mainly used to avoid nonce timeout)
private function _preserve_esi( $con ) {
$esi_placeholder_list = $this->cls('ESI')->contain_preserve_esi($con);
if (!$esi_placeholder_list) {
foreach ($esi_placeholder_list as $esi_placeholder) {
$js_var = '__litespeed_var_' . self::$_var_i++ . '__';
$con = str_replace($esi_placeholder, $js_var, $con);
$this->_var_preserve_js[] = $js_var . '=' . $esi_placeholder;
* Parse css src and remove to-be-removed css
* @return array All the src & related raw html list
private function _parse_css() {
$excludes = apply_filters('litespeed_optimize_css_excludes', $this->conf(self::O_OPTM_CSS_EXC));
$ucss_file_exc_inline = apply_filters('litespeed_optimize_ucss_file_exc_inline', $this->conf(self::O_OPTM_UCSS_FILE_EXC_INLINE));
// Append dummy css to exclude list
$excludes[] = 'litespeed-dummy.css';
$combine_ext_inl = $this->conf(self::O_OPTM_CSS_COMB_EXT_INL);
if (!apply_filters('litespeed_optm_css_comb_ext_inl', true)) {
self::debug2('css_comb_ext_inl bypassed via litespeed_optm_css_comb_ext_inl filter');
$combine_ext_inl = false;
$css_to_be_removed = apply_filters('litespeed_optm_css_to_be_removed', array());
// $dom = new \PHPHtmlParser\Dom;
// $dom->load( $content );return $val;
// $items = $dom->find( 'link' );
// V7 added: (?:\r\n?|\n?) to fix replacement leaving empty new line
array( '#<!--.*-->(?:\r\n?|\n?)#sU', '#<script([^>]*)>.*</script>(?:\r\n?|\n?)#isU', '#<noscript([^>]*)>.*</noscript>(?:\r\n?|\n?)#isU' ),
preg_match_all('#<link ([^>]+)/?>|<style([^>]*)>([^<]+)</style>(?:\r\n?|\n?)#isU', $content, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
// to avoid multiple replacement
if (in_array($match[0], $html_list)) {
if ($exclude = Utility::str_hit_array($match[0], $excludes)) {
self::debug2('_parse_css bypassed exclude ' . $exclude);
if (strpos($match[0], '<link') === 0) {
$attrs = Utility::parse_attr($match[1]);
if (empty($attrs['rel']) || $attrs['rel'] !== 'stylesheet') {
if (empty($attrs['href'])) {
// Check if need to remove this css
if (Utility::str_hit_array($attrs['href'], $css_to_be_removed)) {
self::debug('rm css snippet ' . $attrs['href']);
// Delete this css snippet from orig html
$this->content = str_replace($match[0], '', $this->content);
// Check if need to inline this css file
if ($this->conf(self::O_OPTM_UCSS) && Utility::str_hit_array($attrs['href'], $ucss_file_exc_inline)) {
self::debug('ucss_file_exc_inline hit ' . $attrs['href']);
// Replace this css to inline from orig html
$inline_script = '<style>' . $this->__optimizer->load_file($attrs['href']) . '</style>';
$this->content = str_replace($match[0], $inline_script, $this->content);
// Check Google fonts hit
if (strpos($attrs['href'], 'fonts.googleapis.com') !== false) {
* For async gg fonts, will add webfont into head, hence remove it from buffer and store the matches to use later
* @since 3.0 For font display optm, need to parse google fonts URL too
if (!in_array($attrs['href'], $this->_ggfonts_urls)) {
$this->_ggfonts_urls[] = $attrs['href'];
if ($this->cfg_ggfonts_rm || $this->cfg_ggfonts_async) {
self::debug('rm css snippet [Google fonts] ' . $attrs['href']);
$this->content = str_replace($match[0], '', $this->content);
if (isset($attrs['data-optimized'])) {
// $this_src_arr[ 'exc' ] = true;
} elseif (!empty($attrs['data-no-optimize'])) {
// $this_src_arr[ 'exc' ] = true;
$is_internal = Utility::is_internal_file($attrs['href']);
$ext_excluded = !$combine_ext_inl && !$is_internal;
self::debug2('Bypassed due to external link');
if ($this->cfg_css_async) {
$snippet = $this->_async_css($match[0]);
if ($snippet != $match[0]) {
$this->content = str_replace($match[0], $snippet, $this->content);
if (!empty($attrs['media']) && $attrs['media'] !== 'all') {
$this_src_arr['media'] = $attrs['media'];
$this_src_arr['src'] = $attrs['href'];
self::debug2('Bypassed due to inline');
$attrs = Utility::parse_attr($match[2]);
if (!empty($attrs['data-no-optimize'])) {
if (!empty($attrs['media']) && $attrs['media'] !== 'all') {
$this_src_arr['media'] = $attrs['media'];
$this_src_arr['inl'] = true;
$this_src_arr['src'] = $match[3];
$src_list[] = $this_src_arr;
$html_list[] = $match[0];
return array( $src_list, $html_list );
* Replace css to async loaded css
private function _async_css_list( $html_list, $src_list ) {
foreach ($html_list as $k => $ori) {
if (!empty($src_list[$k]['inl'])) {
$html_list[$k] = $this->_async_css($ori);
private function _async_css( $ori ) {
if (strpos($ori, 'data-asynced') !== false) {
self::debug2('bypass: attr data-asynced exist');
if (strpos($ori, 'data-no-async') !== false) {
self::debug2('bypass: attr api data-no-async');
$v = str_replace('stylesheet', 'preload', $ori);
$v = str_replace('<link', '<link data-asynced="1" as="style" onload="this.onload=null;this.rel=\'stylesheet\'" ', $v);
// Append to noscript content
if (!defined('LITESPEED_GUEST_OPTM') && !$this->conf(self::O_OPTM_NOSCRIPT_RM)) {
$v .= '<noscript>' . preg_replace('/ id=\'[\w-]+\' /U', ' ', $ori) . '</noscript>';
private function _js_defer( $ori, $src ) {
$ori = Utility::remove_attr( $ori, 'async' );
if (strpos($ori, 'defer') !== false) {
if (strpos($ori, 'data-deferred') !== false) {
self::debug2('bypass: attr data-deferred exist');
if (strpos($ori, 'data-no-defer') !== false) {
self::debug2('bypass: attr api data-no-defer');
* Exclude JS from setting
if (Utility::str_hit_array($src, $this->cfg_js_defer_exc)) {
self::debug('js defer exclude ' . $src);
if ($this->cfg_js_defer === 2 || Utility::str_hit_array($src, $this->cfg_js_delay_inc)) {
$ori = Utility::remove_attr( $ori, 'type' );
return str_replace(' src=', ' type="litespeed/javascript" data-src=', $ori);
return str_replace('></script>', ' defer data-deferred="1"></script>', $ori);
* Delay JS for included setting
private function _js_delay( $ori, $src ) {
$ori = Utility::remove_attr( $ori, 'async' );
if (strpos($ori, 'defer') !== false) {
if (strpos($ori, 'data-deferred') !== false) {
self::debug2('bypass: attr data-deferred exist');
if (strpos($ori, 'data-no-defer') !== false) {
self::debug2('bypass: attr api data-no-defer');
if (!Utility::str_hit_array($src, $this->cfg_js_delay_inc)) {
$ori = Utility::remove_attr( $ori, 'type' );
return str_replace(' src=', ' type="litespeed/javascript" data-src=', $ori);