summaryrefslogtreecommitdiff
path: root/includes/libs/CSSMin.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/libs/CSSMin.php')
-rw-r--r--includes/libs/CSSMin.php151
1 files changed, 111 insertions, 40 deletions
diff --git a/includes/libs/CSSMin.php b/includes/libs/CSSMin.php
index c69e79f5..ffe26a96 100644
--- a/includes/libs/CSSMin.php
+++ b/includes/libs/CSSMin.php
@@ -32,12 +32,9 @@ class CSSMin {
/* Constants */
/**
- * Maximum file size to still qualify for in-line embedding as a data-URI
- *
- * 24,576 is used because Internet Explorer has a 32,768 byte limit for data URIs,
- * which when base64 encoded will result in a 1/3 increase in size.
+ * Internet Explorer data URI length limit. See encodeImageAsDataURI().
*/
- const EMBED_SIZE_LIMIT = 24576;
+ const DATA_URI_SIZE_LIMIT = 32768;
const URL_REGEX = 'url\(\s*[\'"]?(?P<file>[^\?\)\'"]*?)(?P<query>\?[^\)\'"]*?|)[\'"]?\s*\)';
const EMBED_REGEX = '\/\*\s*\@embed\s*\*\/';
const COMMENT_REGEX = '\/\*.*?\*\/';
@@ -100,33 +97,68 @@ class CSSMin {
}
/**
- * Encode an image file as a base64 data URI.
- * If the image file has a suitable MIME type and size, encode it as a
- * base64 data URI. Return false if the image type is unfamiliar or exceeds
- * the size limit.
+ * Encode an image file as a data URI.
+ *
+ * If the image file has a suitable MIME type and size, encode it as a data URI, base64-encoded
+ * for binary files or just percent-encoded otherwise. Return false if the image type is
+ * unfamiliar or file exceeds the size limit.
*
* @param string $file Image file to encode.
* @param string|null $type File's MIME type or null. If null, CSSMin will
* try to autodetect the type.
- * @param int|bool $sizeLimit If the size of the target file is greater than
- * this value, decline to encode the image file and return false
- * instead. If $sizeLimit is false, no limit is enforced.
- * @return string|bool: Image contents encoded as a data URI or false.
+ * @param bool $ie8Compat By default, a data URI will only be produced if it can be made short
+ * enough to fit in Internet Explorer 8 (and earlier) URI length limit (32,768 bytes). Pass
+ * `false` to remove this limitation.
+ * @return string|bool Image contents encoded as a data URI or false.
*/
- public static function encodeImageAsDataURI( $file, $type = null,
- $sizeLimit = self::EMBED_SIZE_LIMIT
- ) {
- if ( $sizeLimit !== false && filesize( $file ) >= $sizeLimit ) {
+ public static function encodeImageAsDataURI( $file, $type = null, $ie8Compat = true ) {
+ // Fast-fail for files that definitely exceed the maximum data URI length
+ if ( $ie8Compat && filesize( $file ) >= self::DATA_URI_SIZE_LIMIT ) {
return false;
}
+
if ( $type === null ) {
$type = self::getMimeType( $file );
}
if ( !$type ) {
return false;
}
- $data = base64_encode( file_get_contents( $file ) );
- return 'data:' . $type . ';base64,' . $data;
+
+ return self::encodeStringAsDataURI( file_get_contents( $file ), $type, $ie8Compat );
+ }
+
+ /**
+ * Encode file contents as a data URI with chosen MIME type.
+ *
+ * The URI will be base64-encoded for binary files or just percent-encoded otherwise.
+ *
+ * @since 1.25
+ *
+ * @param string $contents File contents to encode.
+ * @param string $type File's MIME type.
+ * @param bool $ie8Compat See encodeImageAsDataURI().
+ * @return string|bool Image contents encoded as a data URI or false.
+ */
+ public static function encodeStringAsDataURI( $contents, $type, $ie8Compat = true ) {
+ // Try #1: Non-encoded data URI
+ // The regular expression matches ASCII whitespace and printable characters.
+ if ( preg_match( '/^[\r\n\t\x20-\x7e]+$/', $contents ) ) {
+ // Do not base64-encode non-binary files (sane SVGs).
+ // (This often produces longer URLs, but they compress better, yielding a net smaller size.)
+ $uri = 'data:' . $type . ',' . rawurlencode( $contents );
+ if ( !$ie8Compat || strlen( $uri ) < self::DATA_URI_SIZE_LIMIT ) {
+ return $uri;
+ }
+ }
+
+ // Try #2: Encoded data URI
+ $uri = 'data:' . $type . ';base64,' . base64_encode( $contents );
+ if ( !$ie8Compat || strlen( $uri ) < self::DATA_URI_SIZE_LIMIT ) {
+ return $uri;
+ }
+
+ // A data URI couldn't be produced
+ return false;
}
/**
@@ -248,9 +280,12 @@ class CSSMin {
);
if ( $embedData ) {
+ // Remember the occurring MIME types to avoid fallbacks when embedding some files.
+ $mimeTypes = array();
+
$ruleWithEmbedded = preg_replace_callback(
$pattern,
- function ( $match ) use ( $embedAll, $local, $remote ) {
+ function ( $match ) use ( $embedAll, $local, $remote, &$mimeTypes ) {
$embed = $embedAll || $match['embed'];
$embedded = CSSMin::remapOne(
$match['file'],
@@ -260,21 +295,35 @@ class CSSMin {
$embed
);
+ $url = $match['file'] . $match['query'];
+ $file = $local . $match['file'];
+ if (
+ !CSSMin::isRemoteUrl( $url ) && !CSSMin::isLocalUrl( $url )
+ && file_exists( $file )
+ ) {
+ $mimeTypes[ CSSMin::getMimeType( $file ) ] = true;
+ }
+
return CSSMin::buildUrlValue( $embedded );
},
$rule
);
+
+ // Are all referenced images SVGs?
+ $needsEmbedFallback = $mimeTypes !== array( 'image/svg+xml' => true );
}
- if ( $embedData && $ruleWithEmbedded !== $ruleWithRemapped ) {
- // Build 2 CSS properties; one which uses a base64 encoded data URI in place
- // of the @embed comment to try and retain line-number integrity, and the
- // other with a remapped an versioned URL and an Internet Explorer hack
+ if ( !$embedData || $ruleWithEmbedded === $ruleWithRemapped ) {
+ // We're not embedding anything, or we tried to but the file is not embeddable
+ return $ruleWithRemapped;
+ } elseif ( $embedData && $needsEmbedFallback ) {
+ // Build 2 CSS properties; one which uses a data URI in place of the @embed comment, and
+ // the other with a remapped and versioned URL with an Internet Explorer 6 and 7 hack
// making it ignored in all browsers that support data URIs
return "$ruleWithEmbedded;$ruleWithRemapped!ie";
} else {
- // No reason to repeat twice
- return $ruleWithRemapped;
+ // Look ma, no fallbacks! This is for files which IE 6 and 7 don't support anyway: SVG.
+ return $ruleWithEmbedded;
}
}, $source );
@@ -289,6 +338,34 @@ class CSSMin {
}
/**
+ * Is this CSS rule referencing a remote URL?
+ *
+ * @private Until we require PHP 5.5 and we can access self:: from closures.
+ * @param string $maybeUrl
+ * @return bool
+ */
+ public static function isRemoteUrl( $maybeUrl ) {
+ if ( substr( $maybeUrl, 0, 2 ) === '//' || parse_url( $maybeUrl, PHP_URL_SCHEME ) ) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Is this CSS rule referencing a local URL?
+ *
+ * @private Until we require PHP 5.5 and we can access self:: from closures.
+ * @param string $maybeUrl
+ * @return bool
+ */
+ public static function isLocalUrl( $maybeUrl ) {
+ if ( $maybeUrl !== '' && $maybeUrl[0] === '/' && !self::isRemoteUrl( $maybeUrl ) ) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Remap or embed a CSS URL path.
*
* @param string $file URL to remap/embed
@@ -302,22 +379,16 @@ class CSSMin {
// The full URL possibly with query, as passed to the 'url()' value in CSS
$url = $file . $query;
- // Skip fully-qualified and protocol-relative URLs and data URIs
- if ( substr( $url, 0, 2 ) === '//' || parse_url( $url, PHP_URL_SCHEME ) ) {
- return $url;
+ // Expand local URLs with absolute paths like /w/index.php to possibly protocol-relative URL, if
+ // wfExpandUrl() is available. (This will not be the case if we're running outside of MW.)
+ if ( self::isLocalUrl( $url ) && function_exists( 'wfExpandUrl' ) ) {
+ return wfExpandUrl( $url, PROTO_RELATIVE );
}
- // URLs with absolute paths like /w/index.php need to be expanded
- // to absolute URLs but otherwise left alone
- if ( $url !== '' && $url[0] === '/' ) {
- // Replace the file path with an expanded (possibly protocol-relative) URL
- // ...but only if wfExpandUrl() is even available.
- // This will not be the case if we're running outside of MW
- if ( function_exists( 'wfExpandUrl' ) ) {
- return wfExpandUrl( $url, PROTO_RELATIVE );
- } else {
- return $url;
- }
+ // Pass thru fully-qualified and protocol-relative URLs and data URIs, as well as local URLs if
+ // we can't expand them.
+ if ( self::isRemoteUrl( $url ) || self::isLocalUrl( $url ) ) {
+ return $url;
}
if ( $local === false ) {