MDL-55751 themes: remove the css chunker
authorBas Brands <bas@moodle.com>
Mon, 5 Aug 2019 11:37:14 +0000 (13:37 +0200)
committerBas Brands <bas@moodle.com>
Mon, 9 Sep 2019 09:18:05 +0000 (11:18 +0200)
The css chunker is a workaround a browser bug for old IE versions

lib/csslib.php
theme/styles.php
theme/styles_debug.php

index 669ca97..67c633a 100644 (file)
@@ -39,13 +39,9 @@ if (!defined('THEME_DESIGNER_CACHE_LIFETIME')) {
  *
  * @param theme_config $theme The theme that the CSS belongs to.
  * @param string $csspath The path to store the CSS at.
- * @param string $csscontent the complete CSS in one string
- * @param bool $chunk If set to true these files will be chunked to ensure
- *      that no one file contains more than 4095 selectors.
- * @param string $chunkurl If the CSS is be chunked then we need to know the URL
- *      to use for the chunked files.
+ * @param string $csscontent the complete CSS in one string.
  */
-function css_store_css(theme_config $theme, $csspath, $csscontent, $chunk = false, $chunkurl = null) {
+function css_store_css(theme_config $theme, $csspath, $csscontent) {
     global $CFG;
 
     clearstatcache();
@@ -60,24 +56,6 @@ function css_store_css(theme_config $theme, $csspath, $csscontent, $chunk = fals
     // First up write out the single file for all those using decent browsers.
     css_write_file($csspath, $csscontent);
 
-    if ($chunk) {
-        // If we need to chunk the CSS for browsers that are sub-par.
-        $css = css_chunk_by_selector_count($csscontent, $chunkurl);
-        $files = count($css);
-        $count = 1;
-        foreach ($css as $content) {
-            if ($count === $files) {
-                // If there is more than one file and this IS the last file.
-                $filename = preg_replace('#\.css$#', '.0.css', $csspath);
-            } else {
-                // If there is more than one file and this is not the last file.
-                $filename = preg_replace('#\.css$#', '.'.$count.'.css', $csspath);
-            }
-            $count++;
-            css_write_file($filename, $content);
-        }
-    }
-
     ignore_user_abort(false);
     if (connection_aborted()) {
         die;
@@ -101,179 +79,6 @@ function css_write_file($filename, $content) {
     }
 }
 
-/**
- * Takes CSS and chunks it if the number of selectors within it exceeds $maxselectors.
- *
- * The chunking will not split a group of selectors, or a media query. That means that
- * if n > $maxselectors and there are n selectors grouped together,
- * they will not be chunked and you could end up with more selectors than desired.
- * The same applies for a media query that has more than n selectors.
- *
- * Also, as we do not split group of selectors or media queries, the chunking might
- * not be as optimal as it could be, having files with less selectors than it could
- * potentially contain.
- *
- * String functions used here are not compliant with unicode characters. But that is
- * not an issue as the syntax of CSS is using ASCII codes. Even if we have unicode
- * characters in comments, or in the property 'content: ""', it will behave correcly.
- *
- * Please note that this strips out the comments if chunking happens.
- *
- * @param string $css The CSS to chunk.
- * @param string $importurl The URL to use for import statements.
- * @param int $maxselectors The number of selectors to limit a chunk to.
- * @param int $buffer Not used any more.
- * @return array An array of CSS chunks.
- */
-function css_chunk_by_selector_count($css, $importurl, $maxselectors = 4095, $buffer = 50) {
-
-    // Check if we need to chunk this CSS file.
-    $count = substr_count($css, ',') + substr_count($css, '{');
-    if ($count < $maxselectors) {
-        // The number of selectors is less then the max - we're fine.
-        return array($css);
-    }
-
-    $chunks = array();                  // The final chunks.
-    $offsets = array();                 // The indexes to chunk at.
-    $offset = 0;                        // The current offset.
-    $selectorcount = 0;                 // The number of selectors since the last split.
-    $lastvalidoffset = 0;               // The last valid index to split at.
-    $lastvalidoffsetselectorcount = 0;  // The number of selectors used at the time were could split.
-    $inrule = 0;                        // The number of rules we are in, should not be greater than 1.
-    $inmedia = false;                   // Whether or not we are in a media query.
-    $mediacoming = false;               // Whether or not we are expeting a media query.
-    $currentoffseterror = null;         // Not null when we have recorded an error for the current split.
-    $offseterrors = array();            // The offsets where we found errors.
-
-    // Remove the comments. Because it's easier, safer and probably a lot of other good reasons.
-    $css = preg_replace('#/\*(.*?)\*/#s', '', $css);
-    $strlen = strlen($css);
-
-    // Walk through the CSS content character by character.
-    for ($i = 1; $i <= $strlen; $i++) {
-        $char = $css[$i - 1];
-        $offset = $i;
-
-        // Is that a media query that I see coming towards us?
-        if ($char === '@') {
-            if (!$inmedia && substr($css, $offset, 5) === 'media') {
-                $mediacoming = true;
-            }
-        }
-
-        // So we are entering a rule or a media query...
-        if ($char === '{') {
-            if ($mediacoming) {
-                $inmedia = true;
-                $mediacoming = false;
-            } else {
-                $inrule++;
-                $selectorcount++;
-            }
-        }
-
-        // Let's count the number of selectors, but only if we are not in a rule, or in
-        // the definition of a media query, as they can contain commas too.
-        if (!$mediacoming && !$inrule && $char === ',') {
-            $selectorcount++;
-        }
-
-        // We reached the end of something.
-        if ($char === '}') {
-            // Oh, we are in a media query.
-            if ($inmedia) {
-                if (!$inrule) {
-                    // This is the end of the media query.
-                    $inmedia = false;
-                } else {
-                    // We were in a rule, in the media query.
-                    $inrule--;
-                }
-            } else {
-                $inrule--;
-                // Handle stupid broken CSS where there are too many } brackets,
-                // as this can cause it to break (with chunking) where it would
-                // coincidentally have worked otherwise.
-                if ($inrule < 0) {
-                    $inrule = 0;
-                }
-            }
-
-            // We are not in a media query, and there is no pending rule, it is safe to split here.
-            if (!$inmedia && !$inrule) {
-                $lastvalidoffset = $offset;
-                $lastvalidoffsetselectorcount = $selectorcount;
-            }
-        }
-
-        // Alright, this is splitting time...
-        if ($selectorcount > $maxselectors) {
-            if (!$lastvalidoffset) {
-                // We must have reached more selectors into one set than we were allowed. That means that either
-                // the chunk size value is too small, or that we have a gigantic group of selectors, or that a media
-                // query contains more selectors than the chunk size. We have to ignore this because we do not
-                // support split inside a group of selectors or media query.
-                if ($currentoffseterror === null) {
-                    $currentoffseterror = $offset;
-                    $offseterrors[] = $currentoffseterror;
-                }
-            } else {
-                // We identify the offset to split at and reset the number of selectors found from there.
-                $offsets[] = $lastvalidoffset;
-                $selectorcount = $selectorcount - $lastvalidoffsetselectorcount;
-                $lastvalidoffset = 0;
-                $currentoffseterror = null;
-            }
-        }
-    }
-
-    // Report offset errors.
-    if (!empty($offseterrors)) {
-        debugging('Could not find a safe place to split at offset(s): ' . implode(', ', $offseterrors) . '. Those were ignored.',
-            DEBUG_DEVELOPER);
-    }
-
-    // Now that we have got the offets, we can chunk the CSS.
-    $offsetcount = count($offsets);
-    foreach ($offsets as $key => $index) {
-        $start = 0;
-        if ($key > 0) {
-            $start = $offsets[$key - 1];
-        }
-        // From somewhere up to the offset.
-        $chunks[] = substr($css, $start, $index - $start);
-    }
-    // Add the last chunk (if there is one), from the last offset to the end of the string.
-    if (end($offsets) != $strlen) {
-        $chunks[] = substr($css, end($offsets));
-    }
-
-    // The array $chunks now contains CSS split into perfect sized chunks.
-    // Import statements can only appear at the very top of a CSS file.
-    // Imported sheets are applied in the the order they are imported and
-    // are followed by the contents of the CSS.
-    // This is terrible for performance.
-    // It means we must put the import statements at the top of the last chunk
-    // to ensure that things are always applied in the correct order.
-    // This way the chunked files are included in the order they were chunked
-    // followed by the contents of the final chunk in the actual sheet.
-    $importcss = '';
-    $slashargs = strpos($importurl, '.php?') === false;
-    $parts = count($chunks);
-    for ($i = 1; $i < $parts; $i++) {
-        if ($slashargs) {
-            $importcss .= "@import url({$importurl}/chunk{$i});\n";
-        } else {
-            $importcss .= "@import url({$importurl}&chunk={$i});\n";
-        }
-    }
-    $importcss .= end($chunks);
-    $chunks[key($chunks)] = $importcss;
-
-    return $chunks;
-}
-
 /**
  * Sends a cached CSS file
  *
index 9f3ce5f..1f36323 100644 (file)
@@ -44,12 +44,6 @@ if ($slashargument = min_get_slash_argument()) {
         $usesvg = true;
     }
 
-    $chunk = null;
-    if (preg_match('#/(chunk(\d+)(/|$))#', $slashargument, $matches)) {
-        $chunk = (int)$matches[2];
-        $slashargument = str_replace($matches[1], '', $slashargument);
-    }
-
     list($themename, $rev, $type) = explode('/', $slashargument, 3);
     $themename = min_clean_param($themename, 'SAFEDIR');
     $rev       = min_clean_param($rev, 'RAW');
@@ -59,7 +53,6 @@ if ($slashargument = min_get_slash_argument()) {
     $themename = min_optional_param('theme', 'standard', 'SAFEDIR');
     $rev       = min_optional_param('rev', 0, 'RAW');
     $type      = min_optional_param('type', 'all', 'SAFEDIR');
-    $chunk     = min_optional_param('chunk', null, 'INT');
     $usesvg    = (bool)min_optional_param('svg', '1', 'INT');
 }
 
@@ -74,12 +67,7 @@ if (!is_null($themesubrev)) {
 }
 
 // Check that type fits into the expected values.
-if ($type === 'editor') {
-    // The editor CSS is never chunked.
-    $chunk = null;
-} else if ($type === 'all' || $type === 'all-rtl') {
-    // We're fine.
-} else {
+if (!in_array($type, ['all', 'all-rtl', 'editor'])) {
     css_send_css_not_found();
 }
 
@@ -94,17 +82,7 @@ if (file_exists("$CFG->dirroot/theme/$themename/config.php")) {
 
 $candidatedir = "$CFG->localcachedir/theme/$rev/$themename/css";
 $candidatesheet = "{$candidatedir}/" . theme_styles_get_filename($type, $themesubrev, $usesvg);
-$chunkedcandidatesheet = "{$candidatedir}/" . theme_styles_get_filename($type, $themesubrev, $usesvg, $chunk);
-$etag = theme_styles_get_etag($themename, $rev, $type, $themesubrev, $usesvg, $chunk);
-
-if (file_exists($chunkedcandidatesheet)) {
-    if (!empty($_SERVER['HTTP_IF_NONE_MATCH']) || !empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
-        // We do not actually need to verify the etag value because our files
-        // never change in cache because we increment the rev counter.
-        css_send_unmodified(filemtime($chunkedcandidatesheet), $etag);
-    }
-    css_send_cached_css($chunkedcandidatesheet, $etag);
-}
+$etag = theme_styles_get_etag($themename, $rev, $type, $themesubrev, $usesvg);
 
 // Ok, now we need to start normal moodle script, we need to load all libs and $DB.
 define('ABORT_AFTER_CONFIG_CANCEL', true);
@@ -133,8 +111,7 @@ if ($themerev <= 0 or $themerev != $rev or $themesubrev != $currentthemesubrev)
 
     $candidatedir = "$CFG->localcachedir/theme/$rev/$themename/css";
     $candidatesheet = "{$candidatedir}/" . theme_styles_get_filename($type, $themesubrev, $usesvg);
-    $chunkedcandidatesheet = "{$candidatedir}/" . theme_styles_get_filename($type, $themesubrev, $usesvg, $chunk);
-    $etag = theme_styles_get_etag($themename, $rev, $type, $themesubrev, $usesvg, $chunk);
+    $etag = theme_styles_get_etag($themename, $rev, $type, $themesubrev, $usesvg);
 }
 
 make_localcache_directory('theme', false);
@@ -143,7 +120,7 @@ if ($type === 'editor') {
     $csscontent = $theme->get_css_content_editor();
 
     if ($cache) {
-        css_store_css($theme, $candidatesheet, $csscontent, false);
+        css_store_css($theme, $candidatesheet, $csscontent);
         css_send_cached_css($candidatesheet, $etag);
     } else {
         css_send_uncached_css($csscontent);
@@ -179,11 +156,10 @@ $lock = $lockfactory->get_lock($themename, $locktimeout);
 
 if ($sendaftergeneration || $lock) {
     // Either the lock was successful, or the lock was unsuccessful but the content *must* be sent.
-    if (!file_exists($chunkedcandidatesheet)) {
-        // The content does not exist locally.
-        // Generate and save it.
-        $candidatesheet = theme_styles_generate_and_store($theme, $rev, $themesubrev, $candidatedir);
-    }
+
+    // The content does not exist locally.
+    // Generate and save it.
+    $candidatesheet = theme_styles_generate_and_store($theme, $rev, $themesubrev, $candidatedir);
 
     if ($lock) {
         $lock->release();
@@ -195,10 +171,6 @@ if ($sendaftergeneration || $lock) {
             // let's ignore legacy IE breakage here too.
             css_send_uncached_css(file_get_contents($candidatesheet));
 
-        } else if ($chunk !== null and file_exists($chunkedcandidatesheet)) {
-            // Greetings stupid legacy IEs!
-            css_send_cached_css($chunkedcandidatesheet, $etag);
-
         } else {
             // Real browsers - this is the expected result!
             css_send_cached_css($candidatesheet, $etag);
@@ -213,7 +185,7 @@ if ($sendaftergeneration || $lock) {
  * @param   int             $rev The theme revision
  * @param   int             $themesubrev The theme sub-revision
  * @param   string          $candidatedir The directory that it should be stored in
- * @return  string          The path that the primary (non-chunked) CSS was written to
+ * @return  string          The path that the primary CSS was written to
  */
 function theme_styles_generate_and_store($theme, $rev, $themesubrev, $candidatedir) {
     global $CFG;
@@ -232,35 +204,17 @@ function theme_styles_generate_and_store($theme, $rev, $themesubrev, $candidated
     }
 
     // Determine the candidatesheet path.
-    // Note: Do not pass any value for chunking as this is calcualted during css storage.
     $candidatesheet = "{$candidatedir}/" . theme_styles_get_filename($type, $themesubrev, $theme->use_svg_icons());
 
-    // Determine the chunking URL.
-    // Note, this will be removed when support for IE9 is removed.
-    $relroot = preg_replace('|^http.?://[^/]+|', '', $CFG->wwwroot);
-    if (!empty(min_get_slash_argument())) {
-        if ($theme->use_svg_icons()) {
-            $chunkurl = "{$relroot}/theme/styles.php/{$theme->name}/{$rev}/$type";
-        } else {
-            $chunkurl = "{$relroot}/theme/styles.php/_s/{$theme->name}/{$rev}/$type";
-        }
-    } else {
-        if ($theme->use_svg_icons()) {
-            $chunkurl = "{$relroot}/theme/styles.php?theme={$theme->name}&rev={$rev}&type=$type";
-        } else {
-            $chunkurl = "{$relroot}/theme/styles.php?theme={$theme->name}&rev={$rev}&type=$type&svg=0";
-        }
-    }
-
     // Store the CSS.
-    css_store_css($theme, $candidatesheet, $csscontent, true, $chunkurl);
+    css_store_css($theme, $candidatesheet, $csscontent);
 
     // Store the fallback CSS in the temp directory.
     // This file is used as a fallback when waiting for a theme to compile and is not versioned in any way.
     $fallbacksheet = make_temp_directory("theme/{$theme->name}")
         . "/"
         . theme_styles_get_filename($type, 0, $theme->use_svg_icons());
-    css_store_css($theme, $fallbacksheet, $csscontent, true, $chunkurl);
+    css_store_css($theme, $fallbacksheet, $csscontent);
 
     // Delete older revisions from localcache.
     $themecachedirs = glob("{$CFG->localcachedir}/theme/*", GLOB_ONLYDIR);
@@ -318,14 +272,12 @@ function theme_styles_fallback_content($theme) {
  * @param   string  $type The requested sheet type
  * @param   int     $themesubrev The theme sub-revision
  * @param   bool    $usesvg Whether SVGs are allowed
- * @param   int     $chunk The chunk number if specified
  * @return  string  The filename for this sheet
  */
-function theme_styles_get_filename($type, $themesubrev = 0, $usesvg = true, $chunk = null) {
+function theme_styles_get_filename($type, $themesubrev = 0, $usesvg = true) {
     $filename = $type;
     $filename .= ($themesubrev > 0) ? "_{$themesubrev}" : '';
     $filename .= $usesvg ? '' : '-nosvg';
-    $filename .= $chunk ? ".{$chunk}" : '';
 
     return "{$filename}.css";
 }
@@ -338,19 +290,14 @@ function theme_styles_get_filename($type, $themesubrev = 0, $usesvg = true, $chu
  * @param   string  $type The requested sheet type
  * @param   int     $themesubrev The theme sub-revision
  * @param   bool    $usesvg Whether SVGs are allowed
- * @param   int     $chunk The chunk number if specified
  * @return  string  The etag to use for this request
  */
-function theme_styles_get_etag($themename, $rev, $type, $themesubrev, $usesvg, $chunk) {
+function theme_styles_get_etag($themename, $rev, $type, $themesubrev, $usesvg) {
     $etag = [$rev, $themename, $type, $themesubrev];
 
     if (!$usesvg) {
         $etag[] = 'nosvg';
     }
 
-    if ($chunk) {
-        $etag[] = "chunk{$chunk}";
-    }
-
     return sha1(implode('/', $etag));
 }
index 0a61c9f..83bb7c3 100644 (file)
@@ -36,7 +36,6 @@ $type      = optional_param('type', '', PARAM_SAFEDIR);
 $subtype   = optional_param('subtype', '', PARAM_SAFEDIR);
 $sheet     = optional_param('sheet', '', PARAM_SAFEDIR);
 $usesvg    = optional_param('svg', 1, PARAM_BOOL);
-$chunk     = optional_param('chunk', null, PARAM_INT);
 $rtl       = optional_param('rtl', false, PARAM_BOOL);
 
 if (file_exists("$CFG->dirroot/theme/$themename/config.php")) {
@@ -56,9 +55,6 @@ if ($type === 'editor') {
     css_send_uncached_css($csscontent);
 }
 
-$chunkurl = new moodle_url('/theme/styles_debug.php', array('theme' => $themename,
-    'type' => $type, 'subtype' => $subtype, 'sheet' => $sheet, 'usesvg' => $usesvg, 'rtl' => $rtl));
-
 // We need some kind of caching here because otherwise the page navigation becomes
 // way too slow in theme designer mode. Feel free to create full cache definition later...
 $key = "$type $subtype $sheet $usesvg $rtl";
@@ -66,13 +62,6 @@ $cache = cache::make_from_params(cache_store::MODE_APPLICATION, 'core', 'themede
 if ($content = $cache->get($key)) {
     if ($content['created'] > time() - THEME_DESIGNER_CACHE_LIFETIME) {
         $csscontent = $content['data'];
-
-        // We need to chunk the content.
-        if ($chunk !== null) {
-            $chunks = css_chunk_by_selector_count($csscontent, $chunkurl->out(false));
-            $csscontent = ($chunk === 0) ? end($chunks) : $chunks[$chunk - 1];
-        }
-
         css_send_uncached_css($csscontent);
     }
 }
@@ -80,13 +69,4 @@ if ($content = $cache->get($key)) {
 $csscontent = $theme->get_css_content_debug($type, $subtype, $sheet);
 $cache->set($key, array('data' => $csscontent, 'created' => time()));
 
-// We need to chunk the content.
-if ($chunk !== null) {
-    // The chunks are ordered so that the last chunk is the one containing the @import, and so
-    // the first one to be included. All the other chunks are set in the array before that one.
-    // See {@link css_chunk_by_selector_count()} for more details.
-    $chunks = css_chunk_by_selector_count($csscontent, $chunkurl->out(false));
-    $csscontent = ($chunk === 0) ? end($chunks) : $chunks[$chunk - 1];
-}
-
 css_send_uncached_css($csscontent);