Permalink
Browse files

Merge pull request #110 from mobify/jazzcat-inline-scripts

Make Jazzcat use inline scripts as default emission method.
  • Loading branch information...
johnboxall committed Mar 6, 2013
2 parents 9a0702a + 5f8a362 commit dfc5162e8f24d4fca4dd6b5882f2d765c2400e4b
Showing with 99 additions and 17 deletions.
  1. +48 −17 api/combo.js
  2. +51 −0 tests/index.html
View
@@ -188,12 +188,12 @@ var ccDirectives = /^\s*(public|private|no-cache|no-store)\s*$/
, date
, expires;
- // If `max-age` and `date` are present, and no other no other cache
+ // If `max-age` and `date` are present, and no other no other cache
// directives exist, then we are stale if we are older.
if (cacheControl && (date = Date.parse(headers.date))) {
cacheControl = ccParse(cacheControl);
- if ((cacheControl['max-age']) &&
+ if ((cacheControl['max-age']) &&
(!cacheControl['private']) &&
(!cacheControl['no-store']) &&
(!cacheControl['no-cache'])) {
@@ -226,7 +226,7 @@ var $ = Mobify.$
, absolutify = document.createElement('a')
- , combineScripts = function($els) {
+ , combineScripts = function($els, opts) {
var $scripts = $els.filter(defaults.selector).add($els.find(defaults.selector)).remove()
, uncached = []
, combo = false
@@ -237,6 +237,7 @@ var $ = Mobify.$
if (!$scripts.length || !window.localStorage || !window.JSON) {
return $scripts;
}
+ opts = opts || {};
httpCache.load();
@@ -251,7 +252,8 @@ var $ = Mobify.$
this.removeAttribute(defaults.attribute);
this.className += ' x-combo';
- this.innerHTML = defaults.execCallback + "('" + url + "');";
+ this.innerHTML = defaults.execCallback + "('" + url + "', "
+ + (!!opts.forceDataURI) + ");";
});
if (!combo) {
@@ -280,19 +282,48 @@ var $ = Mobify.$
, combo = Mobify.combo = {
/**
- * Emit a <script> tag to execute the contents of `url` using
+ * Emit a <script> tag to execute the contents of `url` using
* `document.write`. Prefer loading contents from cache.
*/
- exec: function(url) {
- var resource;
-
- if (resource = httpCache.get(url, true)) {
- url = httpCache.utils.dataURI(resource);
+ exec: function(url, useDataURI) {
+ var resource = httpCache.get(url, true),
+ out;
+
+ if (!resource) {
+ out = 'src="' + url + '">';
+ } else {
+ out = 'data-orig-src="' + url + '"';
+
+ if (useDataURI) {
+ out += ' src="' + httpCache.utils.dataURI(resource) + '">';
+ } else {
+ // Explanation below uses [] to stand for <>.
+ // Inline scripts appear to work faster than data URIs on many OSes
+ // (e.g. Android 2.3.x, iOS 5, likely most of early 2013 device market)
+ //
+ // However, it is not safe to directly convert a remote script into an
+ // inline one. If there is a closing script tag inside the script,
+ // the script element will be closed prematurely.
+ //
+ // To guard against this, we need to prevent script element spillage.
+ // This is done by replacing [/script] with [/scr\ipt] inside script
+ // content. This transformation renders closing [/script] inert.
+ //
+ // The transformation is safe. There are three ways for a valid JS file
+ // to end up with a [/script] character sequence:
+ // * Inside a comment - safe to alter
+ // * Inside a string - replacing 'i' with '\i' changes nothing, as
+ // backslash in front of characters that need no escaping is ignored.
+ // * Inside a regular expression starting with '/script' - '\i' has no
+ // meaning inside regular expressions, either, so it is treated just
+ // like 'i' when expression is matched.
+ //
+ // Talk to Roman if you want to know more about this.
+ out += '>' + resource.body.replace(/(<\/scr)(ipt\s*>)/ig, '$1\\$2');
+ }
}
-
- // Firefox will choke on closing script tags passed through
- // the ark.
- document.write('<script src="' + url + '"><\/scr'+'ipt>');
+
+ document.write('<script ' + out + '<\/script>');
}
/**
@@ -301,7 +332,7 @@ var $ = Mobify.$
*/
, load: function(resources) {
var resource, i, save = false;
-
+
httpCache.load()
if (!resources) return;
@@ -330,8 +361,8 @@ var $ = Mobify.$
return encodeURIComponent(JSON.stringify(obj));
};
-$.fn.combineScripts = function() {
- return combineScripts.call(window, this)
+$.fn.combineScripts = function(opts) {
+ return combineScripts.call(window, this, opts)
}
Mobify.cssURL = function(obj) {
View
@@ -142,6 +142,12 @@ <h2 id="qunit-userAgent"></h2>
<iframe id="test-unmobify" src="fixtures/unmobify-basic.html"></iframe>
<a href="fixtures-unmobify/basic.html">BASIC</a>
+ <script id="test-mobify-combo-exec" type="text/test">
+/*<\/script>*/
+"<\/script>"
+/<\/script>/
+ </script>
+
<script>
var $ = Mobify.$
, httpCache = Mobify.httpCache
@@ -298,6 +304,51 @@ <h2 id="qunit-userAgent"></h2>
});
});
+ test('Mobify.combo.exec', 5, function() {
+ var cache = {
+ 'cached': {
+ 'headers': {'expires': UTC_TWO_WEEKS_FROM_NOW},
+ 'status': 'ready',
+ 'url': 'cached',
+ 'body': 'cached',
+ 'text': true
+ },
+ 'cached-with-scripts': {
+ 'headers': {'expires': UTC_TWO_WEEKS_FROM_NOW},
+ 'status': 'ready',
+ 'url': 'cached-with-scripts',
+ 'body': getText("#test-mobify-combo-exec"),
+ 'text': true
+ }
+ };
+
+ httpCache.reset(cache);
+
+ var nativeDocumentWrite = document.write
+ document.write = function(s) {
+ wrote = s
+ }
+
+ try {
+ Mobify.combo.exec('cached');
+ equal(wrote, '<script data-orig-src="cached">cached<\/script>');
+
+ Mobify.combo.exec('uncached');
+ equal(wrote, '<script src="uncached"><\/script>');
+
+ Mobify.combo.exec('cached-with-scripts');
+ equal(wrote, '<script data-orig-src=\"cached-with-scripts\">/*<\/scr\\ipt>*/ \"<\/scr\\ipt>\" \/<\/scr\\ipt>\/<\/script>');
+
+ Mobify.combo.exec('cached');
+ equal(wrote, '<script data-orig-src="cached">cached<\/script>');
+
+ Mobify.combo.exec('cached', true);
+ equal(wrote, '<script data-orig-src="cached" src="data:application/x-javascript,cached"><\/script>');
+ } finally {
+ document.write = nativeDocumentWrite;
+ }
+ });
+
asyncTest('httpCache - Eviction on an unused resource', 2, function() {
httpCache.reset();
httpCache.save();

0 comments on commit dfc5162

Please sign in to comment.